Runtime support library for Wire-generated Protocol Buffer classes in Kotlin multiplatform applications
—
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.
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)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)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
}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)
}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)
}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)
}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)
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-squareup-wire--wire-runtime