@sirena-lwm2m/coap 0.8.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.
Files changed (157) hide show
  1. package/LICENSE +17 -0
  2. package/README.md +78 -0
  3. package/dist/api/exchange-context.d.ts +34 -0
  4. package/dist/api/exchange-context.d.ts.map +1 -0
  5. package/dist/api/exchange-context.js +2 -0
  6. package/dist/api/exchange-context.js.map +1 -0
  7. package/dist/api/headers.d.ts +10 -0
  8. package/dist/api/headers.d.ts.map +1 -0
  9. package/dist/api/headers.js +69 -0
  10. package/dist/api/headers.js.map +1 -0
  11. package/dist/api/index.d.ts +5 -0
  12. package/dist/api/index.d.ts.map +1 -0
  13. package/dist/api/index.js +4 -0
  14. package/dist/api/index.js.map +1 -0
  15. package/dist/api/request.d.ts +11 -0
  16. package/dist/api/request.d.ts.map +1 -0
  17. package/dist/api/request.js +49 -0
  18. package/dist/api/request.js.map +1 -0
  19. package/dist/api/response.d.ts +11 -0
  20. package/dist/api/response.d.ts.map +1 -0
  21. package/dist/api/response.js +19 -0
  22. package/dist/api/response.js.map +1 -0
  23. package/dist/blockwise/block1-assembler.d.ts +26 -0
  24. package/dist/blockwise/block1-assembler.d.ts.map +1 -0
  25. package/dist/blockwise/block1-assembler.js +94 -0
  26. package/dist/blockwise/block1-assembler.js.map +1 -0
  27. package/dist/blockwise/block2-chunker.d.ts +23 -0
  28. package/dist/blockwise/block2-chunker.d.ts.map +1 -0
  29. package/dist/blockwise/block2-chunker.js +99 -0
  30. package/dist/blockwise/block2-chunker.js.map +1 -0
  31. package/dist/blockwise/index.d.ts +8 -0
  32. package/dist/blockwise/index.d.ts.map +1 -0
  33. package/dist/blockwise/index.js +5 -0
  34. package/dist/blockwise/index.js.map +1 -0
  35. package/dist/blockwise/middleware.d.ts +7 -0
  36. package/dist/blockwise/middleware.d.ts.map +1 -0
  37. package/dist/blockwise/middleware.js +120 -0
  38. package/dist/blockwise/middleware.js.map +1 -0
  39. package/dist/blockwise/option.d.ts +12 -0
  40. package/dist/blockwise/option.d.ts.map +1 -0
  41. package/dist/blockwise/option.js +55 -0
  42. package/dist/blockwise/option.js.map +1 -0
  43. package/dist/index.d.ts +10 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +6 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/message/index.d.ts +21 -0
  48. package/dist/message/index.d.ts.map +1 -0
  49. package/dist/message/index.js +79 -0
  50. package/dist/message/index.js.map +1 -0
  51. package/dist/observability.d.ts +44 -0
  52. package/dist/observability.d.ts.map +1 -0
  53. package/dist/observability.js +71 -0
  54. package/dist/observability.js.map +1 -0
  55. package/dist/observe-notification.d.ts +21 -0
  56. package/dist/observe-notification.d.ts.map +1 -0
  57. package/dist/observe-notification.js +87 -0
  58. package/dist/observe-notification.js.map +1 -0
  59. package/dist/observe.d.ts +10 -0
  60. package/dist/observe.d.ts.map +1 -0
  61. package/dist/observe.js +3 -0
  62. package/dist/observe.js.map +1 -0
  63. package/dist/option-parser/index.d.ts +30 -0
  64. package/dist/option-parser/index.d.ts.map +1 -0
  65. package/dist/option-parser/index.js +125 -0
  66. package/dist/option-parser/index.js.map +1 -0
  67. package/dist/router/app.d.ts +8 -0
  68. package/dist/router/app.d.ts.map +1 -0
  69. package/dist/router/app.js +25 -0
  70. package/dist/router/app.js.map +1 -0
  71. package/dist/router/content-format.d.ts +7 -0
  72. package/dist/router/content-format.d.ts.map +1 -0
  73. package/dist/router/content-format.js +23 -0
  74. package/dist/router/content-format.js.map +1 -0
  75. package/dist/router/index.d.ts +9 -0
  76. package/dist/router/index.d.ts.map +1 -0
  77. package/dist/router/index.js +5 -0
  78. package/dist/router/index.js.map +1 -0
  79. package/dist/router/radix.d.ts +16 -0
  80. package/dist/router/radix.d.ts.map +1 -0
  81. package/dist/router/radix.js +69 -0
  82. package/dist/router/radix.js.map +1 -0
  83. package/dist/router/router.d.ts +38 -0
  84. package/dist/router/router.d.ts.map +1 -0
  85. package/dist/router/router.js +111 -0
  86. package/dist/router/router.js.map +1 -0
  87. package/dist/router.d.ts +2 -0
  88. package/dist/router.d.ts.map +1 -0
  89. package/dist/router.js +2 -0
  90. package/dist/router.js.map +1 -0
  91. package/dist/security.d.ts +3 -0
  92. package/dist/security.d.ts.map +1 -0
  93. package/dist/security.js +2 -0
  94. package/dist/security.js.map +1 -0
  95. package/dist/store/index.d.ts +26 -0
  96. package/dist/store/index.d.ts.map +1 -0
  97. package/dist/store/index.js +68 -0
  98. package/dist/store/index.js.map +1 -0
  99. package/dist/store/sqlite-store.d.ts +10 -0
  100. package/dist/store/sqlite-store.d.ts.map +1 -0
  101. package/dist/store/sqlite-store.js +75 -0
  102. package/dist/store/sqlite-store.js.map +1 -0
  103. package/dist/store.d.ts +5 -0
  104. package/dist/store.d.ts.map +1 -0
  105. package/dist/store.js +3 -0
  106. package/dist/store.js.map +1 -0
  107. package/dist/transport/dtls.d.ts +38 -0
  108. package/dist/transport/dtls.d.ts.map +1 -0
  109. package/dist/transport/dtls.js +151 -0
  110. package/dist/transport/dtls.js.map +1 -0
  111. package/dist/transport/events.d.ts +34 -0
  112. package/dist/transport/events.d.ts.map +1 -0
  113. package/dist/transport/events.js +2 -0
  114. package/dist/transport/events.js.map +1 -0
  115. package/dist/transport/index.d.ts +10 -0
  116. package/dist/transport/index.d.ts.map +1 -0
  117. package/dist/transport/index.js +5 -0
  118. package/dist/transport/index.js.map +1 -0
  119. package/dist/transport/tcp-connection.d.ts +18 -0
  120. package/dist/transport/tcp-connection.d.ts.map +1 -0
  121. package/dist/transport/tcp-connection.js +72 -0
  122. package/dist/transport/tcp-connection.js.map +1 -0
  123. package/dist/transport/tcp-frame.d.ts +17 -0
  124. package/dist/transport/tcp-frame.d.ts.map +1 -0
  125. package/dist/transport/tcp-frame.js +112 -0
  126. package/dist/transport/tcp-frame.js.map +1 -0
  127. package/dist/transport/tcp-stream.d.ts +21 -0
  128. package/dist/transport/tcp-stream.d.ts.map +1 -0
  129. package/dist/transport/tcp-stream.js +52 -0
  130. package/dist/transport/tcp-stream.js.map +1 -0
  131. package/dist/transport/udp.d.ts +97 -0
  132. package/dist/transport/udp.d.ts.map +1 -0
  133. package/dist/transport/udp.js +994 -0
  134. package/dist/transport/udp.js.map +1 -0
  135. package/dist/transport.d.ts +3 -0
  136. package/dist/transport.d.ts.map +1 -0
  137. package/dist/transport.js +2 -0
  138. package/dist/transport.js.map +1 -0
  139. package/docs/api-reference.md +536 -0
  140. package/docs/blockwise.md +117 -0
  141. package/docs/observability.md +206 -0
  142. package/docs/observe.md +203 -0
  143. package/docs/releasing.md +106 -0
  144. package/docs/router.md +170 -0
  145. package/docs/store.md +212 -0
  146. package/docs/tcp-transport.md +89 -0
  147. package/docs/udp-transport.md +81 -0
  148. package/examples/slice-1.ts +12 -0
  149. package/examples/slice-2.ts +96 -0
  150. package/examples/slice-3-psk.ts +70 -0
  151. package/examples/slice-3-x509.ts +85 -0
  152. package/examples/slice-4.ts +45 -0
  153. package/examples/slice-5-firmware.ts +207 -0
  154. package/examples/slice-6-temperature.ts +163 -0
  155. package/examples/slice-7-file-store.ts +92 -0
  156. package/examples/slice-7-otel.ts +123 -0
  157. package/package.json +73 -0
package/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Copyright 2026 Dejan Dimic <dejan.dimic@gmail.com>
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # @sirena-lwm2m/coap
2
+
3
+ RFC 7252 CoAP stack for Node.js with RFC 8323 TCP transport support.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @sirena-lwm2m/coap
9
+ ```
10
+
11
+ ## Quick start — UDP
12
+
13
+ ```ts
14
+ import { createServer, Response, stringToBytes } from '@sirena-lwm2m/coap';
15
+
16
+ const server = createServer({
17
+ listen: [{ host: '0.0.0.0', port: 5683 }],
18
+ });
19
+
20
+ server.on('request', async (req, ctx) => {
21
+ await ctx.respond(Response.ok(stringToBytes(req.url)));
22
+ });
23
+
24
+ await server.start();
25
+ console.log('Listening on:', server.listenerSummary());
26
+ ```
27
+
28
+ ## TCP transport (RFC 8323)
29
+
30
+ The server supports CoAP over TCP using the `coap+tcp://` URI scheme. Bind one or more TCP listeners alongside UDP by passing URI strings to the `listen` array.
31
+
32
+ ```ts
33
+ import { createServer, Response, stringToBytes } from '@sirena-lwm2m/coap';
34
+
35
+ const server = createServer({
36
+ listen: [
37
+ 'coap://127.0.0.1:5683', // UDP
38
+ 'coap+tcp://127.0.0.1:5684', // TCP (RFC 8323)
39
+ ],
40
+ });
41
+
42
+ server.on('request', async (req, ctx) => {
43
+ // ctx.transport is 'udp' or 'tcp'
44
+ console.log(`[${ctx.transport}] ${req.url}`);
45
+ await ctx.respond(Response.ok(stringToBytes(`hello via ${ctx.transport}`)));
46
+ });
47
+
48
+ await server.start();
49
+ console.log('Listening on:', server.listenerSummary());
50
+ await server.stop();
51
+ ```
52
+
53
+ See [`examples/slice-2.ts`](./examples/slice-2.ts) for a working dual-transport demo.
54
+
55
+ See [`docs/tcp-transport.md`](./docs/tcp-transport.md) for framing details, connection multiplexing, and configuration.
56
+
57
+ ## ExchangeContext
58
+
59
+ Every `'request'` event handler receives a `Request` and an `ExchangeContext`:
60
+
61
+ ```ts
62
+ interface ExchangeContext {
63
+ remote: string; // "ip:port" of the sender
64
+ local: string; // "ip:port" of the receiving socket
65
+ transport: 'udp' | 'tcp'; // which transport delivered this request
66
+ token: Uint8Array; // CoAP token bytes
67
+ messageId: number; // CoAP message ID (0 for TCP)
68
+ options: CoapOption[]; // raw parsed options
69
+ peer: Peer; // { mode: 'anonymous' }
70
+ respond(res: Response): Promise<void>;
71
+ cancel(): void;
72
+ abort(code: CoapCode): void;
73
+ }
74
+ ```
75
+
76
+ ## License
77
+
78
+ MIT
@@ -0,0 +1,34 @@
1
+ import type { X509Certificate } from 'node:crypto';
2
+ import type { CoapCode } from '../message/index.js';
3
+ import type { CoapOption } from '../option-parser/index.js';
4
+ import type { Observable } from '../observe.js';
5
+ import type { Response } from './response.js';
6
+ export type Peer = {
7
+ mode: 'anonymous';
8
+ } | {
9
+ mode: 'psk';
10
+ identity: string;
11
+ } | {
12
+ mode: 'x509';
13
+ certChain: X509Certificate[];
14
+ };
15
+ export interface ObserveOptions {
16
+ notifyEveryValue?: boolean;
17
+ }
18
+ export interface ExchangeContext {
19
+ remote: string;
20
+ local: string;
21
+ transport: 'udp' | 'tcp';
22
+ token: Uint8Array;
23
+ messageId: number;
24
+ options: CoapOption[];
25
+ peer: Peer;
26
+ params?: Record<string, string>;
27
+ observeSeq: number;
28
+ respond(res: Response): Promise<void>;
29
+ continue(): Promise<void>;
30
+ cancel(): void;
31
+ abort(code: CoapCode): void;
32
+ observe(observable: Observable<unknown>, opts?: ObserveOptions): void;
33
+ }
34
+ //# sourceMappingURL=exchange-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exchange-context.d.ts","sourceRoot":"","sources":["../../src/api/exchange-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,MAAM,IAAI,GACZ;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,eAAe,EAAE,CAAA;CAAE,CAAC;AAEnD,MAAM,WAAW,cAAc;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,KAAK,GAAG,KAAK,CAAC;IACzB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,MAAM,IAAI,IAAI,CAAC;IACf,KAAK,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,UAAU,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;CACvE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=exchange-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exchange-context.js","sourceRoot":"","sources":["../../src/api/exchange-context.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import type { CoapOption } from '../option-parser/index.js';
2
+ export declare class Headers {
3
+ private options;
4
+ constructor(options: CoapOption[]);
5
+ get(name: string): string | null;
6
+ has(name: string): boolean;
7
+ set(name: string, value: string): void;
8
+ toOptions(): CoapOption[];
9
+ }
10
+ //# sourceMappingURL=headers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../src/api/headers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAuC5D,qBAAa,OAAO;IAClB,OAAO,CAAC,OAAO,CAAe;gBAElB,OAAO,EAAE,UAAU,EAAE;IAIjC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAQhC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAM1B,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAYtC,SAAS,IAAI,UAAU,EAAE;CAG1B"}
@@ -0,0 +1,69 @@
1
+ import { OptionNumber, bytesToUint, bytesToString, uintToBytes, stringToBytes } from '../option-parser/index.js';
2
+ const NAME_TO_NUMBER = {
3
+ 'Content-Format': OptionNumber.ContentFormat,
4
+ 'Max-Age': OptionNumber.MaxAge,
5
+ 'Accept': OptionNumber.Accept,
6
+ 'ETag': 4,
7
+ };
8
+ const NUMBER_TO_NAME = Object.fromEntries(Object.entries(NAME_TO_NUMBER).map(([k, v]) => [v, k]));
9
+ function optionToString(number, value) {
10
+ switch (number) {
11
+ case OptionNumber.ContentFormat:
12
+ case OptionNumber.Accept:
13
+ return String(bytesToUint(value));
14
+ case OptionNumber.MaxAge:
15
+ return String(bytesToUint(value));
16
+ case 4: // ETag
17
+ return bytesToString(value);
18
+ default:
19
+ return bytesToString(value);
20
+ }
21
+ }
22
+ function stringToOptionValue(number, s) {
23
+ switch (number) {
24
+ case OptionNumber.ContentFormat:
25
+ case OptionNumber.Accept:
26
+ case OptionNumber.MaxAge:
27
+ return uintToBytes(parseInt(s, 10));
28
+ default:
29
+ return stringToBytes(s);
30
+ }
31
+ }
32
+ export class Headers {
33
+ options;
34
+ constructor(options) {
35
+ this.options = [...options];
36
+ }
37
+ get(name) {
38
+ const num = NAME_TO_NUMBER[name];
39
+ if (num === undefined)
40
+ return null;
41
+ const opt = this.options.find(o => o.number === num);
42
+ if (!opt)
43
+ return null;
44
+ return optionToString(num, opt.value);
45
+ }
46
+ has(name) {
47
+ const num = NAME_TO_NUMBER[name];
48
+ if (num === undefined)
49
+ return false;
50
+ return this.options.some(o => o.number === num);
51
+ }
52
+ set(name, value) {
53
+ const num = NAME_TO_NUMBER[name];
54
+ if (num === undefined)
55
+ return;
56
+ const idx = this.options.findIndex(o => o.number === num);
57
+ const encoded = stringToOptionValue(num, value);
58
+ if (idx >= 0) {
59
+ this.options[idx] = { number: num, value: encoded };
60
+ }
61
+ else {
62
+ this.options.push({ number: num, value: encoded });
63
+ }
64
+ }
65
+ toOptions() {
66
+ return [...this.options];
67
+ }
68
+ }
69
+ //# sourceMappingURL=headers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.js","sourceRoot":"","sources":["../../src/api/headers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { Request } from './request.js';
2
+ export { Response } from './response.js';
3
+ export { Headers } from './headers.js';
4
+ export type { ExchangeContext, Peer, ObserveOptions } from './exchange-context.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,eAAe,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { Request } from './request.js';
2
+ export { Response } from './response.js';
3
+ export { Headers } from './headers.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { CoapMessage, CoapCode } from '../message/index.js';
2
+ import { Headers } from './headers.js';
3
+ export declare class Request {
4
+ readonly method: string;
5
+ readonly methodCode: CoapCode;
6
+ readonly url: string;
7
+ readonly headers: Headers;
8
+ readonly body: ReadableStream<Uint8Array> | null;
9
+ constructor(msg: CoapMessage);
10
+ }
11
+ //# sourceMappingURL=request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/api/request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGjE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAiCvC,qBAAa,OAAO;IAClB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;gBAErC,GAAG,EAAE,WAAW;CAc7B"}
@@ -0,0 +1,49 @@
1
+ import { codeToString } from '../message/index.js';
2
+ import { OptionNumber, bytesToString, bytesToUint } from '../option-parser/index.js';
3
+ import { Headers } from './headers.js';
4
+ const METHOD_CODE_MAP = {
5
+ 0x01: 'GET',
6
+ 0x02: 'POST',
7
+ 0x03: 'PUT',
8
+ 0x04: 'DELETE',
9
+ };
10
+ function assembleUrl(msg) {
11
+ const pathParts = msg.options
12
+ .filter(o => o.number === OptionNumber.UriPath)
13
+ .map(o => bytesToString(o.value));
14
+ const queryParts = msg.options
15
+ .filter(o => o.number === OptionNumber.UriQuery)
16
+ .map(o => bytesToString(o.value));
17
+ const hostOpt = msg.options.find(o => o.number === OptionNumber.UriHost);
18
+ const portOpt = msg.options.find(o => o.number === OptionNumber.UriPort);
19
+ const path = pathParts.length > 0 ? '/' + pathParts.join('/') : '/';
20
+ const query = queryParts.length > 0 ? '?' + queryParts.join('&') : '';
21
+ if (hostOpt) {
22
+ const host = bytesToString(hostOpt.value);
23
+ const port = portOpt ? bytesToUint(portOpt.value) : 5683;
24
+ return `coap://${host}:${port}${path}${query}`;
25
+ }
26
+ return `${path}${query}`;
27
+ }
28
+ export class Request {
29
+ method;
30
+ methodCode;
31
+ url;
32
+ headers;
33
+ body;
34
+ constructor(msg) {
35
+ this.methodCode = msg.code;
36
+ this.method = METHOD_CODE_MAP[msg.code] ?? codeToString(msg.code);
37
+ this.url = assembleUrl(msg);
38
+ this.headers = new Headers(msg.options);
39
+ this.body = msg.payload.length > 0
40
+ ? new ReadableStream({
41
+ start(controller) {
42
+ controller.enqueue(msg.payload);
43
+ controller.close();
44
+ },
45
+ })
46
+ : null;
47
+ }
48
+ }
49
+ //# sourceMappingURL=request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.js","sourceRoot":"","sources":["../../src/api/request.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { CoapCode } from '../message/index.js';
2
+ import { Headers } from './headers.js';
3
+ export declare class Response {
4
+ readonly status: string;
5
+ readonly statusCode: CoapCode;
6
+ readonly headers: Headers;
7
+ readonly body: Uint8Array | null;
8
+ constructor(statusCode: CoapCode, body?: Uint8Array, headers?: Headers);
9
+ static ok(body?: Uint8Array, headers?: Headers): Response;
10
+ }
11
+ //# sourceMappingURL=response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/api/response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAIvC,qBAAa,QAAQ;IACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;gBAErB,UAAU,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,OAAO;IAOtE,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ;CAG1D"}
@@ -0,0 +1,19 @@
1
+ import { codeToString, stringToCode } from '../message/index.js';
2
+ import { Headers } from './headers.js';
3
+ const OK_CODE = stringToCode('2.05');
4
+ export class Response {
5
+ status;
6
+ statusCode;
7
+ headers;
8
+ body;
9
+ constructor(statusCode, body, headers) {
10
+ this.statusCode = statusCode;
11
+ this.status = codeToString(statusCode);
12
+ this.headers = headers ?? new Headers([]);
13
+ this.body = body ?? null;
14
+ }
15
+ static ok(body, headers) {
16
+ return new Response(OK_CODE, body, headers);
17
+ }
18
+ }
19
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/api/response.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;AAErC,MAAM,OAAO,QAAQ;IACV,MAAM,CAAS;IACf,UAAU,CAAW;IACrB,OAAO,CAAU;IACjB,IAAI,CAAoB;IAEjC,YAAY,UAAoB,EAAE,IAAiB,EAAE,OAAiB;QACpE,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;QACvC,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,IAAI,OAAO,CAAC,EAAE,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,IAAI,CAAC;IAC3B,CAAC;IAED,MAAM,CAAC,EAAE,CAAC,IAAiB,EAAE,OAAiB;QAC5C,OAAO,IAAI,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;CACF"}
@@ -0,0 +1,26 @@
1
+ import { EventEmitter } from 'node:events';
2
+ export interface Block1AssemblerOptions {
3
+ maxBytes?: number;
4
+ idleTimeoutMs?: number;
5
+ }
6
+ export interface BlockTransferEvent {
7
+ reason: string;
8
+ }
9
+ export declare class Block1Assembler extends EventEmitter {
10
+ readonly tempPath: string;
11
+ private _body;
12
+ private expectedNum;
13
+ private totalBytes;
14
+ private idleTimer;
15
+ private aborted;
16
+ private readonly maxBytes;
17
+ private readonly idleTimeoutMs;
18
+ constructor(options?: Block1AssemblerOptions);
19
+ addChunk(num: number, _szx: number, m: boolean, payload: Uint8Array): 'continue' | 'complete' | 'out-of-order' | 'too-large';
20
+ get body(): ReadableStream<Uint8Array>;
21
+ abort(reason?: string): void;
22
+ private buildReadableStream;
23
+ private resetIdleTimer;
24
+ private clearIdleTimer;
25
+ }
26
+ //# sourceMappingURL=block1-assembler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block1-assembler.d.ts","sourceRoot":"","sources":["../../src/blockwise/block1-assembler.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,eAAgB,SAAQ,YAAY;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,KAAK,CAAyC;IACtD,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,SAAS,CAA4C;IAC7D,OAAO,CAAC,OAAO,CAAS;IAExB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAE3B,OAAO,CAAC,EAAE,sBAAsB;IAQ5C,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,cAAc,GAAG,WAAW;IA0B5H,IAAI,IAAI,IAAI,cAAc,CAAC,UAAU,CAAC,CAKrC;IAED,KAAK,CAAC,MAAM,SAAa,GAAG,IAAI;IAYhC,OAAO,CAAC,mBAAmB;IAuB3B,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,cAAc;CAMvB"}
@@ -0,0 +1,94 @@
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { EventEmitter } from 'node:events';
5
+ export class Block1Assembler extends EventEmitter {
6
+ tempPath;
7
+ _body;
8
+ expectedNum = 0;
9
+ totalBytes = 0;
10
+ idleTimer;
11
+ aborted = false;
12
+ maxBytes;
13
+ idleTimeoutMs;
14
+ constructor(options) {
15
+ super();
16
+ this.maxBytes = options?.maxBytes ?? 16 * 1024 * 1024;
17
+ this.idleTimeoutMs = options?.idleTimeoutMs ?? 60_000;
18
+ this.tempPath = path.join(os.tmpdir(), `coap-block1-${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`);
19
+ this.resetIdleTimer();
20
+ }
21
+ addChunk(num, _szx, m, payload) {
22
+ if (num !== this.expectedNum) {
23
+ return 'out-of-order';
24
+ }
25
+ if (this.totalBytes + payload.length > this.maxBytes) {
26
+ this.abort('too-large');
27
+ return 'too-large';
28
+ }
29
+ fs.appendFileSync(this.tempPath, payload);
30
+ this.totalBytes += payload.length;
31
+ this.expectedNum++;
32
+ this.resetIdleTimer();
33
+ if (!m) {
34
+ this.clearIdleTimer();
35
+ this._body = this.buildReadableStream();
36
+ this.emit('block-transfer-complete');
37
+ return 'complete';
38
+ }
39
+ return 'continue';
40
+ }
41
+ get body() {
42
+ if (!this._body) {
43
+ throw new Error('body is not available until addChunk returns "complete"');
44
+ }
45
+ return this._body;
46
+ }
47
+ abort(reason = 'explicit') {
48
+ if (this.aborted)
49
+ return;
50
+ this.aborted = true;
51
+ this.clearIdleTimer();
52
+ try {
53
+ fs.unlinkSync(this.tempPath);
54
+ }
55
+ catch {
56
+ // file may not exist yet
57
+ }
58
+ this.emit('block-transfer-aborted', { reason });
59
+ }
60
+ buildReadableStream() {
61
+ const filePath = this.tempPath;
62
+ return new ReadableStream({
63
+ start(controller) {
64
+ const nodeStream = fs.createReadStream(filePath);
65
+ nodeStream.on('data', (chunk) => {
66
+ controller.enqueue(new Uint8Array(chunk));
67
+ });
68
+ nodeStream.on('end', () => {
69
+ controller.close();
70
+ try {
71
+ fs.unlinkSync(filePath);
72
+ }
73
+ catch {
74
+ // already deleted
75
+ }
76
+ });
77
+ nodeStream.on('error', (err) => {
78
+ controller.error(err);
79
+ });
80
+ },
81
+ });
82
+ }
83
+ resetIdleTimer() {
84
+ this.clearIdleTimer();
85
+ this.idleTimer = setTimeout(() => this.abort('idle-timeout'), this.idleTimeoutMs);
86
+ }
87
+ clearIdleTimer() {
88
+ if (this.idleTimer !== undefined) {
89
+ clearTimeout(this.idleTimer);
90
+ this.idleTimer = undefined;
91
+ }
92
+ }
93
+ }
94
+ //# sourceMappingURL=block1-assembler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block1-assembler.js","sourceRoot":"","sources":["../../src/blockwise/block1-assembler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { BlockTransferEvent } from './block1-assembler.js';
3
+ export type { BlockTransferEvent };
4
+ export declare class Block2Chunker extends EventEmitter {
5
+ private readonly blockSize;
6
+ private buffered;
7
+ private etag;
8
+ private readonly source;
9
+ private aborted;
10
+ private completeFired;
11
+ constructor(body: Buffer | ReadableStream<Uint8Array>, options?: {
12
+ blockSize?: number;
13
+ });
14
+ get totalBlocks(): number;
15
+ abort(reason?: string): void;
16
+ getBlock(num: number): Promise<{
17
+ payload: Uint8Array;
18
+ m: boolean;
19
+ etag: Uint8Array;
20
+ }>;
21
+ private bufferSource;
22
+ }
23
+ //# sourceMappingURL=block2-chunker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block2-chunker.d.ts","sourceRoot":"","sources":["../../src/blockwise/block2-chunker.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,YAAY,EAAE,kBAAkB,EAAE,CAAC;AA0BnC,qBAAa,aAAc,SAAQ,YAAY;IAC7C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,IAAI,CAAyB;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsC;IAC7D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,aAAa,CAAS;gBAG5B,IAAI,EAAE,MAAM,GAAG,cAAc,CAAC,UAAU,CAAC,EACzC,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;IAiBlC,IAAI,WAAW,IAAI,MAAM,CAKxB;IAED,KAAK,CAAC,MAAM,SAAa,GAAG,IAAI;IAM1B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,UAAU,CAAC;QAAC,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,UAAU,CAAA;KAAE,CAAC;YA0B7E,YAAY;CAsB3B"}
@@ -0,0 +1,99 @@
1
+ // RFC 7959 §2.5 — Block2 download chunker
2
+ // ReadableStream input is fully buffered on first getBlock call; RFC 7959 requires
3
+ // stable block numbers across retransmissions, so we cannot stream lazily.
4
+ import { EventEmitter } from 'node:events';
5
+ const MIN_BLOCK_SIZE = 16;
6
+ const MAX_BLOCK_SIZE = 1024;
7
+ function crc32(data) {
8
+ const table = buildCrc32Table();
9
+ let crc = 0xffffffff;
10
+ for (let i = 0; i < data.length; i++) {
11
+ crc = (crc >>> 8) ^ table[(crc ^ data[i]) & 0xff];
12
+ }
13
+ return (crc ^ 0xffffffff) >>> 0;
14
+ }
15
+ function buildCrc32Table() {
16
+ const t = new Uint32Array(256);
17
+ for (let i = 0; i < 256; i++) {
18
+ let c = i;
19
+ for (let j = 0; j < 8; j++) {
20
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
21
+ }
22
+ t[i] = c;
23
+ }
24
+ return t;
25
+ }
26
+ export class Block2Chunker extends EventEmitter {
27
+ blockSize;
28
+ buffered;
29
+ etag;
30
+ source;
31
+ aborted = false;
32
+ completeFired = false;
33
+ constructor(body, options) {
34
+ super();
35
+ const size = options?.blockSize ?? 512;
36
+ if (size < MIN_BLOCK_SIZE ||
37
+ size > MAX_BLOCK_SIZE ||
38
+ (size & (size - 1)) !== 0) {
39
+ throw new RangeError(`blockSize must be a power of two between ${MIN_BLOCK_SIZE} and ${MAX_BLOCK_SIZE}, got ${size}`);
40
+ }
41
+ this.blockSize = size;
42
+ this.source = body;
43
+ }
44
+ get totalBlocks() {
45
+ if (!this.buffered) {
46
+ throw new Error('totalBlocks is not available until getBlock() has been called');
47
+ }
48
+ return Math.ceil(this.buffered.length / this.blockSize);
49
+ }
50
+ abort(reason = 'explicit') {
51
+ if (this.aborted)
52
+ return;
53
+ this.aborted = true;
54
+ this.emit('block-transfer-aborted', { reason });
55
+ }
56
+ async getBlock(num) {
57
+ if (!this.buffered) {
58
+ this.buffered = await this.bufferSource();
59
+ const crc = crc32(this.buffered);
60
+ this.etag = new Uint8Array(4);
61
+ new DataView(this.etag.buffer).setUint32(0, crc, false);
62
+ }
63
+ const total = Math.ceil(this.buffered.length / this.blockSize);
64
+ if (num >= total) {
65
+ throw new RangeError(`Block NUM ${num} is out of range (totalBlocks=${total})`);
66
+ }
67
+ const start = num * this.blockSize;
68
+ const end = Math.min(start + this.blockSize, this.buffered.length);
69
+ const payload = this.buffered.slice(start, end);
70
+ const m = num < total - 1;
71
+ if (!m && !this.completeFired) {
72
+ this.completeFired = true;
73
+ this.emit('block-transfer-complete');
74
+ }
75
+ return { payload, m, etag: this.etag };
76
+ }
77
+ async bufferSource() {
78
+ if (Buffer.isBuffer(this.source)) {
79
+ return new Uint8Array(this.source);
80
+ }
81
+ const chunks = [];
82
+ const reader = this.source.getReader();
83
+ while (true) {
84
+ const { done, value } = await reader.read();
85
+ if (done)
86
+ break;
87
+ chunks.push(value);
88
+ }
89
+ const total = chunks.reduce((sum, c) => sum + c.length, 0);
90
+ const result = new Uint8Array(total);
91
+ let offset = 0;
92
+ for (const chunk of chunks) {
93
+ result.set(chunk, offset);
94
+ offset += chunk.length;
95
+ }
96
+ return result;
97
+ }
98
+ }
99
+ //# sourceMappingURL=block2-chunker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block2-chunker.js","sourceRoot":"","sources":["../../src/blockwise/block2-chunker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export type { BlockOption } from './option.js';
2
+ export { BlockOptionError, encodeBlockOption, decodeBlockOption, blockSize } from './option.js';
3
+ export type { Block1AssemblerOptions, BlockTransferEvent } from './block1-assembler.js';
4
+ export { Block1Assembler } from './block1-assembler.js';
5
+ export { Block2Chunker } from './block2-chunker.js';
6
+ export { blockwiseMiddleware } from './middleware.js';
7
+ export type { BlockwiseMiddlewareOptions } from './middleware.js';
8
+ //# sourceMappingURL=index.d.ts.map