@totemsdk/edge-coap 0.1.0 → 0.3.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/LICENSE +202 -21
- package/NOTICE +60 -0
- package/README.md +201 -1
- package/dist/cbor.d.ts +22 -0
- package/dist/cbor.js +266 -0
- package/dist/cjs/cbor.d.ts +22 -0
- package/dist/cjs/cbor.js +271 -0
- package/dist/cjs/codec.d.ts +30 -0
- package/dist/cjs/codec.js +349 -0
- package/dist/cjs/gateway.d.ts +76 -0
- package/dist/cjs/gateway.js +511 -0
- package/dist/cjs/index.d.ts +13 -0
- package/dist/cjs/index.js +17 -0
- package/dist/cjs/native-transport.d.ts +33 -0
- package/dist/cjs/native-transport.js +183 -0
- package/dist/cjs/oscore.d.ts +32 -0
- package/dist/cjs/oscore.js +562 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/security.d.ts +29 -0
- package/dist/cjs/security.js +19 -0
- package/dist/cjs/sensor-bridge.d.ts +31 -0
- package/dist/cjs/sensor-bridge.js +99 -0
- package/dist/cjs/transport.d.ts +77 -0
- package/dist/cjs/transport.js +9 -0
- package/dist/codec.d.ts +30 -0
- package/dist/codec.js +339 -0
- package/dist/gateway.d.ts +61 -7
- package/dist/gateway.js +470 -67
- package/dist/index.d.ts +11 -3
- package/dist/index.js +6 -7
- package/dist/native-transport.d.ts +33 -0
- package/dist/native-transport.js +146 -0
- package/dist/oscore.d.ts +32 -0
- package/dist/oscore.js +526 -0
- package/dist/security.d.ts +29 -0
- package/dist/security.js +16 -0
- package/dist/sensor-bridge.d.ts +6 -1
- package/dist/sensor-bridge.js +73 -24
- package/dist/transport.d.ts +31 -1
- package/dist/transport.js +1 -2
- package/package.json +55 -26
- package/sidecar.json +34 -0
package/dist/cbor.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal CBOR (RFC 8949) decoder for constrained-device payloads.
|
|
3
|
+
*
|
|
4
|
+
* Supports the deterministic core used by sensors: unsigned/negative
|
|
5
|
+
* integers, byte/text strings, arrays, maps, tags (unwrapped), booleans, null,
|
|
6
|
+
* undefined, half/single/double floats and indefinite-length containers.
|
|
7
|
+
* Maps with all-text keys decode to plain objects; other maps decode to `Map`.
|
|
8
|
+
*
|
|
9
|
+
* This is a decoder, not a general CBOR library: it does not expose tag
|
|
10
|
+
* semantics and rejects malformed, truncated, duplicate-key or over-deep input.
|
|
11
|
+
*/
|
|
12
|
+
export class CborDecodeError extends Error {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'CborDecodeError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const BREAK = 0xff;
|
|
19
|
+
const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
|
|
20
|
+
export function decodeCbor(input, options = {}) {
|
|
21
|
+
if (!(input instanceof Uint8Array))
|
|
22
|
+
throw new CborDecodeError('CBOR input must be a Uint8Array');
|
|
23
|
+
const state = { offset: 0 };
|
|
24
|
+
const value = decodeItem(input, state, 0, options.maxDepth ?? 32);
|
|
25
|
+
if (state.offset !== input.length) {
|
|
26
|
+
throw new CborDecodeError(`Trailing ${input.length - state.offset} byte(s) after CBOR item`);
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
function decodeItem(input, cursor, depth, maxDepth) {
|
|
31
|
+
if (depth > maxDepth)
|
|
32
|
+
throw new CborDecodeError(`CBOR nesting exceeds ${maxDepth}`);
|
|
33
|
+
const initial = readByte(input, cursor);
|
|
34
|
+
const major = initial >> 5;
|
|
35
|
+
const additional = initial & 0x1f;
|
|
36
|
+
switch (major) {
|
|
37
|
+
case 0: {
|
|
38
|
+
const argument = readArgument(input, cursor, additional);
|
|
39
|
+
return integer(argument);
|
|
40
|
+
}
|
|
41
|
+
case 1: {
|
|
42
|
+
const argument = readArgument(input, cursor, additional);
|
|
43
|
+
const value = integer(argument);
|
|
44
|
+
if (typeof value === 'bigint')
|
|
45
|
+
return -1n - value;
|
|
46
|
+
return -1 - value;
|
|
47
|
+
}
|
|
48
|
+
case 2:
|
|
49
|
+
return decodeByteString(input, cursor, additional);
|
|
50
|
+
case 3:
|
|
51
|
+
return decodeTextString(input, cursor, additional);
|
|
52
|
+
case 4:
|
|
53
|
+
return decodeArray(input, cursor, additional, depth, maxDepth);
|
|
54
|
+
case 5:
|
|
55
|
+
return decodeMap(input, cursor, additional, depth, maxDepth);
|
|
56
|
+
case 6: {
|
|
57
|
+
// Tags are structural metadata; sensor claims do not need them. Read and
|
|
58
|
+
// unwrap, but reject indefinite tag numbers.
|
|
59
|
+
const argument = readArgument(input, cursor, additional);
|
|
60
|
+
if (argument === 'indefinite')
|
|
61
|
+
throw new CborDecodeError('Indefinite CBOR tag');
|
|
62
|
+
return decodeItem(input, cursor, depth + 1, maxDepth);
|
|
63
|
+
}
|
|
64
|
+
default:
|
|
65
|
+
return decodeSimple(input, cursor, additional);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function integer(argument) {
|
|
69
|
+
if (argument === 'indefinite')
|
|
70
|
+
throw new CborDecodeError('Indefinite CBOR integer');
|
|
71
|
+
return argument <= MAX_SAFE ? Number(argument) : argument;
|
|
72
|
+
}
|
|
73
|
+
function decodeByteString(input, cursor, additional) {
|
|
74
|
+
const argument = readArgument(input, cursor, additional);
|
|
75
|
+
if (argument !== 'indefinite')
|
|
76
|
+
return readBytes(input, cursor, toLength(argument, 'byte string'));
|
|
77
|
+
const chunks = [];
|
|
78
|
+
let total = 0;
|
|
79
|
+
while (!atBreak(input, cursor)) {
|
|
80
|
+
const header = peekByte(input, cursor);
|
|
81
|
+
if (header >> 5 !== 2 || (header & 0x1f) === 0x1f) {
|
|
82
|
+
throw new CborDecodeError('Indefinite CBOR byte string contains a non-definite chunk');
|
|
83
|
+
}
|
|
84
|
+
cursor.offset++;
|
|
85
|
+
const length = toLength(readArgument(input, cursor, header & 0x1f), 'byte string chunk');
|
|
86
|
+
chunks.push(readBytes(input, cursor, length));
|
|
87
|
+
total += length;
|
|
88
|
+
}
|
|
89
|
+
cursor.offset++;
|
|
90
|
+
const out = new Uint8Array(total);
|
|
91
|
+
let offset = 0;
|
|
92
|
+
for (const chunk of chunks) {
|
|
93
|
+
out.set(chunk, offset);
|
|
94
|
+
offset += chunk.length;
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
function decodeTextString(input, cursor, additional) {
|
|
99
|
+
const argument = readArgument(input, cursor, additional);
|
|
100
|
+
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
101
|
+
if (argument !== 'indefinite') {
|
|
102
|
+
return decodeUtf8(decoder, readBytes(input, cursor, toLength(argument, 'text string')));
|
|
103
|
+
}
|
|
104
|
+
let text = '';
|
|
105
|
+
while (!atBreak(input, cursor)) {
|
|
106
|
+
const header = peekByte(input, cursor);
|
|
107
|
+
if (header >> 5 !== 3 || (header & 0x1f) === 0x1f) {
|
|
108
|
+
throw new CborDecodeError('Indefinite CBOR text string contains a non-definite chunk');
|
|
109
|
+
}
|
|
110
|
+
cursor.offset++;
|
|
111
|
+
const length = toLength(readArgument(input, cursor, header & 0x1f), 'text string chunk');
|
|
112
|
+
text += decodeUtf8(decoder, readBytes(input, cursor, length));
|
|
113
|
+
}
|
|
114
|
+
cursor.offset++;
|
|
115
|
+
return text;
|
|
116
|
+
}
|
|
117
|
+
function decodeArray(input, cursor, additional, depth, maxDepth) {
|
|
118
|
+
const argument = readArgument(input, cursor, additional);
|
|
119
|
+
const values = [];
|
|
120
|
+
if (argument === 'indefinite') {
|
|
121
|
+
while (!atBreak(input, cursor))
|
|
122
|
+
values.push(decodeItem(input, cursor, depth + 1, maxDepth));
|
|
123
|
+
cursor.offset++;
|
|
124
|
+
return values;
|
|
125
|
+
}
|
|
126
|
+
const length = toLength(argument, 'array');
|
|
127
|
+
for (let i = 0; i < length; i++)
|
|
128
|
+
values.push(decodeItem(input, cursor, depth + 1, maxDepth));
|
|
129
|
+
return values;
|
|
130
|
+
}
|
|
131
|
+
function decodeMap(input, cursor, additional, depth, maxDepth) {
|
|
132
|
+
const argument = readArgument(input, cursor, additional);
|
|
133
|
+
const entries = [];
|
|
134
|
+
const readPair = () => {
|
|
135
|
+
const key = decodeItem(input, cursor, depth + 1, maxDepth);
|
|
136
|
+
const value = decodeItem(input, cursor, depth + 1, maxDepth);
|
|
137
|
+
entries.push([key, value]);
|
|
138
|
+
};
|
|
139
|
+
if (argument === 'indefinite') {
|
|
140
|
+
while (!atBreak(input, cursor))
|
|
141
|
+
readPair();
|
|
142
|
+
cursor.offset++;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
const length = toLength(argument, 'map');
|
|
146
|
+
for (let i = 0; i < length; i++)
|
|
147
|
+
readPair();
|
|
148
|
+
}
|
|
149
|
+
if (entries.every(([key]) => typeof key === 'string')) {
|
|
150
|
+
const object = {};
|
|
151
|
+
const keys = new Set();
|
|
152
|
+
for (const [key, value] of entries) {
|
|
153
|
+
const name = key;
|
|
154
|
+
if (name === '__proto__')
|
|
155
|
+
throw new CborDecodeError('CBOR map contains a reserved key');
|
|
156
|
+
if (keys.has(name))
|
|
157
|
+
throw new CborDecodeError(`Duplicate CBOR map key '${name}'`);
|
|
158
|
+
keys.add(name);
|
|
159
|
+
object[name] = value;
|
|
160
|
+
}
|
|
161
|
+
return object;
|
|
162
|
+
}
|
|
163
|
+
const map = new Map();
|
|
164
|
+
for (const [key, value] of entries) {
|
|
165
|
+
if (map.has(key))
|
|
166
|
+
throw new CborDecodeError('Duplicate CBOR map key');
|
|
167
|
+
map.set(key, value);
|
|
168
|
+
}
|
|
169
|
+
return map;
|
|
170
|
+
}
|
|
171
|
+
function decodeSimple(input, cursor, additional) {
|
|
172
|
+
switch (additional) {
|
|
173
|
+
case 20: return false;
|
|
174
|
+
case 21: return true;
|
|
175
|
+
case 22: return null;
|
|
176
|
+
case 23: return undefined;
|
|
177
|
+
case 24:
|
|
178
|
+
// Simple values outside the well-known set are not used by this profile.
|
|
179
|
+
throw new CborDecodeError(`Unsupported CBOR simple value ${readByte(input, cursor)}`);
|
|
180
|
+
case 25: {
|
|
181
|
+
const bits = readUint(input, cursor, 2);
|
|
182
|
+
return decodeFloat16(Number(bits));
|
|
183
|
+
}
|
|
184
|
+
case 26: return readFloat(input, cursor, 4).getFloat32(0, false);
|
|
185
|
+
case 27: return readFloat(input, cursor, 8).getFloat64(0, false);
|
|
186
|
+
case 31: throw new CborDecodeError('Unexpected CBOR break');
|
|
187
|
+
default: throw new CborDecodeError(`Unsupported CBOR simple value ${additional}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function decodeFloat16(bits) {
|
|
191
|
+
const sign = bits & 0x8000 ? -1 : 1;
|
|
192
|
+
const exponent = (bits >> 10) & 0x1f;
|
|
193
|
+
const fraction = bits & 0x3ff;
|
|
194
|
+
if (exponent === 0)
|
|
195
|
+
return sign * 2 ** -14 * (fraction / 1024);
|
|
196
|
+
if (exponent === 31)
|
|
197
|
+
return fraction === 0 ? sign * Infinity : Number.NaN;
|
|
198
|
+
return sign * 2 ** (exponent - 15) * (1 + fraction / 1024);
|
|
199
|
+
}
|
|
200
|
+
function readArgument(input, cursor, additional) {
|
|
201
|
+
if (additional < 24)
|
|
202
|
+
return BigInt(additional);
|
|
203
|
+
if (additional === 24)
|
|
204
|
+
return readUint(input, cursor, 1);
|
|
205
|
+
if (additional === 25)
|
|
206
|
+
return readUint(input, cursor, 2);
|
|
207
|
+
if (additional === 26)
|
|
208
|
+
return readUint(input, cursor, 4);
|
|
209
|
+
if (additional === 27)
|
|
210
|
+
return readUint(input, cursor, 8);
|
|
211
|
+
if (additional === 31)
|
|
212
|
+
return 'indefinite';
|
|
213
|
+
throw new CborDecodeError(`Reserved CBOR additional information ${additional}`);
|
|
214
|
+
}
|
|
215
|
+
function toLength(argument, label) {
|
|
216
|
+
if (argument > BigInt(Number.MAX_SAFE_INTEGER))
|
|
217
|
+
throw new CborDecodeError(`CBOR ${label} length is too large`);
|
|
218
|
+
const length = Number(argument);
|
|
219
|
+
if (length > 0xffffffff)
|
|
220
|
+
throw new CborDecodeError(`CBOR ${label} length is too large`);
|
|
221
|
+
return length;
|
|
222
|
+
}
|
|
223
|
+
function readByte(input, cursor) {
|
|
224
|
+
if (cursor.offset >= input.length)
|
|
225
|
+
throw new CborDecodeError('Truncated CBOR input');
|
|
226
|
+
return input[cursor.offset++];
|
|
227
|
+
}
|
|
228
|
+
function peekByte(input, cursor) {
|
|
229
|
+
if (cursor.offset >= input.length)
|
|
230
|
+
throw new CborDecodeError('Truncated CBOR input');
|
|
231
|
+
return input[cursor.offset];
|
|
232
|
+
}
|
|
233
|
+
function readBytes(input, cursor, length) {
|
|
234
|
+
if (cursor.offset + length > input.length)
|
|
235
|
+
throw new CborDecodeError('Truncated CBOR string');
|
|
236
|
+
const value = input.slice(cursor.offset, cursor.offset + length);
|
|
237
|
+
cursor.offset += length;
|
|
238
|
+
return value;
|
|
239
|
+
}
|
|
240
|
+
function readUint(input, cursor, length) {
|
|
241
|
+
if (cursor.offset + length > input.length)
|
|
242
|
+
throw new CborDecodeError('Truncated CBOR argument');
|
|
243
|
+
let value = 0n;
|
|
244
|
+
for (let i = 0; i < length; i++)
|
|
245
|
+
value = (value << 8n) | BigInt(input[cursor.offset + i]);
|
|
246
|
+
cursor.offset += length;
|
|
247
|
+
return value;
|
|
248
|
+
}
|
|
249
|
+
function readFloat(input, cursor, length) {
|
|
250
|
+
if (cursor.offset + length > input.length)
|
|
251
|
+
throw new CborDecodeError('Truncated CBOR float');
|
|
252
|
+
const view = new DataView(input.buffer, input.byteOffset + cursor.offset, length);
|
|
253
|
+
cursor.offset += length;
|
|
254
|
+
return view;
|
|
255
|
+
}
|
|
256
|
+
function atBreak(input, cursor) {
|
|
257
|
+
return peekByte(input, cursor) === BREAK;
|
|
258
|
+
}
|
|
259
|
+
function decodeUtf8(decoder, bytes) {
|
|
260
|
+
try {
|
|
261
|
+
return decoder.decode(bytes);
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
throw new CborDecodeError('Invalid UTF-8 in CBOR text string');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal CBOR (RFC 8949) decoder for constrained-device payloads.
|
|
3
|
+
*
|
|
4
|
+
* Supports the deterministic core used by sensors: unsigned/negative
|
|
5
|
+
* integers, byte/text strings, arrays, maps, tags (unwrapped), booleans, null,
|
|
6
|
+
* undefined, half/single/double floats and indefinite-length containers.
|
|
7
|
+
* Maps with all-text keys decode to plain objects; other maps decode to `Map`.
|
|
8
|
+
*
|
|
9
|
+
* This is a decoder, not a general CBOR library: it does not expose tag
|
|
10
|
+
* semantics and rejects malformed, truncated, duplicate-key or over-deep input.
|
|
11
|
+
*/
|
|
12
|
+
export type CborValue = null | undefined | boolean | number | bigint | string | Uint8Array | CborValue[] | {
|
|
13
|
+
[key: string]: CborValue;
|
|
14
|
+
} | Map<CborValue, CborValue>;
|
|
15
|
+
export interface CborDecodeOptions {
|
|
16
|
+
/** Maximum nesting depth. Default 32. */
|
|
17
|
+
maxDepth?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare class CborDecodeError extends Error {
|
|
20
|
+
constructor(message: string);
|
|
21
|
+
}
|
|
22
|
+
export declare function decodeCbor(input: Uint8Array, options?: CborDecodeOptions): CborValue;
|
package/dist/cjs/cbor.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal CBOR (RFC 8949) decoder for constrained-device payloads.
|
|
4
|
+
*
|
|
5
|
+
* Supports the deterministic core used by sensors: unsigned/negative
|
|
6
|
+
* integers, byte/text strings, arrays, maps, tags (unwrapped), booleans, null,
|
|
7
|
+
* undefined, half/single/double floats and indefinite-length containers.
|
|
8
|
+
* Maps with all-text keys decode to plain objects; other maps decode to `Map`.
|
|
9
|
+
*
|
|
10
|
+
* This is a decoder, not a general CBOR library: it does not expose tag
|
|
11
|
+
* semantics and rejects malformed, truncated, duplicate-key or over-deep input.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.CborDecodeError = void 0;
|
|
15
|
+
exports.decodeCbor = decodeCbor;
|
|
16
|
+
class CborDecodeError extends Error {
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = 'CborDecodeError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.CborDecodeError = CborDecodeError;
|
|
23
|
+
const BREAK = 0xff;
|
|
24
|
+
const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
|
|
25
|
+
function decodeCbor(input, options = {}) {
|
|
26
|
+
if (!(input instanceof Uint8Array))
|
|
27
|
+
throw new CborDecodeError('CBOR input must be a Uint8Array');
|
|
28
|
+
const state = { offset: 0 };
|
|
29
|
+
const value = decodeItem(input, state, 0, options.maxDepth ?? 32);
|
|
30
|
+
if (state.offset !== input.length) {
|
|
31
|
+
throw new CborDecodeError(`Trailing ${input.length - state.offset} byte(s) after CBOR item`);
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
function decodeItem(input, cursor, depth, maxDepth) {
|
|
36
|
+
if (depth > maxDepth)
|
|
37
|
+
throw new CborDecodeError(`CBOR nesting exceeds ${maxDepth}`);
|
|
38
|
+
const initial = readByte(input, cursor);
|
|
39
|
+
const major = initial >> 5;
|
|
40
|
+
const additional = initial & 0x1f;
|
|
41
|
+
switch (major) {
|
|
42
|
+
case 0: {
|
|
43
|
+
const argument = readArgument(input, cursor, additional);
|
|
44
|
+
return integer(argument);
|
|
45
|
+
}
|
|
46
|
+
case 1: {
|
|
47
|
+
const argument = readArgument(input, cursor, additional);
|
|
48
|
+
const value = integer(argument);
|
|
49
|
+
if (typeof value === 'bigint')
|
|
50
|
+
return -1n - value;
|
|
51
|
+
return -1 - value;
|
|
52
|
+
}
|
|
53
|
+
case 2:
|
|
54
|
+
return decodeByteString(input, cursor, additional);
|
|
55
|
+
case 3:
|
|
56
|
+
return decodeTextString(input, cursor, additional);
|
|
57
|
+
case 4:
|
|
58
|
+
return decodeArray(input, cursor, additional, depth, maxDepth);
|
|
59
|
+
case 5:
|
|
60
|
+
return decodeMap(input, cursor, additional, depth, maxDepth);
|
|
61
|
+
case 6: {
|
|
62
|
+
// Tags are structural metadata; sensor claims do not need them. Read and
|
|
63
|
+
// unwrap, but reject indefinite tag numbers.
|
|
64
|
+
const argument = readArgument(input, cursor, additional);
|
|
65
|
+
if (argument === 'indefinite')
|
|
66
|
+
throw new CborDecodeError('Indefinite CBOR tag');
|
|
67
|
+
return decodeItem(input, cursor, depth + 1, maxDepth);
|
|
68
|
+
}
|
|
69
|
+
default:
|
|
70
|
+
return decodeSimple(input, cursor, additional);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function integer(argument) {
|
|
74
|
+
if (argument === 'indefinite')
|
|
75
|
+
throw new CborDecodeError('Indefinite CBOR integer');
|
|
76
|
+
return argument <= MAX_SAFE ? Number(argument) : argument;
|
|
77
|
+
}
|
|
78
|
+
function decodeByteString(input, cursor, additional) {
|
|
79
|
+
const argument = readArgument(input, cursor, additional);
|
|
80
|
+
if (argument !== 'indefinite')
|
|
81
|
+
return readBytes(input, cursor, toLength(argument, 'byte string'));
|
|
82
|
+
const chunks = [];
|
|
83
|
+
let total = 0;
|
|
84
|
+
while (!atBreak(input, cursor)) {
|
|
85
|
+
const header = peekByte(input, cursor);
|
|
86
|
+
if (header >> 5 !== 2 || (header & 0x1f) === 0x1f) {
|
|
87
|
+
throw new CborDecodeError('Indefinite CBOR byte string contains a non-definite chunk');
|
|
88
|
+
}
|
|
89
|
+
cursor.offset++;
|
|
90
|
+
const length = toLength(readArgument(input, cursor, header & 0x1f), 'byte string chunk');
|
|
91
|
+
chunks.push(readBytes(input, cursor, length));
|
|
92
|
+
total += length;
|
|
93
|
+
}
|
|
94
|
+
cursor.offset++;
|
|
95
|
+
const out = new Uint8Array(total);
|
|
96
|
+
let offset = 0;
|
|
97
|
+
for (const chunk of chunks) {
|
|
98
|
+
out.set(chunk, offset);
|
|
99
|
+
offset += chunk.length;
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
function decodeTextString(input, cursor, additional) {
|
|
104
|
+
const argument = readArgument(input, cursor, additional);
|
|
105
|
+
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
106
|
+
if (argument !== 'indefinite') {
|
|
107
|
+
return decodeUtf8(decoder, readBytes(input, cursor, toLength(argument, 'text string')));
|
|
108
|
+
}
|
|
109
|
+
let text = '';
|
|
110
|
+
while (!atBreak(input, cursor)) {
|
|
111
|
+
const header = peekByte(input, cursor);
|
|
112
|
+
if (header >> 5 !== 3 || (header & 0x1f) === 0x1f) {
|
|
113
|
+
throw new CborDecodeError('Indefinite CBOR text string contains a non-definite chunk');
|
|
114
|
+
}
|
|
115
|
+
cursor.offset++;
|
|
116
|
+
const length = toLength(readArgument(input, cursor, header & 0x1f), 'text string chunk');
|
|
117
|
+
text += decodeUtf8(decoder, readBytes(input, cursor, length));
|
|
118
|
+
}
|
|
119
|
+
cursor.offset++;
|
|
120
|
+
return text;
|
|
121
|
+
}
|
|
122
|
+
function decodeArray(input, cursor, additional, depth, maxDepth) {
|
|
123
|
+
const argument = readArgument(input, cursor, additional);
|
|
124
|
+
const values = [];
|
|
125
|
+
if (argument === 'indefinite') {
|
|
126
|
+
while (!atBreak(input, cursor))
|
|
127
|
+
values.push(decodeItem(input, cursor, depth + 1, maxDepth));
|
|
128
|
+
cursor.offset++;
|
|
129
|
+
return values;
|
|
130
|
+
}
|
|
131
|
+
const length = toLength(argument, 'array');
|
|
132
|
+
for (let i = 0; i < length; i++)
|
|
133
|
+
values.push(decodeItem(input, cursor, depth + 1, maxDepth));
|
|
134
|
+
return values;
|
|
135
|
+
}
|
|
136
|
+
function decodeMap(input, cursor, additional, depth, maxDepth) {
|
|
137
|
+
const argument = readArgument(input, cursor, additional);
|
|
138
|
+
const entries = [];
|
|
139
|
+
const readPair = () => {
|
|
140
|
+
const key = decodeItem(input, cursor, depth + 1, maxDepth);
|
|
141
|
+
const value = decodeItem(input, cursor, depth + 1, maxDepth);
|
|
142
|
+
entries.push([key, value]);
|
|
143
|
+
};
|
|
144
|
+
if (argument === 'indefinite') {
|
|
145
|
+
while (!atBreak(input, cursor))
|
|
146
|
+
readPair();
|
|
147
|
+
cursor.offset++;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
const length = toLength(argument, 'map');
|
|
151
|
+
for (let i = 0; i < length; i++)
|
|
152
|
+
readPair();
|
|
153
|
+
}
|
|
154
|
+
if (entries.every(([key]) => typeof key === 'string')) {
|
|
155
|
+
const object = {};
|
|
156
|
+
const keys = new Set();
|
|
157
|
+
for (const [key, value] of entries) {
|
|
158
|
+
const name = key;
|
|
159
|
+
if (name === '__proto__')
|
|
160
|
+
throw new CborDecodeError('CBOR map contains a reserved key');
|
|
161
|
+
if (keys.has(name))
|
|
162
|
+
throw new CborDecodeError(`Duplicate CBOR map key '${name}'`);
|
|
163
|
+
keys.add(name);
|
|
164
|
+
object[name] = value;
|
|
165
|
+
}
|
|
166
|
+
return object;
|
|
167
|
+
}
|
|
168
|
+
const map = new Map();
|
|
169
|
+
for (const [key, value] of entries) {
|
|
170
|
+
if (map.has(key))
|
|
171
|
+
throw new CborDecodeError('Duplicate CBOR map key');
|
|
172
|
+
map.set(key, value);
|
|
173
|
+
}
|
|
174
|
+
return map;
|
|
175
|
+
}
|
|
176
|
+
function decodeSimple(input, cursor, additional) {
|
|
177
|
+
switch (additional) {
|
|
178
|
+
case 20: return false;
|
|
179
|
+
case 21: return true;
|
|
180
|
+
case 22: return null;
|
|
181
|
+
case 23: return undefined;
|
|
182
|
+
case 24:
|
|
183
|
+
// Simple values outside the well-known set are not used by this profile.
|
|
184
|
+
throw new CborDecodeError(`Unsupported CBOR simple value ${readByte(input, cursor)}`);
|
|
185
|
+
case 25: {
|
|
186
|
+
const bits = readUint(input, cursor, 2);
|
|
187
|
+
return decodeFloat16(Number(bits));
|
|
188
|
+
}
|
|
189
|
+
case 26: return readFloat(input, cursor, 4).getFloat32(0, false);
|
|
190
|
+
case 27: return readFloat(input, cursor, 8).getFloat64(0, false);
|
|
191
|
+
case 31: throw new CborDecodeError('Unexpected CBOR break');
|
|
192
|
+
default: throw new CborDecodeError(`Unsupported CBOR simple value ${additional}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function decodeFloat16(bits) {
|
|
196
|
+
const sign = bits & 0x8000 ? -1 : 1;
|
|
197
|
+
const exponent = (bits >> 10) & 0x1f;
|
|
198
|
+
const fraction = bits & 0x3ff;
|
|
199
|
+
if (exponent === 0)
|
|
200
|
+
return sign * 2 ** -14 * (fraction / 1024);
|
|
201
|
+
if (exponent === 31)
|
|
202
|
+
return fraction === 0 ? sign * Infinity : Number.NaN;
|
|
203
|
+
return sign * 2 ** (exponent - 15) * (1 + fraction / 1024);
|
|
204
|
+
}
|
|
205
|
+
function readArgument(input, cursor, additional) {
|
|
206
|
+
if (additional < 24)
|
|
207
|
+
return BigInt(additional);
|
|
208
|
+
if (additional === 24)
|
|
209
|
+
return readUint(input, cursor, 1);
|
|
210
|
+
if (additional === 25)
|
|
211
|
+
return readUint(input, cursor, 2);
|
|
212
|
+
if (additional === 26)
|
|
213
|
+
return readUint(input, cursor, 4);
|
|
214
|
+
if (additional === 27)
|
|
215
|
+
return readUint(input, cursor, 8);
|
|
216
|
+
if (additional === 31)
|
|
217
|
+
return 'indefinite';
|
|
218
|
+
throw new CborDecodeError(`Reserved CBOR additional information ${additional}`);
|
|
219
|
+
}
|
|
220
|
+
function toLength(argument, label) {
|
|
221
|
+
if (argument > BigInt(Number.MAX_SAFE_INTEGER))
|
|
222
|
+
throw new CborDecodeError(`CBOR ${label} length is too large`);
|
|
223
|
+
const length = Number(argument);
|
|
224
|
+
if (length > 0xffffffff)
|
|
225
|
+
throw new CborDecodeError(`CBOR ${label} length is too large`);
|
|
226
|
+
return length;
|
|
227
|
+
}
|
|
228
|
+
function readByte(input, cursor) {
|
|
229
|
+
if (cursor.offset >= input.length)
|
|
230
|
+
throw new CborDecodeError('Truncated CBOR input');
|
|
231
|
+
return input[cursor.offset++];
|
|
232
|
+
}
|
|
233
|
+
function peekByte(input, cursor) {
|
|
234
|
+
if (cursor.offset >= input.length)
|
|
235
|
+
throw new CborDecodeError('Truncated CBOR input');
|
|
236
|
+
return input[cursor.offset];
|
|
237
|
+
}
|
|
238
|
+
function readBytes(input, cursor, length) {
|
|
239
|
+
if (cursor.offset + length > input.length)
|
|
240
|
+
throw new CborDecodeError('Truncated CBOR string');
|
|
241
|
+
const value = input.slice(cursor.offset, cursor.offset + length);
|
|
242
|
+
cursor.offset += length;
|
|
243
|
+
return value;
|
|
244
|
+
}
|
|
245
|
+
function readUint(input, cursor, length) {
|
|
246
|
+
if (cursor.offset + length > input.length)
|
|
247
|
+
throw new CborDecodeError('Truncated CBOR argument');
|
|
248
|
+
let value = 0n;
|
|
249
|
+
for (let i = 0; i < length; i++)
|
|
250
|
+
value = (value << 8n) | BigInt(input[cursor.offset + i]);
|
|
251
|
+
cursor.offset += length;
|
|
252
|
+
return value;
|
|
253
|
+
}
|
|
254
|
+
function readFloat(input, cursor, length) {
|
|
255
|
+
if (cursor.offset + length > input.length)
|
|
256
|
+
throw new CborDecodeError('Truncated CBOR float');
|
|
257
|
+
const view = new DataView(input.buffer, input.byteOffset + cursor.offset, length);
|
|
258
|
+
cursor.offset += length;
|
|
259
|
+
return view;
|
|
260
|
+
}
|
|
261
|
+
function atBreak(input, cursor) {
|
|
262
|
+
return peekByte(input, cursor) === BREAK;
|
|
263
|
+
}
|
|
264
|
+
function decodeUtf8(decoder, bytes) {
|
|
265
|
+
try {
|
|
266
|
+
return decoder.decode(bytes);
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
throw new CborDecodeError('Invalid UTF-8 in CBOR text string');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CoAP wire codec (RFC 7252 §3).
|
|
3
|
+
*
|
|
4
|
+
* Kept independent of the transport so the encoding can be exercised against
|
|
5
|
+
* RFC vectors without a socket. Only Uri-Path (option 11) is emitted; Uri-Host
|
|
6
|
+
* and Uri-Port default to the destination address, so they are not sent.
|
|
7
|
+
*/
|
|
8
|
+
import type { CoapMessage } from './transport.js';
|
|
9
|
+
export declare const URI_HOST_OPTION = 3;
|
|
10
|
+
export declare const OBSERVE_OPTION = 6;
|
|
11
|
+
export declare const URI_PORT_OPTION = 7;
|
|
12
|
+
export declare const OSCORE_OPTION = 9;
|
|
13
|
+
export declare const BLOCK2_OPTION = 23;
|
|
14
|
+
export declare const BLOCK1_OPTION = 27;
|
|
15
|
+
export declare const SIZE2_OPTION = 28;
|
|
16
|
+
export declare const SIZE1_OPTION = 60;
|
|
17
|
+
export declare const URI_PATH_OPTION = 11;
|
|
18
|
+
export declare const CONTENT_FORMAT_OPTION = 12;
|
|
19
|
+
export declare const ACCEPT_OPTION = 17;
|
|
20
|
+
export declare const URI_QUERY_OPTION = 15;
|
|
21
|
+
export declare function tokenToKey(token: Uint8Array): string;
|
|
22
|
+
export declare function encodeCoap(msg: CoapMessage): Uint8Array;
|
|
23
|
+
export declare function decodeCoap(raw: Uint8Array, remote: {
|
|
24
|
+
host: string;
|
|
25
|
+
port: number;
|
|
26
|
+
}): CoapMessage;
|
|
27
|
+
export declare function encodeBlock(block: import('./transport.js').CoapBlockOption): number;
|
|
28
|
+
export declare function decodeBlock(value: Uint8Array): import('./transport.js').CoapBlockOption;
|
|
29
|
+
export declare function formatResponseCode(code: number): string;
|
|
30
|
+
export declare function parseResponseCode(code: string): number;
|