@trevrpc/trevrpc-js 0.2.3 → 1.1.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/README.md +127 -46
- package/package.json +12 -45
- package/src/bytes.js +10 -0
- package/src/call.js +520 -0
- package/src/channel.js +258 -288
- package/src/client.js +543 -1254
- package/src/datagram.js +336 -0
- package/src/framing.js +151 -548
- package/src/generator.js +66 -109
- package/src/index.d.ts +235 -463
- package/src/index.js +4 -3
- package/src/qmux/frames.js +262 -0
- package/src/qmux/params.js +104 -0
- package/src/qmux/session.js +739 -0
- package/src/race.js +90 -0
- package/src/status.js +24 -70
- package/src/varint.js +73 -0
- package/src/wire.js +21 -89
- package/examples/greeter/README.md +0 -35
- package/examples/greeter/client.js +0 -172
- package/examples/greeter/greeter.node.trevrpc.d.ts +0 -53
- package/examples/greeter/greeter.node.trevrpc.js +0 -9
- package/examples/greeter/greeter.proto +0 -18
- package/examples/greeter/greeter.trevrpc.d.ts +0 -55
- package/examples/greeter/greeter.trevrpc.js +0 -103
- package/examples/greeter/index.html +0 -140
- package/examples/greeter/static-server.js +0 -168
- package/src/advanced.d.ts +0 -29
- package/src/advanced.js +0 -1
- package/src/browser-channel.js +0 -49
- package/src/connect.js +0 -6
- package/src/connect.node.js +0 -6
- package/src/index.node.d.ts +0 -10
- package/src/index.node.js +0 -3
- package/src/native-loader.js +0 -118
- package/src/node-advanced.d.ts +0 -27
- package/src/node-advanced.js +0 -1
- package/src/node-generated.d.ts +0 -24
- package/src/node-generated.js +0 -126
- package/src/node-index.d.ts +0 -1
- package/src/node-index.js +0 -7
- package/src/node.d.ts +0 -298
- package/src/node.js +0 -1456
- package/src/runtime.js +0 -66
- package/src/server-response.js +0 -49
- package/src/timer.js +0 -29
- package/src/webtransport.js +0 -639
package/src/framing.js
CHANGED
|
@@ -1,618 +1,221 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
preflightWireMessage,
|
|
6
|
-
validateWireMessage,
|
|
7
|
-
} from "./wire.js";
|
|
1
|
+
import { byteView } from "./bytes.js";
|
|
2
|
+
import { internal, invalidArgument, resourceExhausted, unavailable } from "./status.js";
|
|
3
|
+
import { readVarint, varintLength, writeVarint } from "./varint.js";
|
|
4
|
+
import { RpcHeaders, RpcRequest, RpcStatus, preflightWireMessage } from "./wire.js";
|
|
8
5
|
|
|
9
6
|
export const DefaultMaxFrameSize = 4 * 1024 * 1024;
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
const
|
|
8
|
+
/** Frame types; every other type is unknown and skipped by receivers. */
|
|
9
|
+
export const FrameType = Object.freeze({
|
|
10
|
+
Message: 0,
|
|
11
|
+
Request: 1,
|
|
12
|
+
Headers: 2,
|
|
13
|
+
Status: 3,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const KnownFrameTypes = new Set(Object.values(FrameType));
|
|
13
17
|
const EmptyBytes = new Uint8Array(0);
|
|
14
|
-
const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
|
|
15
18
|
|
|
16
19
|
/** Encodes a protobuf message body. */
|
|
17
20
|
export function marshalMessage(messageType, message) {
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
try {
|
|
22
|
+
return messageType.encode(messageType.fromObject(message)).finish();
|
|
23
|
+
} catch (error) {
|
|
24
|
+
throw invalidArgument(
|
|
25
|
+
`failed to encode protobuf message: ${error?.message ?? String(error)}`,
|
|
26
|
+
error,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
20
29
|
}
|
|
21
30
|
|
|
22
|
-
/** Decodes a protobuf message body. */
|
|
31
|
+
/** Decodes a protobuf message body, rejecting malformed wire data. */
|
|
23
32
|
export function unmarshalMessage(messageType, body) {
|
|
24
33
|
try {
|
|
25
34
|
preflightWireMessage(messageType, body);
|
|
26
35
|
return messageType.decode(body);
|
|
27
36
|
} catch (error) {
|
|
28
|
-
|
|
37
|
+
throw invalidArgument(
|
|
29
38
|
`failed to decode protobuf message: ${error?.message ?? String(error)}`,
|
|
39
|
+
error,
|
|
30
40
|
);
|
|
31
|
-
failure.reason = error?.reason ?? "malformed_protobuf";
|
|
32
|
-
failure.cause = error;
|
|
33
|
-
throw failure;
|
|
34
41
|
}
|
|
35
42
|
}
|
|
36
43
|
|
|
37
|
-
/** Encodes
|
|
38
|
-
export function encodeFrame(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const frame = new Uint8Array(FrameHeaderLength + body.byteLength);
|
|
45
|
-
const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
|
|
46
|
-
view.setUint32(0, body.byteLength, false);
|
|
47
|
-
frame.set(body, FrameHeaderLength);
|
|
44
|
+
/** Encodes one frame carrying an already-encoded payload. */
|
|
45
|
+
export function encodeFrame(type, payload, maxFrameSize = DefaultMaxFrameSize) {
|
|
46
|
+
checkFrameLength(payload.byteLength, maxFrameSize);
|
|
47
|
+
const frame = new Uint8Array(frameSize(type, payload.byteLength));
|
|
48
|
+
writeFrame(frame, 0, type, payload);
|
|
48
49
|
return frame;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
/** Encodes
|
|
52
|
-
export function
|
|
53
|
-
return
|
|
52
|
+
/** Encodes a MESSAGE frame; the payload is the raw protobuf message. */
|
|
53
|
+
export function encodeMessageFrame(body, maxFrameSize = DefaultMaxFrameSize) {
|
|
54
|
+
return encodeFrame(FrameType.Message, body, maxFrameSize);
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const byteBodies = Array.from({ length: range.end - range.start });
|
|
66
|
-
for (let index = range.start; index < range.end; index += 1) {
|
|
67
|
-
const body = byteView(range.bodies[index]);
|
|
68
|
-
byteBodies[index - range.start] = body;
|
|
69
|
-
const bodyLength = messageStreamFrameBodyLength(body.byteLength);
|
|
70
|
-
if (bodyLength > maxFrameSize) {
|
|
71
|
-
throw new FrameTooLargeError(bodyLength, maxFrameSize);
|
|
72
|
-
}
|
|
73
|
-
totalLength += FrameHeaderLength + bodyLength;
|
|
57
|
+
/**
|
|
58
|
+
* Encodes a REQUEST frame, followed by a MESSAGE frame when message is given, in one buffer so
|
|
59
|
+
* both go out in a single write.
|
|
60
|
+
*/
|
|
61
|
+
export function encodeRequestFrame(request, message, maxFrameSize = DefaultMaxFrameSize) {
|
|
62
|
+
const payload = marshalMessage(RpcRequest, request);
|
|
63
|
+
checkFrameLength(payload.byteLength, maxFrameSize);
|
|
64
|
+
if (message === undefined) {
|
|
65
|
+
return encodeFrame(FrameType.Request, payload, maxFrameSize);
|
|
74
66
|
}
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const bodyLength = messageStreamFrameBodyLength(body.byteLength);
|
|
81
|
-
view.setUint32(offset, bodyLength, false);
|
|
82
|
-
offset += FrameHeaderLength;
|
|
83
|
-
if (body.byteLength > 0) {
|
|
84
|
-
frames[offset++] = StreamFrameBodyTag;
|
|
85
|
-
offset = appendVarint(frames, offset, body.byteLength);
|
|
86
|
-
frames.set(body, offset);
|
|
87
|
-
offset += body.byteLength;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return frames;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Encodes each streaming message frame separately for vectored writers. */
|
|
95
|
-
export function encodeMessageStreamFrameParts(
|
|
96
|
-
bodies,
|
|
97
|
-
maxFrameSize = DefaultMaxFrameSize,
|
|
98
|
-
start = 0,
|
|
99
|
-
end = undefined,
|
|
100
|
-
) {
|
|
101
|
-
const range = bodyRange(bodies, start, end);
|
|
102
|
-
const frames = Array.from({ length: range.end - range.start });
|
|
103
|
-
for (let index = range.start; index < range.end; index += 1) {
|
|
104
|
-
frames[index - range.start] = encodeMessageStreamFrame(range.bodies[index], maxFrameSize);
|
|
105
|
-
}
|
|
106
|
-
return frames;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Decodes a protobuf message from a TrevRPC frame body. */
|
|
110
|
-
export function decodeFrame(messageType, body) {
|
|
111
|
-
return unmarshalMessage(messageType, body);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** Decodes a streaming RPC frame body using a fast path for common message frames. */
|
|
115
|
-
export function decodeStreamFrameBody(body) {
|
|
116
|
-
const bytes = byteView(body);
|
|
117
|
-
try {
|
|
118
|
-
preflightWireMessage(RpcStreamFrame, bytes);
|
|
119
|
-
} catch (error) {
|
|
120
|
-
const failure = invalidArgument(
|
|
121
|
-
`failed to decode protobuf message: ${error?.message ?? String(error)}`,
|
|
122
|
-
);
|
|
123
|
-
failure.reason = error?.reason ?? "malformed_protobuf";
|
|
124
|
-
failure.cause = error;
|
|
125
|
-
throw failure;
|
|
126
|
-
}
|
|
127
|
-
if (bytes.byteLength === 0) {
|
|
128
|
-
return streamFrameMessage(EmptyBytes);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const cursor = { offset: 0 };
|
|
132
|
-
let kind = RpcStreamFrameKind.Message;
|
|
133
|
-
let status = 0;
|
|
134
|
-
let message = "";
|
|
135
|
-
let frameBody = new Uint8Array(0);
|
|
136
|
-
|
|
137
|
-
while (cursor.offset < bytes.byteLength) {
|
|
138
|
-
const tag = consumeVarint(bytes, cursor, "invalid stream frame field tag");
|
|
139
|
-
if (tag === 0) {
|
|
140
|
-
throw invalidArgument("invalid stream frame field tag");
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const field = Math.floor(tag / 8);
|
|
144
|
-
const wireType = tag % 8;
|
|
145
|
-
switch (field) {
|
|
146
|
-
case 1:
|
|
147
|
-
requireWireType(wireType, 0, "invalid stream frame kind wire type");
|
|
148
|
-
kind = consumeVarint(bytes, cursor, "truncated stream frame kind");
|
|
149
|
-
break;
|
|
150
|
-
case 2:
|
|
151
|
-
requireWireType(wireType, 0, "invalid stream frame status wire type");
|
|
152
|
-
status = consumeVarint(bytes, cursor, "truncated stream frame status");
|
|
153
|
-
break;
|
|
154
|
-
case 3:
|
|
155
|
-
requireWireType(wireType, 2, "invalid stream frame message wire type");
|
|
156
|
-
message = decodeUtf8(consumeLengthDelimited(bytes, cursor, "invalid stream frame message"));
|
|
157
|
-
break;
|
|
158
|
-
case 4:
|
|
159
|
-
requireWireType(wireType, 2, "invalid stream frame body wire type");
|
|
160
|
-
frameBody = consumeLengthDelimited(bytes, cursor, "invalid stream frame body");
|
|
161
|
-
break;
|
|
162
|
-
case 5: {
|
|
163
|
-
const frame = decodeFrame(RpcStreamFrame, bytes);
|
|
164
|
-
validateDecodedWireMessage(RpcStreamFrame, frame);
|
|
165
|
-
return frame;
|
|
166
|
-
}
|
|
167
|
-
default:
|
|
168
|
-
skipProtoField(bytes, cursor, wireType);
|
|
169
|
-
break;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const frame = { kind, status, message, body: frameBody, metadata: {} };
|
|
174
|
-
validateDecodedWireMessage(RpcStreamFrame, frame);
|
|
67
|
+
checkFrameLength(message.byteLength, maxFrameSize);
|
|
68
|
+
const requestSize = frameSize(FrameType.Request, payload.byteLength);
|
|
69
|
+
const frame = new Uint8Array(requestSize + frameSize(FrameType.Message, message.byteLength));
|
|
70
|
+
writeFrame(frame, requestSize, FrameType.Message, message);
|
|
71
|
+
writeFrame(frame, 0, FrameType.Request, payload);
|
|
175
72
|
return frame;
|
|
176
73
|
}
|
|
177
74
|
|
|
178
|
-
/**
|
|
179
|
-
export
|
|
180
|
-
|
|
75
|
+
/** Encodes a HEADERS frame from RpcHeaders fields. */
|
|
76
|
+
export function encodeHeadersFrame(headers, maxFrameSize = DefaultMaxFrameSize) {
|
|
77
|
+
return encodeFrame(FrameType.Headers, marshalMessage(RpcHeaders, headers), maxFrameSize);
|
|
181
78
|
}
|
|
182
79
|
|
|
183
|
-
/**
|
|
184
|
-
export
|
|
185
|
-
|
|
186
|
-
bodies,
|
|
187
|
-
maxFrameSize = DefaultMaxFrameSize,
|
|
188
|
-
start = 0,
|
|
189
|
-
end = undefined,
|
|
190
|
-
) {
|
|
191
|
-
if (typeof writer.writev === "function") {
|
|
192
|
-
await writer.writev(encodeMessageStreamFrameParts(bodies, maxFrameSize, start, end));
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
await writer.write(encodeMessageStreamFrames(bodies, maxFrameSize, start, end));
|
|
80
|
+
/** Encodes a STATUS frame from RpcStatus fields. */
|
|
81
|
+
export function encodeStatusFrame(status, maxFrameSize = DefaultMaxFrameSize) {
|
|
82
|
+
return encodeFrame(FrameType.Status, marshalMessage(RpcStatus, status), maxFrameSize);
|
|
197
83
|
}
|
|
198
84
|
|
|
199
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* Reads frames from a byte stream reader. Frame headers may be split across chunks; payloads
|
|
87
|
+
* inside one chunk are returned as views without copying. Unknown frame types are skipped.
|
|
88
|
+
*/
|
|
200
89
|
export class FrameReader {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
this.chunks = [];
|
|
205
|
-
this.chunkHead = 0;
|
|
206
|
-
this.buffered = 0;
|
|
207
|
-
}
|
|
90
|
+
#reader;
|
|
91
|
+
/** Bytes received from the reader but not yet consumed. */
|
|
92
|
+
#pending = EmptyBytes;
|
|
208
93
|
|
|
209
|
-
/**
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const length = frameBodyLength(header, maxFrameSize);
|
|
225
|
-
const body = await this.readExact(length, false);
|
|
226
|
-
return decodeFrame(messageType, body);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/** Reads and decodes one streaming RPC frame, or returns null when already at EOF. */
|
|
230
|
-
async readStreamFrameOrEOF(maxFrameSize = DefaultMaxFrameSize) {
|
|
231
|
-
const header = await this.readExact(FrameHeaderLength, true);
|
|
232
|
-
if (header == null) {
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const length = frameBodyLength(header, maxFrameSize);
|
|
237
|
-
const body = await this.readExact(length, false);
|
|
238
|
-
return decodeStreamFrameBody(body);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/** Reads one streaming RPC frame, then drains complete frames already buffered. */
|
|
242
|
-
async readStreamFrameBatchOrEOF(maxBatch = 32, maxFrameSize = DefaultMaxFrameSize) {
|
|
243
|
-
const first = await this.readStreamFrameOrEOF(maxFrameSize);
|
|
244
|
-
if (first == null) {
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const limit = Math.max(1, Math.floor(maxBatch));
|
|
249
|
-
const frames = [first];
|
|
250
|
-
while (frames.length < limit) {
|
|
251
|
-
const body = this.tryReadFrameBody(maxFrameSize);
|
|
252
|
-
if (body === undefined) {
|
|
253
|
-
break;
|
|
94
|
+
/** Creates a frame reader over a ReadableStream reader. */
|
|
95
|
+
constructor(reader) {
|
|
96
|
+
this.#reader = reader;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Reads the next known frame as `{ type, payload }`, or returns null when the stream ended
|
|
101
|
+
* cleanly between frames. The declared length of every frame, unknown ones included, is checked
|
|
102
|
+
* against maxFrameSize before any payload is read or allocated.
|
|
103
|
+
*/
|
|
104
|
+
async readFrame(maxFrameSize = DefaultMaxFrameSize) {
|
|
105
|
+
for (;;) {
|
|
106
|
+
const type = await this.#readVarint(true);
|
|
107
|
+
if (type === null) {
|
|
108
|
+
return null;
|
|
254
109
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
async readStreamMessageBodyBatchOrEOF(maxBatch = 32, maxFrameSize = DefaultMaxFrameSize) {
|
|
262
|
-
const body = await this.readRawFrameBodyOrEOF(maxFrameSize);
|
|
263
|
-
if (body == null) {
|
|
264
|
-
return null;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const limit = Math.max(1, Math.floor(maxBatch));
|
|
268
|
-
const result = { bodies: [], status: null };
|
|
269
|
-
pushStreamBodyOrStatus(result, body);
|
|
270
|
-
while (result.status == null && result.bodies.length < limit) {
|
|
271
|
-
const nextBody = this.tryReadFrameBody(maxFrameSize);
|
|
272
|
-
if (nextBody === undefined) {
|
|
273
|
-
break;
|
|
110
|
+
const length = await this.#readVarint(false);
|
|
111
|
+
checkFrameLength(length, maxFrameSize);
|
|
112
|
+
if (!KnownFrameTypes.has(type)) {
|
|
113
|
+
// Types of 2^53 and above are bigints, which are never known.
|
|
114
|
+
await this.#skip(length);
|
|
115
|
+
continue;
|
|
274
116
|
}
|
|
275
|
-
|
|
117
|
+
return { type, payload: await this.#readExact(length) };
|
|
276
118
|
}
|
|
277
|
-
|
|
278
|
-
return result;
|
|
279
119
|
}
|
|
280
120
|
|
|
281
|
-
/**
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Resolves when the rest of the stream consists of unknown frames and a clean end; rejects if
|
|
123
|
+
* a known frame or a malformed frame arrives first.
|
|
124
|
+
*/
|
|
125
|
+
async expectEnd(maxFrameSize = DefaultMaxFrameSize) {
|
|
126
|
+
const frame = await this.readFrame(maxFrameSize);
|
|
127
|
+
if (frame !== null) {
|
|
128
|
+
throw internal(`frame of type ${frame.type} after the final STATUS frame`);
|
|
286
129
|
}
|
|
287
|
-
|
|
288
|
-
return this.readExact(frameBodyLength(header, maxFrameSize), false);
|
|
289
130
|
}
|
|
290
131
|
|
|
291
|
-
/** Reads a
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const header = this.peek(FrameHeaderLength);
|
|
298
|
-
const length = frameBodyLength(header, maxFrameSize);
|
|
299
|
-
if (this.buffered < FrameHeaderLength + length) {
|
|
300
|
-
return undefined;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
this.consume(FrameHeaderLength);
|
|
304
|
-
return this.consume(length);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/** Reads exactly size bytes from the underlying reader. */
|
|
308
|
-
async readExact(size, allowEofAtStart) {
|
|
309
|
-
if (size === 0) {
|
|
310
|
-
return EmptyBytes;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
while (this.buffered < size) {
|
|
314
|
-
const { value, done } = await this.reader.read();
|
|
132
|
+
/** Reads a varint; null if allowEnd and the stream ended before its first byte. */
|
|
133
|
+
async #readVarint(allowEnd) {
|
|
134
|
+
while (this.#pending.byteLength === 0) {
|
|
135
|
+
const { value, done } = await this.#nextChunk();
|
|
315
136
|
if (done) {
|
|
316
|
-
if (
|
|
137
|
+
if (allowEnd) {
|
|
317
138
|
return null;
|
|
318
139
|
}
|
|
319
|
-
|
|
320
|
-
const error = unavailable("transport unavailable: stream ended in the middle of a frame");
|
|
321
|
-
error.reason = "incomplete_frame";
|
|
322
|
-
throw error;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
if (value == null || value.byteLength === 0) {
|
|
326
|
-
continue;
|
|
140
|
+
throw unavailable("stream ended before a complete frame");
|
|
327
141
|
}
|
|
328
|
-
|
|
329
|
-
const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
330
|
-
this.chunks.push(bytes);
|
|
331
|
-
this.buffered += bytes.byteLength;
|
|
142
|
+
this.#pending = value;
|
|
332
143
|
}
|
|
333
|
-
|
|
334
|
-
return
|
|
144
|
+
const bytes = await this.#readExact(1 << (this.#pending[0] >> 6));
|
|
145
|
+
return readVarint(bytes, 0).value;
|
|
335
146
|
}
|
|
336
147
|
|
|
337
|
-
/**
|
|
338
|
-
|
|
339
|
-
if (
|
|
340
|
-
return
|
|
148
|
+
/** Returns exactly size bytes; copies only when the read spans chunks. */
|
|
149
|
+
async #readExact(size) {
|
|
150
|
+
if (this.#pending.byteLength >= size) {
|
|
151
|
+
return this.#take(size);
|
|
341
152
|
}
|
|
342
153
|
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
154
|
+
const out = new Uint8Array(size);
|
|
155
|
+
let filled = this.#pending.byteLength;
|
|
156
|
+
out.set(this.#pending);
|
|
157
|
+
this.#pending = EmptyBytes;
|
|
158
|
+
while (filled < size) {
|
|
159
|
+
const { value, done } = await this.#nextChunk();
|
|
160
|
+
if (done) {
|
|
161
|
+
throw unavailable("stream ended before a complete frame");
|
|
351
162
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
163
|
+
const count = Math.min(value.byteLength, size - filled);
|
|
164
|
+
out.set(value.subarray(0, count), filled);
|
|
165
|
+
filled += count;
|
|
166
|
+
this.#pending = value.subarray(count);
|
|
167
|
+
}
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Discards size bytes without buffering them. */
|
|
172
|
+
async #skip(size) {
|
|
173
|
+
let left = size;
|
|
174
|
+
while (left > 0) {
|
|
175
|
+
if (this.#pending.byteLength === 0) {
|
|
176
|
+
const { value, done } = await this.#nextChunk();
|
|
177
|
+
if (done) {
|
|
178
|
+
throw unavailable("stream ended before a complete frame");
|
|
179
|
+
}
|
|
180
|
+
this.#pending = value;
|
|
370
181
|
}
|
|
182
|
+
const count = Math.min(this.#pending.byteLength, left);
|
|
183
|
+
this.#pending = this.#pending.subarray(count);
|
|
184
|
+
left -= count;
|
|
371
185
|
}
|
|
372
|
-
|
|
373
|
-
this.buffered -= size;
|
|
374
|
-
this.compactChunks();
|
|
375
|
-
return result;
|
|
376
186
|
}
|
|
377
187
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
return first.subarray(0, size);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const result = new Uint8Array(size);
|
|
390
|
-
let offset = 0;
|
|
391
|
-
|
|
392
|
-
for (let index = this.chunkHead; index < this.chunks.length; index += 1) {
|
|
393
|
-
const chunk = this.chunks[index];
|
|
394
|
-
if (offset >= size) {
|
|
395
|
-
break;
|
|
188
|
+
async #nextChunk() {
|
|
189
|
+
for (;;) {
|
|
190
|
+
const { value, done } = await this.#reader.read();
|
|
191
|
+
if (done) {
|
|
192
|
+
return { done: true };
|
|
193
|
+
}
|
|
194
|
+
if (value != null && value.byteLength > 0) {
|
|
195
|
+
return { done: false, value: byteView(value) };
|
|
396
196
|
}
|
|
397
|
-
const count = Math.min(chunk.byteLength, size - offset);
|
|
398
|
-
result.set(chunk.subarray(0, count), offset);
|
|
399
|
-
offset += count;
|
|
400
197
|
}
|
|
401
|
-
|
|
402
|
-
return result;
|
|
403
198
|
}
|
|
404
199
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if (this.chunkHead === this.chunks.length) {
|
|
410
|
-
this.chunks.length = 0;
|
|
411
|
-
this.chunkHead = 0;
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
if (this.chunkHead >= 32 && this.chunkHead * 2 >= this.chunks.length) {
|
|
415
|
-
this.chunks.splice(0, this.chunkHead);
|
|
416
|
-
this.chunkHead = 0;
|
|
417
|
-
}
|
|
200
|
+
#take(size) {
|
|
201
|
+
const bytes = this.#pending.subarray(0, size);
|
|
202
|
+
this.#pending = this.#pending.subarray(size);
|
|
203
|
+
return bytes;
|
|
418
204
|
}
|
|
419
205
|
}
|
|
420
206
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if (messageBody !== undefined) {
|
|
424
|
-
result.bodies.push(messageBody);
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const frame = decodeStreamFrameBody(body);
|
|
429
|
-
switch (frame.kind) {
|
|
430
|
-
case RpcStreamFrameKind.Message:
|
|
431
|
-
result.bodies.push(frame.body ?? EmptyBytes);
|
|
432
|
-
break;
|
|
433
|
-
case RpcStreamFrameKind.Status:
|
|
434
|
-
result.status = frame;
|
|
435
|
-
break;
|
|
436
|
-
default:
|
|
437
|
-
throw invalidArgument("stream frame contained an unknown frame kind");
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
function tryDecodeStreamMessageBody(body) {
|
|
442
|
-
const bytes = byteView(body);
|
|
443
|
-
if (bytes.byteLength === 0) {
|
|
444
|
-
return EmptyBytes;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
if (bytes[0] !== StreamFrameBodyTag) {
|
|
448
|
-
return undefined;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
const cursor = { offset: 1 };
|
|
452
|
-
const length = consumeVarint(bytes, cursor, "invalid stream frame body");
|
|
453
|
-
const end = cursor.offset + length;
|
|
454
|
-
if (end !== bytes.byteLength) {
|
|
455
|
-
return undefined;
|
|
456
|
-
}
|
|
457
|
-
return bytes.subarray(cursor.offset, end);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/** Decodes and validates the body length stored in a TrevRPC frame header. */
|
|
461
|
-
export function frameBodyLength(header, maxFrameSize = DefaultMaxFrameSize) {
|
|
462
|
-
const view = new DataView(header.buffer, header.byteOffset, header.byteLength);
|
|
463
|
-
const length = view.getUint32(0, false);
|
|
207
|
+
/** Rejects payload lengths above the limit; length may be a bigint for huge varints. */
|
|
208
|
+
function checkFrameLength(length, maxFrameSize) {
|
|
464
209
|
if (length > maxFrameSize) {
|
|
465
|
-
throw
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
return length;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
function validateDecodedWireMessage(messageType, message) {
|
|
472
|
-
try {
|
|
473
|
-
validateWireMessage(messageType, message);
|
|
474
|
-
} catch (error) {
|
|
475
|
-
const failure = invalidArgument(error?.message ?? String(error));
|
|
476
|
-
failure.reason = error?.reason;
|
|
477
|
-
failure.cause = error;
|
|
478
|
-
throw failure;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
function prepareMessage(messageType, message) {
|
|
483
|
-
const prepared =
|
|
484
|
-
typeof messageType.fromObject === "function" ? messageType.fromObject(message) : message;
|
|
485
|
-
const error = typeof messageType.verify === "function" ? messageType.verify(prepared) : null;
|
|
486
|
-
if (error != null) {
|
|
487
|
-
throw invalidArgument(`failed to encode protobuf message: ${error}`);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
return prepared;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
function streamFrameMessage(body) {
|
|
494
|
-
return {
|
|
495
|
-
kind: RpcStreamFrameKind.Message,
|
|
496
|
-
status: 0,
|
|
497
|
-
message: "",
|
|
498
|
-
body,
|
|
499
|
-
metadata: {},
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
function byteView(value) {
|
|
504
|
-
if (value instanceof Uint8Array) {
|
|
505
|
-
return value;
|
|
210
|
+
throw resourceExhausted(`frame length ${length} exceeds maximum ${maxFrameSize}`);
|
|
506
211
|
}
|
|
507
|
-
if (ArrayBuffer.isView(value)) {
|
|
508
|
-
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
509
|
-
}
|
|
510
|
-
return new Uint8Array(value ?? 0);
|
|
511
212
|
}
|
|
512
213
|
|
|
513
|
-
function
|
|
514
|
-
|
|
515
|
-
const from = clampRangeIndex(start, source.length);
|
|
516
|
-
const to = Math.max(from, clampRangeIndex(end ?? source.length, source.length));
|
|
517
|
-
return { bodies: source, start: from, end: to };
|
|
214
|
+
function frameSize(type, length) {
|
|
215
|
+
return varintLength(type) + varintLength(length) + length;
|
|
518
216
|
}
|
|
519
217
|
|
|
520
|
-
function
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
}
|
|
524
|
-
return Math.min(length, Math.max(0, Math.floor(value)));
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function messageStreamFrameBodyLength(bodyLength) {
|
|
528
|
-
return bodyLength === 0 ? 0 : 1 + varintLength(bodyLength) + bodyLength;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
function appendVarint(output, offset, value) {
|
|
532
|
-
let remaining = value;
|
|
533
|
-
while (remaining >= 0x80) {
|
|
534
|
-
output[offset++] = (remaining % 0x80) | 0x80;
|
|
535
|
-
remaining = Math.floor(remaining / 0x80);
|
|
536
|
-
}
|
|
537
|
-
output[offset++] = remaining;
|
|
538
|
-
return offset;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
function varintLength(value) {
|
|
542
|
-
let remaining = value;
|
|
543
|
-
let length = 1;
|
|
544
|
-
while (remaining >= 0x80) {
|
|
545
|
-
remaining = Math.floor(remaining / 0x80);
|
|
546
|
-
length += 1;
|
|
547
|
-
}
|
|
548
|
-
return length;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
function consumeVarint(data, cursor, message) {
|
|
552
|
-
let value = 0;
|
|
553
|
-
let multiplier = 1;
|
|
554
|
-
for (let index = 0; index < 10; index += 1) {
|
|
555
|
-
if (cursor.offset >= data.byteLength) {
|
|
556
|
-
throw invalidArgument(message);
|
|
557
|
-
}
|
|
558
|
-
const byte = data[cursor.offset++];
|
|
559
|
-
value += (byte & 0x7f) * multiplier;
|
|
560
|
-
if (byte < 0x80) {
|
|
561
|
-
return value;
|
|
562
|
-
}
|
|
563
|
-
multiplier *= 0x80;
|
|
564
|
-
}
|
|
565
|
-
throw invalidArgument("stream frame varint exceeded 64 bits");
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
function requireWireType(actual, expected, message) {
|
|
569
|
-
if (actual !== expected) {
|
|
570
|
-
throw invalidArgument(message);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
function consumeLengthDelimited(data, cursor, message) {
|
|
575
|
-
const length = consumeVarint(data, cursor, message);
|
|
576
|
-
const end = cursor.offset + length;
|
|
577
|
-
if (!Number.isSafeInteger(end) || end > data.byteLength) {
|
|
578
|
-
throw invalidArgument(message);
|
|
579
|
-
}
|
|
580
|
-
const value = data.subarray(cursor.offset, end);
|
|
581
|
-
cursor.offset = end;
|
|
582
|
-
return value;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
function skipProtoField(data, cursor, wireType) {
|
|
586
|
-
switch (wireType) {
|
|
587
|
-
case 0:
|
|
588
|
-
consumeVarint(data, cursor, "truncated stream frame varint");
|
|
589
|
-
return;
|
|
590
|
-
case 1:
|
|
591
|
-
skipFixed(data, cursor, 8);
|
|
592
|
-
return;
|
|
593
|
-
case 2:
|
|
594
|
-
consumeLengthDelimited(data, cursor, "truncated stream frame field");
|
|
595
|
-
return;
|
|
596
|
-
case 5:
|
|
597
|
-
skipFixed(data, cursor, 4);
|
|
598
|
-
return;
|
|
599
|
-
default:
|
|
600
|
-
throw invalidArgument("stream frame contained an unsupported wire type");
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
function skipFixed(data, cursor, length) {
|
|
605
|
-
const end = cursor.offset + length;
|
|
606
|
-
if (end > data.byteLength) {
|
|
607
|
-
throw invalidArgument("truncated stream frame fixed field");
|
|
608
|
-
}
|
|
609
|
-
cursor.offset = end;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
function decodeUtf8(bytes) {
|
|
613
|
-
try {
|
|
614
|
-
return utf8Decoder.decode(bytes);
|
|
615
|
-
} catch (error) {
|
|
616
|
-
throw invalidArgument(`stream frame message was not valid UTF-8: ${error.message}`);
|
|
617
|
-
}
|
|
218
|
+
function writeFrame(output, offset, type, payload) {
|
|
219
|
+
const next = writeVarint(output, writeVarint(output, offset, type), payload.byteLength);
|
|
220
|
+
output.set(payload, next);
|
|
618
221
|
}
|