@trevrpc/trevrpc-js 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/wire.js CHANGED
@@ -1,9 +1,10 @@
1
- import protobuf from "protobufjs";
2
-
3
- import { byteView } from "./bytes.js";
4
- import { setInt64Representation } from "./int64.js";
5
-
6
- export { protobuf };
1
+ // The protobuf codec of the REQUEST, HEADERS, ANSWER and STATUS payloads, written against the wire
2
+ // format directly, as the Go and Kotlin runtimes do (see "Protocol and Wire Format" in the wiki).
3
+ // Encoding is canonical proto3: fields in number order, zero values left out, and both fields of a
4
+ // map entry always written. Decoding accepts only what a conforming sender produces: a known field
5
+ // with another wire type, a uint32 above 2^32 - 1, a varint above 64 bits, invalid UTF-8 and
6
+ // truncated data are all errors, while unknown fields of every wire type are skipped.
7
+ import { BinaryReader, BinaryWriter, WireType } from "@bufbuild/protobuf/wire";
7
8
 
8
9
  export const RpcKind = Object.freeze({
9
10
  Unary: 0,
@@ -19,373 +20,460 @@ export const StreamCompression = Object.freeze({
19
20
  Optimistic: 2,
20
21
  });
21
22
 
22
- const wireRoot = protobuf.Root.fromJSON({
23
- nested: {
24
- trevrpc: {
25
- nested: {
26
- RpcKind: {
27
- values: {
28
- Unary: 0,
29
- ClientStreaming: 1,
30
- ServerStreaming: 2,
31
- BidirectionalStreaming: 3,
32
- },
33
- },
34
- StreamCompression: {
35
- values: {
36
- STREAM_COMPRESSION_NONE: 0,
37
- STREAM_COMPRESSION_ENABLED: 1,
38
- STREAM_COMPRESSION_OPTIMISTIC: 2,
39
- },
40
- },
41
- RpcRequest: {
42
- fields: {
43
- method: { type: "string", id: 1 },
44
- metadata: { keyType: "string", type: "bytes", id: 2 },
45
- kind: { type: "RpcKind", id: 3 },
46
- timeoutNanos: { type: "uint64", id: 4 },
47
- datagramFlow: { type: "uint64", id: 5 },
48
- acceptDictionaries: { rule: "repeated", type: "uint32", id: 6 },
49
- dictionaryQuery: { rule: "repeated", type: "DictionaryInfo", id: 7 },
50
- streamCompression: { type: "StreamCompression", id: 8 },
51
- connectionMetadata: { type: "ConnectionMetadata", id: 9 },
52
- },
53
- },
54
- RpcHeaders: {
55
- fields: {
56
- metadata: { keyType: "string", type: "bytes", id: 1 },
57
- },
58
- },
59
- RpcAnswer: {
60
- fields: {
61
- dictionaries: { rule: "repeated", type: "DictionaryInfo", id: 1 },
62
- streamCompression: { type: "bool", id: 2 },
63
- connectionMetadata: { type: "uint32", id: 3 },
64
- },
65
- },
66
- ConnectionMetadata: {
67
- fields: {
68
- id: { type: "uint32", id: 1 },
69
- metadata: { keyType: "string", type: "bytes", id: 2 },
70
- },
71
- },
72
- DictionaryInfo: {
73
- fields: {
74
- id: { type: "uint32", id: 1 },
75
- fingerprint: { type: "bytes", id: 2 },
76
- },
77
- },
78
- RpcStatus: {
79
- fields: {
80
- code: { type: "uint32", id: 1 },
81
- message: { type: "string", id: 2 },
82
- metadata: { keyType: "string", type: "bytes", id: 3 },
83
- refusedStreamCompression: { type: "bool", id: 4 },
84
- },
85
- },
86
- },
87
- },
23
+ /** How deeply unknown groups may nest. */
24
+ const MaxGroupDepth = 64;
25
+
26
+ /**
27
+ * The REQUEST payload: `{ method, metadata, kind, timeoutNanos, datagramFlow, acceptDictionaries,
28
+ * dictionaryQuery, streamCompression, connectionMetadata }`. 64-bit fields encode from a bigint, a
29
+ * number or a decimal string and decode as bigints; `connectionMetadata` is `{ id, metadata }`, or
30
+ * undefined when absent.
31
+ */
32
+ export const RpcRequest = Object.freeze({
33
+ name: "RpcRequest",
34
+ encode(request) {
35
+ const writer = new BinaryWriter();
36
+ writeString(writer, 1, request.method);
37
+ writeMetadata(writer, 2, request.metadata);
38
+ writeInt32(writer, 3, request.kind);
39
+ writeUint64(writer, 4, request.timeoutNanos);
40
+ writeUint64(writer, 5, request.datagramFlow);
41
+ const accept = request.acceptDictionaries;
42
+ if (accept != null && accept.length > 0) {
43
+ writer.tag(6, WireType.LengthDelimited).fork();
44
+ for (const id of accept) {
45
+ writer.uint32(id);
46
+ }
47
+ writer.join();
48
+ }
49
+ writeDictionaries(writer, 7, request.dictionaryQuery);
50
+ writeInt32(writer, 8, request.streamCompression);
51
+ const connection = request.connectionMetadata;
52
+ if (connection != null) {
53
+ writer.tag(9, WireType.LengthDelimited).fork();
54
+ writeUint32(writer, 1, connection.id);
55
+ writeMetadata(writer, 2, connection.metadata);
56
+ writer.join();
57
+ }
58
+ return writer.finish();
59
+ },
60
+ decode(bytes) {
61
+ const request = {
62
+ method: "",
63
+ metadata: emptyMetadata(),
64
+ kind: 0,
65
+ timeoutNanos: 0n,
66
+ datagramFlow: 0n,
67
+ acceptDictionaries: [],
68
+ dictionaryQuery: [],
69
+ streamCompression: 0,
70
+ connectionMetadata: undefined,
71
+ };
72
+ const reader = new WireReader(bytes);
73
+ while (reader.more()) {
74
+ const [field, wireType] = reader.tag();
75
+ switch (field) {
76
+ case 1:
77
+ request.method = reader.string(wireType, "method");
78
+ break;
79
+ case 2:
80
+ reader.metadataEntry(wireType, request.metadata, "metadata");
81
+ break;
82
+ case 3:
83
+ request.kind = reader.int32(wireType, "kind");
84
+ break;
85
+ case 4:
86
+ request.timeoutNanos = reader.uint64(wireType, "timeout_nanos");
87
+ break;
88
+ case 5:
89
+ request.datagramFlow = reader.uint64(wireType, "datagram_flow");
90
+ break;
91
+ case 6:
92
+ reader.repeatedUint32(wireType, request.acceptDictionaries, "accept_dictionaries");
93
+ break;
94
+ case 7:
95
+ request.dictionaryQuery.push(reader.dictionaryInfo(wireType, "dictionary_query"));
96
+ break;
97
+ case 8:
98
+ request.streamCompression = reader.int32(wireType, "stream_compression");
99
+ break;
100
+ case 9:
101
+ request.connectionMetadata = reader.connectionMetadata(wireType, "connection_metadata");
102
+ break;
103
+ default:
104
+ reader.skip(field, wireType);
105
+ }
106
+ }
107
+ return request;
108
+ },
109
+ });
110
+
111
+ /** The HEADERS payload: `{ metadata }`. */
112
+ export const RpcHeaders = Object.freeze({
113
+ name: "RpcHeaders",
114
+ encode(headers) {
115
+ const writer = new BinaryWriter();
116
+ writeMetadata(writer, 1, headers.metadata);
117
+ return writer.finish();
118
+ },
119
+ decode(bytes) {
120
+ const headers = { metadata: emptyMetadata() };
121
+ const reader = new WireReader(bytes);
122
+ while (reader.more()) {
123
+ const [field, wireType] = reader.tag();
124
+ if (field === 1) {
125
+ reader.metadataEntry(wireType, headers.metadata, "metadata");
126
+ } else {
127
+ reader.skip(field, wireType);
128
+ }
129
+ }
130
+ return headers;
88
131
  },
89
132
  });
90
133
 
91
- export const RpcRequest = wireRoot.lookupType("trevrpc.RpcRequest");
92
- export const RpcHeaders = wireRoot.lookupType("trevrpc.RpcHeaders");
93
- export const RpcAnswer = wireRoot.lookupType("trevrpc.RpcAnswer");
94
- export const RpcStatus = wireRoot.lookupType("trevrpc.RpcStatus");
134
+ /** The ANSWER payload: `{ dictionaries, streamCompression, connectionMetadata }`. */
135
+ export const RpcAnswer = Object.freeze({
136
+ name: "RpcAnswer",
137
+ encode(answer) {
138
+ const writer = new BinaryWriter();
139
+ writeDictionaries(writer, 1, answer.dictionaries);
140
+ writeBool(writer, 2, answer.streamCompression);
141
+ writeUint32(writer, 3, answer.connectionMetadata);
142
+ return writer.finish();
143
+ },
144
+ decode(bytes) {
145
+ const answer = { dictionaries: [], streamCompression: false, connectionMetadata: 0 };
146
+ const reader = new WireReader(bytes);
147
+ while (reader.more()) {
148
+ const [field, wireType] = reader.tag();
149
+ switch (field) {
150
+ case 1:
151
+ answer.dictionaries.push(reader.dictionaryInfo(wireType, "dictionaries"));
152
+ break;
153
+ case 2:
154
+ answer.streamCompression = reader.bool(wireType, "stream_compression");
155
+ break;
156
+ case 3:
157
+ answer.connectionMetadata = reader.uint32(wireType, "connection_metadata");
158
+ break;
159
+ default:
160
+ reader.skip(field, wireType);
161
+ }
162
+ }
163
+ return answer;
164
+ },
165
+ });
95
166
 
96
- const MaxFieldNumber = 0x1fffffff;
97
- const MaxUint32 = 0xffffffffn;
98
- const MaxUint64 = 0xffffffffffffffffn;
99
- const fatalUtf8Decoder = new TextDecoder("utf-8", { fatal: true });
167
+ /** The STATUS payload: `{ code, message, metadata, refusedStreamCompression }`. */
168
+ export const RpcStatus = Object.freeze({
169
+ name: "RpcStatus",
170
+ encode(status) {
171
+ const writer = new BinaryWriter();
172
+ writeUint32(writer, 1, status.code);
173
+ writeString(writer, 2, status.message);
174
+ writeMetadata(writer, 3, status.metadata);
175
+ writeBool(writer, 4, status.refusedStreamCompression);
176
+ return writer.finish();
177
+ },
178
+ decode(bytes) {
179
+ const status = {
180
+ code: 0,
181
+ message: "",
182
+ metadata: emptyMetadata(),
183
+ refusedStreamCompression: false,
184
+ };
185
+ const reader = new WireReader(bytes);
186
+ while (reader.more()) {
187
+ const [field, wireType] = reader.tag();
188
+ switch (field) {
189
+ case 1:
190
+ status.code = reader.uint32(wireType, "code");
191
+ break;
192
+ case 2:
193
+ status.message = reader.string(wireType, "message");
194
+ break;
195
+ case 3:
196
+ reader.metadataEntry(wireType, status.metadata, "metadata");
197
+ break;
198
+ case 4:
199
+ status.refusedStreamCompression = reader.bool(wireType, "refused_stream_compression");
200
+ break;
201
+ default:
202
+ reader.skip(field, wireType);
203
+ }
204
+ }
205
+ return status;
206
+ },
207
+ });
100
208
 
101
- /** Checks protobuf tags and known-field wire types before protobuf.js decoding. */
102
- export function preflightWireMessage(messageType, body) {
103
- scanMessage(messageType, byteView(body), 0);
209
+ function emptyMetadata() {
210
+ return {};
104
211
  }
105
212
 
106
213
  /**
107
- * Creates a protobuf root from a JSON namespace definition. `options.int64` sets how calls decode
108
- * its messages' 64-bit integers (see `setInt64Representation`); without it they stay Long objects.
214
+ * Sets a decoded metadata entry as an own property, so that a key such as `__proto__` is an entry
215
+ * like any other and never replaces the object's prototype.
109
216
  */
110
- export function createRoot(json, options = {}) {
111
- const root = protobuf.Root.fromJSON(json);
112
- if (options.int64 !== undefined) {
113
- setInt64Representation(root, options.int64);
217
+ function setEntry(metadata, key, value) {
218
+ Object.defineProperty(metadata, key, {
219
+ value,
220
+ enumerable: true,
221
+ writable: true,
222
+ configurable: true,
223
+ });
224
+ }
225
+
226
+ function writeString(writer, field, value) {
227
+ if (value != null && value !== "") {
228
+ writer.tag(field, WireType.LengthDelimited).string(value);
114
229
  }
115
- return root;
116
230
  }
117
231
 
118
- function scanMessage(messageType, bytes, depth) {
119
- if (depth > 64) {
120
- throw malformedWire("protobuf message nesting exceeded limit");
232
+ function writeInt32(writer, field, value) {
233
+ if (value != null && value !== 0) {
234
+ writer.tag(field, WireType.Varint).int32(value);
121
235
  }
236
+ }
122
237
 
123
- const cursor = { offset: 0 };
124
- while (cursor.offset < bytes.byteLength) {
125
- const tag = readVarint(bytes, cursor, "truncated protobuf field tag");
126
- const fieldNumber = Number(tag >> 3n);
127
- const wireType = Number(tag & 7n);
128
- if (fieldNumber === 0 || fieldNumber > MaxFieldNumber) {
129
- throw malformedWire("invalid protobuf field tag");
130
- }
238
+ function writeUint32(writer, field, value) {
239
+ if (value != null && value !== 0) {
240
+ writer.tag(field, WireType.Varint).uint32(value);
241
+ }
242
+ }
131
243
 
132
- const field = messageType.fieldsById?.[fieldNumber];
133
- if (field == null) {
134
- skipUnknownField(bytes, cursor, fieldNumber, wireType, depth);
135
- continue;
136
- }
244
+ function writeUint64(writer, field, value) {
245
+ if (value != null && value !== 0 && value !== 0n && value !== "0") {
246
+ writer.tag(field, WireType.Varint).uint64(value);
247
+ }
248
+ }
137
249
 
138
- if (!acceptedWireTypes(field).includes(wireType)) {
139
- throw malformedWire(`field ${field.name} used incompatible protobuf wire type ${wireType}`);
140
- }
141
- scanKnownField(field, bytes, cursor, wireType, depth);
250
+ function writeBool(writer, field, value) {
251
+ if (value) {
252
+ writer.tag(field, WireType.Varint).bool(true);
142
253
  }
143
254
  }
144
255
 
145
- function scanKnownField(field, bytes, cursor, wireType, depth) {
146
- if (wireType === 0) {
147
- const value = readVarint(bytes, cursor, `truncated protobuf field ${field.name}`);
148
- if (isUint32Field(field) && value > MaxUint32) {
149
- throw malformedWire(`protobuf field ${field.name} exceeded uint32`);
150
- }
256
+ /** Writes a `map<string, bytes>` field; both fields of every entry, even when empty. */
257
+ function writeMetadata(writer, field, metadata) {
258
+ if (metadata == null) {
151
259
  return;
152
260
  }
153
- if (wireType === 1) {
154
- skipFixed(bytes, cursor, 8, field.name);
155
- return;
261
+ for (const key of Object.keys(metadata)) {
262
+ writer
263
+ .tag(field, WireType.LengthDelimited)
264
+ .fork()
265
+ .tag(1, WireType.LengthDelimited)
266
+ .string(key)
267
+ .tag(2, WireType.LengthDelimited)
268
+ .bytes(metadata[key])
269
+ .join();
156
270
  }
157
- if (wireType === 5) {
158
- skipFixed(bytes, cursor, 4, field.name);
271
+ }
272
+
273
+ /** Writes a repeated DictionaryInfo field `{ id, fingerprint }`; an empty entry is still sent. */
274
+ function writeDictionaries(writer, field, infos) {
275
+ if (infos == null) {
159
276
  return;
160
277
  }
161
- if (wireType !== 2) {
162
- throw malformedWire(`unsupported protobuf wire type ${wireType}`);
163
- }
164
-
165
- const value = readLengthDelimited(bytes, cursor, `truncated protobuf field ${field.name}`);
166
- if (field.map) {
167
- scanMapEntry(field, value, depth + 1);
168
- } else if (field.resolvedType?.fieldsById != null) {
169
- scanMessage(field.resolvedType, value, depth + 1);
170
- } else if (field.type === "string") {
171
- validateUtf8(value, field.name);
172
- } else if (field.repeated && isPackableField(field)) {
173
- scanPackedField(field, value);
278
+ for (const info of infos) {
279
+ writer.tag(field, WireType.LengthDelimited).fork();
280
+ writeUint32(writer, 1, info.id);
281
+ if (info.fingerprint != null && info.fingerprint.byteLength > 0) {
282
+ writer.tag(2, WireType.LengthDelimited).bytes(info.fingerprint);
283
+ }
284
+ writer.join();
174
285
  }
175
286
  }
176
287
 
177
- function scanMapEntry(field, bytes, depth) {
178
- if (depth > 64) {
179
- throw malformedWire("protobuf message nesting exceeded limit");
180
- }
181
-
182
- const cursor = { offset: 0 };
183
- while (cursor.offset < bytes.byteLength) {
184
- const tag = readVarint(bytes, cursor, "truncated protobuf map entry tag");
185
- const fieldNumber = Number(tag >> 3n);
186
- const wireType = Number(tag & 7n);
187
- if (fieldNumber === 0 || fieldNumber > MaxFieldNumber) {
188
- throw malformedWire("invalid protobuf map entry tag");
189
- }
288
+ /**
289
+ * Reads the fields of one wire message, checking each known field's wire type and range. Every
290
+ * failure is an Error, which the frame layer reports as InvalidArgument.
291
+ */
292
+ class WireReader {
293
+ #bytes;
294
+ #reader;
190
295
 
191
- if (fieldNumber === 1) {
192
- scanSyntheticField(field.keyType, `${field.name}.key`, bytes, cursor, wireType, depth);
193
- } else if (fieldNumber === 2) {
194
- const messageType = field.resolvedType?.fieldsById != null ? field.resolvedType : undefined;
195
- scanSyntheticField(
196
- field.type,
197
- `${field.name}.value`,
198
- bytes,
199
- cursor,
200
- wireType,
201
- depth,
202
- messageType,
203
- );
204
- } else {
205
- skipUnknownField(bytes, cursor, fieldNumber, wireType, depth);
206
- }
296
+ constructor(bytes) {
297
+ this.#bytes = bytes;
298
+ this.#reader = new BinaryReader(bytes);
207
299
  }
208
- }
209
300
 
210
- /** Scans a map key or value: a scalar of `type`, or a message of `messageType` when it is set. */
211
- function scanSyntheticField(type, name, bytes, cursor, wireType, depth, messageType) {
212
- const expected = messageType === undefined ? scalarWireType(type) : 2;
213
- if (wireType !== expected) {
214
- throw malformedWire(`field ${name} used incompatible protobuf wire type ${wireType}`);
301
+ more() {
302
+ return this.#reader.pos < this.#reader.len;
215
303
  }
216
- if (wireType === 0) {
217
- const value = readVarint(bytes, cursor, `truncated protobuf field ${name}`);
218
- if (isUint32Type(type) && value > MaxUint32) {
219
- throw malformedWire(`protobuf field ${name} exceeded uint32`);
220
- }
221
- } else if (wireType === 1) {
222
- skipFixed(bytes, cursor, 8, name);
223
- } else if (wireType === 5) {
224
- skipFixed(bytes, cursor, 4, name);
225
- } else if (wireType === 2) {
226
- const value = readLengthDelimited(bytes, cursor, `truncated protobuf field ${name}`);
227
- if (messageType !== undefined) {
228
- scanMessage(messageType, value, depth + 1);
229
- } else if (type === "string") {
230
- validateUtf8(value, name);
231
- }
232
- } else {
233
- skipUnknownField(bytes, cursor, 0, wireType, depth);
304
+
305
+ /** The next field's number, at least 1, and wire type. */
306
+ tag() {
307
+ return this.#reader.tag();
234
308
  }
235
- }
236
309
 
237
- function scanPackedField(field, bytes) {
238
- const cursor = { offset: 0 };
239
- const wireType = scalarWireType(field.type);
240
- while (cursor.offset < bytes.byteLength) {
241
- if (wireType === 0) {
242
- const value = readVarint(bytes, cursor, `truncated packed protobuf field ${field.name}`);
243
- if (isUint32Field(field) && value > MaxUint32) {
244
- throw malformedWire(`protobuf field ${field.name} exceeded uint32`);
245
- }
246
- } else if (wireType === 1) {
247
- skipFixed(bytes, cursor, 8, field.name);
248
- } else if (wireType === 5) {
249
- skipFixed(bytes, cursor, 4, field.name);
250
- } else {
251
- throw malformedWire(`protobuf field ${field.name} cannot be packed`);
252
- }
310
+ string(wireType, name) {
311
+ expectWireType(wireType, WireType.LengthDelimited, name);
312
+ return this.#reader.string(true);
253
313
  }
254
- }
255
314
 
256
- function acceptedWireTypes(field) {
257
- // A map entry is a length-delimited message, whatever its value type.
258
- if (field.map) {
259
- return [2];
315
+ bytes(wireType, name) {
316
+ expectWireType(wireType, WireType.LengthDelimited, name);
317
+ return this.#reader.bytes();
260
318
  }
261
- const expected = field.resolvedType?.fieldsById != null ? 2 : scalarWireType(field.type);
262
- return field.repeated && isPackableField(field) ? [expected, 2] : [expected];
263
- }
264
319
 
265
- function scalarWireType(type) {
266
- switch (type) {
267
- case "double":
268
- case "fixed64":
269
- case "sfixed64":
270
- return 1;
271
- case "string":
272
- case "bytes":
273
- return 2;
274
- case "float":
275
- case "fixed32":
276
- case "sfixed32":
277
- return 5;
278
- default:
279
- return 0;
320
+ bool(wireType, name) {
321
+ expectWireType(wireType, WireType.Varint, name);
322
+ return this.#reader.bool();
280
323
  }
281
- }
282
324
 
283
- function isPackableField(field) {
284
- return (
285
- field.type !== "string" && field.type !== "bytes" && field.resolvedType?.fieldsById == null
286
- );
287
- }
325
+ int32(wireType, name) {
326
+ expectWireType(wireType, WireType.Varint, name);
327
+ return this.#reader.int32();
328
+ }
288
329
 
289
- function isUint32Field(field) {
290
- return isUint32Type(field.type) || field.resolvedType?.values != null;
291
- }
330
+ uint32(wireType, name) {
331
+ expectWireType(wireType, WireType.Varint, name);
332
+ return this.#uint32Value(name);
333
+ }
292
334
 
293
- function isUint32Type(type) {
294
- return type === "uint32" || type === "fixed32";
295
- }
335
+ uint64(wireType, name) {
336
+ expectWireType(wireType, WireType.Varint, name);
337
+ return BigInt(this.#reader.uint64());
338
+ }
296
339
 
297
- function readVarint(bytes, cursor, message) {
298
- let value = 0n;
299
- for (let index = 0; index < 10; index += 1) {
300
- if (cursor.offset >= bytes.byteLength) {
301
- throw malformedWire(message);
340
+ /** Appends one value, or a packed run of values, of a repeated uint32 field to `values`. */
341
+ repeatedUint32(wireType, values, name) {
342
+ if (wireType === WireType.Varint) {
343
+ values.push(this.#uint32Value(name));
344
+ return;
302
345
  }
303
- const byte = bytes[cursor.offset++];
304
- if (index === 9 && byte > 1) {
305
- throw malformedWire("protobuf varint exceeded 64 bits");
346
+ const packed = this.#nested(wireType, name);
347
+ while (packed.more()) {
348
+ values.push(packed.#uint32Value(name));
306
349
  }
307
- value |= BigInt(byte & 0x7f) << BigInt(index * 7);
308
- if (byte < 0x80) {
309
- if (value > MaxUint64) {
310
- throw malformedWire("protobuf varint exceeded 64 bits");
350
+ }
351
+
352
+ /** A DictionaryInfo message, `{ id, fingerprint }`. */
353
+ dictionaryInfo(wireType, name) {
354
+ const reader = this.#nested(wireType, name);
355
+ const info = { id: 0, fingerprint: new Uint8Array(0) };
356
+ while (reader.more()) {
357
+ const [field, fieldWireType] = reader.tag();
358
+ if (field === 1) {
359
+ info.id = reader.uint32(fieldWireType, `${name}.id`);
360
+ } else if (field === 2) {
361
+ info.fingerprint = reader.bytes(fieldWireType, `${name}.fingerprint`);
362
+ } else {
363
+ reader.skip(field, fieldWireType);
311
364
  }
312
- return value;
313
365
  }
366
+ return info;
314
367
  }
315
- throw malformedWire("protobuf varint exceeded 64 bits");
316
- }
317
368
 
318
- function readLengthDelimited(bytes, cursor, message) {
319
- const length = readVarint(bytes, cursor, message);
320
- if (length > BigInt(Number.MAX_SAFE_INTEGER)) {
321
- throw malformedWire(message);
369
+ /** A ConnectionMetadata message, `{ id, metadata }`. */
370
+ connectionMetadata(wireType, name) {
371
+ const reader = this.#nested(wireType, name);
372
+ const connection = { id: 0, metadata: emptyMetadata() };
373
+ while (reader.more()) {
374
+ const [field, fieldWireType] = reader.tag();
375
+ if (field === 1) {
376
+ connection.id = reader.uint32(fieldWireType, `${name}.id`);
377
+ } else if (field === 2) {
378
+ reader.metadataEntry(fieldWireType, connection.metadata, `${name}.metadata`);
379
+ } else {
380
+ reader.skip(field, fieldWireType);
381
+ }
382
+ }
383
+ return connection;
322
384
  }
323
- const end = cursor.offset + Number(length);
324
- if (!Number.isSafeInteger(end) || end > bytes.byteLength) {
325
- throw malformedWire(message);
385
+
386
+ /** Adds one `map<string, bytes>` entry to `metadata`; a later entry for a key replaces it. */
387
+ metadataEntry(wireType, metadata, name) {
388
+ const reader = this.#nested(wireType, name);
389
+ let key = "";
390
+ let value = new Uint8Array(0);
391
+ while (reader.more()) {
392
+ const [field, fieldWireType] = reader.tag();
393
+ if (field === 1) {
394
+ key = reader.string(fieldWireType, `${name}.key`);
395
+ } else if (field === 2) {
396
+ value = reader.bytes(fieldWireType, `${name}.value`);
397
+ } else {
398
+ reader.skip(field, fieldWireType);
399
+ }
400
+ }
401
+ setEntry(metadata, key, value);
326
402
  }
327
- const value = bytes.subarray(cursor.offset, end);
328
- cursor.offset = end;
329
- return value;
330
- }
331
403
 
332
- function skipUnknownField(bytes, cursor, fieldNumber, wireType, depth) {
333
- switch (wireType) {
334
- case 0:
335
- readVarint(bytes, cursor, "truncated protobuf varint");
336
- return;
337
- case 1:
338
- skipFixed(bytes, cursor, 8, "unknown");
339
- return;
340
- case 2:
341
- readLengthDelimited(bytes, cursor, "truncated protobuf field");
342
- return;
343
- case 3:
344
- skipGroup(bytes, cursor, fieldNumber, depth + 1);
345
- return;
346
- case 5:
347
- skipFixed(bytes, cursor, 4, "unknown");
348
- return;
349
- default:
350
- throw malformedWire(`unsupported protobuf wire type ${wireType}`);
404
+ /** Skips an unknown field of any wire type. */
405
+ skip(field, wireType, depth = 0) {
406
+ switch (wireType) {
407
+ case WireType.Varint:
408
+ this.#skipVarint();
409
+ return;
410
+ case WireType.StartGroup:
411
+ this.#skipGroup(field, depth + 1);
412
+ return;
413
+ case WireType.EndGroup:
414
+ throw new Error("protobuf end group tag without a start group");
415
+ default:
416
+ this.#reader.skip(wireType, field);
417
+ }
351
418
  }
352
- }
353
419
 
354
- function skipGroup(bytes, cursor, fieldNumber, depth) {
355
- if (depth > 64) {
356
- throw malformedWire("protobuf group nesting exceeded limit");
420
+ #skipGroup(field, depth) {
421
+ if (depth > MaxGroupDepth) {
422
+ throw new Error("protobuf group nesting exceeded limit");
423
+ }
424
+ while (this.more()) {
425
+ const [nested, wireType] = this.tag();
426
+ if (wireType === WireType.EndGroup) {
427
+ if (nested !== field) {
428
+ throw new Error("protobuf group end tag did not match start tag");
429
+ }
430
+ return;
431
+ }
432
+ this.skip(nested, wireType, depth);
433
+ }
434
+ throw new Error("unterminated protobuf group");
357
435
  }
358
- while (cursor.offset < bytes.byteLength) {
359
- const tag = readVarint(bytes, cursor, "truncated protobuf group tag");
360
- const nestedField = Number(tag >> 3n);
361
- const wireType = Number(tag & 7n);
362
- if (wireType === 4) {
363
- if (nestedField !== fieldNumber) {
364
- throw malformedWire("protobuf group end tag did not match start tag");
436
+
437
+ /** Skips a varint of at most 64 bits (10 bytes, the last at most 1). */
438
+ #skipVarint() {
439
+ const bytes = this.#bytes;
440
+ const reader = this.#reader;
441
+ for (let index = 0; index < 10; index += 1) {
442
+ if (reader.pos >= reader.len) {
443
+ throw new Error("truncated protobuf varint");
444
+ }
445
+ const byte = bytes[reader.pos++];
446
+ if (byte < 0x80) {
447
+ if (index === 9 && byte > 1) {
448
+ break;
449
+ }
450
+ return;
365
451
  }
366
- return;
367
452
  }
368
- skipUnknownField(bytes, cursor, nestedField, wireType, depth);
453
+ throw new Error("protobuf varint exceeded 64 bits");
369
454
  }
370
- throw malformedWire("unterminated protobuf group");
371
- }
372
455
 
373
- function skipFixed(bytes, cursor, length, name) {
374
- const end = cursor.offset + length;
375
- if (end > bytes.byteLength) {
376
- throw malformedWire(`truncated protobuf field ${name}`);
456
+ /** Reads a varint that must fit in 32 bits: at most 5 bytes, the last at most 0x0f. */
457
+ #uint32Value(name) {
458
+ const reader = this.#reader;
459
+ const start = reader.pos;
460
+ const value = reader.uint32();
461
+ const length = reader.pos - start;
462
+ if (length > 5 || (length === 5 && this.#bytes[reader.pos - 1] > 0x0f)) {
463
+ throw new Error(`protobuf field ${name} exceeded uint32`);
464
+ }
465
+ return value;
377
466
  }
378
- cursor.offset = end;
379
- }
380
467
 
381
- function validateUtf8(bytes, name) {
382
- try {
383
- fatalUtf8Decoder.decode(bytes);
384
- } catch {
385
- throw malformedWire(`protobuf field ${name} was not valid UTF-8`);
468
+ /** A reader over the length-delimited value of field `name`. */
469
+ #nested(wireType, name) {
470
+ expectWireType(wireType, WireType.LengthDelimited, name);
471
+ return new WireReader(this.#reader.bytes());
386
472
  }
387
473
  }
388
474
 
389
- function malformedWire(message) {
390
- return new Error(message);
475
+ function expectWireType(wireType, expected, name) {
476
+ if (wireType !== expected) {
477
+ throw new Error(`field ${name} used incompatible protobuf wire type ${wireType}`);
478
+ }
391
479
  }