@samlab-corp/sfm-node 0.1.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 (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +479 -0
  3. package/dist/commands/data.d.ts +29 -0
  4. package/dist/commands/data.d.ts.map +1 -0
  5. package/dist/commands/data.js +176 -0
  6. package/dist/commands/data.js.map +1 -0
  7. package/dist/commands/fingerprint.d.ts +132 -0
  8. package/dist/commands/fingerprint.d.ts.map +1 -0
  9. package/dist/commands/fingerprint.js +580 -0
  10. package/dist/commands/fingerprint.js.map +1 -0
  11. package/dist/commands/firmware.d.ts +37 -0
  12. package/dist/commands/firmware.d.ts.map +1 -0
  13. package/dist/commands/firmware.js +152 -0
  14. package/dist/commands/firmware.js.map +1 -0
  15. package/dist/commands/freescan.d.ts +68 -0
  16. package/dist/commands/freescan.d.ts.map +1 -0
  17. package/dist/commands/freescan.js +202 -0
  18. package/dist/commands/freescan.js.map +1 -0
  19. package/dist/commands/index.d.ts +88 -0
  20. package/dist/commands/index.d.ts.map +1 -0
  21. package/dist/commands/index.js +104 -0
  22. package/dist/commands/index.js.map +1 -0
  23. package/dist/commands/system.d.ts +82 -0
  24. package/dist/commands/system.d.ts.map +1 -0
  25. package/dist/commands/system.js +191 -0
  26. package/dist/commands/system.js.map +1 -0
  27. package/dist/constants/crypto.d.ts +11 -0
  28. package/dist/constants/crypto.d.ts.map +1 -0
  29. package/dist/constants/crypto.js +10 -0
  30. package/dist/constants/crypto.js.map +1 -0
  31. package/dist/constants/index.d.ts +6 -0
  32. package/dist/constants/index.d.ts.map +1 -0
  33. package/dist/constants/index.js +4 -0
  34. package/dist/constants/index.js.map +1 -0
  35. package/dist/constants/packet.d.ts +17 -0
  36. package/dist/constants/packet.d.ts.map +1 -0
  37. package/dist/constants/packet.js +17 -0
  38. package/dist/constants/packet.js.map +1 -0
  39. package/dist/constants/protocol.d.ts +653 -0
  40. package/dist/constants/protocol.d.ts.map +1 -0
  41. package/dist/constants/protocol.js +422 -0
  42. package/dist/constants/protocol.js.map +1 -0
  43. package/dist/core/client.d.ts +118 -0
  44. package/dist/core/client.d.ts.map +1 -0
  45. package/dist/core/client.js +320 -0
  46. package/dist/core/client.js.map +1 -0
  47. package/dist/core/crypto.d.ts +86 -0
  48. package/dist/core/crypto.d.ts.map +1 -0
  49. package/dist/core/crypto.js +248 -0
  50. package/dist/core/crypto.js.map +1 -0
  51. package/dist/core/errors.d.ts +25 -0
  52. package/dist/core/errors.d.ts.map +1 -0
  53. package/dist/core/errors.js +66 -0
  54. package/dist/core/errors.js.map +1 -0
  55. package/dist/core/index.d.ts +15 -0
  56. package/dist/core/index.d.ts.map +1 -0
  57. package/dist/core/index.js +10 -0
  58. package/dist/core/index.js.map +1 -0
  59. package/dist/core/packet.d.ts +126 -0
  60. package/dist/core/packet.d.ts.map +1 -0
  61. package/dist/core/packet.js +267 -0
  62. package/dist/core/packet.js.map +1 -0
  63. package/dist/core/serial.d.ts +123 -0
  64. package/dist/core/serial.d.ts.map +1 -0
  65. package/dist/core/serial.js +421 -0
  66. package/dist/core/serial.js.map +1 -0
  67. package/dist/core/tcp.d.ts +58 -0
  68. package/dist/core/tcp.d.ts.map +1 -0
  69. package/dist/core/tcp.js +219 -0
  70. package/dist/core/tcp.js.map +1 -0
  71. package/dist/index.d.ts +19 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +16 -0
  74. package/dist/index.js.map +1 -0
  75. package/package.json +49 -0
@@ -0,0 +1,320 @@
1
+ /**
2
+ * SFM Protocol Manual V3.6.0 Serial Communication Client
3
+ *
4
+ * Provides packet protocol, AES256 encryption, multi-packet transfer,
5
+ * and firmware upgrade functionality.
6
+ */
7
+ import { SfmSerial } from "./serial";
8
+ import { SfmCrypto } from "./crypto";
9
+ import { buildNormalPacket, buildNetworkPacket, parseNormalPacket, parseNetworkPacket, packetToHex, isValidStartByte, encodeHexAscii, decodeHexAscii, } from "./packet";
10
+ import { ENCRYPTION_MODE, PACKET_SIZE } from "../constants";
11
+ const MAX_RESPONSE_RETRIES = 10;
12
+ const MAX_TIMEOUT_MS = 60000; // 60s upper bound for calculated timeouts
13
+ /**
14
+ * SFM Unified Client
15
+ */
16
+ export class SfmClient {
17
+ constructor(options = {}) {
18
+ this._onDisconnect = null;
19
+ this.serial = new SfmSerial({
20
+ baudRate: options.baudRate || 115200,
21
+ autoReconnect: options.autoReconnect,
22
+ });
23
+ this.crypto = new SfmCrypto({
24
+ key: options.encryptionKey,
25
+ iv: options.iv,
26
+ secureCode: options.secureCode,
27
+ mode: options.encryptionMode ?? ENCRYPTION_MODE.AES256_CBC,
28
+ });
29
+ this.portPath = options.portPath;
30
+ this.secureMode = options.secureMode ?? false;
31
+ this.debug = options.debug ?? false;
32
+ // Network protocol mode (RS422/485)
33
+ // false = 13-byte normal packet, true = 15-byte network packet
34
+ this.networkMode = options.networkMode ?? false;
35
+ this.terminalId = options.terminalId ?? 0;
36
+ // Hex-ASCII transmission mode
37
+ // Some serial bridges communicate via hex strings instead of binary
38
+ this.hexAsciiMode = options.hexAsciiMode ?? false;
39
+ // Timeout settings
40
+ this.commandTimeout = options.commandTimeout ?? 20000;
41
+ this.readTimeout = options.readTimeout ?? 3000;
42
+ }
43
+ /** Set disconnect callback */
44
+ setOnDisconnect(callback) {
45
+ this._onDisconnect = callback;
46
+ }
47
+ /** Set auto-reconnect options (delegates to serial layer) */
48
+ setAutoReconnect(options) {
49
+ this.serial.setAutoReconnect(options);
50
+ }
51
+ /** Set auto-reconnect callbacks only */
52
+ setAutoReconnectCallbacks(callbacks) {
53
+ this.serial.setAutoReconnectCallbacks(callbacks);
54
+ }
55
+ /** Whether reconnection is in progress */
56
+ get isReconnecting() {
57
+ return this.serial.isReconnecting;
58
+ }
59
+ /**
60
+ * Connect to device
61
+ */
62
+ async connect(portPath) {
63
+ const path = portPath ?? this.portPath;
64
+ if (!path) {
65
+ throw new Error("portPath is required — pass it to connect() or set it in constructor options");
66
+ }
67
+ this.serial.setOnDisconnect((error) => {
68
+ if (this.debug) {
69
+ console.log(`[DEBUG] Disconnect detected: ${error?.message || "port closed"}`);
70
+ }
71
+ this._onDisconnect?.(error);
72
+ });
73
+ await this.serial.open(path);
74
+ if (this.debug) {
75
+ console.log(`[DEBUG] Connected: ${portPath} @ ${this.serial.baudRate} baud`);
76
+ }
77
+ }
78
+ /**
79
+ * Disconnect from device
80
+ */
81
+ async disconnect() {
82
+ this.serial.setOnDisconnect(null); // Prevent callback on intentional disconnect
83
+ await this.serial.close();
84
+ if (this.debug) {
85
+ console.log("[DEBUG] Disconnected");
86
+ }
87
+ }
88
+ /**
89
+ * Send command (SFM Packet Protocol)
90
+ */
91
+ async sendCommand(command, param = 0, size = 0, flag = 0) {
92
+ if (this.secureMode) {
93
+ return this._sendSecureCommand(command, param, size, flag);
94
+ }
95
+ // Auto-switch to network mode (RS422/485)
96
+ if (this.networkMode) {
97
+ return this.sendNetworkCommand(command, param, size, flag);
98
+ }
99
+ const packet = buildNormalPacket(command, param, size, flag);
100
+ if (this.debug) {
101
+ console.log(`[DEBUG] TX: ${packetToHex(packet)}`);
102
+ }
103
+ await this._writePacket(packet);
104
+ // Scan for start byte then receive response (commandTimeout for fingerprint scan wait)
105
+ // Residual responses from previous commands may arrive, so re-receive until a valid command-matching packet arrives
106
+ let retries = 0;
107
+ while (retries < MAX_RESPONSE_RETRIES) {
108
+ const response = await this._readWithStartByteScan(PACKET_SIZE.NORMAL, "normal", this.commandTimeout);
109
+ if (this.debug) {
110
+ console.log(`[DEBUG] RX: ${packetToHex(response)}`);
111
+ }
112
+ const parsed = parseNormalPacket(response);
113
+ // Valid packet with matching command → return
114
+ if (parsed.valid && parsed.command === command) {
115
+ return parsed;
116
+ }
117
+ // Valid but command mismatch → residual response, skip
118
+ if (parsed.valid && parsed.command !== command) {
119
+ if (this.debug) {
120
+ console.log(`[DEBUG] Response command mismatch: expected 0x${command.toString(16)}, got 0x${parsed.command?.toString(16) ?? "??"} → skip`);
121
+ }
122
+ retries++;
123
+ continue;
124
+ }
125
+ // Invalid packet → corrupted residual data, skip and retry
126
+ if (this.debug) {
127
+ console.log(`[DEBUG] Invalid packet skipped (${parsed.error}), retry ${retries + 1}`);
128
+ }
129
+ retries++;
130
+ }
131
+ return { valid: false, error: `Failed to receive valid response (exceeded ${MAX_RESPONSE_RETRIES} retries)` };
132
+ }
133
+ /**
134
+ * Send network command (15-byte packet, RS422/485)
135
+ */
136
+ async sendNetworkCommand(command, param = 0, size = 0, flag = 0) {
137
+ const packet = buildNetworkPacket(command, this.terminalId, param, size, flag);
138
+ if (this.debug) {
139
+ console.log(`[DEBUG] TX(net): ${packetToHex(packet)}`);
140
+ }
141
+ await this._writePacket(packet);
142
+ let retries = 0;
143
+ while (retries < MAX_RESPONSE_RETRIES) {
144
+ const response = await this._readWithStartByteScan(PACKET_SIZE.NETWORK, "network", this.commandTimeout);
145
+ if (this.debug) {
146
+ console.log(`[DEBUG] RX(net): ${packetToHex(response)}`);
147
+ }
148
+ const parsed = parseNetworkPacket(response);
149
+ if (parsed.valid && parsed.command === command) {
150
+ return parsed;
151
+ }
152
+ if (parsed.valid && parsed.command !== command) {
153
+ if (this.debug) {
154
+ console.log(`[DEBUG] Network response command mismatch: expected 0x${command.toString(16)}, got 0x${parsed.command?.toString(16) ?? "??"} → skip`);
155
+ }
156
+ retries++;
157
+ continue;
158
+ }
159
+ if (this.debug) {
160
+ console.log(`[DEBUG] Invalid network packet skipped (${parsed.error}), retry ${retries + 1}`);
161
+ }
162
+ retries++;
163
+ }
164
+ return { valid: false, error: `Failed to receive valid network response (exceeded ${MAX_RESPONSE_RETRIES} retries)` };
165
+ }
166
+ /**
167
+ * Receive response (used in multi-packet protocol)
168
+ */
169
+ async receiveResponse(timeoutMs) {
170
+ const timeout = timeoutMs ?? this.readTimeout;
171
+ if (this.secureMode) {
172
+ const response = await this._readWithStartByteScan(PACKET_SIZE.SECURE, "secure", timeout);
173
+ if (this.debug) {
174
+ console.log(`[DEBUG] RX(secure): ${packetToHex(response)}`);
175
+ }
176
+ return this.crypto.parseSecureRecvPacket(response);
177
+ }
178
+ // Auto-switch to network mode
179
+ if (this.networkMode) {
180
+ const response = await this._readWithStartByteScan(PACKET_SIZE.NETWORK, "network", timeout);
181
+ if (this.debug) {
182
+ console.log(`[DEBUG] RX(net): ${packetToHex(response)}`);
183
+ }
184
+ return parseNetworkPacket(response);
185
+ }
186
+ const response = await this._readWithStartByteScan(PACKET_SIZE.NORMAL, "normal", timeout);
187
+ if (this.debug) {
188
+ console.log(`[DEBUG] RX: ${packetToHex(response)}`);
189
+ }
190
+ return parseNormalPacket(response);
191
+ }
192
+ /**
193
+ * Send secure command (AES256 encrypted, 35-byte packet)
194
+ * @internal
195
+ */
196
+ async _sendSecureCommand(command, param, size, flag) {
197
+ const packet = this.crypto.buildSecureSendPacket(command, param, size, flag);
198
+ if (this.debug) {
199
+ console.log(`[DEBUG] TX(secure): ${packetToHex(packet)}`);
200
+ console.log(`[DEBUG] Encryption mode: ${this.crypto.getModeName()}`);
201
+ }
202
+ await this._writePacket(packet);
203
+ // Receive secure response (35 bytes) - scan for 0x51 marker
204
+ const response = await this._readWithStartByteScan(PACKET_SIZE.SECURE, "secure");
205
+ if (this.debug) {
206
+ console.log(`[DEBUG] RX(secure): ${packetToHex(response)}`);
207
+ }
208
+ return this.crypto.parseSecureRecvPacket(response);
209
+ }
210
+ /**
211
+ * Switch secure mode
212
+ */
213
+ setSecureMode(enabled, options = {}) {
214
+ this.secureMode = enabled;
215
+ if (options.key)
216
+ this.crypto.setKey(options.key);
217
+ if (options.iv)
218
+ this.crypto.setIV(options.iv);
219
+ if (options.secureCode)
220
+ this.crypto.setSecureCode(options.secureCode);
221
+ if (options.mode !== undefined)
222
+ this.crypto.mode = options.mode;
223
+ }
224
+ /**
225
+ * Switch network protocol mode (RS422/485)
226
+ */
227
+ setNetworkMode(enabled, terminalId) {
228
+ this.networkMode = enabled;
229
+ if (terminalId !== undefined) {
230
+ this.terminalId = terminalId;
231
+ }
232
+ if (this.debug) {
233
+ console.log(`[DEBUG] Network mode: ${enabled ? "15-byte" : "13-byte"}${enabled ? `, TermID=${this.terminalId}` : ""}`);
234
+ }
235
+ }
236
+ /**
237
+ * Write packet (supports Hex-ASCII mode)
238
+ * @internal
239
+ */
240
+ async _writePacket(data) {
241
+ if (this.hexAsciiMode) {
242
+ await this.serial.write(encodeHexAscii(data));
243
+ }
244
+ else {
245
+ await this.serial.write(data);
246
+ }
247
+ }
248
+ /**
249
+ * Read packet (supports Hex-ASCII mode)
250
+ * @internal
251
+ */
252
+ async _readPacket(size, timeoutMs) {
253
+ if (this.hexAsciiMode) {
254
+ // Hex-ASCII mode: read 2x size and decode
255
+ const hexData = await this.serial.read(size * 2, timeoutMs);
256
+ return decodeHexAscii(hexData);
257
+ }
258
+ return this.serial.read(size, timeoutMs);
259
+ }
260
+ /**
261
+ * Read packet after scanning for start byte
262
+ * @internal
263
+ */
264
+ async _readWithStartByteScan(packetSize, packetType, timeoutMs) {
265
+ const timeout = timeoutMs ?? this.readTimeout;
266
+ // Read 1 byte at a time to find valid start byte
267
+ const startTime = Date.now();
268
+ let startByte;
269
+ while (Date.now() - startTime < timeout) {
270
+ const remaining = timeout - (Date.now() - startTime);
271
+ if (remaining <= 0)
272
+ break;
273
+ try {
274
+ const byte = await this._readPacket(1, remaining);
275
+ if (isValidStartByte(byte[0], packetType)) {
276
+ startByte = byte[0];
277
+ break;
278
+ }
279
+ if (this.debug) {
280
+ console.log(`[DEBUG] Scan: skipping invalid byte 0x${byte[0].toString(16).toUpperCase()}`);
281
+ }
282
+ }
283
+ catch {
284
+ break; // Timeout
285
+ }
286
+ }
287
+ if (startByte === undefined) {
288
+ throw new Error(`Start byte scan failed (${packetType})`);
289
+ }
290
+ // Read remaining packet
291
+ const remaining = timeout - (Date.now() - startTime);
292
+ if (remaining <= 0) {
293
+ throw new Error(`Timeout expired after start byte scan (${packetType})`);
294
+ }
295
+ const rest = await this._readPacket(packetSize - 1, remaining);
296
+ if (rest.length < packetSize - 1) {
297
+ throw new Error(`Incomplete packet: received ${rest.length + 1}/${packetSize} bytes (${packetType})`);
298
+ }
299
+ // Combine start byte + rest
300
+ const fullPacket = Buffer.alloc(packetSize);
301
+ fullPacket[0] = startByte;
302
+ rest.copy(fullPacket, 1);
303
+ return fullPacket;
304
+ }
305
+ /**
306
+ * Calculate read timeout (based on data size)
307
+ * @internal
308
+ */
309
+ _getReadTimeout(dataSize) {
310
+ return Math.min(MAX_TIMEOUT_MS, Math.max(this.readTimeout, Math.ceil(dataSize / 100) + 1000));
311
+ }
312
+ /**
313
+ * Calculate write timeout (based on data size)
314
+ * @internal
315
+ */
316
+ _getWriteTimeout(dataSize) {
317
+ return Math.min(MAX_TIMEOUT_MS, Math.max(5000, Math.ceil(dataSize / 100) + 1000));
318
+ }
319
+ }
320
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/core/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * SFM Protocol Manual V3.6.0 Secure Packet Protocol
3
+ *
4
+ * Encryption modes:
5
+ * 0x00 = Rijndael256 ECB (legacy)
6
+ * 0x01 = AES256-ECB
7
+ * 0x11 = AES256-CBC
8
+ *
9
+ * Secure packet structure (35 bytes):
10
+ * Byte 0: 0x50 (Send) / 0x51 (Recv) marker
11
+ * Byte 1-32: AES256 encrypted data (32 bytes)
12
+ * Byte 33: Checksum
13
+ * Byte 34: 0x0A (End)
14
+ *
15
+ * Plaintext structure before encryption (32 bytes) - SFM field mapping:
16
+ * Byte offset+0: Command (1B)
17
+ * Byte offset+1-4: Param (4B, LE)
18
+ * Byte offset+5-8: Size (4B, LE)
19
+ * Byte offset+9: Flag (1B)
20
+ * Byte offset+10-17: Secure Code (8B)
21
+ * Byte offset+18-31: Padding
22
+ */
23
+ import type { EncryptionMode } from "../constants";
24
+ export { ENCRYPTION_MODE } from "../constants";
25
+ export type { EncryptionMode } from "../constants";
26
+ export interface SfmCryptoOptions {
27
+ key?: Buffer | string;
28
+ iv?: Buffer | string;
29
+ secureCode?: Buffer | string;
30
+ mode?: EncryptionMode;
31
+ dataOffset?: number;
32
+ }
33
+ export interface SecureRecvPacketResult {
34
+ valid: boolean;
35
+ secureCodeValid?: boolean;
36
+ command?: number;
37
+ param?: number;
38
+ size?: number;
39
+ flag?: number;
40
+ secureCode?: Buffer;
41
+ error: string | null;
42
+ }
43
+ export declare class SfmCrypto {
44
+ key: Buffer;
45
+ iv: Buffer;
46
+ secureCode: Buffer;
47
+ mode: EncryptionMode;
48
+ dataOffset: number;
49
+ sequenceCounter: number;
50
+ private _storedIV;
51
+ constructor(options?: SfmCryptoOptions);
52
+ setKey(key: Buffer | string): void;
53
+ setIV(iv: Buffer | string): void;
54
+ resetIV(): void;
55
+ setSecureCode(code: Buffer | string): void;
56
+ /**
57
+ * AES256 encrypt (32-byte block)
58
+ */
59
+ encrypt(plaintext: Buffer | Uint8Array): Buffer;
60
+ /**
61
+ * AES256 decrypt (32-byte block)
62
+ */
63
+ decrypt(ciphertext: Buffer | Uint8Array): Buffer;
64
+ /**
65
+ * AES256 encrypt (variable size, for multi-packet)
66
+ */
67
+ encryptRaw(data: Buffer | Uint8Array): Buffer;
68
+ /**
69
+ * AES256 decrypt (variable size)
70
+ * @param originalSize - original data size before encryption (to strip padding)
71
+ */
72
+ decryptRaw(data: Buffer | Uint8Array, originalSize?: number): Buffer;
73
+ /**
74
+ * Build secure send packet (SFM field structure)
75
+ *
76
+ * Plaintext (32B):
77
+ * [command(1)] [param LE(4)] [size LE(4)] [flag(1)] [secureCode(8)] [padding]
78
+ */
79
+ buildSecureSendPacket(command: number, param?: number, size?: number, flag?: number): Buffer;
80
+ /**
81
+ * Parse secure receive packet (SFM field structure)
82
+ */
83
+ parseSecureRecvPacket(data: Buffer | Uint8Array): SecureRecvPacketResult;
84
+ getModeName(): string;
85
+ }
86
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/core/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGnD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAQnD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,qBAAa,SAAS;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,cAAc,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IAExB,OAAO,CAAC,SAAS,CAAS;gBAEd,OAAO,GAAE,gBAAqB;IA0B1C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAQlC,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAShC,OAAO,IAAI,IAAI;IAIf,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAQ1C;;OAEG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM;IAgB/C;;OAEG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM;IAoBhD;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM;IAkB7C;;;OAGG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM;IAwBpE;;;;;OAKG;IACH,qBAAqB,CACnB,OAAO,EAAE,MAAM,EACf,KAAK,SAAI,EACT,IAAI,SAAI,EACR,IAAI,SAAI,GACP,MAAM;IAmCT;;OAEG;IACH,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,sBAAsB;IA4DxE,WAAW,IAAI,MAAM;CAYtB"}
@@ -0,0 +1,248 @@
1
+ /**
2
+ * SFM Protocol Manual V3.6.0 Secure Packet Protocol
3
+ *
4
+ * Encryption modes:
5
+ * 0x00 = Rijndael256 ECB (legacy)
6
+ * 0x01 = AES256-ECB
7
+ * 0x11 = AES256-CBC
8
+ *
9
+ * Secure packet structure (35 bytes):
10
+ * Byte 0: 0x50 (Send) / 0x51 (Recv) marker
11
+ * Byte 1-32: AES256 encrypted data (32 bytes)
12
+ * Byte 33: Checksum
13
+ * Byte 34: 0x0A (End)
14
+ *
15
+ * Plaintext structure before encryption (32 bytes) - SFM field mapping:
16
+ * Byte offset+0: Command (1B)
17
+ * Byte offset+1-4: Param (4B, LE)
18
+ * Byte offset+5-8: Size (4B, LE)
19
+ * Byte offset+9: Flag (1B)
20
+ * Byte offset+10-17: Secure Code (8B)
21
+ * Byte offset+18-31: Padding
22
+ */
23
+ import crypto from "node:crypto";
24
+ import { calcChecksum } from "./packet";
25
+ import { ENCRYPTION_MODE } from "../constants";
26
+ // Backward-compatible re-export
27
+ export { ENCRYPTION_MODE } from "../constants";
28
+ const SECURE_PACKET_SIZE = 35;
29
+ const ENCRYPTED_DATA_SIZE = 32;
30
+ const SECURE_CODE_SIZE = 8;
31
+ const AES256_KEY_SIZE = 32;
32
+ const AES_IV_SIZE = 16;
33
+ export class SfmCrypto {
34
+ constructor(options = {}) {
35
+ this.key = options.key
36
+ ? Buffer.isBuffer(options.key)
37
+ ? options.key
38
+ : Buffer.from(options.key, "hex")
39
+ : Buffer.alloc(32);
40
+ this.iv = options.iv
41
+ ? Buffer.isBuffer(options.iv)
42
+ ? options.iv
43
+ : Buffer.from(options.iv, "hex")
44
+ : Buffer.alloc(16);
45
+ this._storedIV = Buffer.from(this.iv);
46
+ this.secureCode = options.secureCode
47
+ ? Buffer.isBuffer(options.secureCode)
48
+ ? options.secureCode
49
+ : Buffer.from(options.secureCode, "hex")
50
+ : Buffer.alloc(SECURE_CODE_SIZE);
51
+ this.mode = options.mode ?? ENCRYPTION_MODE.AES256_CBC;
52
+ this.dataOffset = options.dataOffset ?? 0;
53
+ this.sequenceCounter = 0;
54
+ }
55
+ setKey(key) {
56
+ const buf = Buffer.isBuffer(key) ? key : Buffer.from(key, "hex");
57
+ if (buf.length !== AES256_KEY_SIZE) {
58
+ throw new Error(`Invalid AES256 key length: expected ${AES256_KEY_SIZE}, got ${buf.length}`);
59
+ }
60
+ this.key = buf;
61
+ }
62
+ setIV(iv) {
63
+ const buf = Buffer.isBuffer(iv) ? iv : Buffer.from(iv, "hex");
64
+ if (buf.length !== AES_IV_SIZE) {
65
+ throw new Error(`Invalid AES IV length: expected ${AES_IV_SIZE}, got ${buf.length}`);
66
+ }
67
+ this.iv = buf;
68
+ this._storedIV = Buffer.from(this.iv);
69
+ }
70
+ resetIV() {
71
+ this._storedIV.copy(this.iv);
72
+ }
73
+ setSecureCode(code) {
74
+ const buf = Buffer.isBuffer(code) ? code : Buffer.from(code, "hex");
75
+ if (buf.length !== SECURE_CODE_SIZE) {
76
+ throw new Error(`Invalid Secure Code length: expected ${SECURE_CODE_SIZE}, got ${buf.length}`);
77
+ }
78
+ this.secureCode = buf;
79
+ }
80
+ /**
81
+ * AES256 encrypt (32-byte block)
82
+ */
83
+ encrypt(plaintext) {
84
+ const buf = Buffer.isBuffer(plaintext) ? plaintext : Buffer.from(plaintext);
85
+ const padded = Buffer.alloc(ENCRYPTED_DATA_SIZE);
86
+ buf.copy(padded, 0, 0, Math.min(buf.length, ENCRYPTED_DATA_SIZE));
87
+ if (this.mode === ENCRYPTION_MODE.AES256_CBC) {
88
+ const cipher = crypto.createCipheriv("aes-256-cbc", this.key, this.iv);
89
+ cipher.setAutoPadding(false);
90
+ return Buffer.concat([cipher.update(padded), cipher.final()]);
91
+ }
92
+ const cipher = crypto.createCipheriv("aes-256-ecb", this.key, null);
93
+ cipher.setAutoPadding(false);
94
+ return Buffer.concat([cipher.update(padded), cipher.final()]);
95
+ }
96
+ /**
97
+ * AES256 decrypt (32-byte block)
98
+ */
99
+ decrypt(ciphertext) {
100
+ const buf = Buffer.isBuffer(ciphertext)
101
+ ? ciphertext
102
+ : Buffer.from(ciphertext);
103
+ if (this.mode === ENCRYPTION_MODE.AES256_CBC) {
104
+ const decipher = crypto.createDecipheriv("aes-256-cbc", this.key, this.iv);
105
+ decipher.setAutoPadding(false);
106
+ return Buffer.concat([decipher.update(buf), decipher.final()]);
107
+ }
108
+ const decipher = crypto.createDecipheriv("aes-256-ecb", this.key, null);
109
+ decipher.setAutoPadding(false);
110
+ return Buffer.concat([decipher.update(buf), decipher.final()]);
111
+ }
112
+ /**
113
+ * AES256 encrypt (variable size, for multi-packet)
114
+ */
115
+ encryptRaw(data) {
116
+ const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
117
+ const blockSize = 16;
118
+ const paddedLen = Math.ceil(buf.length / blockSize) * blockSize;
119
+ const padded = Buffer.alloc(paddedLen);
120
+ buf.copy(padded);
121
+ if (this.mode === ENCRYPTION_MODE.AES256_CBC) {
122
+ const cipher = crypto.createCipheriv("aes-256-cbc", this.key, this.iv);
123
+ cipher.setAutoPadding(false);
124
+ return Buffer.concat([cipher.update(padded), cipher.final()]);
125
+ }
126
+ const cipher = crypto.createCipheriv("aes-256-ecb", this.key, null);
127
+ cipher.setAutoPadding(false);
128
+ return Buffer.concat([cipher.update(padded), cipher.final()]);
129
+ }
130
+ /**
131
+ * AES256 decrypt (variable size)
132
+ * @param originalSize - original data size before encryption (to strip padding)
133
+ */
134
+ decryptRaw(data, originalSize) {
135
+ const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
136
+ let decrypted;
137
+ if (this.mode === ENCRYPTION_MODE.AES256_CBC) {
138
+ const decipher = crypto.createDecipheriv("aes-256-cbc", this.key, this.iv);
139
+ decipher.setAutoPadding(false);
140
+ decrypted = Buffer.concat([decipher.update(buf), decipher.final()]);
141
+ }
142
+ else {
143
+ const decipher = crypto.createDecipheriv("aes-256-ecb", this.key, null);
144
+ decipher.setAutoPadding(false);
145
+ decrypted = Buffer.concat([decipher.update(buf), decipher.final()]);
146
+ }
147
+ if (originalSize != null && originalSize < decrypted.length) {
148
+ return decrypted.subarray(0, originalSize);
149
+ }
150
+ return decrypted;
151
+ }
152
+ /**
153
+ * Build secure send packet (SFM field structure)
154
+ *
155
+ * Plaintext (32B):
156
+ * [command(1)] [param LE(4)] [size LE(4)] [flag(1)] [secureCode(8)] [padding]
157
+ */
158
+ buildSecureSendPacket(command, param = 0, size = 0, flag = 0) {
159
+ const plaintext = Buffer.alloc(ENCRYPTED_DATA_SIZE);
160
+ const offset = this.dataOffset;
161
+ if (offset < 0 || offset + 10 + SECURE_CODE_SIZE > ENCRYPTED_DATA_SIZE) {
162
+ throw new Error(`Invalid dataOffset: ${offset} (exceeds ${ENCRYPTED_DATA_SIZE}-byte block)`);
163
+ }
164
+ // SFM field mapping
165
+ plaintext[offset] = command;
166
+ plaintext.writeUInt32LE(param >>> 0, offset + 1);
167
+ plaintext.writeUInt32LE(size >>> 0, offset + 5);
168
+ plaintext[offset + 9] = flag;
169
+ // Insert Secure Code
170
+ this.secureCode.copy(plaintext, offset + 10, 0, SECURE_CODE_SIZE);
171
+ // Reset CBC IV
172
+ if (this.mode === ENCRYPTION_MODE.AES256_CBC) {
173
+ this.resetIV();
174
+ }
175
+ const encrypted = this.encrypt(plaintext);
176
+ // Build secure packet (35 bytes)
177
+ const packet = Buffer.alloc(SECURE_PACKET_SIZE);
178
+ packet[0] = 0x50; // SEND marker
179
+ encrypted.copy(packet, 1, 0, ENCRYPTED_DATA_SIZE);
180
+ packet[33] = calcChecksum(packet, 33);
181
+ packet[34] = 0x0a;
182
+ this.sequenceCounter++;
183
+ return packet;
184
+ }
185
+ /**
186
+ * Parse secure receive packet (SFM field structure)
187
+ */
188
+ parseSecureRecvPacket(data) {
189
+ const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
190
+ if (buf.length < SECURE_PACKET_SIZE) {
191
+ return {
192
+ valid: false,
193
+ error: `Insufficient size: ${buf.length} < ${SECURE_PACKET_SIZE}`,
194
+ };
195
+ }
196
+ const receivedChecksum = buf[33];
197
+ const expectedChecksum = calcChecksum(buf, 33);
198
+ if (receivedChecksum !== expectedChecksum) {
199
+ return {
200
+ valid: false,
201
+ error: `Checksum mismatch: got=0x${receivedChecksum.toString(16)}, expected=0x${expectedChecksum.toString(16)}`,
202
+ };
203
+ }
204
+ if (this.mode === ENCRYPTION_MODE.AES256_CBC) {
205
+ this.resetIV();
206
+ }
207
+ const encrypted = buf.subarray(1, 1 + ENCRYPTED_DATA_SIZE);
208
+ const decrypted = this.decrypt(encrypted);
209
+ const offset = this.dataOffset;
210
+ if (offset < 0 || offset + 10 + SECURE_CODE_SIZE > ENCRYPTED_DATA_SIZE) {
211
+ return { valid: false, error: `Invalid dataOffset: ${offset} (exceeds ${ENCRYPTED_DATA_SIZE}-byte block)` };
212
+ }
213
+ const command = decrypted[offset];
214
+ const param = decrypted.readUInt32LE(offset + 1);
215
+ const size = decrypted.readUInt32LE(offset + 5);
216
+ const flag = decrypted[offset + 9];
217
+ // Secure Code verification
218
+ const codeStart = offset + 10;
219
+ const receivedCode = decrypted.subarray(codeStart, codeStart + SECURE_CODE_SIZE);
220
+ const codeValid = this.secureCode.equals(receivedCode);
221
+ if (codeValid) {
222
+ receivedCode.copy(this.secureCode);
223
+ }
224
+ return {
225
+ valid: codeValid,
226
+ secureCodeValid: codeValid,
227
+ command,
228
+ param,
229
+ size,
230
+ flag,
231
+ secureCode: Buffer.from(receivedCode),
232
+ error: codeValid ? null : "Secure Code mismatch",
233
+ };
234
+ }
235
+ getModeName() {
236
+ switch (this.mode) {
237
+ case ENCRYPTION_MODE.AES256_CBC:
238
+ return "AES256-CBC";
239
+ case ENCRYPTION_MODE.AES256_ECB:
240
+ return "AES256-ECB";
241
+ case ENCRYPTION_MODE.RIJNDAEL_ECB:
242
+ return "Rijndael256-ECB";
243
+ default:
244
+ return `Unknown(0x${this.mode.toString(16)})`;
245
+ }
246
+ }
247
+ }
248
+ //# sourceMappingURL=crypto.js.map