@particle/esim-tooling 1.0.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 (77) hide show
  1. package/README.md +187 -0
  2. package/dist/activation-code.d.ts +11 -0
  3. package/dist/activation-code.d.ts.map +1 -0
  4. package/dist/activation-code.js +56 -0
  5. package/dist/activation-code.js.map +1 -0
  6. package/dist/apdu.d.ts +73 -0
  7. package/dist/apdu.d.ts.map +1 -0
  8. package/dist/apdu.js +357 -0
  9. package/dist/apdu.js.map +1 -0
  10. package/dist/errors.d.ts +32 -0
  11. package/dist/errors.d.ts.map +1 -0
  12. package/dist/errors.js +52 -0
  13. package/dist/errors.js.map +1 -0
  14. package/dist/es10/es10b-notifications.d.ts +30 -0
  15. package/dist/es10/es10b-notifications.d.ts.map +1 -0
  16. package/dist/es10/es10b-notifications.js +294 -0
  17. package/dist/es10/es10b-notifications.js.map +1 -0
  18. package/dist/es10/es10b.d.ts +34 -0
  19. package/dist/es10/es10b.d.ts.map +1 -0
  20. package/dist/es10/es10b.js +108 -0
  21. package/dist/es10/es10b.js.map +1 -0
  22. package/dist/es10/es10c.d.ts +12 -0
  23. package/dist/es10/es10c.d.ts.map +1 -0
  24. package/dist/es10/es10c.js +133 -0
  25. package/dist/es10/es10c.js.map +1 -0
  26. package/dist/es10/iccid.d.ts +9 -0
  27. package/dist/es10/iccid.d.ts.map +1 -0
  28. package/dist/es10/iccid.js +31 -0
  29. package/dist/es10/iccid.js.map +1 -0
  30. package/dist/es10/index.d.ts +5 -0
  31. package/dist/es10/index.d.ts.map +1 -0
  32. package/dist/es10/index.js +4 -0
  33. package/dist/es10/index.js.map +1 -0
  34. package/dist/es10/tags.d.ts +55 -0
  35. package/dist/es10/tags.d.ts.map +1 -0
  36. package/dist/es10/tags.js +63 -0
  37. package/dist/es10/tags.js.map +1 -0
  38. package/dist/es9plus.d.ts +52 -0
  39. package/dist/es9plus.d.ts.map +1 -0
  40. package/dist/es9plus.js +227 -0
  41. package/dist/es9plus.js.map +1 -0
  42. package/dist/gsma-rsp2-root-ci1.d.ts +38 -0
  43. package/dist/gsma-rsp2-root-ci1.d.ts.map +1 -0
  44. package/dist/gsma-rsp2-root-ci1.js +52 -0
  45. package/dist/gsma-rsp2-root-ci1.js.map +1 -0
  46. package/dist/index.d.ts +6 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +5 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/lpa.d.ts +15 -0
  51. package/dist/lpa.d.ts.map +1 -0
  52. package/dist/lpa.js +283 -0
  53. package/dist/lpa.js.map +1 -0
  54. package/dist/tlv.d.ts +14 -0
  55. package/dist/tlv.d.ts.map +1 -0
  56. package/dist/tlv.js +132 -0
  57. package/dist/tlv.js.map +1 -0
  58. package/dist/types.d.ts +230 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/dist/types.js +108 -0
  61. package/dist/types.js.map +1 -0
  62. package/package.json +50 -0
  63. package/src/activation-code.ts +64 -0
  64. package/src/apdu.ts +419 -0
  65. package/src/errors.ts +69 -0
  66. package/src/es10/es10b-notifications.ts +331 -0
  67. package/src/es10/es10b.ts +163 -0
  68. package/src/es10/es10c.ts +168 -0
  69. package/src/es10/iccid.ts +32 -0
  70. package/src/es10/index.ts +42 -0
  71. package/src/es10/tags.ts +69 -0
  72. package/src/es9plus.ts +331 -0
  73. package/src/gsma-rsp2-root-ci1.ts +53 -0
  74. package/src/index.ts +43 -0
  75. package/src/lpa.ts +346 -0
  76. package/src/tlv.ts +137 -0
  77. package/src/types.ts +264 -0
package/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # @particle/esim-tooling
2
+
3
+ TypeScript library for eSIM profile provisioning (SGP.22). Manages eSIM profiles on
4
+ devices with an eUICC — list, enable, disable, delete, and install profiles.
5
+
6
+ ## Installation
7
+
8
+ ```
9
+ npm install @particle/esim-tooling
10
+ ```
11
+
12
+ ## Quick Start
13
+
14
+ ```typescript
15
+ import { EsimLpa } from '@particle/esim-tooling';
16
+ import type { DeviceAdapter, ServerAdapter } from '@particle/esim-tooling';
17
+
18
+ // Create adapters for your environment
19
+ const device: DeviceAdapter = createMyDeviceAdapter();
20
+ const server: ServerAdapter = createMyServerAdapter();
21
+
22
+ const lpa = new EsimLpa({ device, server });
23
+
24
+ // List profiles
25
+ const profiles = await lpa.listProfiles();
26
+ for (const p of profiles) {
27
+ console.log(`${p.iccid} ${p.profileName} [${p.state === 1 ? 'enabled' : 'disabled'}]`);
28
+ }
29
+
30
+ // Enable a profile
31
+ await lpa.enableProfile('89012345678901234567');
32
+
33
+ // Disable a profile
34
+ await lpa.disableProfile('89012345678901234567');
35
+
36
+ // Delete a profile (must be disabled first)
37
+ await lpa.deleteProfile('89012345678901234567');
38
+
39
+ // Install a new profile
40
+ const result = await lpa.installProfile({
41
+ activationCode: '1$smdp.example.com$ACTIVATION-TOKEN',
42
+ onProgress: (p) => console.log(p.step),
43
+ });
44
+ console.log(`Installed: ${result.iccid}`);
45
+
46
+ // Process pending notifications
47
+ const notifResult = await lpa.processNotifications();
48
+ console.log(`Sent ${notifResult.sent}/${notifResult.total} notifications`);
49
+ ```
50
+
51
+ ## Adapters
52
+
53
+ This library does not directly communicate with devices or servers. You provide
54
+ two adapters that handle I/O.
55
+
56
+ ### DeviceAdapter
57
+
58
+ Sends a single APDU to the eUICC and returns the response. The library handles
59
+ all higher-level protocol concerns (logical channels, STORE DATA chunking,
60
+ GET RESPONSE chaining, BPP segmentation).
61
+
62
+ ```typescript
63
+ interface DeviceAdapter {
64
+ sendApdu(apdu: Uint8Array): Promise<Uint8Array>;
65
+ }
66
+ ```
67
+
68
+ **Example: Particle USB adapter**
69
+
70
+ ```typescript
71
+ import { openDeviceById } from 'particle-usb';
72
+ import DeviceOSProtobuf from '@particle/device-os-protobuf';
73
+
74
+ const proto = {
75
+ ManagerRequest: DeviceOSProtobuf.schema.system.esim.ManagerRequest,
76
+ ManagerResponse: DeviceOSProtobuf.schema.system.esim.ManagerResponse,
77
+ };
78
+
79
+ function createParticleUsbAdapter(device): DeviceAdapter {
80
+ return {
81
+ async sendApdu(apdu: Uint8Array): Promise<Uint8Array> {
82
+ // Encode APDU as protobuf ManagerRequest
83
+ const request = proto.ManagerRequest.create({ apdu: { data: apdu } });
84
+ const requestBytes = proto.ManagerRequest.encode(request).finish();
85
+ const response = await device.sendControlRequest(10, Buffer.from(requestBytes));
86
+ // Decode protobuf ManagerResponse, return APDU response
87
+ const managerResponse = proto.ManagerResponse.decode(response.data);
88
+ return new Uint8Array(managerResponse.apdu.data);
89
+ }
90
+ };
91
+ }
92
+ ```
93
+
94
+ ### ServerAdapter
95
+
96
+ Forwards ES9+ ASN.1 DER requests to SM-DP+ servers. SM-DP+ servers use TLS certificates
97
+ signed by the GSMA RSP2 Root CI, which is not in standard system CA stores.
98
+ The adapter owns the entire HTTP lifecycle — URL construction, headers, TLS.
99
+
100
+ ```typescript
101
+ interface ServerAdapter {
102
+ sendRsp(smdpAddress: string, endpoint: string, requestData: Uint8Array): Promise<ServerResponse>;
103
+ }
104
+
105
+ interface ServerResponse {
106
+ statusCode: number;
107
+ responseData?: Uint8Array;
108
+ }
109
+ ```
110
+
111
+ **Example: direct adapter**
112
+
113
+ ```typescript
114
+ const server: ServerAdapter = {
115
+ async sendRsp(smdpAddress, endpoint, requestData) {
116
+ const response = await fetch(`https://${smdpAddress}/gsma/rsp2/asn1`, {
117
+ method: 'POST',
118
+ headers: {
119
+ 'Content-Type': 'application/x-gsma-rsp-asn1',
120
+ 'User-Agent': 'gsma-rsp-lpad',
121
+ 'X-Admin-Protocol': 'gsma/rsp/v2.1.0',
122
+ },
123
+ body: requestData,
124
+ });
125
+ const statusCode = response.status;
126
+ if (statusCode === 204) return { statusCode };
127
+ const responseData = new Uint8Array(await response.arrayBuffer());
128
+ return { statusCode, responseData };
129
+ }
130
+ };
131
+ ```
132
+
133
+ ## API Reference
134
+
135
+ ### `EsimLpa`
136
+
137
+ | Method | Description |
138
+ |--------|-------------|
139
+ | `getEid()` | Get the 32-character hex EID |
140
+ | `listProfiles()` | List all profiles on the eUICC |
141
+ | `enableProfile(iccid)` | Enable a disabled profile |
142
+ | `disableProfile(iccid)` | Disable the active profile |
143
+ | `deleteProfile(iccid)` | Delete a disabled profile |
144
+ | `installProfile(options)` | Download and install a profile from SM-DP+ |
145
+ | `listNotifications()` | List pending notification metadata |
146
+ | `processNotifications()` | Send all pending notifications to SM-DP+ servers |
147
+
148
+ ### Profile States
149
+
150
+ | Value | State |
151
+ |-------|-------|
152
+ | 0 | Disabled |
153
+ | 1 | Enabled |
154
+
155
+ ### Notification Events
156
+
157
+ | Value | Event |
158
+ |-------|-------|
159
+ | 0x80 | Install |
160
+ | 0x40 | Enable |
161
+ | 0x20 | Disable |
162
+ | 0x10 | Delete |
163
+
164
+ ## Error Handling
165
+
166
+ ```typescript
167
+ import { Es10Error, Es9PlusError, DeviceError } from '@particle/esim-tooling';
168
+
169
+ try {
170
+ await lpa.enableProfile(iccid);
171
+ } catch (error) {
172
+ if (error instanceof Es10Error) {
173
+ // eUICC returned an error (e.g., profile not found, policy violation)
174
+ console.log('eUICC error:', error.resultCode, error.message);
175
+ } else if (error instanceof Es9PlusError) {
176
+ // SM-DP+ server error
177
+ console.log('Server error:', error.statusCode, error.serverStatus);
178
+ } else if (error instanceof DeviceError) {
179
+ // Device communication error
180
+ console.log('Device error:', error.result);
181
+ }
182
+ }
183
+ ```
184
+
185
+ ## License
186
+
187
+ Apache-2.0
@@ -0,0 +1,11 @@
1
+ import type { ActivationCode } from './types.js';
2
+ /**
3
+ * Parse an RSP activation code string per SGP.22 Section 4.1.
4
+ * Format: LPA:1$<smdpAddress>$<matchingId>[$<smdpOid>][$<ccRequired>]
5
+ * Per spec, the parser SHALL ignore a delimiter and any further parameters
6
+ * beyond those it recognizes.
7
+ */
8
+ export declare function parseActivationCode(code: string): ActivationCode;
9
+ export declare function isValidActivationCode(code: string): boolean;
10
+ export declare function formatActivationCode(params: Omit<ActivationCode, 'format'>): string;
11
+ //# sourceMappingURL=activation-code.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activation-code.d.ts","sourceRoot":"","sources":["../src/activation-code.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAkChE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAO3D;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,GAAG,MAAM,CASnF"}
@@ -0,0 +1,56 @@
1
+ import { ActivationCodeError } from './errors.js';
2
+ /**
3
+ * Parse an RSP activation code string per SGP.22 Section 4.1.
4
+ * Format: LPA:1$<smdpAddress>$<matchingId>[$<smdpOid>][$<ccRequired>]
5
+ * Per spec, the parser SHALL ignore a delimiter and any further parameters
6
+ * beyond those it recognizes.
7
+ */
8
+ export function parseActivationCode(code) {
9
+ // Strip optional "LPA:" prefix
10
+ let input = code;
11
+ if (input.startsWith('LPA:')) {
12
+ input = input.substring(4);
13
+ }
14
+ const parts = input.split('$');
15
+ if (parts.length < 3) {
16
+ throw new ActivationCodeError('Activation code must have at least format, address, and matching ID');
17
+ }
18
+ const format = parseInt(parts[0], 10);
19
+ if (format !== 1) {
20
+ throw new ActivationCodeError(`Unsupported activation code format: ${format}`);
21
+ }
22
+ const smdpAddress = parts[1];
23
+ if (!smdpAddress) {
24
+ throw new ActivationCodeError('SM-DP+ address is required');
25
+ }
26
+ const matchingId = parts[2];
27
+ const smdpOid = parts.length > 3 && parts[3] ? parts[3] : undefined;
28
+ const confirmationCodeRequired = parts.length > 4 && parts[4] === '1';
29
+ return {
30
+ format,
31
+ smdpAddress,
32
+ matchingId,
33
+ smdpOid,
34
+ confirmationCodeRequired,
35
+ };
36
+ }
37
+ export function isValidActivationCode(code) {
38
+ try {
39
+ parseActivationCode(code);
40
+ return true;
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ export function formatActivationCode(params) {
47
+ let code = `1$${params.smdpAddress}$${params.matchingId}`;
48
+ if (params.smdpOid || params.confirmationCodeRequired) {
49
+ code += `$${params.smdpOid ?? ''}`;
50
+ }
51
+ if (params.confirmationCodeRequired) {
52
+ code += '$1';
53
+ }
54
+ return code;
55
+ }
56
+ //# sourceMappingURL=activation-code.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activation-code.js","sourceRoot":"","sources":["../src/activation-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGlD;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,+BAA+B;IAC/B,IAAI,KAAK,GAAG,IAAI,CAAC;IACjB,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7B,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,mBAAmB,CAAC,qEAAqE,CAAC,CAAC;IACvG,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACtC,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;QACjB,MAAM,IAAI,mBAAmB,CAAC,uCAAuC,MAAM,EAAE,CAAC,CAAC;IACjF,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7B,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,mBAAmB,CAAC,4BAA4B,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAE5B,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACpE,MAAM,wBAAwB,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;IAEtE,OAAO;QACL,MAAM;QACN,WAAW;QACX,UAAU;QACV,OAAO;QACP,wBAAwB;KACzB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,IAAI,CAAC;QACH,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAsC;IACzE,IAAI,IAAI,GAAG,KAAK,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;IAC1D,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,wBAAwB,EAAE,CAAC;QACtD,IAAI,IAAI,IAAI,MAAM,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;IACrC,CAAC;IACD,IAAI,MAAM,CAAC,wBAAwB,EAAE,CAAC;QACpC,IAAI,IAAI,IAAI,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
package/dist/apdu.d.ts ADDED
@@ -0,0 +1,73 @@
1
+ import type { DeviceAdapter } from './types.js';
2
+ /**
3
+ * APDU transport layer — handles logical channel management, STORE DATA
4
+ * chunking, GET RESPONSE chaining, and BPP segmentation over a DeviceAdapter
5
+ * that sends individual APDUs.
6
+ */
7
+ export declare class ApduTransport {
8
+ private device;
9
+ private channel;
10
+ constructor(device: DeviceAdapter);
11
+ /**
12
+ * Send a complete SGP.22 TLV payload to the eUICC and return the complete
13
+ * TLV response. Handles STORE DATA chunking and GET RESPONSE chaining.
14
+ */
15
+ sendCommand(data: Uint8Array): Promise<Uint8Array>;
16
+ /**
17
+ * Load BPP per SGP.22 Section 2.5.5 Segmented BPP loading.
18
+ *
19
+ * Segmentation order:
20
+ * 1. BF36 header + BF23 (InitialiseSecureChannel)
21
+ * 2. A0 complete (firstSequenceOf87)
22
+ * 3. A1 header only (sequenceOf88)
23
+ * 4. Each 88 TLV individually
24
+ * 5. A2 complete if present (secondSequenceOf87)
25
+ * 6. A3 header only (sequenceOf86)
26
+ * 7. Each 86 TLV individually
27
+ *
28
+ * Critical: P1=0x91 only on the VERY LAST APDU of entire BPP.
29
+ * Block number (P2) resets at each segment boundary.
30
+ */
31
+ loadBoundProfilePackage(bpp: Uint8Array): Promise<Uint8Array | undefined>;
32
+ /**
33
+ * Build Segment 1: BF36 header (tag+length) + complete BF23
34
+ */
35
+ private buildBppSegment1;
36
+ /**
37
+ * Build sequence header (tag+length only, no children)
38
+ */
39
+ private buildSequenceHeader;
40
+ /**
41
+ * Send BPP segments with correct P1 handling:
42
+ * - P1=0x11 for all APDUs except the very last
43
+ * - P1=0x91 only on the VERY LAST APDU of the entire BPP
44
+ * - P2 resets at each segment boundary
45
+ *
46
+ * Returns the ProfileInstallationResult (BF37) from the last APDU response.
47
+ */
48
+ private sendBppSegments;
49
+ /**
50
+ * Check if BPP response data contains a ProfileInstallationResult (BF37).
51
+ * Returns true if BF37 is detected (caller should stop sending and return the data).
52
+ */
53
+ private isBppResult;
54
+ /**
55
+ * Close the logical channel if open.
56
+ */
57
+ close(): Promise<void>;
58
+ private ensureChannel;
59
+ private openChannel;
60
+ private selectIsdR;
61
+ private closeChannel;
62
+ private closeAllChannels;
63
+ /**
64
+ * GlobalPlatform CLA byte for the current logical channel.
65
+ */
66
+ private gpCla;
67
+ /**
68
+ * Send data via STORE DATA chunking and collect the response via
69
+ * GET RESPONSE chaining.
70
+ */
71
+ private storeDataAndGetResponse;
72
+ }
73
+ //# sourceMappingURL=apdu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apdu.d.ts","sourceRoot":"","sources":["../src/apdu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAmBhD;;;;GAIG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,OAAO,CAAM;gBAET,MAAM,EAAE,aAAa;IAIjC;;;OAGG;IACG,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAKxD;;;;;;;;;;;;;;OAcG;IACG,uBAAuB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAmE/E;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAYxB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAQ3B;;;;;;;OAOG;YACW,eAAe;IAiF7B;;;OAGG;IACH,OAAO,CAAC,WAAW;IAOnB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAWd,aAAa;YAiBb,WAAW;YAYX,UAAU;YAuBV,YAAY;YAKZ,gBAAgB;IAc9B;;OAEG;IACH,OAAO,CAAC,KAAK;IAIb;;;OAGG;YACW,uBAAuB;CAyEtC"}