@onekeyfe/hd-core 1.2.0-alpha.84 → 1.2.0-alpha.86
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__/check-all-firmware-release-protocol-v2.test.ts +69 -26
- package/__tests__/check-firmware-release-protocol-v2.test.ts +1 -2
- package/__tests__/firmware-memory-host.test.ts +108 -0
- package/__tests__/firmware-update/device-update-bootloader.test.ts +4 -1
- package/__tests__/firmware-update/firmware-host-binding.test.ts +9 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +15 -70
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +53 -1
- package/__tests__/protocol-v2-resources.test.ts +59 -39
- package/__tests__/protocol-v2.test.ts +255 -306
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +1 -2
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts +1 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareMemoryHost.d.ts +27 -0
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +3 -4
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +0 -1
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +3 -1
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/index.d.ts +32 -70
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +450 -390
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +0 -17
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +0 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +0 -1
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +1 -10
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +0 -2
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/settings.d.ts +3 -15
- package/dist/types/settings.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +4 -13
- package/src/api/FirmwareUpdateV4.ts +166 -170
- package/src/api/firmware/FirmwareHostBinding.ts +16 -3
- package/src/api/firmware/FirmwareMemoryHost.ts +174 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +28 -48
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +8 -1
- package/src/data-manager/DataManager.ts +33 -13
- package/src/index.ts +6 -1
- package/src/inject.ts +0 -2
- package/src/protocols/protocol-v2/resources.ts +0 -47
- package/src/types/api/checkAllFirmwareRelease.ts +0 -1
- package/src/types/api/export.ts +0 -4
- package/src/types/api/firmwareUpdate.ts +2 -15
- package/src/types/api/index.ts +0 -2
- package/src/types/settings.ts +3 -25
- package/dist/types/api/protocolV2ResourceManifest.d.ts +0 -17
- package/dist/types/api/protocolV2ResourceManifest.d.ts.map +0 -1
- package/src/types/api/protocolV2ResourceManifest.ts +0 -19
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
2
|
+
import { bytesToHex } from '@noble/hashes/utils';
|
|
3
|
+
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
4
|
+
|
|
5
|
+
import type { CoreApi } from '../../types/api';
|
|
6
|
+
import type {
|
|
7
|
+
FirmwareArtifactReader,
|
|
8
|
+
FirmwareArtifactReference,
|
|
9
|
+
FirmwareUpdateV4Target,
|
|
10
|
+
} from '../../types/api/firmwareUpdate';
|
|
11
|
+
import type { FirmwareUpdatePlan } from '../../types/api/firmwareUpdatePlan';
|
|
12
|
+
|
|
13
|
+
export type FirmwareMemoryArtifactEntry = {
|
|
14
|
+
entryName: string;
|
|
15
|
+
binary: ArrayBuffer;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type FirmwareMemoryArtifact = {
|
|
19
|
+
artifactId: string;
|
|
20
|
+
binary: ArrayBuffer;
|
|
21
|
+
materializedEntries?: FirmwareMemoryArtifactEntry[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type FirmwareUpdateV4MemoryHost = {
|
|
25
|
+
preparedPlan: ReturnType<CoreApi['prepareFirmwareUpdatePlan']>;
|
|
26
|
+
hostBindingGeneration: number;
|
|
27
|
+
targetsToUpdate: FirmwareUpdateV4Target[];
|
|
28
|
+
expectedDeviceId: string;
|
|
29
|
+
expectedTargetVersions: Partial<Record<FirmwareUpdateV4Target, string>>;
|
|
30
|
+
componentArtifacts: Partial<
|
|
31
|
+
Record<Exclude<FirmwareUpdateV4Target, 'resource'>, FirmwareArtifactReference>
|
|
32
|
+
>;
|
|
33
|
+
release: () => void;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
let memoryHostSequence = 0;
|
|
37
|
+
|
|
38
|
+
const FIRMWARE_UPDATE_V4_COMPONENT_TARGETS = new Set<Exclude<FirmwareUpdateV4Target, 'resource'>>([
|
|
39
|
+
'boot',
|
|
40
|
+
'app_v1',
|
|
41
|
+
'app_v2',
|
|
42
|
+
'coprocessor',
|
|
43
|
+
'se01',
|
|
44
|
+
'se02',
|
|
45
|
+
'se03',
|
|
46
|
+
'se04',
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
const isFirmwareUpdateV4ComponentTarget = (
|
|
50
|
+
target: string
|
|
51
|
+
): target is Exclude<FirmwareUpdateV4Target, 'resource'> =>
|
|
52
|
+
FIRMWARE_UPDATE_V4_COMPONENT_TARGETS.has(target as Exclude<FirmwareUpdateV4Target, 'resource'>);
|
|
53
|
+
|
|
54
|
+
const createReference = (binary: ArrayBuffer, prefix: string): FirmwareArtifactReference => {
|
|
55
|
+
const digest = bytesToHex(sha256(new Uint8Array(binary)));
|
|
56
|
+
return {
|
|
57
|
+
artifactRef: `fwmem:${prefix}:${digest.slice(0, 32)}`,
|
|
58
|
+
size: binary.byteLength,
|
|
59
|
+
sha256: digest,
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export function prepareFirmwareUpdateV4MemoryHost({
|
|
64
|
+
sdk,
|
|
65
|
+
plan,
|
|
66
|
+
artifacts,
|
|
67
|
+
}: {
|
|
68
|
+
sdk: Pick<
|
|
69
|
+
CoreApi,
|
|
70
|
+
| 'prepareFirmwareUpdatePlan'
|
|
71
|
+
| 'registerFirmwareUpdateHostBinding'
|
|
72
|
+
| 'unregisterFirmwareUpdateHostBinding'
|
|
73
|
+
>;
|
|
74
|
+
plan: FirmwareUpdatePlan;
|
|
75
|
+
artifacts: FirmwareMemoryArtifact[];
|
|
76
|
+
}): FirmwareUpdateV4MemoryHost {
|
|
77
|
+
if (plan.executor !== 'v4') {
|
|
78
|
+
throw ERRORS.TypedError(
|
|
79
|
+
HardwareErrorCode.RuntimeError,
|
|
80
|
+
'Firmware memory host only supports V4 plans'
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
memoryHostSequence += 1;
|
|
84
|
+
const hostId = `${Date.now()}:${memoryHostSequence}`;
|
|
85
|
+
const binaries = new Map<string, Uint8Array>();
|
|
86
|
+
const inputs = artifacts.map((input, artifactIndex) => {
|
|
87
|
+
const artifact = createReference(input.binary, `${hostId}:artifact:${artifactIndex}`);
|
|
88
|
+
binaries.set(artifact.artifactRef, new Uint8Array(input.binary));
|
|
89
|
+
const materializedEntries = input.materializedEntries?.map((entry, entryIndex) => {
|
|
90
|
+
const entryArtifact = createReference(
|
|
91
|
+
entry.binary,
|
|
92
|
+
`${hostId}:entry:${artifactIndex}:${entryIndex}`
|
|
93
|
+
);
|
|
94
|
+
binaries.set(entryArtifact.artifactRef, new Uint8Array(entry.binary));
|
|
95
|
+
return {
|
|
96
|
+
entryName: entry.entryName,
|
|
97
|
+
artifact: entryArtifact,
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
artifactId: input.artifactId,
|
|
102
|
+
artifact,
|
|
103
|
+
...(materializedEntries?.length ? { materializedEntries } : {}),
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
const preparedPlan = sdk.prepareFirmwareUpdatePlan({
|
|
107
|
+
plan,
|
|
108
|
+
leaseRef: `fwmemlease:${hostId}`,
|
|
109
|
+
artifacts: inputs,
|
|
110
|
+
});
|
|
111
|
+
const readers = new Map<string, Uint8Array>();
|
|
112
|
+
let readerSequence = 0;
|
|
113
|
+
const artifactReader: FirmwareArtifactReader = {
|
|
114
|
+
open({ artifactRef }) {
|
|
115
|
+
const binary = binaries.get(artifactRef);
|
|
116
|
+
if (!binary) {
|
|
117
|
+
throw ERRORS.TypedError(
|
|
118
|
+
HardwareErrorCode.RuntimeError,
|
|
119
|
+
'Firmware memory artifact is unavailable'
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
readerSequence += 1;
|
|
123
|
+
const readerId = `fwmemreader:${hostId}:${readerSequence}`;
|
|
124
|
+
readers.set(readerId, binary);
|
|
125
|
+
return Promise.resolve({ readerId, size: binary.byteLength });
|
|
126
|
+
},
|
|
127
|
+
read({ readerId, offset, length }) {
|
|
128
|
+
const binary = readers.get(readerId);
|
|
129
|
+
if (!binary || offset < 0 || length <= 0 || offset + length > binary.byteLength) {
|
|
130
|
+
throw ERRORS.TypedError(
|
|
131
|
+
HardwareErrorCode.RuntimeError,
|
|
132
|
+
'Firmware memory artifact read is invalid'
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
const data = binary.slice(offset, offset + length).buffer;
|
|
136
|
+
return Promise.resolve({
|
|
137
|
+
data,
|
|
138
|
+
bytesRead: data.byteLength,
|
|
139
|
+
eof: offset + length === binary.byteLength,
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
close({ readerId }) {
|
|
143
|
+
readers.delete(readerId);
|
|
144
|
+
return Promise.resolve();
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
const hostBindingGeneration = sdk.registerFirmwareUpdateHostBinding({
|
|
148
|
+
artifactReader,
|
|
149
|
+
preparedPlanDigest: preparedPlan.preparedPlanDigest,
|
|
150
|
+
});
|
|
151
|
+
const componentArtifacts: FirmwareUpdateV4MemoryHost['componentArtifacts'] = {};
|
|
152
|
+
const expectedTargetVersions: FirmwareUpdateV4MemoryHost['expectedTargetVersions'] = {};
|
|
153
|
+
for (const artifact of preparedPlan.artifacts) {
|
|
154
|
+
if (artifact.role === 'component' && isFirmwareUpdateV4ComponentTarget(artifact.target)) {
|
|
155
|
+
componentArtifacts[artifact.target] = artifact.artifact;
|
|
156
|
+
}
|
|
157
|
+
if (artifact.targetVersion) {
|
|
158
|
+
expectedTargetVersions[artifact.target as FirmwareUpdateV4Target] = artifact.targetVersion;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
preparedPlan,
|
|
163
|
+
hostBindingGeneration,
|
|
164
|
+
targetsToUpdate: [...preparedPlan.targetsToUpdate] as FirmwareUpdateV4Target[],
|
|
165
|
+
expectedDeviceId: preparedPlan.deviceIdentity,
|
|
166
|
+
expectedTargetVersions,
|
|
167
|
+
componentArtifacts,
|
|
168
|
+
release: () => {
|
|
169
|
+
sdk.unregisterFirmwareUpdateHostBinding(hostBindingGeneration);
|
|
170
|
+
readers.clear();
|
|
171
|
+
binaries.clear();
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
FirmwareUpdatePlanForceTarget,
|
|
18
18
|
FirmwareUpdatePlanTarget,
|
|
19
19
|
} from '../../types/api/firmwareUpdatePlan';
|
|
20
|
-
import type { Features } from '../../types';
|
|
20
|
+
import type { Features, IProtocolV2ResourceSource } from '../../types';
|
|
21
21
|
|
|
22
22
|
type FirmwareUpdatePlatform = 'native' | 'desktop' | 'ext' | 'web' | 'web-embed';
|
|
23
23
|
|
|
@@ -31,7 +31,6 @@ type ReleaseRecord = {
|
|
|
31
31
|
version?: unknown;
|
|
32
32
|
components?: unknown;
|
|
33
33
|
installOrder?: unknown;
|
|
34
|
-
resourceBundles?: unknown;
|
|
35
34
|
fingerprint?: unknown;
|
|
36
35
|
fingerprintWeb?: unknown;
|
|
37
36
|
expectedSize?: unknown;
|
|
@@ -444,11 +443,9 @@ const buildProtocolV2Artifacts = (
|
|
|
444
443
|
release: ReleaseRecord,
|
|
445
444
|
{
|
|
446
445
|
includeComponents = true,
|
|
447
|
-
includeResources = true,
|
|
448
446
|
componentTargets: selectedComponentTargets,
|
|
449
447
|
}: {
|
|
450
448
|
includeComponents?: boolean;
|
|
451
|
-
includeResources?: boolean;
|
|
452
449
|
componentTargets?: ReadonlySet<FirmwareUpdatePlanTarget>;
|
|
453
450
|
} = {}
|
|
454
451
|
): {
|
|
@@ -523,43 +520,6 @@ const buildProtocolV2Artifacts = (
|
|
|
523
520
|
targets.push(target);
|
|
524
521
|
}
|
|
525
522
|
|
|
526
|
-
const bundles =
|
|
527
|
-
includeResources && Array.isArray(release.resourceBundles) ? release.resourceBundles : [];
|
|
528
|
-
const resourceBundleNames = new Set<string>();
|
|
529
|
-
for (const value of bundles) {
|
|
530
|
-
const bundle = asRecord(value);
|
|
531
|
-
const name = asString(bundle?.name);
|
|
532
|
-
if (!bundle || !name) {
|
|
533
|
-
throw ERRORS.TypedError(
|
|
534
|
-
HardwareErrorCode.RuntimeError,
|
|
535
|
-
'Protocol V2 resource bundle is invalid',
|
|
536
|
-
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
537
|
-
);
|
|
538
|
-
}
|
|
539
|
-
if (resourceBundleNames.has(name)) {
|
|
540
|
-
throw ERRORS.TypedError(
|
|
541
|
-
HardwareErrorCode.RuntimeError,
|
|
542
|
-
`Protocol V2 resource bundle duplicates name ${name}`,
|
|
543
|
-
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
544
|
-
);
|
|
545
|
-
}
|
|
546
|
-
resourceBundleNames.add(name);
|
|
547
|
-
artifacts.push({
|
|
548
|
-
artifactId: `resourceBundle:${name}`,
|
|
549
|
-
role: 'resourceBundle',
|
|
550
|
-
target: 'resource',
|
|
551
|
-
url: assertArtifactUrl(bundle.url, `Protocol V2 resource bundle ${name}`),
|
|
552
|
-
container: 'raw',
|
|
553
|
-
logicalName: name,
|
|
554
|
-
...asIntegrity({
|
|
555
|
-
size: bundle.expectedSize,
|
|
556
|
-
sha256: bundle.fingerprint,
|
|
557
|
-
}),
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
if (bundles.length > 0) {
|
|
561
|
-
targets.push('resource');
|
|
562
|
-
}
|
|
563
523
|
return { artifacts, targets: [...new Set(targets)] };
|
|
564
524
|
};
|
|
565
525
|
|
|
@@ -679,7 +639,7 @@ export const buildProtocolV2FirmwareUpdatePlan = ({
|
|
|
679
639
|
release,
|
|
680
640
|
targetsToUpdate,
|
|
681
641
|
forceUpdateTargets,
|
|
682
|
-
|
|
642
|
+
resourceArchive,
|
|
683
643
|
}: {
|
|
684
644
|
features: Features;
|
|
685
645
|
firmwareType: EFirmwareType;
|
|
@@ -687,7 +647,7 @@ export const buildProtocolV2FirmwareUpdatePlan = ({
|
|
|
687
647
|
release: ReleaseRecord | undefined;
|
|
688
648
|
targetsToUpdate: readonly FirmwareUpdateV4Target[];
|
|
689
649
|
forceUpdateTargets?: FirmwareUpdatePlanForceTarget[];
|
|
690
|
-
|
|
650
|
+
resourceArchive: IProtocolV2ResourceSource | undefined;
|
|
691
651
|
}): FirmwareUpdatePlan => {
|
|
692
652
|
const validatedForceTargets = validateFirmwareUpdatePlanForceTargets(forceUpdateTargets);
|
|
693
653
|
if (validatedForceTargets.some(target => target === 'ble' || target === 'bootloader')) {
|
|
@@ -705,9 +665,32 @@ export const buildProtocolV2FirmwareUpdatePlan = ({
|
|
|
705
665
|
}
|
|
706
666
|
const protocolV2 = buildProtocolV2Artifacts(release ?? {}, {
|
|
707
667
|
includeComponents: requestedComponentTargets.size > 0,
|
|
708
|
-
includeResources: false,
|
|
709
668
|
componentTargets: requestedComponentTargets,
|
|
710
669
|
});
|
|
670
|
+
const resourceRequested = targetsToUpdate.includes('resource');
|
|
671
|
+
if (resourceRequested) {
|
|
672
|
+
const archive = resourceArchive;
|
|
673
|
+
if (!archive) {
|
|
674
|
+
return planError('Protocol V2 resource target has no resource archive');
|
|
675
|
+
}
|
|
676
|
+
const integrity = asIntegrity({
|
|
677
|
+
size: archive.archiveSize,
|
|
678
|
+
sha256: archive.archiveSha256,
|
|
679
|
+
});
|
|
680
|
+
if (integrity.expectedSize === undefined || integrity.expectedSha256 === undefined) {
|
|
681
|
+
planError('Protocol V2 resource archive integrity metadata is invalid');
|
|
682
|
+
}
|
|
683
|
+
protocolV2.artifacts.push({
|
|
684
|
+
artifactId: 'resource:archive',
|
|
685
|
+
role: 'resourceBundle',
|
|
686
|
+
target: 'resource',
|
|
687
|
+
url: assertArtifactUrl(archive.archiveUrl, 'Protocol V2 resource archive'),
|
|
688
|
+
container: 'zip',
|
|
689
|
+
logicalName: 'protocol-v2-resource-archive',
|
|
690
|
+
...integrity,
|
|
691
|
+
});
|
|
692
|
+
protocolV2.targets.push('resource');
|
|
693
|
+
}
|
|
711
694
|
const representedTargets = new Set(protocolV2.targets);
|
|
712
695
|
const missingTarget = Array.from(requestedComponentTargets).find(
|
|
713
696
|
target => !representedTargets.has(target)
|
|
@@ -718,12 +701,10 @@ export const buildProtocolV2FirmwareUpdatePlan = ({
|
|
|
718
701
|
if (validatedForceTargets.includes('firmware') && protocolV2.targets.length === 0) {
|
|
719
702
|
planError('Forced firmware update target firmware is not represented by the plan');
|
|
720
703
|
}
|
|
721
|
-
if (validatedForceTargets.includes('resource') && !
|
|
704
|
+
if (validatedForceTargets.includes('resource') && !resourceArchive) {
|
|
722
705
|
planError('Forced firmware update target resource has no Protocol V2 resource archive');
|
|
723
706
|
}
|
|
724
707
|
|
|
725
|
-
// Protocol V2 resource archives are materialized by the host into explicit
|
|
726
|
-
// resourceFiles. The prepared artifact plan therefore binds firmware components only.
|
|
727
708
|
return finalizeFirmwareUpdatePlan({
|
|
728
709
|
features,
|
|
729
710
|
firmwareType,
|
|
@@ -781,7 +762,6 @@ export const buildFirmwareUpdatePlan = ({
|
|
|
781
762
|
if (executor === 'v4' && (shouldUpdateFirmware || shouldUpdateResource)) {
|
|
782
763
|
const protocolV2 = buildProtocolV2Artifacts(firmwareRelease ?? {}, {
|
|
783
764
|
includeComponents: shouldUpdateFirmware,
|
|
784
|
-
includeResources: shouldUpdateResource,
|
|
785
765
|
});
|
|
786
766
|
artifacts = protocolV2.artifacts;
|
|
787
767
|
targetsToUpdate = protocolV2.targets;
|
|
@@ -278,7 +278,14 @@ export const validateFirmwareUpdatePreparedPlan = (value: unknown): FirmwareUpda
|
|
|
278
278
|
artifactIds.add(artifact.artifactId);
|
|
279
279
|
artifactTargets.add(artifact.target);
|
|
280
280
|
assertFirmwareArtifactReference(artifact.artifact);
|
|
281
|
-
|
|
281
|
+
const materializedEntryNames =
|
|
282
|
+
artifact.materializedEntries?.map(entry => {
|
|
283
|
+
assertPreparedEntry(entry);
|
|
284
|
+
return getFirmwareUpdateResourceName(entry.entryName).toLowerCase();
|
|
285
|
+
}) ?? [];
|
|
286
|
+
if (new Set(materializedEntryNames).size !== materializedEntryNames.length) {
|
|
287
|
+
return preparedPlanError('Firmware prepared plan contains duplicate entry names');
|
|
288
|
+
}
|
|
282
289
|
}
|
|
283
290
|
if (
|
|
284
291
|
preparedPlan.targetsToUpdate.some(target => !FIRMWARE_UPDATE_PLAN_TARGETS.has(target)) ||
|
|
@@ -94,12 +94,10 @@ export default class DataManager {
|
|
|
94
94
|
ble: [],
|
|
95
95
|
},
|
|
96
96
|
[EDeviceType.Pro2]: {
|
|
97
|
-
firmware: [],
|
|
98
|
-
ble: [],
|
|
97
|
+
'firmware-v1': [],
|
|
99
98
|
},
|
|
100
99
|
[EDeviceType.Neo]: {
|
|
101
|
-
firmware: [],
|
|
102
|
-
ble: [],
|
|
100
|
+
'firmware-v1': [],
|
|
103
101
|
},
|
|
104
102
|
[EDeviceType.ClassicPure]: {
|
|
105
103
|
firmware: [],
|
|
@@ -121,6 +119,8 @@ export default class DataManager {
|
|
|
121
119
|
|
|
122
120
|
static protocolV2ResourcesConfigError: Error | undefined;
|
|
123
121
|
|
|
122
|
+
private static protocolV2NeoResourcesConfigError: Error | undefined;
|
|
123
|
+
|
|
124
124
|
static getFirmwareStatus = (
|
|
125
125
|
features: Features,
|
|
126
126
|
firmwareType: EFirmwareType
|
|
@@ -410,6 +410,7 @@ export default class DataManager {
|
|
|
410
410
|
static async load(settings: ConnectSettings): Promise<boolean> {
|
|
411
411
|
this.settings = settings;
|
|
412
412
|
this.protocolV2ResourcesConfigError = undefined;
|
|
413
|
+
this.protocolV2NeoResourcesConfigError = undefined;
|
|
413
414
|
const manifestMode =
|
|
414
415
|
settings.firmwareManifestMode ?? (settings.fetchConfig ? 'sdk-managed' : undefined);
|
|
415
416
|
if (settings.preloadedConfig) {
|
|
@@ -476,15 +477,21 @@ export default class DataManager {
|
|
|
476
477
|
pro2Resources = parseProtocolV2Resources(
|
|
477
478
|
(data.pro2 as { resources?: unknown } | undefined)?.resources
|
|
478
479
|
);
|
|
479
|
-
neoResources = parseProtocolV2Resources(
|
|
480
|
-
(data.neo as { resources?: unknown } | undefined)?.resources
|
|
481
|
-
);
|
|
482
480
|
} catch (error) {
|
|
483
481
|
// Firmware resource metadata is not required for base communication. If the
|
|
484
482
|
// remote config is temporarily incomplete, disable this resource update only.
|
|
485
483
|
this.protocolV2ResourcesConfigError =
|
|
486
484
|
error instanceof Error ? error : new Error(String(error));
|
|
487
|
-
Log.warn('[DataConfig] Ignoring invalid
|
|
485
|
+
Log.warn('[DataConfig] Ignoring invalid Pro2 resources config:', error);
|
|
486
|
+
}
|
|
487
|
+
try {
|
|
488
|
+
neoResources = parseProtocolV2Resources(
|
|
489
|
+
(data.neo as { resources?: unknown } | undefined)?.resources
|
|
490
|
+
);
|
|
491
|
+
} catch (error) {
|
|
492
|
+
this.protocolV2NeoResourcesConfigError =
|
|
493
|
+
error instanceof Error ? error : new Error(String(error));
|
|
494
|
+
Log.warn('[DataConfig] Ignoring invalid Neo resources config:', error);
|
|
488
495
|
}
|
|
489
496
|
const enrichedPro2Config = this.enrichFirmwareReleaseInfo(data.pro2);
|
|
490
497
|
const enrichedNeoConfig = this.enrichFirmwareReleaseInfo(data.neo);
|
|
@@ -537,7 +544,12 @@ export default class DataManager {
|
|
|
537
544
|
/** Force a fresh remote config before an update is allowed to mutate the device. */
|
|
538
545
|
static async forceReloadData({
|
|
539
546
|
requireResources = false,
|
|
540
|
-
|
|
547
|
+
resourceDeviceType = EDeviceType.Pro2,
|
|
548
|
+
}: {
|
|
549
|
+
requireResources?: boolean;
|
|
550
|
+
resourceDeviceType?: EDeviceType.Pro2 | EDeviceType.Neo;
|
|
551
|
+
} = {}): Promise<void> {
|
|
552
|
+
const resourceDeviceName = resourceDeviceType === EDeviceType.Pro2 ? 'Pro2' : 'Neo';
|
|
541
553
|
if (!this.settings) {
|
|
542
554
|
throw new Error('Remote config settings are not initialized');
|
|
543
555
|
}
|
|
@@ -545,10 +557,14 @@ export default class DataManager {
|
|
|
545
557
|
this.lastCheckTimestamp > 0 &&
|
|
546
558
|
getTimeStamp() - this.lastCheckTimestamp <= FIRMWARE_UPDATE_CONFIG_FRESHNESS_MS;
|
|
547
559
|
if (hasFreshConfig) {
|
|
548
|
-
|
|
560
|
+
const resourcesConfigError =
|
|
561
|
+
resourceDeviceType === EDeviceType.Pro2
|
|
562
|
+
? this.protocolV2ResourcesConfigError
|
|
563
|
+
: this.protocolV2NeoResourcesConfigError;
|
|
564
|
+
if (requireResources && resourcesConfigError) {
|
|
549
565
|
throw ERRORS.TypedError(
|
|
550
566
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
551
|
-
`Invalid
|
|
567
|
+
`Invalid ${resourceDeviceName} resources config: ${resourcesConfigError.message}`
|
|
552
568
|
);
|
|
553
569
|
}
|
|
554
570
|
return;
|
|
@@ -560,10 +576,14 @@ export default class DataManager {
|
|
|
560
576
|
'Unable to refresh the latest remote config'
|
|
561
577
|
);
|
|
562
578
|
}
|
|
563
|
-
|
|
579
|
+
const resourcesConfigError =
|
|
580
|
+
resourceDeviceType === EDeviceType.Pro2
|
|
581
|
+
? this.protocolV2ResourcesConfigError
|
|
582
|
+
: this.protocolV2NeoResourcesConfigError;
|
|
583
|
+
if (requireResources && resourcesConfigError) {
|
|
564
584
|
throw ERRORS.TypedError(
|
|
565
585
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
566
|
-
`Invalid
|
|
586
|
+
`Invalid ${resourceDeviceName} resources config: ${resourcesConfigError.message}`
|
|
567
587
|
);
|
|
568
588
|
}
|
|
569
589
|
this.lastCheckTimestamp = getTimeStamp();
|
package/src/index.ts
CHANGED
|
@@ -23,9 +23,14 @@ export { projectFeatures as projectDeviceStateFeatures } from './device/DeviceSt
|
|
|
23
23
|
export { getMethodSupportedProtocols } from './api/utils';
|
|
24
24
|
export {
|
|
25
25
|
parseProtocolV2ResourceManifest,
|
|
26
|
-
prepareProtocolV2ResourceFiles,
|
|
27
26
|
selectProtocolV2ResourceManifestFiles,
|
|
28
27
|
} from './protocols/protocol-v2/resources';
|
|
28
|
+
export { prepareFirmwareUpdateV4MemoryHost } from './api/firmware/FirmwareMemoryHost';
|
|
29
|
+
export type {
|
|
30
|
+
FirmwareMemoryArtifact,
|
|
31
|
+
FirmwareMemoryArtifactEntry,
|
|
32
|
+
FirmwareUpdateV4MemoryHost,
|
|
33
|
+
} from './api/firmware/FirmwareMemoryHost';
|
|
29
34
|
export {
|
|
30
35
|
getFirmwareUpdateHostBindingGeneration,
|
|
31
36
|
registerFirmwareUpdateHostBinding,
|
package/src/inject.ts
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
prepareFirmwareUpdatePlan,
|
|
4
4
|
validateFirmwareUpdatePreparedPlan,
|
|
5
5
|
} from './api/firmware/FirmwareUpdatePreparedPlan';
|
|
6
|
-
import { prepareProtocolV2ResourceFiles } from './protocols/protocol-v2/resources';
|
|
7
6
|
import {
|
|
8
7
|
getFirmwareUpdateHostBindingGeneration,
|
|
9
8
|
registerFirmwareUpdateHostBinding,
|
|
@@ -169,7 +168,6 @@ export const createCoreApi = (
|
|
|
169
168
|
getFirmwareUpdateHostBindingGeneration,
|
|
170
169
|
prepareFirmwareUpdatePlan,
|
|
171
170
|
validateFirmwareUpdatePreparedPlan,
|
|
172
|
-
prepareProtocolV2ResourceFiles,
|
|
173
171
|
getLogs: () => call({ method: 'getLogs' }),
|
|
174
172
|
clearSessionCache: params => call({ ...params, method: 'clearSessionCache' }),
|
|
175
173
|
/**
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { sha256 } from '@noble/hashes/sha256';
|
|
2
|
-
|
|
3
1
|
import type {
|
|
4
2
|
IProtocolV2ResourceManifest,
|
|
5
3
|
IProtocolV2ResourceManifestFile,
|
|
@@ -210,48 +208,3 @@ export function selectProtocolV2ResourceManifestFiles({
|
|
|
210
208
|
}): IProtocolV2ResourceManifestFile[] {
|
|
211
209
|
return targetsToUpdate.includes('resource') ? [...manifest.files] : [];
|
|
212
210
|
}
|
|
213
|
-
|
|
214
|
-
export function prepareProtocolV2ResourceFiles({
|
|
215
|
-
manifest: value,
|
|
216
|
-
files,
|
|
217
|
-
targetsToUpdate,
|
|
218
|
-
}: {
|
|
219
|
-
manifest: unknown;
|
|
220
|
-
files: Array<{ archivePath: string; binary: ArrayBuffer }>;
|
|
221
|
-
targetsToUpdate: readonly FirmwareUpdateV4Target[];
|
|
222
|
-
}): Array<{ binary: ArrayBuffer; devicePath: string; size: number; fileHash: string }> {
|
|
223
|
-
const manifest = parseProtocolV2ResourceManifest(value);
|
|
224
|
-
const selected = selectProtocolV2ResourceManifestFiles({ manifest, targetsToUpdate });
|
|
225
|
-
const binaries = new Map(files.map(file => [file.archivePath, file.binary] as const));
|
|
226
|
-
return selected.map(file => {
|
|
227
|
-
const binary = binaries.get(file.archive_path);
|
|
228
|
-
if (
|
|
229
|
-
!binary ||
|
|
230
|
-
!isProtocolV2ResourceFileValid(binary, {
|
|
231
|
-
size: file.size,
|
|
232
|
-
fileHash: file.sha256,
|
|
233
|
-
})
|
|
234
|
-
) {
|
|
235
|
-
throw new Error(`Pro2 resource manifest file verification failed: ${file.archive_path}`);
|
|
236
|
-
}
|
|
237
|
-
return {
|
|
238
|
-
binary,
|
|
239
|
-
devicePath: file.device_path,
|
|
240
|
-
size: file.size,
|
|
241
|
-
fileHash: file.sha256,
|
|
242
|
-
};
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function bytesToHex(bytes: Uint8Array): string {
|
|
247
|
-
return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/** Verify the complete downloaded file before any device mutation. */
|
|
251
|
-
export function isProtocolV2ResourceFileValid(
|
|
252
|
-
binary: ArrayBuffer,
|
|
253
|
-
resource: { size: number; fileHash: string }
|
|
254
|
-
): boolean {
|
|
255
|
-
if (binary.byteLength !== resource.size) return false;
|
|
256
|
-
return bytesToHex(sha256(new Uint8Array(binary))) === resource.fileHash.toLowerCase();
|
|
257
|
-
}
|
|
@@ -75,7 +75,6 @@ export type AllFirmwareRelease = {
|
|
|
75
75
|
required?: boolean;
|
|
76
76
|
resourceStatus?: 'valid' | 'outdated' | 'unknown';
|
|
77
77
|
resourceArchive?: IProtocolV2ResourceSource;
|
|
78
|
-
resourcePreparationRequired?: boolean;
|
|
79
78
|
currentVersions?: DeviceStateVersions;
|
|
80
79
|
components?: ProtocolV2FirmwareComponentRelease[];
|
|
81
80
|
targetsToUpdate?: FirmwareUpdateV4Target[];
|
package/src/types/api/export.ts
CHANGED
|
@@ -49,10 +49,6 @@ export type {
|
|
|
49
49
|
FirmwareUpdatePreparedEntry,
|
|
50
50
|
FirmwareUpdatePreparedPlan,
|
|
51
51
|
} from './firmwareUpdatePreparedPlan';
|
|
52
|
-
export type {
|
|
53
|
-
ProtocolV2PreparedResourceFile,
|
|
54
|
-
ProtocolV2ResourceManifestBinary,
|
|
55
|
-
} from './protocolV2ResourceManifest';
|
|
56
52
|
export type { FirmwareUpdateCapabilities } from './firmwareUpdateCapabilities';
|
|
57
53
|
export type {
|
|
58
54
|
AllFirmwareRelease,
|
|
@@ -26,6 +26,8 @@ export interface FirmwareArtifactReader {
|
|
|
26
26
|
|
|
27
27
|
export interface FirmwareUpdateHostBinding {
|
|
28
28
|
artifactReader: FirmwareArtifactReader;
|
|
29
|
+
/** 将读取器 generation 绑定到包含 ZIP 条目的完整 PreparedPlan。 */
|
|
30
|
+
preparedPlanDigest: string;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
export interface FirmwareUpdateBinaryParams {
|
|
@@ -153,25 +155,10 @@ export interface FirmwareUpdateV4Params {
|
|
|
153
155
|
se03Binary?: ArrayBuffer;
|
|
154
156
|
se04Binary?: ArrayBuffer;
|
|
155
157
|
forcedUpdateRes?: boolean;
|
|
156
|
-
/**
|
|
157
|
-
* Arbitrary Protocol V2 resource files written directly with FilesystemFileWrite.
|
|
158
|
-
* Use this for every file selected from the manifest-driven resource archive.
|
|
159
|
-
* When provided, these files are authoritative for the resource target.
|
|
160
|
-
*/
|
|
161
|
-
resourceFiles?: Array<{
|
|
162
|
-
binary: ArrayBuffer;
|
|
163
|
-
devicePath: string;
|
|
164
|
-
size?: number;
|
|
165
|
-
fileHash?: string;
|
|
166
|
-
}>;
|
|
167
158
|
artifactReader?: FirmwareArtifactReader;
|
|
168
159
|
componentArtifacts?: Partial<
|
|
169
160
|
Record<Exclude<FirmwareUpdateV4Target, 'resource'>, FirmwareArtifactReference>
|
|
170
161
|
>;
|
|
171
|
-
resourceBundleArtifacts?: Array<{
|
|
172
|
-
name: string;
|
|
173
|
-
artifact: FirmwareArtifactReference;
|
|
174
|
-
}>;
|
|
175
162
|
}
|
|
176
163
|
|
|
177
164
|
export declare function registerFirmwareUpdateHostBinding(
|
package/src/types/api/index.ts
CHANGED
|
@@ -41,7 +41,6 @@ import type {
|
|
|
41
41
|
prepareFirmwareUpdatePlan,
|
|
42
42
|
validateFirmwareUpdatePreparedPlan,
|
|
43
43
|
} from './firmwareUpdatePreparedPlan';
|
|
44
|
-
import type { prepareProtocolV2ResourceFiles } from './protocolV2ResourceManifest';
|
|
45
44
|
import type { promptWebDeviceAccess } from './promptWebDeviceAccess';
|
|
46
45
|
import type { deviceReset } from './deviceReset';
|
|
47
46
|
import type { deviceRecovery } from './deviceRecovery';
|
|
@@ -254,7 +253,6 @@ export type CoreApi = {
|
|
|
254
253
|
getFirmwareUpdateCapabilities: typeof getFirmwareUpdateCapabilities;
|
|
255
254
|
prepareFirmwareUpdatePlan: typeof prepareFirmwareUpdatePlan;
|
|
256
255
|
validateFirmwareUpdatePreparedPlan: typeof validateFirmwareUpdatePreparedPlan;
|
|
257
|
-
prepareProtocolV2ResourceFiles: typeof prepareProtocolV2ResourceFiles;
|
|
258
256
|
cipherKeyValue: typeof cipherKeyValue;
|
|
259
257
|
|
|
260
258
|
/**
|
package/src/types/settings.ts
CHANGED
|
@@ -104,24 +104,6 @@ export type IProtocolV2ResourceManifest = {
|
|
|
104
104
|
files: IProtocolV2ResourceManifestFile[];
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
-
/** Pro2 RESC bundle okpkg descriptor for incremental FileWrite synchronization. */
|
|
108
|
-
export type IProtocolV2ResourceBundle = {
|
|
109
|
-
/** Bundle name, such as images, animation, translations, or fonts_roobert. */
|
|
110
|
-
name: string;
|
|
111
|
-
/** Download URL. */
|
|
112
|
-
url: string;
|
|
113
|
-
fingerprint?: string;
|
|
114
|
-
expectedSize?: number;
|
|
115
|
-
/** Device target path, such as vol0:/bundles/images/images.okpkg. */
|
|
116
|
-
devicePath: string;
|
|
117
|
-
/** okpkg payload_version used to skip matching content after FileRead. */
|
|
118
|
-
version?: IVersionArray;
|
|
119
|
-
/** okpkg payload_hash used for SHA3-512 comparison after FileRead. */
|
|
120
|
-
payloadHash?: string;
|
|
121
|
-
/** okpkg header_hash used for SHA3-512 comparison after FileRead. */
|
|
122
|
-
headerHash?: string;
|
|
123
|
-
};
|
|
124
|
-
|
|
125
107
|
/** STM32 firmware config */
|
|
126
108
|
export type IFirmwareReleaseInfo = {
|
|
127
109
|
required: boolean;
|
|
@@ -149,8 +131,6 @@ export type IFirmwareReleaseInfo = {
|
|
|
149
131
|
upgradeType?: 'payload-package-set' | string;
|
|
150
132
|
components?: Record<string, IProtocolV2FirmwareComponent>;
|
|
151
133
|
installOrder?: string[];
|
|
152
|
-
/** Pro2 RESC bundles for incremental direct FileWrite synchronization. */
|
|
153
|
-
resourceBundles?: IProtocolV2ResourceBundle[];
|
|
154
134
|
bootloaderChangelog?: {
|
|
155
135
|
[k in ILocale]: string;
|
|
156
136
|
};
|
|
@@ -179,22 +159,20 @@ export type IBLEFirmwareReleaseInfo = {
|
|
|
179
159
|
};
|
|
180
160
|
|
|
181
161
|
type IKnownDevice = Exclude<IDeviceType, 'unknown'>;
|
|
182
|
-
type ILegacyKnownDevice = Exclude<IKnownDevice, 'neo'>;
|
|
183
|
-
|
|
184
162
|
type IDeviceReleaseInfo = {
|
|
185
|
-
firmware
|
|
163
|
+
firmware?: IFirmwareReleaseInfo[];
|
|
186
164
|
/** Protocol V2 payload package set */
|
|
187
165
|
'firmware-v1'?: IFirmwareReleaseInfo[];
|
|
188
166
|
'firmware-v2'?: IFirmwareReleaseInfo[];
|
|
189
167
|
'firmware-v8'?: IFirmwareReleaseInfo[];
|
|
190
168
|
'firmware-btc-v8'?: IFirmwareReleaseInfo[];
|
|
191
|
-
ble
|
|
169
|
+
ble?: IBLEFirmwareReleaseInfo[];
|
|
192
170
|
/** Independent Protocol V2 resource release configuration. */
|
|
193
171
|
resources?: IProtocolV2Resources;
|
|
194
172
|
};
|
|
195
173
|
|
|
196
174
|
export type DeviceTypeMap = {
|
|
197
|
-
[k in
|
|
175
|
+
[k in Exclude<IKnownDevice, 'neo'>]: IDeviceReleaseInfo;
|
|
198
176
|
} & {
|
|
199
177
|
/** Optional until every remote-config producer publishes a Neo entry. */
|
|
200
178
|
neo?: IDeviceReleaseInfo;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { FirmwareUpdateV4Target } from './firmwareUpdate';
|
|
2
|
-
export type ProtocolV2ResourceManifestBinary = {
|
|
3
|
-
archivePath: string;
|
|
4
|
-
binary: ArrayBuffer;
|
|
5
|
-
};
|
|
6
|
-
export type ProtocolV2PreparedResourceFile = {
|
|
7
|
-
binary: ArrayBuffer;
|
|
8
|
-
devicePath: string;
|
|
9
|
-
size: number;
|
|
10
|
-
fileHash: string;
|
|
11
|
-
};
|
|
12
|
-
export declare function prepareProtocolV2ResourceFiles(input: {
|
|
13
|
-
manifest: unknown;
|
|
14
|
-
files: ProtocolV2ResourceManifestBinary[];
|
|
15
|
-
targetsToUpdate: readonly FirmwareUpdateV4Target[];
|
|
16
|
-
}): ProtocolV2PreparedResourceFile[];
|
|
17
|
-
//# sourceMappingURL=protocolV2ResourceManifest.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"protocolV2ResourceManifest.d.ts","sourceRoot":"","sources":["../../../src/types/api/protocolV2ResourceManifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE/D,MAAM,MAAM,gCAAgC,GAAG;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG;IAC3C,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,8BAA8B,CAAC,KAAK,EAAE;IAC5D,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,gCAAgC,EAAE,CAAC;IAC1C,eAAe,EAAE,SAAS,sBAAsB,EAAE,CAAC;CACpD,GAAG,8BAA8B,EAAE,CAAC"}
|