@onekeyfe/hd-transport 1.2.0-alpha.20 → 1.2.0-alpha.22
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/__tests__/messages.test.js +32 -7
- package/__tests__/protocol-v2-link-manager.test.js +27 -34
- package/__tests__/protocol-v2.test.js +242 -56
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +101 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +166 -106
- package/dist/protocols/index.d.ts +5 -2
- package/dist/protocols/index.d.ts.map +1 -1
- package/dist/protocols/v2/decode.d.ts +11 -1
- package/dist/protocols/v2/decode.d.ts.map +1 -1
- package/dist/protocols/v2/errors.d.ts +8 -0
- package/dist/protocols/v2/errors.d.ts.map +1 -0
- package/dist/protocols/v2/frame-assembler.d.ts.map +1 -1
- package/dist/protocols/v2/index.d.ts +1 -0
- package/dist/protocols/v2/index.d.ts.map +1 -1
- package/dist/protocols/v2/link-manager.d.ts +0 -1
- package/dist/protocols/v2/link-manager.d.ts.map +1 -1
- package/dist/protocols/v2/session.d.ts +4 -5
- package/dist/protocols/v2/session.d.ts.map +1 -1
- package/dist/types/messages.d.ts +34 -5
- package/dist/types/messages.d.ts.map +1 -1
- package/messages-protocol-v2.json +77 -4
- package/package.json +2 -2
- package/scripts/protobuf-types.js +7 -1
- package/src/constants.ts +8 -0
- package/src/protocols/index.ts +18 -4
- package/src/protocols/v2/decode.ts +49 -19
- package/src/protocols/v2/errors.ts +23 -0
- package/src/protocols/v2/frame-assembler.ts +6 -4
- package/src/protocols/v2/index.ts +1 -0
- package/src/protocols/v2/link-manager.ts +0 -2
- package/src/protocols/v2/session.ts +124 -122
- package/src/types/messages.ts +46 -6
|
@@ -101,17 +101,44 @@ describe('messages', () => {
|
|
|
101
101
|
expect(v2Messages.nested.MessageType.values).toMatchObject({
|
|
102
102
|
MessageType_DeviceStatusGet: 60602,
|
|
103
103
|
MessageType_DeviceStatus: 60603,
|
|
104
|
-
MessageType_DeviceSessionGet: 60606,
|
|
105
104
|
MessageType_DeviceSession: 60607,
|
|
106
105
|
MessageType_DeviceSessionAskPin: 60608,
|
|
106
|
+
MessageType_DeviceSessionOpen: 60609,
|
|
107
107
|
});
|
|
108
108
|
expect(v2Messages.nested.DeviceStatusGet).toEqual({ fields: {} });
|
|
109
|
-
expect(v2Messages.nested.
|
|
109
|
+
expect(v2Messages.nested.ProtocolInfoRequest.fields.eventless_wallet_session).toMatchObject({
|
|
110
|
+
id: 1,
|
|
111
|
+
type: 'bool',
|
|
112
|
+
options: { default: false },
|
|
113
|
+
});
|
|
114
|
+
expect(v2Messages.nested).not.toHaveProperty('DeviceSessionGet');
|
|
115
|
+
expect(v2Messages.nested.DeviceSessionResume.fields.session_id).toMatchObject({
|
|
116
|
+
rule: 'required',
|
|
110
117
|
id: 1,
|
|
111
118
|
type: 'bytes',
|
|
112
119
|
});
|
|
113
|
-
expect(v2Messages.nested).
|
|
114
|
-
|
|
120
|
+
expect(v2Messages.nested.DeviceSessionOpen.fields).toMatchObject({
|
|
121
|
+
resume: { id: 1, type: 'DeviceSessionResume' },
|
|
122
|
+
select: { id: 2, type: 'DeviceSessionSelect' },
|
|
123
|
+
});
|
|
124
|
+
expect(v2Messages.nested.DeviceSessionSelect.fields).toMatchObject({
|
|
125
|
+
host_passphrase: { id: 1, type: 'DeviceSessionHostPassphrase' },
|
|
126
|
+
passphrase_on_device: { id: 2, type: 'DeviceSessionPassphraseOnDevice' },
|
|
127
|
+
attach_pin_on_device: { id: 3, type: 'DeviceSessionAttachPinOnDevice' },
|
|
128
|
+
});
|
|
129
|
+
expect(v2Messages.nested.DeviceSessionHostPassphrase.fields.passphrase).toMatchObject({
|
|
130
|
+
rule: 'required',
|
|
131
|
+
id: 1,
|
|
132
|
+
type: 'string',
|
|
133
|
+
});
|
|
134
|
+
expect(v2Messages.nested.DeviceSessionErrorCode.values).toEqual({
|
|
135
|
+
DeviceSessionError_None: 0,
|
|
136
|
+
DeviceSessionError_UserCancelled: 1,
|
|
137
|
+
DeviceSessionError_InvalidSession: 2,
|
|
138
|
+
DeviceSessionError_AttachPinUnavailable: 3,
|
|
139
|
+
DeviceSessionError_PassphraseDisabled: 4,
|
|
140
|
+
DeviceSessionError_Busy: 5,
|
|
141
|
+
});
|
|
115
142
|
expect(v2Messages.nested).not.toHaveProperty('DeviceWalletSelect');
|
|
116
143
|
expect(v2Messages.nested).not.toHaveProperty('DeviceWalletType');
|
|
117
144
|
expect(v2Messages.nested).not.toHaveProperty('DeviceHiddenWalletSelect');
|
|
@@ -126,9 +153,7 @@ describe('messages', () => {
|
|
|
126
153
|
expect(v2Messages.nested.MessageType.values).not.toHaveProperty(
|
|
127
154
|
'MessageType_DeviceSessionPinResult'
|
|
128
155
|
);
|
|
129
|
-
expect(v2Messages.nested.MessageType.values).not.toHaveProperty(
|
|
130
|
-
'MessageType_DeviceSessionOpen'
|
|
131
|
-
);
|
|
156
|
+
expect(v2Messages.nested.MessageType.values).not.toHaveProperty('MessageType_DeviceSessionGet');
|
|
132
157
|
});
|
|
133
158
|
|
|
134
159
|
test('Protocol V2 onboarding status matches the current firmware-pro2 schema', () => {
|
|
@@ -55,7 +55,12 @@ const rewriteSeq = (frame, seq) => {
|
|
|
55
55
|
const createAdapterFactory = sentSeqs => {
|
|
56
56
|
const adapters = [];
|
|
57
57
|
let generation = 0;
|
|
58
|
-
const success = ProtocolV2.encodeFrame(
|
|
58
|
+
const success = ProtocolV2.encodeFrame(
|
|
59
|
+
schemas,
|
|
60
|
+
'Success',
|
|
61
|
+
{ message: 'ok' },
|
|
62
|
+
{ router: 1, packetSrc: 0, seq: 1 }
|
|
63
|
+
);
|
|
59
64
|
|
|
60
65
|
return {
|
|
61
66
|
adapters,
|
|
@@ -108,12 +113,15 @@ describe('ProtocolV2LinkManager', () => {
|
|
|
108
113
|
|
|
109
114
|
await manager.call('device-a', createAdapter, 'Ping', { message: '1' }, { timeoutMs: 123 });
|
|
110
115
|
|
|
111
|
-
expect(adapters[0].prepareCall).toHaveBeenCalledWith(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
expect(adapters[0].prepareCall).toHaveBeenCalledWith(
|
|
117
|
+
expect.objectContaining({
|
|
118
|
+
messageName: 'Ping',
|
|
119
|
+
timeoutMs: 123,
|
|
120
|
+
highVolume: false,
|
|
121
|
+
generation: 1,
|
|
122
|
+
signal: expect.any(AbortSignal),
|
|
123
|
+
})
|
|
124
|
+
);
|
|
117
125
|
});
|
|
118
126
|
|
|
119
127
|
test('invalidates a link-fatal call before the next call creates a new link', async () => {
|
|
@@ -158,31 +166,6 @@ describe('ProtocolV2LinkManager', () => {
|
|
|
158
166
|
expect(sentSeqs).toEqual([1, 2]);
|
|
159
167
|
});
|
|
160
168
|
|
|
161
|
-
test('times out a stalled write and resets the link before releasing the call queue', async () => {
|
|
162
|
-
const adapter = {
|
|
163
|
-
router: 1,
|
|
164
|
-
generation: 1,
|
|
165
|
-
prepareCall: jest.fn(),
|
|
166
|
-
writeFrame: jest.fn(() => new Promise(() => {})),
|
|
167
|
-
readFrame: jest.fn(),
|
|
168
|
-
reset: jest.fn(() => Promise.resolve()),
|
|
169
|
-
writeTimeoutMs: 10,
|
|
170
|
-
};
|
|
171
|
-
const manager = new ProtocolV2LinkManager({
|
|
172
|
-
getSchemas: () => schemas,
|
|
173
|
-
classifyError: error =>
|
|
174
|
-
String(error).includes('write timeout') ? 'link-fatal' : 'recoverable',
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
await expect(
|
|
178
|
-
manager.call('device-a', () => adapter, 'Ping', { message: 'pending' })
|
|
179
|
-
).rejects.toThrow('Protocol V2 write timeout after 10ms for Ping');
|
|
180
|
-
|
|
181
|
-
expect(adapter.reset).toHaveBeenCalledWith(expect.stringContaining('write timeout'));
|
|
182
|
-
expect(adapter.readFrame).not.toHaveBeenCalled();
|
|
183
|
-
expect(manager.callQueues.size).toBe(0);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
169
|
test('invalidates every active link while retaining per-device cursors', async () => {
|
|
187
170
|
const sentSeqs = [];
|
|
188
171
|
const { adapters, createAdapter } = createAdapterFactory(sentSeqs);
|
|
@@ -225,7 +208,12 @@ describe('ProtocolV2LinkManager', () => {
|
|
|
225
208
|
const deviceABlocked = new Promise(resolve => {
|
|
226
209
|
releaseDeviceA = resolve;
|
|
227
210
|
});
|
|
228
|
-
const success = ProtocolV2.encodeFrame(
|
|
211
|
+
const success = ProtocolV2.encodeFrame(
|
|
212
|
+
schemas,
|
|
213
|
+
'Success',
|
|
214
|
+
{ message: 'ok' },
|
|
215
|
+
{ router: 1, packetSrc: 0, seq: 1 }
|
|
216
|
+
);
|
|
229
217
|
const createAdapter = key => {
|
|
230
218
|
let requestSeq = 0;
|
|
231
219
|
return {
|
|
@@ -323,7 +311,12 @@ describe('ProtocolV2LinkManager', () => {
|
|
|
323
311
|
const writeBlocked = new Promise(resolve => {
|
|
324
312
|
releaseWrite = resolve;
|
|
325
313
|
});
|
|
326
|
-
const success = ProtocolV2.encodeFrame(
|
|
314
|
+
const success = ProtocolV2.encodeFrame(
|
|
315
|
+
schemas,
|
|
316
|
+
'Success',
|
|
317
|
+
{ message: 'ok' },
|
|
318
|
+
{ router: 1, packetSrc: 0, seq: 1 }
|
|
319
|
+
);
|
|
327
320
|
const adapter = {
|
|
328
321
|
router: 1,
|
|
329
322
|
generation: 1,
|
|
@@ -2,13 +2,17 @@ const { ProtocolV2 } = require('../src/protocols');
|
|
|
2
2
|
const { parseConfigure } = require('../src/serialization/protobuf/messages');
|
|
3
3
|
const {
|
|
4
4
|
ProtocolV2FrameAssembler,
|
|
5
|
+
ProtocolV2LinkError,
|
|
5
6
|
ProtocolV2SequenceCursor,
|
|
6
7
|
ProtocolV2Session,
|
|
7
8
|
hexToBytes,
|
|
8
9
|
probeProtocolV2,
|
|
9
10
|
} = require('../src/protocols/v2/session');
|
|
10
11
|
const protocolV2 = require('../src/protocols/v2');
|
|
11
|
-
const {
|
|
12
|
+
const {
|
|
13
|
+
PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS,
|
|
14
|
+
PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
15
|
+
} = require('../src/constants');
|
|
12
16
|
|
|
13
17
|
const protocolV1Messages = parseConfigure({
|
|
14
18
|
nested: {
|
|
@@ -162,6 +166,18 @@ const protocolV2Messages = parseConfigure({
|
|
|
162
166
|
},
|
|
163
167
|
},
|
|
164
168
|
},
|
|
169
|
+
DeviceSession: {
|
|
170
|
+
fields: {
|
|
171
|
+
session_id: {
|
|
172
|
+
type: 'bytes',
|
|
173
|
+
id: 1,
|
|
174
|
+
},
|
|
175
|
+
btc_test_address: {
|
|
176
|
+
type: 'string',
|
|
177
|
+
id: 2,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
165
181
|
FileWrite: {
|
|
166
182
|
fields: {},
|
|
167
183
|
},
|
|
@@ -195,6 +211,7 @@ const protocolV2Messages = parseConfigure({
|
|
|
195
211
|
MessageType_FileWrite: 60805,
|
|
196
212
|
MessageType_DeviceFirmwareUpdateRequest: 61000,
|
|
197
213
|
MessageType_DeviceFirmwareUpdateStatus: 61002,
|
|
214
|
+
MessageType_DeviceSession: 60607,
|
|
198
215
|
MessageType_PartialNested: 62000,
|
|
199
216
|
},
|
|
200
217
|
},
|
|
@@ -208,6 +225,7 @@ const schemas = {
|
|
|
208
225
|
|
|
209
226
|
const rewriteSeq = (frame, seq) => {
|
|
210
227
|
const copy = new Uint8Array(frame);
|
|
228
|
+
copy[4] = 1;
|
|
211
229
|
copy[6] = seq;
|
|
212
230
|
copy[copy.length - 1] = protocolV2.crc8(copy, copy.length - 1);
|
|
213
231
|
return copy;
|
|
@@ -259,6 +277,34 @@ describe('Protocol V2 framing and session', () => {
|
|
|
259
277
|
expect(decoded.message).toEqual({ message: 'ok' });
|
|
260
278
|
});
|
|
261
279
|
|
|
280
|
+
test('round-trips a DeviceSession response with a 32-byte session id', () => {
|
|
281
|
+
const sessionId = '01'.repeat(32);
|
|
282
|
+
const frame = ProtocolV2.encodeFrame(schemas, 'DeviceSession', {
|
|
283
|
+
session_id: sessionId,
|
|
284
|
+
btc_test_address: 'tb1qwallet',
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const decoded = ProtocolV2.decodeFrame(schemas, frame);
|
|
288
|
+
|
|
289
|
+
expect(decoded.type).toBe('DeviceSession');
|
|
290
|
+
expect(decoded.message).toEqual({
|
|
291
|
+
session_id: sessionId,
|
|
292
|
+
btc_test_address: 'tb1qwallet',
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test('reports malformed DeviceSession protobuf as a schema-compatible transport error', () => {
|
|
297
|
+
const malformedPayload = new Uint8Array(32);
|
|
298
|
+
malformedPayload[0] = 0x0a;
|
|
299
|
+
malformedPayload[1] = 101;
|
|
300
|
+
const frame = protocolV2.encodeProtobufFrame(60607, malformedPayload);
|
|
301
|
+
|
|
302
|
+
expect(() => ProtocolV2.decodeFrame(schemas, frame)).toThrow(
|
|
303
|
+
'Protocol V2 protobuf decode failed for "DeviceSession" (60607, 32-byte payload); ' +
|
|
304
|
+
'the payload is malformed or incompatible with the active SDK schema.'
|
|
305
|
+
);
|
|
306
|
+
});
|
|
307
|
+
|
|
262
308
|
test('decodes missing optional nested messages as null', () => {
|
|
263
309
|
const frame = ProtocolV2.encodeFrame(schemas, 'PartialNested', {
|
|
264
310
|
label: 'only label',
|
|
@@ -388,7 +434,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
388
434
|
logger,
|
|
389
435
|
logPrefix: 'ProtocolV2 Test',
|
|
390
436
|
writeFrame: () => Promise.resolve(),
|
|
391
|
-
readFrame: () => Promise.resolve(response),
|
|
437
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
392
438
|
});
|
|
393
439
|
|
|
394
440
|
await expect(session.call('Ping', { message: 'hello' })).rejects.toThrow();
|
|
@@ -410,7 +456,10 @@ describe('Protocol V2 framing and session', () => {
|
|
|
410
456
|
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
411
457
|
message: 'ok',
|
|
412
458
|
});
|
|
413
|
-
const readFrame = jest
|
|
459
|
+
const readFrame = jest
|
|
460
|
+
.fn()
|
|
461
|
+
.mockResolvedValueOnce(ack)
|
|
462
|
+
.mockResolvedValueOnce(rewriteSeq(response, 1));
|
|
414
463
|
const session = new ProtocolV2Session({
|
|
415
464
|
schemas,
|
|
416
465
|
router: 1,
|
|
@@ -427,56 +476,93 @@ describe('Protocol V2 framing and session', () => {
|
|
|
427
476
|
expect(readFrame).toHaveBeenCalledTimes(2);
|
|
428
477
|
});
|
|
429
478
|
|
|
430
|
-
test('session
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
.
|
|
479
|
+
test('session accepts a terminal response before an ACK', async () => {
|
|
480
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
481
|
+
message: 'ok',
|
|
482
|
+
});
|
|
483
|
+
const session = new ProtocolV2Session({
|
|
484
|
+
schemas,
|
|
485
|
+
router: 1,
|
|
486
|
+
writeFrame: () => Promise.resolve(),
|
|
487
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
await expect(
|
|
491
|
+
session.call('Ping', { message: 'hello' }, { expectedTypes: ['Success'] })
|
|
492
|
+
).resolves.toEqual({
|
|
493
|
+
type: 'Success',
|
|
494
|
+
message: {
|
|
495
|
+
message: 'ok',
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
test('session uses the unified response timeout before the first ACK', async () => {
|
|
501
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
502
|
+
message: 'ok',
|
|
503
|
+
});
|
|
504
|
+
const readFrame = jest.fn(() => Promise.resolve(rewriteSeq(response, 1)));
|
|
443
505
|
const session = new ProtocolV2Session({
|
|
444
506
|
schemas,
|
|
445
507
|
router: 1,
|
|
446
|
-
deliveryTimeoutMs: 10,
|
|
447
508
|
writeFrame: () => Promise.resolve(),
|
|
448
509
|
readFrame,
|
|
449
510
|
});
|
|
450
511
|
|
|
451
|
-
await expect(
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
512
|
+
await expect(
|
|
513
|
+
session.call(
|
|
514
|
+
'Ping',
|
|
515
|
+
{ message: 'hello' },
|
|
516
|
+
{
|
|
517
|
+
timeoutMs: 12_345,
|
|
518
|
+
expectedTypes: ['Success'],
|
|
519
|
+
}
|
|
520
|
+
)
|
|
521
|
+
).resolves.toEqual({
|
|
522
|
+
type: 'Success',
|
|
523
|
+
message: {
|
|
524
|
+
message: 'ok',
|
|
525
|
+
},
|
|
526
|
+
});
|
|
527
|
+
expect(readFrame).toHaveBeenCalledWith(expect.objectContaining({ timeoutMs: 12_345 }));
|
|
455
528
|
});
|
|
456
529
|
|
|
457
|
-
test('session
|
|
530
|
+
test('session rejects an ACK that belongs to another request sequence', async () => {
|
|
458
531
|
const ack = new Uint8Array(8);
|
|
459
532
|
ack[0] = 0x5a;
|
|
460
533
|
ack[1] = 8;
|
|
461
534
|
ack[4] = 1;
|
|
462
535
|
ack[5] = 1;
|
|
463
|
-
ack[6] =
|
|
536
|
+
ack[6] = 2;
|
|
464
537
|
ack[3] = protocolV2.crc8(ack, 3);
|
|
465
538
|
ack[7] = protocolV2.crc8(ack, 7);
|
|
466
|
-
const readFrame = jest
|
|
467
|
-
.fn()
|
|
468
|
-
.mockResolvedValueOnce(ack)
|
|
469
|
-
.mockImplementation(() => new Promise(() => {}));
|
|
470
539
|
const session = new ProtocolV2Session({
|
|
471
540
|
schemas,
|
|
472
541
|
router: 1,
|
|
473
|
-
deliveryTimeoutMs: 10,
|
|
474
542
|
writeFrame: () => Promise.resolve(),
|
|
475
|
-
readFrame,
|
|
543
|
+
readFrame: () => Promise.resolve(ack),
|
|
476
544
|
});
|
|
477
545
|
|
|
478
|
-
await expect(session.call('Ping', { message: 'hello' }
|
|
479
|
-
|
|
546
|
+
await expect(session.call('Ping', { message: 'hello' })).rejects.toEqual(
|
|
547
|
+
expect.objectContaining({
|
|
548
|
+
name: 'ProtocolV2LinkError',
|
|
549
|
+
code: 'ack-sequence',
|
|
550
|
+
message: 'Protocol V2 ACK sequence mismatch: expected 1, got 2',
|
|
551
|
+
})
|
|
552
|
+
);
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
test('session rejects a response from another routing channel', async () => {
|
|
556
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'wrong route' });
|
|
557
|
+
const session = new ProtocolV2Session({
|
|
558
|
+
schemas,
|
|
559
|
+
router: 1,
|
|
560
|
+
writeFrame: () => Promise.resolve(),
|
|
561
|
+
readFrame: () => Promise.resolve(response),
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
await expect(session.call('Ping', { message: 'hello' })).rejects.toBeInstanceOf(
|
|
565
|
+
ProtocolV2LinkError
|
|
480
566
|
);
|
|
481
567
|
});
|
|
482
568
|
|
|
@@ -490,7 +576,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
490
576
|
router: 1,
|
|
491
577
|
maxFrameBytes: 2048,
|
|
492
578
|
writeFrame,
|
|
493
|
-
readFrame: () => Promise.resolve(response),
|
|
579
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
494
580
|
});
|
|
495
581
|
|
|
496
582
|
await expect(session.call('Ping', { message: 'x'.repeat(2048) })).rejects.toThrow(
|
|
@@ -499,28 +585,40 @@ describe('Protocol V2 framing and session', () => {
|
|
|
499
585
|
expect(writeFrame).not.toHaveBeenCalled();
|
|
500
586
|
});
|
|
501
587
|
|
|
502
|
-
test('session
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
});
|
|
588
|
+
test('session watchdog covers a write that never settles', async () => {
|
|
589
|
+
const readFrame = jest.fn();
|
|
590
|
+
let writeSignal;
|
|
506
591
|
const session = new ProtocolV2Session({
|
|
507
592
|
schemas,
|
|
508
593
|
router: 1,
|
|
509
|
-
writeFrame: () =>
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
readFrame
|
|
594
|
+
writeFrame: (_frame, context) => {
|
|
595
|
+
writeSignal = context.signal;
|
|
596
|
+
return new Promise(() => {});
|
|
597
|
+
},
|
|
598
|
+
readFrame,
|
|
514
599
|
});
|
|
515
600
|
|
|
516
601
|
await expect(
|
|
517
602
|
session.call('Ping', { message: 'hello' }, { timeoutMs: 10, expectedTypes: ['Success'] })
|
|
518
|
-
).
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
603
|
+
).rejects.toThrow('Protocol V2 response timeout after 10ms for Ping');
|
|
604
|
+
expect(writeSignal.aborted).toBe(true);
|
|
605
|
+
expect(readFrame).not.toHaveBeenCalled();
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
test('session watchdog covers prepareCall before writing', async () => {
|
|
609
|
+
const writeFrame = jest.fn();
|
|
610
|
+
const session = new ProtocolV2Session({
|
|
611
|
+
schemas,
|
|
612
|
+
router: 1,
|
|
613
|
+
prepareCall: () => new Promise(() => {}),
|
|
614
|
+
writeFrame,
|
|
615
|
+
readFrame: jest.fn(),
|
|
523
616
|
});
|
|
617
|
+
|
|
618
|
+
await expect(session.call('Ping', { message: 'hello' }, { timeoutMs: 10 })).rejects.toThrow(
|
|
619
|
+
'Protocol V2 response timeout after 10ms for Ping'
|
|
620
|
+
);
|
|
621
|
+
expect(writeFrame).not.toHaveBeenCalled();
|
|
524
622
|
});
|
|
525
623
|
|
|
526
624
|
test('session logs a device-owned response seq without rejecting the frame', async () => {
|
|
@@ -550,6 +648,41 @@ describe('Protocol V2 framing and session', () => {
|
|
|
550
648
|
expect(logger.debug).not.toHaveBeenCalled();
|
|
551
649
|
});
|
|
552
650
|
|
|
651
|
+
test('session accepts gaps in the firmware-global response sequence', async () => {
|
|
652
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' });
|
|
653
|
+
const readFrame = jest
|
|
654
|
+
.fn()
|
|
655
|
+
.mockResolvedValueOnce(rewriteSeq(response, 1))
|
|
656
|
+
.mockResolvedValueOnce(rewriteSeq(response, 4));
|
|
657
|
+
const session = new ProtocolV2Session({
|
|
658
|
+
schemas,
|
|
659
|
+
router: 1,
|
|
660
|
+
writeFrame: () => Promise.resolve(),
|
|
661
|
+
readFrame,
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
await session.call('Ping', { message: 'first' });
|
|
665
|
+
await expect(session.call('Ping', { message: 'second' })).resolves.toMatchObject({
|
|
666
|
+
type: 'Success',
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
test('session rejects a duplicate device response sequence', async () => {
|
|
671
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' });
|
|
672
|
+
const readFrame = jest.fn(() => Promise.resolve(rewriteSeq(response, 7)));
|
|
673
|
+
const session = new ProtocolV2Session({
|
|
674
|
+
schemas,
|
|
675
|
+
router: 1,
|
|
676
|
+
writeFrame: () => Promise.resolve(),
|
|
677
|
+
readFrame,
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
await session.call('Ping', { message: 'first' });
|
|
681
|
+
await expect(session.call('Ping', { message: 'second' })).rejects.toThrow(
|
|
682
|
+
'Protocol V2 duplicate response sequence: 7'
|
|
683
|
+
);
|
|
684
|
+
});
|
|
685
|
+
|
|
553
686
|
test('session does not log transmit or receive payload details', async () => {
|
|
554
687
|
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
555
688
|
message: 'accepted',
|
|
@@ -561,7 +694,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
561
694
|
schemas,
|
|
562
695
|
router: 1,
|
|
563
696
|
writeFrame: () => Promise.resolve(),
|
|
564
|
-
readFrame: () => Promise.resolve(response),
|
|
697
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
565
698
|
logger,
|
|
566
699
|
logPrefix: 'ProtocolV2 Test',
|
|
567
700
|
});
|
|
@@ -587,7 +720,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
587
720
|
schemas,
|
|
588
721
|
router: 1,
|
|
589
722
|
writeFrame: () => Promise.resolve(),
|
|
590
|
-
readFrame: () => Promise.resolve(response),
|
|
723
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
591
724
|
logger,
|
|
592
725
|
});
|
|
593
726
|
|
|
@@ -612,7 +745,10 @@ describe('Protocol V2 framing and session', () => {
|
|
|
612
745
|
const logger = {
|
|
613
746
|
debug: jest.fn(),
|
|
614
747
|
};
|
|
615
|
-
const readFrame = jest
|
|
748
|
+
const readFrame = jest
|
|
749
|
+
.fn()
|
|
750
|
+
.mockResolvedValueOnce(rewriteSeq(stale, 1))
|
|
751
|
+
.mockResolvedValueOnce(rewriteSeq(response, 2));
|
|
616
752
|
const session = new ProtocolV2Session({
|
|
617
753
|
schemas,
|
|
618
754
|
router: 1,
|
|
@@ -649,7 +785,9 @@ describe('Protocol V2 framing and session', () => {
|
|
|
649
785
|
const [writtenFrame] = written;
|
|
650
786
|
const { seq } = protocolV2.decodeFrame(writtenFrame);
|
|
651
787
|
return Promise.resolve(
|
|
652
|
-
readFrame.mock.calls.length === 1
|
|
788
|
+
readFrame.mock.calls.length === 1
|
|
789
|
+
? rewriteSeq(progress, seq)
|
|
790
|
+
: rewriteSeq(success, protocolV2.nextProtoSeq(seq))
|
|
653
791
|
);
|
|
654
792
|
});
|
|
655
793
|
const session = new ProtocolV2Session({
|
|
@@ -826,7 +964,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
826
964
|
}
|
|
827
965
|
return Promise.resolve();
|
|
828
966
|
},
|
|
829
|
-
readFrame: () => Promise.resolve(response),
|
|
967
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
830
968
|
});
|
|
831
969
|
|
|
832
970
|
await expect(session.call('Ping', { message: '1' })).rejects.toThrow('transport write failed');
|
|
@@ -895,6 +1033,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
895
1033
|
const writeContexts = [];
|
|
896
1034
|
const readContexts = [];
|
|
897
1035
|
const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' });
|
|
1036
|
+
let responseSeq = 0;
|
|
898
1037
|
const session = new ProtocolV2Session({
|
|
899
1038
|
schemas,
|
|
900
1039
|
router: 1,
|
|
@@ -905,18 +1044,65 @@ describe('Protocol V2 framing and session', () => {
|
|
|
905
1044
|
},
|
|
906
1045
|
readFrame: context => {
|
|
907
1046
|
readContexts.push(context);
|
|
908
|
-
|
|
1047
|
+
responseSeq = protocolV2.nextProtoSeq(responseSeq);
|
|
1048
|
+
return Promise.resolve(rewriteSeq(response, responseSeq));
|
|
909
1049
|
},
|
|
910
1050
|
});
|
|
911
1051
|
|
|
912
1052
|
await session.call('Ping', { message: 'ping' }, { timeoutMs: 123 });
|
|
913
1053
|
await session.call('FileWrite', {}, { timeoutMs: 456 });
|
|
1054
|
+
await session.call('Ping', { message: 'default-timeout' });
|
|
914
1055
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1056
|
+
const normalizeContext = ({ signal, ...context }) => ({
|
|
1057
|
+
...context,
|
|
1058
|
+
signalAborted: signal.aborted,
|
|
1059
|
+
});
|
|
1060
|
+
expect(writeContexts.map(normalizeContext)).toEqual([
|
|
1061
|
+
{
|
|
1062
|
+
messageName: 'Ping',
|
|
1063
|
+
timeoutMs: 123,
|
|
1064
|
+
highVolume: false,
|
|
1065
|
+
generation: 7,
|
|
1066
|
+
signalAborted: false,
|
|
1067
|
+
},
|
|
1068
|
+
{
|
|
1069
|
+
messageName: 'FileWrite',
|
|
1070
|
+
timeoutMs: 456,
|
|
1071
|
+
highVolume: true,
|
|
1072
|
+
generation: 7,
|
|
1073
|
+
signalAborted: false,
|
|
1074
|
+
},
|
|
1075
|
+
{
|
|
1076
|
+
messageName: 'Ping',
|
|
1077
|
+
timeoutMs: PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS,
|
|
1078
|
+
highVolume: false,
|
|
1079
|
+
generation: 7,
|
|
1080
|
+
signalAborted: false,
|
|
1081
|
+
},
|
|
1082
|
+
]);
|
|
1083
|
+
expect(readContexts.map(normalizeContext)).toEqual([
|
|
1084
|
+
{
|
|
1085
|
+
messageName: 'Ping',
|
|
1086
|
+
timeoutMs: 123,
|
|
1087
|
+
highVolume: false,
|
|
1088
|
+
generation: 7,
|
|
1089
|
+
signalAborted: false,
|
|
1090
|
+
},
|
|
1091
|
+
{
|
|
1092
|
+
messageName: 'FileWrite',
|
|
1093
|
+
timeoutMs: 456,
|
|
1094
|
+
highVolume: true,
|
|
1095
|
+
generation: 7,
|
|
1096
|
+
signalAborted: false,
|
|
1097
|
+
},
|
|
1098
|
+
{
|
|
1099
|
+
messageName: 'Ping',
|
|
1100
|
+
timeoutMs: PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS,
|
|
1101
|
+
highVolume: false,
|
|
1102
|
+
generation: 7,
|
|
1103
|
+
signalAborted: false,
|
|
1104
|
+
},
|
|
918
1105
|
]);
|
|
919
|
-
expect(readContexts).toEqual(writeContexts);
|
|
920
1106
|
});
|
|
921
1107
|
|
|
922
1108
|
test('assembler throws and resets on frames with an impossible length field', () => {
|
package/dist/constants.d.ts
CHANGED
|
@@ -14,4 +14,5 @@ export declare const PROTOCOL_V2_CHANNEL_USB = 0;
|
|
|
14
14
|
export declare const PROTOCOL_V2_CHANNEL_BLE_UART = 1;
|
|
15
15
|
export declare const PROTOCOL_V2_CHANNEL_SOCKET = 2;
|
|
16
16
|
export declare const PROTOCOL_V2_PACKET_SRC_COMMAND = 0;
|
|
17
|
+
export declare const PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS: number;
|
|
17
18
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,qBAAqB,KAAO,CAAC;AAG1C,eAAO,MAAM,uBAAuB,KAAO,CAAC;AAG5C,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAGjD,eAAO,MAAM,2BAA2B,QAAqC,CAAC;AAG9E,eAAO,MAAM,+BAA+B,QAAQ,CAAC;AAGrD,eAAO,MAAM,gCAAgC,QAA0C,CAAC;AAKxF,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAGhD,eAAO,MAAM,kCAAkC,OAAO,CAAC;AAGvD,eAAO,MAAM,+BAA+B,OAAO,CAAC;AAGpD,eAAO,MAAM,oCAAoC,MAAM,CAAC;AAGxD,eAAO,MAAM,+BAA+B,OAAO,CAAC;AAGpD,eAAO,MAAM,2BAA2B,OAAqC,CAAC;AAM9E,eAAO,MAAM,uBAAuB,IAAI,CAAC;AACzC,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAC9C,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAG5C,eAAO,MAAM,8BAA8B,IAAI,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,qBAAqB,KAAO,CAAC;AAG1C,eAAO,MAAM,uBAAuB,KAAO,CAAC;AAG5C,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAGjD,eAAO,MAAM,2BAA2B,QAAqC,CAAC;AAG9E,eAAO,MAAM,+BAA+B,QAAQ,CAAC;AAGrD,eAAO,MAAM,gCAAgC,QAA0C,CAAC;AAKxF,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAGhD,eAAO,MAAM,kCAAkC,OAAO,CAAC;AAGvD,eAAO,MAAM,+BAA+B,OAAO,CAAC;AAGpD,eAAO,MAAM,oCAAoC,MAAM,CAAC;AAGxD,eAAO,MAAM,+BAA+B,OAAO,CAAC;AAGpD,eAAO,MAAM,2BAA2B,OAAqC,CAAC;AAM9E,eAAO,MAAM,uBAAuB,IAAI,CAAC;AACzC,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAC9C,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAG5C,eAAO,MAAM,8BAA8B,IAAI,CAAC;AAQhD,eAAO,MAAM,uCAAuC,QAAgB,CAAC"}
|