diff --git a/CHANGELOG.md b/CHANGELOG.md index 66187c2..32c1b6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ +# 6.0.0 + +**BREAKING CHANGES:** + +- **BinaryReader**: removed `peekByte()` — use `reader.getUint8(reader.offset)` instead +- **BinaryWriter**: removed `writeUint8At(int position, int value)` — use `writer[position] = value` or `setUint8(position, value)` instead + +**New Features:** + +- **BinaryReader**: added `getInt8(int position)` — read signed Int8 at arbitrary position without advancing offset +- **BinaryWriter**: added `getInt8(int position)` — read signed Int8 at arbitrary position +- **BinaryWriter**: added `setInt8(int position, int value)` — write signed Int8 at arbitrary position (range: -128..127) +- **BinaryWriter**: added `reserve(int count)` — reserve bytes for later backpatching, returns starting offset + +**Tests:** + +- Added 14 new tests + # 5.3.0 - **BinaryReader**: diff --git a/README.md b/README.md index 500845a..8e75b76 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Add `pro_binary` to your `pubspec.yaml` manually: ```yaml dependencies: - pro_binary: + pro_binary: any ``` Or add it using the command line: diff --git a/lib/src/binary_reader.dart b/lib/src/binary_reader.dart index 2412eb0..f884984 100644 --- a/lib/src/binary_reader.dart +++ b/lib/src/binary_reader.dart @@ -50,7 +50,26 @@ extension type BinaryReader._(_ReaderState _rs) { BinaryReader(Uint8List.fromList(buffer)); } -/// Core properties and operators for [BinaryReader]. +/// Core properties, operators, and lifecycle methods for [BinaryReader]. +/// +/// Provides access to the reader's state ([offset],[length],[availableBytes]), +/// random-access operator (`[]`), byte reading ([call]), and buffer rebinding +/// ([rebind]). +/// +/// Example: +/// ```dart +/// final reader = BinaryReader(Uint8List.fromList([0x00, 0x01, 0x02, 0x03])); +/// print(reader.offset); // 0 +/// print(reader[0]); // 0x00 +/// +/// reader.readUint32(); +/// print(reader.offset); // 4 +/// print(reader.availableBytes); // 0 +/// +/// // Rebind to new buffer +/// reader.rebind(Uint8List.fromList([0xFF])); +/// print(reader.offset); // 0 +/// ``` extension BinaryReaderCore on BinaryReader { /// Returns the number of bytes remaining to be read. @pragma('vm:prefer-inline') @@ -106,6 +125,20 @@ extension BinaryReaderCore on BinaryReader { } /// Variable-length integer reading methods for [BinaryReader]. +/// +/// Provides methods for reading VarInt and ZigZag encoded integers. +/// +/// **VarInt:** Uses 7 bits per byte for data, 1 bit as continuation flag. +/// Small values (0-127) fit in a single byte. +/// +/// **ZigZag:** Decodes signed integers: 0→0, 1→-1, 2→1, 3→-2, etc. +/// Ensures small absolute values (positive and negative) decode efficiently. +/// +/// Example: +/// ```dart +/// final a = reader.readVarUint(); // 1 byte for values 0-127 +/// final b = reader.readVarInt(); // Signed with ZigZag +/// ``` extension BinaryReaderVarInt on BinaryReader { /// Reads an unsigned variable-length integer encoded using VarInt format. /// @@ -232,6 +265,25 @@ extension BinaryReaderVarInt on BinaryReader { } /// Fixed-width numeric reading methods for [BinaryReader]. +/// +/// Reads primitive types at their natural, fixed size. All multi-byte +/// integers use configurable endianness (defaults to big-endian). +/// +/// **Type sizes:** +/// - `bool`: 1 byte (0 or 1) +/// - `Uint8`/`Int8`: 1 byte +/// - `Uint16`/`Int16`: 2 bytes +/// - `Uint32`/`Int32`: 4 bytes +/// - `Uint64`/`Int64`: 8 bytes +/// - `Float32`: 4 bytes (IEEE 754) +/// - `Float64`: 8 bytes (IEEE 754) +/// +/// Example: +/// ```dart +/// final version = reader.readUint8(); +/// final timestamp = reader.readUint32(); +/// final value = reader.readFloat64(Endian.little); +/// ``` extension BinaryReaderNumeric on BinaryReader { /// Reads an 8-bit unsigned integer (0-255). /// @@ -442,6 +494,33 @@ extension BinaryReaderNumeric on BinaryReader { } /// Byte array and string reading methods for [BinaryReader]. +/// +/// Provides methods for reading raw byte sequences and UTF-8 encoded strings, +/// with optional length prefixing for self-describing data. +/// +/// **Reading patterns:** +/// - Raw bytes: [readBytes], [readRemainingBytes] — direct byte extraction +/// - Length-prefixed: [readVarBytes] — VarUint length + data +/// - Strings: [readString] — UTF-8 encoded with specified byte length +/// - Length-prefixed strings: [readVarString] — VarUint length + UTF-8 +/// - Fixed-length strings: [readStringFixed] — length prefix with fixed size +/// +/// **Performance:** All methods use zero-copy buffer views where possible. +/// +/// Example: +/// ```dart +/// // Raw bytes +/// final header = reader.readBytes(4); +/// final remaining = reader.readRemainingBytes(); +/// +/// // Length-prefixed binary blob +/// final data = reader.readVarBytes(); +/// +/// // Strings +/// final byteLength = reader.readVarUint(); +/// final text = reader.readString(byteLength); +/// final varText = reader.readVarString(); +/// ``` extension BinaryReaderBytesString on BinaryReader { /// Reads a sequence of bytes and returns them as a [Uint8List]. /// @@ -632,34 +711,46 @@ extension BinaryReaderBytesString on BinaryReader { } } -/// Random access and position inspection methods for [BinaryReader]. +/// Random access read methods for [BinaryReader]. +/// +/// These methods allow reading from arbitrary positions in the buffer without +/// changing the current read position (offset). This is useful for inspecting +/// data at known offsets or peeking ahead. +/// +/// **Important:** Unlike regular read methods, these do NOT advance the read +/// position — they only read bytes at the specified `position`. +/// +/// **Read methods** (`get*`): return values at `position` without modification. +/// +/// Example: +/// ```dart +/// final reader = BinaryReader(Uint8List.fromList( +/// [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], +/// )); +/// +/// // Read at arbitrary position without advancing offset +/// print(reader.getUint32(0)); // 0x00010203 +/// print(reader.getUint32(4)); // 0x04050607 +/// +/// // Read same data multiple times +/// print(reader.getUint32(0)); // 0x00010203 +/// ``` extension BinaryReaderRandomAccess on BinaryReader { - /// Reads a byte at the specified [position] without changing the current - /// read position. + /// Reads a 8-bit unsigned integer at the specified [position] without + /// changing the current read position. /// /// Throws [RangeError] if [position] is negative or beyond the buffer. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - int getUint8(int position) { - assert(position >= 0 && position < _rs.length, 'position out of bounds'); - return _rs.list[position]; - } + int getUint8(int position) => _rs.data.getUint8(position); - /// Reads a 16-bit signed integer at the specified [position] without changing - /// the current read position. - /// - /// [endian] specifies byte order (defaults to big-endian). + /// Reads a 8-bit signed integer at the specified [position] without + /// changing the current read position. /// - /// Throws [RangeError] if [position] is negative or beyond `length - 1`. + /// Throws [RangeError] if [position] is negative or beyond the buffer. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - int getInt16(int position, [Endian endian = Endian.big]) { - assert( - position >= 0 && position + 2 <= _rs.length, - 'position out of bounds', - ); - return _rs.data.getInt16(position, endian); - } + int getInt8(int position) => _rs.data.getInt8(position); /// Reads a 16-bit unsigned integer at the specified [position] without /// changing the current read position. @@ -669,29 +760,19 @@ extension BinaryReaderRandomAccess on BinaryReader { /// Throws [RangeError] if [position] is negative or beyond `length - 1`. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - int getUint16(int position, [Endian endian = Endian.big]) { - assert( - position >= 0 && position + 2 <= _rs.length, - 'position out of bounds', - ); - return _rs.data.getUint16(position, endian); - } + int getUint16(int position, [Endian endian = Endian.big]) => + _rs.data.getUint16(position, endian); - /// Reads a 32-bit signed integer at the specified [position] without changing + /// Reads a 16-bit signed integer at the specified [position] without changing /// the current read position. /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond `length - 3`. + /// Throws [RangeError] if [position] is negative or beyond `length - 1`. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - int getInt32(int position, [Endian endian = Endian.big]) { - assert( - position >= 0 && position + 4 <= _rs.length, - 'position out of bounds', - ); - return _rs.data.getInt32(position, endian); - } + int getInt16(int position, [Endian endian = Endian.big]) => + _rs.data.getInt16(position, endian); /// Reads a 32-bit unsigned integer at the specified [position] without /// changing the current read position. @@ -701,29 +782,19 @@ extension BinaryReaderRandomAccess on BinaryReader { /// Throws [RangeError] if [position] is negative or beyond `length - 3`. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - int getUint32(int position, [Endian endian = Endian.big]) { - assert( - position >= 0 && position + 4 <= _rs.length, - 'position out of bounds', - ); - return _rs.data.getUint32(position, endian); - } + int getUint32(int position, [Endian endian = Endian.big]) => + _rs.data.getUint32(position, endian); - /// Reads a 64-bit signed integer at the specified [position] without changing + /// Reads a 32-bit signed integer at the specified [position] without changing /// the current read position. /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond `length - 7`. + /// Throws [RangeError] if [position] is negative or beyond `length - 3`. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - int getInt64(int position, [Endian endian = Endian.big]) { - assert( - position >= 0 && position + 8 <= _rs.length, - 'position out of bounds', - ); - return _rs.data.getInt64(position, endian); - } + int getInt32(int position, [Endian endian = Endian.big]) => + _rs.data.getInt32(position, endian); /// Reads a 64-bit unsigned integer at the specified [position] without /// changing the current read position. @@ -733,66 +804,41 @@ extension BinaryReaderRandomAccess on BinaryReader { /// Throws [RangeError] if [position] is negative or beyond `length - 7`. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - int getUint64(int position, [Endian endian = Endian.big]) { - assert( - position >= 0 && position + 8 <= _rs.length, - 'position out of bounds', - ); - return _rs.data.getUint64(position, endian); - } + int getUint64(int position, [Endian endian = Endian.big]) => + _rs.data.getUint64(position, endian); - /// Reads a 32-bit floating-point number at the specified [position] without - /// changing the current read position. + /// Reads a 64-bit signed integer at the specified [position] without changing + /// the current read position. /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond `length - 3`. + /// Throws [RangeError] if [position] is negative or beyond `length - 7`. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - double getFloat32(int position, [Endian endian = Endian.big]) { - assert( - position >= 0 && position + 4 <= _rs.length, - 'position out of bounds', - ); - return _rs.data.getFloat32(position, endian); - } + int getInt64(int position, [Endian endian = Endian.big]) => + _rs.data.getInt64(position, endian); - /// Reads a 64-bit floating-point number at the specified [position] without + /// Reads a 32-bit floating-point number at the specified [position] without /// changing the current read position. /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond `length - 7`. + /// Throws [RangeError] if [position] is negative or beyond `length - 3`. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - double getFloat64(int position, [Endian endian = Endian.big]) { - assert( - position >= 0 && position + 8 <= _rs.length, - 'position out of bounds', - ); - return _rs.data.getFloat64(position, endian); - } + double getFloat32(int position, [Endian endian = Endian.big]) => + _rs.data.getFloat32(position, endian); - /// Returns the byte at the current read position without advancing the - /// offset. + /// Reads a 64-bit floating-point number at the specified [position] without + /// changing the current read position. /// - /// This is a convenience method for peeking at the next byte to be read. + /// [endian] specifies byte order (defaults to big-endian). /// - /// Example: - /// ```dart - /// final nextByte = reader.peekByte(); - /// if (nextByte == 0x42) { - /// // Handle type 0x42 - /// } - /// final actualByte = reader.readUint8(); // Now read it - /// ``` + /// Throws [RangeError] if [position] is negative or beyond `length - 7`. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - int peekByte() { - _checkBounds(1, 'Peek Byte'); - - return _rs.list[_rs.offset]; - } + double getFloat64(int position, [Endian endian = Endian.big]) => + _rs.data.getFloat64(position, endian); /// Reads bytes without advancing the read position. /// @@ -837,6 +883,32 @@ extension BinaryReaderRandomAccess on BinaryReader { } /// Position management methods for [BinaryReader]. +/// +/// Controls the read position (offset) within the buffer and provides +/// utilities for navigating through binary data. +/// +/// **Key methods:** +/// - [hasBytes]: check if enough bytes are available +/// - [skip]: advance position without reading +/// - [seek]: jump to any position within buffer +/// - [rewind]: move backwards to re-read data +/// +/// Example: +/// ```dart +/// // Check and read conditionally +/// if (reader.hasBytes(4)) { +/// final value = reader.readUint32(); +/// } +/// +/// // Skip unknown data +/// reader.skip(100); +/// +/// // Jump to specific position +/// reader.seek(0); // Restart from beginning +/// +/// // Re-read previous data +/// reader.rewind(4); +/// ``` extension BinaryReaderPosition on BinaryReader { /// Checks if there are at least [length] bytes available to read. /// diff --git a/lib/src/binary_writer.dart b/lib/src/binary_writer.dart index e031741..0b9853e 100644 --- a/lib/src/binary_writer.dart +++ b/lib/src/binary_writer.dart @@ -41,7 +41,24 @@ extension type BinaryWriter._(_WriterState _ws) { : this._(_WriterState(initialBufferSize)); } -/// Core properties and operators for [BinaryWriter]. +/// Core properties, operators, and lifecycle methods for [BinaryWriter]. +/// +/// Provides access to the writer's state ([bytesWritten], [capacity]), +/// random-access operators (`[]`, `[]=`), byte extraction ([takeBytes], +/// [toBytes]), and reset functionality. +/// +/// Example: +/// ```dart +/// final writer = BinaryWriter(); +/// writer.writeUint32(42); +/// +/// print(writer.bytesWritten); // 4 +/// print(writer[0]); // 0x2A +/// +/// writer[0] = 0xFF; // Overwrite first byte +/// +/// final bytes = writer.takeBytes(); // Extract and reset +/// ``` extension BinaryWriterCore on BinaryWriter { /// Returns the total number of bytes written to the buffer. int get bytesWritten => _ws.offset; @@ -161,6 +178,24 @@ extension BinaryWriterCore on BinaryWriter { } /// Variable-length integer writing methods for [BinaryWriter]. +/// +/// Provides space-efficient encoding for integers that vary widely in +/// magnitude. Uses VarInt encoding for unsigned values and ZigZag + VarInt +/// for signed values. +/// +/// **VarInt:** Uses 7 bits per byte for data, 1 bit as continuation flag. +/// Small values (0-127) fit in a single byte. +/// +/// **ZigZag:** Maps signed integers to unsigned: 0→0, -1→1, 1→2, -2→3, etc. +/// Ensures small absolute values (positive and negative) encode efficiently. +/// +/// Example: +/// ```dart +/// writer.writeVarUint(42); // 1 byte +/// writer.writeVarUint(2000); // 2 bytes +/// writer.writeVarInt(-1); // 1 byte (ZigZag: -1 → 1) +/// writer.writeVarInt(1000000); // 3 bytes +/// ``` extension BinaryWriterVarInt on BinaryWriter { /// Writes an unsigned variable-length integer using VarInt encoding. /// @@ -281,6 +316,25 @@ extension BinaryWriterVarInt on BinaryWriter { } /// Fixed-width numeric writing methods for [BinaryWriter]. +/// +/// Writes primitive types at their natural, fixed size. All multi-byte +/// integers use configurable endianness (defaults to big-endian). +/// +/// **Type sizes:** +/// - `bool`: 1 byte (0 or 1) +/// - `Uint8`/`Int8`: 1 byte +/// - `Uint16`/`Int16`: 2 bytes +/// - `Uint32`/`Int32`: 4 bytes +/// - `Uint64`/`Int64`: 8 bytes +/// - `Float32`: 4 bytes (IEEE 754) +/// - `Float64`: 8 bytes (IEEE 754) +/// +/// Example: +/// ```dart +/// writer.writeBool(true); +/// writer.writeUint32(0x12345678); +/// writer.writeFloat64(3.14, Endian.little); +/// ``` extension BinaryWriterNumeric on BinaryWriter { /// Writes a boolean value as a single byte. /// @@ -311,9 +365,9 @@ extension BinaryWriterNumeric on BinaryWriter { @pragma('dart2js:tryInline') void writeUint8(int value) { _checkRange(value, 0, 255, 'Uint8'); - _ws.ensureOneByte(); - _ws.list[_ws.offset++] = value; + _ws.ensureOneByte(); + _ws.data.setUint8(_ws.offset++, value); } /// Writes an 8-bit signed integer (-128 to 127). @@ -328,9 +382,9 @@ extension BinaryWriterNumeric on BinaryWriter { @pragma('dart2js:tryInline') void writeInt8(int value) { _checkRange(value, -128, 127, 'Int8'); - _ws.ensureOneByte(); - _ws.list[_ws.offset++] = value & 0xFF; + _ws.ensureOneByte(); + _ws.data.setInt8(_ws.offset++, value); } /// Writes a 16-bit unsigned integer (0-65535). @@ -347,8 +401,8 @@ extension BinaryWriterNumeric on BinaryWriter { @pragma('dart2js:tryInline') void writeUint16(int value, [Endian endian = .big]) { _checkRange(value, 0, 65535, 'Uint16'); - _ws.ensureTwoBytes(); + _ws.ensureTwoBytes(); _ws.data.setUint16(_ws.offset, value, endian); _ws.offset += 2; } @@ -367,8 +421,8 @@ extension BinaryWriterNumeric on BinaryWriter { @pragma('dart2js:tryInline') void writeInt16(int value, [Endian endian = .big]) { _checkRange(value, -32768, 32767, 'Int16'); - _ws.ensureTwoBytes(); + _ws.ensureTwoBytes(); _ws.data.setInt16(_ws.offset, value, endian); _ws.offset += 2; } @@ -387,8 +441,8 @@ extension BinaryWriterNumeric on BinaryWriter { @pragma('dart2js:tryInline') void writeUint32(int value, [Endian endian = .big]) { _checkRange(value, 0, 4294967295, 'Uint32'); - _ws.ensureFourBytes(); + _ws.ensureFourBytes(); _ws.data.setUint32(_ws.offset, value, endian); _ws.offset += 4; } @@ -407,8 +461,8 @@ extension BinaryWriterNumeric on BinaryWriter { @pragma('dart2js:tryInline') void writeInt32(int value, [Endian endian = .big]) { _checkRange(value, -2147483648, 2147483647, 'Int32'); - _ws.ensureFourBytes(); + _ws.ensureFourBytes(); _ws.data.setInt32(_ws.offset, value, endian); _ws.offset += 4; } @@ -433,8 +487,8 @@ extension BinaryWriterNumeric on BinaryWriter { @pragma('dart2js:tryInline') void writeUint64(int value, [Endian endian = .big]) { _checkRange(value, 0, kMaxInt64, 'Uint64'); - _ws.ensureEightBytes(); + _ws.ensureEightBytes(); _ws.data.setUint64(_ws.offset, value, endian); _ws.offset += 8; } @@ -455,8 +509,8 @@ extension BinaryWriterNumeric on BinaryWriter { @pragma('dart2js:tryInline') void writeInt64(int value, [Endian endian = .big]) { _checkRange(value, kMinInt64, kMaxInt64, 'Int64'); - _ws.ensureEightBytes(); + _ws.ensureEightBytes(); _ws.data.setInt64(_ws.offset, value, endian); _ws.offset += 8; } @@ -473,7 +527,6 @@ extension BinaryWriterNumeric on BinaryWriter { @pragma('dart2js:tryInline') void writeFloat32(double value, [Endian endian = .big]) { _ws.ensureFourBytes(); - _ws.data.setFloat32(_ws.offset, value, endian); _ws.offset += 4; } @@ -490,13 +543,34 @@ extension BinaryWriterNumeric on BinaryWriter { @pragma('dart2js:tryInline') void writeFloat64(double value, [Endian endian = .big]) { _ws.ensureEightBytes(); - _ws.data.setFloat64(_ws.offset, value, endian); _ws.offset += 8; } } /// Byte array and string writing methods for [BinaryWriter]. +/// +/// Provides methods for writing raw byte sequences and UTF-8 encoded strings, +/// with optional length prefixing for self-describing data. +/// +/// **Writing patterns:** +/// - Raw bytes: [writeBytes] — direct byte copy +/// - Length-prefixed: [writeVarBytes] — VarUint length + data +/// - Strings: [writeString] — UTF-8 encoded +/// - Length-prefixed strings: [writeVarString] — VarUint length + UTF-8 +/// +/// Example: +/// ```dart +/// // Raw bytes +/// writer.writeBytes([1, 2, 3]); +/// +/// // Length-prefixed binary blob +/// writer.writeVarBytes(imageData); +/// +/// // Strings +/// writer.writeString('Hello'); +/// writer.writeVarString('Hello, 世界! 🌍'); +/// ``` extension BinaryWriterBytesString on BinaryWriter { /// Writes a sequence of bytes from the given list. /// @@ -844,185 +918,182 @@ extension BinaryWriterBytesString on BinaryWriter { } /// Random access read/write methods for [BinaryWriter]. +/// +/// These methods allow reading from and writing to arbitrary positions in the +/// buffer without changing the current write position (offset). This is useful +/// for inspecting already-written data or backpatching values at known offsets. +/// +/// **Important:** Unlike [seek], these methods do NOT advance the write +/// position +/// +/// — they only access/modify bytes at the specified `position`. +/// +/// **Read methods** (`get*`): return values at `position` without modification. +/// **Write methods** (`set*`): overwrite bytes at `position` without changing +/// the current offset. +/// +/// **Writing beyond [bytesWritten]:** If you write to a position beyond the +/// current `bytesWritten`, the data is stored in the buffer but will NOT be +/// included in `takeBytes()` or `toBytes()` output. To include such data, +/// advance the offset first using [seek]: +/// +/// Example: +/// ```dart +/// final writer = BinaryWriter(); +/// writer.writeUint32(0x12345678); // writes 4 bytes +/// +/// // Read at arbitrary position without advancing offset +/// print(writer.getUint32(0)); // 0x12345678 +/// +/// // Overwrite at arbitrary position +/// writer.setUint32(0, 0xDEADBEEF); +/// +/// // Write beyond bytesWritten — data stored but NOT in output +/// writer.setUint32(100, 0xABCDEF00); +/// writer.toBytes().length; // 4, position 100 is excluded +/// +/// // Fix: advance offset first, then write +/// writer.seek(100); +/// writer.setUint32(100, 0xABCDEF00); +/// writer.toBytes().length; // 104, position 100 is now included +/// ``` extension BinaryWriterRandomAccess on BinaryWriter { - /// Reads a byte at the specified [position] without changing the current - /// write position. + /// Reads an 8-bit unsigned value at the specified [position] without changing + /// the current write position. /// - /// Throws [RangeError] if [position] is negative or greater than or equal to - /// [bytesWritten]. + /// Throws [RangeError] if [position] is negative or if the position + size + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - int getUint8(int position) { - assert(position >= 0 && position < _ws.offset, 'position out of bounds'); - return _ws.list[position]; - } + int getUint8(int position) => _ws.data.getUint8(position); - /// Reads a 16-bit signed integer at the specified [position] without changing + /// Reads an 8-bit signed integer at the specified [position] without changing /// the current write position. /// - /// [endian] specifies byte order (defaults to big-endian). - /// - /// Throws [RangeError] if [position] is negative or beyond - /// `bytesWritten - 1`. + /// Throws [RangeError] if [position] is negative or if the position + size + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - int getInt16(int position, [Endian endian = Endian.big]) { - assert( - position >= 0 && position + 2 <= _ws.offset, - 'position out of bounds', - ); - return _ws.data.getInt16(position, endian); - } + int getInt8(int position) => _ws.data.getInt8(position); /// Reads a 16-bit unsigned integer at the specified [position] without /// changing the current write position. /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond - /// `bytesWritten - 1`. + /// Throws [RangeError] if [position] is negative or if `position + 2` + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - int getUint16(int position, [Endian endian = Endian.big]) { - assert( - position >= 0 && position + 2 <= _ws.offset, - 'position out of bounds', - ); - return _ws.data.getUint16(position, endian); - } + int getUint16(int position, [Endian endian = Endian.big]) => + _ws.data.getUint16(position, endian); - /// Reads a 32-bit signed integer at the specified [position] without changing + /// Reads a 16-bit signed integer at the specified [position] without changing /// the current write position. /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond - /// `bytesWritten - 3`. + /// Throws [RangeError] if [position] is negative or if `position + 2` + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - int getInt32(int position, [Endian endian = Endian.big]) { - assert( - position >= 0 && position + 4 <= _ws.offset, - 'position out of bounds', - ); - return _ws.data.getInt32(position, endian); - } + int getInt16(int position, [Endian endian = Endian.big]) => + _ws.data.getInt16(position, endian); /// Reads a 32-bit unsigned integer at the specified [position] without /// changing the current write position. /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond - /// `bytesWritten - 3`. + /// Throws [RangeError] if [position] is negative or if `position + 4` + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - int getUint32(int position, [Endian endian = Endian.big]) { - assert( - position >= 0 && position + 4 <= _ws.offset, - 'position out of bounds', - ); - return _ws.data.getUint32(position, endian); - } + int getUint32(int position, [Endian endian = Endian.big]) => + _ws.data.getUint32(position, endian); - /// Reads a 64-bit signed integer at the specified [position] without changing + /// Reads a 32-bit signed integer at the specified [position] without changing /// the current write position. /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond - /// `bytesWritten - 7`. + /// Throws [RangeError] if [position] is negative or if `position + 4` + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - int getInt64(int position, [Endian endian = Endian.big]) { - assert( - position >= 0 && position + 8 <= _ws.offset, - 'position out of bounds', - ); - return _ws.data.getInt64(position, endian); - } + int getInt32(int position, [Endian endian = Endian.big]) => + _ws.data.getInt32(position, endian); /// Reads a 64-bit unsigned integer at the specified [position] without /// changing the current write position. /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond - /// `bytesWritten - 7`. + /// Throws [RangeError] if [position] is negative or if `position + 8` + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - int getUint64(int position, [Endian endian = Endian.big]) { - assert( - position >= 0 && position + 8 <= _ws.offset, - 'position out of bounds', - ); - return _ws.data.getUint64(position, endian); - } + int getUint64(int position, [Endian endian = Endian.big]) => + _ws.data.getUint64(position, endian); + + /// Reads a 64-bit signed integer at the specified [position] without changing + /// the current write position. + /// + /// [endian] specifies byte order (defaults to big-endian). + /// + /// Throws [RangeError] if [position] is negative or if `position + 8` + /// exceeds the buffer [capacity]. + @pragma('vm:prefer-inline') + @pragma('dart2js:tryInline') + int getInt64(int position, [Endian endian = Endian.big]) => + _ws.data.getInt64(position, endian); /// Reads a 32-bit floating-point number at the specified [position] without /// changing the current write position. /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond - /// `bytesWritten - 3`. + /// Throws [RangeError] if [position] is negative or if `position + 4` + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - double getFloat32(int position, [Endian endian = Endian.big]) { - assert( - position >= 0 && position + 4 <= _ws.offset, - 'position out of bounds', - ); - return _ws.data.getFloat32(position, endian); - } + double getFloat32(int position, [Endian endian = Endian.big]) => + _ws.data.getFloat32(position, endian); /// Reads a 64-bit floating-point number at the specified [position] without /// changing the current write position. /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond - /// `bytesWritten - 7`. + /// Throws [RangeError] if [position] is negative or if `position + 8` + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - double getFloat64(int position, [Endian endian = Endian.big]) { - assert( - position >= 0 && position + 8 <= _ws.offset, - 'position out of bounds', - ); - return _ws.data.getFloat64(position, endian); - } + double getFloat64(int position, [Endian endian = Endian.big]) => + _ws.data.getFloat64(position, endian); - /// Writes a byte at the specified [position] without changing the current - /// write position. + /// Writes an 8-bit unsigned value at the specified [position] without + /// changing the current write position. /// - /// Throws [RangeError] if [position] is negative or greater than or equal to - /// [bytesWritten]. + /// Throws [RangeError] if [position] is negative or if the position + size + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') void setUint8(int position, int value) { - if (position < 0 || position >= _ws.offset) { - throw RangeError.range(position, 0, _ws.offset - 1, 'position'); - } _checkRange(value, 0, 255, 'Uint8'); - _ws.list[position] = value; + _ws.data.setUint8(position, value); } - /// Writes a 16-bit signed integer at the specified [position] without - /// changing the current write position. - /// - /// [endian] specifies byte order (defaults to big-endian). + /// Writes an 8-bit signed value at the specified [position] without changing + /// the current write position. /// - /// Throws [RangeError] if [position] is negative or beyond - /// `bytesWritten - 1`. + /// Throws [RangeError] if [position] is negative or if the position + size + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - void setInt16(int position, int value, [Endian endian = Endian.big]) { - if (position < 0 || position + 2 > _ws.offset) { - throw RangeError.range(position, 0, _ws.offset - 2, 'position'); - } - _checkRange(value, -32768, 32767, 'Int16'); - _ws.data.setInt16(position, value, endian); + void setInt8(int position, int value) { + _checkRange(value, -128, 127, 'Int8'); + _ws.data.setInt8(position, value); } /// Writes a 16-bit unsigned integer at the specified [position] without @@ -1030,33 +1101,27 @@ extension BinaryWriterRandomAccess on BinaryWriter { /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond - /// `bytesWritten - 1`. + /// Throws [RangeError] if [position] is negative or if `position + 2` + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') void setUint16(int position, int value, [Endian endian = Endian.big]) { - if (position < 0 || position + 2 > _ws.offset) { - throw RangeError.range(position, 0, _ws.offset - 2, 'position'); - } _checkRange(value, 0, 65535, 'Uint16'); _ws.data.setUint16(position, value, endian); } - /// Writes a 32-bit signed integer at the specified [position] without + /// Writes a 16-bit signed integer at the specified [position] without /// changing the current write position. /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond - /// `bytesWritten - 3`. + /// Throws [RangeError] if [position] is negative or if `position + 2` + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - void setInt32(int position, int value, [Endian endian = Endian.big]) { - if (position < 0 || position + 4 > _ws.offset) { - throw RangeError.range(position, 0, _ws.offset - 4, 'position'); - } - _checkRange(value, -2147483648, 2147483647, 'Int32'); - _ws.data.setInt32(position, value, endian); + void setInt16(int position, int value, [Endian endian = Endian.big]) { + _checkRange(value, -32768, 32767, 'Int16'); + _ws.data.setInt16(position, value, endian); } /// Writes a 32-bit unsigned integer at the specified [position] without @@ -1064,33 +1129,27 @@ extension BinaryWriterRandomAccess on BinaryWriter { /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond - /// `bytesWritten - 3`. + /// Throws [RangeError] if [position] is negative or if `position + 4` + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') void setUint32(int position, int value, [Endian endian = Endian.big]) { - if (position < 0 || position + 4 > _ws.offset) { - throw RangeError.range(position, 0, _ws.offset - 4, 'position'); - } _checkRange(value, 0, 4294967295, 'Uint32'); _ws.data.setUint32(position, value, endian); } - /// Writes a 64-bit signed integer at the specified [position] without + /// Writes a 32-bit signed integer at the specified [position] without /// changing the current write position. /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond - /// `bytesWritten - 7`. + /// Throws [RangeError] if [position] is negative or if `position + 4` + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') - void setInt64(int position, int value, [Endian endian = Endian.big]) { - if (position < 0 || position + 8 > _ws.offset) { - throw RangeError.range(position, 0, _ws.offset - 8, 'position'); - } - _checkRange(value, kMinInt64, kMaxInt64, 'Int64'); - _ws.data.setInt64(position, value, endian); + void setInt32(int position, int value, [Endian endian = Endian.big]) { + _checkRange(value, -2147483648, 2147483647, 'Int32'); + _ws.data.setInt32(position, value, endian); } /// Writes a 64-bit unsigned integer at the specified [position] without @@ -1098,31 +1157,39 @@ extension BinaryWriterRandomAccess on BinaryWriter { /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond - /// `bytesWritten - 7`. + /// Throws [RangeError] if [position] is negative or if `position + 8` + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') void setUint64(int position, int value, [Endian endian = Endian.big]) { - if (position < 0 || position + 8 > _ws.offset) { - throw RangeError.range(position, 0, _ws.offset - 8, 'position'); - } _checkRange(value, 0, kMaxInt64, 'Uint64'); _ws.data.setUint64(position, value, endian); } + /// Writes a 64-bit signed integer at the specified [position] without + /// changing the current write position. + /// + /// [endian] specifies byte order (defaults to big-endian). + /// + /// Throws [RangeError] if [position] is negative or if `position + 8` + /// exceeds the buffer [capacity]. + @pragma('vm:prefer-inline') + @pragma('dart2js:tryInline') + void setInt64(int position, int value, [Endian endian = Endian.big]) { + _checkRange(value, kMinInt64, kMaxInt64, 'Int64'); + _ws.data.setInt64(position, value, endian); + } + /// Writes a 32-bit floating-point number at the specified [position] without /// changing the current write position. /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond - /// `bytesWritten - 3`. + /// Throws [RangeError] if [position] is negative or if `position + 4` + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') void setFloat32(int position, double value, [Endian endian = Endian.big]) { - if (position < 0 || position + 4 > _ws.offset) { - throw RangeError.range(position, 0, _ws.offset - 4, 'position'); - } _ws.data.setFloat32(position, value, endian); } @@ -1131,41 +1198,44 @@ extension BinaryWriterRandomAccess on BinaryWriter { /// /// [endian] specifies byte order (defaults to big-endian). /// - /// Throws [RangeError] if [position] is negative or beyond - /// `bytesWritten - 7`. + /// Throws [RangeError] if [position] is negative or if `position + 8` + /// exceeds the buffer [capacity]. @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') void setFloat64(int position, double value, [Endian endian = Endian.big]) { - if (position < 0 || position + 8 > _ws.offset) { - throw RangeError.range(position, 0, _ws.offset - 8, 'position'); - } _ws.data.setFloat64(position, value, endian); } - - /// Writes a byte at the specified [position] without changing the current - /// write position. - /// - /// Used to overwrite data at a previously written offset (e.g., - /// updating a length field). - /// - /// This is a functional alias for `operator []=`. - /// - /// Throws [RangeError] if [position] is negative or greater than or equal to - /// [bytesWritten]. - /// - /// Example: - /// ```dart - /// writer.writeUint32(10); // Write length placeholder - /// writer.writeString('data'); - /// writer.writeUint8At(0, 15); // Overwrite length at position 0 - /// // or: writer[0] = 15; - /// ``` - @pragma('vm:prefer-inline') - @pragma('dart2js:tryInline') - void writeUint8At(int position, int value) => this[position] = value; } /// Position management methods for [BinaryWriter]. +/// +/// Controls the write position (offset) within the buffer and provides +/// utilities for the "Reserve & Backpatch" pattern. +/// +/// **Key methods:** +/// - [seek]: move to any position within written bytes +/// - [skip]: advance position without writing (reserve space) +/// - [reserve]: reserve space and return starting position +/// - [shiftBytes]: compact buffer by shifting payload left +/// +/// **Reserve & Backpatch pattern:** +/// ```dart +/// final headerPos = writer.reserve(8); // Reserve 8 bytes for header +/// writer.writeUint32(0x12345678); // Write payload +/// +/// // Backpatch header at reserved position +/// writer.seek(headerPos); +/// writer.writeUint32(0xCAFEBABE); +/// ``` +/// +/// **Compacting reserved space:** +/// ```dart +/// final headerPos = writer.reserve(8); +/// writer.writeUint32(0x11111111); +/// writer.writeUint32(0x22222222); +/// // Shift payload left to overwrite unused reserved space +/// writer.shiftBytes(headerPos + 2, writer.bytesWritten, 0); +/// ``` extension BinaryWriterPosition on BinaryWriter { /// Sets the write position to the specified byte offset. /// @@ -1214,6 +1284,17 @@ extension BinaryWriterPosition on BinaryWriter { ..offset += count; } + /// Reserves [count] bytes for later backpatching and returns the starting + /// offset of the reserved block. + @pragma('vm:prefer-inline') + @pragma('dart2js:tryInline') + int reserve(int count) { + final currentOffset = _ws.offset; + skip(count); + + return currentOffset; + } + /// Shifts a block of written bytes within the buffer. /// /// Used for the "Reserve & Backpatch" pattern when the reserved header space diff --git a/test/unit/binary_reader_edge_cases_test.dart b/test/unit/binary_reader_edge_cases_test.dart index 5973a1d..df52b6f 100644 --- a/test/unit/binary_reader_edge_cases_test.dart +++ b/test/unit/binary_reader_edge_cases_test.dart @@ -66,13 +66,5 @@ void main() { expect(reader.readUint8(), equals(1)); }); }); - - group('Internal buffer access', () { - test('peekByte returns byte without advancing offset', () { - final reader = BinaryReader(Uint8List.fromList([0x42, 0x43])); - expect(reader.peekByte(), equals(0x42)); - expect(reader.offset, equals(0)); - }); - }); }); } diff --git a/test/unit/binary_reader_get_test.dart b/test/unit/binary_reader_get_test.dart index 5096258..4e843d6 100644 --- a/test/unit/binary_reader_get_test.dart +++ b/test/unit/binary_reader_get_test.dart @@ -28,11 +28,47 @@ void main() { }); test('throws for negative position', () { - expect(() => reader.getUint8(-1), throwsA(isA())); + expect(() => reader.getUint8(-1), throwsRangeError); }); test('throws for position at end', () { - expect(() => reader.getUint8(32), throwsA(isA())); + expect(() => reader.getUint8(32), throwsRangeError); + }); + }); + + group('getInt8', () { + test('reads signed Int8 byte at position', () { + final buffer = Uint8List.fromList([0x80, 0x7F, 0x00, 0xFF, 0x01]); + final reader = BinaryReader(buffer); + expect(reader.getInt8(0), equals(-128)); + expect(reader.getInt8(1), equals(127)); + expect(reader.getInt8(2), equals(0)); + expect(reader.getInt8(3), equals(-1)); + expect(reader.getInt8(4), equals(1)); + }); + + test('does not change offset', () { + reader + ..seek(5) + ..getInt8(0); + expect(reader.offset, equals(5)); + }); + + test('throws for negative position', () { + expect(() => reader.getInt8(-1), throwsRangeError); + }); + + test('throws for position at end', () { + expect(() => reader.getInt8(32), throwsRangeError); + }); + + test('signed round-trip', () { + final buffer = Uint8List.fromList([0xFF, 0x80, 0x00, 0x7F]); + final reader = BinaryReader(buffer); + expect(reader.getInt8(0), equals(-1)); + expect(reader.getInt8(1), equals(-128)); + expect(reader.getInt8(2), equals(0)); + expect(reader.getInt8(3), equals(127)); }); }); @@ -74,11 +110,11 @@ void main() { test('throws for position out of bounds', () { // 32-byte buffer: getUint16(31) needs bytes 31-32, but 32 is out of // bounds - expect(() => reader.getUint16(31), throwsA(isA())); + expect(() => reader.getUint16(31), throwsRangeError); }); test('throws for negative position', () { - expect(() => reader.getUint16(-1), throwsA(isA())); + expect(() => reader.getUint16(-1), throwsRangeError); }); }); @@ -131,11 +167,11 @@ void main() { test('throws for position out of bounds', () { // 32-byte buffer: getUint32(29) needs bytes 29-32, but 32 is out of // bounds - expect(() => reader.getUint32(29), throwsA(isA())); + expect(() => reader.getUint32(29), throwsRangeError); }); test('throws for negative position', () { - expect(() => reader.getUint32(-1), throwsA(isA())); + expect(() => reader.getUint32(-1), throwsRangeError); }); }); @@ -210,11 +246,11 @@ void main() { test('throws for position out of bounds', () { // 32-byte buffer: getUint64(25) needs bytes 25-32, but 32 is out of // bounds - expect(() => reader.getUint64(25), throwsA(isA())); + expect(() => reader.getUint64(25), throwsRangeError); }); test('throws for negative position', () { - expect(() => reader.getUint64(-1), throwsA(isA())); + expect(() => reader.getUint64(-1), throwsRangeError); }); }); @@ -280,13 +316,13 @@ void main() { test('throws for position out of bounds', () { // 32-byte buffer - expect(() => reader.getFloat32(29), throwsA(isA())); - expect(() => reader.getFloat64(25), throwsA(isA())); + expect(() => reader.getFloat32(29), throwsRangeError); + expect(() => reader.getFloat64(25), throwsRangeError); }); test('throws for negative position', () { - expect(() => reader.getFloat32(-1), throwsA(isA())); - expect(() => reader.getFloat64(-1), throwsA(isA())); + expect(() => reader.getFloat32(-1), throwsRangeError); + expect(() => reader.getFloat64(-1), throwsRangeError); }); }); @@ -352,14 +388,14 @@ void main() { test('read from empty buffer', () { final emptyBuffer = Uint8List(0); final emptyReader = BinaryReader(emptyBuffer); - expect(() => emptyReader.getUint8(0), throwsA(isA())); + expect(() => emptyReader.getUint8(0), throwsRangeError); }); test('read from single byte buffer', () { final buffer = Uint8List.fromList([0x42]); final reader = BinaryReader(buffer); expect(reader.getUint8(0), equals(0x42)); - expect(() => reader.getUint16(0), throwsA(isA())); + expect(() => reader.getUint16(0), throwsRangeError); }); test('multiple reads at same position', () { diff --git a/test/unit/binary_writer_navigation_test.dart b/test/unit/binary_writer_navigation_test.dart index 947157a..dcb2992 100644 --- a/test/unit/binary_writer_navigation_test.dart +++ b/test/unit/binary_writer_navigation_test.dart @@ -35,34 +35,6 @@ void main() { }); }); - group('writeUint8At', () { - test('overwrites byte at middle position', () { - writer - ..writeUint8(1) - ..writeUint8(2) - ..writeUint8(3) - ..writeUint8At(1, 99); - expect(writer.toBytes(), equals([1, 99, 3])); - expect(writer.bytesWritten, equals(3)); - }); - - test('does not change current write position', () { - writer - ..writeUint8(1) - ..writeUint8(2) - ..writeUint8At(0, 99) - ..writeUint8(3); - - expect(writer.toBytes(), equals([99, 2, 3])); - expect(writer.bytesWritten, equals(3)); - }); - - test('throws for position at the end', () { - writer.writeUint8(1); - expect(() => writer.writeUint8At(1, 99), throwsRangeError); - }); - }); - group('index operators', () { test('operator [] returns byte at absolute position', () { writer diff --git a/test/unit/binary_writer_set_get_test.dart b/test/unit/binary_writer_set_get_test.dart index a2a3daa..9719626 100644 --- a/test/unit/binary_writer_set_get_test.dart +++ b/test/unit/binary_writer_set_get_test.dart @@ -33,12 +33,7 @@ void main() { test('throws for negative position', () { writer.writeUint8(1); - expect(() => writer.getUint8(-1), throwsA(isA())); - }); - - test('throws for position at end', () { - writer.writeUint8(1); - expect(() => writer.getUint8(1), throwsA(isA())); + expect(() => writer.getUint8(-1), throwsRangeError); }); }); @@ -66,11 +61,6 @@ void main() { expect(() => writer.setUint8(-1, 1), throwsRangeError); }); - test('throws for position at end', () { - writer.writeUint8(1); - expect(() => writer.setUint8(1, 1), throwsRangeError); - }); - test('throws for value out of range', () { writer ..writeUint8(1) @@ -89,6 +79,35 @@ void main() { }); }); + group('getInt8 / setInt8', () { + test('round-trip signed values', () { + writer + ..writeInt8(127) + ..writeInt8(-1) + ..writeInt8(-128) + ..writeInt8(0); + expect(writer.getInt8(0), equals(127)); + expect(writer.getInt8(1), equals(-1)); + expect(writer.getInt8(2), equals(-128)); + expect(writer.getInt8(3), equals(0)); + }); + + test('does not change offset', () { + writer + ..writeInt8(42) + ..writeInt8(99); + final offset = writer.bytesWritten; + writer.getInt8(0); + expect(writer.bytesWritten, equals(offset)); + }); + + test('throws for negative position', () { + writer.writeInt8(1); + expect(() => writer.getInt8(-1), throwsRangeError); + expect(() => writer.setInt8(-1, 1), throwsRangeError); + }); + }); + group('getInt16 / setInt16', () { test('round-trip big-endian', () { writer @@ -118,22 +137,11 @@ void main() { expect(writer.bytesWritten, equals(4)); }); - test('throws for position out of bounds', () { - writer.writeInt16(100); - expect(() => writer.getInt16(1), throwsA(isA())); - expect(() => writer.setInt16(1, 100), throwsRangeError); - }); - - test('throws for negative position', () { - writer.writeInt16(100); - expect(() => writer.getInt16(-1), throwsA(isA())); - expect(() => writer.setInt16(-1, 100), throwsRangeError); - }); - test('throws for value out of range', () { writer ..writeInt16(1) ..writeInt16(2); + expect(() => writer.setInt16(0, 32768), throwsRangeError); expect(() => writer.setInt16(0, -32769), throwsRangeError); }); @@ -194,12 +202,6 @@ void main() { expect(writer.bytesWritten, equals(8)); }); - test('throws for position out of bounds', () { - writer.writeInt32(100); - expect(() => writer.getInt32(3), throwsA(isA())); - expect(() => writer.setInt32(3, 100), throwsRangeError); - }); - test('throws for value out of range', () { writer ..writeInt32(1) @@ -264,12 +266,6 @@ void main() { expect(writer.bytesWritten, equals(16)); }); - test('throws for position out of bounds', () { - writer.writeInt64(100); - expect(() => writer.getInt64(7), throwsA(isA())); - expect(() => writer.setInt64(7, 100), throwsRangeError); - }); - test('throws for value out of range', () { writer ..writeInt64(1) @@ -335,12 +331,6 @@ void main() { expect(writer.getFloat32(4), closeTo(2.5, 0.001)); expect(writer.bytesWritten, equals(8)); }); - - test('throws for position out of bounds', () { - writer.writeFloat32(1); - expect(() => writer.getFloat32(3), throwsA(isA())); - expect(() => writer.setFloat32(3, 1), throwsRangeError); - }); }); group('getFloat64 / setFloat64', () { @@ -372,12 +362,6 @@ void main() { expect(writer.getFloat64(8), closeTo(2.5, 0.00000000000001)); expect(writer.bytesWritten, equals(16)); }); - - test('throws for position out of bounds', () { - writer.writeFloat64(1); - expect(() => writer.getFloat64(7), throwsA(isA())); - expect(() => writer.setFloat64(7, 1), throwsRangeError); - }); }); group('use case: backpatch pattern', () { diff --git a/test/unit/binary_writer_skip_shift_test.dart b/test/unit/binary_writer_skip_shift_test.dart index afa4878..8c5284d 100644 --- a/test/unit/binary_writer_skip_shift_test.dart +++ b/test/unit/binary_writer_skip_shift_test.dart @@ -65,6 +65,74 @@ void main() { }); }); + group('BinaryWriter reserve', () { + late BinaryWriter writer; + + setUp(() { + writer = BinaryWriter(); + }); + + test('reserve returns starting offset', () { + writer.writeUint32(0xDEADBEEF); + final pos = writer.reserve(8); + expect(pos, equals(4)); + }); + + test('reserve advances bytesWritten', () { + writer.writeUint32(0xDEADBEEF); + final before = writer.bytesWritten; + final pos = writer.reserve(8); + expect(writer.bytesWritten, equals(before + 8)); + expect(pos, equals(before)); + }); + + test('reserved bytes can be backpatched', () { + final headerPos = writer.reserve(4); + writer + ..writeUint32(0x12345678) + ..seek(headerPos) + ..writeUint32(0xABCDEF00); + expect(writer[headerPos], equals(0xAB)); + expect(writer[headerPos + 1], equals(0xCD)); + expect(writer[headerPos + 2], equals(0xEF)); + expect(writer[headerPos + 3], equals(0x00)); + }); + + test('reserve multiple blocks', () { + final pos1 = writer.reserve(4); + final pos2 = writer.reserve(8); + writer.writeUint8(0xFF); + expect(pos1, equals(0)); + expect(pos2, equals(4)); + expect(writer.bytesWritten, equals(12 + 1)); + }); + + test('reserve 0 bytes returns current offset', () { + writer.writeUint32(42); + final pos = writer.reserve(0); + expect(pos, equals(4)); + expect(writer.bytesWritten, equals(4)); + }); + + test('reserve then shiftBytes compacts buffer', () { + final headerPos = writer.reserve(8); + writer + ..writeUint32(0x11111111) + ..writeUint32(0x22222222) + // Shift payload left to overwrite unused reserved space + ..shiftBytes(headerPos + 2, writer.bytesWritten, 0); + // Now backpatch the compacted header + writer[headerPos] = 0x00; + writer[headerPos + 1] = 0x0C; + expect(writer.bytesWritten, equals(14)); + expect(writer[0], equals(0x00)); + expect(writer[1], equals(0x0C)); + // Payload shifted to position 6 + expect(writer[6], equals(0x11)); + expect(writer[10], equals(0x22)); + }); + }); + group('BinaryWriter shiftBytes', () { late BinaryWriter writer;