@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.
- package/LICENSE +21 -0
- package/README.md +479 -0
- package/dist/commands/data.d.ts +29 -0
- package/dist/commands/data.d.ts.map +1 -0
- package/dist/commands/data.js +176 -0
- package/dist/commands/data.js.map +1 -0
- package/dist/commands/fingerprint.d.ts +132 -0
- package/dist/commands/fingerprint.d.ts.map +1 -0
- package/dist/commands/fingerprint.js +580 -0
- package/dist/commands/fingerprint.js.map +1 -0
- package/dist/commands/firmware.d.ts +37 -0
- package/dist/commands/firmware.d.ts.map +1 -0
- package/dist/commands/firmware.js +152 -0
- package/dist/commands/firmware.js.map +1 -0
- package/dist/commands/freescan.d.ts +68 -0
- package/dist/commands/freescan.d.ts.map +1 -0
- package/dist/commands/freescan.js +202 -0
- package/dist/commands/freescan.js.map +1 -0
- package/dist/commands/index.d.ts +88 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +104 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/system.d.ts +82 -0
- package/dist/commands/system.d.ts.map +1 -0
- package/dist/commands/system.js +191 -0
- package/dist/commands/system.js.map +1 -0
- package/dist/constants/crypto.d.ts +11 -0
- package/dist/constants/crypto.d.ts.map +1 -0
- package/dist/constants/crypto.js +10 -0
- package/dist/constants/crypto.js.map +1 -0
- package/dist/constants/index.d.ts +6 -0
- package/dist/constants/index.d.ts.map +1 -0
- package/dist/constants/index.js +4 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/constants/packet.d.ts +17 -0
- package/dist/constants/packet.d.ts.map +1 -0
- package/dist/constants/packet.js +17 -0
- package/dist/constants/packet.js.map +1 -0
- package/dist/constants/protocol.d.ts +653 -0
- package/dist/constants/protocol.d.ts.map +1 -0
- package/dist/constants/protocol.js +422 -0
- package/dist/constants/protocol.js.map +1 -0
- package/dist/core/client.d.ts +118 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +320 -0
- package/dist/core/client.js.map +1 -0
- package/dist/core/crypto.d.ts +86 -0
- package/dist/core/crypto.d.ts.map +1 -0
- package/dist/core/crypto.js +248 -0
- package/dist/core/crypto.js.map +1 -0
- package/dist/core/errors.d.ts +25 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +66 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/index.d.ts +15 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +10 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/packet.d.ts +126 -0
- package/dist/core/packet.d.ts.map +1 -0
- package/dist/core/packet.js +267 -0
- package/dist/core/packet.js.map +1 -0
- package/dist/core/serial.d.ts +123 -0
- package/dist/core/serial.d.ts.map +1 -0
- package/dist/core/serial.js +421 -0
- package/dist/core/serial.js.map +1 -0
- package/dist/core/tcp.d.ts +58 -0
- package/dist/core/tcp.d.ts.map +1 -0
- package/dist/core/tcp.js +219 -0
- package/dist/core/tcp.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- 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
|