@onekeyfe/hd-core 1.2.0-alpha.82 → 1.2.0-alpha.85
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 +285 -13
- package/__tests__/check-firmware-release-protocol-v2.test.ts +6 -0
- package/__tests__/firmware-memory-host.test.ts +105 -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 +0 -38
- package/__tests__/protocol-v2.test.ts +212 -320
- 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/FirmwareMemoryHost.d.ts +27 -0
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +33 -1
- 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/api/firmware/protocolV2Release.d.ts.map +1 -1
- package/dist/index.d.ts +25 -64
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +546 -387
- 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 +1 -0
- 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 +0 -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 +0 -11
- package/dist/types/settings.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +72 -5
- package/src/api/FirmwareUpdateV4.ts +154 -165
- package/src/api/firmware/FirmwareMemoryHost.ts +171 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +176 -79
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +8 -1
- package/src/api/firmware/protocolV2Release.ts +2 -0
- 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 +1 -0
- package/src/types/api/export.ts +0 -4
- package/src/types/api/firmwareUpdate.ts +0 -15
- package/src/types/api/index.ts +0 -2
- package/src/types/settings.ts +0 -20
- package/src/utils/deviceFeaturesUtils.ts +2 -2
- 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
|
@@ -10,13 +10,14 @@ import {
|
|
|
10
10
|
getDeviceFirmwareVersion,
|
|
11
11
|
} from '../../utils/deviceVersionUtils';
|
|
12
12
|
|
|
13
|
+
import type { FirmwareUpdateV4Target } from '../../types/api/firmwareUpdate';
|
|
13
14
|
import type {
|
|
14
15
|
FirmwareUpdatePlan,
|
|
15
16
|
FirmwareUpdatePlanArtifact,
|
|
16
17
|
FirmwareUpdatePlanForceTarget,
|
|
17
18
|
FirmwareUpdatePlanTarget,
|
|
18
19
|
} from '../../types/api/firmwareUpdatePlan';
|
|
19
|
-
import type { Features } from '../../types';
|
|
20
|
+
import type { Features, IProtocolV2ResourceSource } from '../../types';
|
|
20
21
|
|
|
21
22
|
type FirmwareUpdatePlatform = 'native' | 'desktop' | 'ext' | 'web' | 'web-embed';
|
|
22
23
|
|
|
@@ -30,7 +31,6 @@ type ReleaseRecord = {
|
|
|
30
31
|
version?: unknown;
|
|
31
32
|
components?: unknown;
|
|
32
33
|
installOrder?: unknown;
|
|
33
|
-
resourceBundles?: unknown;
|
|
34
34
|
fingerprint?: unknown;
|
|
35
35
|
fingerprintWeb?: unknown;
|
|
36
36
|
expectedSize?: unknown;
|
|
@@ -62,6 +62,11 @@ const PROTOCOL_V2_TARGETS: Readonly<
|
|
|
62
62
|
SE04: 'se04',
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
+
const PROTOCOL_V2_FIRMWARE_UPDATE_TARGETS = new Set<FirmwareUpdateV4Target>([
|
|
66
|
+
...Object.values(PROTOCOL_V2_TARGETS),
|
|
67
|
+
'resource',
|
|
68
|
+
]);
|
|
69
|
+
|
|
65
70
|
const asRecord = (value: unknown): Record<string, unknown> | undefined =>
|
|
66
71
|
value && typeof value === 'object' && !Array.isArray(value)
|
|
67
72
|
? (value as Record<string, unknown>)
|
|
@@ -191,6 +196,23 @@ export const validateFirmwareUpdatePlanForceTargets = (
|
|
|
191
196
|
return [...value] as FirmwareUpdatePlanForceTarget[];
|
|
192
197
|
};
|
|
193
198
|
|
|
199
|
+
export const validateProtocolV2FirmwareUpdateTargets = (
|
|
200
|
+
value: unknown
|
|
201
|
+
): FirmwareUpdateV4Target[] => {
|
|
202
|
+
if (value === undefined) {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
if (
|
|
206
|
+
!Array.isArray(value) ||
|
|
207
|
+
value.length > PROTOCOL_V2_FIRMWARE_UPDATE_TARGETS.size ||
|
|
208
|
+
value.some(target => !PROTOCOL_V2_FIRMWARE_UPDATE_TARGETS.has(target)) ||
|
|
209
|
+
new Set(value).size !== value.length
|
|
210
|
+
) {
|
|
211
|
+
return planError('Protocol V2 firmware update targets are invalid');
|
|
212
|
+
}
|
|
213
|
+
return [...value] as FirmwareUpdateV4Target[];
|
|
214
|
+
};
|
|
215
|
+
|
|
194
216
|
const assertExactKeys = (
|
|
195
217
|
value: Record<string, unknown>,
|
|
196
218
|
required: readonly string[],
|
|
@@ -405,7 +427,7 @@ const selectResourceUrl = ({
|
|
|
405
427
|
|
|
406
428
|
const getExecutor = (features: Features): FirmwareUpdatePlan['executor'] => {
|
|
407
429
|
const deviceType = getDeviceType(features);
|
|
408
|
-
if (deviceType === EDeviceType.Pro2) {
|
|
430
|
+
if (deviceType === EDeviceType.Pro2 || deviceType === EDeviceType.Neo) {
|
|
409
431
|
return 'v4';
|
|
410
432
|
}
|
|
411
433
|
if (
|
|
@@ -421,10 +443,10 @@ const buildProtocolV2Artifacts = (
|
|
|
421
443
|
release: ReleaseRecord,
|
|
422
444
|
{
|
|
423
445
|
includeComponents = true,
|
|
424
|
-
|
|
446
|
+
componentTargets: selectedComponentTargets,
|
|
425
447
|
}: {
|
|
426
448
|
includeComponents?: boolean;
|
|
427
|
-
|
|
449
|
+
componentTargets?: ReadonlySet<FirmwareUpdatePlanTarget>;
|
|
428
450
|
} = {}
|
|
429
451
|
): {
|
|
430
452
|
artifacts: FirmwareUpdatePlanArtifact[];
|
|
@@ -445,12 +467,21 @@ const buildProtocolV2Artifacts = (
|
|
|
445
467
|
includeComponents && components
|
|
446
468
|
? [...installOrder, ...Object.keys(components).filter(key => !installOrder.includes(key))]
|
|
447
469
|
: [];
|
|
470
|
+
const selectedComponentKeys = selectedComponentTargets
|
|
471
|
+
? componentKeys.filter(key => {
|
|
472
|
+
const component = asRecord(components?.[key]);
|
|
473
|
+
const targetName = asString(component?.target)?.toUpperCase();
|
|
474
|
+
const target = targetName ? PROTOCOL_V2_TARGETS[targetName] : undefined;
|
|
475
|
+
return !!target && selectedComponentTargets.has(target);
|
|
476
|
+
})
|
|
477
|
+
: componentKeys;
|
|
448
478
|
const artifacts: FirmwareUpdatePlanArtifact[] = [];
|
|
449
479
|
const targets: FirmwareUpdatePlanTarget[] = [];
|
|
450
|
-
const
|
|
451
|
-
for (const key of
|
|
480
|
+
const componentTargetSet = new Set<FirmwareUpdatePlanTarget>();
|
|
481
|
+
for (const key of selectedComponentKeys) {
|
|
452
482
|
const component = asRecord(components?.[key]);
|
|
453
483
|
const targetName = asString(component?.target)?.toUpperCase();
|
|
484
|
+
const target = targetName ? PROTOCOL_V2_TARGETS[targetName] : undefined;
|
|
454
485
|
if (targetName === 'ROMLOADER') {
|
|
455
486
|
throw ERRORS.TypedError(
|
|
456
487
|
HardwareErrorCode.RuntimeError,
|
|
@@ -458,7 +489,6 @@ const buildProtocolV2Artifacts = (
|
|
|
458
489
|
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
459
490
|
);
|
|
460
491
|
}
|
|
461
|
-
const target = targetName ? PROTOCOL_V2_TARGETS[targetName] : undefined;
|
|
462
492
|
if (!component || !target) {
|
|
463
493
|
throw ERRORS.TypedError(
|
|
464
494
|
HardwareErrorCode.RuntimeError,
|
|
@@ -466,14 +496,14 @@ const buildProtocolV2Artifacts = (
|
|
|
466
496
|
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
467
497
|
);
|
|
468
498
|
}
|
|
469
|
-
if (
|
|
499
|
+
if (componentTargetSet.has(target)) {
|
|
470
500
|
throw ERRORS.TypedError(
|
|
471
501
|
HardwareErrorCode.RuntimeError,
|
|
472
502
|
`Protocol V2 component ${key} duplicates target ${target}`,
|
|
473
503
|
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
474
504
|
);
|
|
475
505
|
}
|
|
476
|
-
|
|
506
|
+
componentTargetSet.add(target);
|
|
477
507
|
artifacts.push({
|
|
478
508
|
artifactId: `component:${target}`,
|
|
479
509
|
role: 'component',
|
|
@@ -490,43 +520,6 @@ const buildProtocolV2Artifacts = (
|
|
|
490
520
|
targets.push(target);
|
|
491
521
|
}
|
|
492
522
|
|
|
493
|
-
const bundles =
|
|
494
|
-
includeResources && Array.isArray(release.resourceBundles) ? release.resourceBundles : [];
|
|
495
|
-
const resourceBundleNames = new Set<string>();
|
|
496
|
-
for (const value of bundles) {
|
|
497
|
-
const bundle = asRecord(value);
|
|
498
|
-
const name = asString(bundle?.name);
|
|
499
|
-
if (!bundle || !name) {
|
|
500
|
-
throw ERRORS.TypedError(
|
|
501
|
-
HardwareErrorCode.RuntimeError,
|
|
502
|
-
'Protocol V2 resource bundle is invalid',
|
|
503
|
-
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
504
|
-
);
|
|
505
|
-
}
|
|
506
|
-
if (resourceBundleNames.has(name)) {
|
|
507
|
-
throw ERRORS.TypedError(
|
|
508
|
-
HardwareErrorCode.RuntimeError,
|
|
509
|
-
`Protocol V2 resource bundle duplicates name ${name}`,
|
|
510
|
-
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
resourceBundleNames.add(name);
|
|
514
|
-
artifacts.push({
|
|
515
|
-
artifactId: `resourceBundle:${name}`,
|
|
516
|
-
role: 'resourceBundle',
|
|
517
|
-
target: 'resource',
|
|
518
|
-
url: assertArtifactUrl(bundle.url, `Protocol V2 resource bundle ${name}`),
|
|
519
|
-
container: 'raw',
|
|
520
|
-
logicalName: name,
|
|
521
|
-
...asIntegrity({
|
|
522
|
-
size: bundle.expectedSize,
|
|
523
|
-
sha256: bundle.fingerprint,
|
|
524
|
-
}),
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
if (bundles.length > 0) {
|
|
528
|
-
targets.push('resource');
|
|
529
|
-
}
|
|
530
523
|
return { artifacts, targets: [...new Set(targets)] };
|
|
531
524
|
};
|
|
532
525
|
|
|
@@ -587,6 +580,140 @@ const assertForcedTargetsRepresented = ({
|
|
|
587
580
|
}
|
|
588
581
|
};
|
|
589
582
|
|
|
583
|
+
const finalizeFirmwareUpdatePlan = ({
|
|
584
|
+
features,
|
|
585
|
+
firmwareType,
|
|
586
|
+
platform,
|
|
587
|
+
artifacts,
|
|
588
|
+
targetsToUpdate,
|
|
589
|
+
}: {
|
|
590
|
+
features: Features;
|
|
591
|
+
firmwareType: EFirmwareType;
|
|
592
|
+
platform: FirmwareUpdatePlatform;
|
|
593
|
+
artifacts: FirmwareUpdatePlanArtifact[];
|
|
594
|
+
targetsToUpdate: FirmwareUpdatePlanTarget[];
|
|
595
|
+
}): FirmwareUpdatePlan => {
|
|
596
|
+
const executor = getExecutor(features);
|
|
597
|
+
const bootloaderMode = resolveDeviceBootloaderMode(features);
|
|
598
|
+
// 'unavailable' is the reserved degraded-identity sentinel; a device-reported
|
|
599
|
+
// serial must never be able to collide with it.
|
|
600
|
+
const reportedIdentity = getDeviceUUID(features);
|
|
601
|
+
const deviceIdentity = reportedIdentity === 'unavailable' ? '' : reportedIdentity;
|
|
602
|
+
// Bootloader-mode classic-family devices (executor v2, e.g. classic1s) cannot report
|
|
603
|
+
// a serial number, so their recovery plans fall back to the degraded 'unavailable'
|
|
604
|
+
// identity. Binding is then enforced against the live device at install time instead
|
|
605
|
+
// (assertFirmwareUpdatePreparedPlanDeviceIdentity), and plan integrity by the digest.
|
|
606
|
+
// Pro (v3), Pro2, and Neo (v4) report a serial even in loader mode and stay strict.
|
|
607
|
+
if (
|
|
608
|
+
artifacts.length > 0 &&
|
|
609
|
+
(platform === 'native' || platform === 'desktop') &&
|
|
610
|
+
!deviceIdentity &&
|
|
611
|
+
!(bootloaderMode && executor === 'v2')
|
|
612
|
+
) {
|
|
613
|
+
throw ERRORS.TypedError(
|
|
614
|
+
HardwareErrorCode.RuntimeError,
|
|
615
|
+
'Prepared firmware update requires a stable device identity',
|
|
616
|
+
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
const planWithoutDigest: Omit<FirmwareUpdatePlan, 'planDigest'> = {
|
|
620
|
+
schemaVersion: 2,
|
|
621
|
+
executor,
|
|
622
|
+
deviceIdentity: deviceIdentity || 'unavailable',
|
|
623
|
+
deviceModel: String(getDeviceType(features)),
|
|
624
|
+
firmwareType,
|
|
625
|
+
platform,
|
|
626
|
+
artifacts,
|
|
627
|
+
targetsToUpdate,
|
|
628
|
+
};
|
|
629
|
+
return assertFirmwareUpdatePlan({
|
|
630
|
+
...planWithoutDigest,
|
|
631
|
+
planDigest: digestFirmwareUpdatePlan(planWithoutDigest),
|
|
632
|
+
});
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
export const buildProtocolV2FirmwareUpdatePlan = ({
|
|
636
|
+
features,
|
|
637
|
+
firmwareType,
|
|
638
|
+
platform,
|
|
639
|
+
release,
|
|
640
|
+
targetsToUpdate,
|
|
641
|
+
forceUpdateTargets,
|
|
642
|
+
resourceArchive,
|
|
643
|
+
}: {
|
|
644
|
+
features: Features;
|
|
645
|
+
firmwareType: EFirmwareType;
|
|
646
|
+
platform: FirmwareUpdatePlatform;
|
|
647
|
+
release: ReleaseRecord | undefined;
|
|
648
|
+
targetsToUpdate: readonly FirmwareUpdateV4Target[];
|
|
649
|
+
forceUpdateTargets?: FirmwareUpdatePlanForceTarget[];
|
|
650
|
+
resourceArchive: IProtocolV2ResourceSource | undefined;
|
|
651
|
+
}): FirmwareUpdatePlan => {
|
|
652
|
+
const validatedForceTargets = validateFirmwareUpdatePlanForceTargets(forceUpdateTargets);
|
|
653
|
+
if (validatedForceTargets.some(target => target === 'ble' || target === 'bootloader')) {
|
|
654
|
+
planError('Protocol V2 does not support forced BLE or legacy bootloader targets');
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const requestedComponentTargets = new Set<FirmwareUpdatePlanTarget>(
|
|
658
|
+
targetsToUpdate.filter(target => target !== 'resource')
|
|
659
|
+
);
|
|
660
|
+
if (
|
|
661
|
+
getDeviceType(features) === EDeviceType.Neo &&
|
|
662
|
+
(requestedComponentTargets.has('se03') || requestedComponentTargets.has('se04'))
|
|
663
|
+
) {
|
|
664
|
+
planError('Neo does not support Protocol V2 targets se03 or se04');
|
|
665
|
+
}
|
|
666
|
+
const protocolV2 = buildProtocolV2Artifacts(release ?? {}, {
|
|
667
|
+
includeComponents: requestedComponentTargets.size > 0,
|
|
668
|
+
componentTargets: requestedComponentTargets,
|
|
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
|
+
}
|
|
694
|
+
const representedTargets = new Set(protocolV2.targets);
|
|
695
|
+
const missingTarget = Array.from(requestedComponentTargets).find(
|
|
696
|
+
target => !representedTargets.has(target)
|
|
697
|
+
);
|
|
698
|
+
if (missingTarget) {
|
|
699
|
+
planError(`Protocol V2 release does not contain requested target ${missingTarget}`);
|
|
700
|
+
}
|
|
701
|
+
if (validatedForceTargets.includes('firmware') && protocolV2.targets.length === 0) {
|
|
702
|
+
planError('Forced firmware update target firmware is not represented by the plan');
|
|
703
|
+
}
|
|
704
|
+
if (validatedForceTargets.includes('resource') && !resourceArchive) {
|
|
705
|
+
planError('Forced firmware update target resource has no Protocol V2 resource archive');
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return finalizeFirmwareUpdatePlan({
|
|
709
|
+
features,
|
|
710
|
+
firmwareType,
|
|
711
|
+
platform,
|
|
712
|
+
artifacts: protocolV2.artifacts,
|
|
713
|
+
targetsToUpdate: protocolV2.targets,
|
|
714
|
+
});
|
|
715
|
+
};
|
|
716
|
+
|
|
590
717
|
export const buildFirmwareUpdatePlan = ({
|
|
591
718
|
features,
|
|
592
719
|
firmwareType,
|
|
@@ -635,7 +762,6 @@ export const buildFirmwareUpdatePlan = ({
|
|
|
635
762
|
if (executor === 'v4' && (shouldUpdateFirmware || shouldUpdateResource)) {
|
|
636
763
|
const protocolV2 = buildProtocolV2Artifacts(firmwareRelease ?? {}, {
|
|
637
764
|
includeComponents: shouldUpdateFirmware,
|
|
638
|
-
includeResources: shouldUpdateResource,
|
|
639
765
|
});
|
|
640
766
|
artifacts = protocolV2.artifacts;
|
|
641
767
|
targetsToUpdate = protocolV2.targets;
|
|
@@ -733,40 +859,11 @@ export const buildFirmwareUpdatePlan = ({
|
|
|
733
859
|
artifacts,
|
|
734
860
|
targetsToUpdate,
|
|
735
861
|
});
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
// serial must never be able to collide with it.
|
|
739
|
-
const reportedIdentity = getDeviceUUID(features);
|
|
740
|
-
const deviceIdentity = reportedIdentity === 'unavailable' ? '' : reportedIdentity;
|
|
741
|
-
// Bootloader-mode classic-family devices (executor v2, e.g. classic1s) cannot report
|
|
742
|
-
// a serial number, so their recovery plans fall back to the degraded 'unavailable'
|
|
743
|
-
// identity. Binding is then enforced against the live device at install time instead
|
|
744
|
-
// (assertFirmwareUpdatePreparedPlanDeviceIdentity), and plan integrity by the digest.
|
|
745
|
-
// Pro (v3) and Pro2 (v4) report a serial even in bootloader mode and stay strict.
|
|
746
|
-
if (
|
|
747
|
-
artifacts.length > 0 &&
|
|
748
|
-
(platform === 'native' || platform === 'desktop') &&
|
|
749
|
-
!deviceIdentity &&
|
|
750
|
-
!(bootloaderMode && executor === 'v2')
|
|
751
|
-
) {
|
|
752
|
-
throw ERRORS.TypedError(
|
|
753
|
-
HardwareErrorCode.RuntimeError,
|
|
754
|
-
'Prepared firmware update requires a stable device identity',
|
|
755
|
-
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
756
|
-
);
|
|
757
|
-
}
|
|
758
|
-
const planWithoutDigest: Omit<FirmwareUpdatePlan, 'planDigest'> = {
|
|
759
|
-
schemaVersion: 2,
|
|
760
|
-
executor,
|
|
761
|
-
deviceIdentity: deviceIdentity || 'unavailable',
|
|
762
|
-
deviceModel: String(getDeviceType(features)),
|
|
862
|
+
return finalizeFirmwareUpdatePlan({
|
|
863
|
+
features,
|
|
763
864
|
firmwareType,
|
|
764
865
|
platform,
|
|
765
866
|
artifacts,
|
|
766
867
|
targetsToUpdate,
|
|
767
|
-
};
|
|
768
|
-
return assertFirmwareUpdatePlan({
|
|
769
|
-
...planWithoutDigest,
|
|
770
|
-
planDigest: digestFirmwareUpdatePlan(planWithoutDigest),
|
|
771
868
|
});
|
|
772
869
|
};
|
|
@@ -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)) ||
|
|
@@ -135,6 +135,8 @@ export function toProtocolV2FirmwareReleaseInfo({
|
|
|
135
135
|
return {
|
|
136
136
|
shouldUpdate: plan.hasUpgrade,
|
|
137
137
|
status: plan.status === 'unavailable' ? 'unknown' : plan.status,
|
|
138
|
+
// Protocol V2 components share package-set release notes. The optional
|
|
139
|
+
// bootloaderChangelog field belongs to the legacy Protocol V1 release model.
|
|
138
140
|
changelog: plan.release ? [plan.release.changelog] : [],
|
|
139
141
|
release,
|
|
140
142
|
bootloaderMode: state.status.mode === 'bootloader' || state.status.mode === 'romloader',
|
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
|
-
}
|
|
@@ -89,6 +89,7 @@ export type CheckAllFirmwareReleaseParams = {
|
|
|
89
89
|
firmwareType?: EFirmwareType;
|
|
90
90
|
platform?: 'native' | 'desktop' | 'ext' | 'web' | 'web-embed';
|
|
91
91
|
forceUpdateTargets?: FirmwareUpdatePlanForceTarget[];
|
|
92
|
+
protocolV2ForceUpdateTargets?: FirmwareUpdateV4Target[];
|
|
92
93
|
};
|
|
93
94
|
|
|
94
95
|
export declare function checkAllFirmwareRelease(
|
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,
|
|
@@ -153,25 +153,10 @@ export interface FirmwareUpdateV4Params {
|
|
|
153
153
|
se03Binary?: ArrayBuffer;
|
|
154
154
|
se04Binary?: ArrayBuffer;
|
|
155
155
|
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
156
|
artifactReader?: FirmwareArtifactReader;
|
|
168
157
|
componentArtifacts?: Partial<
|
|
169
158
|
Record<Exclude<FirmwareUpdateV4Target, 'resource'>, FirmwareArtifactReference>
|
|
170
159
|
>;
|
|
171
|
-
resourceBundleArtifacts?: Array<{
|
|
172
|
-
name: string;
|
|
173
|
-
artifact: FirmwareArtifactReference;
|
|
174
|
-
}>;
|
|
175
160
|
}
|
|
176
161
|
|
|
177
162
|
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
|
};
|
|
@@ -328,7 +328,7 @@ export const getFirmwareUpdateField = ({
|
|
|
328
328
|
if (deviceType === EDeviceType.Pro) {
|
|
329
329
|
return latestFirmwareField;
|
|
330
330
|
}
|
|
331
|
-
if (deviceType === EDeviceType.Pro2) {
|
|
331
|
+
if (deviceType === EDeviceType.Pro2 || deviceType === EDeviceType.Neo) {
|
|
332
332
|
return 'firmware-v1';
|
|
333
333
|
}
|
|
334
334
|
return 'firmware';
|
|
@@ -374,7 +374,7 @@ export const getFirmwareUpdateFieldArray = (
|
|
|
374
374
|
return ['firmware-v8'];
|
|
375
375
|
}
|
|
376
376
|
|
|
377
|
-
if (deviceType === 'pro2') {
|
|
377
|
+
if (deviceType === 'pro2' || deviceType === 'neo') {
|
|
378
378
|
return ['firmware-v1'];
|
|
379
379
|
}
|
|
380
380
|
|
|
@@ -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"}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { FirmwareUpdateV4Target } from './firmwareUpdate';
|
|
2
|
-
|
|
3
|
-
export type ProtocolV2ResourceManifestBinary = {
|
|
4
|
-
archivePath: string;
|
|
5
|
-
binary: ArrayBuffer;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export type ProtocolV2PreparedResourceFile = {
|
|
9
|
-
binary: ArrayBuffer;
|
|
10
|
-
devicePath: string;
|
|
11
|
-
size: number;
|
|
12
|
-
fileHash: string;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export declare function prepareProtocolV2ResourceFiles(input: {
|
|
16
|
-
manifest: unknown;
|
|
17
|
-
files: ProtocolV2ResourceManifestBinary[];
|
|
18
|
-
targetsToUpdate: readonly FirmwareUpdateV4Target[];
|
|
19
|
-
}): ProtocolV2PreparedResourceFile[];
|