// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file // for details. 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:collection'; import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; /// Reader of lists of boolean values. /// /// The returned unmodifiable lists lazily read values on access. class BoolListReader extends Reader> { const BoolListReader(); @override int get size => 4; @override List read(BufferContext bc, int offset) => _FbBoolList(bc, bc.derefObject(offset)); } /// The reader of booleans. class BoolReader extends Reader { const BoolReader() : super(); @override int get size => 1; @override bool read(BufferContext bc, int offset) => bc._getInt8(offset) != 0; } /// Buffer with data and some context about it. class BufferContext { final ByteData _buffer; factory BufferContext.fromBytes(List byteList) { Uint8List uint8List = _asUint8List(byteList); ByteData buf = ByteData.view(uint8List.buffer, uint8List.offsetInBytes); return BufferContext._(buf); } BufferContext._(this._buffer); int derefObject(int offset) { return offset + _getUint32(offset); } Uint8List _asUint8LIst(int offset, int length) => _buffer.buffer.asUint8List(_buffer.offsetInBytes + offset, length); double _getFloat64(int offset) => _buffer.getFloat64(offset, Endian.little); int _getInt32(int offset) => _buffer.getInt32(offset, Endian.little); int _getInt8(int offset) => _buffer.getInt8(offset); int _getUint16(int offset) => _buffer.getUint16(offset, Endian.little); int _getUint32(int offset) => _buffer.getUint32(offset, Endian.little); int _getUint8(int offset) => _buffer.getUint8(offset); /// If the [byteList] is already a [Uint8List] return it. /// Otherwise return a [Uint8List] copy of the [byteList]. static Uint8List _asUint8List(List byteList) { if (byteList is Uint8List) { return byteList; } else { return Uint8List.fromList(byteList); } } } /// Class that helps building flat buffers. class Builder { final int initialSize; /// The list of field tails, reused by [_VTable] instances. final Int32List _reusedFieldTails = Int32List(1024); /// The list of field offsets, reused by [_VTable] instances. final Int32List _reusedFieldOffsets = Int32List(1024); /// The list of existing VTable(s). final List<_VTable> _vTables = <_VTable>[]; late ByteData _buf; /// The maximum alignment that has been seen so far. If [_buf] has to be /// reallocated in the future (to insert room at its start for more bytes) the /// reallocation will need to be a multiple of this many bytes. late int _maxAlign; /// The number of bytes that have been written to the buffer so far. The /// most recently written byte is this many bytes from the end of [_buf]. late int _tail; /// The location of the end of the current table, measured in bytes from the /// end of [_buf], or `null` if a table is not currently being built. late int _currentTableEndTail; _VTable? _currentVTable; /// Map containing all strings that have been written so far. This allows us /// to avoid duplicating strings. final Map> _strings = >{}; Builder({this.initialSize = 1024}) { reset(); } /// Add the [field] with the given boolean [value]. The field is not added if /// the [value] is equal to [def]. Booleans are stored as 8-bit fields with /// `0` for `false` and `1` for `true`. void addBool(int field, bool? value, [bool? def]) { _ensureCurrentVTable(); if (value != null && value != def) { int size = 1; _prepare(size, 1); _trackField(field); _buf.setInt8(_buf.lengthInBytes - _tail, value ? 1 : 0); } } /// Add the [field] with the given 64-bit float [value]. void addFloat64(int field, double? value, [double? def]) { _ensureCurrentVTable(); if (value != null && value != def) { int size = 8; _prepare(size, 1); _trackField(field); _setFloat64AtTail(_buf, _tail, value); } } /// Add the [field] with the given 32-bit signed integer [value]. The field /// is not added if the [value] is equal to [def]. void addInt32(int field, int? value, [int? def]) { _ensureCurrentVTable(); if (value != null && value != def) { int size = 4; _prepare(size, 1); _trackField(field); _setInt32AtTail(_buf, _tail, value); } } /// Add the [field] with the given 8-bit signed integer [value]. The field is /// not added if the [value] is equal to [def]. void addInt8(int field, int? value, [int? def]) { _ensureCurrentVTable(); if (value != null && value != def) { int size = 1; _prepare(size, 1); _trackField(field); _buf.setInt8(_buf.lengthInBytes - _tail, value); } } /// Add the [field] referencing an object with the given [offset]. void addOffset(int field, Offset? offset) { _ensureCurrentVTable(); if (offset != null) { _prepare(4, 1); _trackField(field); _setUint32AtTail(_buf, _tail, _tail - offset._tail); } } /// Add the [field] with the given 32-bit unsigned integer [value]. The field /// is not added if the [value] is equal to [def]. void addUint32(int field, int? value, [int? def]) { _ensureCurrentVTable(); if (value != null && value != def) { int size = 4; _prepare(size, 1); _trackField(field); _setUint32AtTail(_buf, _tail, value); } } /// Add the [field] with the given 8-bit unsigned integer [value]. The field /// is not added if the [value] is equal to [def]. void addUint8(int field, int? value, [int? def]) { _ensureCurrentVTable(); if (value != null && value != def) { int size = 1; _prepare(size, 1); _trackField(field); _setUint8AtTail(_buf, _tail, value); } } /// End the current table and return its offset. Offset endTable() { if (_currentVTable == null) { throw StateError('Start a table before ending it.'); } // Prepare for writing the VTable. _prepare(4, 1); int tableTail = _tail; // Prepare the size of the current table. _currentVTable!.tableSize = tableTail - _currentTableEndTail; // Prepare the VTable to use for the current table. int? vTableTail; { _currentVTable!.computeFieldOffsets(tableTail); // Try to find an existing compatible VTable. for (int i = 0; i < _vTables.length; i++) { _VTable vTable = _vTables[i]; if (_currentVTable!.canUseExistingVTable(vTable)) { vTableTail = vTable.tail; break; } } // Write a new VTable. if (vTableTail == null) { _currentVTable!.takeFieldOffsets(); _prepare(2, _currentVTable!.numOfUint16); vTableTail = _tail; _currentVTable!.tail = vTableTail; _currentVTable!.output(_buf, _buf.lengthInBytes - _tail); _vTables.add(_currentVTable!); } } // Set the VTable offset. _setInt32AtTail(_buf, tableTail, vTableTail - tableTail); // Done with this table. _currentVTable = null; return Offset(tableTail); } /// Finish off the creation of the buffer. The given [offset] is used as the /// root object offset, and usually references directly or indirectly every /// written object. If [fileIdentifier] is specified (and not `null`), it is /// interpreted as a 4-byte Latin-1 encoded string that should be placed at /// bytes 4-7 of the file. Uint8List finish(Offset offset, [String? fileIdentifier]) { _prepare(max(4, _maxAlign), fileIdentifier == null ? 1 : 2); int alignedTail = _tail + ((-_tail) % _maxAlign); _setUint32AtTail(_buf, alignedTail, alignedTail - offset._tail); if (fileIdentifier != null) { for (int i = 0; i < 4; i++) { _setUint8AtTail( _buf, alignedTail - 4 - i, fileIdentifier.codeUnitAt(i), ); } } return _buf.buffer.asUint8List(_buf.lengthInBytes - alignedTail); } /// This is a low-level method, it should not be invoked by clients. Uint8List lowFinish() { int alignedTail = _tail + ((-_tail) % _maxAlign); return _buf.buffer.asUint8List(_buf.lengthInBytes - alignedTail); } /// This is a low-level method, it should not be invoked by clients. void lowReset() { _buf = ByteData(initialSize); _maxAlign = 1; _tail = 0; } /// This is a low-level method, it should not be invoked by clients. void lowWriteUint32(int value) { _prepare(4, 1); _setUint32AtTail(_buf, _tail, value); } /// This is a low-level method, it should not be invoked by clients. void lowWriteUint8(int value) { _prepare(1, 1); _buf.setUint8(_buf.lengthInBytes - _tail, value); } /// Reset the builder and make it ready for filling a new buffer. void reset() { _buf = ByteData(initialSize); _maxAlign = 1; _tail = 0; _currentVTable = null; } /// Start a new table. Must be finished with [endTable] invocation. void startTable() { if (_currentVTable != null) { throw StateError('Inline tables are not supported.'); } _currentVTable = _VTable(_reusedFieldTails, _reusedFieldOffsets); _currentTableEndTail = _tail; } /// Write the given list of [values]. Offset writeList(List values) { _ensureNoVTable(); _prepare(4, 1 + values.length); Offset result = Offset(_tail); int tail = _tail; _setUint32AtTail(_buf, tail, values.length); tail -= 4; for (Offset value in values) { _setUint32AtTail(_buf, tail, tail - value._tail); tail -= 4; } return result; } /// Write the given list of boolean [values]. Offset writeListBool(List values) { int bitLength = values.length; int padding = (-bitLength) % 8; int byteLength = (bitLength + padding) ~/ 8; // Prepare the backing Uint8List. Uint8List bytes = Uint8List(byteLength + 1); // Record every bit. int byteIndex = 0; int byte = 0; int mask = 1; for (int bitIndex = 0; bitIndex < bitLength; bitIndex++) { if (bitIndex != 0 && (bitIndex % 8 == 0)) { bytes[byteIndex++] = byte; byte = 0; mask = 1; } if (values[bitIndex]) { byte |= mask; } mask <<= 1; } // Write the last byte, even if it may be on the padding. bytes[byteIndex] = byte; // Write the padding length. bytes[byteLength] = padding; // Write as a Uint8 list. return writeListUint8(bytes); } /// Write the given list of 64-bit float [values]. Offset writeListFloat64(List values) { _ensureNoVTable(); _prepare(8, 1 + values.length); Offset result = Offset(_tail); int tail = _tail; _setUint32AtTail(_buf, tail, values.length); tail -= 8; for (double value in values) { _setFloat64AtTail(_buf, tail, value); tail -= 8; } return result; } /// Write the given list of signed 32-bit integer [values]. Offset writeListInt32(List values) { _ensureNoVTable(); _prepare(4, 1 + values.length); Offset result = Offset(_tail); int tail = _tail; _setUint32AtTail(_buf, tail, values.length); tail -= 4; for (int value in values) { _setInt32AtTail(_buf, tail, value); tail -= 4; } return result; } /// Write the given list of unsigned 32-bit integer [values]. Offset writeListUint32(List values) { _ensureNoVTable(); _prepare(4, 1 + values.length); Offset result = Offset(_tail); int tail = _tail; _setUint32AtTail(_buf, tail, values.length); tail -= 4; for (int value in values) { _setUint32AtTail(_buf, tail, value); tail -= 4; } return result; } /// Write the given list of unsigned 8-bit integer [values]. Offset writeListUint8(List values) { _ensureNoVTable(); _prepare(4, 1, additionalBytes: values.length); Offset result = Offset(_tail); int tail = _tail; _setUint32AtTail(_buf, tail, values.length); tail -= 4; for (int value in values) { _setUint8AtTail(_buf, tail, value); tail -= 1; } return result; } /// Write the given string [value] and return its [Offset]. Offset writeString(String value) { _ensureNoVTable(); return _strings.putIfAbsent(value, () { // TODO(scheglov): optimize for ASCII strings var bytes = const Utf8Encoder().convert(value); int length = bytes.length; _prepare(4, 1, additionalBytes: length); Offset result = Offset(_tail); _setUint32AtTail(_buf, _tail, length); int offset = _buf.lengthInBytes - _tail + 4; for (int i = 0; i < length; i++) { _buf.setUint8(offset++, bytes[i]); } return result; }); } /// Throw an exception if there is not currently a vtable. void _ensureCurrentVTable() { if (_currentVTable == null) { throw StateError('Start a table before adding values.'); } } /// Throw an exception if there is currently a vtable. void _ensureNoVTable() { if (_currentVTable != null) { throw StateError( 'Cannot write a non-scalar value while writing a table.', ); } } /// Prepare for writing the given [count] of scalars of the given [size]. /// Additionally allocate the specified [additionalBytes]. Update the current /// tail pointer to point at the allocated space. void _prepare(int size, int count, {int additionalBytes = 0}) { // Update the alignment. if (_maxAlign < size) { _maxAlign = size; } // Prepare amount of required space. int dataSize = size * count + additionalBytes; int alignDelta = (-(_tail + dataSize)) % size; int bufSize = alignDelta + dataSize; // Ensure that we have the required amount of space. { int oldCapacity = _buf.lengthInBytes; if (_tail + bufSize > oldCapacity) { int desiredNewCapacity = (oldCapacity + bufSize) * 2; int deltaCapacity = desiredNewCapacity - oldCapacity; deltaCapacity += (-deltaCapacity) % _maxAlign; int newCapacity = oldCapacity + deltaCapacity; ByteData newBuf = ByteData(newCapacity); newBuf.buffer.asUint8List().setAll( deltaCapacity, _buf.buffer.asUint8List(), ); _buf = newBuf; } } // Update the tail pointer. _tail += bufSize; } /// Record the offset of the given [field]. void _trackField(int field) { _currentVTable!.addField(field, _tail); } static void _setFloat64AtTail(ByteData buf, int tail, double x) { buf.setFloat64(buf.lengthInBytes - tail, x, Endian.little); } static void _setInt32AtTail(ByteData buf, int tail, int x) { buf.setInt32(buf.lengthInBytes - tail, x, Endian.little); } static void _setUint32AtTail(ByteData buf, int tail, int x) { buf.setUint32(buf.lengthInBytes - tail, x, Endian.little); } static void _setUint8AtTail(ByteData buf, int tail, int x) { buf.setUint8(buf.lengthInBytes - tail, x); } } /// The reader of lists of 64-bit float values. /// /// The returned unmodifiable lists lazily read values on access. class Float64ListReader extends Reader> { const Float64ListReader(); @override int get size => 4; @override List read(BufferContext bc, int offset) => _FbFloat64List(bc, bc.derefObject(offset)); } /// The reader of 64-bit floats. class Float64Reader extends Reader { const Float64Reader() : super(); @override int get size => 8; @override double read(BufferContext bc, int offset) => bc._getFloat64(offset); } /// The reader of signed 32-bit integers. class Int32Reader extends Reader { const Int32Reader() : super(); @override int get size => 4; @override int read(BufferContext bc, int offset) => bc._getInt32(offset); } /// The reader of 8-bit signed integers. class Int8Reader extends Reader { const Int8Reader() : super(); @override int get size => 1; @override int read(BufferContext bc, int offset) => bc._getInt8(offset); } /// The reader of lists of objects. /// /// The returned unmodifiable lists lazily read objects on access. class ListReader extends Reader> { final Reader _elementReader; const ListReader(this._elementReader); @override int get size => 4; @override List read(BufferContext bc, int offset) => _FbGenericList(_elementReader, bc, bc.derefObject(offset)); } /// The offset from the end of the buffer to a serialized object of the type /// [T]. class Offset { final int _tail; Offset(this._tail); } /// Object that can read a value at a [BufferContext]. abstract class Reader { const Reader(); /// The size of the value in bytes. int get size; /// Read the value at the given [offset] in [bc]. T read(BufferContext bc, int offset); /// Read the value of the given [field] in the given [object]. T vTableGet(BufferContext object, int offset, int field, T defaultValue) { return vTableGetOrNull(object, offset, field) ?? defaultValue; } /// Read the value of the given [field] in the given [object]. T? vTableGetOrNull(BufferContext object, int offset, int field) { int vTableSOffset = object._getInt32(offset); int vTableOffset = offset - vTableSOffset; int vTableSize = object._getUint16(vTableOffset); int vTableFieldOffset = (1 + 1 + field) * 2; if (vTableFieldOffset < vTableSize) { int fieldOffsetInObject = object._getUint16( vTableOffset + vTableFieldOffset, ); if (fieldOffsetInObject != 0) { return read(object, offset + fieldOffsetInObject); } } return null; } } /// The reader of string values. class StringReader extends Reader { const StringReader() : super(); @override int get size => 4; @override String read(BufferContext bc, int offset) { int strOffset = bc.derefObject(offset); int length = bc._getUint32(strOffset); Uint8List bytes = bc._asUint8LIst(strOffset + 4, length); if (_isLatin(bytes)) { return String.fromCharCodes(bytes); } return utf8.decode(bytes); } static bool _isLatin(Uint8List bytes) { int length = bytes.length; for (int i = 0; i < length; i++) { if (bytes[i] > 127) { return false; } } return true; } } /// An abstract reader for tables. abstract class TableReader extends Reader { const TableReader(); @override int get size => 4; /// Return the object at [offset]. T createObject(BufferContext bc, int offset); @override T read(BufferContext bc, int offset) { int objectOffset = bc.derefObject(offset); return createObject(bc, objectOffset); } } /// Reader of lists of unsigned 32-bit integer values. /// /// The returned unmodifiable lists lazily read values on access. class Uint32ListReader extends Reader> { const Uint32ListReader(); @override int get size => 4; @override List read(BufferContext bc, int offset) => _FbUint32List(bc, bc.derefObject(offset)); } /// The reader of unsigned 32-bit integers. class Uint32Reader extends Reader { const Uint32Reader() : super(); @override int get size => 4; @override int read(BufferContext bc, int offset) => bc._getUint32(offset); } /// Reader of lists of unsigned 8-bit integer values. /// /// The returned unmodifiable lists lazily read values on access. class Uint8ListReader extends Reader> { const Uint8ListReader(); @override int get size => 4; @override List read(BufferContext bc, int offset) => _FbUint8List(bc, bc.derefObject(offset)); } /// The reader of unsigned 8-bit integers. class Uint8Reader extends Reader { const Uint8Reader() : super(); @override int get size => 1; @override int read(BufferContext bc, int offset) => bc._getUint8(offset); } /// List of booleans backed by 8-bit unsigned integers. class _FbBoolList with ListMixin implements List { final BufferContext bc; final int offset; int? _length; _FbBoolList(this.bc, this.offset); @override int get length { if (_length == null) { int byteLength = bc._getUint32(offset); _length = (byteLength - 1) * 8 - _getByte(byteLength - 1); } return _length!; } @override set length(int i) => throw StateError('Attempt to modify immutable list'); @override bool operator [](int i) { int index = i ~/ 8; int mask = 1 << i % 8; return _getByte(index) & mask != 0; } @override void operator []=(int i, bool e) => throw StateError('Attempt to modify immutable list'); int _getByte(int index) => bc._getUint8(offset + 4 + index); } /// The list backed by 64-bit values - Uint64 length and Float64. class _FbFloat64List extends _FbList { _FbFloat64List(super.bc, super.offset); @override double operator [](int i) { return bc._getFloat64(offset + 8 + 8 * i); } } /// List backed by a generic object which may have any size. class _FbGenericList extends _FbList { final Reader elementReader; List? _items; _FbGenericList(this.elementReader, BufferContext bp, int offset) : super(bp, offset); @override E operator [](int i) { _items ??= List.filled(length, null); E? item = _items![i]; if (item == null) { item = elementReader.read(bc, offset + 4 + elementReader.size * i); _items![i] = item; } return item!; } } /// The base class for immutable lists read from flat buffers. abstract class _FbList with ListMixin implements List { final BufferContext bc; final int offset; int? _length; _FbList(this.bc, this.offset); @override int get length { return _length ??= bc._getUint32(offset); } @override set length(int i) => throw StateError('Attempt to modify immutable list'); @override void operator []=(int i, E e) => throw StateError('Attempt to modify immutable list'); } /// List backed by 32-bit unsigned integers. class _FbUint32List extends _FbList { _FbUint32List(super.bc, super.offset); @override int operator [](int i) { return bc._getUint32(offset + 4 + 4 * i); } } /// List backed by 8-bit unsigned integers. class _FbUint8List extends _FbList { _FbUint8List(super.bc, super.offset); @override int operator [](int i) { return bc._getUint8(offset + 4 + i); } } /// Class that describes the structure of a table. class _VTable { final Int32List _reusedFieldTails; final Int32List _reusedFieldOffsets; /// The number of fields in [_reusedFieldTails]. int _fieldCount = 0; /// The private copy of [_reusedFieldOffsets], which is made only when we /// find that this table is unique. Int32List? _fieldOffsets; /// The size of the table that uses this VTable. int? tableSize; /// The tail of this VTable. It is used to share the same VTable between /// multiple tables of identical structure. int? tail; _VTable(this._reusedFieldTails, this._reusedFieldOffsets); int get numOfUint16 => 1 + 1 + _fieldCount; void addField(int field, int offset) { while (_fieldCount <= field) { _reusedFieldTails[_fieldCount++] = -1; } _reusedFieldTails[field] = offset; } /// Return `true` if the [existing] VTable can be used instead of this. bool canUseExistingVTable(_VTable existing) { assert(tail == null); assert(existing.tail != null); if (tableSize == existing.tableSize && _fieldCount == existing._fieldCount) { for (int i = 0; i < _fieldCount; i++) { if (_reusedFieldOffsets[i] != existing._fieldOffsets![i]) { return false; } } return true; } return false; } /// Fill the [_reusedFieldOffsets] field. void computeFieldOffsets(int tableTail) { for (int i = 0; i < _fieldCount; ++i) { int fieldTail = _reusedFieldTails[i]; _reusedFieldOffsets[i] = fieldTail == -1 ? 0 : tableTail - fieldTail; } } /// Outputs this VTable to [buf], which is expected to be aligned to 16-bit /// and have at least [numOfUint16] 16-bit words available. void output(ByteData buf, int bufOffset) { // VTable size. buf.setUint16(bufOffset, numOfUint16 * 2, Endian.little); bufOffset += 2; // Table size. buf.setUint16(bufOffset, tableSize!, Endian.little); bufOffset += 2; // Field offsets. for (int fieldOffset in _fieldOffsets!) { buf.setUint16(bufOffset, fieldOffset, Endian.little); bufOffset += 2; } } /// Fill the [_fieldOffsets] field. void takeFieldOffsets() { assert(_fieldOffsets == null); _fieldOffsets = Int32List(_fieldCount); for (int i = 0; i < _fieldCount; ++i) { _fieldOffsets![i] = _reusedFieldOffsets[i]; } } }