0
# Protocol Buffer I/O
1
2
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.
3
4
## Capabilities
5
6
### ProtoReader
7
8
Reads and decodes protocol message fields from a BufferedSource with support for nested messages, packed fields, and unknown field preservation.
9
10
```kotlin { .api }
11
/**
12
* Reads and decodes protocol message fields
13
* @param source The BufferedSource to read from
14
*/
15
class ProtoReader(private val source: BufferedSource) {
16
/** Begin a nested message, returns token for endMessage */
17
@Throws(IOException::class)
18
fun beginMessage(): Long
19
20
/** End nested message and return unknown fields */
21
@Throws(IOException::class)
22
fun endMessageAndGetUnknownFields(token: Long): ByteString
23
24
/** Read next tag, returns -1 if no more tags */
25
@Throws(IOException::class)
26
fun nextTag(): Int
27
28
/** Get encoding of next field value */
29
fun peekFieldEncoding(): FieldEncoding?
30
31
/** Skip current field value */
32
@Throws(IOException::class)
33
fun skip()
34
35
/** Read bytes field value */
36
@Throws(IOException::class)
37
fun readBytes(): ByteString
38
39
/** Read string field value */
40
@Throws(IOException::class)
41
fun readString(): String
42
43
/** Read 32-bit varint */
44
@Throws(IOException::class)
45
fun readVarint32(): Int
46
47
/** Read 64-bit varint */
48
@Throws(IOException::class)
49
fun readVarint64(): Long
50
51
/** Read 32-bit little-endian integer */
52
@Throws(IOException::class)
53
fun readFixed32(): Int
54
55
/** Read 64-bit little-endian integer */
56
@Throws(IOException::class)
57
fun readFixed64(): Long
58
59
/** Read length of next length-delimited message */
60
@Throws(IOException::class)
61
fun nextLengthDelimited(): Int
62
63
/** Process each tag with handler function */
64
inline fun forEachTag(tagHandler: (Int) -> Any): ByteString
65
66
/** Read unknown field and store for later retrieval */
67
fun readUnknownField(tag: Int)
68
69
/** Add already-read unknown field */
70
fun addUnknownField(tag: Int, fieldEncoding: FieldEncoding, value: Any?)
71
72
/** Get minimum length in bytes of next field */
73
fun nextFieldMinLengthInBytes(): Long
74
}
75
```
76
77
**Usage Examples:**
78
79
```kotlin
80
import com.squareup.wire.*
81
import okio.Buffer
82
import okio.ByteString
83
84
// Reading a simple message
85
val buffer = Buffer().write(encodedData)
86
val reader = ProtoReader(buffer)
87
88
// Process all fields in a message
89
val unknownFields = reader.forEachTag { tag ->
90
when (tag) {
91
1 -> {
92
val name = ProtoAdapter.STRING.decode(reader)
93
// Handle name field
94
}
95
2 -> {
96
val age = ProtoAdapter.INT32.decode(reader)
97
// Handle age field
98
}
99
else -> reader.readUnknownField(tag)
100
}
101
}
102
103
// Manual field reading
104
val reader2 = ProtoReader(buffer)
105
val token = reader2.beginMessage()
106
while (true) {
107
val tag = reader2.nextTag()
108
if (tag == -1) break
109
110
when (tag) {
111
1 -> {
112
val encoding = reader2.peekFieldEncoding()
113
if (encoding == FieldEncoding.LENGTH_DELIMITED) {
114
val value = reader2.readString()
115
}
116
}
117
2 -> {
118
val value = reader2.readVarint32()
119
}
120
else -> reader2.skip()
121
}
122
}
123
val unknownFields2 = reader2.endMessageAndGetUnknownFields(token)
124
125
// Reading nested messages
126
val reader3 = ProtoReader(buffer)
127
val outerToken = reader3.beginMessage()
128
while (true) {
129
val tag = reader3.nextTag()
130
if (tag == -1) break
131
132
if (tag == 3) { // nested message field
133
val innerToken = reader3.beginMessage()
134
// Read inner message fields...
135
reader3.endMessageAndGetUnknownFields(innerToken)
136
}
137
}
138
reader3.endMessageAndGetUnknownFields(outerToken)
139
```
140
141
### ProtoWriter
142
143
Encodes and writes protocol message fields to a BufferedSink with support for all protobuf wire types and proper tag encoding.
144
145
```kotlin { .api }
146
/**
147
* Utilities for encoding and writing protocol message fields
148
* @param sink The BufferedSink to write to
149
*/
150
class ProtoWriter(private val sink: BufferedSink) {
151
/** Write bytes value */
152
@Throws(IOException::class)
153
fun writeBytes(value: ByteString)
154
155
/** Write string value */
156
@Throws(IOException::class)
157
fun writeString(value: String)
158
159
/** Encode and write a tag */
160
@Throws(IOException::class)
161
fun writeTag(fieldNumber: Int, fieldEncoding: FieldEncoding)
162
163
/** Write signed 32-bit varint */
164
@Throws(IOException::class)
165
internal fun writeSignedVarint32(value: Int)
166
167
/** Write unsigned 32-bit varint */
168
@Throws(IOException::class)
169
fun writeVarint32(value: Int)
170
171
/** Write 64-bit varint */
172
@Throws(IOException::class)
173
fun writeVarint64(value: Long)
174
175
/** Write little-endian 32-bit integer */
176
@Throws(IOException::class)
177
fun writeFixed32(value: Int)
178
179
/** Write little-endian 64-bit integer */
180
@Throws(IOException::class)
181
fun writeFixed64(value: Long)
182
}
183
```
184
185
**Usage Examples:**
186
187
```kotlin
188
import com.squareup.wire.*
189
import okio.Buffer
190
191
// Writing a simple message
192
val buffer = Buffer()
193
val writer = ProtoWriter(buffer)
194
195
// Write string field (tag 1)
196
writer.writeTag(1, FieldEncoding.LENGTH_DELIMITED)
197
writer.writeVarint32("Alice".utf8Size().toInt())
198
writer.writeString("Alice")
199
200
// Write integer field (tag 2)
201
writer.writeTag(2, FieldEncoding.VARINT)
202
writer.writeVarint32(30)
203
204
// Write bytes field (tag 3)
205
val data = ByteString.encodeUtf8("binary data")
206
writer.writeTag(3, FieldEncoding.LENGTH_DELIMITED)
207
writer.writeVarint32(data.size)
208
writer.writeBytes(data)
209
210
// Get encoded result
211
val encoded = buffer.readByteArray()
212
213
// Using adapters for convenience (recommended approach)
214
val buffer2 = Buffer()
215
val writer2 = ProtoWriter(buffer2)
216
217
ProtoAdapter.STRING.encodeWithTag(writer2, 1, "Alice")
218
ProtoAdapter.INT32.encodeWithTag(writer2, 2, 30)
219
ProtoAdapter.BYTES.encodeWithTag(writer2, 3, data)
220
```
221
222
### ProtoWriter Companion Utilities
223
224
Static utilities for computing sizes and encoding ZigZag values.
225
226
```kotlin { .api }
227
companion object {
228
/** Make tag value given field number and wire type */
229
internal fun makeTag(fieldNumber: Int, fieldEncoding: FieldEncoding): Int
230
231
/** Compute bytes needed to encode a tag */
232
internal fun tagSize(tag: Int): Int
233
234
/** Compute bytes needed for signed 32-bit integer */
235
internal fun int32Size(value: Int): Int
236
237
/** Compute bytes needed for 32-bit varint */
238
internal fun varint32Size(value: Int): Int
239
240
/** Compute bytes needed for 64-bit varint */
241
internal fun varint64Size(value: Long): Int
242
243
/** Encode ZigZag 32-bit value for efficient negative number encoding */
244
internal fun encodeZigZag32(n: Int): Int
245
246
/** Decode ZigZag 32-bit value */
247
internal fun decodeZigZag32(n: Int): Int
248
249
/** Encode ZigZag 64-bit value for efficient negative number encoding */
250
internal fun encodeZigZag64(n: Long): Long
251
252
/** Decode ZigZag 64-bit value */
253
internal fun decodeZigZag64(n: Long): Long
254
}
255
```
256
257
### ReverseProtoWriter
258
259
Reverse protocol buffer writer for optimized encoding in certain scenarios.
260
261
```kotlin { .api }
262
/**
263
* Reverse protocol buffer writer for optimized encoding
264
*/
265
class ReverseProtoWriter {
266
/** Current byte count */
267
val byteCount: Long
268
269
/** Write to forward writer callback */
270
fun writeForward(block: (ProtoWriter) -> Unit)
271
272
/** Write to buffered sink */
273
fun writeTo(sink: BufferedSink)
274
275
// Similar methods to ProtoWriter but optimized for reverse writing
276
fun writeTag(fieldNumber: Int, fieldEncoding: FieldEncoding)
277
fun writeString(value: String)
278
fun writeBytes(value: ByteString)
279
fun writeVarint32(value: Int)
280
fun writeVarint64(value: Long)
281
fun writeFixed32(value: Int)
282
fun writeFixed64(value: Long)
283
}
284
```
285
286
## Reading Patterns
287
288
### Message Reading Pattern
289
290
Standard pattern for reading a complete protocol buffer message:
291
292
```kotlin
293
fun readMessage(source: BufferedSource): SomeMessage {
294
val reader = ProtoReader(source)
295
296
var field1: String = ""
297
var field2: Int = 0
298
var field3: List<String> = emptyList()
299
300
val unknownFields = reader.forEachTag { tag ->
301
when (tag) {
302
1 -> field1 = ProtoAdapter.STRING.decode(reader)
303
2 -> field2 = ProtoAdapter.INT32.decode(reader)
304
3 -> field3 += ProtoAdapter.STRING.decode(reader)
305
else -> reader.readUnknownField(tag)
306
}
307
}
308
309
return SomeMessage(field1, field2, field3, unknownFields)
310
}
311
```
312
313
### Packed Field Reading
314
315
Reading packed repeated fields (more efficient encoding for repeated primitive types):
316
317
```kotlin
318
// Reading packed integers
319
val reader = ProtoReader(buffer)
320
val tag = reader.nextTag()
321
if (tag == expectedTag) {
322
val values = mutableListOf<Int>()
323
val adapter = ProtoAdapter.INT32.asPacked()
324
val packedList = adapter.decode(reader)
325
values.addAll(packedList)
326
}
327
```
328
329
## Writing Patterns
330
331
### Message Writing Pattern
332
333
Standard pattern for writing a complete protocol buffer message:
334
335
```kotlin
336
fun writeMessage(message: SomeMessage, sink: BufferedSink) {
337
val writer = ProtoWriter(sink)
338
339
// Write fields in tag order
340
if (message.field1.isNotEmpty()) {
341
ProtoAdapter.STRING.encodeWithTag(writer, 1, message.field1)
342
}
343
if (message.field2 != 0) {
344
ProtoAdapter.INT32.encodeWithTag(writer, 2, message.field2)
345
}
346
for (item in message.field3) {
347
ProtoAdapter.STRING.encodeWithTag(writer, 3, item)
348
}
349
350
// Write unknown fields
351
if (message.unknownFields.size > 0) {
352
writer.writeBytes(message.unknownFields)
353
}
354
}
355
```
356
357
## Performance Considerations
358
359
- **Streaming**: Both readers and writers operate on streams for memory efficiency
360
- **Lazy Evaluation**: Unknown fields are processed lazily to avoid unnecessary work
361
- **Buffer Reuse**: Reuse Buffer instances where possible to reduce allocations
362
- **Packed Fields**: Use packed encoding for repeated primitive types to reduce wire size
363
- **ZigZag Encoding**: Negative numbers use ZigZag encoding for more efficient varint representation