or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

binary-io.mdbytestring-utilities.mddebug-utilities.mdextension-fields.mdindex.mdmap-operations.mdmessage-operations.md
tile.json

binary-io.mddocs/

Binary I/O Operations

The binary I/O system provides low-level reading and writing of Protocol Buffer wire format data. It consists of BinaryReader for parsing binary data and BinaryWriter for generating binary data, along with supporting encoder/decoder classes.

BinaryReader { .api }

The BinaryReader class reads Protocol Buffer binary data with type checking and field navigation:

const { BinaryReader } = require('google-protobuf');

// Create reader from binary data
const reader = new BinaryReader(bytes, start, length, options);

// Alternative: Use cached allocation
const reader = BinaryReader.alloc(bytes, start, length, options);

// Navigate through fields
while (reader.nextField()) {
  const fieldNumber = reader.getFieldNumber();
  const wireType = reader.getWireType();
  
  // Read field based on type
  switch (fieldNumber) {
    case 1:
      const stringValue = reader.readString();
      break;
    case 2: 
      const int32Value = reader.readInt32();
      break;
  }
}

// Clean up (returns to cache if allocated)
reader.free();

Type Definitions:

/**
 * @constructor
 * @param {!Uint8Array} bytes Binary data to read
 * @param {number=} start Starting offset (default: 0)  
 * @param {number=} length Length to read (default: bytes.length)
 * @param {BinaryReaderOptions=} options Reader options
 */

/**
 * @typedef {Object} BinaryReaderOptions
 * @property {boolean=} discardUnknownFields Whether to discard unknown fields
 * @property {boolean=} aliasBytesFields Whether bytes fields should alias original buffer
 */

Reader Navigation { .api }

const { BinaryReader } = require('google-protobuf');

// Field navigation
const hasNextField = reader.nextField();
const hasExpectedTag = reader.nextFieldIfTagEqualTo(expectedTag);

// Position information
const cursor = reader.getCursor();
const fieldCursor = reader.getFieldCursor(); // Position before current field
reader.advance(byteCount);

// Field information
const tag = reader.getTag();
const fieldNumber = reader.getFieldNumber();
const wireType = reader.getWireType();
const isEndOfGroup = reader.isEndGroup();
const isDelimited = reader.isDelimited();

// Buffer access
const buffer = reader.getBuffer();
const byteString = reader.getBufferAsByteString();
const isImmutable = reader.dataIsImmutable();

// Example: Manual field parsing
const reader = new BinaryReader(data);
while (reader.nextField()) {
  console.log(`Field ${reader.getFieldNumber()}, Wire Type: ${reader.getWireType()}`);
  
  if (reader.getWireType() === 2) { // DELIMITED
    console.log(`Delimited field at cursor: ${reader.getCursor()}`);
  }
}

Field Reading Methods { .api }

Varint Fields

// Integer types
const uint32 = reader.readUint32();
const int32 = reader.readInt32(); 
const sint32 = reader.readSint32();
const uint64 = reader.readUint64();
const int64 = reader.readInt64();
const sint64 = reader.readSint64();

// Boolean
const boolValue = reader.readBool();

// Enum (reads as varint)
const enumValue = reader.readEnum();

// 64-bit values with split high/low parts (for precision)
const uint64Split = reader.readSplitUint64();
const int64Split = reader.readSplitInt64();
const sint64Split = reader.readSplitSint64();

// Example: Reading various integer types
while (reader.nextField()) {
  switch (reader.getFieldNumber()) {
    case 1:
      const id = reader.readUint32();
      break;
    case 2:
      const count = reader.readInt32();
      break;
    case 3:
      const flag = reader.readBool();
      break;
  }
}

Fixed Fields

// 32-bit fixed
const fixed32 = reader.readFixed32();
const sfixed32 = reader.readSfixed32();
const float = reader.readFloat();

// 64-bit fixed  
const fixed64 = reader.readFixed64();
const sfixed64 = reader.readSfixed64();
const double = reader.readDouble();

// Split 64-bit reads (for precision)
const fixed64Split = reader.readSplitFixed64();
const sfixed64Split = reader.readSplitSfixed64();

// Example: Reading fixed-width fields
while (reader.nextField()) {
  switch (reader.getFieldNumber()) {
    case 1:
      const timestamp = reader.readFixed64();
      break;
    case 2:
      const temperature = reader.readFloat();
      break;
    case 3:
      const coordinate = reader.readDouble();
      break;
  }
}

Delimited Fields

// String and bytes
const stringValue = reader.readString();
const bytesValue = reader.readBytes();

// Messages (nested)
const messageBytes = reader.readMessage(subMessage, deserializeFn);

// Packed repeated fields
const packedInt32s = reader.readPackedInt32();
const packedUint32s = reader.readPackedUint32();
const packedBools = reader.readPackedBool();
const packedFloats = reader.readPackedFloat();
const packedDoubles = reader.readPackedDouble();
// ... other packed types

// Example: Reading delimited fields
while (reader.nextField()) {
  switch (reader.getFieldNumber()) {
    case 1:
      const name = reader.readString();
      break;
    case 2:
      const data = reader.readBytes();
      break;
    case 3:
      const nestedMessage = new NestedMessage();
      reader.readMessage(nestedMessage, NestedMessage.deserializeBinaryFromReader);
      break;
    case 4:
      const numbers = reader.readPackedInt32();
      break;
  }
}

Field Skipping { .api }

const { BinaryReader } = require('google-protobuf');

// Skip fields by wire type
reader.skipVarintField();    // Skip varint field
reader.skipDelimitedField(); // Skip delimited field  
reader.skipFixed32Field();   // Skip 32-bit fixed field
reader.skipFixed64Field();   // Skip 64-bit fixed field
reader.skipGroup();          // Skip group field (deprecated)

// Example: Selective field parsing with skipping
while (reader.nextField()) {
  const fieldNumber = reader.getFieldNumber();
  
  switch (fieldNumber) {
    case 1:
      const importantValue = reader.readString();
      break;
    case 2:
      // Skip this field - not needed
      reader.skipDelimitedField();
      break;
    case 3:
      const anotherValue = reader.readInt32();
      break;
    default:
      // Skip unknown fields
      switch (reader.getWireType()) {
        case 0: reader.skipVarintField(); break;
        case 1: reader.skipFixed64Field(); break;
        case 2: reader.skipDelimitedField(); break;
        case 5: reader.skipFixed32Field(); break;
      }
  }
}

BinaryWriter { .api }

The BinaryWriter class generates Protocol Buffer binary data:

const { BinaryWriter } = require('google-protobuf');

// Create writer
const writer = new BinaryWriter();

// Write fields
writer.writeString(1, "Hello World");
writer.writeInt32(2, 42);
writer.writeBool(3, true);

// Get result
const resultBuffer = writer.getResultBuffer();        // Uint8Array
const resultBytes = writer.getResultBufferAsByteString(); // ByteString
const resultBase64 = writer.getResultBase64String();  // Base64 string

// Reset for reuse
writer.reset();

Type Definitions:

/**
 * @constructor
 * Creates a new binary writer for Protocol Buffer wire format
 */

Field Writing Methods { .api }

Varint Fields

const { BinaryWriter } = require('google-protobuf');

const writer = new BinaryWriter();

// Integer types
writer.writeUint32(fieldNumber, uint32Value);
writer.writeInt32(fieldNumber, int32Value);
writer.writeSint32(fieldNumber, sint32Value);
writer.writeUint64(fieldNumber, uint64Value);  
writer.writeInt64(fieldNumber, int64Value);
writer.writeSint64(fieldNumber, sint64Value);

// Boolean and enum
writer.writeBool(fieldNumber, boolValue);
writer.writeEnum(fieldNumber, enumValue);

// Split 64-bit writes (for precision)
writer.writeSplitUint64(fieldNumber, lowBits, highBits);
writer.writeSplitInt64(fieldNumber, lowBits, highBits);
writer.writeSplitSint64(fieldNumber, lowBits, highBits);

// Example: Writing message fields
writer.writeString(1, message.getName());
writer.writeInt32(2, message.getAge());
writer.writeBool(3, message.getActive());
writer.writeEnum(4, message.getStatus());

Fixed Fields

// 32-bit fixed
writer.writeFixed32(fieldNumber, fixed32Value);
writer.writeSfixed32(fieldNumber, sfixed32Value);
writer.writeFloat(fieldNumber, floatValue);

// 64-bit fixed
writer.writeFixed64(fieldNumber, fixed64Value);
writer.writeSfixed64(fieldNumber, sfixed64Value);  
writer.writeDouble(fieldNumber, doubleValue);

// Split 64-bit writes
writer.writeSplitFixed64(fieldNumber, lowBits, highBits);
writer.writeSplitSfixed64(fieldNumber, lowBits, highBits);

// Example: Writing numeric data
writer.writeFloat(1, temperature);
writer.writeDouble(2, coordinates.latitude);
writer.writeFixed64(3, timestamp);

Delimited Fields

// String and bytes
writer.writeString(fieldNumber, stringValue);
writer.writeBytes(fieldNumber, bytesValue);

// Messages (nested)
writer.writeMessage(fieldNumber, message, serializeFn);

// Pre-serialized messages
writer.writeSerializedMessage(serializedBytes, start, end);

// Group fields (deprecated)
writer.writeGroup(fieldNumber, message, serializeFn);

// Example: Writing complex fields
writer.writeString(1, "User Name");
writer.writeBytes(2, profilePicture);

// Write nested message
writer.writeMessage(3, address, (msg, writer) => {
  writer.writeString(1, msg.getStreet());
  writer.writeString(2, msg.getCity());
});

Repeated and Packed Fields { .api }

const { BinaryWriter } = require('google-protobuf');

const writer = new BinaryWriter();

// Repeated fields (write multiple times with same field number)
const names = ["Alice", "Bob", "Charlie"];
names.forEach(name => {
  writer.writeString(1, name);
});

// Packed repeated fields (more efficient for numeric types)
const numbers = [1, 2, 3, 4, 5];
writer.writePackedInt32(2, numbers);

// Other packed types
writer.writePackedUint32(3, uint32Array);
writer.writePackedBool(4, boolArray);
writer.writePackedFloat(5, floatArray);
writer.writePackedDouble(6, doubleArray);
writer.writePackedFixed32(7, fixed32Array);
writer.writePackedFixed64(8, fixed64Array);
// ... other packed types available

// Example: Efficient numeric array serialization
const measurements = [1.1, 2.2, 3.3, 4.4, 5.5];
writer.writePackedFloat(1, measurements);

const timestamps = [100n, 200n, 300n, 400n];  
writer.writePackedUint64(2, timestamps);

Advanced Writing { .api }

const { BinaryWriter } = require('google-protobuf');

const writer = new BinaryWriter();

// Write any field type dynamically
const fieldType = 9; // STRING type
writer.writeAny(fieldType, fieldNumber, value);

// Write unknown fields (preserves unrecognized data)
writer.writeUnknownFields(unknownFieldsMap);

// Direct buffer writing for pre-serialized data
writer.writeSerializedMessage(preSerializedBytes, 0, preSerializedBytes.length);

// Example: Dynamic field writing
const fields = [
  { type: 9, number: 1, value: "string value" },    // STRING
  { type: 5, number: 2, value: 42 },                // INT32  
  { type: 8, number: 3, value: true }               // BOOL
];

fields.forEach(field => {
  writer.writeAny(field.type, field.number, field.value);
});

const result = writer.getResultBuffer();

Binary Constants and Types { .api }

const { BinaryConstants } = require('google-protobuf');

// Field types
const fieldTypes = {
  INVALID: BinaryConstants.FieldType.INVALID,    // -1
  DOUBLE: BinaryConstants.FieldType.DOUBLE,      // 1
  FLOAT: BinaryConstants.FieldType.FLOAT,        // 2
  INT64: BinaryConstants.FieldType.INT64,        // 3
  UINT64: BinaryConstants.FieldType.UINT64,      // 4
  INT32: BinaryConstants.FieldType.INT32,        // 5
  FIXED64: BinaryConstants.FieldType.FIXED64,    // 6
  FIXED32: BinaryConstants.FieldType.FIXED32,    // 7
  BOOL: BinaryConstants.FieldType.BOOL,          // 8
  STRING: BinaryConstants.FieldType.STRING,      // 9
  MESSAGE: BinaryConstants.FieldType.MESSAGE,    // 11
  BYTES: BinaryConstants.FieldType.BYTES,        // 12
  UINT32: BinaryConstants.FieldType.UINT32,      // 13
  ENUM: BinaryConstants.FieldType.ENUM,          // 14
  SFIXED32: BinaryConstants.FieldType.SFIXED32,  // 15
  SFIXED64: BinaryConstants.FieldType.SFIXED64,  // 16
  SINT32: BinaryConstants.FieldType.SINT32,      // 17
  SINT64: BinaryConstants.FieldType.SINT64       // 18
};

// Wire types  
const wireTypes = {
  INVALID: BinaryConstants.WireType.INVALID,      // -1
  VARINT: BinaryConstants.WireType.VARINT,        // 0
  FIXED64: BinaryConstants.WireType.FIXED64,      // 1
  DELIMITED: BinaryConstants.WireType.DELIMITED,  // 2
  FIXED32: BinaryConstants.WireType.FIXED32       // 5
};

// Utility functions
const wireType = BinaryConstants.FieldTypeToWireType(fieldType);
const isValidWire = BinaryConstants.isValidWireType(wireType);

// Constants
const invalidField = BinaryConstants.INVALID_FIELD_NUMBER; // -1
const invalidTag = BinaryConstants.INVALID_TAG;            // -1

Type Definitions:

/**
 * @enum {number} Field types for Protocol Buffer fields
 */

/**
 * @enum {number} Wire types for Protocol Buffer encoding
 */

Advanced Binary Operations { .api }

For complex binary operations, use the high-level BinaryReader and BinaryWriter classes which provide comprehensive wire format support:

const { BinaryReader, BinaryWriter } = require('google-protobuf');

// Complex message parsing
function parseComplexMessage(bytes) {
  const reader = new BinaryReader(bytes);
  const result = {};
  
  while (reader.nextField()) {
    const fieldNumber = reader.getFieldNumber();
    const wireType = reader.getWireType();
    
    // Handle different field types based on wire type
    switch (wireType) {
      case 0: // VARINT
        result[fieldNumber] = reader.readUint64();
        break;
      case 1: // FIXED64
        result[fieldNumber] = reader.readDouble();
        break;
      case 2: // DELIMITED
        result[fieldNumber] = reader.readString();
        break;
      case 5: // FIXED32
        result[fieldNumber] = reader.readFloat();
        break;
    }
  }
  
  return result;
}

// Complex message generation
function generateComplexMessage(data) {
  const writer = new BinaryWriter();
  
  Object.entries(data).forEach(([fieldNum, value]) => {
    const fieldNumber = parseInt(fieldNum);
    
    if (typeof value === 'string') {
      writer.writeString(fieldNumber, value);
    } else if (typeof value === 'number') {
      writer.writeDouble(fieldNumber, value);
    }
  });
  
  return writer.getResultBuffer();
}

Performance and Memory Considerations

Reader Optimization

// Use allocation cache for frequently created readers
const reader = BinaryReader.alloc(bytes); // From cache
// ... use reader
reader.free(); // Return to cache

// Configure reader options for performance
const options = {
  aliasBytesFields: true,    // Avoid copying bytes (careful with mutability)
  discardUnknownFields: true // Skip unknown fields entirely
};
const reader = new BinaryReader(bytes, 0, bytes.length, options);

Writer Optimization

// Reuse writer instances
const writer = new BinaryWriter();

function serializeMessage(message) {
  writer.reset(); // Clear previous data
  
  // Write fields
  writer.writeString(1, message.name);
  writer.writeInt32(2, message.value);
  
  return writer.getResultBuffer();
}

// Multiple serializations with same writer
const results = messages.map(serializeMessage);

Memory Management

// Efficient buffer handling
const reader = new BinaryReader(buffer);

// Avoid creating unnecessary intermediate objects
while (reader.nextField()) {
  const fieldNum = reader.getFieldNumber();
  
  // Process fields directly without intermediate storage
  switch (fieldNum) {
    case 1:
      processString(reader.readString());
      break;
    case 2:
      processNumber(reader.readInt32());
      break;
  }
}