BitBuffer

BitBuffer is an efficient utility library for bit-level data manipulation, supporting various data types such as integers, floating-point numbers, strings, and more. It provides flexible construction methods and data encoding strategies.

Features

  • Support for little-endian and big-endian byte orders.
  • Construction from integer lists, byte lists (UInt8List), and Base64 strings.
  • Encoding and decoding of linear and stepped variable-length integers and floating-point numbers.
  • Support for single-bit operations and multi-bit batch operations.
  • Flexible encoding for strings.

Getting Started

Creating a BitBuffer

  • Default constructor with support for little-endian and big-endian byte orders.
  • Construct from existing data, including integer lists, byte lists, and Base64 strings.
  • Clone an existing BitBuffer.

Writing Data

  • Fixed-width integer writing.
  • Linear and stepped variable-length integer encoding.
  • Support for writing floating-point numbers, strings, and boolean values.

Reading Data

  • Read fixed-width, linear variable-length, and stepped variable-length integers.
  • Read floating-point numbers, strings, and boolean values.

Usage

  /// Creates a new `BitBuffer` object.
  ///
  /// The `BitBuffer` is initialized with a specified byte order (`endian`).
  ///
  /// ### Parameters:
  /// - `endian` (optional): Defines the byte order of the buffer.
  ///   - `Endian.little`: Little-endian order (default).
  ///   - `Endian.big`: Big-endian order.
  BitBuffer buffer1 = BitBuffer(Endian.little); // or Endian.big

  /// Creates a `BitBuffer` from a list of integers, interpreting each integer as a specified number of bits.
  ///
  /// ### Parameters:
  /// - `data`: A list of integers to be converted into the `BitBuffer`.
  /// - `bitsPerIndex` (optional): The number of bits used to represent each integer in the list.
  ///   - Default: `bitsPerInt` (platform-specific, typically 32 or 64 bits).
  /// - `trueSize` (optional): Specifies the exact number of bits to consider from the input.
  ///   - If `null`, the size is calculated based on the entire input data.
  /// ### Notes:
  /// - The `bitsPerIndex` parameter determines how each integer in `data` is encoded.
  /// - The `trueSize` parameter allows fine-grained control over the actual bit length of the buffer.
  ///
  /// Example: 11110100111101001111010010000000 (32 bits) 8*4
  ///          11110100 (LSB first) 0x2F
  ///          11110100 (LSB first) 0x2F
  ///          11110100 (LSB first) 0x2F
  ///          10000000 (LSB first) 0x01
  BitBuffer buffer2 = BitBuffer.fromBits([0x2F, 0x2F, 0x2F, 0x01], bitsPerIndex: 8);

  /// Example: 1111010000000000000000000000000000000000000000000000000000000000111101000000000000000000000000000000000000000000000000000000000011110100000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000 (256 bits) 64*4
  ///          1111010000000000000000000000000000000000000000000000000000000000 (LSB first) 0x2F
  ///          1111010000000000000000000000000000000000000000000000000000000000 (LSB first) 0x2F
  ///          1111010000000000000000000000000000000000000000000000000000000000 (LSB first) 0x2F
  ///          1000000000000000000000000000000000000000000000000000000000000000 (LSB first) 0x01
  BitBuffer buffer3 = BitBuffer.fromBits([0x2F, 0x2F, 0x2F, 0x01], bitsPerIndex: 64);

  /// Creates a `BitBuffer` from a list of unsigned 8-bit integers (bytes).
  ///
  /// ### Parameters:
  /// - `bytes`: A list of integers (0–255) representing the byte values to be converted into the `BitBuffer`.
  ///
  /// ### Notes:
  /// - Each integer in the `bytes` list is treated as a single 8-bit value.
  /// - The resulting `BitBuffer` will have a size equal to `bytes.length * 8` bits.
  ///
  /// Example: 11110100111101001111010010000000 (32 bits) 8*4
  ///          11110100 (LSB first) 0x2F
  ///          11110100 (LSB first) 0x2F
  ///          11110100 (LSB first) 0x2F
  ///          10000000 (LSB first) 0x01
  BitBuffer buffer4 = BitBuffer.fromUInt8List([0x2F, 0x2F, 0x2F, 0x01]);

  /// Creates a new `BitBuffer` by cloning an existing `BitBuffer`.
  ///
  /// The new `BitBuffer` is initialized using the bit data, `bitsPerIndex`, and size from the source `BitBuffer`.
  ///
  /// ### Parameters:
  /// - `obb`: The source `BitBuffer` to be cloned.
  ///
  /// ### Notes:
  /// - The `bitsPerIndex` of the new `BitBuffer` is inherited from the source buffer.
  /// - The `_size` of the new `BitBuffer` is set to match the source buffer's true size.
  /// - This is a convenient way to duplicate an existing `BitBuffer` without manually reinitializing it.
  ///
  /// Example: 1111010000000000000000000000000000000000000000000000000000000000111101000000000000000000000000000000000000000000000000000000000011110100000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000 (256 bits) 64*4
  ///          1111010000000000000000000000000000000000000000000000000000000000 (LSB first) 0x2F
  ///          1111010000000000000000000000000000000000000000000000000000000000 (LSB first) 0x2F
  ///          1111010000000000000000000000000000000000000000000000000000000000 (LSB first) 0x2F
  ///          1000000000000000000000000000000000000000000000000000000000000000 (LSB first) 0x01
  BitBuffer buffer5 = BitBuffer.fromBB(buffer3);

  /// Example: 1111010011110100111101001000000000000000000000000000000000000000 (64 bits) 8*8
  ///          11110100 (LSB first) 0x2F
  ///          11110100 (LSB first) 0x2F
  ///          11110100 (LSB first) 0x2F
  ///          10000000 (LSB first) 0x01
  ///          00000000 (LSB first) 0x00
  ///          00000000 (LSB first) 0x00
  ///          00000000 (LSB first) 0x00
  ///          00000000 (LSB first) 0x00
  BitBuffer buffer6 = BitBuffer.fromBB(buffer4);

  /// Creates a `BitBuffer` from a Base64-encoded string.
  ///
  /// The Base64 string is first decoded into a list of bytes, which is then converted into a `BitBuffer`.
  ///
  /// ### Parameters:
  /// - `compressed`: The Base64-encoded string to be decoded and converted into a `BitBuffer`.
  ///
  /// ### Notes:
  /// - The Base64 string is decoded using the standard Base64 decoding.
  /// - The resulting `BitBuffer` represents the decoded bytes as binary data.
  ///
  /// Example: 10000110010001101100011000100110 (32 bits)
  ///          "YWJjZA==" -> "abcd" -> 0x61 0x62 0x63 0x64 -> 10000110 (LSB first) ...
  BitBuffer buffer7 = BitBuffer.fromBase64("YWJjZA==");

  BitBuffer buffer8 = BitBuffer.fromBase64Compressed("YWJjZA==");

  BitBuffer buffer = BitBuffer();
  BitBufferWriter writer = buffer.writer();

  /// Writes an integer value to the `BitBuffer` with the specified bit width and sign.
  ///
  /// ### Parameters:
  /// - `value`: The integer value to be written into the `BitBuffer`.
  /// - `signed` (optional): Determines whether the value is treated as signed or unsigned.
  ///   - `true` (default): Treats the value as signed (e.g., supports negative values).
  ///   - `false`: Treats the value as unsigned.
  /// - `bits` (optional): The number of bits to use for representing the value.
  ///   - Default: `64` bits.
  ///
  /// ### Notes:
  /// - The `bits` parameter must be large enough to hold the `value` (e.g., an 8-bit integer cannot hold values outside the range -128 to 127 for signed integers).
  /// - If `signed` is `false`, the `value` must be non-negative and within the range allowed by `bits`.
  /// Example: 1111010000000000000000000000000000000000000000000000000000000000 (64 bits)   64(parameters:bits)
  ///          1111010000000000000000000000000000000000000000000000000000000000 (LSB first) 0x2F
  writer.writeInt(0x2F, signed: false, bits: 64);

  /// Example:111110100000000000000000000000000 (33 bits)   1(sign)+32
  ///         1                                 (LSB first) sign
  ///          11111010000000000000000000000000 (LSB first) 0x2F
  // writer.writeInt(0x2F, signed: true, bits: 32);

  /// Example: 11111010000000000000000000000000000000000000000000000000000000000 (65 bits) 1(sign)+64(parameters:bits)
  ///          1                                                                 (LSB first) sign
  ///           1111010000000000000000000000000000000000000000000000000000000000 (LSB first) 0x2F
  writer.writeInt(0x2F, signed: true, bits: 64);

  /// Writes a linear variable-length integer to the `BitBuffer`.
  ///
  /// This method encodes an integer value using a specified maximum bit width and sign.
  /// It adapts the bit length based on the value, ensuring efficient storage.
  ///
  /// ### Parameters:
  /// - `value`: The integer value to be written into the `BitBuffer`.
  /// - `signed` (optional): Specifies whether the value is treated as signed or unsigned.
  ///   - `true` (default): Treats the value as signed (e.g., supports negative values).
  ///   - `false`: Treats the value as unsigned.
  /// - `maxBits` (optional): The maximum number of bits available for encoding the value.
  ///   - Default: `64` bits.
  ///
  /// ### Notes:
  /// - The method optimizes the bit representation based on the value's magnitude.
  /// - If `signed` is `false`, the `value` must be non-negative and fit within the specified `maxBits`.
  /// - If the value exceeds the available bit space, it may result in data loss or an exception.
  ///
  /// ### Common Use Cases:
  /// - Encoding integers for protocols or formats where variable-length representation is desired.
  /// - Minimizing the size of encoded data by dynamically adjusting bit usage.
  ///
  /// Example:0110000111101 (13 bits)   7(constant)+6(var)
  ///         0110000       (LSB first) 0x06
  ///                111101 (LSB first) 0x2F
  writer.writeLinearVarInt(0x2F, signed: false, maxBits: 64);

  /// Example:0110001111101 (13 bits)   6(constant)+1(sign)+6(var)
  ///         011000        (LSB first) 0x06
  ///               1       (LSB first) sign
  ///                111101 (LSB first) 0x2F
  writer.writeLinearVarInt(0x2F, signed: true, maxBits: 32);

  /// Example:01100001111101 (14 bits)   7(constant)+1(sign)+6(var)
  ///         0110000        (LSB first) 0x06
  ///                1       (LSB first) sign
  ///                 111101 (LSB first) 0x2F
  writer.writeLinearVarInt(0x2F, signed: true, maxBits: 64);

  /// Writes a stepped variable-length integer to the `BitBuffer`.
  ///
  /// This method encodes an integer using a stepped bit-length approach.
  /// The bit length is dynamically chosen from the provided `bitLimits` list based on the value's size.
  ///
  /// ### Parameters:
  /// - `value`: The integer value to be written into the `BitBuffer`.
  /// - `signed` (optional): Determines whether the value is treated as signed or unsigned.
  ///   - `true` (default): Treats the value as signed (supports negative values).
  ///   - `false`: Treats the value as unsigned.
  /// - `bitLimits` (optional): A list of bit-length limits to determine encoding steps.
  ///   - Default: `stepList2b`, a predefined list of stepped limits.
  ///
  /// ### Notes:
  /// - The method selects the smallest bit-length from `bitLimits` that can accommodate the value.
  /// - If `signed` is `false`, the `value` must be non-negative and fit within the largest bit limit.
  /// - Ensure that `bitLimits` contains valid and ascending values to avoid unexpected behavior.
  ///
  /// ### Common Use Cases:
  /// - Encoding integers with fine-grained control over size optimization.
  /// - Storing variable-length integers in data streams or protocols with stepped encoding strategies.
  /// Example:0011110100 (10 bits)   2+8
  ///         00         (LSB first) 0x00
  ///           11110100 (LSB first) 0x2F
  writer.writeSteppedVarInt(0x2F, signed: false, bitLimits: stepList2b);

  /// Example:10011110100 (11 bits)   2+1+8
  ///         00          (LSB first) 0x00
  ///           1         (LSB first) sign
  ///           11110100  (LSB first) 0x2F
  writer.writeSteppedVarInt(0x2F, signed: true, bitLimits: stepList2b);
  writer.writeSteppedVarInt(0x2F, signed: true, bitLimits: [8, 16, 32, 64]);

  /// Writes the specified number of bits from an integer value into the `BitBuffer`.
  ///
  /// The method extracts the least significant `bits` from the `value` and writes them to the buffer.
  ///
  /// ### Parameters:
  /// - `value`: The integer value from which bits will be written.
  /// - `bits`: The number of bits to write from the `value`.
  ///
  /// ### Notes:
  /// - The `bits` parameter determines how many bits to extract from the `value`.
  /// - If the `value` contains more bits than specified, only the least significant `bits` are written.
  /// - Ensure the `bits` parameter is within a valid range (e.g., 1–32 or 1–64, depending on platform limits).
  ///
  /// ### Common Use Cases:
  /// - Writing specific parts of an integer to a bit stream.
  /// - Packing multiple values into a single binary buffer.
  /// Example:1111010 (7 bits)
  writer.writeBits(0x2F, 7);

  /// Example:1111010000000 (13 bits)
  writer.writeBits(0x2F, 13);

  writer.writeBit(true); //or false
  writer.writeDouble(0.01, signed: true, bits: 64, maxDecimal: 8);
  writer.writeLinearVarDouble(0.01, signed: true, maxBits: 64, maxDecimal: 8);
  writer.writeSteppedVarDouble(0.01, signed: true, bitLimits: stepList2b, decimalBitLimits: stepDecList2b);
  writer.writeString("abcd");
  writer.writeLinearVarString("abcd");
  writer.writeSteppedVarString("abcd", steps: stepCharList1b);

  BitBufferReader reader = buffer.reader();
  reader.readInt();
  reader.readLinearVarInt();
  reader.readSteppedVarInt();
  reader.readDouble();
  reader.readLinearVarDouble();
  reader.readSteppedVarDouble();
  reader.readString();
  reader.readLinearVarString();
  reader.readSteppedVarString();
  reader.readBits(bits);
  reader.readBit();
  reader.readBit();