@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
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CoAP wire codec (RFC 7252 §3).
|
|
4
|
+
*
|
|
5
|
+
* Kept independent of the transport so the encoding can be exercised against
|
|
6
|
+
* RFC vectors without a socket. Only Uri-Path (option 11) is emitted; Uri-Host
|
|
7
|
+
* and Uri-Port default to the destination address, so they are not sent.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.URI_QUERY_OPTION = exports.ACCEPT_OPTION = exports.CONTENT_FORMAT_OPTION = exports.URI_PATH_OPTION = exports.SIZE1_OPTION = exports.SIZE2_OPTION = exports.BLOCK1_OPTION = exports.BLOCK2_OPTION = exports.OSCORE_OPTION = exports.URI_PORT_OPTION = exports.OBSERVE_OPTION = exports.URI_HOST_OPTION = void 0;
|
|
11
|
+
exports.tokenToKey = tokenToKey;
|
|
12
|
+
exports.encodeCoap = encodeCoap;
|
|
13
|
+
exports.decodeCoap = decodeCoap;
|
|
14
|
+
exports.encodeBlock = encodeBlock;
|
|
15
|
+
exports.decodeBlock = decodeBlock;
|
|
16
|
+
exports.formatResponseCode = formatResponseCode;
|
|
17
|
+
exports.parseResponseCode = parseResponseCode;
|
|
18
|
+
exports.URI_HOST_OPTION = 3;
|
|
19
|
+
exports.OBSERVE_OPTION = 6;
|
|
20
|
+
exports.URI_PORT_OPTION = 7;
|
|
21
|
+
exports.OSCORE_OPTION = 9;
|
|
22
|
+
exports.BLOCK2_OPTION = 23;
|
|
23
|
+
exports.BLOCK1_OPTION = 27;
|
|
24
|
+
exports.SIZE2_OPTION = 28;
|
|
25
|
+
exports.SIZE1_OPTION = 60;
|
|
26
|
+
exports.URI_PATH_OPTION = 11;
|
|
27
|
+
exports.CONTENT_FORMAT_OPTION = 12;
|
|
28
|
+
exports.ACCEPT_OPTION = 17;
|
|
29
|
+
exports.URI_QUERY_OPTION = 15;
|
|
30
|
+
const PAYLOAD_MARKER = 0xff;
|
|
31
|
+
const MAX_TOKEN_LENGTH = 8;
|
|
32
|
+
const MAX_OPTION_VALUE = 65804; // 269 + 0xffff
|
|
33
|
+
const METHOD_CODES = { GET: 1, POST: 2, PUT: 3, DELETE: 4, FETCH: 5 };
|
|
34
|
+
const CODE_METHODS = { 1: 'GET', 2: 'POST', 3: 'PUT', 4: 'DELETE', 5: 'FETCH' };
|
|
35
|
+
function tokenToKey(token) {
|
|
36
|
+
return Array.from(token).map((byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
37
|
+
}
|
|
38
|
+
function encodeCoap(msg) {
|
|
39
|
+
const tokenLen = msg.token.length;
|
|
40
|
+
if (tokenLen > MAX_TOKEN_LENGTH)
|
|
41
|
+
throw new Error('CoAP token exceeds 8 bytes');
|
|
42
|
+
if (msg.messageId < 0 || msg.messageId > 0xffff)
|
|
43
|
+
throw new Error('CoAP message id out of range');
|
|
44
|
+
const entries = [];
|
|
45
|
+
if (msg.uriHost !== undefined)
|
|
46
|
+
entries.push({ number: exports.URI_HOST_OPTION, value: new TextEncoder().encode(msg.uriHost) });
|
|
47
|
+
if (msg.observe !== undefined)
|
|
48
|
+
entries.push({ number: exports.OBSERVE_OPTION, value: uintToBytes(msg.observe) });
|
|
49
|
+
if (msg.uriPort !== undefined)
|
|
50
|
+
entries.push({ number: exports.URI_PORT_OPTION, value: uintToBytes(msg.uriPort) });
|
|
51
|
+
if (msg.oscore)
|
|
52
|
+
entries.push({ number: exports.OSCORE_OPTION, value: msg.oscore });
|
|
53
|
+
for (const segment of msg.path) {
|
|
54
|
+
if (segment.length === 0)
|
|
55
|
+
throw new Error('CoAP Uri-Path segment cannot be empty');
|
|
56
|
+
entries.push({ number: exports.URI_PATH_OPTION, value: new TextEncoder().encode(segment) });
|
|
57
|
+
}
|
|
58
|
+
for (const query of msg.uriQuery ?? []) {
|
|
59
|
+
entries.push({ number: exports.URI_QUERY_OPTION, value: new TextEncoder().encode(query) });
|
|
60
|
+
}
|
|
61
|
+
if (msg.contentFormat !== undefined)
|
|
62
|
+
entries.push({ number: exports.CONTENT_FORMAT_OPTION, value: uintToBytes(msg.contentFormat) });
|
|
63
|
+
if (msg.accept !== undefined)
|
|
64
|
+
entries.push({ number: exports.ACCEPT_OPTION, value: uintToBytes(msg.accept) });
|
|
65
|
+
if (msg.block2)
|
|
66
|
+
entries.push({ number: exports.BLOCK2_OPTION, value: uintToBytes(encodeBlock(msg.block2)) });
|
|
67
|
+
if (msg.block1)
|
|
68
|
+
entries.push({ number: exports.BLOCK1_OPTION, value: uintToBytes(encodeBlock(msg.block1)) });
|
|
69
|
+
if (msg.size2 !== undefined)
|
|
70
|
+
entries.push({ number: exports.SIZE2_OPTION, value: uintToBytes(msg.size2) });
|
|
71
|
+
if (msg.size1 !== undefined)
|
|
72
|
+
entries.push({ number: exports.SIZE1_OPTION, value: uintToBytes(msg.size1) });
|
|
73
|
+
// Stable sort keeps repeated Uri-Path segments in path order.
|
|
74
|
+
entries.sort((a, b) => a.number - b.number);
|
|
75
|
+
const options = [];
|
|
76
|
+
let previousOption = 0;
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
const delta = optionDeltaNibble(entry.number - previousOption);
|
|
79
|
+
const length = optionLengthNibble(entry.value.length);
|
|
80
|
+
options.push((delta.nibble << 4) | length.nibble, ...delta.extended, ...length.extended, ...entry.value);
|
|
81
|
+
previousOption = entry.number;
|
|
82
|
+
}
|
|
83
|
+
let code = 0;
|
|
84
|
+
if (msg.type === 'CON' || msg.type === 'NON') {
|
|
85
|
+
if (msg.method) {
|
|
86
|
+
if (METHOD_CODES[msg.method] === undefined)
|
|
87
|
+
throw new Error('CoAP request requires a method');
|
|
88
|
+
code = METHOD_CODES[msg.method];
|
|
89
|
+
}
|
|
90
|
+
else if (msg.responseCode !== undefined) {
|
|
91
|
+
code = parseResponseCode(msg.responseCode);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
throw new Error('CoAP CON/NON message requires a method or response code');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (msg.responseCode !== undefined) {
|
|
98
|
+
code = parseResponseCode(msg.responseCode);
|
|
99
|
+
}
|
|
100
|
+
const marker = msg.payload.length > 0 ? [PAYLOAD_MARKER] : [];
|
|
101
|
+
const out = new Uint8Array(4 + tokenLen + options.length + marker.length + msg.payload.length);
|
|
102
|
+
out[0] = 0x40 | ((msgTypeCode(msg.type) & 0x03) << 4) | tokenLen;
|
|
103
|
+
out[1] = code;
|
|
104
|
+
out[2] = (msg.messageId >> 8) & 0xff;
|
|
105
|
+
out[3] = msg.messageId & 0xff;
|
|
106
|
+
out.set(msg.token, 4);
|
|
107
|
+
out.set(options, 4 + tokenLen);
|
|
108
|
+
const payloadOffset = 4 + tokenLen + options.length;
|
|
109
|
+
if (marker.length)
|
|
110
|
+
out[payloadOffset] = PAYLOAD_MARKER;
|
|
111
|
+
out.set(msg.payload, payloadOffset + marker.length);
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
function decodeCoap(raw, remote) {
|
|
115
|
+
if (raw.length < 4)
|
|
116
|
+
throw new Error('Truncated CoAP header');
|
|
117
|
+
const version = (raw[0] >> 6) & 0x03;
|
|
118
|
+
if (version !== 1)
|
|
119
|
+
throw new Error(`Unsupported CoAP version ${version}`);
|
|
120
|
+
const tokenLen = raw[0] & 0x0f;
|
|
121
|
+
if (tokenLen > MAX_TOKEN_LENGTH)
|
|
122
|
+
throw new Error('Invalid CoAP token length');
|
|
123
|
+
if (raw.length < 4 + tokenLen)
|
|
124
|
+
throw new Error('Truncated CoAP token');
|
|
125
|
+
const type = typeFromCode((raw[0] >> 4) & 0x03);
|
|
126
|
+
const code = raw[1];
|
|
127
|
+
const messageId = ((raw[2] << 8) | raw[3]) & 0xffff;
|
|
128
|
+
const token = raw.slice(4, 4 + tokenLen);
|
|
129
|
+
const path = [];
|
|
130
|
+
const uriQuery = [];
|
|
131
|
+
let uriHost;
|
|
132
|
+
let uriPort;
|
|
133
|
+
let optionNumber = 0;
|
|
134
|
+
let offset = 4 + tokenLen;
|
|
135
|
+
let payload = new Uint8Array(0);
|
|
136
|
+
let contentFormat;
|
|
137
|
+
let accept;
|
|
138
|
+
let observe;
|
|
139
|
+
let oscore;
|
|
140
|
+
let block1;
|
|
141
|
+
let block2;
|
|
142
|
+
let size1;
|
|
143
|
+
let size2;
|
|
144
|
+
const seen = new Set();
|
|
145
|
+
while (offset < raw.length) {
|
|
146
|
+
if (raw[offset] === PAYLOAD_MARKER) {
|
|
147
|
+
offset++;
|
|
148
|
+
if (offset === raw.length)
|
|
149
|
+
throw new Error('CoAP payload marker without payload');
|
|
150
|
+
payload = raw.slice(offset);
|
|
151
|
+
offset = raw.length;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
const delta = (raw[offset] >> 4) & 0x0f;
|
|
155
|
+
const length = raw[offset] & 0x0f;
|
|
156
|
+
offset++;
|
|
157
|
+
const deltaValue = readExtendedOptionValue(delta, raw, offset);
|
|
158
|
+
offset = deltaValue.offset;
|
|
159
|
+
const lengthValue = readExtendedOptionValue(length, raw, offset);
|
|
160
|
+
offset = lengthValue.offset;
|
|
161
|
+
optionNumber += deltaValue.value;
|
|
162
|
+
if (optionNumber > 0xffff)
|
|
163
|
+
throw new Error('CoAP option number out of range');
|
|
164
|
+
if (offset + lengthValue.value > raw.length)
|
|
165
|
+
throw new Error('Truncated CoAP option value');
|
|
166
|
+
const value = raw.slice(offset, offset + lengthValue.value);
|
|
167
|
+
if (optionNumber !== exports.URI_PATH_OPTION && optionNumber !== exports.URI_QUERY_OPTION && seen.has(optionNumber)) {
|
|
168
|
+
throw new Error(`Duplicate CoAP option ${optionNumber}`);
|
|
169
|
+
}
|
|
170
|
+
if (optionNumber === exports.URI_HOST_OPTION) {
|
|
171
|
+
uriHost = new TextDecoder().decode(value);
|
|
172
|
+
}
|
|
173
|
+
else if (optionNumber === exports.URI_PORT_OPTION) {
|
|
174
|
+
uriPort = uintFromBytes(value);
|
|
175
|
+
}
|
|
176
|
+
else if (optionNumber === exports.URI_PATH_OPTION) {
|
|
177
|
+
const segment = new TextDecoder().decode(value);
|
|
178
|
+
if (segment.length === 0)
|
|
179
|
+
throw new Error('Empty CoAP Uri-Path segment');
|
|
180
|
+
path.push(segment);
|
|
181
|
+
}
|
|
182
|
+
else if (optionNumber === exports.URI_QUERY_OPTION) {
|
|
183
|
+
uriQuery.push(new TextDecoder().decode(value));
|
|
184
|
+
}
|
|
185
|
+
else if (optionNumber === exports.CONTENT_FORMAT_OPTION) {
|
|
186
|
+
contentFormat = uintFromBytes(value);
|
|
187
|
+
}
|
|
188
|
+
else if (optionNumber === exports.ACCEPT_OPTION) {
|
|
189
|
+
accept = uintFromBytes(value);
|
|
190
|
+
}
|
|
191
|
+
else if (optionNumber === exports.OBSERVE_OPTION) {
|
|
192
|
+
observe = uintFromBytes(value);
|
|
193
|
+
}
|
|
194
|
+
else if (optionNumber === exports.OSCORE_OPTION) {
|
|
195
|
+
oscore = value.slice();
|
|
196
|
+
}
|
|
197
|
+
else if (optionNumber === exports.BLOCK1_OPTION) {
|
|
198
|
+
block1 = decodeBlock(value);
|
|
199
|
+
}
|
|
200
|
+
else if (optionNumber === exports.BLOCK2_OPTION) {
|
|
201
|
+
block2 = decodeBlock(value);
|
|
202
|
+
}
|
|
203
|
+
else if (optionNumber === exports.SIZE1_OPTION) {
|
|
204
|
+
size1 = uintFromBytes(value);
|
|
205
|
+
}
|
|
206
|
+
else if (optionNumber === exports.SIZE2_OPTION) {
|
|
207
|
+
size2 = uintFromBytes(value);
|
|
208
|
+
}
|
|
209
|
+
seen.add(optionNumber);
|
|
210
|
+
offset += lengthValue.value;
|
|
211
|
+
}
|
|
212
|
+
const message = {
|
|
213
|
+
type,
|
|
214
|
+
messageId,
|
|
215
|
+
token,
|
|
216
|
+
path,
|
|
217
|
+
payload,
|
|
218
|
+
remote,
|
|
219
|
+
receivedAt: Date.now(),
|
|
220
|
+
};
|
|
221
|
+
if (uriHost !== undefined)
|
|
222
|
+
message.uriHost = uriHost;
|
|
223
|
+
if (uriPort !== undefined)
|
|
224
|
+
message.uriPort = uriPort;
|
|
225
|
+
if (uriQuery.length > 0)
|
|
226
|
+
message.uriQuery = uriQuery;
|
|
227
|
+
if (contentFormat !== undefined)
|
|
228
|
+
message.contentFormat = contentFormat;
|
|
229
|
+
if (accept !== undefined)
|
|
230
|
+
message.accept = accept;
|
|
231
|
+
if (observe !== undefined)
|
|
232
|
+
message.observe = observe;
|
|
233
|
+
if (oscore !== undefined)
|
|
234
|
+
message.oscore = oscore;
|
|
235
|
+
if (block1 !== undefined)
|
|
236
|
+
message.block1 = block1;
|
|
237
|
+
if (block2 !== undefined)
|
|
238
|
+
message.block2 = block2;
|
|
239
|
+
if (size1 !== undefined)
|
|
240
|
+
message.size1 = size1;
|
|
241
|
+
if (size2 !== undefined)
|
|
242
|
+
message.size2 = size2;
|
|
243
|
+
const codeClass = code >> 5;
|
|
244
|
+
if (codeClass === 0) {
|
|
245
|
+
const method = CODE_METHODS[code];
|
|
246
|
+
if (method)
|
|
247
|
+
message.method = method;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
message.responseCode = formatResponseCode(code);
|
|
251
|
+
}
|
|
252
|
+
return message;
|
|
253
|
+
}
|
|
254
|
+
function msgTypeCode(type) {
|
|
255
|
+
switch (type) {
|
|
256
|
+
case 'CON': return 0;
|
|
257
|
+
case 'NON': return 1;
|
|
258
|
+
case 'ACK': return 2;
|
|
259
|
+
case 'RST': return 3;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function typeFromCode(code) {
|
|
263
|
+
switch (code & 0x03) {
|
|
264
|
+
case 0: return 'CON';
|
|
265
|
+
case 1: return 'NON';
|
|
266
|
+
case 2: return 'ACK';
|
|
267
|
+
default: return 'RST';
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function optionDeltaNibble(value) {
|
|
271
|
+
if (value <= 12)
|
|
272
|
+
return { nibble: value, extended: [] };
|
|
273
|
+
if (value <= 268)
|
|
274
|
+
return { nibble: 13, extended: [value - 13] };
|
|
275
|
+
return { nibble: 14, extended: [(value - 269) >> 8, (value - 269) & 0xff] };
|
|
276
|
+
}
|
|
277
|
+
function optionLengthNibble(value) {
|
|
278
|
+
if (value > MAX_OPTION_VALUE)
|
|
279
|
+
throw new Error('CoAP option value exceeds 65804 bytes');
|
|
280
|
+
if (value <= 12)
|
|
281
|
+
return { nibble: value, extended: [] };
|
|
282
|
+
if (value <= 268)
|
|
283
|
+
return { nibble: 13, extended: [value - 13] };
|
|
284
|
+
return { nibble: 14, extended: [(value - 269) >> 8, (value - 269) & 0xff] };
|
|
285
|
+
}
|
|
286
|
+
function readExtendedOptionValue(nibble, raw, offset) {
|
|
287
|
+
if (nibble <= 12)
|
|
288
|
+
return { value: nibble, offset };
|
|
289
|
+
if (nibble === 13) {
|
|
290
|
+
if (offset >= raw.length)
|
|
291
|
+
throw new Error('Truncated CoAP option delta/length');
|
|
292
|
+
return { value: raw[offset] + 13, offset: offset + 1 };
|
|
293
|
+
}
|
|
294
|
+
if (nibble === 14) {
|
|
295
|
+
if (offset + 1 >= raw.length)
|
|
296
|
+
throw new Error('Truncated CoAP option delta/length');
|
|
297
|
+
return { value: ((raw[offset] << 8) | raw[offset + 1]) + 269, offset: offset + 2 };
|
|
298
|
+
}
|
|
299
|
+
throw new Error('Invalid CoAP option delta/length nibble');
|
|
300
|
+
}
|
|
301
|
+
function encodeBlock(block) {
|
|
302
|
+
if (!Number.isInteger(block.num) || block.num < 0 || block.num > 0xfffff)
|
|
303
|
+
throw new Error(`Invalid CoAP block number ${block.num}`);
|
|
304
|
+
if (!Number.isInteger(block.sizeExponent) || block.sizeExponent < 0 || block.sizeExponent > 6) {
|
|
305
|
+
throw new Error(`Invalid CoAP block size exponent ${block.sizeExponent}`);
|
|
306
|
+
}
|
|
307
|
+
return ((block.num & 0xfffff) << 4) | (block.more ? 0x08 : 0) | block.sizeExponent;
|
|
308
|
+
}
|
|
309
|
+
function decodeBlock(value) {
|
|
310
|
+
const encoded = uintFromBytes(value);
|
|
311
|
+
const sizeExponent = encoded & 0x07;
|
|
312
|
+
if (sizeExponent === 7)
|
|
313
|
+
throw new Error('Reserved CoAP block size exponent');
|
|
314
|
+
return { num: encoded >>> 4, more: (encoded & 0x08) !== 0, sizeExponent };
|
|
315
|
+
}
|
|
316
|
+
function uintToBytes(value) {
|
|
317
|
+
if (!Number.isInteger(value) || value < 0 || value > 0xffffffff) {
|
|
318
|
+
throw new Error(`Invalid CoAP uint option value ${value}`);
|
|
319
|
+
}
|
|
320
|
+
if (value === 0)
|
|
321
|
+
return new Uint8Array(0);
|
|
322
|
+
const bytes = [];
|
|
323
|
+
let remaining = value;
|
|
324
|
+
while (remaining > 0) {
|
|
325
|
+
bytes.unshift(remaining & 0xff);
|
|
326
|
+
remaining = Math.floor(remaining / 256);
|
|
327
|
+
}
|
|
328
|
+
return Uint8Array.from(bytes);
|
|
329
|
+
}
|
|
330
|
+
function uintFromBytes(value) {
|
|
331
|
+
if (value.length > 4)
|
|
332
|
+
throw new Error('CoAP uint option exceeds 4 bytes');
|
|
333
|
+
let result = 0;
|
|
334
|
+
for (const byte of value)
|
|
335
|
+
result = result * 256 + byte;
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
function formatResponseCode(code) {
|
|
339
|
+
return `${(code >> 5) & 0x07}.${String(code & 0x1f).padStart(2, '0')}`;
|
|
340
|
+
}
|
|
341
|
+
function parseResponseCode(code) {
|
|
342
|
+
const match = /^([1-5])\.(\d{2})$/.exec(code);
|
|
343
|
+
if (!match)
|
|
344
|
+
throw new Error(`Invalid CoAP response code '${code}'`);
|
|
345
|
+
const detail = Number(match[2]);
|
|
346
|
+
if (detail > 31)
|
|
347
|
+
throw new Error(`Invalid CoAP response code '${code}'`);
|
|
348
|
+
return (Number(match[1]) << 5) | detail;
|
|
349
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CoAP Edge Gateway — wires a CoapTransportPort into an EdgeRuntime.
|
|
3
|
+
*
|
|
4
|
+
* Requests are CON messages matched to responses by token and remote endpoint
|
|
5
|
+
* (RFC 7252 §5.3). Confirmable requests are retransmitted with exponential
|
|
6
|
+
* backoff until acknowledged or until the request deadline expires. An empty
|
|
7
|
+
* ACK keeps the request pending for a separate response, which is acknowledged
|
|
8
|
+
* before being handed to the caller.
|
|
9
|
+
*
|
|
10
|
+
* RFC 7641 observations keep a token registered after the initial response;
|
|
11
|
+
* later notifications with a fresh 24-bit Observe sequence are delivered to the
|
|
12
|
+
* caller and confirmable notifications are acknowledged.
|
|
13
|
+
*/
|
|
14
|
+
import type { EdgeRuntime, EdgeOperationResult } from '@totemsdk/edge';
|
|
15
|
+
import type { CoapTransportPort, CoapBlockOption } from './transport.js';
|
|
16
|
+
import type { CoapSecurityPort } from './security.js';
|
|
17
|
+
export interface CoapGatewayTiming {
|
|
18
|
+
/** Overall request deadline in milliseconds. Default 10_000. */
|
|
19
|
+
requestTimeoutMs?: number;
|
|
20
|
+
/** Initial retransmission interval in milliseconds. Default 2_000. */
|
|
21
|
+
ackTimeoutMs?: number;
|
|
22
|
+
/** Maximum retransmissions before waiting for the deadline. Default 4. */
|
|
23
|
+
maxRetransmits?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface CoapGatewayConfig {
|
|
26
|
+
runtime: EdgeRuntime;
|
|
27
|
+
transport: CoapTransportPort;
|
|
28
|
+
localPort: number;
|
|
29
|
+
timing?: CoapGatewayTiming;
|
|
30
|
+
/** Optional OSCORE boundary; protected endpoints fail closed without it. */
|
|
31
|
+
security?: CoapSecurityPort;
|
|
32
|
+
/** Transparent RFC 7959 blockwise transfer for large payloads. */
|
|
33
|
+
blockwise?: CoapBlockwiseOptions;
|
|
34
|
+
}
|
|
35
|
+
export interface CoapBlockwiseOptions {
|
|
36
|
+
/** Enable transparent blockwise transfer. Default true. */
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
/** Preferred block size in bytes (power of two, 16-1024). Default 1024. */
|
|
39
|
+
blockSize?: number;
|
|
40
|
+
/** Maximum assembled response body. Default 1 MiB. */
|
|
41
|
+
maxBodySize?: number;
|
|
42
|
+
}
|
|
43
|
+
export interface CoapResponseData {
|
|
44
|
+
payload: Uint8Array;
|
|
45
|
+
/** Content-Format option value, e.g. 0 text/plain or 60 application/cbor. */
|
|
46
|
+
contentFormat?: number;
|
|
47
|
+
/** Observe option value (notifications only). */
|
|
48
|
+
observe?: number;
|
|
49
|
+
/** Block1 option echoed by the server. */
|
|
50
|
+
block1?: CoapBlockOption;
|
|
51
|
+
/** Block2 option describing this response block. */
|
|
52
|
+
block2?: CoapBlockOption;
|
|
53
|
+
/** Size1 hint from the server. */
|
|
54
|
+
size1?: number;
|
|
55
|
+
/** Size2 hint (total response body size). */
|
|
56
|
+
size2?: number;
|
|
57
|
+
}
|
|
58
|
+
export interface CoapObservationData extends CoapResponseData {
|
|
59
|
+
observe: number;
|
|
60
|
+
receivedAt: number;
|
|
61
|
+
}
|
|
62
|
+
export interface CoapObservation {
|
|
63
|
+
/** Correlation token as lowercase hex. */
|
|
64
|
+
readonly token: string;
|
|
65
|
+
/** Deregister and stop delivering notifications. */
|
|
66
|
+
cancel(): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
export interface CoapGateway {
|
|
69
|
+
start(): Promise<void>;
|
|
70
|
+
stop(): Promise<void>;
|
|
71
|
+
readonly status: 'stopped' | 'running' | 'error';
|
|
72
|
+
get(path: string[], host: string, port: number): Promise<EdgeOperationResult<CoapResponseData>>;
|
|
73
|
+
post(path: string[], payload: Uint8Array, host: string, port: number): Promise<EdgeOperationResult<CoapResponseData>>;
|
|
74
|
+
observe(path: string[], host: string, port: number, handler: (notification: CoapObservationData) => void): Promise<EdgeOperationResult<CoapObservation>>;
|
|
75
|
+
}
|
|
76
|
+
export declare function createCoapGateway(config: CoapGatewayConfig): CoapGateway;
|