@zlink-systems/stream-wire 0.26.0 → 0.28.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/dist/index.js CHANGED
@@ -1,11 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ZLINK_STREAM_FORMAT_MARKER = exports.ZlinkStreamCodec = void 0;
3
+ exports.ZLINK_STREAM_FORMAT_MARKER = exports.ZlinkStreamContentType = exports.ZlinkStreamControlPacket = exports.ZLINK_STREAM_RESERVED_PACKET_NAME_PREFIX = exports.ZLINK_STREAM_MAX_METADATA_BYTES = exports.ZLINK_STREAM_MAX_PACKET_NAME_BYTES = exports.ZLINK_STREAM_FRAME_PREFIX_BYTES = exports.UINT32_MAX = exports.UINT64_MAX = exports.UINT64_BYTES = exports.ZlinkStreamCloseReasonCode = exports.ZlinkStreamHeaderFlags = exports.ZlinkStreamMessageKind = exports.ZlinkStreamCodec = exports.defaultMaxDecompressedPayloadSize = void 0;
4
+ exports.isStreamWireMessageKind = isStreamWireMessageKind;
5
+ exports.isStreamWireCodec = isStreamWireCodec;
6
+ exports.encodeStreamWireActorBoundPayload = encodeStreamWireActorBoundPayload;
7
+ exports.decodeStreamWireActorBoundPayload = decodeStreamWireActorBoundPayload;
8
+ exports.encodeStreamWireActorUnboundPayload = encodeStreamWireActorUnboundPayload;
9
+ exports.decodeStreamWireActorUnboundPayload = decodeStreamWireActorUnboundPayload;
10
+ exports.encodeStreamWireSessionClosingPayload = encodeStreamWireSessionClosingPayload;
11
+ exports.decodeStreamWireSessionClosing = decodeStreamWireSessionClosing;
12
+ exports.splitStreamWireFrames = splitStreamWireFrames;
13
+ exports.streamCodecContentType = streamCodecContentType;
14
+ exports.streamCodecForContentType = streamCodecForContentType;
4
15
  exports.encodeStreamWireFrame = encodeStreamWireFrame;
5
16
  exports.decodeStreamWireFrame = decodeStreamWireFrame;
6
17
  exports.tryDecodeStreamWireFrame = tryDecodeStreamWireFrame;
7
18
  exports.encodeStreamWireHeader = encodeStreamWireHeader;
8
19
  exports.decodeStreamWireHeader = decodeStreamWireHeader;
20
+ exports.streamWireMetadataSize = streamWireMetadataSize;
9
21
  exports.encodeStreamWireMetadata = encodeStreamWireMetadata;
10
22
  exports.decodeStreamWireMetadata = decodeStreamWireMetadata;
11
23
  exports.lz4PickleUncompressed = lz4PickleUncompressed;
@@ -13,7 +25,10 @@ exports.lz4PickledLength = lz4PickledLength;
13
25
  exports.lz4UnpicklePayload = lz4UnpicklePayload;
14
26
  exports.utf8Encode = utf8Encode;
15
27
  exports.utf8Decode = utf8Decode;
28
+ exports.validateStreamWirePacketName = validateStreamWirePacketName;
16
29
  const lz4_pickle_1 = require("./lz4-pickle");
30
+ var lz4_pickle_2 = require("./lz4-pickle");
31
+ Object.defineProperty(exports, "defaultMaxDecompressedPayloadSize", { enumerable: true, get: function () { return lz4_pickle_2.defaultMaxDecompressedPayloadSize; } });
17
32
  /** Numeric payload codec values shared by browser and server wire paths. */
18
33
  var ZlinkStreamCodec;
19
34
  (function (ZlinkStreamCodec) {
@@ -22,76 +37,264 @@ var ZlinkStreamCodec;
22
37
  ZlinkStreamCodec[ZlinkStreamCodec["MessagePack"] = 2] = "MessagePack";
23
38
  ZlinkStreamCodec[ZlinkStreamCodec["Protobuf"] = 3] = "Protobuf";
24
39
  })(ZlinkStreamCodec || (exports.ZlinkStreamCodec = ZlinkStreamCodec = {}));
40
+ var ZlinkStreamMessageKind;
41
+ (function (ZlinkStreamMessageKind) {
42
+ ZlinkStreamMessageKind[ZlinkStreamMessageKind["Send"] = 1] = "Send";
43
+ ZlinkStreamMessageKind[ZlinkStreamMessageKind["Request"] = 2] = "Request";
44
+ ZlinkStreamMessageKind[ZlinkStreamMessageKind["Response"] = 3] = "Response";
45
+ ZlinkStreamMessageKind[ZlinkStreamMessageKind["Error"] = 4] = "Error";
46
+ ZlinkStreamMessageKind[ZlinkStreamMessageKind["Control"] = 5] = "Control";
47
+ })(ZlinkStreamMessageKind || (exports.ZlinkStreamMessageKind = ZlinkStreamMessageKind = {}));
48
+ var ZlinkStreamHeaderFlags;
49
+ (function (ZlinkStreamHeaderFlags) {
50
+ ZlinkStreamHeaderFlags[ZlinkStreamHeaderFlags["None"] = 0] = "None";
51
+ ZlinkStreamHeaderFlags[ZlinkStreamHeaderFlags["HasRequestSeq"] = 1] = "HasRequestSeq";
52
+ ZlinkStreamHeaderFlags[ZlinkStreamHeaderFlags["HasMetadata"] = 2] = "HasMetadata";
53
+ ZlinkStreamHeaderFlags[ZlinkStreamHeaderFlags["PayloadCompressed"] = 4] = "PayloadCompressed";
54
+ ZlinkStreamHeaderFlags[ZlinkStreamHeaderFlags["HasCorrelationId"] = 8] = "HasCorrelationId";
55
+ ZlinkStreamHeaderFlags[ZlinkStreamHeaderFlags["HasFlowId"] = 16] = "HasFlowId";
56
+ ZlinkStreamHeaderFlags[ZlinkStreamHeaderFlags["HasActorSlot"] = 32] = "HasActorSlot";
57
+ })(ZlinkStreamHeaderFlags || (exports.ZlinkStreamHeaderFlags = ZlinkStreamHeaderFlags = {}));
58
+ var ZlinkStreamCloseReasonCode;
59
+ (function (ZlinkStreamCloseReasonCode) {
60
+ ZlinkStreamCloseReasonCode[ZlinkStreamCloseReasonCode["ClientClose"] = 1] = "ClientClose";
61
+ ZlinkStreamCloseReasonCode[ZlinkStreamCloseReasonCode["IdleTimeout"] = 2] = "IdleTimeout";
62
+ ZlinkStreamCloseReasonCode[ZlinkStreamCloseReasonCode["HeartbeatTimeout"] = 3] = "HeartbeatTimeout";
63
+ ZlinkStreamCloseReasonCode[ZlinkStreamCloseReasonCode["ServerDrain"] = 4] = "ServerDrain";
64
+ ZlinkStreamCloseReasonCode[ZlinkStreamCloseReasonCode["ProtocolError"] = 5] = "ProtocolError";
65
+ ZlinkStreamCloseReasonCode[ZlinkStreamCloseReasonCode["TransportError"] = 6] = "TransportError";
66
+ })(ZlinkStreamCloseReasonCode || (exports.ZlinkStreamCloseReasonCode = ZlinkStreamCloseReasonCode = {}));
67
+ const unicodeWhiteSpaceOnly = /^\p{White_Space}*$/u;
68
+ const validMessageKinds = new Set(Object.values(ZlinkStreamMessageKind).filter((value) => typeof value === 'number'));
69
+ const validCodecs = new Set(Object.values(ZlinkStreamCodec).filter((value) => typeof value === 'number'));
70
+ function isStreamWireMessageKind(value) {
71
+ return validMessageKinds.has(value);
72
+ }
73
+ function isStreamWireCodec(value) {
74
+ return validCodecs.has(value);
75
+ }
76
+ const UTF8_TWO_BYTE_MIN = 0x80;
77
+ const UTF8_THREE_BYTE_MIN = 0x800;
78
+ const UTF16_HIGH_SURROGATE_MIN = 0xd800;
79
+ const UTF16_HIGH_SURROGATE_MAX = 0xdbff;
80
+ const UTF16_LOW_SURROGATE_MIN = 0xdc00;
81
+ const UTF16_LOW_SURROGATE_MAX = 0xdfff;
82
+ const UINT8_MAX = 0xff;
83
+ const UINT16_MAX = 0xffff;
84
+ const UINT16_BYTES = 2;
85
+ const UINT32_BYTES = 4;
86
+ exports.UINT64_BYTES = 8;
87
+ exports.UINT64_MAX = 0xffffffffffffffffn;
88
+ exports.UINT32_MAX = 0xffff_ffff;
89
+ exports.ZLINK_STREAM_FRAME_PREFIX_BYTES = UINT16_BYTES + UINT32_BYTES;
90
+ exports.ZLINK_STREAM_MAX_PACKET_NAME_BYTES = UINT8_MAX;
91
+ exports.ZLINK_STREAM_MAX_METADATA_BYTES = 1024;
92
+ const FLOW_ID_BYTES = 36;
93
+ const FLOW_FIELDS_BYTES = FLOW_ID_BYTES + 1;
94
+ const HEADER_MIN_BYTES = 5;
95
+ const HEADER_FLAG_MASK = ZlinkStreamHeaderFlags.HasRequestSeq |
96
+ ZlinkStreamHeaderFlags.HasMetadata |
97
+ ZlinkStreamHeaderFlags.PayloadCompressed |
98
+ ZlinkStreamHeaderFlags.HasCorrelationId |
99
+ ZlinkStreamHeaderFlags.HasFlowId |
100
+ ZlinkStreamHeaderFlags.HasActorSlot;
101
+ const CONTROL_PAYLOAD_VERSION = 1;
102
+ const ACTOR_SLOT_OFFSET = 1;
103
+ const ACTOR_ID_LENGTH_OFFSET = ACTOR_SLOT_OFFSET + UINT16_BYTES;
104
+ const ACTOR_BOUND_PREFIX_BYTES = ACTOR_ID_LENGTH_OFFSET + 1;
105
+ const ACTOR_UNBOUND_BYTES = ACTOR_SLOT_OFFSET + UINT16_BYTES;
106
+ const CLOSING_DIAGNOSTIC_LENGTH_OFFSET = 2;
107
+ const CLOSING_PREFIX_BYTES = CLOSING_DIAGNOSTIC_LENGTH_OFFSET + UINT16_BYTES;
108
+ const MAX_CLOSING_DIAGNOSTIC_BYTES = 512;
109
+ function encodeStreamWireActorBoundPayload(actorSlot, actorId) {
110
+ const actorIdBytes = utf8Encode(actorId);
111
+ if (actorIdBytes.length < 1 || actorIdBytes.length > UINT8_MAX) {
112
+ throw new Error('Actor id length is invalid for a STREAM binding control packet.');
113
+ }
114
+ const payload = new Uint8Array(ACTOR_BOUND_PREFIX_BYTES + actorIdBytes.length);
115
+ payload[0] = CONTROL_PAYLOAD_VERSION;
116
+ writeUInt16BE(payload, ACTOR_SLOT_OFFSET, actorSlot);
117
+ payload[ACTOR_ID_LENGTH_OFFSET] = actorIdBytes.length;
118
+ payload.set(actorIdBytes, ACTOR_BOUND_PREFIX_BYTES);
119
+ return payload;
120
+ }
121
+ function decodeStreamWireActorBoundPayload(payload) {
122
+ if (payload.length < ACTOR_BOUND_PREFIX_BYTES + 1 || payload[0] !== CONTROL_PAYLOAD_VERSION) {
123
+ throw new Error('Actor bound payload is invalid.');
124
+ }
125
+ const slot = readUInt16BE(payload, ACTOR_SLOT_OFFSET);
126
+ const idLength = payload[ACTOR_ID_LENGTH_OFFSET];
127
+ if (slot === 0 || idLength === 0 || payload.length !== ACTOR_BOUND_PREFIX_BYTES + idLength) {
128
+ throw new Error('Actor bound payload is invalid.');
129
+ }
130
+ try {
131
+ return { slot, actorId: decodeControlText(payload.subarray(ACTOR_BOUND_PREFIX_BYTES)) };
132
+ }
133
+ catch (cause) {
134
+ throw new Error('Actor id is not valid UTF-8.', { cause });
135
+ }
136
+ }
137
+ function encodeStreamWireActorUnboundPayload(actorSlot) {
138
+ const payload = new Uint8Array(ACTOR_UNBOUND_BYTES);
139
+ payload[0] = CONTROL_PAYLOAD_VERSION;
140
+ writeUInt16BE(payload, ACTOR_SLOT_OFFSET, actorSlot);
141
+ return payload;
142
+ }
143
+ function decodeStreamWireActorUnboundPayload(payload) {
144
+ if (payload.length !== ACTOR_UNBOUND_BYTES || payload[0] !== CONTROL_PAYLOAD_VERSION) {
145
+ throw new Error('Actor unbound payload is invalid.');
146
+ }
147
+ return readUInt16BE(payload, ACTOR_SLOT_OFFSET);
148
+ }
149
+ function encodeStreamWireSessionClosingPayload(reasonCode, diagnostic = '') {
150
+ const bytes = utf8Encode(diagnostic);
151
+ if (bytes.length > MAX_CLOSING_DIAGNOSTIC_BYTES) {
152
+ throw new Error('Session-closing diagnostic is too large.');
153
+ }
154
+ const payload = new Uint8Array(CLOSING_PREFIX_BYTES + bytes.length);
155
+ payload[0] = CONTROL_PAYLOAD_VERSION;
156
+ payload[1] = reasonCode;
157
+ writeUInt16BE(payload, CLOSING_DIAGNOSTIC_LENGTH_OFFSET, bytes.length);
158
+ payload.set(bytes, CLOSING_PREFIX_BYTES);
159
+ return payload;
160
+ }
161
+ function decodeStreamWireSessionClosing(payload) {
162
+ if (payload.length < CLOSING_PREFIX_BYTES || payload[0] !== CONTROL_PAYLOAD_VERSION) {
163
+ throw new Error('Unsupported session-closing version.');
164
+ }
165
+ const closeReason = ZlinkStreamCloseReasonCode[payload[1]];
166
+ if (closeReason === undefined)
167
+ throw new Error('Unknown session-closing reason.');
168
+ const length = readUInt16BE(payload, CLOSING_DIAGNOSTIC_LENGTH_OFFSET);
169
+ if (length > MAX_CLOSING_DIAGNOSTIC_BYTES || payload.length !== CLOSING_PREFIX_BYTES + length) {
170
+ throw new Error('Invalid session-closing diagnostic length.');
171
+ }
172
+ const diagnostic = length === 0 ? undefined : decodeControlText(payload.subarray(CLOSING_PREFIX_BYTES));
173
+ return { closeReason, diagnostic };
174
+ }
175
+ function decodeControlText(value) {
176
+ return new TextDecoder('utf-8', { fatal: true }).decode(value);
177
+ }
178
+ function splitStreamWireFrames(chunk) {
179
+ if (chunk.length === 0)
180
+ throw new Error('Stream frame prefix is incomplete.');
181
+ const frames = [];
182
+ let offset = 0;
183
+ while (offset < chunk.length) {
184
+ const remaining = chunk.length - offset;
185
+ if (remaining < exports.ZLINK_STREAM_FRAME_PREFIX_BYTES) {
186
+ throw new Error('Stream frame prefix is incomplete.');
187
+ }
188
+ const headerLength = readUInt16BE(chunk, offset);
189
+ const payloadLength = readUInt32BE(chunk, offset + UINT16_BYTES);
190
+ const frameLength = exports.ZLINK_STREAM_FRAME_PREFIX_BYTES + headerLength + payloadLength;
191
+ if (frameLength > remaining)
192
+ throw new Error('Frame length does not match prefix.');
193
+ frames.push(chunk.subarray(offset, offset + frameLength));
194
+ offset += frameLength;
195
+ }
196
+ return frames;
197
+ }
198
+ /** @internal Reserved packet name prefix owned by the stream protocol. */
199
+ exports.ZLINK_STREAM_RESERVED_PACKET_NAME_PREFIX = '$zlink.';
200
+ exports.ZlinkStreamControlPacket = Object.freeze({
201
+ HeartbeatPing: `${exports.ZLINK_STREAM_RESERVED_PACKET_NAME_PREFIX}heartbeat.ping`,
202
+ HeartbeatPong: `${exports.ZLINK_STREAM_RESERVED_PACKET_NAME_PREFIX}heartbeat.pong`,
203
+ ActorBound: `${exports.ZLINK_STREAM_RESERVED_PACKET_NAME_PREFIX}actor.bound`,
204
+ ActorUnbound: `${exports.ZLINK_STREAM_RESERVED_PACKET_NAME_PREFIX}actor.unbound`,
205
+ SessionClosing: 'session-closing'
206
+ });
207
+ exports.ZlinkStreamContentType = Object.freeze({
208
+ Raw: 'application/octet-stream',
209
+ Json: 'application/json',
210
+ MessagePack: 'application/x-msgpack',
211
+ Protobuf: 'application/x-protobuf'
212
+ });
213
+ const contentTypesByCodec = new Map([
214
+ [ZlinkStreamCodec.Raw, exports.ZlinkStreamContentType.Raw],
215
+ [ZlinkStreamCodec.Json, exports.ZlinkStreamContentType.Json],
216
+ [ZlinkStreamCodec.MessagePack, exports.ZlinkStreamContentType.MessagePack],
217
+ [ZlinkStreamCodec.Protobuf, exports.ZlinkStreamContentType.Protobuf]
218
+ ]);
219
+ const codecsByContentType = new Map(Array.from(contentTypesByCodec, ([codec, contentType]) => [contentType, codec]));
220
+ function streamCodecContentType(codec) {
221
+ const contentType = contentTypesByCodec.get(codec);
222
+ if (contentType === undefined)
223
+ throw new TypeError(`Unsupported STREAM codec '${codec}'.`);
224
+ return contentType;
225
+ }
226
+ function streamCodecForContentType(contentType) {
227
+ const codec = codecsByContentType.get(contentType);
228
+ if (codec === undefined)
229
+ throw new TypeError(`Unsupported STREAM content type '${contentType}'.`);
230
+ return codec;
231
+ }
25
232
  // The functions below resolve this inside their bodies rather than in a parameter
26
233
  // default. The browser IIFE build of this package is committed into the Unity
27
234
  // WebGL adapter as an emscripten pre-js, and emscripten's dead-code pass does not
28
235
  // walk parameter default initializers: a binding whose only references are
29
236
  // defaults is deleted, and the player fails at runtime with "not defined".
30
237
  const defaultHeaderFlags = {
31
- hasRequestSeq: 0x01,
32
- hasMetadata: 0x02,
33
- hasCorrelationId: 0x08,
34
- hasFlowId: 0x10,
35
- hasActorSlot: 0x20
238
+ hasRequestSeq: ZlinkStreamHeaderFlags.HasRequestSeq,
239
+ hasMetadata: ZlinkStreamHeaderFlags.HasMetadata,
240
+ hasCorrelationId: ZlinkStreamHeaderFlags.HasCorrelationId,
241
+ hasFlowId: ZlinkStreamHeaderFlags.HasFlowId,
242
+ hasActorSlot: ZlinkStreamHeaderFlags.HasActorSlot
36
243
  };
37
244
  exports.ZLINK_STREAM_FORMAT_MARKER = 0xf2;
38
- const ZLINK_STREAM_RESPONSE_KIND = 3;
39
- const ZLINK_STREAM_ERROR_KIND = 4;
40
245
  function encodeStreamWireFrame(header, payload) {
41
- if (header.length > 0xffff) {
246
+ if (header.length > UINT16_MAX) {
42
247
  throw new Error('Stream header is too large.');
43
248
  }
44
- if (payload.length > 0xffffffff) {
249
+ if (payload.length > exports.UINT32_MAX) {
45
250
  throw new Error('Stream payload is too large.');
46
251
  }
47
- const frame = new Uint8Array(6 + header.length + payload.length);
252
+ const frame = new Uint8Array(exports.ZLINK_STREAM_FRAME_PREFIX_BYTES + header.length + payload.length);
48
253
  writeUInt16BE(frame, 0, header.length);
49
- writeUInt32BE(frame, 2, payload.length);
50
- frame.set(header, 6);
51
- frame.set(payload, 6 + header.length);
254
+ writeUInt32BE(frame, UINT16_BYTES, payload.length);
255
+ frame.set(header, exports.ZLINK_STREAM_FRAME_PREFIX_BYTES);
256
+ frame.set(payload, exports.ZLINK_STREAM_FRAME_PREFIX_BYTES + header.length);
52
257
  return frame;
53
258
  }
54
259
  function decodeStreamWireFrame(frame) {
55
- if (frame.length < 6) {
260
+ if (frame.length < exports.ZLINK_STREAM_FRAME_PREFIX_BYTES) {
56
261
  throw new Error('Stream frame prefix is incomplete.');
57
262
  }
58
263
  const headerLength = readUInt16BE(frame, 0);
59
- const payloadLength = readUInt32BE(frame, 2);
60
- if (frame.length !== 6 + headerLength + payloadLength) {
264
+ const payloadLength = readUInt32BE(frame, UINT16_BYTES);
265
+ if (frame.length !== exports.ZLINK_STREAM_FRAME_PREFIX_BYTES + headerLength + payloadLength) {
61
266
  throw new Error('Stream frame length does not match prefix.');
62
267
  }
63
268
  return {
64
- header: frame.slice(6, 6 + headerLength),
65
- payload: frame.slice(6 + headerLength)
269
+ header: frame.slice(exports.ZLINK_STREAM_FRAME_PREFIX_BYTES, exports.ZLINK_STREAM_FRAME_PREFIX_BYTES + headerLength),
270
+ payload: frame.slice(exports.ZLINK_STREAM_FRAME_PREFIX_BYTES + headerLength)
66
271
  };
67
272
  }
68
273
  function tryDecodeStreamWireFrame(frame) {
69
- if (frame.length < 6) {
274
+ if (frame.length < exports.ZLINK_STREAM_FRAME_PREFIX_BYTES) {
70
275
  return undefined;
71
276
  }
72
277
  const headerLength = readUInt16BE(frame, 0);
73
- const payloadLength = readUInt32BE(frame, 2);
74
- if (frame.length !== 6 + headerLength + payloadLength) {
278
+ const payloadLength = readUInt32BE(frame, UINT16_BYTES);
279
+ if (frame.length !== exports.ZLINK_STREAM_FRAME_PREFIX_BYTES + headerLength + payloadLength) {
75
280
  return undefined;
76
281
  }
77
282
  return {
78
- header: frame.slice(6, 6 + headerLength),
79
- payload: frame.slice(6 + headerLength)
283
+ header: frame.slice(exports.ZLINK_STREAM_FRAME_PREFIX_BYTES, exports.ZLINK_STREAM_FRAME_PREFIX_BYTES + headerLength),
284
+ payload: frame.slice(exports.ZLINK_STREAM_FRAME_PREFIX_BYTES + headerLength)
80
285
  };
81
286
  }
82
287
  function encodeStreamWireHeader(header, flagOverrides) {
83
288
  const flags = flagOverrides ?? defaultHeaderFlags;
84
289
  const reply = isReplyKind(header.kind);
85
290
  const packetName = reply ? '' : header.name;
86
- if (!reply)
87
- validateStreamWirePacketName(packetName);
88
- const nameBytes = utf8Encode(packetName);
291
+ const nameBytes = reply ? new Uint8Array() : validateStreamWirePacketName(packetName);
89
292
  const hasRequestSeq = header.requestSeq !== undefined;
90
293
  const hasMetadata = header.metadata.size > 0;
91
294
  const correlationBytes = header.correlationId !== undefined && header.correlationId.length > 0
92
295
  ? utf8Encode(header.correlationId)
93
296
  : undefined;
94
- if (correlationBytes !== undefined && correlationBytes.length > 0xff) {
297
+ if (correlationBytes !== undefined && correlationBytes.length > UINT8_MAX) {
95
298
  throw new Error('Stream correlation id is too large.');
96
299
  }
97
300
  const hasCorrelation = correlationBytes !== undefined;
@@ -106,7 +309,7 @@ function encodeStreamWireHeader(header, flagOverrides) {
106
309
  throw new Error('Stream flow origin is invalid.');
107
310
  }
108
311
  if (header.actorSlot !== undefined &&
109
- (!Number.isInteger(header.actorSlot) || header.actorSlot < 1 || header.actorSlot > 0xffff)) {
312
+ (!Number.isInteger(header.actorSlot) || header.actorSlot < 1 || header.actorSlot > UINT16_MAX)) {
110
313
  throw new Error('Stream actor slot is invalid.');
111
314
  }
112
315
  let headerFlags = header.flags;
@@ -121,13 +324,13 @@ function encodeStreamWireHeader(header, flagOverrides) {
121
324
  headerFlags = hasActorSlot ? headerFlags | flags.hasActorSlot : headerFlags & ~flags.hasActorSlot;
122
325
  const metadataBytes = hasMetadata ? encodeStreamWireMetadata(header.metadata) : new Uint8Array();
123
326
  const size = 4 +
124
- (hasRequestSeq ? 8 : 0) +
327
+ (hasRequestSeq ? exports.UINT64_BYTES : 0) +
125
328
  1 +
126
329
  nameBytes.length +
127
- (hasMetadata ? 2 + metadataBytes.length : 0) +
330
+ (hasMetadata ? UINT16_BYTES + metadataBytes.length : 0) +
128
331
  (hasCorrelation ? 1 + correlationBytes.length : 0) +
129
- (hasFlow ? 37 : 0) +
130
- (hasActorSlot ? 2 : 0);
332
+ (hasFlow ? FLOW_FIELDS_BYTES : 0) +
333
+ (hasActorSlot ? UINT16_BYTES : 0);
131
334
  const buffer = new Uint8Array(size);
132
335
  let offset = 0;
133
336
  buffer[offset++] = exports.ZLINK_STREAM_FORMAT_MARKER;
@@ -139,14 +342,14 @@ function encodeStreamWireHeader(header, flagOverrides) {
139
342
  throw new Error('Request sequence must not be zero.');
140
343
  }
141
344
  writeBigUInt64BE(buffer, offset, header.requestSeq);
142
- offset += 8;
345
+ offset += exports.UINT64_BYTES;
143
346
  }
144
347
  buffer[offset++] = nameBytes.length;
145
348
  buffer.set(nameBytes, offset);
146
349
  offset += nameBytes.length;
147
350
  if (hasMetadata) {
148
351
  writeUInt16BE(buffer, offset, metadataBytes.length);
149
- offset += 2;
352
+ offset += UINT16_BYTES;
150
353
  buffer.set(metadataBytes, offset);
151
354
  offset += metadataBytes.length;
152
355
  }
@@ -157,7 +360,7 @@ function encodeStreamWireHeader(header, flagOverrides) {
157
360
  }
158
361
  if (hasFlow) {
159
362
  buffer.set(asciiEncode(header.flowId), offset);
160
- offset += 36;
363
+ offset += FLOW_ID_BYTES;
161
364
  buffer[offset++] = header.flowOrigin;
162
365
  }
163
366
  if (hasActorSlot) {
@@ -168,7 +371,7 @@ function encodeStreamWireHeader(header, flagOverrides) {
168
371
  function decodeStreamWireHeader(header, flagOverrides, includeFlow = true) {
169
372
  const flags = flagOverrides ?? defaultHeaderFlags;
170
373
  let offset = 0;
171
- if (header.length < 5) {
374
+ if (header.length < HEADER_MIN_BYTES) {
172
375
  throw new Error('Stream header is incomplete.');
173
376
  }
174
377
  if (header[offset++] !== exports.ZLINK_STREAM_FORMAT_MARKER) {
@@ -182,29 +385,29 @@ function decodeStreamWireHeader(header, flagOverrides, includeFlow = true) {
182
385
  const hasCorrelation = (headerFlags & flags.hasCorrelationId) !== 0;
183
386
  const hasFlow = (headerFlags & flags.hasFlowId) !== 0;
184
387
  const hasActorSlot = (headerFlags & flags.hasActorSlot) !== 0;
185
- if ((headerFlags & ~0x3f) !== 0) {
388
+ if ((headerFlags & ~HEADER_FLAG_MASK) !== 0) {
186
389
  throw new Error('Unknown mandatory stream header flag.');
187
390
  }
188
391
  let requestSeq;
189
392
  if (hasRequestSeq) {
190
- if (header.length - offset < 8) {
393
+ if (header.length - offset < exports.UINT64_BYTES) {
191
394
  throw new Error('Stream request sequence is incomplete.');
192
395
  }
193
396
  requestSeq = readBigUInt64BE(header, offset);
194
397
  if (requestSeq === 0n) {
195
398
  throw new Error('Request sequence must not be zero.');
196
399
  }
197
- offset += 8;
400
+ offset += exports.UINT64_BYTES;
198
401
  }
199
402
  if (header.length - offset < 1) {
200
403
  throw new Error('Stream packet name length is missing.');
201
404
  }
202
405
  const nameLength = header[offset++];
203
- if ((!isReplyKind(kind) && nameLength === 0) || header.length - offset < nameLength) {
406
+ if ((isReplyKind(kind) ? nameLength !== 0 : nameLength === 0) ||
407
+ header.length - offset < nameLength) {
204
408
  throw new Error('Stream packet name is invalid.');
205
409
  }
206
- const decodedName = utf8Decode(header.subarray(offset, offset + nameLength));
207
- const name = isReplyKind(kind) ? '' : decodedName;
410
+ const name = utf8Decode(header.subarray(offset, offset + nameLength));
208
411
  offset += nameLength;
209
412
  const decodedMetadata = hasMetadata
210
413
  ? decodeStreamWireHeaderMetadata(header, offset)
@@ -225,14 +428,14 @@ function decodeStreamWireHeader(header, flagOverrides, includeFlow = true) {
225
428
  let flowId;
226
429
  let flowOrigin;
227
430
  if (hasFlow) {
228
- if (header.length - offset < 37) {
431
+ if (header.length - offset < FLOW_FIELDS_BYTES) {
229
432
  throw new Error('Stream flow fields are incomplete.');
230
433
  }
231
434
  if (includeFlow) {
232
- flowId = asciiDecode(header.subarray(offset, offset + 36));
435
+ flowId = asciiDecode(header.subarray(offset, offset + FLOW_ID_BYTES));
233
436
  validateFlowId(flowId);
234
437
  }
235
- offset += 36;
438
+ offset += FLOW_ID_BYTES;
236
439
  const decodedFlowOrigin = header[offset++];
237
440
  if (includeFlow && ![1, 2, 3, 4].includes(decodedFlowOrigin)) {
238
441
  throw new Error('Stream flow origin is invalid.');
@@ -241,14 +444,14 @@ function decodeStreamWireHeader(header, flagOverrides, includeFlow = true) {
241
444
  }
242
445
  let actorSlot;
243
446
  if (hasActorSlot) {
244
- if (header.length - offset < 2) {
447
+ if (header.length - offset < UINT16_BYTES) {
245
448
  throw new Error('Stream actor slot is incomplete.');
246
449
  }
247
450
  actorSlot = readUInt16BE(header, offset);
248
451
  if (actorSlot === 0) {
249
452
  throw new Error('Stream actor slot must not be zero.');
250
453
  }
251
- offset += 2;
454
+ offset += UINT16_BYTES;
252
455
  }
253
456
  if (offset !== header.length) {
254
457
  throw new Error('Stream header has trailing bytes.');
@@ -279,32 +482,37 @@ function asciiDecode(value) {
279
482
  throw new Error('Stream flow id must be ASCII.');
280
483
  return String.fromCharCode(...value);
281
484
  }
282
- function encodeStreamWireMetadata(metadata) {
283
- if (metadata.size > 255) {
485
+ /** Standalone metadata includes its count byte even when the map is empty. */
486
+ function streamWireMetadataSize(metadata) {
487
+ if (metadata.size > UINT8_MAX) {
284
488
  throw new Error('Metadata entry count must not exceed 255.');
285
489
  }
286
490
  let size = 1;
287
- const encoded = [...metadata].map(([key, value]) => {
288
- const keyBytes = utf8Encode(key);
289
- const valueBytes = utf8Encode(value);
290
- if (keyBytes.length === 0 || keyBytes.length > 255) {
491
+ for (const [key, value] of metadata) {
492
+ const keySize = utf8Size(key);
493
+ const valueSize = utf8Size(value);
494
+ if (keySize === 0 || keySize > UINT8_MAX) {
291
495
  throw new Error('Metadata key length is invalid.');
292
496
  }
293
- if (valueBytes.length > 0xffff) {
497
+ if (valueSize > UINT16_MAX) {
294
498
  throw new Error('Metadata value is too large.');
295
499
  }
296
- size += 1 + keyBytes.length + 2 + valueBytes.length;
297
- return { keyBytes, valueBytes };
298
- });
299
- const buffer = new Uint8Array(size);
500
+ size += 1 + keySize + UINT16_BYTES + valueSize;
501
+ }
502
+ return size;
503
+ }
504
+ function encodeStreamWireMetadata(metadata) {
505
+ const buffer = new Uint8Array(streamWireMetadataSize(metadata));
300
506
  let offset = 0;
301
507
  buffer[offset++] = metadata.size;
302
- for (const { keyBytes, valueBytes } of encoded) {
508
+ for (const [key, value] of metadata) {
509
+ const keyBytes = utf8Encode(key);
510
+ const valueBytes = utf8Encode(value);
303
511
  buffer[offset++] = keyBytes.length;
304
512
  buffer.set(keyBytes, offset);
305
513
  offset += keyBytes.length;
306
514
  writeUInt16BE(buffer, offset, valueBytes.length);
307
- offset += 2;
515
+ offset += UINT16_BYTES;
308
516
  buffer.set(valueBytes, offset);
309
517
  offset += valueBytes.length;
310
518
  }
@@ -333,11 +541,11 @@ function utf8Decode(value) {
333
541
  return new TextDecoder().decode(value);
334
542
  }
335
543
  function decodeStreamWireHeaderMetadata(header, offset) {
336
- if (header.length - offset < 2) {
544
+ if (header.length - offset < UINT16_BYTES) {
337
545
  throw new Error('Stream metadata section is incomplete.');
338
546
  }
339
547
  const metadataLength = readUInt16BE(header, offset);
340
- offset += 2;
548
+ offset += UINT16_BYTES;
341
549
  if (header.length - offset < metadataLength) {
342
550
  throw new Error('Stream metadata payload is incomplete.');
343
551
  }
@@ -363,7 +571,7 @@ function decodeStreamWireMetadataAt(source, offset, end) {
363
571
  throw new Error('Stream metadata value length is missing.');
364
572
  }
365
573
  const valueLength = readUInt16BE(source, offset);
366
- offset += 2;
574
+ offset += UINT16_BYTES;
367
575
  if (end - offset < valueLength) {
368
576
  throw new Error('Stream metadata value is incomplete.');
369
577
  }
@@ -378,18 +586,20 @@ function decodeStreamWireMetadataAt(source, offset, end) {
378
586
  }
379
587
  return { metadata, offset };
380
588
  }
589
+ /** @internal Shared packet name structure validation for the connector. */
381
590
  function validateStreamWirePacketName(name) {
382
591
  const nameBytes = utf8Encode(name);
383
- if (name.trim().length === 0 || nameBytes.length > 255) {
592
+ if (unicodeWhiteSpaceOnly.test(name) || nameBytes.length > exports.ZLINK_STREAM_MAX_PACKET_NAME_BYTES) {
384
593
  throw new Error('Stream packet name is invalid.');
385
594
  }
595
+ return nameBytes;
386
596
  }
387
597
  function isReplyKind(kind) {
388
- return kind === ZLINK_STREAM_RESPONSE_KIND || kind === ZLINK_STREAM_ERROR_KIND;
598
+ return kind === ZlinkStreamMessageKind.Response || kind === ZlinkStreamMessageKind.Error;
389
599
  }
390
600
  function writeUInt16BE(buffer, offset, value) {
391
- buffer[offset] = (value >>> 8) & 0xff;
392
- buffer[offset + 1] = value & 0xff;
601
+ buffer[offset] = (value >>> 8) & UINT8_MAX;
602
+ buffer[offset + 1] = value & UINT8_MAX;
393
603
  }
394
604
  function readUInt16BE(buffer, offset) {
395
605
  return (buffer[offset] << 8) | buffer[offset + 1];
@@ -399,10 +609,10 @@ function readUInt32BE(buffer, offset) {
399
609
  ((buffer[offset + 1] << 16) | (buffer[offset + 2] << 8) | buffer[offset + 3]));
400
610
  }
401
611
  function writeUInt32BE(buffer, offset, value) {
402
- buffer[offset] = (value >>> 24) & 0xff;
403
- buffer[offset + 1] = (value >>> 16) & 0xff;
404
- buffer[offset + 2] = (value >>> 8) & 0xff;
405
- buffer[offset + 3] = value & 0xff;
612
+ buffer[offset] = (value >>> 24) & UINT8_MAX;
613
+ buffer[offset + 1] = (value >>> 16) & UINT8_MAX;
614
+ buffer[offset + 2] = (value >>> 8) & UINT8_MAX;
615
+ buffer[offset + 3] = value & UINT8_MAX;
406
616
  }
407
617
  function writeBigUInt64BE(buffer, offset, value) {
408
618
  for (let index = 7; index >= 0; index -= 1) {
@@ -417,3 +627,25 @@ function readBigUInt64BE(buffer, offset) {
417
627
  }
418
628
  return value;
419
629
  }
630
+ /** TextEncoder replaces lone surrogates with the three-byte replacement character. */
631
+ function utf8Size(value) {
632
+ let size = 0;
633
+ for (let index = 0; index < value.length; index += 1) {
634
+ const code = value.charCodeAt(index);
635
+ if (code < UTF8_TWO_BYTE_MIN)
636
+ size += 1;
637
+ else if (code < UTF8_THREE_BYTE_MIN)
638
+ size += 2;
639
+ else if (code >= UTF16_HIGH_SURROGATE_MIN &&
640
+ code <= UTF16_HIGH_SURROGATE_MAX &&
641
+ index + 1 < value.length &&
642
+ value.charCodeAt(index + 1) >= UTF16_LOW_SURROGATE_MIN &&
643
+ value.charCodeAt(index + 1) <= UTF16_LOW_SURROGATE_MAX) {
644
+ size += 4;
645
+ index += 1;
646
+ }
647
+ else
648
+ size += 3;
649
+ }
650
+ return size;
651
+ }
@@ -4,6 +4,10 @@ exports.defaultMaxDecompressedPayloadSize = void 0;
4
4
  exports.lz4PickleUncompressed = lz4PickleUncompressed;
5
5
  exports.lz4PickledLength = lz4PickledLength;
6
6
  exports.lz4UnpicklePayload = lz4UnpicklePayload;
7
+ const LZ4_MIN_MATCH_LENGTH = 4;
8
+ const LZ4_MATCH_OFFSET_BYTES = 2;
9
+ const LZ4_EXTENDED_LENGTH_NIBBLE = 15;
10
+ const LZ4_EXTENDED_LENGTH_BYTE = 255;
7
11
  // Resolved inside the function body rather than in a parameter default: see the
8
12
  // note on defaultHeaderFlags in index.ts. Emscripten's dead-code pass deletes a
9
13
  // binding whose only references are parameter defaults.
@@ -86,15 +90,15 @@ function decodeLz4Block(source, resultLength) {
86
90
  if (sourceOffset >= source.length) {
87
91
  break;
88
92
  }
89
- if (source.length - sourceOffset < 2) {
93
+ if (source.length - sourceOffset < LZ4_MATCH_OFFSET_BYTES) {
90
94
  throw new Error('LZ4 match offset is incomplete.');
91
95
  }
92
96
  const matchOffset = source[sourceOffset] | (source[sourceOffset + 1] << 8);
93
- sourceOffset += 2;
97
+ sourceOffset += LZ4_MATCH_OFFSET_BYTES;
94
98
  if (matchOffset === 0 || matchOffset > targetOffset) {
95
99
  throw new Error('LZ4 match offset is invalid.');
96
100
  }
97
- const matchLength = readLz4Length(source, token & 0x0f, () => sourceOffset++) + 4;
101
+ const matchLength = readLz4Length(source, token & 0x0f, () => sourceOffset++) + LZ4_MIN_MATCH_LENGTH;
98
102
  if (target.length - targetOffset < matchLength) {
99
103
  throw new Error('LZ4 match run exceeds output size.');
100
104
  }
@@ -110,7 +114,7 @@ function decodeLz4Block(source, resultLength) {
110
114
  }
111
115
  function readLz4Length(source, nibble, nextOffset) {
112
116
  let length = nibble;
113
- if (length !== 15) {
117
+ if (length !== LZ4_EXTENDED_LENGTH_NIBBLE) {
114
118
  return length;
115
119
  }
116
120
  for (;;) {
@@ -120,7 +124,7 @@ function readLz4Length(source, nibble, nextOffset) {
120
124
  }
121
125
  const value = source[offset];
122
126
  length += value;
123
- if (value !== 255) {
127
+ if (value !== LZ4_EXTENDED_LENGTH_BYTE) {
124
128
  return length;
125
129
  }
126
130
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zlink-systems/stream-wire",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "repository": {
6
6
  "type": "git",