| // Copyright 2019 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:meta/meta.dart'; |
| |
| import 'bitfield64.dart'; |
| import 'heap.dart'; |
| import 'util.dart'; |
| import 'vmo_fields.dart'; |
| import 'vmo_holder.dart'; |
| |
| /// Mirrors a single block in the VMO. |
| /// |
| /// Can be read from VMO and/or initialized and modified by code, then |
| /// written to VMO if desired. |
| class Block { |
| final _header = Bitfield64(); |
| final _payloadBits = Bitfield64(); |
| |
| final VmoHolder _vmo; |
| |
| /// Index of the block within the VMO |
| final int index; |
| |
| /// Initializes an empty [BlockType.reserved] block that isn't in the VMO yet. |
| Block.create(this._vmo, this.index) { |
| _header |
| ..write(typeBits, BlockType.reserved.value) |
| ..write(orderBits, defaultBlockOrder); |
| } |
| |
| /// Create a block with arbitrary type. |
| @visibleForTesting |
| Block.createWithType(this._vmo, this.index, BlockType type) { |
| _header..write(typeBits, type.value)..write(orderBits, defaultBlockOrder); |
| } |
| |
| /// Initializes by reading the block from the VMO. |
| Block.read(this._vmo, this.index) { |
| // TODO(CF-603): Validate index. More validating of parameters everywhere. |
| _header.value = _vmo.readInt64(_offset); |
| _payloadBits.value = _vmo.readInt64(_payloadOffset); |
| } |
| |
| /// The block's payload as a string of bytes (for [BlockType.nameUtf8] or |
| /// [BlockType.extent]). |
| @visibleForTesting |
| ByteData get payloadBytes => |
| _vmo.read(_payloadOffset, size - headerSizeBytes); |
| |
| void _writeHeader() { |
| _vmo.writeInt64(_offset, _header.value); |
| } |
| |
| void _writePayloadBits() { |
| _vmo.writeInt64(_payloadOffset, _payloadBits.value); |
| } |
| |
| void _writeAllBits() { |
| _writeHeader(); |
| _writePayloadBits(); |
| } |
| |
| void _writePayloadBytes(ByteData bytes) { |
| _vmo.write(_payloadOffset, bytes); |
| } |
| |
| /// Initializes (the one and only) [BlockType.header] block. |
| void becomeHeader() { |
| _header |
| ..write(typeBits, BlockType.header.value) |
| ..write(orderBits, 0) |
| ..write(headerMagicBits, headerMagicNumber) |
| ..write(headerVersionBits, headerVersionNumber); |
| _payloadBits.value = 0; |
| _writeAllBits(); |
| } |
| |
| /// Start a VMO update. |
| /// |
| /// Only valid for the [BlockType.header] block; otherwise throws |
| /// [StateError]. |
| void lock() { |
| _checkType(BlockType.header); |
| _checkLocked(false); |
| _payloadBits.value++; |
| _vmo.writeInt64Direct(_payloadOffset, _payloadBits.value); |
| } |
| |
| /// Finish a VMO update. |
| /// Only valid for the [BlockType.header] block; otherwise throws |
| /// [StateError]. |
| void unlock() { |
| _checkType(BlockType.header); |
| _checkLocked(true); |
| _payloadBits.value++; |
| _vmo.writeInt64Direct(_payloadOffset, _payloadBits.value); |
| } |
| |
| /// Initializes the root [BlockType.nodeValue] block. |
| /// |
| /// Throws [StateError] if this block wasn't [BlockType.reserved]. |
| void becomeRoot() { |
| _checkType(BlockType.reserved); |
| becomeValue(parentIndex: rootParentIndex, nameIndex: rootNameIndex); |
| _header.write(orderBits, 0); |
| becomeNode(); |
| _writeAllBits(); |
| } |
| |
| /// Converts a [BlockType.anyValue] block to a [BlockType.nodeValue] block. |
| /// |
| /// Throws [StateError] if this block wasn't [BlockType.anyValue]. |
| void becomeNode() { |
| _checkType(BlockType.anyValue); |
| _header.write(typeBits, BlockType.nodeValue.value); |
| _payloadBits.value = 0; |
| _writeAllBits(); |
| } |
| |
| /// Child count of [BlockType.nodeValue] or [BlockType.tombstone] block. |
| /// |
| /// Throws [StateError] if this block isn't [BlockType.nodeValue] |
| /// or [BlockType.tombstone]. |
| set childCount(int value) { |
| _checkNodeOrTombstone(); |
| _payloadBits.value = value; |
| _writePayloadBits(); |
| } |
| |
| /// Child count of [BlockType.nodeValue] or [BlockType.tombstone] block. |
| /// |
| /// Throws [StateError] if this block isn't [BlockType.nodeValue] |
| /// or [BlockType.tombstone]. |
| int get childCount { |
| _checkNodeOrTombstone(); |
| return _payloadBits.value; |
| } |
| |
| /// Converts an anyValue block to a [BlockType.propertyValue] block. |
| /// |
| /// Throws [StateError] if this block wasn't [BlockType.anyValue]. |
| void becomeProperty() { |
| _checkType(BlockType.anyValue); |
| _header.write(typeBits, BlockType.propertyValue.value); |
| _payloadBits |
| ..write(propertyExtentIndexBits, 0) |
| ..write(propertyTotalLengthBits, 0) |
| ..write(propertyFlagsBits, 0); |
| _writeAllBits(); |
| } |
| |
| /// Total Length field of a [BlockType.propertyValue] block. |
| /// |
| /// Throws [StateError] if this block isn't [BlockType.propertyValue]. |
| int get propertyTotalLength { |
| _checkType(BlockType.propertyValue); |
| return _payloadBits.read(propertyTotalLengthBits); |
| } |
| |
| /// Total Length field of a [BlockType.propertyValue] block. |
| /// |
| /// Throws [StateError] if this block isn't [BlockType.propertyValue]. |
| set propertyTotalLength(int length) { |
| _checkType(BlockType.propertyValue); |
| _payloadBits.write(propertyTotalLengthBits, length); |
| _writePayloadBits(); |
| } |
| |
| /// Extent Index field of a [BlockType.propertyValue] block. |
| /// |
| /// Throws [StateError] if this block isn't [BlockType.propertyValue]. |
| int get propertyExtentIndex { |
| _checkType(BlockType.propertyValue); |
| return _payloadBits.read(propertyExtentIndexBits); |
| } |
| |
| /// Extent Index field of a [BlockType.propertyValue] block. |
| /// |
| /// Throws [StateError] if this block isn't [BlockType.propertyValue]. |
| set propertyExtentIndex(int index) { |
| _checkType(BlockType.propertyValue); |
| _payloadBits.write(propertyExtentIndexBits, index); |
| _writePayloadBits(); |
| } |
| |
| /// Flags field of a [BlockType.propertyValue] block. |
| /// |
| /// Throws [StateError] if this block isn't [BlockType.propertyValue]. |
| int get propertyFlags { |
| _checkType(BlockType.propertyValue); |
| return _payloadBits.read(propertyFlagsBits); |
| } |
| |
| /// Flags field of a [BlockType.propertyValue] block. |
| /// |
| /// Throws [StateError] if this block isn't [BlockType.propertyValue]. |
| set propertyFlags(int flags) { |
| _checkType(BlockType.propertyValue); |
| _payloadBits.write(propertyFlagsBits, flags); |
| _writePayloadBits(); |
| } |
| |
| /// Converts a [BlockType.nodeValue] block to a [BlockType.tombstone] block. |
| /// |
| /// Throws [StateError] if this block wasn't [BlockType.nodeValue]. |
| void becomeTombstone() { |
| _checkType(BlockType.nodeValue); |
| _header.write(typeBits, BlockType.tombstone.value); |
| _writeHeader(); |
| } |
| |
| /// Makes any block [BlockType.free]. |
| void becomeFree(int next) { |
| var orderValue = _header.read(orderBits); |
| _header |
| ..value = 0 |
| ..write(orderBits, orderValue) |
| ..write(typeBits, BlockType.free.value) |
| ..write(nextFreeBits, next); |
| _writeHeader(); |
| } |
| |
| /// Converts a [BlockType.free] to a [BlockType.reserved] block. |
| /// |
| /// Throws [StateError] if this block wasn't [BlockType.nodeValue]. |
| void becomeReserved() { |
| _checkType(BlockType.free); |
| _header.write(typeBits, BlockType.reserved.value); |
| _writeHeader(); |
| } |
| |
| /// Index of next-free-block. |
| /// |
| /// Throws [StateError] if block isn't [BlockType.free]. |
| int get nextFree { |
| _checkType(BlockType.free); |
| return _header.read(nextFreeBits); // _header.read(nextFreeBits); |
| } |
| |
| /// Initializes a value-holding block as [BlockType.anyValue]. |
| /// |
| /// Does not write it, because [BlockType.anyValue] is not part of VMO format. |
| /// This is a helper function called prior to [becomeNode()], |
| /// [becomeProperty()], and [becomeMetric()]. |
| /// |
| /// Throws [StateError] if block wasn't [BlockType.reserved]. |
| void becomeValue({@required int nameIndex, @required int parentIndex}) { |
| _checkType(BlockType.reserved); |
| _header |
| ..write(typeBits, BlockType.anyValue.value) |
| ..write(parentIndexBits, parentIndex) |
| ..write(nameIndexBits, nameIndex); |
| _writeHeader(); |
| } |
| |
| /// The index of the [BlockType.name] block of a *_VALUE block. |
| /// |
| /// Throws [StateError] if block isn't a value-holding block. |
| int get nameIndex { |
| _checkIsValue(); |
| return _header.read(nameIndexBits); |
| } |
| |
| /// The index of the parent [BlockType.nodeValue] block of a *_VALUE block. |
| /// |
| /// Throws [StateError] if block isn't a value-holding block. |
| int get parentIndex { |
| _checkIsValue(); |
| return _header.read(parentIndexBits); |
| } |
| |
| /// Converts a [BlockType.anyValue] block to a [BlockType.doubleValue] block, |
| /// and sets starting [value]. |
| /// |
| /// Throws [StateError] if block wasn't a [BlockType.anyValue] block. |
| void becomeDoubleMetric(double value) { |
| _checkType(BlockType.anyValue); |
| _header.write(typeBits, BlockType.doubleValue.value); |
| _payloadBits.value = _doubleBitsToInt(value); |
| _writeAllBits(); |
| } |
| |
| /// Converts a [BlockType.anyValue] block to a [BlockType.intValue] block, |
| /// and sets starting [value]. |
| /// |
| /// Throws [StateError] if block wasn't a [BlockType.anyValue] block. |
| void becomeIntMetric(int value) { |
| _checkType(BlockType.anyValue); |
| _header.write(typeBits, BlockType.intValue.value); |
| _payloadBits.value = value; |
| _writeAllBits(); |
| } |
| |
| /// Value payload stored in a [BlockType.intValue] block. |
| /// |
| /// Throws [StateError] if block isn't a [BlockType.intValue] block. |
| int get intValue { |
| _checkType(BlockType.intValue); |
| return _payloadBits.value; |
| } |
| |
| /// Writes int value payload to a [BlockType.intValue] block. |
| /// |
| /// Throws [StateError] if block isn't a [BlockType.intValue] block. |
| set intValue(int value) { |
| _checkType(BlockType.intValue); |
| _payloadBits.value = value; |
| _writePayloadBits(); |
| } |
| |
| /// Value payload stored in a [BlockType.doubleValue] block. |
| /// |
| /// Throws [StateError] if block isn't a [BlockType.doubleValue] block. |
| double get doubleValue { |
| _checkType(BlockType.doubleValue); |
| return _intBitsToDouble(_payloadBits.value); |
| } |
| |
| /// Write double value payload to a [BlockType.doubleValue] block. |
| /// |
| /// Throws [StateError] if block isn't a [BlockType.doubleValue] block. |
| set doubleValue(double value) { |
| _checkType(BlockType.doubleValue); |
| _payloadBits.value = _doubleBitsToInt(value); |
| _writePayloadBits(); |
| } |
| |
| /// Initializes a [BlockType.name] block. |
| /// |
| /// Throws [StateError] if block wasn't a [BlockType.reserved] block. |
| void becomeName(String name) { |
| _checkType(BlockType.reserved); |
| var stringBytes = toByteData(name, maxBytes: payloadSpaceBytes); |
| _header |
| ..write(typeBits, BlockType.nameUtf8.value) |
| ..write(nameLengthBits, stringBytes.lengthInBytes); |
| _writeHeader(); |
| _writePayloadBytes(stringBytes); |
| } |
| |
| /// Gets the utf8 name from a NAME block |
| @visibleForTesting |
| ByteData get nameUtf8 { |
| _checkType(BlockType.nameUtf8); |
| return payloadBytes; |
| } |
| |
| /// Adds a [BlockType.reserved] block to the head of a [BlockType.extent] |
| /// chain. |
| /// |
| /// Throws [StateError] if block wasn't a [BlockType.reserved] block. |
| void becomeExtent(int nextExtent) { |
| _checkType(BlockType.reserved); |
| _header |
| ..write(typeBits, BlockType.extent.value) |
| ..write(nextExtentBits, nextExtent); |
| _writeHeader(); |
| } |
| |
| /// Writes the [BlockType.extent]'s data. |
| /// |
| /// Throws [StateError] if block isn't a [BlockType.extent] block. |
| void setExtentPayload(ByteData data) { |
| _checkType(BlockType.extent); |
| _writePayloadBytes(data); |
| } |
| |
| /// The next [Block] in a [BlockType.extent] chain. |
| /// |
| /// Throws [StateError] if block isn't a [BlockType.extent] block. |
| int get nextExtent { |
| _checkType(BlockType.extent); |
| return _header.read(nextExtentBits); |
| } |
| |
| /// The number of bytes available for payload in this [BlockType.extent] |
| /// [Block]. |
| int get payloadSpaceBytes { |
| return size - headerSizeBytes; |
| } |
| |
| /// The VMO-format-defined type of this [Block]. |
| BlockType get type => BlockType.values[_header.read(typeBits)]; |
| |
| /// Size of the [Block] in bytes. |
| int get size => 1 << (_header.read(orderBits) + 4); |
| |
| /// Verifies this [Block] has the expected type; throws [StateError] if not. |
| void _checkType(BlockType blockType) { |
| if (type != blockType) { |
| throw StateError('Incorrect block type: ' |
| 'expected $blockType, but found $type.'); |
| } |
| } |
| |
| /// Throws [StateError] if [type] is not [BlockType.nodeValue] or |
| /// [BlockType.tombstone]. |
| void _checkNodeOrTombstone() { |
| if (type != BlockType.nodeValue && type != BlockType.tombstone) { |
| throw StateError('Incorrect block type: ' |
| 'expected node or tombstone, but found $type.'); |
| } |
| } |
| |
| /// Throws [StateError] if [type] is not a *_VALUE block, i.e. |
| /// [BlockType.nodeValue], [BlockType.anyValue], [BlockType.intValue], |
| /// [BlockType.doubleValue], or [BlockType.propertyValue]. |
| void _checkIsValue() { |
| if (type != BlockType.anyValue && |
| type != BlockType.nodeValue && |
| type != BlockType.propertyValue && |
| type != BlockType.intValue && |
| type != BlockType.doubleValue) { |
| throw StateError('Value block expected; this block is $type.'); |
| } |
| } |
| |
| /// This block (verified elsewhere to be the HEADER) "locks" the VMO during |
| /// updates: if the payload value is odd, the VMO contents are in flux, |
| /// and readers should retry. |
| void _checkLocked(bool locked) { |
| if ((_payloadBits.value & 1 == 1) != locked) { |
| throw StateError('Lock state error; expected locked = $locked.'); |
| } |
| } |
| |
| /// Converts 64 [bits] (supplied as int) to the [double] they really are. |
| double _intBitsToDouble(int bits) { |
| var scratchpad = ByteData(8)..setInt64(0, bits); |
| return scratchpad.getFloat64(0); |
| } |
| |
| /// Converts a [double] [value] to its 64-bit contents returned as [int]. |
| int _doubleBitsToInt(double value) { |
| var scratchpad = ByteData(8)..setFloat64(0, value); |
| return scratchpad.getInt64(0); |
| } |
| |
| /// The byte offset of this [Block] in the VMO (calculated from its [index]). |
| int get _offset => index * bytesPerIndex; |
| |
| /// The byte offset of this [Block]'s payload in the VMO. |
| int get _payloadOffset => _offset + headerSizeBytes; |
| } |