CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-squareup-wire--wire-runtime

Runtime support library for Wire-generated Protocol Buffer classes in Kotlin multiplatform applications

Pending
Overview
Eval results
Files

protobuf-io.mddocs/

Protocol Buffer I/O

Low-level reading and writing of protocol buffer wire format data. The ProtoReader and ProtoWriter classes provide efficient streaming I/O for all protobuf field types with proper handling of packed fields, nested messages, and unknown fields.

Capabilities

ProtoReader

Reads and decodes protocol message fields from a BufferedSource with support for nested messages, packed fields, and unknown field preservation.

/**
 * Reads and decodes protocol message fields
 * @param source The BufferedSource to read from
 */
class ProtoReader(private val source: BufferedSource) {
    /** Begin a nested message, returns token for endMessage */
    @Throws(IOException::class)
    fun beginMessage(): Long
    
    /** End nested message and return unknown fields */
    @Throws(IOException::class)
    fun endMessageAndGetUnknownFields(token: Long): ByteString
    
    /** Read next tag, returns -1 if no more tags */
    @Throws(IOException::class)
    fun nextTag(): Int
    
    /** Get encoding of next field value */
    fun peekFieldEncoding(): FieldEncoding?
    
    /** Skip current field value */
    @Throws(IOException::class)
    fun skip()
    
    /** Read bytes field value */
    @Throws(IOException::class)
    fun readBytes(): ByteString
    
    /** Read string field value */
    @Throws(IOException::class)
    fun readString(): String
    
    /** Read 32-bit varint */
    @Throws(IOException::class)
    fun readVarint32(): Int
    
    /** Read 64-bit varint */
    @Throws(IOException::class)
    fun readVarint64(): Long
    
    /** Read 32-bit little-endian integer */
    @Throws(IOException::class)
    fun readFixed32(): Int
    
    /** Read 64-bit little-endian integer */
    @Throws(IOException::class)
    fun readFixed64(): Long
    
    /** Read length of next length-delimited message */
    @Throws(IOException::class)
    fun nextLengthDelimited(): Int
    
    /** Process each tag with handler function */
    inline fun forEachTag(tagHandler: (Int) -> Any): ByteString
    
    /** Read unknown field and store for later retrieval */
    fun readUnknownField(tag: Int)
    
    /** Add already-read unknown field */
    fun addUnknownField(tag: Int, fieldEncoding: FieldEncoding, value: Any?)
    
    /** Get minimum length in bytes of next field */
    fun nextFieldMinLengthInBytes(): Long
}

Usage Examples:

import com.squareup.wire.*
import okio.Buffer
import okio.ByteString

// Reading a simple message
val buffer = Buffer().write(encodedData)
val reader = ProtoReader(buffer)

// Process all fields in a message
val unknownFields = reader.forEachTag { tag ->
    when (tag) {
        1 -> {
            val name = ProtoAdapter.STRING.decode(reader)
            // Handle name field
        }
        2 -> {
            val age = ProtoAdapter.INT32.decode(reader)
            // Handle age field
        }
        else -> reader.readUnknownField(tag)
    }
}

// Manual field reading
val reader2 = ProtoReader(buffer)
val token = reader2.beginMessage()
while (true) {
    val tag = reader2.nextTag()
    if (tag == -1) break
    
    when (tag) {
        1 -> {
            val encoding = reader2.peekFieldEncoding()
            if (encoding == FieldEncoding.LENGTH_DELIMITED) {
                val value = reader2.readString()
            }
        }
        2 -> {
            val value = reader2.readVarint32()
        }
        else -> reader2.skip()
    }
}
val unknownFields2 = reader2.endMessageAndGetUnknownFields(token)

// Reading nested messages
val reader3 = ProtoReader(buffer)
val outerToken = reader3.beginMessage()
while (true) {
    val tag = reader3.nextTag()
    if (tag == -1) break
    
    if (tag == 3) { // nested message field
        val innerToken = reader3.beginMessage()
        // Read inner message fields...
        reader3.endMessageAndGetUnknownFields(innerToken)
    }
}
reader3.endMessageAndGetUnknownFields(outerToken)

ProtoWriter

Encodes and writes protocol message fields to a BufferedSink with support for all protobuf wire types and proper tag encoding.

/**
 * Utilities for encoding and writing protocol message fields
 * @param sink The BufferedSink to write to
 */
class ProtoWriter(private val sink: BufferedSink) {
    /** Write bytes value */
    @Throws(IOException::class)
    fun writeBytes(value: ByteString)
    
    /** Write string value */
    @Throws(IOException::class)
    fun writeString(value: String)
    
    /** Encode and write a tag */
    @Throws(IOException::class)
    fun writeTag(fieldNumber: Int, fieldEncoding: FieldEncoding)
    
    /** Write signed 32-bit varint */
    @Throws(IOException::class)
    internal fun writeSignedVarint32(value: Int)
    
    /** Write unsigned 32-bit varint */
    @Throws(IOException::class)
    fun writeVarint32(value: Int)
    
    /** Write 64-bit varint */
    @Throws(IOException::class)
    fun writeVarint64(value: Long)
    
    /** Write little-endian 32-bit integer */
    @Throws(IOException::class)
    fun writeFixed32(value: Int)
    
    /** Write little-endian 64-bit integer */
    @Throws(IOException::class)
    fun writeFixed64(value: Long)
}

Usage Examples:

import com.squareup.wire.*
import okio.Buffer

// Writing a simple message
val buffer = Buffer()
val writer = ProtoWriter(buffer)

// Write string field (tag 1)
writer.writeTag(1, FieldEncoding.LENGTH_DELIMITED)
writer.writeVarint32("Alice".utf8Size().toInt())
writer.writeString("Alice")

// Write integer field (tag 2) 
writer.writeTag(2, FieldEncoding.VARINT)
writer.writeVarint32(30)

// Write bytes field (tag 3)
val data = ByteString.encodeUtf8("binary data")
writer.writeTag(3, FieldEncoding.LENGTH_DELIMITED)
writer.writeVarint32(data.size)
writer.writeBytes(data)

// Get encoded result
val encoded = buffer.readByteArray()

// Using adapters for convenience (recommended approach)
val buffer2 = Buffer()
val writer2 = ProtoWriter(buffer2)

ProtoAdapter.STRING.encodeWithTag(writer2, 1, "Alice")
ProtoAdapter.INT32.encodeWithTag(writer2, 2, 30)
ProtoAdapter.BYTES.encodeWithTag(writer2, 3, data)

ProtoWriter Companion Utilities

Static utilities for computing sizes and encoding ZigZag values.

companion object {
    /** Make tag value given field number and wire type */
    internal fun makeTag(fieldNumber: Int, fieldEncoding: FieldEncoding): Int
    
    /** Compute bytes needed to encode a tag */
    internal fun tagSize(tag: Int): Int
    
    /** Compute bytes needed for signed 32-bit integer */
    internal fun int32Size(value: Int): Int
    
    /** Compute bytes needed for 32-bit varint */
    internal fun varint32Size(value: Int): Int
    
    /** Compute bytes needed for 64-bit varint */
    internal fun varint64Size(value: Long): Int
    
    /** Encode ZigZag 32-bit value for efficient negative number encoding */
    internal fun encodeZigZag32(n: Int): Int
    
    /** Decode ZigZag 32-bit value */
    internal fun decodeZigZag32(n: Int): Int
    
    /** Encode ZigZag 64-bit value for efficient negative number encoding */
    internal fun encodeZigZag64(n: Long): Long
    
    /** Decode ZigZag 64-bit value */
    internal fun decodeZigZag64(n: Long): Long
}

ReverseProtoWriter

Reverse protocol buffer writer for optimized encoding in certain scenarios.

/**
 * Reverse protocol buffer writer for optimized encoding
 */
class ReverseProtoWriter {
    /** Current byte count */
    val byteCount: Long
    
    /** Write to forward writer callback */
    fun writeForward(block: (ProtoWriter) -> Unit)
    
    /** Write to buffered sink */
    fun writeTo(sink: BufferedSink)
    
    // Similar methods to ProtoWriter but optimized for reverse writing
    fun writeTag(fieldNumber: Int, fieldEncoding: FieldEncoding)
    fun writeString(value: String)
    fun writeBytes(value: ByteString)
    fun writeVarint32(value: Int)
    fun writeVarint64(value: Long)
    fun writeFixed32(value: Int)
    fun writeFixed64(value: Long)
}

Reading Patterns

Message Reading Pattern

Standard pattern for reading a complete protocol buffer message:

fun readMessage(source: BufferedSource): SomeMessage {
    val reader = ProtoReader(source)
    
    var field1: String = ""
    var field2: Int = 0
    var field3: List<String> = emptyList()
    
    val unknownFields = reader.forEachTag { tag ->
        when (tag) {
            1 -> field1 = ProtoAdapter.STRING.decode(reader)
            2 -> field2 = ProtoAdapter.INT32.decode(reader)
            3 -> field3 += ProtoAdapter.STRING.decode(reader)
            else -> reader.readUnknownField(tag)
        }
    }
    
    return SomeMessage(field1, field2, field3, unknownFields)
}

Packed Field Reading

Reading packed repeated fields (more efficient encoding for repeated primitive types):

// Reading packed integers
val reader = ProtoReader(buffer)
val tag = reader.nextTag()
if (tag == expectedTag) {
    val values = mutableListOf<Int>()
    val adapter = ProtoAdapter.INT32.asPacked()
    val packedList = adapter.decode(reader)
    values.addAll(packedList)
}

Writing Patterns

Message Writing Pattern

Standard pattern for writing a complete protocol buffer message:

fun writeMessage(message: SomeMessage, sink: BufferedSink) {
    val writer = ProtoWriter(sink)
    
    // Write fields in tag order
    if (message.field1.isNotEmpty()) {
        ProtoAdapter.STRING.encodeWithTag(writer, 1, message.field1)
    }
    if (message.field2 != 0) {
        ProtoAdapter.INT32.encodeWithTag(writer, 2, message.field2)
    }
    for (item in message.field3) {
        ProtoAdapter.STRING.encodeWithTag(writer, 3, item)
    }
    
    // Write unknown fields
    if (message.unknownFields.size > 0) {
        writer.writeBytes(message.unknownFields)
    }
}

Performance Considerations

  • Streaming: Both readers and writers operate on streams for memory efficiency
  • Lazy Evaluation: Unknown fields are processed lazily to avoid unnecessary work
  • Buffer Reuse: Reuse Buffer instances where possible to reduce allocations
  • Packed Fields: Use packed encoding for repeated primitive types to reduce wire size
  • ZigZag Encoding: Negative numbers use ZigZag encoding for more efficient varint representation

Install with Tessl CLI

npx tessl i tessl/maven-com-squareup-wire--wire-runtime

docs

any-message.md

enum-support.md

field-annotations.md

index.md

message-framework.md

proto-adapters.md

protobuf-io.md

time-types.md

tile.json