@onekeyfe/hd-core 1.2.1 → 1.2.2-alpha.1
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__/deviceUploadNft.test.ts +68 -0
- package/__tests__/evmSignTypedData.test.ts +42 -0
- package/__tests__/pro2HostAssetPackage.test.ts +58 -0
- package/__tests__/protocol-v2.test.ts +82 -0
- package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.js +267 -23
- package/dist/utils/pro2HostAssetPackage.d.ts +8 -0
- package/dist/utils/pro2HostAssetPackage.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/evm/EVMSignTypedData.ts +1 -0
- package/src/api/protocol-v2/DeviceUploadNft.ts +31 -8
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +27 -8
- package/src/utils/pro2HostAssetPackage.ts +354 -0
|
@@ -32,10 +32,12 @@ const createMethod = ({
|
|
|
32
32
|
typedCall,
|
|
33
33
|
supportedMessages = [60802, 60805, 60808, 61500],
|
|
34
34
|
useFullBundle = false,
|
|
35
|
+
firmwareVersion = '1.0.0',
|
|
35
36
|
}: {
|
|
36
37
|
typedCall: jest.Mock;
|
|
37
38
|
supportedMessages?: number[];
|
|
38
39
|
useFullBundle?: boolean;
|
|
40
|
+
firmwareVersion?: string;
|
|
39
41
|
}) => {
|
|
40
42
|
const method = new DeviceUploadNft({
|
|
41
43
|
id: 1,
|
|
@@ -52,6 +54,7 @@ const createMethod = ({
|
|
|
52
54
|
});
|
|
53
55
|
(method as any).device = {
|
|
54
56
|
commands: { typedCall },
|
|
57
|
+
state: { versions: { firmware: firmwareVersion } },
|
|
55
58
|
ensureProtocolV2RuntimeContext: jest.fn(() =>
|
|
56
59
|
Promise.resolve({
|
|
57
60
|
version: 2,
|
|
@@ -60,6 +63,7 @@ const createMethod = ({
|
|
|
60
63
|
})
|
|
61
64
|
),
|
|
62
65
|
getCurrentFirmwareType: jest.fn(),
|
|
66
|
+
getCurrentFirmwareVersionString: jest.fn(() => firmwareVersion),
|
|
63
67
|
};
|
|
64
68
|
method.postMessage = jest.fn();
|
|
65
69
|
|
|
@@ -202,6 +206,70 @@ describe('DeviceUploadNft', () => {
|
|
|
202
206
|
});
|
|
203
207
|
});
|
|
204
208
|
|
|
209
|
+
test('uploads one host asset package on firmware 1.0.1', async () => {
|
|
210
|
+
const typedCall = jest.fn((request: string, _response: string, params: any) => {
|
|
211
|
+
if (request === 'FilesystemFileWrite') return fileWriteSuccess(params);
|
|
212
|
+
if (request === 'NftUpdate') return { message: { message: 'NFT updated' } };
|
|
213
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
214
|
+
});
|
|
215
|
+
const method = createMethod({
|
|
216
|
+
typedCall,
|
|
217
|
+
supportedMessages: [60805, 61500],
|
|
218
|
+
firmwareVersion: '1.0.1',
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const result = await method.run();
|
|
222
|
+
|
|
223
|
+
const requests = typedCall.mock.calls.map(call => call[0]);
|
|
224
|
+
expect(requests).not.toContain('FilesystemPathInfoQuery');
|
|
225
|
+
expect(requests).not.toContain('FilesystemDirList');
|
|
226
|
+
const fileWrites = typedCall.mock.calls.filter(call => call[0] === 'FilesystemFileWrite');
|
|
227
|
+
expect(new Set(fileWrites.map(call => call[2].file.path))).toEqual(
|
|
228
|
+
new Set(['vol1:/nft/nft-deadbeef-1760000000000.okpkg'])
|
|
229
|
+
);
|
|
230
|
+
expect(fileWrites[0][2].file.data.subarray(0, 4)).toEqual(
|
|
231
|
+
new Uint8Array([0x4f, 0x4b, 0x50, 0x50])
|
|
232
|
+
);
|
|
233
|
+
expect(typedCall).toHaveBeenLastCalledWith(
|
|
234
|
+
'NftUpdate',
|
|
235
|
+
'Success',
|
|
236
|
+
{ file_name_no_ext: result.basename },
|
|
237
|
+
{ timeoutMs: 15_000 }
|
|
238
|
+
);
|
|
239
|
+
expect(result).toMatchObject({
|
|
240
|
+
imagePath: 'vol1:/nft/nft-deadbeef-1760000000000.bin',
|
|
241
|
+
thumbnailPath: 'vol1:/nft/nft-deadbeef-1760000000000_m.bin',
|
|
242
|
+
metadataPath: 'vol1:/nft/nft-deadbeef-1760000000000.json',
|
|
243
|
+
nftUpdated: true,
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('keeps the legacy upload flow on a 1.0.1 prerelease', async () => {
|
|
248
|
+
const typedCall = jest.fn((request: string, _response: string, params: any) => {
|
|
249
|
+
if (request === 'FilesystemPathInfoQuery') {
|
|
250
|
+
return { message: { exist: true, directory: true } };
|
|
251
|
+
}
|
|
252
|
+
if (request === 'FilesystemDirList') {
|
|
253
|
+
return { message: { path: 'vol1:/nft', child_files: '' } };
|
|
254
|
+
}
|
|
255
|
+
if (request === 'FilesystemFileWrite') return fileWriteSuccess(params);
|
|
256
|
+
if (request === 'NftUpdate') return { message: {} };
|
|
257
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
258
|
+
});
|
|
259
|
+
const method = createMethod({ typedCall, firmwareVersion: '1.0.1-beta.1' });
|
|
260
|
+
|
|
261
|
+
await method.run();
|
|
262
|
+
|
|
263
|
+
const requests = typedCall.mock.calls.map(call => call[0]);
|
|
264
|
+
expect(requests).toContain('FilesystemPathInfoQuery');
|
|
265
|
+
expect(requests).toContain('FilesystemDirList');
|
|
266
|
+
const paths = typedCall.mock.calls
|
|
267
|
+
.filter(call => call[0] === 'FilesystemFileWrite')
|
|
268
|
+
.map(call => call[2].file.path as string);
|
|
269
|
+
expect(paths.some(path => path.endsWith('.okpkg'))).toBe(false);
|
|
270
|
+
expect(paths).toContain('vol1:/nft/nft-deadbeef-1760000000000.bin');
|
|
271
|
+
});
|
|
272
|
+
|
|
205
273
|
test('treats a missing NFT directory as empty before the first upload', async () => {
|
|
206
274
|
const typedCall = jest.fn((request: string, _response: string, params: any) => {
|
|
207
275
|
if (request === 'FilesystemPathInfoQuery') {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { EDeviceType } from '@onekeyfe/hd-shared';
|
|
2
|
+
|
|
1
3
|
import EVMSignTypedData from '../src/api/evm/EVMSignTypedData';
|
|
2
4
|
|
|
3
5
|
import type { EthereumSignTypedDataMessage, EthereumSignTypedDataTypes } from '../src/types';
|
|
@@ -577,3 +579,43 @@ describe('EVMSignTypedData — OneKey Pro Safe Protocol V1', () => {
|
|
|
577
579
|
});
|
|
578
580
|
});
|
|
579
581
|
});
|
|
582
|
+
|
|
583
|
+
describe('EVMSignTypedData — Protocol V2 data size routing', () => {
|
|
584
|
+
const buildSafeTxData = (
|
|
585
|
+
dataSize: number
|
|
586
|
+
): EthereumSignTypedDataMessage<EthereumSignTypedDataTypes> =>
|
|
587
|
+
({
|
|
588
|
+
types: {
|
|
589
|
+
EIP712Domain: [],
|
|
590
|
+
SafeTx: [{ name: 'data', type: 'bytes' }],
|
|
591
|
+
},
|
|
592
|
+
primaryType: 'SafeTx',
|
|
593
|
+
domain: {},
|
|
594
|
+
message: { data: `0x${'ab'.repeat(dataSize)}` },
|
|
595
|
+
} as EthereumSignTypedDataMessage<EthereumSignTypedDataTypes>);
|
|
596
|
+
|
|
597
|
+
test.each([EDeviceType.Pro2, EDeviceType.Neo])(
|
|
598
|
+
'keeps SafeTx data up to 1536 bytes on the structured route for %s',
|
|
599
|
+
deviceType => {
|
|
600
|
+
const data = buildSafeTxData(1316);
|
|
601
|
+
const method = createMethod(data);
|
|
602
|
+
method.device.getCurrentDeviceType = jest.fn(() => deviceType);
|
|
603
|
+
method.device.getCurrentFirmwareVersionString = jest.fn(() => '1.0.0');
|
|
604
|
+
|
|
605
|
+
expect(method.hasBiggerData(data)).toBe(false);
|
|
606
|
+
expect(method.hasBiggerData(buildSafeTxData(1536))).toBe(false);
|
|
607
|
+
}
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
test.each([EDeviceType.Pro2, EDeviceType.Neo])(
|
|
611
|
+
'falls back to the hash route above 1536 bytes for %s',
|
|
612
|
+
deviceType => {
|
|
613
|
+
const data = buildSafeTxData(1537);
|
|
614
|
+
const method = createMethod(data);
|
|
615
|
+
method.device.getCurrentDeviceType = jest.fn(() => deviceType);
|
|
616
|
+
method.device.getCurrentFirmwareVersionString = jest.fn(() => '1.0.0');
|
|
617
|
+
|
|
618
|
+
expect(method.hasBiggerData(data)).toBe(true);
|
|
619
|
+
}
|
|
620
|
+
);
|
|
621
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { sha3_512 } from '@noble/hashes/sha3';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildPro2HostAssetPackage,
|
|
5
|
+
supportsPro2HostAssetPackage,
|
|
6
|
+
} from '../src/utils/pro2HostAssetPackage';
|
|
7
|
+
|
|
8
|
+
describe('Pro2 host asset package', () => {
|
|
9
|
+
test('builds the unsigned RESOURCE container and LZ4-blocked archive expected by firmware', () => {
|
|
10
|
+
const raw = new TextEncoder().encode('123456789');
|
|
11
|
+
const packageData = buildPro2HostAssetPackage([{ name: 'wallpaper.bin', data: raw }]);
|
|
12
|
+
const headerSize = 0x5f90;
|
|
13
|
+
const payload = packageData.subarray(headerSize);
|
|
14
|
+
const packageView = new DataView(
|
|
15
|
+
packageData.buffer,
|
|
16
|
+
packageData.byteOffset,
|
|
17
|
+
packageData.byteLength
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect(packageView.getUint32(0, true)).toBe(0x50504b4f);
|
|
21
|
+
expect(packageView.getUint32(4, true)).toBe(1);
|
|
22
|
+
expect(packageView.getUint32(8, true)).toBe(0x43534552);
|
|
23
|
+
expect(packageView.getUint32(0x0c, true)).toBe(headerSize);
|
|
24
|
+
expect(packageView.getUint32(0x10, true)).toBe(1);
|
|
25
|
+
expect(packageView.getUint32(0x14, true)).toBe(payload.byteLength);
|
|
26
|
+
expect(packageData.subarray(0x200, 0x240)).toEqual(sha3_512(payload));
|
|
27
|
+
expect(packageData.subarray(0x240, 0x280)).toEqual(sha3_512(packageData.subarray(0, 0x240)));
|
|
28
|
+
expect(packageData[0x400]).toBe(0);
|
|
29
|
+
expect(packageView.getUint32(0x408, true)).toBe(0x71717171);
|
|
30
|
+
|
|
31
|
+
const archiveView = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
|
|
32
|
+
expect(archiveView.getUint32(0, true)).toBe(0x52414b4f);
|
|
33
|
+
expect(archiveView.getUint32(4, true)).toBe(1);
|
|
34
|
+
expect(archiveView.getUint16(8, true)).toBe(1);
|
|
35
|
+
expect(new TextDecoder().decode(payload.subarray(43, 56))).toBe('wallpaper.bin');
|
|
36
|
+
expect(archiveView.getUint32(42 + 0x100, true)).toBe(340);
|
|
37
|
+
expect(archiveView.getUint32(42 + 0x104, true)).toBe(raw.byteLength);
|
|
38
|
+
expect(archiveView.getUint32(42 + 0x10c, true)).toBe(0xcbf43926);
|
|
39
|
+
expect(payload[42 + 0x114]).toBe(1);
|
|
40
|
+
|
|
41
|
+
const compressedOffset = archiveView.getUint32(42 + 0x100, true);
|
|
42
|
+
expect(archiveView.getUint16(compressedOffset, true)).toBe(1);
|
|
43
|
+
expect(archiveView.getUint16(compressedOffset + 2, true)).toBe(12);
|
|
44
|
+
expect(archiveView.getUint32(compressedOffset + 4, true)).toBe(0);
|
|
45
|
+
expect(archiveView.getUint32(compressedOffset + 8, true)).toBe(10);
|
|
46
|
+
expect(payload.subarray(compressedOffset + 12)).toEqual(new Uint8Array([0x90, ...raw]));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test.each([
|
|
50
|
+
['1.0.0', false],
|
|
51
|
+
['1.0.1-beta.1', false],
|
|
52
|
+
['1.0.1', true],
|
|
53
|
+
['1.1.0', true],
|
|
54
|
+
[undefined, false],
|
|
55
|
+
])('selects package uploads for firmware %s', (firmwareVersion, expected) => {
|
|
56
|
+
expect(supportsPro2HostAssetPackage(firmwareVersion)).toBe(expected);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -254,6 +254,88 @@ describe('DeviceUploadWallpaper', () => {
|
|
|
254
254
|
});
|
|
255
255
|
});
|
|
256
256
|
|
|
257
|
+
test('uploads and applies the fixed wallpaper package on firmware 1.0.1', async () => {
|
|
258
|
+
const typedCall = jest.fn().mockImplementation((request, _response, params) => {
|
|
259
|
+
if (request === 'FilesystemDirMake') return { message: {} };
|
|
260
|
+
if (request === 'FilesystemFileWrite') {
|
|
261
|
+
const file = params.file as { data: Uint8Array; offset: number };
|
|
262
|
+
return { message: { processed_byte: file.offset + file.data.byteLength } };
|
|
263
|
+
}
|
|
264
|
+
if (request === 'DeviceSettingsSet') {
|
|
265
|
+
return { message: { message: 'wallpaper applied' } };
|
|
266
|
+
}
|
|
267
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
268
|
+
});
|
|
269
|
+
const method = new DeviceUploadWallpaper({
|
|
270
|
+
id: 1,
|
|
271
|
+
payload: {
|
|
272
|
+
method: 'deviceUploadWallpaper',
|
|
273
|
+
jpegBase64: createJpegBase64(604, 1024),
|
|
274
|
+
fileName: 'ignored-on-package-firmware.bin',
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
const device = stubWallpaperDevice({
|
|
278
|
+
commands: { typedCall },
|
|
279
|
+
state: { versions: { firmware: '1.0.1' } },
|
|
280
|
+
getCurrentFirmwareVersionString: jest.fn(() => '1.0.1'),
|
|
281
|
+
});
|
|
282
|
+
(method as any).device = device;
|
|
283
|
+
method.postMessage = jest.fn();
|
|
284
|
+
|
|
285
|
+
method.init();
|
|
286
|
+
const result = await method.run();
|
|
287
|
+
|
|
288
|
+
const fileWrites = typedCall.mock.calls.filter(call => call[0] === 'FilesystemFileWrite');
|
|
289
|
+
expect(new Set(fileWrites.map(call => call[2].file.path))).toEqual(
|
|
290
|
+
new Set(['vol1:/wallpapers/wallpaper.okpkg'])
|
|
291
|
+
);
|
|
292
|
+
expect(fileWrites[0][2].file.data.subarray(0, 4)).toEqual(
|
|
293
|
+
new Uint8Array([0x4f, 0x4b, 0x50, 0x50])
|
|
294
|
+
);
|
|
295
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSettingsSet', 'Success', {
|
|
296
|
+
settings: { wallpaper_path: 'vol1:/wallpapers/wallpaper.okpkg' },
|
|
297
|
+
});
|
|
298
|
+
expect(result).toMatchObject({
|
|
299
|
+
path: 'vol1:/wallpapers/wallpaper.okpkg',
|
|
300
|
+
colorFormat: 'RGB565',
|
|
301
|
+
message: 'wallpaper applied',
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test('keeps the legacy wallpaper path on a 1.0.1 prerelease', async () => {
|
|
306
|
+
const typedCall = jest.fn().mockImplementation((request, _response, params) => {
|
|
307
|
+
if (request === 'FilesystemDirMake') return { message: {} };
|
|
308
|
+
if (request === 'FilesystemFileWrite') {
|
|
309
|
+
const file = params.file as { data: Uint8Array; offset: number };
|
|
310
|
+
return { message: { processed_byte: file.offset + file.data.byteLength } };
|
|
311
|
+
}
|
|
312
|
+
if (request === 'DeviceSettingsSet') return { message: {} };
|
|
313
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
314
|
+
});
|
|
315
|
+
const method = new DeviceUploadWallpaper({
|
|
316
|
+
id: 1,
|
|
317
|
+
payload: {
|
|
318
|
+
method: 'deviceUploadWallpaper',
|
|
319
|
+
jpegBase64: createJpegBase64(604, 1024),
|
|
320
|
+
fileName: 'prerelease-wallpaper.bin',
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
const device = stubWallpaperDevice({
|
|
324
|
+
commands: { typedCall },
|
|
325
|
+
state: { versions: { firmware: '1.0.1-beta.1' } },
|
|
326
|
+
});
|
|
327
|
+
(method as any).device = device;
|
|
328
|
+
|
|
329
|
+
method.init();
|
|
330
|
+
const result = await method.run();
|
|
331
|
+
|
|
332
|
+
expect(result.path).toBe('vol1:/wallpapers/prerelease-wallpaper.bin');
|
|
333
|
+
const fileWrites = typedCall.mock.calls.filter(call => call[0] === 'FilesystemFileWrite');
|
|
334
|
+
expect(new Set(fileWrites.map(call => call[2].file.path))).toEqual(
|
|
335
|
+
new Set(['vol1:/wallpapers/prerelease-wallpaper.bin'])
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
|
|
257
339
|
test('文件上传失败时不修改 wallpaper_path', async () => {
|
|
258
340
|
const typedCall = jest.fn().mockImplementation(request => {
|
|
259
341
|
if (request === 'FilesystemDirMake') return { message: {} };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EVMSignTypedData.d.ts","sourceRoot":"","sources":["../../../src/api/evm/EVMSignTypedData.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAI3C,OAAO,EAEL,KAAK,4BAA4B,EACjC,KAAK,0BAA0B,EAChC,MAAM,aAAa,CAAC;AAQrB,OAAO,KAAK,EAIV,UAAU,EACV,eAAe,EACf,SAAS,EACV,MAAM,wBAAwB,CAAC;AAShC,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,IAAI,EAAE,4BAA4B,CAAC,0BAA0B,CAAC,CAAC;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAUF,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,UAAU,CAAC,sBAAsB,CAAC;IAC9E,qBAAqB;IAIrB,IAAI;IAuCE,mBAAmB,CAAC,EACxB,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,aAAa,GACd,EAAE;QACD,SAAS,EAAE,SAAS,CAAC;QACrB,QAAQ,EAAE,4BAA4B,CAAC,0BAA0B,CAAC,CAAC;QACnE,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC;QACtC,aAAa,EAAE,OAAO,CAAC;KACxB;;;;IAoKK,aAAa;;;;IAgCnB,aAAa,CAAC,EACZ,SAAS,EACT,QAAQ,EACR,OAAO,EACP,UAAU,EACV,WAAW,GACZ,EAAE;QACD,SAAS,EAAE,SAAS,CAAC;QACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;QAC5B,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;QAC/B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;KACjC;IAuBD,eAAe;;;;;IAQf,aAAa,CAAC,IAAI,EAAE,4BAA4B,CAAC,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"EVMSignTypedData.d.ts","sourceRoot":"","sources":["../../../src/api/evm/EVMSignTypedData.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAI3C,OAAO,EAEL,KAAK,4BAA4B,EACjC,KAAK,0BAA0B,EAChC,MAAM,aAAa,CAAC;AAQrB,OAAO,KAAK,EAIV,UAAU,EACV,eAAe,EACf,SAAS,EACV,MAAM,wBAAwB,CAAC;AAShC,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,IAAI,EAAE,4BAA4B,CAAC,0BAA0B,CAAC,CAAC;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAUF,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,UAAU,CAAC,sBAAsB,CAAC;IAC9E,qBAAqB;IAIrB,IAAI;IAuCE,mBAAmB,CAAC,EACxB,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,aAAa,GACd,EAAE;QACD,SAAS,EAAE,SAAS,CAAC;QACrB,QAAQ,EAAE,4BAA4B,CAAC,0BAA0B,CAAC,CAAC;QACnE,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC;QACtC,aAAa,EAAE,OAAO,CAAC;KACxB;;;;IAoKK,aAAa;;;;IAgCnB,aAAa,CAAC,EACZ,SAAS,EACT,QAAQ,EACR,OAAO,EACP,UAAU,EACV,WAAW,GACZ,EAAE;QACD,SAAS,EAAE,SAAS,CAAC;QACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;QAC5B,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;QAC/B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;KACjC;IAuBD,eAAe;;;;;IAQf,aAAa,CAAC,IAAI,EAAE,4BAA4B,CAAC,0BAA0B,CAAC;IAwB5E,eAAe,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO;IA8BnC,yCAAyC,CACvC,IAAI,EAAE,4BAA4B,CAAC,0BAA0B,CAAC;IAuIhE,gBAAgB;IAcV,GAAG;CAgFV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeviceUploadNft.d.ts","sourceRoot":"","sources":["../../../src/api/protocol-v2/DeviceUploadNft.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DeviceUploadNft.d.ts","sourceRoot":"","sources":["../../../src/api/protocol-v2/DeviceUploadNft.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAK3C,MAAM,MAAM,qBAAqB,GAAG;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,MAAM,CAAC;IAEjB,SAAS,EAAE,MAAM,CAAC;IAElB,aAAa,EAAE,MAAM,CAAC;IAEtB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,IAAI,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAOF,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,UAAU,CAAC,qBAAqB,CAAC;IAC5E,OAAO,CAAC,MAAM,CAAC,CAAgB;IAE/B,qBAAqB;IAIrB,IAAI;YAgFU,kBAAkB;YAiBlB,qBAAqB;YAwBrB,SAAS;IASjB,GAAG,IAAI,OAAO,CAAC,uBAAuB,CAAC;CA0E9C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeviceUploadWallpaper.d.ts","sourceRoot":"","sources":["../../../src/api/protocol-v2/DeviceUploadWallpaper.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAO3C,OAAO,EAGL,KAAK,wBAAwB,EAE9B,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"DeviceUploadWallpaper.d.ts","sourceRoot":"","sources":["../../../src/api/protocol-v2/DeviceUploadWallpaper.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAO3C,OAAO,EAGL,KAAK,wBAAwB,EAE9B,MAAM,2BAA2B,CAAC;AAMnC,MAAM,MAAM,2BAA2B,GAAG;IACxC,UAAU,EAAE,MAAM,CAAC;IAKnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAK1C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,wBAAwB,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAqBF,MAAM,CAAC,OAAO,OAAO,qBAAsB,SAAQ,UAAU,CAAC,2BAA2B,CAAC;IACxF,qBAAqB;IAIrB,OAAO,CAAC,OAAO,CAAC,CAA8D;IAE9E,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,IAAI,CAAM;IAElB,IAAI;YA2BU,kBAAkB;YAgBlB,eAAe;YAaf,MAAM;IAsBd,GAAG,IAAI,OAAO,CAAC,6BAA6B,CAAC;CAgCpD"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1127,10 +1127,18 @@ declare function encodePro2Wallpaper(options: {
|
|
|
1127
1127
|
|
|
1128
1128
|
type DeviceUploadWallpaperParams = {
|
|
1129
1129
|
jpegBase64: string;
|
|
1130
|
+
/**
|
|
1131
|
+
* Legacy firmware uses this name for the uploaded `.bin` file. Firmware
|
|
1132
|
+
* 1.0.1+ always consumes the fixed `wallpaper.okpkg` package path.
|
|
1133
|
+
*/
|
|
1130
1134
|
fileName?: string;
|
|
1131
1135
|
chunkSize?: number;
|
|
1132
1136
|
};
|
|
1133
1137
|
type DeviceUploadWallpaperResponse = {
|
|
1138
|
+
/**
|
|
1139
|
+
* Filesystem path sent to `DeviceSettingsSet`. On firmware 1.0.1+ this is
|
|
1140
|
+
* the temporary package path; firmware extracts and persists wallpaper.bin.
|
|
1141
|
+
*/
|
|
1134
1142
|
path: string;
|
|
1135
1143
|
size: number;
|
|
1136
1144
|
colorFormat: Pro2WallpaperColorFormat;
|
|
@@ -1149,8 +1157,11 @@ type DeviceUploadNftParams = {
|
|
|
1149
1157
|
};
|
|
1150
1158
|
type DeviceUploadNftResponse = {
|
|
1151
1159
|
basename: string;
|
|
1160
|
+
/** Final image path after firmware extracts a host-asset package. */
|
|
1152
1161
|
imagePath: string;
|
|
1162
|
+
/** Final thumbnail path after firmware extracts a host-asset package. */
|
|
1153
1163
|
thumbnailPath: string;
|
|
1164
|
+
/** Final metadata path after firmware extracts a host-asset package. */
|
|
1154
1165
|
metadataPath: string;
|
|
1155
1166
|
totalSize: number;
|
|
1156
1167
|
nftUpdated: true;
|
package/dist/index.js
CHANGED
|
@@ -42623,7 +42623,7 @@ function invalidParameter$2(message) {
|
|
|
42623
42623
|
function asBytes(rgba) {
|
|
42624
42624
|
return rgba instanceof Uint8Array ? rgba : new Uint8Array(rgba);
|
|
42625
42625
|
}
|
|
42626
|
-
function align(value, boundary) {
|
|
42626
|
+
function align$1(value, boundary) {
|
|
42627
42627
|
return Math.ceil(value / boundary) * boundary;
|
|
42628
42628
|
}
|
|
42629
42629
|
function encodePro2Image(options) {
|
|
@@ -42651,7 +42651,7 @@ function encodePro2Image(options) {
|
|
|
42651
42651
|
}
|
|
42652
42652
|
}
|
|
42653
42653
|
const colorFormat = hasTransparency ? 'RGB565A8' : 'RGB565';
|
|
42654
|
-
const stride = align(width * 2, 4);
|
|
42654
|
+
const stride = align$1(width * 2, 4);
|
|
42655
42655
|
const alphaStride = stride / 2;
|
|
42656
42656
|
const rgbSize = stride * height;
|
|
42657
42657
|
const alphaSize = hasTransparency ? alphaStride * height : 0;
|
|
@@ -54612,7 +54612,234 @@ function decodeJpegBase64ToRgba({ jpegBase64, parameterName, expectedWidth, expe
|
|
|
54612
54612
|
return decoded;
|
|
54613
54613
|
}
|
|
54614
54614
|
|
|
54615
|
+
const PRO2_HOST_ASSET_PACKAGE_MIN_VERSION = '1.0.1';
|
|
54616
|
+
const CONTAINER_HEADER_SIZE = 0x5f90;
|
|
54617
|
+
const CONTAINER_HEADER_HASH_INPUT_LENGTH = 0x240;
|
|
54618
|
+
const CONTAINER_HASH_SECTION_OFFSET = 0x200;
|
|
54619
|
+
const CONTAINER_SIGNATURE_ALGORITHM_OFFSET = 0x408;
|
|
54620
|
+
const CONTAINER_HEADER_MAGIC = 0x50504b4f;
|
|
54621
|
+
const CONTAINER_HEADER_VERSION = 1;
|
|
54622
|
+
const CONTAINER_RESOURCE_TYPE_MAGIC = 0x43534552;
|
|
54623
|
+
const CONTAINER_ED25519_SIGNATURE_ALGORITHM = 0x71717171;
|
|
54624
|
+
const HOST_ASSET_PACKAGE_MAX_SIZE = 4 * 1024 * 1024;
|
|
54625
|
+
const ARCHIVE_MAGIC = 0x52414b4f;
|
|
54626
|
+
const ARCHIVE_VERSION = 1;
|
|
54627
|
+
const ARCHIVE_HEADER_SIZE = 42;
|
|
54628
|
+
const ARCHIVE_ENTRY_SIZE = 296;
|
|
54629
|
+
const ARCHIVE_ENTRY_NAME_MAX_LENGTH = 255;
|
|
54630
|
+
const ARCHIVE_COMPRESS_LZ4_BLOCKED = 1;
|
|
54631
|
+
const ARCHIVE_ALIGNMENT = 4;
|
|
54632
|
+
const LZ4_BLOCK_SIZE_LOG2 = 12;
|
|
54633
|
+
function supportsPro2HostAssetPackage(firmwareVersion) {
|
|
54634
|
+
return Boolean(firmwareVersion &&
|
|
54635
|
+
semver__default["default"].valid(firmwareVersion) &&
|
|
54636
|
+
semver__default["default"].gte(firmwareVersion, PRO2_HOST_ASSET_PACKAGE_MIN_VERSION));
|
|
54637
|
+
}
|
|
54638
|
+
function concatBytes(parts) {
|
|
54639
|
+
const output = new Uint8Array(parts.reduce((length, part) => length + part.byteLength, 0));
|
|
54640
|
+
let offset = 0;
|
|
54641
|
+
for (const part of parts) {
|
|
54642
|
+
output.set(part, offset);
|
|
54643
|
+
offset += part.byteLength;
|
|
54644
|
+
}
|
|
54645
|
+
return output;
|
|
54646
|
+
}
|
|
54647
|
+
const LZ4_MIN_MATCH = 4;
|
|
54648
|
+
const LZ4_LAST_LITERALS = 5;
|
|
54649
|
+
const LZ4_MATCH_FIND_LIMIT = 12;
|
|
54650
|
+
const LZ4_MAX_OFFSET = 0xffff;
|
|
54651
|
+
const LZ4_LENGTH_MASK = 15;
|
|
54652
|
+
const LZ4_HASH_LOG = 16;
|
|
54653
|
+
const LZ4_HASH_MULTIPLIER = 2654435761;
|
|
54654
|
+
const LZ4_SKIP_TRIGGER = 6;
|
|
54655
|
+
function writeExtendedLength(output, offset, length) {
|
|
54656
|
+
let remaining = length;
|
|
54657
|
+
let nextOffset = offset;
|
|
54658
|
+
while (remaining >= 255) {
|
|
54659
|
+
output[nextOffset] = 255;
|
|
54660
|
+
nextOffset += 1;
|
|
54661
|
+
remaining -= 255;
|
|
54662
|
+
}
|
|
54663
|
+
output[nextOffset] = remaining;
|
|
54664
|
+
return nextOffset + 1;
|
|
54665
|
+
}
|
|
54666
|
+
function copyBytes(output, outputOffset, input, inputOffset, length) {
|
|
54667
|
+
output.set(input.subarray(inputOffset, inputOffset + length), outputOffset);
|
|
54668
|
+
return outputOffset + length;
|
|
54669
|
+
}
|
|
54670
|
+
function emitSequence(output, outputOffset, input, anchor, literalLength, matchOffset, matchLengthCode) {
|
|
54671
|
+
const tokenOffset = outputOffset;
|
|
54672
|
+
let nextOffset = outputOffset + 1;
|
|
54673
|
+
let token = 0;
|
|
54674
|
+
if (literalLength >= LZ4_LENGTH_MASK) {
|
|
54675
|
+
token = LZ4_LENGTH_MASK << 4;
|
|
54676
|
+
nextOffset = writeExtendedLength(output, nextOffset, literalLength - LZ4_LENGTH_MASK);
|
|
54677
|
+
}
|
|
54678
|
+
else {
|
|
54679
|
+
token = literalLength << 4;
|
|
54680
|
+
}
|
|
54681
|
+
nextOffset = copyBytes(output, nextOffset, input, anchor, literalLength);
|
|
54682
|
+
output[nextOffset] = matchOffset & 0xff;
|
|
54683
|
+
output[nextOffset + 1] = (matchOffset >>> 8) & 0xff;
|
|
54684
|
+
nextOffset += 2;
|
|
54685
|
+
if (matchLengthCode >= LZ4_LENGTH_MASK) {
|
|
54686
|
+
token |= LZ4_LENGTH_MASK;
|
|
54687
|
+
nextOffset = writeExtendedLength(output, nextOffset, matchLengthCode - LZ4_LENGTH_MASK);
|
|
54688
|
+
}
|
|
54689
|
+
else {
|
|
54690
|
+
token |= matchLengthCode;
|
|
54691
|
+
}
|
|
54692
|
+
output[tokenOffset] = token;
|
|
54693
|
+
return nextOffset;
|
|
54694
|
+
}
|
|
54695
|
+
function emitLastLiterals(output, outputOffset, input, anchor, literalLength) {
|
|
54696
|
+
const tokenOffset = outputOffset;
|
|
54697
|
+
let nextOffset = outputOffset + 1;
|
|
54698
|
+
if (literalLength >= LZ4_LENGTH_MASK) {
|
|
54699
|
+
output[tokenOffset] = LZ4_LENGTH_MASK << 4;
|
|
54700
|
+
nextOffset = writeExtendedLength(output, nextOffset, literalLength - LZ4_LENGTH_MASK);
|
|
54701
|
+
}
|
|
54702
|
+
else {
|
|
54703
|
+
output[tokenOffset] = literalLength << 4;
|
|
54704
|
+
}
|
|
54705
|
+
return copyBytes(output, nextOffset, input, anchor, literalLength);
|
|
54706
|
+
}
|
|
54707
|
+
function compressRawLz4Block(input, hashTable) {
|
|
54708
|
+
const output = new Uint8Array(input.byteLength + Math.floor(input.byteLength / 255) + 16);
|
|
54709
|
+
const inputView = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
|
54710
|
+
const matchFindLimit = input.byteLength - LZ4_MATCH_FIND_LIMIT;
|
|
54711
|
+
const matchExtendLimit = input.byteLength - LZ4_LAST_LITERALS;
|
|
54712
|
+
let anchor = 0;
|
|
54713
|
+
let inputOffset = 0;
|
|
54714
|
+
let outputOffset = 0;
|
|
54715
|
+
let searchMatchCount = 1 << LZ4_SKIP_TRIGGER;
|
|
54716
|
+
hashTable.fill(0);
|
|
54717
|
+
while (inputOffset < matchFindLimit) {
|
|
54718
|
+
const sequence = inputView.getUint32(inputOffset, true);
|
|
54719
|
+
const hash = Math.imul(sequence, LZ4_HASH_MULTIPLIER) >>> (32 - LZ4_HASH_LOG);
|
|
54720
|
+
const candidate = hashTable[hash] - 1;
|
|
54721
|
+
hashTable[hash] = inputOffset + 1;
|
|
54722
|
+
const hasMatch = !(candidate < 0 ||
|
|
54723
|
+
inputOffset - candidate > LZ4_MAX_OFFSET ||
|
|
54724
|
+
inputView.getUint32(candidate, true) !== sequence);
|
|
54725
|
+
if (!hasMatch) {
|
|
54726
|
+
inputOffset += searchMatchCount >> LZ4_SKIP_TRIGGER;
|
|
54727
|
+
searchMatchCount += 1;
|
|
54728
|
+
}
|
|
54729
|
+
else {
|
|
54730
|
+
searchMatchCount = 1 << LZ4_SKIP_TRIGGER;
|
|
54731
|
+
let matchEnd = inputOffset + LZ4_MIN_MATCH;
|
|
54732
|
+
let reference = candidate + LZ4_MIN_MATCH;
|
|
54733
|
+
while (matchEnd < matchExtendLimit && input[matchEnd] === input[reference]) {
|
|
54734
|
+
matchEnd += 1;
|
|
54735
|
+
reference += 1;
|
|
54736
|
+
}
|
|
54737
|
+
outputOffset = emitSequence(output, outputOffset, input, anchor, inputOffset - anchor, inputOffset - candidate, matchEnd - inputOffset - LZ4_MIN_MATCH);
|
|
54738
|
+
inputOffset = matchEnd;
|
|
54739
|
+
anchor = inputOffset;
|
|
54740
|
+
}
|
|
54741
|
+
}
|
|
54742
|
+
outputOffset = emitLastLiterals(output, outputOffset, input, anchor, input.byteLength - anchor);
|
|
54743
|
+
return output.slice(0, outputOffset);
|
|
54744
|
+
}
|
|
54745
|
+
function encodeLz4Blocked(data) {
|
|
54746
|
+
const blockSize = 1 << LZ4_BLOCK_SIZE_LOG2;
|
|
54747
|
+
const blockCount = Math.ceil(data.byteLength / blockSize);
|
|
54748
|
+
const hashTable = new Uint32Array(1 << LZ4_HASH_LOG);
|
|
54749
|
+
const blocks = [];
|
|
54750
|
+
const header = new Uint8Array(8 + blockCount * 4);
|
|
54751
|
+
const headerView = new DataView(header.buffer);
|
|
54752
|
+
headerView.setUint16(0, blockCount, true);
|
|
54753
|
+
headerView.setUint16(2, LZ4_BLOCK_SIZE_LOG2, true);
|
|
54754
|
+
for (let index = 0; index < blockCount; index += 1) {
|
|
54755
|
+
const block = compressRawLz4Block(data.subarray(index * blockSize, Math.min((index + 1) * blockSize, data.byteLength)), hashTable);
|
|
54756
|
+
headerView.setUint32(8 + index * 4, block.byteLength, true);
|
|
54757
|
+
blocks.push(block);
|
|
54758
|
+
}
|
|
54759
|
+
return concatBytes([header, ...blocks]);
|
|
54760
|
+
}
|
|
54761
|
+
const CRC32_TABLE = (() => {
|
|
54762
|
+
const table = new Uint32Array(256);
|
|
54763
|
+
for (let index = 0; index < table.length; index += 1) {
|
|
54764
|
+
let value = index;
|
|
54765
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
54766
|
+
value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
|
|
54767
|
+
}
|
|
54768
|
+
table[index] = value >>> 0;
|
|
54769
|
+
}
|
|
54770
|
+
return table;
|
|
54771
|
+
})();
|
|
54772
|
+
function crc32(data) {
|
|
54773
|
+
let value = 0xffffffff;
|
|
54774
|
+
for (const byte of data) {
|
|
54775
|
+
value = CRC32_TABLE[(value ^ byte) & 0xff] ^ (value >>> 8);
|
|
54776
|
+
}
|
|
54777
|
+
return (value ^ 0xffffffff) >>> 0;
|
|
54778
|
+
}
|
|
54779
|
+
function align(value) {
|
|
54780
|
+
return (value + ARCHIVE_ALIGNMENT - 1) & ~(ARCHIVE_ALIGNMENT - 1);
|
|
54781
|
+
}
|
|
54782
|
+
function buildArchive(entries) {
|
|
54783
|
+
const textEncoder = new TextEncoder();
|
|
54784
|
+
let dataOffset = ARCHIVE_HEADER_SIZE + entries.length * ARCHIVE_ENTRY_SIZE;
|
|
54785
|
+
const encodedEntries = entries.map(entry => {
|
|
54786
|
+
const nameBytes = textEncoder.encode(entry.name);
|
|
54787
|
+
if (!entry.name || nameBytes.byteLength > ARCHIVE_ENTRY_NAME_MAX_LENGTH) {
|
|
54788
|
+
throw new Error('Pro2 host asset package entry names must contain 1 to 255 UTF-8 bytes.');
|
|
54789
|
+
}
|
|
54790
|
+
if (!(entry.data instanceof Uint8Array) || entry.data.byteLength === 0) {
|
|
54791
|
+
throw new Error(`Pro2 host asset package entry [${entry.name}] must not be empty.`);
|
|
54792
|
+
}
|
|
54793
|
+
const compressed = encodeLz4Blocked(entry.data);
|
|
54794
|
+
const offset = align(dataOffset);
|
|
54795
|
+
dataOffset = offset + compressed.byteLength;
|
|
54796
|
+
return Object.assign(Object.assign({}, entry), { nameBytes, compressed, offset });
|
|
54797
|
+
});
|
|
54798
|
+
const archive = new Uint8Array(dataOffset);
|
|
54799
|
+
const view = new DataView(archive.buffer);
|
|
54800
|
+
view.setUint32(0, ARCHIVE_MAGIC, true);
|
|
54801
|
+
view.setUint32(4, ARCHIVE_VERSION, true);
|
|
54802
|
+
view.setUint16(8, encodedEntries.length, true);
|
|
54803
|
+
encodedEntries.forEach((entry, index) => {
|
|
54804
|
+
const recordOffset = ARCHIVE_HEADER_SIZE + index * ARCHIVE_ENTRY_SIZE;
|
|
54805
|
+
archive[recordOffset] = entry.nameBytes.byteLength;
|
|
54806
|
+
archive.set(entry.nameBytes, recordOffset + 1);
|
|
54807
|
+
view.setUint32(recordOffset + 0x100, entry.offset, true);
|
|
54808
|
+
view.setUint32(recordOffset + 0x104, entry.data.byteLength, true);
|
|
54809
|
+
view.setUint32(recordOffset + 0x108, entry.compressed.byteLength, true);
|
|
54810
|
+
view.setUint32(recordOffset + 0x10c, crc32(entry.data), true);
|
|
54811
|
+
view.setUint32(recordOffset + 0x110, crc32(entry.compressed), true);
|
|
54812
|
+
archive[recordOffset + 0x114] = ARCHIVE_COMPRESS_LZ4_BLOCKED;
|
|
54813
|
+
archive.set(entry.compressed, entry.offset);
|
|
54814
|
+
});
|
|
54815
|
+
return archive;
|
|
54816
|
+
}
|
|
54817
|
+
function buildPro2HostAssetPackage(entries) {
|
|
54818
|
+
if (entries.length === 0 || new Set(entries.map(entry => entry.name)).size !== entries.length) {
|
|
54819
|
+
throw new Error('Pro2 host asset package entries must have unique, non-empty names.');
|
|
54820
|
+
}
|
|
54821
|
+
const payload = buildArchive(entries);
|
|
54822
|
+
const header = new Uint8Array(CONTAINER_HEADER_SIZE);
|
|
54823
|
+
const view = new DataView(header.buffer);
|
|
54824
|
+
view.setUint32(0, CONTAINER_HEADER_MAGIC, true);
|
|
54825
|
+
view.setUint32(4, CONTAINER_HEADER_VERSION, true);
|
|
54826
|
+
view.setUint32(8, CONTAINER_RESOURCE_TYPE_MAGIC, true);
|
|
54827
|
+
view.setUint32(0x0c, CONTAINER_HEADER_SIZE, true);
|
|
54828
|
+
view.setUint32(0x10, 1, true);
|
|
54829
|
+
view.setUint32(0x14, payload.byteLength, true);
|
|
54830
|
+
header.set(sha3.sha3_512(payload), CONTAINER_HASH_SECTION_OFFSET);
|
|
54831
|
+
header.set(sha3.sha3_512(header.subarray(0, CONTAINER_HEADER_HASH_INPUT_LENGTH)), 0x240);
|
|
54832
|
+
view.setUint32(CONTAINER_SIGNATURE_ALGORITHM_OFFSET, CONTAINER_ED25519_SIGNATURE_ALGORITHM, true);
|
|
54833
|
+
const packageData = concatBytes([header, payload]);
|
|
54834
|
+
if (packageData.byteLength > HOST_ASSET_PACKAGE_MAX_SIZE) {
|
|
54835
|
+
throw new Error('Pro2 host asset package exceeds the firmware 4 MiB limit.');
|
|
54836
|
+
}
|
|
54837
|
+
return packageData;
|
|
54838
|
+
}
|
|
54839
|
+
|
|
54615
54840
|
const WALLPAPER_DIRECTORY = 'vol1:/wallpapers';
|
|
54841
|
+
const WALLPAPER_PACKAGE_PATH = `${WALLPAPER_DIRECTORY}/wallpaper.okpkg`;
|
|
54842
|
+
const WALLPAPER_PACKAGE_ENTRY = 'wallpaper.bin';
|
|
54616
54843
|
const SAFE_FILE_NAME = /^[A-Za-z0-9_-]+(?:\.bin)?$/;
|
|
54617
54844
|
const DEVICE_SETTINGS_SET_MESSAGE_TYPE = 60412;
|
|
54618
54845
|
const FILESYSTEM_FILE_WRITE_MESSAGE_TYPE$2 = 60805;
|
|
@@ -54688,18 +54915,15 @@ class DeviceUploadWallpaper extends BaseMethod {
|
|
|
54688
54915
|
this.directoryReady = true;
|
|
54689
54916
|
});
|
|
54690
54917
|
}
|
|
54691
|
-
upload() {
|
|
54918
|
+
upload(path, data) {
|
|
54692
54919
|
return __awaiter(this, void 0, void 0, function* () {
|
|
54693
54920
|
if (this.uploaded)
|
|
54694
54921
|
return;
|
|
54695
|
-
const { encoded } = this;
|
|
54696
|
-
if (!encoded)
|
|
54697
|
-
throw invalidParameter$1('Wallpaper data has not been initialized.');
|
|
54698
54922
|
yield writeProtocolV2File({
|
|
54699
54923
|
commands: this.device.commands,
|
|
54700
|
-
path
|
|
54701
|
-
data
|
|
54702
|
-
totalSize:
|
|
54924
|
+
path,
|
|
54925
|
+
data,
|
|
54926
|
+
totalSize: data.byteLength,
|
|
54703
54927
|
chunkSize: this.params.chunkSize,
|
|
54704
54928
|
maxChunkRetries: 3,
|
|
54705
54929
|
overwrite: true,
|
|
@@ -54715,14 +54939,20 @@ class DeviceUploadWallpaper extends BaseMethod {
|
|
|
54715
54939
|
});
|
|
54716
54940
|
}
|
|
54717
54941
|
run() {
|
|
54718
|
-
var _a;
|
|
54942
|
+
var _a, _b, _c;
|
|
54719
54943
|
return __awaiter(this, void 0, void 0, function* () {
|
|
54720
54944
|
const { encoded } = this;
|
|
54721
54945
|
if (!encoded)
|
|
54722
54946
|
throw invalidParameter$1('Wallpaper data has not been initialized.');
|
|
54723
54947
|
yield this.assertCapabilities();
|
|
54724
54948
|
yield this.ensureDirectory();
|
|
54725
|
-
|
|
54949
|
+
const useHostAssetPackage = supportsPro2HostAssetPackage((_b = (_a = this.device.state) === null || _a === void 0 ? void 0 : _a.versions.firmware) !== null && _b !== void 0 ? _b : undefined);
|
|
54950
|
+
const data = useHostAssetPackage
|
|
54951
|
+
? buildPro2HostAssetPackage([{ name: WALLPAPER_PACKAGE_ENTRY, data: encoded.data }])
|
|
54952
|
+
: encoded.data;
|
|
54953
|
+
if (useHostAssetPackage)
|
|
54954
|
+
this.path = WALLPAPER_PACKAGE_PATH;
|
|
54955
|
+
yield this.upload(this.path, data);
|
|
54726
54956
|
const response = yield this.device.commands.typedCall('DeviceSettingsSet', 'Success', {
|
|
54727
54957
|
settings: { wallpaper_path: this.path },
|
|
54728
54958
|
});
|
|
@@ -54736,9 +54966,9 @@ class DeviceUploadWallpaper extends BaseMethod {
|
|
|
54736
54966
|
}
|
|
54737
54967
|
return {
|
|
54738
54968
|
path: this.path,
|
|
54739
|
-
size:
|
|
54969
|
+
size: data.byteLength,
|
|
54740
54970
|
colorFormat: encoded.colorFormat,
|
|
54741
|
-
message: (
|
|
54971
|
+
message: (_c = response.message) === null || _c === void 0 ? void 0 : _c.message,
|
|
54742
54972
|
};
|
|
54743
54973
|
});
|
|
54744
54974
|
}
|
|
@@ -54815,14 +55045,14 @@ class DeviceUploadNft extends BaseMethod {
|
|
|
54815
55045
|
this.skipForceUpdateCheck = true;
|
|
54816
55046
|
this.useDevicePassphraseState = false;
|
|
54817
55047
|
}
|
|
54818
|
-
assertCapabilities() {
|
|
55048
|
+
assertCapabilities(useHostAssetPackage) {
|
|
54819
55049
|
return __awaiter(this, void 0, void 0, function* () {
|
|
54820
55050
|
const protocolInfo = yield this.device.ensureProtocolV2RuntimeContext();
|
|
54821
55051
|
const hasFileWrite = supportsProtocolV2Message(protocolInfo, FILESYSTEM_FILE_WRITE_MESSAGE_TYPE$1);
|
|
54822
55052
|
const hasPathInfo = supportsProtocolV2Message(protocolInfo, FILESYSTEM_PATH_INFO_QUERY_MESSAGE_TYPE);
|
|
54823
55053
|
const hasDirList = supportsProtocolV2Message(protocolInfo, FILESYSTEM_DIR_LIST_MESSAGE_TYPE);
|
|
54824
55054
|
const hasNftUpdate = supportsProtocolV2Message(protocolInfo, NFT_UPDATE_MESSAGE_TYPE);
|
|
54825
|
-
if (!hasFileWrite || !
|
|
55055
|
+
if (!hasFileWrite || !hasNftUpdate || (!useHostAssetPackage && (!hasPathInfo || !hasDirList))) {
|
|
54826
55056
|
throw hdShared.createDeviceNotSupportMethodError(this.name, this.device.getCurrentFirmwareType());
|
|
54827
55057
|
}
|
|
54828
55058
|
});
|
|
@@ -54848,20 +55078,33 @@ class DeviceUploadNft extends BaseMethod {
|
|
|
54848
55078
|
});
|
|
54849
55079
|
}
|
|
54850
55080
|
run() {
|
|
54851
|
-
var _a;
|
|
55081
|
+
var _a, _b, _c;
|
|
54852
55082
|
return __awaiter(this, void 0, void 0, function* () {
|
|
54853
55083
|
const { bundle } = this;
|
|
54854
55084
|
if (!bundle)
|
|
54855
55085
|
throw invalidParameter$1('NFT data has not been initialized.');
|
|
54856
|
-
|
|
55086
|
+
const useHostAssetPackage = supportsPro2HostAssetPackage((_b = (_a = this.device.state) === null || _a === void 0 ? void 0 : _a.versions.firmware) !== null && _b !== void 0 ? _b : undefined);
|
|
55087
|
+
yield this.assertCapabilities(useHostAssetPackage);
|
|
54857
55088
|
this.throwIfAborted();
|
|
54858
|
-
|
|
55089
|
+
if (!useHostAssetPackage)
|
|
55090
|
+
yield this.assertStorageCapacity(bundle.basename);
|
|
54859
55091
|
this.throwIfAborted();
|
|
54860
|
-
const
|
|
55092
|
+
const extractedFiles = [
|
|
54861
55093
|
{ path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}.bin`, data: bundle.image },
|
|
54862
55094
|
{ path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}_m.bin`, data: bundle.thumbnail },
|
|
54863
55095
|
{ path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}.json`, data: bundle.metadata },
|
|
54864
55096
|
];
|
|
55097
|
+
const files = useHostAssetPackage
|
|
55098
|
+
? [
|
|
55099
|
+
{
|
|
55100
|
+
path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}.okpkg`,
|
|
55101
|
+
data: buildPro2HostAssetPackage(extractedFiles.map(file => ({
|
|
55102
|
+
name: file.path.slice(`${PRO2_NFT_DIRECTORY}/`.length),
|
|
55103
|
+
data: file.data,
|
|
55104
|
+
}))),
|
|
55105
|
+
},
|
|
55106
|
+
]
|
|
55107
|
+
: extractedFiles;
|
|
54865
55108
|
const totalSize = files.reduce((sum, file) => sum + file.data.byteLength, 0);
|
|
54866
55109
|
let transferredBeforeFile = 0;
|
|
54867
55110
|
for (const file of files) {
|
|
@@ -54890,12 +55133,12 @@ class DeviceUploadNft extends BaseMethod {
|
|
|
54890
55133
|
const response = yield this.updateNft(bundle.basename);
|
|
54891
55134
|
return {
|
|
54892
55135
|
basename: bundle.basename,
|
|
54893
|
-
imagePath:
|
|
54894
|
-
thumbnailPath:
|
|
54895
|
-
metadataPath:
|
|
55136
|
+
imagePath: extractedFiles[0].path,
|
|
55137
|
+
thumbnailPath: extractedFiles[1].path,
|
|
55138
|
+
metadataPath: extractedFiles[2].path,
|
|
54896
55139
|
totalSize,
|
|
54897
55140
|
nftUpdated: true,
|
|
54898
|
-
message: (
|
|
55141
|
+
message: (_c = response.message) === null || _c === void 0 ? void 0 : _c.message,
|
|
54899
55142
|
};
|
|
54900
55143
|
});
|
|
54901
55144
|
}
|
|
@@ -58187,6 +58430,7 @@ class EVMSignTypedData extends BaseMethod {
|
|
|
58187
58430
|
const currentDeviceType = this.device.getCurrentDeviceType();
|
|
58188
58431
|
const supportBiggerDataVersion = '4.4.0';
|
|
58189
58432
|
const supportBiggerData = DeviceModelToTypes.model_classic1s.includes(currentDeviceType) ||
|
|
58433
|
+
DeviceModelToTypes.model_pro2.includes(currentDeviceType) ||
|
|
58190
58434
|
(DeviceModelToTypes.model_touch.includes(currentDeviceType) &&
|
|
58191
58435
|
semver__default["default"].gte(currentVersion, supportBiggerDataVersion));
|
|
58192
58436
|
if (supportBiggerData) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const PRO2_HOST_ASSET_PACKAGE_MIN_VERSION = "1.0.1";
|
|
2
|
+
export type Pro2HostAssetPackageEntry = {
|
|
3
|
+
name: string;
|
|
4
|
+
data: Uint8Array;
|
|
5
|
+
};
|
|
6
|
+
export declare function supportsPro2HostAssetPackage(firmwareVersion: string | undefined): boolean;
|
|
7
|
+
export declare function buildPro2HostAssetPackage(entries: Pro2HostAssetPackageEntry[]): Uint8Array;
|
|
8
|
+
//# sourceMappingURL=pro2HostAssetPackage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pro2HostAssetPackage.d.ts","sourceRoot":"","sources":["../../src/utils/pro2HostAssetPackage.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,mCAAmC,UAAU,CAAC;AAwB3D,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAQF,wBAAgB,4BAA4B,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAMzF;AA2QD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,yBAAyB,EAAE,GAAG,UAAU,CA4B1F"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-core",
|
|
3
|
-
"version": "1.2.1",
|
|
3
|
+
"version": "1.2.2-alpha.1",
|
|
4
4
|
"description": "Core processes and APIs for communicating with OneKey hardware devices.",
|
|
5
5
|
"author": "OneKey",
|
|
6
6
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@onekeyfe/hd-shared": "1.2.1",
|
|
29
|
-
"@onekeyfe/hd-transport": "1.2.1",
|
|
28
|
+
"@onekeyfe/hd-shared": "1.2.2-alpha.1",
|
|
29
|
+
"@onekeyfe/hd-transport": "1.2.2-alpha.1",
|
|
30
30
|
"axios": "1.15.2",
|
|
31
31
|
"bignumber.js": "^9.0.2",
|
|
32
32
|
"buffer": "^6.0.3",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"@types/w3c-web-usb": "^1.0.10",
|
|
47
47
|
"@types/web-bluetooth": "^0.0.21"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "66f45610d36e06a46ac82cd49a4b0e1a128f19d4"
|
|
50
50
|
}
|
|
@@ -360,6 +360,7 @@ export default class EVMSignTypedData extends BaseMethod<EVMSignTypedDataParams>
|
|
|
360
360
|
|
|
361
361
|
const supportBiggerData =
|
|
362
362
|
DeviceModelToTypes.model_classic1s.includes(currentDeviceType) ||
|
|
363
|
+
DeviceModelToTypes.model_pro2.includes(currentDeviceType) ||
|
|
363
364
|
(DeviceModelToTypes.model_touch.includes(currentDeviceType) &&
|
|
364
365
|
semver.gte(currentVersion, supportBiggerDataVersion));
|
|
365
366
|
|
|
@@ -20,6 +20,10 @@ import {
|
|
|
20
20
|
getCompletePro2NftBasenames,
|
|
21
21
|
} from '../../utils/pro2Nft';
|
|
22
22
|
import { encodePro2Image } from '../../utils/pro2Wallpaper';
|
|
23
|
+
import {
|
|
24
|
+
buildPro2HostAssetPackage,
|
|
25
|
+
supportsPro2HostAssetPackage,
|
|
26
|
+
} from '../../utils/pro2HostAssetPackage';
|
|
23
27
|
import { BaseMethod } from '../BaseMethod';
|
|
24
28
|
import { decodeJpegBase64ToRgba } from '../helpers/base64Data';
|
|
25
29
|
import { invalidParameter } from '../helpers/filesystemValidation';
|
|
@@ -38,8 +42,11 @@ export type DeviceUploadNftParams = {
|
|
|
38
42
|
|
|
39
43
|
export type DeviceUploadNftResponse = {
|
|
40
44
|
basename: string;
|
|
45
|
+
/** Final image path after firmware extracts a host-asset package. */
|
|
41
46
|
imagePath: string;
|
|
47
|
+
/** Final thumbnail path after firmware extracts a host-asset package. */
|
|
42
48
|
thumbnailPath: string;
|
|
49
|
+
/** Final metadata path after firmware extracts a host-asset package. */
|
|
43
50
|
metadataPath: string;
|
|
44
51
|
totalSize: number;
|
|
45
52
|
nftUpdated: true;
|
|
@@ -138,7 +145,7 @@ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
|
|
|
138
145
|
this.useDevicePassphraseState = false;
|
|
139
146
|
}
|
|
140
147
|
|
|
141
|
-
private async assertCapabilities() {
|
|
148
|
+
private async assertCapabilities(useHostAssetPackage: boolean) {
|
|
142
149
|
const protocolInfo = await this.device.ensureProtocolV2RuntimeContext();
|
|
143
150
|
const hasFileWrite = supportsProtocolV2Message(
|
|
144
151
|
protocolInfo,
|
|
@@ -150,7 +157,7 @@ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
|
|
|
150
157
|
);
|
|
151
158
|
const hasDirList = supportsProtocolV2Message(protocolInfo, FILESYSTEM_DIR_LIST_MESSAGE_TYPE);
|
|
152
159
|
const hasNftUpdate = supportsProtocolV2Message(protocolInfo, NFT_UPDATE_MESSAGE_TYPE);
|
|
153
|
-
if (!hasFileWrite || !
|
|
160
|
+
if (!hasFileWrite || !hasNftUpdate || (!useHostAssetPackage && (!hasPathInfo || !hasDirList))) {
|
|
154
161
|
throw createDeviceNotSupportMethodError(this.name, this.device.getCurrentFirmwareType());
|
|
155
162
|
}
|
|
156
163
|
}
|
|
@@ -192,16 +199,32 @@ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
|
|
|
192
199
|
const { bundle } = this;
|
|
193
200
|
if (!bundle) throw invalidParameter('NFT data has not been initialized.');
|
|
194
201
|
|
|
195
|
-
|
|
202
|
+
const useHostAssetPackage = supportsPro2HostAssetPackage(
|
|
203
|
+
this.device.state?.versions.firmware ?? undefined
|
|
204
|
+
);
|
|
205
|
+
await this.assertCapabilities(useHostAssetPackage);
|
|
196
206
|
this.throwIfAborted();
|
|
197
|
-
await this.assertStorageCapacity(bundle.basename);
|
|
207
|
+
if (!useHostAssetPackage) await this.assertStorageCapacity(bundle.basename);
|
|
198
208
|
this.throwIfAborted();
|
|
199
209
|
|
|
200
|
-
const
|
|
210
|
+
const extractedFiles = [
|
|
201
211
|
{ path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}.bin`, data: bundle.image },
|
|
202
212
|
{ path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}_m.bin`, data: bundle.thumbnail },
|
|
203
213
|
{ path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}.json`, data: bundle.metadata },
|
|
204
214
|
];
|
|
215
|
+
const files = useHostAssetPackage
|
|
216
|
+
? [
|
|
217
|
+
{
|
|
218
|
+
path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}.okpkg`,
|
|
219
|
+
data: buildPro2HostAssetPackage(
|
|
220
|
+
extractedFiles.map(file => ({
|
|
221
|
+
name: file.path.slice(`${PRO2_NFT_DIRECTORY}/`.length),
|
|
222
|
+
data: file.data,
|
|
223
|
+
}))
|
|
224
|
+
),
|
|
225
|
+
},
|
|
226
|
+
]
|
|
227
|
+
: extractedFiles;
|
|
205
228
|
const totalSize = files.reduce((sum, file) => sum + file.data.byteLength, 0);
|
|
206
229
|
let transferredBeforeFile = 0;
|
|
207
230
|
|
|
@@ -238,9 +261,9 @@ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
|
|
|
238
261
|
const response = await this.updateNft(bundle.basename);
|
|
239
262
|
return {
|
|
240
263
|
basename: bundle.basename,
|
|
241
|
-
imagePath:
|
|
242
|
-
thumbnailPath:
|
|
243
|
-
metadataPath:
|
|
264
|
+
imagePath: extractedFiles[0].path,
|
|
265
|
+
thumbnailPath: extractedFiles[1].path,
|
|
266
|
+
metadataPath: extractedFiles[2].path,
|
|
244
267
|
totalSize,
|
|
245
268
|
nftUpdated: true,
|
|
246
269
|
message: response.message?.message,
|
|
@@ -16,14 +16,26 @@ import {
|
|
|
16
16
|
type Pro2WallpaperColorFormat,
|
|
17
17
|
encodePro2Wallpaper,
|
|
18
18
|
} from '../../utils/pro2Wallpaper';
|
|
19
|
+
import {
|
|
20
|
+
buildPro2HostAssetPackage,
|
|
21
|
+
supportsPro2HostAssetPackage,
|
|
22
|
+
} from '../../utils/pro2HostAssetPackage';
|
|
19
23
|
|
|
20
24
|
export type DeviceUploadWallpaperParams = {
|
|
21
25
|
jpegBase64: string;
|
|
26
|
+
/**
|
|
27
|
+
* Legacy firmware uses this name for the uploaded `.bin` file. Firmware
|
|
28
|
+
* 1.0.1+ always consumes the fixed `wallpaper.okpkg` package path.
|
|
29
|
+
*/
|
|
22
30
|
fileName?: string;
|
|
23
31
|
chunkSize?: number;
|
|
24
32
|
};
|
|
25
33
|
|
|
26
34
|
export type DeviceUploadWallpaperResponse = {
|
|
35
|
+
/**
|
|
36
|
+
* Filesystem path sent to `DeviceSettingsSet`. On firmware 1.0.1+ this is
|
|
37
|
+
* the temporary package path; firmware extracts and persists wallpaper.bin.
|
|
38
|
+
*/
|
|
27
39
|
path: string;
|
|
28
40
|
size: number;
|
|
29
41
|
colorFormat: Pro2WallpaperColorFormat;
|
|
@@ -31,6 +43,8 @@ export type DeviceUploadWallpaperResponse = {
|
|
|
31
43
|
};
|
|
32
44
|
|
|
33
45
|
const WALLPAPER_DIRECTORY = 'vol1:/wallpapers';
|
|
46
|
+
const WALLPAPER_PACKAGE_PATH = `${WALLPAPER_DIRECTORY}/wallpaper.okpkg`;
|
|
47
|
+
const WALLPAPER_PACKAGE_ENTRY = 'wallpaper.bin';
|
|
34
48
|
const SAFE_FILE_NAME = /^[A-Za-z0-9_-]+(?:\.bin)?$/;
|
|
35
49
|
const DEVICE_SETTINGS_SET_MESSAGE_TYPE = 60412;
|
|
36
50
|
const FILESYSTEM_FILE_WRITE_MESSAGE_TYPE = 60805;
|
|
@@ -116,16 +130,14 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
|
|
|
116
130
|
this.directoryReady = true;
|
|
117
131
|
}
|
|
118
132
|
|
|
119
|
-
private async upload() {
|
|
133
|
+
private async upload(path: string, data: Uint8Array) {
|
|
120
134
|
if (this.uploaded) return;
|
|
121
|
-
const { encoded } = this;
|
|
122
|
-
if (!encoded) throw invalidParameter('Wallpaper data has not been initialized.');
|
|
123
135
|
|
|
124
136
|
await writeProtocolV2File({
|
|
125
137
|
commands: this.device.commands,
|
|
126
|
-
path
|
|
127
|
-
data
|
|
128
|
-
totalSize:
|
|
138
|
+
path,
|
|
139
|
+
data,
|
|
140
|
+
totalSize: data.byteLength,
|
|
129
141
|
chunkSize: this.params.chunkSize,
|
|
130
142
|
maxChunkRetries: 3,
|
|
131
143
|
overwrite: true,
|
|
@@ -145,7 +157,14 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
|
|
|
145
157
|
if (!encoded) throw invalidParameter('Wallpaper data has not been initialized.');
|
|
146
158
|
await this.assertCapabilities();
|
|
147
159
|
await this.ensureDirectory();
|
|
148
|
-
|
|
160
|
+
const useHostAssetPackage = supportsPro2HostAssetPackage(
|
|
161
|
+
this.device.state?.versions.firmware ?? undefined
|
|
162
|
+
);
|
|
163
|
+
const data = useHostAssetPackage
|
|
164
|
+
? buildPro2HostAssetPackage([{ name: WALLPAPER_PACKAGE_ENTRY, data: encoded.data }])
|
|
165
|
+
: encoded.data;
|
|
166
|
+
if (useHostAssetPackage) this.path = WALLPAPER_PACKAGE_PATH;
|
|
167
|
+
await this.upload(this.path, data);
|
|
149
168
|
const response = await this.device.commands.typedCall('DeviceSettingsSet', 'Success', {
|
|
150
169
|
settings: { wallpaper_path: this.path },
|
|
151
170
|
});
|
|
@@ -160,7 +179,7 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
|
|
|
160
179
|
}
|
|
161
180
|
return {
|
|
162
181
|
path: this.path,
|
|
163
|
-
size:
|
|
182
|
+
size: data.byteLength,
|
|
164
183
|
colorFormat: encoded.colorFormat,
|
|
165
184
|
message: response.message?.message,
|
|
166
185
|
};
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { sha3_512 } from '@noble/hashes/sha3';
|
|
2
|
+
import semver from 'semver';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds the unsigned host-asset package consumed by Pro2 firmware:
|
|
6
|
+
*
|
|
7
|
+
* OKPP RESOURCE container
|
|
8
|
+
* └── OKAR archive
|
|
9
|
+
* └── independently compressed raw LZ4 blocks
|
|
10
|
+
*
|
|
11
|
+
* OKPP and OKAR are OneKey formats. Their constants mirror firmware-pro2's
|
|
12
|
+
* payload_package headers; the LZ4 bytes inside each block follow the standard
|
|
13
|
+
* raw block format and deliberately do not use an LZ4 frame or size prefix.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/* eslint-disable no-bitwise */
|
|
17
|
+
|
|
18
|
+
export const PRO2_HOST_ASSET_PACKAGE_MIN_VERSION = '1.0.1';
|
|
19
|
+
|
|
20
|
+
// OKPP container layout. The fixed header has seven empty signature slots even
|
|
21
|
+
// for a host-generated unsigned package.
|
|
22
|
+
const CONTAINER_HEADER_SIZE = 0x5f90;
|
|
23
|
+
const CONTAINER_HEADER_HASH_INPUT_LENGTH = 0x240;
|
|
24
|
+
const CONTAINER_HASH_SECTION_OFFSET = 0x200;
|
|
25
|
+
const CONTAINER_SIGNATURE_ALGORITHM_OFFSET = 0x408;
|
|
26
|
+
const CONTAINER_HEADER_MAGIC = 0x50504b4f;
|
|
27
|
+
const CONTAINER_HEADER_VERSION = 1;
|
|
28
|
+
const CONTAINER_RESOURCE_TYPE_MAGIC = 0x43534552;
|
|
29
|
+
const CONTAINER_ED25519_SIGNATURE_ALGORITHM = 0x71717171;
|
|
30
|
+
const HOST_ASSET_PACKAGE_MAX_SIZE = 4 * 1024 * 1024;
|
|
31
|
+
|
|
32
|
+
// OKAR archive layout.
|
|
33
|
+
const ARCHIVE_MAGIC = 0x52414b4f;
|
|
34
|
+
const ARCHIVE_VERSION = 1;
|
|
35
|
+
const ARCHIVE_HEADER_SIZE = 42;
|
|
36
|
+
const ARCHIVE_ENTRY_SIZE = 296;
|
|
37
|
+
const ARCHIVE_ENTRY_NAME_MAX_LENGTH = 255;
|
|
38
|
+
const ARCHIVE_COMPRESS_LZ4_BLOCKED = 1;
|
|
39
|
+
const ARCHIVE_ALIGNMENT = 4;
|
|
40
|
+
const LZ4_BLOCK_SIZE_LOG2 = 12;
|
|
41
|
+
|
|
42
|
+
export type Pro2HostAssetPackageEntry = {
|
|
43
|
+
name: string;
|
|
44
|
+
data: Uint8Array;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type EncodedArchiveEntry = Pro2HostAssetPackageEntry & {
|
|
48
|
+
nameBytes: Uint8Array;
|
|
49
|
+
compressed: Uint8Array;
|
|
50
|
+
offset: number;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export function supportsPro2HostAssetPackage(firmwareVersion: string | undefined): boolean {
|
|
54
|
+
return Boolean(
|
|
55
|
+
firmwareVersion &&
|
|
56
|
+
semver.valid(firmwareVersion) &&
|
|
57
|
+
semver.gte(firmwareVersion, PRO2_HOST_ASSET_PACKAGE_MIN_VERSION)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function concatBytes(parts: Uint8Array[]): Uint8Array {
|
|
62
|
+
const output = new Uint8Array(parts.reduce((length, part) => length + part.byteLength, 0));
|
|
63
|
+
let offset = 0;
|
|
64
|
+
for (const part of parts) {
|
|
65
|
+
output.set(part, offset);
|
|
66
|
+
offset += part.byteLength;
|
|
67
|
+
}
|
|
68
|
+
return output;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Raw LZ4 block encoder
|
|
72
|
+
// ---------------------
|
|
73
|
+
//
|
|
74
|
+
// This encoder is adapted from lz4-lite 1.1.2 and intentionally kept local so
|
|
75
|
+
// every SDK runtime uses the same dependency-free implementation. Keep this
|
|
76
|
+
// section isolated from the OneKey package writer below. Firmware requires raw
|
|
77
|
+
// blocks here; replacing it with an LZ4 frame encoder is not compatible.
|
|
78
|
+
//
|
|
79
|
+
// MIT License
|
|
80
|
+
// Copyright (c) 2026 Alexander Vukov
|
|
81
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
82
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
83
|
+
// in the Software without restriction, including without limitation the rights
|
|
84
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
85
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
86
|
+
// furnished to do so, subject to the following conditions:
|
|
87
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
88
|
+
// copies or substantial portions of the Software.
|
|
89
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
90
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
91
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
92
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
93
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
94
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
95
|
+
// SOFTWARE.
|
|
96
|
+
|
|
97
|
+
const LZ4_MIN_MATCH = 4;
|
|
98
|
+
const LZ4_LAST_LITERALS = 5;
|
|
99
|
+
const LZ4_MATCH_FIND_LIMIT = 12;
|
|
100
|
+
const LZ4_MAX_OFFSET = 0xffff;
|
|
101
|
+
const LZ4_LENGTH_MASK = 15;
|
|
102
|
+
const LZ4_HASH_LOG = 16;
|
|
103
|
+
const LZ4_HASH_MULTIPLIER = 2654435761;
|
|
104
|
+
const LZ4_SKIP_TRIGGER = 6;
|
|
105
|
+
|
|
106
|
+
function writeExtendedLength(output: Uint8Array, offset: number, length: number): number {
|
|
107
|
+
let remaining = length;
|
|
108
|
+
let nextOffset = offset;
|
|
109
|
+
while (remaining >= 255) {
|
|
110
|
+
output[nextOffset] = 255;
|
|
111
|
+
nextOffset += 1;
|
|
112
|
+
remaining -= 255;
|
|
113
|
+
}
|
|
114
|
+
output[nextOffset] = remaining;
|
|
115
|
+
return nextOffset + 1;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function copyBytes(
|
|
119
|
+
output: Uint8Array,
|
|
120
|
+
outputOffset: number,
|
|
121
|
+
input: Uint8Array,
|
|
122
|
+
inputOffset: number,
|
|
123
|
+
length: number
|
|
124
|
+
): number {
|
|
125
|
+
output.set(input.subarray(inputOffset, inputOffset + length), outputOffset);
|
|
126
|
+
return outputOffset + length;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function emitSequence(
|
|
130
|
+
output: Uint8Array,
|
|
131
|
+
outputOffset: number,
|
|
132
|
+
input: Uint8Array,
|
|
133
|
+
anchor: number,
|
|
134
|
+
literalLength: number,
|
|
135
|
+
matchOffset: number,
|
|
136
|
+
matchLengthCode: number
|
|
137
|
+
): number {
|
|
138
|
+
const tokenOffset = outputOffset;
|
|
139
|
+
let nextOffset = outputOffset + 1;
|
|
140
|
+
let token = 0;
|
|
141
|
+
|
|
142
|
+
if (literalLength >= LZ4_LENGTH_MASK) {
|
|
143
|
+
token = LZ4_LENGTH_MASK << 4;
|
|
144
|
+
nextOffset = writeExtendedLength(output, nextOffset, literalLength - LZ4_LENGTH_MASK);
|
|
145
|
+
} else {
|
|
146
|
+
token = literalLength << 4;
|
|
147
|
+
}
|
|
148
|
+
nextOffset = copyBytes(output, nextOffset, input, anchor, literalLength);
|
|
149
|
+
output[nextOffset] = matchOffset & 0xff;
|
|
150
|
+
output[nextOffset + 1] = (matchOffset >>> 8) & 0xff;
|
|
151
|
+
nextOffset += 2;
|
|
152
|
+
|
|
153
|
+
if (matchLengthCode >= LZ4_LENGTH_MASK) {
|
|
154
|
+
token |= LZ4_LENGTH_MASK;
|
|
155
|
+
nextOffset = writeExtendedLength(output, nextOffset, matchLengthCode - LZ4_LENGTH_MASK);
|
|
156
|
+
} else {
|
|
157
|
+
token |= matchLengthCode;
|
|
158
|
+
}
|
|
159
|
+
output[tokenOffset] = token;
|
|
160
|
+
return nextOffset;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function emitLastLiterals(
|
|
164
|
+
output: Uint8Array,
|
|
165
|
+
outputOffset: number,
|
|
166
|
+
input: Uint8Array,
|
|
167
|
+
anchor: number,
|
|
168
|
+
literalLength: number
|
|
169
|
+
): number {
|
|
170
|
+
const tokenOffset = outputOffset;
|
|
171
|
+
let nextOffset = outputOffset + 1;
|
|
172
|
+
if (literalLength >= LZ4_LENGTH_MASK) {
|
|
173
|
+
output[tokenOffset] = LZ4_LENGTH_MASK << 4;
|
|
174
|
+
nextOffset = writeExtendedLength(output, nextOffset, literalLength - LZ4_LENGTH_MASK);
|
|
175
|
+
} else {
|
|
176
|
+
output[tokenOffset] = literalLength << 4;
|
|
177
|
+
}
|
|
178
|
+
return copyBytes(output, nextOffset, input, anchor, literalLength);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function compressRawLz4Block(input: Uint8Array, hashTable: Uint32Array): Uint8Array {
|
|
182
|
+
const output = new Uint8Array(input.byteLength + Math.floor(input.byteLength / 255) + 16);
|
|
183
|
+
const inputView = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
|
184
|
+
const matchFindLimit = input.byteLength - LZ4_MATCH_FIND_LIMIT;
|
|
185
|
+
const matchExtendLimit = input.byteLength - LZ4_LAST_LITERALS;
|
|
186
|
+
let anchor = 0;
|
|
187
|
+
let inputOffset = 0;
|
|
188
|
+
let outputOffset = 0;
|
|
189
|
+
let searchMatchCount = 1 << LZ4_SKIP_TRIGGER;
|
|
190
|
+
|
|
191
|
+
hashTable.fill(0);
|
|
192
|
+
while (inputOffset < matchFindLimit) {
|
|
193
|
+
const sequence = inputView.getUint32(inputOffset, true);
|
|
194
|
+
const hash = Math.imul(sequence, LZ4_HASH_MULTIPLIER) >>> (32 - LZ4_HASH_LOG);
|
|
195
|
+
const candidate = hashTable[hash] - 1;
|
|
196
|
+
hashTable[hash] = inputOffset + 1;
|
|
197
|
+
|
|
198
|
+
const hasMatch = !(
|
|
199
|
+
candidate < 0 ||
|
|
200
|
+
inputOffset - candidate > LZ4_MAX_OFFSET ||
|
|
201
|
+
inputView.getUint32(candidate, true) !== sequence
|
|
202
|
+
);
|
|
203
|
+
if (!hasMatch) {
|
|
204
|
+
inputOffset += searchMatchCount >> LZ4_SKIP_TRIGGER;
|
|
205
|
+
searchMatchCount += 1;
|
|
206
|
+
} else {
|
|
207
|
+
searchMatchCount = 1 << LZ4_SKIP_TRIGGER;
|
|
208
|
+
let matchEnd = inputOffset + LZ4_MIN_MATCH;
|
|
209
|
+
let reference = candidate + LZ4_MIN_MATCH;
|
|
210
|
+
while (matchEnd < matchExtendLimit && input[matchEnd] === input[reference]) {
|
|
211
|
+
matchEnd += 1;
|
|
212
|
+
reference += 1;
|
|
213
|
+
}
|
|
214
|
+
outputOffset = emitSequence(
|
|
215
|
+
output,
|
|
216
|
+
outputOffset,
|
|
217
|
+
input,
|
|
218
|
+
anchor,
|
|
219
|
+
inputOffset - anchor,
|
|
220
|
+
inputOffset - candidate,
|
|
221
|
+
matchEnd - inputOffset - LZ4_MIN_MATCH
|
|
222
|
+
);
|
|
223
|
+
inputOffset = matchEnd;
|
|
224
|
+
anchor = inputOffset;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
outputOffset = emitLastLiterals(output, outputOffset, input, anchor, input.byteLength - anchor);
|
|
229
|
+
return output.slice(0, outputOffset);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// OneKey LZ4-blocked wrapper
|
|
233
|
+
// --------------------------
|
|
234
|
+
// The archive stores an 8-byte descriptor, one compressed-size value per
|
|
235
|
+
// block, then the concatenated raw blocks. Blocks are independent so firmware
|
|
236
|
+
// can validate and decompress them with bounded memory.
|
|
237
|
+
function encodeLz4Blocked(data: Uint8Array): Uint8Array {
|
|
238
|
+
const blockSize = 1 << LZ4_BLOCK_SIZE_LOG2;
|
|
239
|
+
const blockCount = Math.ceil(data.byteLength / blockSize);
|
|
240
|
+
const hashTable = new Uint32Array(1 << LZ4_HASH_LOG);
|
|
241
|
+
const blocks: Uint8Array[] = [];
|
|
242
|
+
const header = new Uint8Array(8 + blockCount * 4);
|
|
243
|
+
const headerView = new DataView(header.buffer);
|
|
244
|
+
headerView.setUint16(0, blockCount, true);
|
|
245
|
+
headerView.setUint16(2, LZ4_BLOCK_SIZE_LOG2, true);
|
|
246
|
+
|
|
247
|
+
for (let index = 0; index < blockCount; index += 1) {
|
|
248
|
+
const block = compressRawLz4Block(
|
|
249
|
+
data.subarray(index * blockSize, Math.min((index + 1) * blockSize, data.byteLength)),
|
|
250
|
+
hashTable
|
|
251
|
+
);
|
|
252
|
+
headerView.setUint32(8 + index * 4, block.byteLength, true);
|
|
253
|
+
blocks.push(block);
|
|
254
|
+
}
|
|
255
|
+
return concatBytes([header, ...blocks]);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// OKAR integrity helpers
|
|
259
|
+
// ----------------------
|
|
260
|
+
const CRC32_TABLE = (() => {
|
|
261
|
+
const table = new Uint32Array(256);
|
|
262
|
+
for (let index = 0; index < table.length; index += 1) {
|
|
263
|
+
let value = index;
|
|
264
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
265
|
+
value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
|
|
266
|
+
}
|
|
267
|
+
table[index] = value >>> 0;
|
|
268
|
+
}
|
|
269
|
+
return table;
|
|
270
|
+
})();
|
|
271
|
+
|
|
272
|
+
function crc32(data: Uint8Array): number {
|
|
273
|
+
let value = 0xffffffff;
|
|
274
|
+
for (const byte of data) {
|
|
275
|
+
value = CRC32_TABLE[(value ^ byte) & 0xff] ^ (value >>> 8);
|
|
276
|
+
}
|
|
277
|
+
return (value ^ 0xffffffff) >>> 0;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function align(value: number): number {
|
|
281
|
+
return (value + ARCHIVE_ALIGNMENT - 1) & ~(ARCHIVE_ALIGNMENT - 1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// OKAR archive writer
|
|
285
|
+
// -------------------
|
|
286
|
+
function buildArchive(entries: Pro2HostAssetPackageEntry[]): Uint8Array {
|
|
287
|
+
const textEncoder = new TextEncoder();
|
|
288
|
+
let dataOffset = ARCHIVE_HEADER_SIZE + entries.length * ARCHIVE_ENTRY_SIZE;
|
|
289
|
+
const encodedEntries: EncodedArchiveEntry[] = entries.map(entry => {
|
|
290
|
+
const nameBytes = textEncoder.encode(entry.name);
|
|
291
|
+
if (!entry.name || nameBytes.byteLength > ARCHIVE_ENTRY_NAME_MAX_LENGTH) {
|
|
292
|
+
throw new Error('Pro2 host asset package entry names must contain 1 to 255 UTF-8 bytes.');
|
|
293
|
+
}
|
|
294
|
+
if (!(entry.data instanceof Uint8Array) || entry.data.byteLength === 0) {
|
|
295
|
+
throw new Error(`Pro2 host asset package entry [${entry.name}] must not be empty.`);
|
|
296
|
+
}
|
|
297
|
+
const compressed = encodeLz4Blocked(entry.data);
|
|
298
|
+
const offset = align(dataOffset);
|
|
299
|
+
dataOffset = offset + compressed.byteLength;
|
|
300
|
+
return { ...entry, nameBytes, compressed, offset };
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const archive = new Uint8Array(dataOffset);
|
|
304
|
+
const view = new DataView(archive.buffer);
|
|
305
|
+
view.setUint32(0, ARCHIVE_MAGIC, true);
|
|
306
|
+
view.setUint32(4, ARCHIVE_VERSION, true);
|
|
307
|
+
view.setUint16(8, encodedEntries.length, true);
|
|
308
|
+
|
|
309
|
+
encodedEntries.forEach((entry, index) => {
|
|
310
|
+
const recordOffset = ARCHIVE_HEADER_SIZE + index * ARCHIVE_ENTRY_SIZE;
|
|
311
|
+
archive[recordOffset] = entry.nameBytes.byteLength;
|
|
312
|
+
archive.set(entry.nameBytes, recordOffset + 1);
|
|
313
|
+
view.setUint32(recordOffset + 0x100, entry.offset, true);
|
|
314
|
+
view.setUint32(recordOffset + 0x104, entry.data.byteLength, true);
|
|
315
|
+
view.setUint32(recordOffset + 0x108, entry.compressed.byteLength, true);
|
|
316
|
+
view.setUint32(recordOffset + 0x10c, crc32(entry.data), true);
|
|
317
|
+
view.setUint32(recordOffset + 0x110, crc32(entry.compressed), true);
|
|
318
|
+
archive[recordOffset + 0x114] = ARCHIVE_COMPRESS_LZ4_BLOCKED;
|
|
319
|
+
archive.set(entry.compressed, entry.offset);
|
|
320
|
+
});
|
|
321
|
+
return archive;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// OKPP container writer
|
|
325
|
+
// ---------------------
|
|
326
|
+
export function buildPro2HostAssetPackage(entries: Pro2HostAssetPackageEntry[]): Uint8Array {
|
|
327
|
+
if (entries.length === 0 || new Set(entries.map(entry => entry.name)).size !== entries.length) {
|
|
328
|
+
throw new Error('Pro2 host asset package entries must have unique, non-empty names.');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const payload = buildArchive(entries);
|
|
332
|
+
const header = new Uint8Array(CONTAINER_HEADER_SIZE);
|
|
333
|
+
const view = new DataView(header.buffer);
|
|
334
|
+
view.setUint32(0, CONTAINER_HEADER_MAGIC, true);
|
|
335
|
+
view.setUint32(4, CONTAINER_HEADER_VERSION, true);
|
|
336
|
+
view.setUint32(8, CONTAINER_RESOURCE_TYPE_MAGIC, true);
|
|
337
|
+
view.setUint32(0x0c, CONTAINER_HEADER_SIZE, true);
|
|
338
|
+
view.setUint32(0x10, 1, true);
|
|
339
|
+
view.setUint32(0x14, payload.byteLength, true);
|
|
340
|
+
|
|
341
|
+
// Host asset packages are unsigned, but firmware still requires a valid
|
|
342
|
+
// payload hash, header hash, and the Ed25519 algorithm discriminator. The
|
|
343
|
+
// zero-initialized header intentionally leaves sig_used_count and every
|
|
344
|
+
// signature slot empty.
|
|
345
|
+
header.set(sha3_512(payload), CONTAINER_HASH_SECTION_OFFSET);
|
|
346
|
+
header.set(sha3_512(header.subarray(0, CONTAINER_HEADER_HASH_INPUT_LENGTH)), 0x240);
|
|
347
|
+
view.setUint32(CONTAINER_SIGNATURE_ALGORITHM_OFFSET, CONTAINER_ED25519_SIGNATURE_ALGORITHM, true);
|
|
348
|
+
|
|
349
|
+
const packageData = concatBytes([header, payload]);
|
|
350
|
+
if (packageData.byteLength > HOST_ASSET_PACKAGE_MAX_SIZE) {
|
|
351
|
+
throw new Error('Pro2 host asset package exceeds the firmware 4 MiB limit.');
|
|
352
|
+
}
|
|
353
|
+
return packageData;
|
|
354
|
+
}
|