@zerodds/wire 0.0.0-stage → 1.0.0-rc.7
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 +13 -2
- package/dist/branded.d.ts +98 -0
- package/dist/branded.js +64 -0
- package/dist/dds_any.d.ts +47 -0
- package/dist/dds_any.js +41 -0
- package/dist/endianness.d.ts +22 -0
- package/dist/endianness.js +35 -0
- package/dist/equal.d.ts +46 -0
- package/dist/equal.js +273 -0
- package/dist/error.d.ts +52 -0
- package/dist/error.js +78 -0
- package/dist/float128.d.ts +15 -0
- package/dist/float128.js +181 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +33 -0
- package/dist/operations.d.ts +180 -0
- package/dist/operations.js +74 -0
- package/dist/policy.d.ts +133 -0
- package/dist/policy.js +184 -0
- package/dist/reader.d.ts +605 -0
- package/dist/reader.js +1345 -0
- package/dist/registry.d.ts +84 -0
- package/dist/registry.js +166 -0
- package/dist/test_backend.d.ts +9 -0
- package/dist/test_backend.js +173 -0
- package/dist/types.d.ts +246 -0
- package/dist/types.js +3 -0
- package/dist/wasm.d.ts +203 -0
- package/dist/wasm.js +194 -0
- package/dist/writer.d.ts +662 -0
- package/dist/writer.js +1333 -0
- package/package.json +27 -4
package/dist/reader.js
ADDED
|
@@ -0,0 +1,1345 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 ZeroDDS Contributors
|
|
3
|
+
/**
|
|
4
|
+
* The read half of XCDR, implemented from DDS-XTypes 1.3 §7.4.
|
|
5
|
+
*
|
|
6
|
+
* The mirror of `writer.ts` clause for clause and not by inversion of
|
|
7
|
+
* it: a reader written as "whatever undoes the writer" agrees with the
|
|
8
|
+
* writer about a misreading of the clause, and agreement between two
|
|
9
|
+
* halves of one column is the one thing that proves nothing.
|
|
10
|
+
*
|
|
11
|
+
* ## Two kinds of failure, and why they are not one
|
|
12
|
+
*
|
|
13
|
+
* A malformed *stream* throws. A length that runs past the end of the
|
|
14
|
+
* buffer is not a state a caller can carry on from, and
|
|
15
|
+
* {@link Truncated} against {@link ImpossibleLength} is the distinction
|
|
16
|
+
* that matters: the first says the stream is short, the second says the
|
|
17
|
+
* stream is lying. Allocating for a claimed length before checking it
|
|
18
|
+
* is how a reader turns a bad message into an out-of-memory.
|
|
19
|
+
*
|
|
20
|
+
* A refusal about the *type* sets {@link Status} and does not throw.
|
|
21
|
+
* The runtime owns the wire and the generated decoder owns the type,
|
|
22
|
+
* and this reader cannot see an integer that names no enumerator, a
|
|
23
|
+
* bitmask bit the declaration does not have, or a member that did not
|
|
24
|
+
* arrive. §7.2.2.7's DISCARD is a sample the receiver drops and carries
|
|
25
|
+
* on from, which is not a failure of the peer.
|
|
26
|
+
*
|
|
27
|
+
* ## What TypeScript decides differently from the other columns
|
|
28
|
+
*
|
|
29
|
+
* - `readChar` and `readWchar` answer with a one-character **string**,
|
|
30
|
+
* not with a code point. TypeScript has no character type, the
|
|
31
|
+
* materialiser declares a `char` member as `string`, and
|
|
32
|
+
* `Writer.writeChar` takes one; a reader answering with a `number`
|
|
33
|
+
* would be the only one of the three that did not.
|
|
34
|
+
* - `readU32` answers with a `number`, because 4294967295 is far below
|
|
35
|
+
* `Number.MAX_SAFE_INTEGER`. `readI64` and `readU64` answer with a
|
|
36
|
+
* `bigint`, because 2^63 is not.
|
|
37
|
+
* - `readEnum`, `readBitmask` and `readBitset` answer with a `bigint`
|
|
38
|
+
* whatever their bound. The generated decoder compares the answer
|
|
39
|
+
* against 64-bit patterns, and one comparison that mixed a `number`
|
|
40
|
+
* with a `bigint` is a `tsc` error rather than a wrong arm — which
|
|
41
|
+
* is the outcome this column wants.
|
|
42
|
+
* - `readF128` answers with the sixteen octets. A binary64 would drop
|
|
43
|
+
* sixty bits of the significand at the assignment.
|
|
44
|
+
*/
|
|
45
|
+
import { isLittle } from "./endianness.js";
|
|
46
|
+
import { ImpossibleLength, NotUtf8, OutOfRange, Truncated } from "./error.js";
|
|
47
|
+
import { FixedDigitCount, ParameterRole, Status, WcharFraming, WcharWidth, WstringLengthPolicy, WstringUnitOrder, } from "./policy.js";
|
|
48
|
+
import { PID_EXTENDED, PID_IMPL_EXTENSION, PID_LIST_END, PID_MUST_UNDERSTAND, PL_CDR1_DEFAULT, pidFlagsAt, } from "./writer.js";
|
|
49
|
+
/**
|
|
50
|
+
* The low fourteen bits of a short parameter id, which are the id.
|
|
51
|
+
*
|
|
52
|
+
* §7.4.1.2.2 puts `FLAG_IMPL_EXTENSION` at bit 15 and the
|
|
53
|
+
* must-understand flag at bit 14; everything below them is the
|
|
54
|
+
* identifier.
|
|
55
|
+
*/
|
|
56
|
+
export const PID_ID_MASK = 0x3fff;
|
|
57
|
+
/**
|
|
58
|
+
* `PID_IGNORE`. §7.4.1.2.2: "All consumers of this Data Representation
|
|
59
|
+
* shall ignore this parameter" — with no exception for the
|
|
60
|
+
* must-understand bit above it.
|
|
61
|
+
*
|
|
62
|
+
* Declared here and not in `writer.ts` because no writer in this tree
|
|
63
|
+
* emits one. It is a parameter a *peer* sends, so only the reader has
|
|
64
|
+
* anything to do with it.
|
|
65
|
+
*/
|
|
66
|
+
export const PID_IGNORE = 0x3f03;
|
|
67
|
+
/** `FLAG_2` of an `eMemberHeader`: the member's must-understand. §7.4.1.2.1. */
|
|
68
|
+
const E_FLAG_MUST_UNDERSTAND = 0x4000_0000;
|
|
69
|
+
/**
|
|
70
|
+
* `FLAG_1` of an `eMemberHeader`: `FLAG_IMPL_EXTENSION`, at bit 31
|
|
71
|
+
* exactly as bit 15 carries it in the short header.
|
|
72
|
+
*/
|
|
73
|
+
const E_FLAG_IMPL_EXTENSION = 0x8000_0000;
|
|
74
|
+
/** The member id an `eMemberHeader` carries. §7.4.1.2.1. */
|
|
75
|
+
const E_MEMBER_ID_MASK = 0x0fff_ffff;
|
|
76
|
+
/** The UTF-16 byte-order mark. */
|
|
77
|
+
const BYTE_ORDER_MARK = 0xfeff;
|
|
78
|
+
/**
|
|
79
|
+
* The byte-swapped mark.
|
|
80
|
+
*
|
|
81
|
+
* `U+FFFE` is not a character: a mark that reads as one says the units
|
|
82
|
+
* behind it are the other way round, which is the whole of what a mark
|
|
83
|
+
* is for.
|
|
84
|
+
*/
|
|
85
|
+
const SWAPPED_BYTE_ORDER_MARK = 0xfffe;
|
|
86
|
+
/**
|
|
87
|
+
* The widest `fixed` IDL admits is `fixed<31, S>`, which is sixteen
|
|
88
|
+
* octets — thirty-two nibbles. A value-precision field that presents no
|
|
89
|
+
* sign nibble inside them is not a `fixed` at all.
|
|
90
|
+
*/
|
|
91
|
+
const MAX_FIXED_NIBBLES = 32;
|
|
92
|
+
/** The first nibble value [CDR] §9.3.2.8 reserves for a sign. */
|
|
93
|
+
const FIRST_SIGN_NIBBLE = 0x0a;
|
|
94
|
+
/** The sign nibble that means negative. [CDR] §9.3.2.8. */
|
|
95
|
+
const MINUS_NIBBLE = 0x0d;
|
|
96
|
+
/**
|
|
97
|
+
* Whether a parameter can be one of the reading type's members.
|
|
98
|
+
*
|
|
99
|
+
* False for a vendor-private parameter, for a reserved id and for
|
|
100
|
+
* `PID_IGNORE`. All three are legal for a peer to send and none of them
|
|
101
|
+
* is a member, so all three take the unknown-parameter path — skipped,
|
|
102
|
+
* or the sample discarded when the writer set must-understand.
|
|
103
|
+
* §7.4.1.2.2, §7.2.2.4.4.4.6.
|
|
104
|
+
*
|
|
105
|
+
* A free function and not a method, because the generated decoder calls
|
|
106
|
+
* it without a receiver.
|
|
107
|
+
*/
|
|
108
|
+
export function pidIsUserMember(header) {
|
|
109
|
+
return !header.implExtension && !header.reserved && !header.ignore;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* How many octets `text` is as UTF-8, which is what a `string<N>` bound
|
|
113
|
+
* counts: `string<5>` is `char[6]` in the C mapping of every measured
|
|
114
|
+
* DDS implementation, so a five-scalar value of ten octets is one no
|
|
115
|
+
* peer can hold.
|
|
116
|
+
*/
|
|
117
|
+
export function utf8Octets(text) {
|
|
118
|
+
return new TextEncoder().encode(text).length;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* How many UTF-16 code units `text` is, which is what a `wstring<N>`
|
|
122
|
+
* bound counts. A surrogate pair is two, which is what `String.length`
|
|
123
|
+
* already answers.
|
|
124
|
+
*/
|
|
125
|
+
export function utf16Units(text) {
|
|
126
|
+
return text.length;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The same value in a `number`, whatever holder it arrived in.
|
|
130
|
+
*
|
|
131
|
+
* The generated decoder's narrowings are `as` in every other column and
|
|
132
|
+
* `as` is *erased* here: `zd_bits as number` on a `bigint` is a `tsc`
|
|
133
|
+
* error, and where it is not — `x as unknown as number` — it would put
|
|
134
|
+
* a `bigint` in a `number` field and every later comparison would be
|
|
135
|
+
* false. So the three coercions below are real conversions, and the
|
|
136
|
+
* TypeScript backend emits a call to one where the other columns emit a
|
|
137
|
+
* cast.
|
|
138
|
+
*
|
|
139
|
+
* A one-character string answers with its code point, which is what the
|
|
140
|
+
* narrowing means where the source is a `char`.
|
|
141
|
+
*/
|
|
142
|
+
export function zdNumber(value) {
|
|
143
|
+
if (typeof value === "string") {
|
|
144
|
+
return value.codePointAt(0) ?? 0;
|
|
145
|
+
}
|
|
146
|
+
if (typeof value === "boolean") {
|
|
147
|
+
return value ? 1 : 0;
|
|
148
|
+
}
|
|
149
|
+
return Number(value);
|
|
150
|
+
}
|
|
151
|
+
/** The same value in a `bigint`. See {@link zdNumber}. */
|
|
152
|
+
export function zdBigInt(value) {
|
|
153
|
+
if (typeof value === "string") {
|
|
154
|
+
return BigInt(value.codePointAt(0) ?? 0);
|
|
155
|
+
}
|
|
156
|
+
return BigInt(value);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The same value as a one-character `string`, which is what an IDL
|
|
160
|
+
* `char` and `wchar` are here.
|
|
161
|
+
*
|
|
162
|
+
* A value that is already a string is itself; a number or a `bigint` is
|
|
163
|
+
* the character its code point names. `String(65)` would be `"65"`,
|
|
164
|
+
* which is two characters and a different value.
|
|
165
|
+
*/
|
|
166
|
+
export function zdChar(value) {
|
|
167
|
+
if (typeof value === "string") {
|
|
168
|
+
return value;
|
|
169
|
+
}
|
|
170
|
+
return String.fromCodePoint(Number(value));
|
|
171
|
+
}
|
|
172
|
+
const WSTRING_CELLS = new Map([
|
|
173
|
+
[
|
|
174
|
+
WstringLengthPolicy.Specification,
|
|
175
|
+
{ countsCharacters: false, terminated: false, unitWidth: 2 },
|
|
176
|
+
],
|
|
177
|
+
[
|
|
178
|
+
WstringLengthPolicy.CharacterCount,
|
|
179
|
+
{ countsCharacters: true, terminated: false, unitWidth: 2 },
|
|
180
|
+
],
|
|
181
|
+
[
|
|
182
|
+
WstringLengthPolicy.CharacterCountTerminated,
|
|
183
|
+
{ countsCharacters: true, terminated: true, unitWidth: 4 },
|
|
184
|
+
],
|
|
185
|
+
[
|
|
186
|
+
WstringLengthPolicy.SpecificationWideUnits,
|
|
187
|
+
{ countsCharacters: false, terminated: false, unitWidth: 4 },
|
|
188
|
+
],
|
|
189
|
+
[
|
|
190
|
+
WstringLengthPolicy.RosTypeSupport,
|
|
191
|
+
{ countsCharacters: true, terminated: false, unitWidth: 4 },
|
|
192
|
+
],
|
|
193
|
+
[
|
|
194
|
+
WstringLengthPolicy.OctetsTerminated2,
|
|
195
|
+
{ countsCharacters: false, terminated: true, unitWidth: 2 },
|
|
196
|
+
],
|
|
197
|
+
[
|
|
198
|
+
WstringLengthPolicy.OctetsTerminated4,
|
|
199
|
+
{ countsCharacters: false, terminated: true, unitWidth: 4 },
|
|
200
|
+
],
|
|
201
|
+
[
|
|
202
|
+
WstringLengthPolicy.CharactersTerminated2,
|
|
203
|
+
{ countsCharacters: true, terminated: true, unitWidth: 2 },
|
|
204
|
+
],
|
|
205
|
+
]);
|
|
206
|
+
/**
|
|
207
|
+
* Whether a unit order reads its code units little-endian regardless of
|
|
208
|
+
* the stream.
|
|
209
|
+
*
|
|
210
|
+
* `host-unswapped` is what Fast CDR writes — the host's order, never
|
|
211
|
+
* byte-swapped — and this runtime's host is little-endian wherever
|
|
212
|
+
* JavaScript's `DataView` default is not asked for. `byte-order-mark`
|
|
213
|
+
* reads little too and then obeys the mark it found, which is correct in
|
|
214
|
+
* all four combinations of writer host and stream order: the mark is
|
|
215
|
+
* written in the same order as the content it introduces.
|
|
216
|
+
*/
|
|
217
|
+
function readsLittleAlways(order) {
|
|
218
|
+
return (order === WstringUnitOrder.HostUnswapped ||
|
|
219
|
+
order === WstringUnitOrder.ByteOrderMark);
|
|
220
|
+
}
|
|
221
|
+
export class Reader {
|
|
222
|
+
order;
|
|
223
|
+
plCdr1;
|
|
224
|
+
bytes;
|
|
225
|
+
at = 0;
|
|
226
|
+
origin = 0;
|
|
227
|
+
maxAlignment;
|
|
228
|
+
state = Status.Ok;
|
|
229
|
+
/**
|
|
230
|
+
* A reader over `data` in `order`, capped at `maxAlign`.
|
|
231
|
+
*
|
|
232
|
+
* Four for XCDR2, eight for XCDR1 — {@link Writer}'s counterpart.
|
|
233
|
+
* §7.4.1: `ALIGN(N)` aligns to `MIN(N, XCDR.maxalign)`. A reader that
|
|
234
|
+
* skipped padding the writer never wrote reads the wrong octets from
|
|
235
|
+
* the second member on.
|
|
236
|
+
*
|
|
237
|
+
* `plCdr1` is the writer's framing, and the reader needs two things
|
|
238
|
+
* out of it. Under a framing that puts `FLAG_IMPL_EXTENSION` on every
|
|
239
|
+
* member id — OpenDDS writes `c0010004` for `@id(1) long a` — the flag
|
|
240
|
+
* no longer tells a vendor-private parameter from a user member. And
|
|
241
|
+
* §7.4.1.2.2 stops the short form's ids at `0x3F00` inclusive while
|
|
242
|
+
* one measured implementation carries the whole low fourteen bits, so
|
|
243
|
+
* a reader holding the clause's threshold as a constant reports
|
|
244
|
+
* `@id(16383)` as a reserved parameter and drops the member.
|
|
245
|
+
*/
|
|
246
|
+
constructor(data, order, maxAlign = 8, plCdr1 = PL_CDR1_DEFAULT) {
|
|
247
|
+
this.order = order;
|
|
248
|
+
this.plCdr1 = plCdr1;
|
|
249
|
+
this.bytes = data;
|
|
250
|
+
this.maxAlignment = Math.max(1, maxAlign);
|
|
251
|
+
}
|
|
252
|
+
/** The alignment cap in force. */
|
|
253
|
+
get maxAlign() {
|
|
254
|
+
return this.maxAlignment;
|
|
255
|
+
}
|
|
256
|
+
// -- the status a generated decoder reads ----------------------------
|
|
257
|
+
/**
|
|
258
|
+
* Whether a refusal has been made, so every later read does nothing.
|
|
259
|
+
*
|
|
260
|
+
* The generated decoder carries on to the end of the type rather than
|
|
261
|
+
* returning early — that is what lets one `if` at the top of the
|
|
262
|
+
* caller decide what to do with the sample — and a read that consumed
|
|
263
|
+
* octets after a refusal would leave the cursor somewhere no writer
|
|
264
|
+
* put it, which the trailing-octet check at the top would then report
|
|
265
|
+
* as a second, invented, fault.
|
|
266
|
+
*/
|
|
267
|
+
stopped() {
|
|
268
|
+
return this.state !== Status.Ok;
|
|
269
|
+
}
|
|
270
|
+
/** How the read is going. */
|
|
271
|
+
readerStatus() {
|
|
272
|
+
return this.state;
|
|
273
|
+
}
|
|
274
|
+
/** Refuse the stream from outside the runtime. The first refusal sticks. */
|
|
275
|
+
readerRefuse(refusal) {
|
|
276
|
+
if (refusal !== Status.Ok && this.state === Status.Ok) {
|
|
277
|
+
this.state = refusal;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Take back a {@link Status.Discarded}, and only that one.
|
|
282
|
+
*
|
|
283
|
+
* A member whose own `@try_construct` is `USE_DEFAULT` but which also
|
|
284
|
+
* carries an explicit `@default` cannot be answered inside the type's
|
|
285
|
+
* decoder: the decoder knows Table 9's value for the type and not the
|
|
286
|
+
* literal the member's declaration gave it, and §7.2.2.4.4.4.9 puts
|
|
287
|
+
* the literal first. So the call site asks for DISCARD, and where it
|
|
288
|
+
* gets one it clears it and assigns the literal.
|
|
289
|
+
*
|
|
290
|
+
* Every other status stays: a truncated stream is not a narrowing.
|
|
291
|
+
*/
|
|
292
|
+
readerClearDiscard() {
|
|
293
|
+
if (this.state === Status.Discarded) {
|
|
294
|
+
this.state = Status.Ok;
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
/** Where it is. The generated decoder's name for {@link position}. */
|
|
300
|
+
readerPosition() {
|
|
301
|
+
return this.at;
|
|
302
|
+
}
|
|
303
|
+
/** How many octets are left. */
|
|
304
|
+
readerRemaining() {
|
|
305
|
+
return this.remaining();
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Move the alignment origin to the current position.
|
|
309
|
+
*
|
|
310
|
+
* The read-side pair of the writer's `beginPackedElement`, for
|
|
311
|
+
* `ElementPadding::Packed`. Returns the origin displaced.
|
|
312
|
+
*/
|
|
313
|
+
readerBeginPackedElement() {
|
|
314
|
+
const saved = this.origin;
|
|
315
|
+
this.origin = this.at;
|
|
316
|
+
return saved;
|
|
317
|
+
}
|
|
318
|
+
/** Restore the origin {@link readerBeginPackedElement} displaced. */
|
|
319
|
+
readerFinishPackedElement(saved) {
|
|
320
|
+
this.origin = saved;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Move the cursor forward to `target`, which is never backwards.
|
|
324
|
+
*
|
|
325
|
+
* A framing word pointing behind the cursor means the reader has
|
|
326
|
+
* already read past what it framed, and seeking back would paper over
|
|
327
|
+
* it: the member would be read twice and the sample would come back
|
|
328
|
+
* plausible.
|
|
329
|
+
*/
|
|
330
|
+
readerSkipTo(target) {
|
|
331
|
+
if (this.stopped()) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (target < this.at) {
|
|
335
|
+
this.readerRefuse(Status.OutOfRange);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (target > this.bytes.length) {
|
|
339
|
+
throw new Truncated(this.at, target - this.at, this.remaining());
|
|
340
|
+
}
|
|
341
|
+
this.at = target;
|
|
342
|
+
}
|
|
343
|
+
/** The cursor, the origin and the status, to be put back later. */
|
|
344
|
+
readerSave() {
|
|
345
|
+
return { at: this.at, origin: this.origin, status: this.state };
|
|
346
|
+
}
|
|
347
|
+
/** Put back what {@link readerSave} took. */
|
|
348
|
+
readerRestore(mark) {
|
|
349
|
+
this.at = mark.at;
|
|
350
|
+
this.origin = mark.origin;
|
|
351
|
+
this.state = mark.status;
|
|
352
|
+
}
|
|
353
|
+
// -- state -----------------------------------------------------------
|
|
354
|
+
/** Where it is. */
|
|
355
|
+
position() {
|
|
356
|
+
return this.at;
|
|
357
|
+
}
|
|
358
|
+
/** How many octets are left. */
|
|
359
|
+
remaining() {
|
|
360
|
+
return Math.max(0, this.bytes.length - this.at);
|
|
361
|
+
}
|
|
362
|
+
/** Skip padding to `align`, counted from the stream's origin. */
|
|
363
|
+
alignTo(align) {
|
|
364
|
+
if (this.stopped()) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const to = Math.min(align, this.maxAlignment);
|
|
368
|
+
if (to <= 1) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const offset = this.at - this.origin;
|
|
372
|
+
this.at += (to - (((offset % to) + to) % to)) % to;
|
|
373
|
+
}
|
|
374
|
+
/** Step over `count` octets, refusing to step past the end. */
|
|
375
|
+
skip(count) {
|
|
376
|
+
if (this.stopped()) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
this.take(count);
|
|
380
|
+
}
|
|
381
|
+
/** Move to an absolute offset inside the buffer. */
|
|
382
|
+
seek(target) {
|
|
383
|
+
if (this.stopped()) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (target < 0 || target > this.bytes.length) {
|
|
387
|
+
throw new Truncated(this.at, target, this.bytes.length);
|
|
388
|
+
}
|
|
389
|
+
this.at = target;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Advance over `count` octets and answer where they started.
|
|
393
|
+
*
|
|
394
|
+
* A negative count is refused rather than treated as zero: it can only
|
|
395
|
+
* come from a length field this reader computed wrongly, and a zero
|
|
396
|
+
* there would carry on reading at an offset nothing wrote.
|
|
397
|
+
*/
|
|
398
|
+
take(count) {
|
|
399
|
+
if (!Number.isInteger(count) || count < 0) {
|
|
400
|
+
throw new Truncated(this.at, count, this.remaining());
|
|
401
|
+
}
|
|
402
|
+
const end = this.at + count;
|
|
403
|
+
if (end > this.bytes.length) {
|
|
404
|
+
throw new Truncated(this.at, count, this.remaining());
|
|
405
|
+
}
|
|
406
|
+
const from = this.at;
|
|
407
|
+
this.at = end;
|
|
408
|
+
return from;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* An integer of `width` octets in the stream's order, as a `bigint`.
|
|
412
|
+
*
|
|
413
|
+
* A `bigint` throughout and narrowed by the caller, because the eight-
|
|
414
|
+
* octet case does not fit a `number` and a second code path for it
|
|
415
|
+
* would be the one place the two widths could disagree about a sign.
|
|
416
|
+
*/
|
|
417
|
+
readInt(width, signed) {
|
|
418
|
+
this.alignTo(width);
|
|
419
|
+
return this.rawInt(width, signed, isLittle(this.order));
|
|
420
|
+
}
|
|
421
|
+
/** {@link readInt} in a stated order rather than the stream's. */
|
|
422
|
+
rawInt(width, signed, little) {
|
|
423
|
+
const from = this.take(width);
|
|
424
|
+
let value = 0n;
|
|
425
|
+
for (let index = 0; index < width; index += 1) {
|
|
426
|
+
const shift = little ? index : width - 1 - index;
|
|
427
|
+
value |= BigInt(this.bytes[from + index] ?? 0) << BigInt(8 * shift);
|
|
428
|
+
}
|
|
429
|
+
if (signed) {
|
|
430
|
+
const sign = 1n << BigInt(width * 8 - 1);
|
|
431
|
+
if ((value & sign) !== 0n) {
|
|
432
|
+
value -= 1n << BigInt(width * 8);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return value;
|
|
436
|
+
}
|
|
437
|
+
// -- primitives ------------------------------------------------------
|
|
438
|
+
/**
|
|
439
|
+
* `boolean`.
|
|
440
|
+
*
|
|
441
|
+
* An octet that is neither 0 nor 1 is a stream that disagrees with its
|
|
442
|
+
* own type, and is refused rather than coerced: `!!octet` would turn
|
|
443
|
+
* every one of the 254 impossible values into `true` and say nothing.
|
|
444
|
+
*/
|
|
445
|
+
readBool() {
|
|
446
|
+
if (this.stopped()) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
const where = this.at;
|
|
450
|
+
const value = this.readU8();
|
|
451
|
+
if (value === 0) {
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
if (value === 1) {
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
throw new OutOfRange(where, "a boolean of 0 or 1");
|
|
458
|
+
}
|
|
459
|
+
/** `octet` and `uint8`. */
|
|
460
|
+
readU8() {
|
|
461
|
+
if (this.stopped()) {
|
|
462
|
+
return 0;
|
|
463
|
+
}
|
|
464
|
+
return this.bytes[this.take(1)] ?? 0;
|
|
465
|
+
}
|
|
466
|
+
/** `int8`. */
|
|
467
|
+
readI8() {
|
|
468
|
+
if (this.stopped()) {
|
|
469
|
+
return 0;
|
|
470
|
+
}
|
|
471
|
+
const value = this.bytes[this.take(1)] ?? 0;
|
|
472
|
+
return value >= 0x80 ? value - 0x100 : value;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* `char`, one octet, as the one-character string it is here.
|
|
476
|
+
*
|
|
477
|
+
* The octet is a code point in `[0, 255]` and not a UTF-8 lead: an IDL
|
|
478
|
+
* `char` is one octet and §7.4.1.1.1 gives it no encoding of its own,
|
|
479
|
+
* so `0xE4` is U+00E4 and not the first half of anything.
|
|
480
|
+
*/
|
|
481
|
+
readChar() {
|
|
482
|
+
if (this.stopped()) {
|
|
483
|
+
return "\0";
|
|
484
|
+
}
|
|
485
|
+
return String.fromCharCode(this.bytes[this.take(1)] ?? 0);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* `wchar`, at `width` octets and in `framing`'s shape.
|
|
489
|
+
*
|
|
490
|
+
* The four-octet form is RTI Connext's under XCDR1, where the high
|
|
491
|
+
* sixteen bits are padding around one UTF-16 code unit. A value that
|
|
492
|
+
* uses them is refused rather than narrowed: narrowing would return a
|
|
493
|
+
* character the peer never sent.
|
|
494
|
+
*
|
|
495
|
+
* {@link WcharFraming.GiopLengthPrefixed} is CORBA 3.3 part 2
|
|
496
|
+
* §9.3.1.6's GIOP 1.2 form, which all three ORBs write. The octet
|
|
497
|
+
* count is checked against the configured code-set width rather than
|
|
498
|
+
* believed: a stream whose first octet says `0xE4` — which is what a
|
|
499
|
+
* bare two-octet `wchar` looks like read this way — would otherwise
|
|
500
|
+
* consume 228 octets of whatever followed and return a character
|
|
501
|
+
* assembled out of the next four members.
|
|
502
|
+
*/
|
|
503
|
+
readWchar(width = WcharWidth.Octets2, framing = WcharFraming.Fixed, order = WstringUnitOrder.Stream) {
|
|
504
|
+
if (this.stopped()) {
|
|
505
|
+
return "\0";
|
|
506
|
+
}
|
|
507
|
+
const where = this.at;
|
|
508
|
+
const octets = width;
|
|
509
|
+
let value;
|
|
510
|
+
if (framing === WcharFraming.GiopLengthPrefixed) {
|
|
511
|
+
const declared = this.readU8();
|
|
512
|
+
if (declared !== octets) {
|
|
513
|
+
throw new OutOfRange(where, `a GIOP 1.2 wchar of ${octets} octets, the configured transmission ` +
|
|
514
|
+
`code set, and not ${declared}`);
|
|
515
|
+
}
|
|
516
|
+
// A scalar cannot carry a byte-order mark, so `byte-order-mark`
|
|
517
|
+
// means CORBA's default here — big-endian — exactly as
|
|
518
|
+
// `big-endian-always` does. The units are aligned to one inside
|
|
519
|
+
// the GIOP form, so `rawInt` and not `readInt`.
|
|
520
|
+
const little = order === WstringUnitOrder.Stream && isLittle(this.order);
|
|
521
|
+
value = Number(this.rawInt(octets, false, little));
|
|
522
|
+
}
|
|
523
|
+
else if (octets === 4) {
|
|
524
|
+
value = this.readU32();
|
|
525
|
+
if (value > 0xffff) {
|
|
526
|
+
throw new OutOfRange(where, "a four-octet wchar holding one UTF-16 code unit");
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
value = this.readU16();
|
|
531
|
+
}
|
|
532
|
+
// §7.2.2.2.1.2.5 restricts a `wchar` to the BMP, and a lone
|
|
533
|
+
// surrogate is not a character of it: returning one would produce a
|
|
534
|
+
// string no `TextEncoder` can encode back.
|
|
535
|
+
if (value >= 0xd800 && value <= 0xdfff) {
|
|
536
|
+
throw new OutOfRange(where, "a Unicode code point");
|
|
537
|
+
}
|
|
538
|
+
return String.fromCharCode(value);
|
|
539
|
+
}
|
|
540
|
+
/** `short`. */
|
|
541
|
+
readI16() {
|
|
542
|
+
if (this.stopped()) {
|
|
543
|
+
return 0;
|
|
544
|
+
}
|
|
545
|
+
return Number(this.readInt(2, true));
|
|
546
|
+
}
|
|
547
|
+
/** `unsigned short`. */
|
|
548
|
+
readU16() {
|
|
549
|
+
if (this.stopped()) {
|
|
550
|
+
return 0;
|
|
551
|
+
}
|
|
552
|
+
return Number(this.readInt(2, false));
|
|
553
|
+
}
|
|
554
|
+
/** `long`. */
|
|
555
|
+
readI32() {
|
|
556
|
+
if (this.stopped()) {
|
|
557
|
+
return 0;
|
|
558
|
+
}
|
|
559
|
+
return Number(this.readInt(4, true));
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* `unsigned long`, as a `number`.
|
|
563
|
+
*
|
|
564
|
+
* 4294967295 is far below `Number.MAX_SAFE_INTEGER`, so unlike the
|
|
565
|
+
* Java column nothing has to widen and unlike the C column nothing
|
|
566
|
+
* comes back negative.
|
|
567
|
+
*/
|
|
568
|
+
readU32() {
|
|
569
|
+
if (this.stopped()) {
|
|
570
|
+
return 0;
|
|
571
|
+
}
|
|
572
|
+
return Number(this.readInt(4, false));
|
|
573
|
+
}
|
|
574
|
+
/** `long long`, which is a `bigint` because a binary64 is not one. */
|
|
575
|
+
readI64() {
|
|
576
|
+
if (this.stopped()) {
|
|
577
|
+
return 0n;
|
|
578
|
+
}
|
|
579
|
+
return this.readInt(8, true);
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* `unsigned long long`, as the value and not as a pattern.
|
|
583
|
+
*
|
|
584
|
+
* Where the Java column has to hand back `-1L` for
|
|
585
|
+
* `0xFFFFFFFFFFFFFFFF` because it has nothing wider than a `long`, a
|
|
586
|
+
* `bigint` holds 18446744073709551615 as itself.
|
|
587
|
+
*/
|
|
588
|
+
readU64() {
|
|
589
|
+
if (this.stopped()) {
|
|
590
|
+
return 0n;
|
|
591
|
+
}
|
|
592
|
+
return this.readInt(8, false);
|
|
593
|
+
}
|
|
594
|
+
/** `float`, a binary32. */
|
|
595
|
+
readF32() {
|
|
596
|
+
if (this.stopped()) {
|
|
597
|
+
return 0;
|
|
598
|
+
}
|
|
599
|
+
this.alignTo(4);
|
|
600
|
+
const from = this.take(4);
|
|
601
|
+
return new DataView(this.bytes.buffer, this.bytes.byteOffset + from, 4).getFloat32(0, isLittle(this.order));
|
|
602
|
+
}
|
|
603
|
+
/** `double`, a binary64. */
|
|
604
|
+
readF64() {
|
|
605
|
+
if (this.stopped()) {
|
|
606
|
+
return 0;
|
|
607
|
+
}
|
|
608
|
+
this.alignTo(8);
|
|
609
|
+
const from = this.take(8);
|
|
610
|
+
return new DataView(this.bytes.buffer, this.bytes.byteOffset + from, 8).getFloat64(0, isLittle(this.order));
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* `long double`: all sixteen octets, as the writer's counterpart holds
|
|
614
|
+
* them.
|
|
615
|
+
*
|
|
616
|
+
* The octets come back in the order a little-endian stream carries
|
|
617
|
+
* them, which is what {@link Writer.writeF128} takes; a big-endian
|
|
618
|
+
* stream's are reversed on the way in. Reading the first eight as a
|
|
619
|
+
* `double` and skipping the rest would decode every value a binary64
|
|
620
|
+
* cannot hold as a different number and say nothing.
|
|
621
|
+
*/
|
|
622
|
+
readF128() {
|
|
623
|
+
if (this.stopped()) {
|
|
624
|
+
return { octets: new Uint8Array(16) };
|
|
625
|
+
}
|
|
626
|
+
this.alignTo(8);
|
|
627
|
+
const from = this.take(16);
|
|
628
|
+
const octets = this.bytes.slice(from, from + 16);
|
|
629
|
+
if (!isLittle(this.order)) {
|
|
630
|
+
octets.reverse();
|
|
631
|
+
}
|
|
632
|
+
return { octets };
|
|
633
|
+
}
|
|
634
|
+
/** A 16-bit value little-endian whatever the stream's order is. */
|
|
635
|
+
readU16Little() {
|
|
636
|
+
if (this.stopped()) {
|
|
637
|
+
return 0;
|
|
638
|
+
}
|
|
639
|
+
this.alignTo(2);
|
|
640
|
+
return Number(this.rawInt(2, false, true));
|
|
641
|
+
}
|
|
642
|
+
/** A 16-bit value big-endian whatever the stream's order is. */
|
|
643
|
+
readU16Big() {
|
|
644
|
+
if (this.stopped()) {
|
|
645
|
+
return 0;
|
|
646
|
+
}
|
|
647
|
+
this.alignTo(2);
|
|
648
|
+
return Number(this.rawInt(2, false, false));
|
|
649
|
+
}
|
|
650
|
+
/** A 32-bit value little-endian whatever the stream's order is. */
|
|
651
|
+
readU32Little() {
|
|
652
|
+
if (this.stopped()) {
|
|
653
|
+
return 0;
|
|
654
|
+
}
|
|
655
|
+
this.alignTo(4);
|
|
656
|
+
return Number(this.rawInt(4, false, true));
|
|
657
|
+
}
|
|
658
|
+
/** A 32-bit value big-endian whatever the stream's order is. */
|
|
659
|
+
readU32Big() {
|
|
660
|
+
if (this.stopped()) {
|
|
661
|
+
return 0;
|
|
662
|
+
}
|
|
663
|
+
this.alignTo(4);
|
|
664
|
+
return Number(this.rawInt(4, false, false));
|
|
665
|
+
}
|
|
666
|
+
// -- strings ---------------------------------------------------------
|
|
667
|
+
/**
|
|
668
|
+
* `string`: a four-octet length that includes the terminator, then the
|
|
669
|
+
* octets, then a NUL. §7.4.1.1.1.
|
|
670
|
+
*
|
|
671
|
+
* A declared length of zero is impossible rather than empty: the count
|
|
672
|
+
* includes the terminator, so the shortest honest frame states one.
|
|
673
|
+
* A length past the end is {@link ImpossibleLength} and not
|
|
674
|
+
* {@link Truncated} — the stream is not short, the field is lying, and
|
|
675
|
+
* a reader that allocated for it first would be the vector.
|
|
676
|
+
*/
|
|
677
|
+
readString() {
|
|
678
|
+
if (this.stopped()) {
|
|
679
|
+
return "";
|
|
680
|
+
}
|
|
681
|
+
const where = this.at;
|
|
682
|
+
const length = this.readU32();
|
|
683
|
+
if (length === 0 || length > this.remaining() + 1) {
|
|
684
|
+
throw new ImpossibleLength(where, length, this.remaining());
|
|
685
|
+
}
|
|
686
|
+
const from = this.take(length - 1);
|
|
687
|
+
const text = this.decodeUtf8(from, length - 1, where);
|
|
688
|
+
this.readU8();
|
|
689
|
+
return text;
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* A `string` the reader materialises itself.
|
|
693
|
+
*
|
|
694
|
+
* `runtime/c` takes it out of a heap the caller set, because an
|
|
695
|
+
* embedded target decides where memory comes from. TypeScript has one
|
|
696
|
+
* answer to that question and this is it; the name is kept so that one
|
|
697
|
+
* walk names one operation in both columns.
|
|
698
|
+
*/
|
|
699
|
+
readStringAlloc() {
|
|
700
|
+
return this.readString();
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* A `wstring`, in whichever length convention the policy names.
|
|
704
|
+
*
|
|
705
|
+
* `runtime/c` converts to UTF-8 because that is what its
|
|
706
|
+
* `const char *` holds, and its name carries it. A TypeScript `string`
|
|
707
|
+
* is UTF-16 already and {@link readWstring} is the whole of the
|
|
708
|
+
* operation.
|
|
709
|
+
*/
|
|
710
|
+
readWstringUtf8(policy, order) {
|
|
711
|
+
return this.readWstring(policy, order);
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* The inverse of `Writer.writeWstringUtf8Octets`.
|
|
715
|
+
*
|
|
716
|
+
* `WstringCodeUnits::Utf8`: the declared length is the UTF-8 octet count,
|
|
717
|
+
* and the terminator `policy` asks for is the last of those octets. A count
|
|
718
|
+
* the stream cannot hold, a missing or an interior NUL, or an ill-formed
|
|
719
|
+
* UTF-8 sequence is refused, never a replacement character or a truncated
|
|
720
|
+
* value.
|
|
721
|
+
*/
|
|
722
|
+
readWstringUtf8Octets(policy) {
|
|
723
|
+
if (this.stopped()) {
|
|
724
|
+
return "";
|
|
725
|
+
}
|
|
726
|
+
const where = this.at;
|
|
727
|
+
const cell = WSTRING_CELLS.get(policy);
|
|
728
|
+
if (cell === undefined) {
|
|
729
|
+
throw new OutOfRange(where, `a wstring length policy, not \`${policy}\``);
|
|
730
|
+
}
|
|
731
|
+
const declared = this.readU32();
|
|
732
|
+
if (declared > this.remaining()) {
|
|
733
|
+
throw new ImpossibleLength(where, declared, this.remaining());
|
|
734
|
+
}
|
|
735
|
+
const from = this.take(declared);
|
|
736
|
+
let count = declared;
|
|
737
|
+
if (cell.terminated) {
|
|
738
|
+
if (count === 0 || this.bytes[from + count - 1] !== 0) {
|
|
739
|
+
throw new OutOfRange(where, "a NUL octet ending the wstring, as its count includes one");
|
|
740
|
+
}
|
|
741
|
+
// A NUL inside the text is the narrow string's problem in wide form:
|
|
742
|
+
// every C peer stops at it.
|
|
743
|
+
for (let index = 0; index < count - 1; index += 1) {
|
|
744
|
+
if (this.bytes[from + index] === 0) {
|
|
745
|
+
throw new OutOfRange(where, "no NUL inside a wstring a C peer would stop at");
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
count -= 1;
|
|
749
|
+
}
|
|
750
|
+
return this.decodeUtf8(from, count, where);
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* A `wstring`, in the form `policy` and `order` name.
|
|
754
|
+
*
|
|
755
|
+
* Under {@link WstringLengthPolicy.Specification} the count is octets
|
|
756
|
+
* and not characters — §7.4.1.1.2 — so a count the code-unit width
|
|
757
|
+
* does not divide is a stream no encoder produced and is refused
|
|
758
|
+
* rather than rounded down: rounding would leave the remainder in the
|
|
759
|
+
* stream and put every later member at the wrong offset.
|
|
760
|
+
*
|
|
761
|
+
* The form is never inferred from the stream. The cells are not
|
|
762
|
+
* separable: a Fast DDS `"ab"` followed by a `long` is byte-identical
|
|
763
|
+
* to a specification `"a"` followed by the same `long`, differing only
|
|
764
|
+
* in padding no clause fixes. Choosing between them would mean letting
|
|
765
|
+
* the length field — the one field a hostile peer controls — decide
|
|
766
|
+
* how much is read.
|
|
767
|
+
*/
|
|
768
|
+
readWstring(policy, order) {
|
|
769
|
+
if (this.stopped()) {
|
|
770
|
+
return "";
|
|
771
|
+
}
|
|
772
|
+
const where = this.at;
|
|
773
|
+
const cell = WSTRING_CELLS.get(policy);
|
|
774
|
+
if (cell === undefined) {
|
|
775
|
+
throw new OutOfRange(where, `a wstring length policy, not \`${policy}\``);
|
|
776
|
+
}
|
|
777
|
+
const declared = this.readU32();
|
|
778
|
+
let count;
|
|
779
|
+
if (cell.countsCharacters) {
|
|
780
|
+
count = declared;
|
|
781
|
+
}
|
|
782
|
+
else {
|
|
783
|
+
if (declared % cell.unitWidth !== 0) {
|
|
784
|
+
throw new ImpossibleLength(where, declared, this.remaining());
|
|
785
|
+
}
|
|
786
|
+
count = declared / cell.unitWidth;
|
|
787
|
+
}
|
|
788
|
+
let units = this.readWstringUnits(where, count, cell.unitWidth, order);
|
|
789
|
+
if (cell.terminated) {
|
|
790
|
+
// The count includes the NUL, so a count of zero is a frame that
|
|
791
|
+
// does not hold its own terminator.
|
|
792
|
+
if (declared < 1 || units.length === 0) {
|
|
793
|
+
throw new ImpossibleLength(where, declared, this.remaining());
|
|
794
|
+
}
|
|
795
|
+
if (units[units.length - 1] !== 0) {
|
|
796
|
+
throw new OutOfRange(where, "a NUL character ending the wstring, as its count includes one");
|
|
797
|
+
}
|
|
798
|
+
units = units.slice(0, units.length - 1);
|
|
799
|
+
// A NUL inside the text is the narrow string's problem in wide
|
|
800
|
+
// form: every C peer stops at it. Only where a terminator is
|
|
801
|
+
// expected — a cell without one is entitled to carry U+0000 as a
|
|
802
|
+
// character.
|
|
803
|
+
if (units.includes(0)) {
|
|
804
|
+
throw new OutOfRange(where, "no NUL inside a wstring a C peer would stop at");
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
if (order === WstringUnitOrder.ByteOrderMark) {
|
|
808
|
+
units = stripByteOrderMark(units);
|
|
809
|
+
}
|
|
810
|
+
let out = "";
|
|
811
|
+
let index = 0;
|
|
812
|
+
while (index < units.length) {
|
|
813
|
+
const unit = units[index] ?? 0;
|
|
814
|
+
if (unit >= 0xd800 && unit <= 0xdbff && index + 1 < units.length) {
|
|
815
|
+
const low = units[index + 1] ?? 0;
|
|
816
|
+
if (low >= 0xdc00 && low <= 0xdfff) {
|
|
817
|
+
out += String.fromCodePoint(0x10000 + ((unit - 0xd800) << 10) + (low - 0xdc00));
|
|
818
|
+
index += 2;
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (unit >= 0xd800 && unit <= 0xdfff) {
|
|
823
|
+
throw new OutOfRange(where, "a UTF-16 sequence with no lone surrogate");
|
|
824
|
+
}
|
|
825
|
+
out += String.fromCharCode(unit);
|
|
826
|
+
index += 1;
|
|
827
|
+
}
|
|
828
|
+
return out;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* `count` `wstring` characters of `width` octets, as UTF-16 units.
|
|
832
|
+
*
|
|
833
|
+
* The whole run is checked against what is left **before** the first
|
|
834
|
+
* unit is read, because a count is what a hostile peer controls: the
|
|
835
|
+
* check is the difference between refusing a frame and reading 4·2^32
|
|
836
|
+
* octets of whatever follows.
|
|
837
|
+
*
|
|
838
|
+
* The four-octet width is RTI's XCDR1 form, where a character is a
|
|
839
|
+
* code unit zero-extended. A value above `0xFFFF` there is not a form
|
|
840
|
+
* this reader knows how to narrow, so it is refused rather than
|
|
841
|
+
* truncated — truncating is the defect this whole policy exists
|
|
842
|
+
* because of.
|
|
843
|
+
*/
|
|
844
|
+
readWstringUnits(where, count, width, order) {
|
|
845
|
+
if (count < 0 || count * width > this.remaining()) {
|
|
846
|
+
throw new ImpossibleLength(where, count * width, this.remaining());
|
|
847
|
+
}
|
|
848
|
+
const little = readsLittleAlways(order);
|
|
849
|
+
const units = [];
|
|
850
|
+
for (let index = 0; index < count; index += 1) {
|
|
851
|
+
let unit;
|
|
852
|
+
if (width === 4) {
|
|
853
|
+
if (order === WstringUnitOrder.BigEndianAlways) {
|
|
854
|
+
unit = this.readU32Big();
|
|
855
|
+
}
|
|
856
|
+
else if (little) {
|
|
857
|
+
unit = this.readU32Little();
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
unit = this.readU32();
|
|
861
|
+
}
|
|
862
|
+
if (unit > 0xffff) {
|
|
863
|
+
throw new OutOfRange(where, "a four-octet wstring character holding one UTF-16 unit");
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
else if (order === WstringUnitOrder.BigEndianAlways) {
|
|
867
|
+
unit = this.readU16Big();
|
|
868
|
+
}
|
|
869
|
+
else if (little) {
|
|
870
|
+
unit = this.readU16Little();
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
unit = this.readU16();
|
|
874
|
+
}
|
|
875
|
+
units.push(unit);
|
|
876
|
+
}
|
|
877
|
+
return units;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* `text` cut to at most `octets` UTF-8 octets, never inside a
|
|
881
|
+
* character.
|
|
882
|
+
*
|
|
883
|
+
* Table 11: "The order of the characters in the string ... is
|
|
884
|
+
* preserved", so this is a prefix. A character that would straddle the
|
|
885
|
+
* bound is left out whole: a peer holding `char[octets + 1]` cannot
|
|
886
|
+
* store half of one either, and half a UTF-8 sequence is not text.
|
|
887
|
+
*/
|
|
888
|
+
trimUtf8(text, octets) {
|
|
889
|
+
if (octets < 0) {
|
|
890
|
+
return "";
|
|
891
|
+
}
|
|
892
|
+
const encoder = new TextEncoder();
|
|
893
|
+
let out = "";
|
|
894
|
+
let used = 0;
|
|
895
|
+
for (const character of text) {
|
|
896
|
+
const width = encoder.encode(character).length;
|
|
897
|
+
if (used + width > octets) {
|
|
898
|
+
break;
|
|
899
|
+
}
|
|
900
|
+
out += character;
|
|
901
|
+
used += width;
|
|
902
|
+
}
|
|
903
|
+
return out;
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* `text` cut to at most `units` UTF-16 code units, never between the
|
|
907
|
+
* halves of a surrogate pair.
|
|
908
|
+
*/
|
|
909
|
+
trimUtf16(text, units) {
|
|
910
|
+
if (units < 0) {
|
|
911
|
+
return "";
|
|
912
|
+
}
|
|
913
|
+
if (text.length <= units) {
|
|
914
|
+
return text;
|
|
915
|
+
}
|
|
916
|
+
let end = units;
|
|
917
|
+
// A high surrogate at the cut has lost its pair, and half a pair is
|
|
918
|
+
// not a character.
|
|
919
|
+
const last = text.charCodeAt(end - 1);
|
|
920
|
+
if (end > 0 && last >= 0xd800 && last <= 0xdbff) {
|
|
921
|
+
end -= 1;
|
|
922
|
+
}
|
|
923
|
+
return text.slice(0, end);
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* `fixed<digits, scale>`, as the decimal string the writer took.
|
|
927
|
+
*
|
|
928
|
+
* The mirror of `Writer.writeFixed`: `ceil((digits + 1) / 2)` octets
|
|
929
|
+
* of BCD, digits most-significant first, sign nibble last.
|
|
930
|
+
*
|
|
931
|
+
* The leading zeros go, because the BCD field is `digits` wide
|
|
932
|
+
* whatever the value: the nibbles for `0.01` in a `fixed<5, 2>` spell
|
|
933
|
+
* `00001`, and returning `"000.01"` would put a decimal on the read
|
|
934
|
+
* side that the write side would never produce from the same value.
|
|
935
|
+
* A negative zero loses its sign for the same reason: `-0.00` and
|
|
936
|
+
* `0.00` are one value, and the sign nibble is the only place they
|
|
937
|
+
* differ.
|
|
938
|
+
*
|
|
939
|
+
* Under {@link FixedDigitCount.ValuePrecision} the field's end is read
|
|
940
|
+
* out of the octets: [CDR] §9.3.2.8 makes a digit `0` to `9` and a
|
|
941
|
+
* sign `0xa` upwards, and the nibble count is even by construction, so
|
|
942
|
+
* the field ends at the first octet whose **low** nibble is a sign. A
|
|
943
|
+
* field that never presents one within the sixteen octets of the
|
|
944
|
+
* widest `fixed` IDL admits is refused.
|
|
945
|
+
*/
|
|
946
|
+
readFixed(digits, scale, count) {
|
|
947
|
+
if (this.stopped()) {
|
|
948
|
+
return "";
|
|
949
|
+
}
|
|
950
|
+
// The same bound as the writer's, on this side too: OMG IDL 4.2 rule
|
|
951
|
+
// (42) caps the BCD rule at 31 digits, and a reader asked for a wider
|
|
952
|
+
// field would take octets no declaration can name.
|
|
953
|
+
if (digits <= 0 || digits > 31 || scale > digits) {
|
|
954
|
+
throw new OutOfRange(this.at, `a fixed<P, S> with S <= P and 1 <= P <= 31, not fixed<${digits}, ${scale}>`);
|
|
955
|
+
}
|
|
956
|
+
let nibbles = [];
|
|
957
|
+
let sign;
|
|
958
|
+
if (count === FixedDigitCount.ValuePrecision ||
|
|
959
|
+
count === FixedDigitCount.ValueStringDigits) {
|
|
960
|
+
for (;;) {
|
|
961
|
+
if (nibbles.length >= MAX_FIXED_NIBBLES) {
|
|
962
|
+
throw new OutOfRange(this.at, "a sign nibble within sixteen octets");
|
|
963
|
+
}
|
|
964
|
+
const octet = this.readU8();
|
|
965
|
+
nibbles.push(octet >> 4);
|
|
966
|
+
nibbles.push(octet & 0x0f);
|
|
967
|
+
if ((octet & 0x0f) >= FIRST_SIGN_NIBBLE) {
|
|
968
|
+
break;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
sign = nibbles.pop() ?? 0;
|
|
972
|
+
}
|
|
973
|
+
else if (count === FixedDigitCount.Declared) {
|
|
974
|
+
const octets = Math.floor((digits + 2) / 2);
|
|
975
|
+
const from = this.take(octets);
|
|
976
|
+
for (let index = 0; index < octets; index += 1) {
|
|
977
|
+
const octet = this.bytes[from + index] ?? 0;
|
|
978
|
+
nibbles.push(octet >> 4);
|
|
979
|
+
nibbles.push(octet & 0x0f);
|
|
980
|
+
}
|
|
981
|
+
sign = nibbles.pop() ?? 0;
|
|
982
|
+
// An even declared digit count leaves the field one nibble wider
|
|
983
|
+
// than the value, and the pad is at the front.
|
|
984
|
+
if ((digits + 1) % 2 === 1) {
|
|
985
|
+
nibbles = nibbles.slice(1);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
else {
|
|
989
|
+
throw new OutOfRange(this.at, `a fixed digit count, not \`${count}\``);
|
|
990
|
+
}
|
|
991
|
+
let text = "";
|
|
992
|
+
for (const nibble of nibbles) {
|
|
993
|
+
if (nibble > 9) {
|
|
994
|
+
throw new OutOfRange(this.at, "BCD digits");
|
|
995
|
+
}
|
|
996
|
+
text += String.fromCharCode(0x30 + nibble);
|
|
997
|
+
}
|
|
998
|
+
// The point sits `scale` digits from the right and not
|
|
999
|
+
// `digits - scale` from the left: under the value's count the field
|
|
1000
|
+
// carries fewer than `digits`, and one that carries fewer than the
|
|
1001
|
+
// scale — `1c` under `fixed<5,2>` — needs its integer part written
|
|
1002
|
+
// as a zero.
|
|
1003
|
+
text = text.padStart(scale + 1, "0");
|
|
1004
|
+
const split = text.length - scale;
|
|
1005
|
+
const whole = text.slice(0, split).replace(/^0+(?=.)/, "");
|
|
1006
|
+
const fraction = text.slice(split);
|
|
1007
|
+
const out = scale === 0 ? whole : `${whole}.${fraction}`;
|
|
1008
|
+
const zero = whole === "0" && !/[1-9]/.test(fraction);
|
|
1009
|
+
return sign === MINUS_NIBBLE && !zero ? `-${out}` : out;
|
|
1010
|
+
}
|
|
1011
|
+
// -- the bit-bounded kinds -------------------------------------------
|
|
1012
|
+
/**
|
|
1013
|
+
* An enumerator's holder, **sign-extended from its holder's width**.
|
|
1014
|
+
*
|
|
1015
|
+
* §7.4.3.5.3 rule (5) makes the holder signed: `@value(200)` under a
|
|
1016
|
+
* bound of eight goes out as `c8`, and that octet read as the type
|
|
1017
|
+
* says is `-56`. `@value(-1)` and `@value(255)` are the same octet and
|
|
1018
|
+
* nothing in the stream separates them — which one was meant is a
|
|
1019
|
+
* question about the *type*, and the generated decoder answers it by
|
|
1020
|
+
* comparing this against each declared enumerator's own pattern, taken
|
|
1021
|
+
* the same way.
|
|
1022
|
+
*
|
|
1023
|
+
* The Java column had this unsigned while its walk compared against
|
|
1024
|
+
* the signed pattern, and every `@bit_bound(8)` enumerator above 127
|
|
1025
|
+
* matched no arm at all — the sample came back discarded rather than
|
|
1026
|
+
* wrong, which is why it took a corpus run to find. The sign extension
|
|
1027
|
+
* is from the *holder's* width and not from the bound: `@bit_bound(5)`
|
|
1028
|
+
* still occupies an octet, and rule (5) makes the octet signed.
|
|
1029
|
+
*/
|
|
1030
|
+
readEnum(bitBound) {
|
|
1031
|
+
if (this.stopped()) {
|
|
1032
|
+
return 0n;
|
|
1033
|
+
}
|
|
1034
|
+
if (bitBound <= 0 || bitBound > 32) {
|
|
1035
|
+
throw new OutOfRange(this.at, `an @bit_bound between 1 and 32, not ${bitBound}`);
|
|
1036
|
+
}
|
|
1037
|
+
const width = bitBound <= 8 ? 8 : bitBound <= 16 ? 16 : 32;
|
|
1038
|
+
const pattern = width === 8
|
|
1039
|
+
? BigInt(this.readU8())
|
|
1040
|
+
: width === 16
|
|
1041
|
+
? BigInt(this.readU16())
|
|
1042
|
+
: BigInt(this.readU32());
|
|
1043
|
+
const sign = 1n << BigInt(width - 1);
|
|
1044
|
+
return (pattern & sign) === 0n ? pattern : pattern - (1n << BigInt(width));
|
|
1045
|
+
}
|
|
1046
|
+
/** A bitmask's holder. Table 33 of §7.4.1.1.3.2. */
|
|
1047
|
+
readBitmask(bitBound) {
|
|
1048
|
+
if (this.stopped()) {
|
|
1049
|
+
return 0n;
|
|
1050
|
+
}
|
|
1051
|
+
if (bitBound <= 0 || bitBound > 64) {
|
|
1052
|
+
throw new OutOfRange(this.at, `an @bit_bound between 1 and 64, not ${bitBound}`);
|
|
1053
|
+
}
|
|
1054
|
+
if (bitBound <= 8) {
|
|
1055
|
+
return BigInt(this.readU8());
|
|
1056
|
+
}
|
|
1057
|
+
if (bitBound <= 16) {
|
|
1058
|
+
return BigInt(this.readU16());
|
|
1059
|
+
}
|
|
1060
|
+
if (bitBound <= 32) {
|
|
1061
|
+
return BigInt(this.readU32());
|
|
1062
|
+
}
|
|
1063
|
+
return this.readU64();
|
|
1064
|
+
}
|
|
1065
|
+
/** A bitset's anchor integer, of the width the planner sized. */
|
|
1066
|
+
readBitset(anchorOctets) {
|
|
1067
|
+
if (this.stopped()) {
|
|
1068
|
+
return 0n;
|
|
1069
|
+
}
|
|
1070
|
+
switch (anchorOctets) {
|
|
1071
|
+
case 1:
|
|
1072
|
+
return BigInt(this.readU8());
|
|
1073
|
+
case 2:
|
|
1074
|
+
return BigInt(this.readU16());
|
|
1075
|
+
case 4:
|
|
1076
|
+
return BigInt(this.readU32());
|
|
1077
|
+
case 8:
|
|
1078
|
+
return this.readU64();
|
|
1079
|
+
default:
|
|
1080
|
+
throw new OutOfRange(this.at, `a bitset anchor of 1, 2, 4 or 8 octets, not ${anchorOctets}`);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
// -- framing ---------------------------------------------------------
|
|
1084
|
+
/**
|
|
1085
|
+
* A sequence's element count, refusing one the stream cannot hold.
|
|
1086
|
+
*
|
|
1087
|
+
* An element is one octet or more, so a stream with fewer octets left
|
|
1088
|
+
* than elements claimed cannot be honest about it. Allocating for it
|
|
1089
|
+
* is how a reader turns a bad message into an out-of-memory — and in
|
|
1090
|
+
* this column `new Array(n)` for a claimed 4294967295 is exactly that.
|
|
1091
|
+
*/
|
|
1092
|
+
readSequenceLength() {
|
|
1093
|
+
if (this.stopped()) {
|
|
1094
|
+
return 0;
|
|
1095
|
+
}
|
|
1096
|
+
const where = this.at;
|
|
1097
|
+
const count = this.readU32();
|
|
1098
|
+
if (count > this.remaining()) {
|
|
1099
|
+
throw new ImpossibleLength(where, count, this.remaining());
|
|
1100
|
+
}
|
|
1101
|
+
return count;
|
|
1102
|
+
}
|
|
1103
|
+
/** A DHEADER: the length the members that follow occupy. §7.4.3.5.3. */
|
|
1104
|
+
readDheader() {
|
|
1105
|
+
if (this.stopped()) {
|
|
1106
|
+
return 0;
|
|
1107
|
+
}
|
|
1108
|
+
const where = this.at;
|
|
1109
|
+
const length = this.readU32();
|
|
1110
|
+
if (length > this.remaining()) {
|
|
1111
|
+
throw new ImpossibleLength(where, length, this.remaining());
|
|
1112
|
+
}
|
|
1113
|
+
return length;
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* An EMHEADER, unpacked into its parts and the NEXTINT it carries.
|
|
1117
|
+
*
|
|
1118
|
+
* Codes 5 to 7 report the NEXTINT they read, but the reader is put
|
|
1119
|
+
* back before it: §7.4.3.4.2 says that word "shall be reused also as
|
|
1120
|
+
* part of the serialized member", so it is the member's own leading
|
|
1121
|
+
* length and the member's decoder has to see it.
|
|
1122
|
+
*/
|
|
1123
|
+
readEmheader() {
|
|
1124
|
+
if (this.stopped()) {
|
|
1125
|
+
return emHeader(0, 0, false, 0, false);
|
|
1126
|
+
}
|
|
1127
|
+
const word = this.readU32();
|
|
1128
|
+
const lengthCode = (word >>> 28) & 0x7;
|
|
1129
|
+
const memberId = word & 0x0fff_ffff;
|
|
1130
|
+
const mustUnderstand = word >>> 31 !== 0;
|
|
1131
|
+
if (lengthCode === 4) {
|
|
1132
|
+
return emHeader(memberId, lengthCode, mustUnderstand, this.readU32(), true);
|
|
1133
|
+
}
|
|
1134
|
+
if (lengthCode >= 5) {
|
|
1135
|
+
const where = this.at;
|
|
1136
|
+
const peeked = this.readU32();
|
|
1137
|
+
this.at = where;
|
|
1138
|
+
return emHeader(memberId, lengthCode, mustUnderstand, peeked, true);
|
|
1139
|
+
}
|
|
1140
|
+
return emHeader(memberId, lengthCode, mustUnderstand, 0, false);
|
|
1141
|
+
}
|
|
1142
|
+
/** A PL_CDR1 parameter header in the {@link ParameterRole.Member} role. */
|
|
1143
|
+
readPid() {
|
|
1144
|
+
return this.readPidAs(ParameterRole.Member);
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* A PL_CDR1 parameter header, in whichever form it was written.
|
|
1148
|
+
*
|
|
1149
|
+
* `role` is the caller's, not the stream's: the octets are the same
|
|
1150
|
+
* octets and only what the declared length means differs. §7.4.1.2.
|
|
1151
|
+
*/
|
|
1152
|
+
readPidAs(role) {
|
|
1153
|
+
if (this.stopped()) {
|
|
1154
|
+
return {
|
|
1155
|
+
pid: 0,
|
|
1156
|
+
length: 0,
|
|
1157
|
+
end: true,
|
|
1158
|
+
mustUnderstand: false,
|
|
1159
|
+
implExtension: false,
|
|
1160
|
+
reserved: false,
|
|
1161
|
+
ignore: false,
|
|
1162
|
+
role,
|
|
1163
|
+
savedOrigin: this.origin,
|
|
1164
|
+
valueAt: this.at,
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
this.alignTo(4);
|
|
1168
|
+
const pid = this.readU16();
|
|
1169
|
+
const length = this.readU16();
|
|
1170
|
+
if ((pid & PID_ID_MASK) === (PID_EXTENDED & PID_ID_MASK)) {
|
|
1171
|
+
// The four flag bits are not part of the id: §7.4.1.2.1 splits the
|
|
1172
|
+
// `eMemberHeader` into `FLAG_1` to `FLAG_4` and a 28-bit member
|
|
1173
|
+
// id, and `FLAG_2` is the member's must-understand. Taking the
|
|
1174
|
+
// whole word gave a Cyclone member declared `@must_understand` an
|
|
1175
|
+
// id of `0x40000001`, which matches nothing, and the member was
|
|
1176
|
+
// dropped.
|
|
1177
|
+
const memberHeader = this.readU32();
|
|
1178
|
+
const wideLength = this.readU32();
|
|
1179
|
+
// The parameter's value is its own stream block: §7.4.1.2 counts a
|
|
1180
|
+
// member's internal alignment from the first octet after the
|
|
1181
|
+
// header, not from the start of the message.
|
|
1182
|
+
const saved = this.origin;
|
|
1183
|
+
this.origin = this.at;
|
|
1184
|
+
return {
|
|
1185
|
+
pid: memberHeader & E_MEMBER_ID_MASK,
|
|
1186
|
+
length: wideLength,
|
|
1187
|
+
end: false,
|
|
1188
|
+
mustUnderstand: (memberHeader & E_FLAG_MUST_UNDERSTAND) !== 0,
|
|
1189
|
+
implExtension: (memberHeader & E_FLAG_IMPL_EXTENSION) !== 0,
|
|
1190
|
+
// The extended id is 28 bits and has no reserved range.
|
|
1191
|
+
reserved: false,
|
|
1192
|
+
ignore: false,
|
|
1193
|
+
role,
|
|
1194
|
+
savedOrigin: saved,
|
|
1195
|
+
valueAt: this.at,
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
const identifier = pid & PID_ID_MASK;
|
|
1199
|
+
const end = identifier === (PID_LIST_END & PID_ID_MASK);
|
|
1200
|
+
// Under a framing that puts `FLAG_IMPL_EXTENSION` on every member id
|
|
1201
|
+
// the bit no longer tells a vendor-private parameter from a user
|
|
1202
|
+
// member — OpenDDS writes `c0010004` for `@id(1) long a`. It is
|
|
1203
|
+
// reported absent there because under that framing it carries no
|
|
1204
|
+
// information, and reporting it would send every member down the
|
|
1205
|
+
// unknown-parameter path.
|
|
1206
|
+
const flagged = (pid & PID_IMPL_EXTENSION) !== 0 &&
|
|
1207
|
+
(pidFlagsAt(this.plCdr1.pidFlags, "member") & PID_IMPL_EXTENSION) === 0;
|
|
1208
|
+
const saved = this.origin;
|
|
1209
|
+
this.origin = this.at;
|
|
1210
|
+
return {
|
|
1211
|
+
pid: identifier,
|
|
1212
|
+
length,
|
|
1213
|
+
end,
|
|
1214
|
+
mustUnderstand: (pid & PID_MUST_UNDERSTAND) !== 0,
|
|
1215
|
+
implExtension: flagged,
|
|
1216
|
+
reserved: !end && identifier > this.plCdr1.shortMaxId,
|
|
1217
|
+
ignore: identifier === PID_IGNORE,
|
|
1218
|
+
role,
|
|
1219
|
+
savedOrigin: saved,
|
|
1220
|
+
valueAt: this.at,
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* End a parameter's value and step to the next header.
|
|
1225
|
+
*
|
|
1226
|
+
* The padding to four follows the value and is outside the declared
|
|
1227
|
+
* length, so the next parameter is found by alignment and not by
|
|
1228
|
+
* adding the length — which is what lets one reader take RTI's
|
|
1229
|
+
* rounded-up length and Fast DDS's exact one.
|
|
1230
|
+
*
|
|
1231
|
+
* The declared length is checked against the configured framing rather
|
|
1232
|
+
* than believed: a length belonging to the other convention is a
|
|
1233
|
+
* stream this reader was pointed at with the wrong framing, and
|
|
1234
|
+
* reading on would put every later member at an offset no writer
|
|
1235
|
+
* produced. Both readings are accepted where they coincide, which is
|
|
1236
|
+
* every value already a multiple of four.
|
|
1237
|
+
*/
|
|
1238
|
+
finishPidRead(header) {
|
|
1239
|
+
this.origin = header.savedOrigin;
|
|
1240
|
+
if (this.stopped() || header.end) {
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
const used = this.at - header.valueAt;
|
|
1244
|
+
const padded = (used + 3) & ~3;
|
|
1245
|
+
const pads = header.role === ParameterRole.Optional
|
|
1246
|
+
? this.plCdr1.padsOptionalLength
|
|
1247
|
+
: this.plCdr1.padsDeclaredLength;
|
|
1248
|
+
if (header.length !== (pads ? padded : used) &&
|
|
1249
|
+
header.length !== used &&
|
|
1250
|
+
header.length !== padded) {
|
|
1251
|
+
this.readerRefuse(Status.OutOfRange);
|
|
1252
|
+
}
|
|
1253
|
+
this.alignTo(4);
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Step over a member this reader does not know.
|
|
1257
|
+
*
|
|
1258
|
+
* The whole point of the length code, and the reason §7.4.3.4.2 gives
|
|
1259
|
+
* one to every mutable member: a reader of an older type has to be
|
|
1260
|
+
* able to walk past what it does not have a field for.
|
|
1261
|
+
*/
|
|
1262
|
+
skipMember(header) {
|
|
1263
|
+
if (this.stopped()) {
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
if (header.lengthCode > 3 && !header.hasNextInt) {
|
|
1267
|
+
throw new OutOfRange(this.at, "a NEXTINT for a length code of 4 or above");
|
|
1268
|
+
}
|
|
1269
|
+
this.skip(header.extent);
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Start reading a parameter's value, answering with the origin it
|
|
1273
|
+
* replaces. The reader's half of the writer's parameter block.
|
|
1274
|
+
*/
|
|
1275
|
+
beginParameterValue() {
|
|
1276
|
+
const saved = this.origin;
|
|
1277
|
+
this.origin = this.at;
|
|
1278
|
+
return saved;
|
|
1279
|
+
}
|
|
1280
|
+
/** End a parameter's value and step over its trailing padding. */
|
|
1281
|
+
finishParameterValue(savedOrigin) {
|
|
1282
|
+
this.origin = savedOrigin;
|
|
1283
|
+
this.alignTo(4);
|
|
1284
|
+
}
|
|
1285
|
+
// -- text -------------------------------------------------------------
|
|
1286
|
+
/**
|
|
1287
|
+
* `length` octets from `from` as UTF-8, refusing anything that is not.
|
|
1288
|
+
*
|
|
1289
|
+
* `fatal: true` is the whole of it: without it `TextDecoder` replaces
|
|
1290
|
+
* every malformed sequence with U+FFFD and answers a string that is
|
|
1291
|
+
* the right length and the wrong text, which is precisely the silent
|
|
1292
|
+
* substitution {@link NotUtf8} exists to prevent.
|
|
1293
|
+
*/
|
|
1294
|
+
decodeUtf8(from, length, where) {
|
|
1295
|
+
try {
|
|
1296
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(this.bytes.subarray(from, from + length));
|
|
1297
|
+
}
|
|
1298
|
+
catch {
|
|
1299
|
+
throw new NotUtf8(where);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
/** An {@link EmHeaderRead} with its {@link EmHeaderRead.extent} worked out. */
|
|
1304
|
+
function emHeader(memberId, lengthCode, mustUnderstand, nextInt, hasNextInt) {
|
|
1305
|
+
let extent;
|
|
1306
|
+
if (lengthCode <= 3) {
|
|
1307
|
+
extent = 1 << lengthCode;
|
|
1308
|
+
}
|
|
1309
|
+
else if (lengthCode === 4) {
|
|
1310
|
+
extent = nextInt;
|
|
1311
|
+
}
|
|
1312
|
+
else {
|
|
1313
|
+
const scale = lengthCode === 5 ? 1 : lengthCode === 6 ? 4 : 8;
|
|
1314
|
+
extent = 4 + nextInt * scale;
|
|
1315
|
+
}
|
|
1316
|
+
return { memberId, lengthCode, mustUnderstand, nextInt, hasNextInt, extent };
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Take the byte-order mark off the front and apply what it said.
|
|
1320
|
+
*
|
|
1321
|
+
* The units arrived little-endian, because that is what omniORB writes
|
|
1322
|
+
* on a little-endian host and what the `byte-order-mark` order therefore
|
|
1323
|
+
* reads. Three cases, and the third is the one that matters:
|
|
1324
|
+
*
|
|
1325
|
+
* - `FEFF` — the mark is the right way up, the units are as read.
|
|
1326
|
+
* - `FFFE` — the mark is byte-swapped, so every unit behind it is too,
|
|
1327
|
+
* and each one is swapped back. This is what a conforming big-endian
|
|
1328
|
+
* omniORB peer produces and what a reader that ignored the mark would
|
|
1329
|
+
* return as different characters, well formed and the right length.
|
|
1330
|
+
* - no mark — CORBA's rule for a GIOP 1.2 `wstring` without one is
|
|
1331
|
+
* UTF-16 big-endian, so every unit read little-endian is swapped.
|
|
1332
|
+
*
|
|
1333
|
+
* The mark is never a character of the returned string in any of the
|
|
1334
|
+
* three, which is the half §7.4.1.1.2 cares about.
|
|
1335
|
+
*/
|
|
1336
|
+
function stripByteOrderMark(units) {
|
|
1337
|
+
if (units.length === 0) {
|
|
1338
|
+
return units;
|
|
1339
|
+
}
|
|
1340
|
+
if (units[0] === BYTE_ORDER_MARK) {
|
|
1341
|
+
return units.slice(1);
|
|
1342
|
+
}
|
|
1343
|
+
const rest = units[0] === SWAPPED_BYTE_ORDER_MARK ? units.slice(1) : units;
|
|
1344
|
+
return rest.map((unit) => ((unit & 0xff) << 8) | (unit >> 8));
|
|
1345
|
+
}
|