blob: 8ecdf1b0bfdfa177a96288fe868c7c52044d4e2d [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:typed_data';
import 'package:collection/collection.dart';
import '../../sledge_errors.dart';
import '../../uint8list_ops.dart';
import 'key_value.dart';
/// Class to compress long keys.
///
/// Instead of directly storing (key, value) pairs in Ledger, we store
/// ({hash(key)}, {|key|}{key}{value}).
/// {|key|} is an Uint64 and takes 8 bytes to store.
class Compressor {
static const _listEquality = const ListEquality();
final Map<Uint8List, Uint8List> _keyByHash = newUint8ListMap<Uint8List>();
/// Compress Key
Uint8List compressKey(Uint8List key) {
return _getAndSaveHashOfKey(key);
}
/// Compress KeyValue
KeyValue compressKeyInEntry(KeyValue entry) {
Uint8List newKey = compressKey(entry.key);
Uint8List newValue = concatListOfUint8Lists([
new Uint8List(8)..buffer.asByteData().setUint64(0, entry.key.length),
entry.key,
entry.value
]);
return new KeyValue(newKey, newValue);
}
/// Uncompress key.
Uint8List uncompressKey(Uint8List keyHash) {
final key = _keyByHash[keyHash];
if (key == null) {
throw new InternalSledgeError('Unable to uncompress key `$keyHash`.');
}
return key;
}
/// Uncompress KeyValue.
KeyValue uncompressKeyInEntry(KeyValue entry) {
if (entry.value.length < 8) {
throw new InternalSledgeError(
'In a hashed key mode, the value size must be '
'>= 8. Found ${entry.value.length} instead for entry `$entry`.');
}
final keyLength = entry.value.buffer.asByteData().getUint64(0);
if (entry.value.length < 8 + keyLength) {
throw new InternalSledgeError(
'Incorrect format for value of given entry: '
'The parsed length ($keyLength) is larger than the value content\'s '
'length (${entry.value.length - 8}). Entry: `$entry`');
}
final key = getSublistView(entry.value, start: 8, end: 8 + keyLength);
final value = getSublistView(entry.value, start: 8 + keyLength);
// TODO(nellyv): Remove this validation?
// Important side effect: result.key is added to _keyByHash.
final hash = _getAndSaveHashOfKey(key);
if (!_listEquality.equals(hash, entry.key)) {
throw new InternalSledgeError(
'Hash of parsed key is not equal to passed hash (expected `$hash`, got `${entry.key}`).');
}
return new KeyValue(key, value);
}
/// Returns hash of key, and adds (hash, key) pair to caching map.
Uint8List _getAndSaveHashOfKey(Uint8List key) {
// TODO: consider using more efficient hash function.
Uint8List keyHash = hash(key);
_keyByHash.putIfAbsent(keyHash, () => key);
return keyHash;
}
}