@onekeyfe/hd-transport 1.2.8 → 1.2.10-alpha.2
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__/babylon-context-hash.test.js +44 -0
- package/__tests__/protocol-v2-file-write-pipeline.test.js +478 -0
- package/__tests__/protocol-v2.test.js +3 -3
- package/__tests__/static-protobuf.test.js +436 -0
- package/__tests__/transport-log.test.js +176 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +158 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +109280 -98
- package/dist/protocols/index.d.ts.map +1 -1
- package/dist/protocols/v2/session.d.ts +7 -1
- package/dist/protocols/v2/session.d.ts.map +1 -1
- package/dist/serialization/protobuf/messages.d.ts +1 -1
- package/dist/serialization/protobuf/messages.d.ts.map +1 -1
- package/dist/types/messages.d.ts +72 -0
- package/dist/types/messages.d.ts.map +1 -1
- package/dist/types/transport.d.ts +6 -0
- package/dist/types/transport.d.ts.map +1 -1
- package/dist/utils/transportLog.d.ts.map +1 -1
- package/jest.config.js +3 -0
- package/messages-protocol-v2.json +152 -0
- package/package.json +6 -4
- package/scripts/protobuf-build.sh +37 -0
- package/scripts/protobuf-static-csp.js +103 -0
- package/scripts/protobuf-static.js +85 -0
- package/scripts/protobuf-types.js +1 -0
- package/src/constants.ts +10 -1
- package/src/index.ts +1 -0
- package/src/protocols/index.ts +10 -23
- package/src/protocols/v2/session.ts +326 -96
- package/src/serialization/protobuf/generated/codecs.d.ts +3 -0
- package/src/serialization/protobuf/generated/codecs.js +108990 -0
- package/src/serialization/protobuf/legacy-protocol-info.json +16 -0
- package/src/serialization/protobuf/messages.ts +12 -6
- package/src/types/messages.ts +97 -0
- package/src/types/transport.ts +25 -0
- package/src/utils/transportLog.ts +17 -2
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'util';
|
|
2
|
+
import { mkdirSync, writeFileSync } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import ByteBuffer from 'bytebuffer';
|
|
5
|
+
import * as protobuf from 'protobufjs/light';
|
|
6
|
+
import Long from 'long';
|
|
7
|
+
|
|
8
|
+
import v1 from '@onekeyfe/hd-core/src/data/messages/messages.json';
|
|
9
|
+
import legacy from '@onekeyfe/hd-core/src/data/messages/messages_legacy_v1.json';
|
|
10
|
+
import v2 from '../messages-protocol-v2.json';
|
|
11
|
+
import { parseConfigure, createMessageFromName } from '../src/serialization/protobuf/messages';
|
|
12
|
+
import { encode } from '../src/serialization/protobuf/encode';
|
|
13
|
+
import { decode } from '../src/serialization/protobuf/decode';
|
|
14
|
+
import { installStaticCodecs } from '../src/serialization/protobuf/generated/codecs';
|
|
15
|
+
import { ProtocolV1, ProtocolV2 } from '../src/protocols';
|
|
16
|
+
|
|
17
|
+
import { patchFeatures } from '@onekeyfe/hd-core/src/utils/patch';
|
|
18
|
+
import { fixFeaturesFirmwareVersion } from '@onekeyfe/hd-core/src/utils/deviceFeaturesUtils';
|
|
19
|
+
import { buildProtocolV1FeaturesPayload } from '@onekeyfe/hd-core/src/deviceProfile/buildDeviceFeatures';
|
|
20
|
+
import {
|
|
21
|
+
mapFeaturesToState,
|
|
22
|
+
mapProtocolV2DeviceInfoToState,
|
|
23
|
+
mapProtocolV2DeviceStatusToState,
|
|
24
|
+
} from '@onekeyfe/hd-core/src/device/DeviceStateMapper';
|
|
25
|
+
import { DeviceStateStore } from '@onekeyfe/hd-core/src/device/DeviceStateStore';
|
|
26
|
+
import { projectFeatures } from '@onekeyfe/hd-core/src/device/DeviceStateProjector';
|
|
27
|
+
|
|
28
|
+
jest.mock('@onekeyfe/hd-core/src/data/config', () => ({
|
|
29
|
+
getSDKVersion: () => '1.0.0-test',
|
|
30
|
+
DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0-test/',
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
const constants = Object.values(Long)
|
|
34
|
+
.filter(value => value instanceof Long)
|
|
35
|
+
.map(value => ({ value, low: value.low, high: value.high, unsigned: value.unsigned }));
|
|
36
|
+
const resetLong = () => {
|
|
37
|
+
constants.forEach(({ value, low, high, unsigned }) =>
|
|
38
|
+
Object.assign(value, { low, high, unsigned })
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
const collectTypes = namespace => [
|
|
42
|
+
...(namespace instanceof protobuf.Type ? [namespace] : []),
|
|
43
|
+
...(namespace.nestedArray || []).flatMap(collectTypes),
|
|
44
|
+
];
|
|
45
|
+
const capture = fn => {
|
|
46
|
+
try {
|
|
47
|
+
return { ok: true, value: fn() };
|
|
48
|
+
} catch (error) {
|
|
49
|
+
return { ok: false, name: error.name, message: error.message };
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function seed(type, mode, depth = 0) {
|
|
54
|
+
const result = {};
|
|
55
|
+
if (depth > 5) return result;
|
|
56
|
+
type.fieldsArray.forEach(field => {
|
|
57
|
+
if (field.partOf && field.partOf.fieldsArray[0] !== field) return;
|
|
58
|
+
if (mode === 'required' && !field.required) return;
|
|
59
|
+
if (field.resolvedType instanceof protobuf.Type && !field.required && depth > 2) return;
|
|
60
|
+
let value;
|
|
61
|
+
if (field.resolvedType instanceof protobuf.Type)
|
|
62
|
+
value = seed(field.resolvedType, mode, depth + 1);
|
|
63
|
+
else if (field.resolvedType instanceof protobuf.Enum) {
|
|
64
|
+
const values = Object.values(field.resolvedType.values);
|
|
65
|
+
value = values[mode === 'values' ? Math.min(1, values.length - 1) : 0];
|
|
66
|
+
} else if (field.type === 'bytes') value = mode === 'values' ? '00ff' : '';
|
|
67
|
+
else if (field.type === 'string') value = mode === 'values' ? 'test-验证' : '';
|
|
68
|
+
else if (field.type === 'bool') value = mode === 'values';
|
|
69
|
+
else if (field.long) value = mode === 'values' ? '9007199254740993' : '0';
|
|
70
|
+
else value = mode === 'values' ? 1 : 0;
|
|
71
|
+
result[field.name] = field.repeated ? [value] : value;
|
|
72
|
+
});
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function inputCases(type) {
|
|
77
|
+
const base = seed(type, 'required');
|
|
78
|
+
const cases = [
|
|
79
|
+
['absent', {}],
|
|
80
|
+
['required', base],
|
|
81
|
+
['zeros', seed(type, 'zeros')],
|
|
82
|
+
['values', seed(type, 'values')],
|
|
83
|
+
];
|
|
84
|
+
type.fieldsArray.forEach(field => {
|
|
85
|
+
const add = (label, value) =>
|
|
86
|
+
cases.push([`${field.name}:${label}`, { ...base, [field.name]: value }]);
|
|
87
|
+
const scalar = value => (field.repeated ? [value] : value);
|
|
88
|
+
add('undefined', undefined);
|
|
89
|
+
if (!field.repeated) add('null', null);
|
|
90
|
+
let values = [];
|
|
91
|
+
if (field.resolvedType instanceof protobuf.Enum) {
|
|
92
|
+
values = [
|
|
93
|
+
...Object.entries(field.resolvedType.values).flat(),
|
|
94
|
+
-1,
|
|
95
|
+
2147483647,
|
|
96
|
+
'0',
|
|
97
|
+
'UNKNOWN_ENUM',
|
|
98
|
+
];
|
|
99
|
+
} else if (!field.resolvedType) {
|
|
100
|
+
if (field.type === 'bytes')
|
|
101
|
+
values = ['', '00', '0', 'zz', Buffer.alloc(0), new Uint8Array([0, 255])];
|
|
102
|
+
else if (field.type === 'string') values = [0, 1, false, '', '01'];
|
|
103
|
+
else if (field.type === 'bool') values = [false, true, 0, 1, 'false', 'true'];
|
|
104
|
+
else if (field.long)
|
|
105
|
+
values = [
|
|
106
|
+
'0',
|
|
107
|
+
'9007199254740991',
|
|
108
|
+
'9007199254740993',
|
|
109
|
+
'18446744073709551615',
|
|
110
|
+
'-9223372036854775808',
|
|
111
|
+
9007199254740992,
|
|
112
|
+
];
|
|
113
|
+
else values = [0, -1, 1, '0', '1', '01', 1.5, NaN, Infinity];
|
|
114
|
+
}
|
|
115
|
+
values.forEach((value, index) => add(`coercion-${index}`, scalar(value)));
|
|
116
|
+
if (field.repeated) add('empty-array', []);
|
|
117
|
+
if (field.repeated && field.type === 'bytes') add('three-empty-bytes', ['', '', '']);
|
|
118
|
+
});
|
|
119
|
+
return cases;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Do not print inputs or wire bytes on failures, even though all data here is synthetic.
|
|
123
|
+
function compare(label, oldCall, newCall, reset = true) {
|
|
124
|
+
if (reset) resetLong();
|
|
125
|
+
const expected = capture(oldCall);
|
|
126
|
+
if (reset) resetLong();
|
|
127
|
+
const actual = capture(newCall);
|
|
128
|
+
if (!isDeepStrictEqual(expected, actual)) throw new Error(`Static protobuf differs: ${label}`);
|
|
129
|
+
return expected;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const toBytes = (type, input) => Buffer.from(encode(type, input).toBuffer());
|
|
133
|
+
const fromBytes = (type, bytes) => decode(type, ByteBuffer.wrap(bytes));
|
|
134
|
+
|
|
135
|
+
afterEach(() => {
|
|
136
|
+
resetLong();
|
|
137
|
+
jest.restoreAllMocks();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test.each([
|
|
141
|
+
['v1', v1],
|
|
142
|
+
['legacy-v1', legacy],
|
|
143
|
+
['v2', v2],
|
|
144
|
+
])(
|
|
145
|
+
'%s matches legacy bytes, own properties, scalar types, malformed input and framing',
|
|
146
|
+
(schemaName, schema) => {
|
|
147
|
+
const reference = protobuf.Root.fromJSON(schema);
|
|
148
|
+
const types = collectTypes(reference);
|
|
149
|
+
types.forEach(type => {
|
|
150
|
+
type.setup();
|
|
151
|
+
type.create();
|
|
152
|
+
});
|
|
153
|
+
// Baseline functions are ready. Every candidate constructor and codec must now be static.
|
|
154
|
+
jest.spyOn(protobuf.Type.prototype, 'setup').mockImplementation(() => {
|
|
155
|
+
throw new Error('Dynamic setup');
|
|
156
|
+
});
|
|
157
|
+
jest.spyOn(protobuf.Type, 'generateConstructor').mockImplementation(() => {
|
|
158
|
+
throw new Error('Dynamic constructor');
|
|
159
|
+
});
|
|
160
|
+
const candidate = parseConfigure(schema);
|
|
161
|
+
const stringCandidate = parseConfigure(JSON.stringify(schema));
|
|
162
|
+
expect(collectTypes(candidate)).toHaveLength(types.length);
|
|
163
|
+
let inputs = 0;
|
|
164
|
+
let wires = 0;
|
|
165
|
+
let frames = 0;
|
|
166
|
+
const rows = [];
|
|
167
|
+
types.forEach(type => {
|
|
168
|
+
const next = candidate.lookupType(type.fullName);
|
|
169
|
+
rows.push({ message: type.fullName, result: 'equal', inputs: inputCases(type).length });
|
|
170
|
+
inputCases(type).forEach(([label, input]) => {
|
|
171
|
+
const encoded = compare(
|
|
172
|
+
`${schemaName}/${type.name}/${label}/encode`,
|
|
173
|
+
() => toBytes(type, input),
|
|
174
|
+
() => toBytes(next, input)
|
|
175
|
+
);
|
|
176
|
+
inputs += 1;
|
|
177
|
+
if (encoded.ok) {
|
|
178
|
+
compare(
|
|
179
|
+
`${schemaName}/${type.name}/${label}/decode`,
|
|
180
|
+
() => fromBytes(type, encoded.value),
|
|
181
|
+
() => fromBytes(next, encoded.value)
|
|
182
|
+
);
|
|
183
|
+
wires += 1;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
const base = capture(() => toBytes(type, seed(type, 'required')));
|
|
187
|
+
if (!base.ok) return;
|
|
188
|
+
const checkWire = (label, bytes) => {
|
|
189
|
+
compare(
|
|
190
|
+
`${schemaName}/${type.name}/${label}`,
|
|
191
|
+
() => fromBytes(type, bytes),
|
|
192
|
+
() => fromBytes(next, bytes)
|
|
193
|
+
);
|
|
194
|
+
wires += 1;
|
|
195
|
+
};
|
|
196
|
+
checkWire('empty-wire', Buffer.alloc(0));
|
|
197
|
+
const dense = capture(() => toBytes(type, seed(type, 'values')));
|
|
198
|
+
if (dense.ok) {
|
|
199
|
+
[0, 1, Math.floor(dense.value.length / 2), dense.value.length - 1]
|
|
200
|
+
.filter(length => length >= 0)
|
|
201
|
+
.forEach(length => checkWire(`truncated-${length}`, dense.value.subarray(0, length)));
|
|
202
|
+
for (let index = 0; index < Math.min(12, dense.value.length); index += 1) {
|
|
203
|
+
const bytes = Buffer.from(dense.value);
|
|
204
|
+
bytes[index] = (bytes[index] + 2 ** (index % 8)) % 256;
|
|
205
|
+
checkWire(`mutation-${index}`, bytes);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const tail = protobuf.Writer.create()
|
|
209
|
+
.uint32(500000 * 8)
|
|
210
|
+
.finish();
|
|
211
|
+
checkWire('battery-ff-fallback', Buffer.concat([base.value, tail, Buffer.from([255])]));
|
|
212
|
+
checkWire(
|
|
213
|
+
'unknown-field',
|
|
214
|
+
Buffer.concat([
|
|
215
|
+
base.value,
|
|
216
|
+
protobuf.Writer.create()
|
|
217
|
+
.uint32(500000 * 8 + 2)
|
|
218
|
+
.bytes([0, 255, 1])
|
|
219
|
+
.finish(),
|
|
220
|
+
])
|
|
221
|
+
);
|
|
222
|
+
type.fieldsArray.forEach(field => {
|
|
223
|
+
if (field.resolvedType instanceof protobuf.Enum) {
|
|
224
|
+
const known = Object.values(field.resolvedType.values)[0];
|
|
225
|
+
[-1, 2147483647].forEach(unknown => {
|
|
226
|
+
const write = value =>
|
|
227
|
+
protobuf.Writer.create()
|
|
228
|
+
.uint32(field.id * 8)
|
|
229
|
+
.int32(value)
|
|
230
|
+
.finish();
|
|
231
|
+
checkWire(`${field.name}/unknown`, Buffer.concat([base.value, write(unknown)]));
|
|
232
|
+
checkWire(
|
|
233
|
+
`${field.name}/mixed`,
|
|
234
|
+
Buffer.concat([base.value, write(known), write(unknown), write(known)])
|
|
235
|
+
);
|
|
236
|
+
if (field.repeated) {
|
|
237
|
+
const packed = protobuf.Writer.create()
|
|
238
|
+
.int32(known)
|
|
239
|
+
.int32(unknown)
|
|
240
|
+
.int32(known)
|
|
241
|
+
.finish();
|
|
242
|
+
checkWire(
|
|
243
|
+
`${field.name}/packed-mixed`,
|
|
244
|
+
Buffer.concat([
|
|
245
|
+
base.value,
|
|
246
|
+
protobuf.Writer.create()
|
|
247
|
+
.uint32(field.id * 8 + 2)
|
|
248
|
+
.bytes(packed)
|
|
249
|
+
.finish(),
|
|
250
|
+
])
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
} else if (field.resolvedType instanceof protobuf.Type && !field.repeated) {
|
|
255
|
+
const first = capture(() =>
|
|
256
|
+
toBytes(field.resolvedType, seed(field.resolvedType, 'values'))
|
|
257
|
+
);
|
|
258
|
+
const second = capture(() =>
|
|
259
|
+
toBytes(field.resolvedType, seed(field.resolvedType, 'required'))
|
|
260
|
+
);
|
|
261
|
+
if (first.ok && second.ok) {
|
|
262
|
+
const wrap = bytes =>
|
|
263
|
+
protobuf.Writer.create()
|
|
264
|
+
.uint32(field.id * 8 + 2)
|
|
265
|
+
.bytes(bytes)
|
|
266
|
+
.finish();
|
|
267
|
+
checkWire(
|
|
268
|
+
`${field.name}/duplicate-message`,
|
|
269
|
+
Buffer.concat([base.value, wrap(first.value), wrap(second.value)])
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
const name = type.fullName.slice(1);
|
|
275
|
+
const message = capture(() => createMessageFromName(reference, name));
|
|
276
|
+
if (message.ok) {
|
|
277
|
+
const input = seed(type, 'values');
|
|
278
|
+
compare(
|
|
279
|
+
`${name}/id`,
|
|
280
|
+
() => message.value.messageTypeId,
|
|
281
|
+
() => createMessageFromName(candidate, name).messageTypeId
|
|
282
|
+
);
|
|
283
|
+
compare(
|
|
284
|
+
`${name}/v1-envelope`,
|
|
285
|
+
() => ProtocolV1.encodeEnvelope(reference, name, input).toBuffer(),
|
|
286
|
+
() => ProtocolV1.encodeEnvelope(candidate, name, input).toBuffer()
|
|
287
|
+
);
|
|
288
|
+
compare(
|
|
289
|
+
`${name}/v1-chunks`,
|
|
290
|
+
() => ProtocolV1.encodeMessageChunks(reference, name, input),
|
|
291
|
+
() => ProtocolV1.encodeMessageChunks(candidate, name, input)
|
|
292
|
+
);
|
|
293
|
+
if (schemaName === 'v2') {
|
|
294
|
+
compare(
|
|
295
|
+
`${name}/v2-frame`,
|
|
296
|
+
() =>
|
|
297
|
+
ProtocolV2.encodeFrame(
|
|
298
|
+
{ protocolV1: reference, protocolV2: reference },
|
|
299
|
+
name,
|
|
300
|
+
input,
|
|
301
|
+
{ seq: 23 }
|
|
302
|
+
),
|
|
303
|
+
() =>
|
|
304
|
+
ProtocolV2.encodeFrame(
|
|
305
|
+
{ protocolV1: candidate, protocolV2: candidate },
|
|
306
|
+
name,
|
|
307
|
+
input,
|
|
308
|
+
{ seq: 23 }
|
|
309
|
+
)
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
frames += 1;
|
|
313
|
+
}
|
|
314
|
+
compare(
|
|
315
|
+
`${name}/string-config`,
|
|
316
|
+
() => toBytes(next, seed(type, 'required')),
|
|
317
|
+
() => toBytes(stringCandidate.lookupType(type.fullName), seed(type, 'required'))
|
|
318
|
+
);
|
|
319
|
+
});
|
|
320
|
+
const reportDirectory = path.resolve(__dirname, '../../../node_modules/.cache/protobuf-static');
|
|
321
|
+
mkdirSync(reportDirectory, { recursive: true });
|
|
322
|
+
writeFileSync(
|
|
323
|
+
path.join(reportDirectory, `${schemaName}.json`),
|
|
324
|
+
JSON.stringify({ schemaName, inputs, wires, frames, rows }, null, 2)
|
|
325
|
+
);
|
|
326
|
+
console.info(
|
|
327
|
+
`Static protobuf ${schemaName}: ${types.length} messages, ${inputs} inputs, ${wires} decodes, ${frames} message envelopes; zero differences`
|
|
328
|
+
);
|
|
329
|
+
},
|
|
330
|
+
120000
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
test('custom schemas retain reflection support instead of selecting a similar built-in message', () => {
|
|
334
|
+
const schema = { nested: { Features: { fields: { custom: { type: 'string', id: 1 } } } } };
|
|
335
|
+
const root = parseConfigure(schema);
|
|
336
|
+
expect(installStaticCodecs(root)).toBe(false);
|
|
337
|
+
expect(
|
|
338
|
+
fromBytes(root.lookupType('Features'), toBytes(root.lookupType('Features'), { custom: 'test' }))
|
|
339
|
+
).toStrictEqual({ custom: 'test' });
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test.each([
|
|
343
|
+
['v1', v1],
|
|
344
|
+
['legacy-v1', legacy],
|
|
345
|
+
['v2', v2],
|
|
346
|
+
])(
|
|
347
|
+
'%s preserves Features compatibility patches, state and projected own properties',
|
|
348
|
+
(schemaName, schema) => {
|
|
349
|
+
jest.spyOn(Date, 'now').mockReturnValue(1700000000000);
|
|
350
|
+
const reference = protobuf.Root.fromJSON(schema).resolveAll();
|
|
351
|
+
const candidate = parseConfigure(schema);
|
|
352
|
+
const names = schemaName === 'v2' ? ['Features', 'DeviceInfo', 'DeviceStatus'] : ['Features'];
|
|
353
|
+
let comparisons = 0;
|
|
354
|
+
names.forEach(name => {
|
|
355
|
+
const type = reference.lookupType(name);
|
|
356
|
+
const next = candidate.lookupType(name);
|
|
357
|
+
const pipeline = (decoded, cached, mode) => {
|
|
358
|
+
let payload = decoded;
|
|
359
|
+
let mapped;
|
|
360
|
+
if (name === 'Features') {
|
|
361
|
+
const patched = patchFeatures({ type: name, message: decoded }).message;
|
|
362
|
+
payload = buildProtocolV1FeaturesPayload(patched, cached?.previous);
|
|
363
|
+
payload.unlocked = payload.unlocked ?? true;
|
|
364
|
+
payload = fixFeaturesFirmwareVersion(payload);
|
|
365
|
+
mapped = mapFeaturesToState(payload);
|
|
366
|
+
} else {
|
|
367
|
+
mapped =
|
|
368
|
+
name === 'DeviceInfo'
|
|
369
|
+
? mapProtocolV2DeviceInfoToState(decoded, mode)
|
|
370
|
+
: mapProtocolV2DeviceStatusToState(decoded);
|
|
371
|
+
}
|
|
372
|
+
const store = new DeviceStateStore(cached?.state);
|
|
373
|
+
const updated = store.update(mapped, 'initialize');
|
|
374
|
+
return { payload, mapped, ...updated, projected: projectFeatures(updated.state) };
|
|
375
|
+
};
|
|
376
|
+
const dense = toBytes(type, seed(type, 'values'));
|
|
377
|
+
const initial = pipeline(fromBytes(type, dense), undefined, 'normal');
|
|
378
|
+
const cache = { state: initial.state, previous: initial.projected };
|
|
379
|
+
const cases = inputCases(type);
|
|
380
|
+
const populated = seed(type, 'values');
|
|
381
|
+
type.fieldsArray.forEach(field => {
|
|
382
|
+
const omitted = { ...populated };
|
|
383
|
+
delete omitted[field.name];
|
|
384
|
+
cases.push([`${field.name}/omitted`, omitted]);
|
|
385
|
+
});
|
|
386
|
+
cases.forEach(([label, input]) => {
|
|
387
|
+
const encoded = capture(() => toBytes(type, input));
|
|
388
|
+
if (!encoded.ok) return;
|
|
389
|
+
[undefined, cache].forEach(cached => {
|
|
390
|
+
['normal', 'bootloader'].forEach(mode => {
|
|
391
|
+
compare(
|
|
392
|
+
`${schemaName}/${name}/${label}/pipeline/${mode}/${!!cached}`,
|
|
393
|
+
() => pipeline(fromBytes(type, encoded.value), cached, mode),
|
|
394
|
+
() => pipeline(fromBytes(next, encoded.value), cached, mode)
|
|
395
|
+
);
|
|
396
|
+
comparisons += 1;
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
console.info(
|
|
402
|
+
`Static protobuf ${schemaName}: ${comparisons} Features pipeline comparisons; zero differences`
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
test('retains sequence-dependent Long conversion behavior', () => {
|
|
408
|
+
const reference = protobuf.Root.fromJSON(v2).resolveAll();
|
|
409
|
+
const candidate = parseConfigure(v2);
|
|
410
|
+
const signed = reference.lookupType('CardanoToken');
|
|
411
|
+
const unsigned = collectTypes(reference).find(type =>
|
|
412
|
+
type.fieldsArray.some(field => field.type === 'uint64' && !field.repeated)
|
|
413
|
+
);
|
|
414
|
+
const field = unsigned.fieldsArray.find(item => item.type === 'uint64' && !item.repeated);
|
|
415
|
+
const bytes = toBytes(signed, {
|
|
416
|
+
...seed(signed, 'required'),
|
|
417
|
+
mint_amount: '-9223372036854775808',
|
|
418
|
+
});
|
|
419
|
+
const sequence = root => {
|
|
420
|
+
resetLong();
|
|
421
|
+
const before = capture(() => fromBytes(root.lookupType(signed.fullName), bytes));
|
|
422
|
+
const encoded = capture(() =>
|
|
423
|
+
toBytes(root.lookupType(unsigned.fullName), {
|
|
424
|
+
...seed(unsigned, 'required'),
|
|
425
|
+
[field.name]: '-9223372036854775808',
|
|
426
|
+
})
|
|
427
|
+
);
|
|
428
|
+
const after = capture(() => fromBytes(root.lookupType(signed.fullName), bytes));
|
|
429
|
+
return { before, encoded, after };
|
|
430
|
+
};
|
|
431
|
+
compare(
|
|
432
|
+
'Long mutation sequence',
|
|
433
|
+
() => sequence(reference),
|
|
434
|
+
() => sequence(candidate)
|
|
435
|
+
);
|
|
436
|
+
});
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
const { createTransportCallLog, getSafeTransportLogPayload } = require('../src/utils/transportLog');
|
|
2
2
|
|
|
3
3
|
describe('transport log sanitization', () => {
|
|
4
|
+
test('redacts Babylon derived secrets from response logs', () => {
|
|
5
|
+
expect(
|
|
6
|
+
getSafeTransportLogPayload({ secret: '00'.repeat(32) }, 'BabylonDerivedContextHash')
|
|
7
|
+
).toEqual({ secret: '[REDACTED]' });
|
|
8
|
+
});
|
|
9
|
+
|
|
4
10
|
test('logs non-sensitive request fields and redacts passphrases recursively', () => {
|
|
5
11
|
expect(
|
|
6
12
|
createTransportCallLog('DeviceSessionAskPassphrase', 'V2', {
|
|
@@ -65,6 +71,24 @@ describe('transport log sanitization', () => {
|
|
|
65
71
|
).toBe('[REDACTED]');
|
|
66
72
|
});
|
|
67
73
|
|
|
74
|
+
test.each(['V1', 'V2'])('redacts BTC message signing protocol logs for %s', protocol => {
|
|
75
|
+
const request = { message: 'synthetic-message-marker', coin_name: 'Bitcoin' };
|
|
76
|
+
const response = {
|
|
77
|
+
signature: 'synthetic-signature-marker',
|
|
78
|
+
address: 'synthetic-address-marker',
|
|
79
|
+
};
|
|
80
|
+
const originals = JSON.stringify([request, response]);
|
|
81
|
+
|
|
82
|
+
expect(createTransportCallLog('SignMessage', protocol, request)).toEqual({
|
|
83
|
+
name: 'SignMessage',
|
|
84
|
+
protocol,
|
|
85
|
+
request: '[REDACTED]',
|
|
86
|
+
});
|
|
87
|
+
expect(getSafeTransportLogPayload(request, 'SignMessage')).toBe('[REDACTED]');
|
|
88
|
+
expect(getSafeTransportLogPayload(response, 'MessageSignature')).toBe('[REDACTED]');
|
|
89
|
+
expect(JSON.stringify([request, response])).toBe(originals);
|
|
90
|
+
});
|
|
91
|
+
|
|
68
92
|
test('replaces binary content with its byte length', () => {
|
|
69
93
|
expect(
|
|
70
94
|
getSafeTransportLogPayload({
|
|
@@ -76,4 +100,156 @@ describe('transport log sanitization', () => {
|
|
|
76
100
|
data: '[BINARY:128]',
|
|
77
101
|
});
|
|
78
102
|
});
|
|
103
|
+
|
|
104
|
+
test('redacts Zcash viewing keys and fingerprints in protocol and API field names', () => {
|
|
105
|
+
const payload = {
|
|
106
|
+
address: 'synthetic-address-marker',
|
|
107
|
+
ufvk: 'synthetic-viewing-key-marker',
|
|
108
|
+
seed_fingerprint: 'synthetic-fingerprint-marker',
|
|
109
|
+
nested: [{ seedFingerprint: 'synthetic-fingerprint-marker' }],
|
|
110
|
+
};
|
|
111
|
+
expect(getSafeTransportLogPayload(payload, 'ZcashAddress')).toEqual({
|
|
112
|
+
address: '[REDACTED]',
|
|
113
|
+
ufvk: '[REDACTED]',
|
|
114
|
+
seed_fingerprint: '[REDACTED]',
|
|
115
|
+
nested: [{ seedFingerprint: '[REDACTED]' }],
|
|
116
|
+
});
|
|
117
|
+
expect(payload.ufvk).toBe('synthetic-viewing-key-marker');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('redacts the exported Zcash viewing key response', () => {
|
|
121
|
+
const payload = { ufvk: 'synthetic-viewing-key-marker' };
|
|
122
|
+
expect(getSafeTransportLogPayload(payload, 'ZcashFullViewingKey')).toEqual({
|
|
123
|
+
ufvk: '[REDACTED]',
|
|
124
|
+
});
|
|
125
|
+
expect(
|
|
126
|
+
JSON.stringify(createTransportCallLog('ZcashFullViewingKey', 'V2', payload))
|
|
127
|
+
).not.toContain('synthetic-');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('redacts Monero view keys in protocol and API field names', () => {
|
|
131
|
+
expect(
|
|
132
|
+
getSafeTransportLogPayload(
|
|
133
|
+
{
|
|
134
|
+
address: 'synthetic-address-marker',
|
|
135
|
+
watch_key: 'synthetic-view-key-marker',
|
|
136
|
+
nested: { watchKey: 'synthetic-view-key-marker' },
|
|
137
|
+
},
|
|
138
|
+
'MoneroWatchKey'
|
|
139
|
+
)
|
|
140
|
+
).toEqual({
|
|
141
|
+
address: '[REDACTED]',
|
|
142
|
+
watch_key: '[REDACTED]',
|
|
143
|
+
nested: { watchKey: '[REDACTED]' },
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test.each([
|
|
148
|
+
[
|
|
149
|
+
'MoneroKeyImageExportInitRequest',
|
|
150
|
+
{ num: 1, hash: 'synthetic-hash-marker', subs: [{ account: 0, minor_indices: [2] }] },
|
|
151
|
+
],
|
|
152
|
+
[
|
|
153
|
+
'MoneroKeyImageSyncStepRequest',
|
|
154
|
+
{
|
|
155
|
+
tdis: [
|
|
156
|
+
{
|
|
157
|
+
out_key: 'synthetic-out-key-marker',
|
|
158
|
+
tx_pub_key: 'synthetic-tx-key-marker',
|
|
159
|
+
internal_output_index: 1,
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
[
|
|
165
|
+
'MoneroKeyImageSyncStepAck',
|
|
166
|
+
{ kis: [{ iv: 'synthetic-iv-marker', blob: 'synthetic-key-image-marker' }] },
|
|
167
|
+
],
|
|
168
|
+
['MoneroKeyImageSyncFinalAck', { enc_key: 'synthetic-enc-key-marker' }],
|
|
169
|
+
['MoneroGetTxKeyAck', { salt: 'synthetic-salt-marker', tx_keys: 'synthetic-tx-keys-marker' }],
|
|
170
|
+
[
|
|
171
|
+
'MoneroLiveRefreshStepAck',
|
|
172
|
+
{ salt: 'synthetic-salt-marker', key_image: 'synthetic-ki-marker' },
|
|
173
|
+
],
|
|
174
|
+
])('redacts the complete Monero key image message %s', (name, payload) => {
|
|
175
|
+
expect(createTransportCallLog(name, 'V1', payload).request).toBe('[REDACTED]');
|
|
176
|
+
expect(getSafeTransportLogPayload(payload, name)).toBe('[REDACTED]');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('redacts Monero key image fields even without protocol message context', () => {
|
|
180
|
+
expect(
|
|
181
|
+
getSafeTransportLogPayload({
|
|
182
|
+
encKey: 'synthetic-enc-key-marker',
|
|
183
|
+
transfers: [
|
|
184
|
+
{
|
|
185
|
+
outKey: 'synthetic-out-key-marker',
|
|
186
|
+
txPubKey: 'synthetic-tx-key-marker',
|
|
187
|
+
additional_tx_pub_keys: ['synthetic-additional-key-marker'],
|
|
188
|
+
internalOutputIndex: 1,
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
})
|
|
192
|
+
).toEqual({
|
|
193
|
+
encKey: '[REDACTED]',
|
|
194
|
+
transfers: [
|
|
195
|
+
{
|
|
196
|
+
outKey: '[REDACTED]',
|
|
197
|
+
txPubKey: '[REDACTED]',
|
|
198
|
+
additional_tx_pub_keys: '[REDACTED]',
|
|
199
|
+
internalOutputIndex: 1,
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test.each([
|
|
206
|
+
'MoneroTransactionInitRequest',
|
|
207
|
+
'MoneroTransactionInitAck',
|
|
208
|
+
'MoneroTransactionSetInputRequest',
|
|
209
|
+
'MoneroTransactionSetInputAck',
|
|
210
|
+
'MoneroTransactionInputViniRequest',
|
|
211
|
+
'MoneroTransactionInputViniAck',
|
|
212
|
+
'MoneroTransactionAllInputsSetRequest',
|
|
213
|
+
'MoneroTransactionAllInputsSetAck',
|
|
214
|
+
'MoneroTransactionSetOutputRequest',
|
|
215
|
+
'MoneroTransactionSetOutputAck',
|
|
216
|
+
'MoneroTransactionAllOutSetRequest',
|
|
217
|
+
'MoneroTransactionAllOutSetAck',
|
|
218
|
+
'MoneroTransactionSignInputRequest',
|
|
219
|
+
'MoneroTransactionSignInputAck',
|
|
220
|
+
'MoneroTransactionFinalRequest',
|
|
221
|
+
'MoneroTransactionFinalAck',
|
|
222
|
+
])('redacts the complete Monero signing message %s', name => {
|
|
223
|
+
const payload = { src_entr: { mask: 'synthetic-mask-marker' }, opening_key: 'synthetic-key' };
|
|
224
|
+
expect(createTransportCallLog(name, 'V1', payload).request).toBe('[REDACTED]');
|
|
225
|
+
expect(getSafeTransportLogPayload(payload, name)).toBe('[REDACTED]');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test.each([
|
|
229
|
+
'ZcashSignPczt',
|
|
230
|
+
'ZcashPcztChunkRequest',
|
|
231
|
+
'ZcashPcztChunkAck',
|
|
232
|
+
'ZcashSignedPczt',
|
|
233
|
+
'ZcashSignedPcztChunkRequest',
|
|
234
|
+
])('redacts the complete %s request and response', name => {
|
|
235
|
+
const payload = { data_chunk: 'synthetic-pczt-marker', chunk_length: 64 };
|
|
236
|
+
expect(createTransportCallLog(name, 'V2', payload).request).toBe('[REDACTED]');
|
|
237
|
+
expect(getSafeTransportLogPayload(payload, name)).toBe('[REDACTED]');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('redacts named PCZT fields even without protocol message context', () => {
|
|
241
|
+
expect(
|
|
242
|
+
getSafeTransportLogPayload({
|
|
243
|
+
pczt: 'synthetic-pczt-marker',
|
|
244
|
+
signedPczt: 'synthetic-signed-pczt-marker',
|
|
245
|
+
data_chunk: 'synthetic-chunk-marker',
|
|
246
|
+
nested: { dataChunk: 'synthetic-chunk-marker' },
|
|
247
|
+
})
|
|
248
|
+
).toEqual({
|
|
249
|
+
pczt: '[REDACTED]',
|
|
250
|
+
signedPczt: '[REDACTED]',
|
|
251
|
+
data_chunk: '[REDACTED]',
|
|
252
|
+
nested: { dataChunk: '[REDACTED]' },
|
|
253
|
+
});
|
|
254
|
+
});
|
|
79
255
|
});
|
package/dist/constants.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare const PROTOCOL_V2_BLE_FILE_CHUNK_SIZE = 1800;
|
|
|
10
10
|
export declare const PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE = 1960;
|
|
11
11
|
export declare const PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE = 900;
|
|
12
12
|
export declare const PROTOCOL_V2_BLE_FRAME_MAX_BYTES = 2048;
|
|
13
|
+
export declare const PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW = 2;
|
|
13
14
|
export declare const PROTOCOL_V2_FILE_CHUNK_SIZE = 4000;
|
|
14
15
|
export declare const PROTOCOL_V2_CHANNEL_USB = 0;
|
|
15
16
|
export declare const PROTOCOL_V2_CHANNEL_BLE_UART = 1;
|
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;
|
|
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;AAOpD,eAAO,MAAM,wCAAwC,OAAO,CAAC;AAG7D,eAAO,MAAM,oCAAoC,MAAM,CAAC;AAGxD,eAAO,MAAM,+BAA+B,OAAO,CAAC;AAQpD,eAAO,MAAM,sCAAsC,IAAI,CAAC;AAGxD,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"}
|