@onekeyfe/hd-core 1.2.0-alpha.40 → 1.2.0-alpha.42
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 +14 -1
- package/__tests__/protocol-v2-resources.test.ts +204 -0
- package/__tests__/protocol-v2.test.ts +152 -282
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +2 -4
- package/dist/api/FirmwareUpdateV4.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 +24 -3
- package/dist/index.js +348 -217
- package/dist/protocols/protocol-v2/resources.d.ts +30 -0
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
- package/dist/types/api/checkAllFirmwareRelease.d.ts +1 -0
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/settings.d.ts +12 -0
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +35 -3
- package/src/api/FirmwareUpdateV4.ts +76 -214
- package/src/data/messages/messages-protocol-v2.json +43 -0
- package/src/data-manager/DataManager.ts +32 -7
- package/src/protocols/protocol-v2/resources.ts +233 -0
- package/src/types/api/checkAllFirmwareRelease.ts +1 -0
- package/src/types/settings.ts +26 -0
|
@@ -2,7 +2,6 @@ import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@on
|
|
|
2
2
|
import {
|
|
3
3
|
DeviceRebootType,
|
|
4
4
|
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
5
|
-
PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
|
|
6
5
|
PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
|
|
7
6
|
} from '@onekeyfe/hd-transport';
|
|
8
7
|
import { sha256 } from '@noble/hashes/sha256';
|
|
@@ -27,6 +26,12 @@ import {
|
|
|
27
26
|
ProtocolV2FirmwareTargetType,
|
|
28
27
|
} from '../protocols/protocol-v2';
|
|
29
28
|
import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
|
|
29
|
+
import {
|
|
30
|
+
PROTOCOL_V2_RESOURCE_DEVICE_PATHS,
|
|
31
|
+
buildProtocolV2ResourceUpdatePlan,
|
|
32
|
+
isProtocolV2ResourceFileValid,
|
|
33
|
+
requestProtocolV2ResourceInventory,
|
|
34
|
+
} from '../protocols/protocol-v2/resources';
|
|
30
35
|
import {
|
|
31
36
|
getProtocolV2UnknownErrorText,
|
|
32
37
|
isProtocolV2DeviceDisconnectedError,
|
|
@@ -41,6 +46,7 @@ import type {
|
|
|
41
46
|
Features,
|
|
42
47
|
IFirmwareReleaseInfo,
|
|
43
48
|
IProtocolV2FirmwareComponent,
|
|
49
|
+
IProtocolV2Resource,
|
|
44
50
|
IVersionArray,
|
|
45
51
|
} from '../types';
|
|
46
52
|
|
|
@@ -120,11 +126,6 @@ type ProtocolV2ResourceBundleBinary = {
|
|
|
120
126
|
name: string;
|
|
121
127
|
binary: ArrayBuffer;
|
|
122
128
|
devicePath: string;
|
|
123
|
-
/** Download URL for remote-config mode; omitted in manual mode. */
|
|
124
|
-
url?: string;
|
|
125
|
-
version?: IVersionArray;
|
|
126
|
-
payloadHash?: string;
|
|
127
|
-
headerHash?: string;
|
|
128
129
|
};
|
|
129
130
|
|
|
130
131
|
type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
|
|
@@ -276,18 +277,6 @@ const normalizeProtocolV2TargetStatus = (
|
|
|
276
277
|
|
|
277
278
|
const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
|
|
278
279
|
|
|
279
|
-
const versionArrayToNumber = (version?: IVersionArray) => {
|
|
280
|
-
if (!version) return undefined;
|
|
281
|
-
return version[0] * 0x10000 + version[1] * 0x100 + version[2];
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
const compareProtocolV2Versions = (current?: IVersionArray, target?: IVersionArray) => {
|
|
285
|
-
const currentNumber = versionArrayToNumber(current);
|
|
286
|
-
const targetNumber = versionArrayToNumber(target);
|
|
287
|
-
if (currentNumber === undefined || targetNumber === undefined) return undefined;
|
|
288
|
-
return currentNumber - targetNumber;
|
|
289
|
-
};
|
|
290
|
-
|
|
291
280
|
const bytesToHex = (bytes: Uint8Array) =>
|
|
292
281
|
Array.from(bytes)
|
|
293
282
|
.map(byte => byte.toString(16).padStart(2, '0'))
|
|
@@ -468,16 +457,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
468
457
|
};
|
|
469
458
|
}
|
|
470
459
|
|
|
471
|
-
private getProtocolV2FirmwareChunkSize(
|
|
460
|
+
private getProtocolV2FirmwareChunkSize() {
|
|
472
461
|
const payloadChunkSize = Number(this.params?.chunkSize);
|
|
473
462
|
const env = DataManager.getSettings('env');
|
|
474
463
|
const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
|
|
475
464
|
let maxChunkSize = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
|
|
476
465
|
if (isBle) {
|
|
477
|
-
maxChunkSize =
|
|
478
|
-
direction === 'read'
|
|
479
|
-
? PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE
|
|
480
|
-
: PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
|
|
466
|
+
maxChunkSize = PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
|
|
481
467
|
}
|
|
482
468
|
if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
|
|
483
469
|
return maxChunkSize;
|
|
@@ -498,6 +484,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
498
484
|
const deviceFeatures = await this.getProtocolV2DeviceFeatures();
|
|
499
485
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
500
486
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
487
|
+
const resourceRecoveryMode = Boolean(
|
|
488
|
+
this.isProtocolV2BootloaderMode() || this.isProtocolV2RomloaderMode()
|
|
489
|
+
);
|
|
501
490
|
|
|
502
491
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
503
492
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
@@ -507,7 +496,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
507
496
|
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
508
497
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
509
498
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
510
|
-
|
|
499
|
+
const needsRemoteFirmware = !this.hasExplicitProtocolV2Payload(fwBinaryMap);
|
|
500
|
+
const needsRemoteResources =
|
|
501
|
+
!this.params.resourceBundleFiles?.length &&
|
|
502
|
+
!!this.params.targetsToUpdate?.includes('resource');
|
|
503
|
+
if (needsRemoteFirmware || needsRemoteResources) {
|
|
504
|
+
// Remote updates must use a freshly fetched config before any reboot or file write.
|
|
505
|
+
await DataManager.forceReloadData();
|
|
506
|
+
}
|
|
507
|
+
if (needsRemoteFirmware) {
|
|
511
508
|
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
512
509
|
firmwareType,
|
|
513
510
|
deviceFeatures
|
|
@@ -516,16 +513,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
516
513
|
fwBinaryMap = remoteBinaries.fwBinaryMap;
|
|
517
514
|
installItems = remoteBinaries.installItems;
|
|
518
515
|
}
|
|
519
|
-
resourceBundles = this.prepareProtocolV2ResourceBundles(
|
|
520
|
-
if (resourceBundles?.length) {
|
|
521
|
-
resourceBundles = await this.prefetchProtocolV2ResourceBundles(resourceBundles);
|
|
522
|
-
}
|
|
516
|
+
resourceBundles = await this.prepareProtocolV2ResourceBundles(resourceRecoveryMode);
|
|
523
517
|
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
524
518
|
} catch (err) {
|
|
525
519
|
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
526
520
|
}
|
|
527
521
|
|
|
528
522
|
if (!bootloaderBinary && fwBinaryMap.length === 0 && !resourceBundles?.length) {
|
|
523
|
+
if (resourceBundles !== undefined) {
|
|
524
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
525
|
+
return this.getProtocolV2VersionResult(deviceFeatures);
|
|
526
|
+
}
|
|
529
527
|
throw ERRORS.TypedError(
|
|
530
528
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
531
529
|
'No firmware to update'
|
|
@@ -662,57 +660,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
662
660
|
return target;
|
|
663
661
|
}
|
|
664
662
|
|
|
665
|
-
private getProtocolV2ResourceFilePath(path: string) {
|
|
666
|
-
if (path.startsWith('vol')) return path;
|
|
667
|
-
if (path.startsWith('/')) return `vol0:${path}`;
|
|
668
|
-
return `vol0:/${path}`;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
private async readProtocolV2DeviceFileHeader(path: string) {
|
|
672
|
-
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
673
|
-
const filePath = this.getProtocolV2ResourceFilePath(path);
|
|
674
|
-
const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
|
|
675
|
-
path: filePath,
|
|
676
|
-
});
|
|
677
|
-
const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
|
|
678
|
-
if (
|
|
679
|
-
!pathInfoRes.message?.exist ||
|
|
680
|
-
pathInfoRes.message?.directory ||
|
|
681
|
-
fileSize === undefined ||
|
|
682
|
-
fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
|
|
683
|
-
) {
|
|
684
|
-
return null;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
|
|
688
|
-
const chunks: Uint8Array[] = [];
|
|
689
|
-
let offset = 0;
|
|
690
|
-
while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
|
|
691
|
-
const readLen = Math.min(chunkSize, PROTOCOL_V2_OKPP_HEADER_SIZE - offset);
|
|
692
|
-
const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
|
|
693
|
-
file: {
|
|
694
|
-
path: filePath,
|
|
695
|
-
offset,
|
|
696
|
-
total_size: 0,
|
|
697
|
-
},
|
|
698
|
-
chunk_len: readLen,
|
|
699
|
-
ui_percentage: undefined,
|
|
700
|
-
});
|
|
701
|
-
const data = toProtocolV2Bytes(res.message?.data);
|
|
702
|
-
if (data.byteLength === 0) return null;
|
|
703
|
-
chunks.push(data);
|
|
704
|
-
offset += data.byteLength;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
const headerBytes = new Uint8Array(offset);
|
|
708
|
-
let cursor = 0;
|
|
709
|
-
chunks.forEach(chunk => {
|
|
710
|
-
headerBytes.set(chunk, cursor);
|
|
711
|
-
cursor += chunk.byteLength;
|
|
712
|
-
});
|
|
713
|
-
return parseProtocolV2OkppHeader(headerBytes);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
663
|
private async downloadRemoteProtocolV2Component(
|
|
717
664
|
key: string,
|
|
718
665
|
component: IProtocolV2FirmwareComponent
|
|
@@ -801,23 +748,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
801
748
|
};
|
|
802
749
|
}
|
|
803
750
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
/**
|
|
809
|
-
* Prepare the RESC bundle list.
|
|
810
|
-
*
|
|
811
|
-
* Two modes, matching FirmwareUpdateV3 binary-versus-version behavior:
|
|
812
|
-
* - resourceBundleFiles supplied: use binaries directly without version checks.
|
|
813
|
-
* - Otherwise load release.resourceBundles from remote config.json; the binaries
|
|
814
|
-
* are prefetched before reboot and synchronization skips matching device headers.
|
|
815
|
-
*/
|
|
816
|
-
private prepareProtocolV2ResourceBundles(
|
|
817
|
-
firmwareType: EFirmwareType,
|
|
818
|
-
features: Features
|
|
819
|
-
): ProtocolV2ResourceBundleBinary[] | undefined {
|
|
820
|
-
// Manual binaries are installed directly without comparison.
|
|
751
|
+
private async prepareProtocolV2ResourceBundles(
|
|
752
|
+
recoveryMode: boolean
|
|
753
|
+
): Promise<ProtocolV2ResourceBundleBinary[] | undefined> {
|
|
821
754
|
if (this.params.resourceBundleFiles?.length) {
|
|
822
755
|
return this.params.resourceBundleFiles.map((file, index) => {
|
|
823
756
|
const devicePath = validateProtocolV2FilesystemPath(
|
|
@@ -836,70 +769,51 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
836
769
|
return undefined;
|
|
837
770
|
}
|
|
838
771
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
772
|
+
const resources = DataManager.getProtocolV2Resources();
|
|
773
|
+
if (!resources?.length) {
|
|
774
|
+
throw new Error('Missing Pro2 stable resource configuration');
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const inventory = recoveryMode
|
|
778
|
+
? undefined
|
|
779
|
+
: await requestProtocolV2ResourceInventory({
|
|
780
|
+
commands: this.device.getCommands(),
|
|
781
|
+
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
782
|
+
});
|
|
783
|
+
const plan = buildProtocolV2ResourceUpdatePlan({
|
|
784
|
+
resources,
|
|
785
|
+
inventory,
|
|
786
|
+
mode: recoveryMode ? 'bootloader-recovery' : 'application',
|
|
787
|
+
forced: this.params.forcedUpdateRes,
|
|
788
|
+
});
|
|
789
|
+
Log.log(
|
|
790
|
+
`[FirmwareUpdateV4] Pro2 resource plan mode=${
|
|
791
|
+
recoveryMode ? 'bootloader-recovery' : 'application'
|
|
792
|
+
} status=${plan.status} count=${plan.resources.length}`
|
|
793
|
+
);
|
|
794
|
+
|
|
795
|
+
const bundles: ProtocolV2ResourceBundleBinary[] = [];
|
|
796
|
+
for (const resource of plan.resources) {
|
|
797
|
+
bundles.push(await this.downloadProtocolV2Resource(resource));
|
|
798
|
+
}
|
|
799
|
+
return bundles;
|
|
855
800
|
}
|
|
856
801
|
|
|
857
|
-
private async
|
|
858
|
-
|
|
859
|
-
): Promise<ProtocolV2ResourceBundleBinary
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if (binary.byteLength === 0) {
|
|
865
|
-
if (!bundle.url) {
|
|
866
|
-
throw new Error(`Missing Protocol V2 RESC bundle binary: ${bundle.name}`);
|
|
867
|
-
}
|
|
868
|
-
Log.log(`[FirmwareUpdateV4] downloading remote RESC bundle ${bundle.name}`);
|
|
869
|
-
({ binary } = await getSysResourceBinary(bundle.url));
|
|
870
|
-
downloadedFromRemote = true;
|
|
871
|
-
}
|
|
872
|
-
if (binary.byteLength === 0) {
|
|
873
|
-
throw new Error(`Protocol V2 RESC bundle is empty: ${bundle.name}`);
|
|
874
|
-
}
|
|
875
|
-
if (downloadedFromRemote) {
|
|
876
|
-
const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
|
|
877
|
-
if (!header || header.type !== 'RESC') {
|
|
878
|
-
throw new Error(`Invalid Protocol V2 RESC bundle header: ${bundle.name}`);
|
|
879
|
-
}
|
|
880
|
-
if (bundle.version && compareProtocolV2Versions(header.version, bundle.version) !== 0) {
|
|
881
|
-
throw new Error(`Protocol V2 RESC bundle version mismatch: ${bundle.name}`);
|
|
882
|
-
}
|
|
883
|
-
const expectedPayloadHash = normalizeProtocolV2Hex(bundle.payloadHash);
|
|
884
|
-
if (expectedPayloadHash && header.payloadHash !== expectedPayloadHash) {
|
|
885
|
-
throw new Error(`Protocol V2 RESC bundle payload hash mismatch: ${bundle.name}`);
|
|
886
|
-
}
|
|
887
|
-
const expectedHeaderHash = normalizeProtocolV2Hex(bundle.headerHash);
|
|
888
|
-
if (expectedHeaderHash && header.headerHash !== expectedHeaderHash) {
|
|
889
|
-
throw new Error(`Protocol V2 RESC bundle header hash mismatch: ${bundle.name}`);
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
prefetched.push({ ...bundle, binary });
|
|
802
|
+
private async downloadProtocolV2Resource(
|
|
803
|
+
resource: IProtocolV2Resource
|
|
804
|
+
): Promise<ProtocolV2ResourceBundleBinary> {
|
|
805
|
+
Log.log(`[FirmwareUpdateV4] downloading Pro2 resource ${resource.type}`);
|
|
806
|
+
const { binary } = await getSysResourceBinary(resource.url);
|
|
807
|
+
if (!isProtocolV2ResourceFileValid(binary, resource)) {
|
|
808
|
+
throw new Error(`Pro2 resource file verification failed: ${resource.type}`);
|
|
893
809
|
}
|
|
894
|
-
return
|
|
810
|
+
return {
|
|
811
|
+
name: `${resource.type}.okpkg`,
|
|
812
|
+
binary,
|
|
813
|
+
devicePath: PROTOCOL_V2_RESOURCE_DEVICE_PATHS[resource.type],
|
|
814
|
+
};
|
|
895
815
|
}
|
|
896
816
|
|
|
897
|
-
/**
|
|
898
|
-
* Synchronize RESC bundles to the device.
|
|
899
|
-
*
|
|
900
|
-
* Manual mode writes supplied binaries directly.
|
|
901
|
-
* Remote-config mode uses prefetched binaries and skips matching bundles after FileRead.
|
|
902
|
-
*/
|
|
903
817
|
private async syncProtocolV2ResourceBundles(
|
|
904
818
|
bundles: ProtocolV2ResourceBundleBinary[],
|
|
905
819
|
firmwareSize: number
|
|
@@ -907,37 +821,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
907
821
|
const transferStartTime = Date.now();
|
|
908
822
|
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
909
823
|
|
|
910
|
-
const isRemoteMode = bundles.some(bundle => !!bundle.url);
|
|
911
|
-
let bundlesToSync = bundles;
|
|
912
|
-
|
|
913
|
-
// Remote binaries were prefetched before bootloader entry. Only device-local
|
|
914
|
-
// header comparison and transfer are allowed in this phase.
|
|
915
|
-
if (isRemoteMode) {
|
|
916
|
-
const filtered: ProtocolV2ResourceBundleBinary[] = [];
|
|
917
|
-
for (const bundle of bundles) {
|
|
918
|
-
// Compare the existing device header.
|
|
919
|
-
const upToDate = await this.isProtocolV2ResourceBundleUpToDate(bundle);
|
|
920
|
-
if (upToDate) {
|
|
921
|
-
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${bundle.name}; already up to date`);
|
|
922
|
-
} else {
|
|
923
|
-
filtered.push(bundle);
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
bundlesToSync = filtered;
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
if (bundlesToSync.length === 0) {
|
|
930
|
-
Log.log('[FirmwareUpdateV4] all RESC bundles up to date, nothing to sync');
|
|
931
|
-
return { processedSize: 0, totalSize: firmwareSize };
|
|
932
|
-
}
|
|
933
|
-
|
|
934
824
|
// Write directly to the device through FileWrite.
|
|
935
825
|
let totalSize = 0;
|
|
936
|
-
for (const b of
|
|
826
|
+
for (const b of bundles) totalSize += b.binary.byteLength;
|
|
937
827
|
|
|
938
828
|
const transferTotalSize = totalSize + firmwareSize;
|
|
939
829
|
let processedSize = 0;
|
|
940
|
-
for (const bundle of
|
|
830
|
+
for (const bundle of bundles) {
|
|
941
831
|
Log.log(
|
|
942
832
|
`[FirmwareUpdateV4] syncing RESC bundle ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
|
|
943
833
|
);
|
|
@@ -958,41 +848,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
958
848
|
return { processedSize, totalSize: transferTotalSize };
|
|
959
849
|
}
|
|
960
850
|
|
|
961
|
-
/**
|
|
962
|
-
* Compare the existing okpkg OKPP header in remote-config mode.
|
|
963
|
-
*/
|
|
964
|
-
private async isProtocolV2ResourceBundleUpToDate(
|
|
965
|
-
bundle: ProtocolV2ResourceBundleBinary
|
|
966
|
-
): Promise<boolean> {
|
|
967
|
-
if (this.params.forcedUpdateRes) return false;
|
|
968
|
-
if (!bundle.version && !bundle.payloadHash) return false;
|
|
969
|
-
|
|
970
|
-
try {
|
|
971
|
-
const header = await this.readProtocolV2DeviceFileHeader(bundle.devicePath);
|
|
972
|
-
if (!header) return false;
|
|
973
|
-
|
|
974
|
-
if (bundle.version) {
|
|
975
|
-
const cmp = compareProtocolV2Versions(header.version, bundle.version);
|
|
976
|
-
if (cmp === undefined || cmp !== 0) return false;
|
|
977
|
-
}
|
|
978
|
-
if (bundle.payloadHash) {
|
|
979
|
-
const expected = normalizeProtocolV2Hex(bundle.payloadHash);
|
|
980
|
-
if (expected && header.payloadHash !== expected) return false;
|
|
981
|
-
}
|
|
982
|
-
if (bundle.headerHash) {
|
|
983
|
-
const expected = normalizeProtocolV2Hex(bundle.headerHash);
|
|
984
|
-
if (expected && header.headerHash !== expected) return false;
|
|
985
|
-
}
|
|
986
|
-
return true;
|
|
987
|
-
} catch (error) {
|
|
988
|
-
Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
|
|
989
|
-
return false;
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
|
|
993
851
|
private isProtocolV2BootloaderMode() {
|
|
994
852
|
if (typeof this.device.isBootloader === 'function') {
|
|
995
|
-
return this.device.isBootloader();
|
|
853
|
+
return !!this.device.isBootloader();
|
|
996
854
|
}
|
|
997
855
|
return (
|
|
998
856
|
this.device.features?.mode === 'bootloader' ||
|
|
@@ -1406,6 +1264,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1406
1264
|
PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT
|
|
1407
1265
|
);
|
|
1408
1266
|
|
|
1267
|
+
return this.getProtocolV2VersionResult(features);
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
private getProtocolV2VersionResult(features: Features) {
|
|
1409
1271
|
const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
|
|
1410
1272
|
const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
|
|
1411
1273
|
const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
|
|
@@ -450,6 +450,8 @@
|
|
|
450
450
|
"MessageType_DeviceInfo": 60601,
|
|
451
451
|
"MessageType_DeviceStatusGet": 60602,
|
|
452
452
|
"MessageType_DeviceStatus": 60603,
|
|
453
|
+
"MessageType_ResourceInventoryGet": 60604,
|
|
454
|
+
"MessageType_ResourceInventory": 60605,
|
|
453
455
|
"MessageType_FilesystemPermissionFix": 60800,
|
|
454
456
|
"MessageType_FilesystemPathInfo": 60801,
|
|
455
457
|
"MessageType_FilesystemPathInfoQuery": 60802,
|
|
@@ -11994,6 +11996,16 @@
|
|
|
11994
11996
|
"APP": 85
|
|
11995
11997
|
}
|
|
11996
11998
|
},
|
|
11999
|
+
"ResourceBundleType": {
|
|
12000
|
+
"values": {
|
|
12001
|
+
"IMAGES": 0,
|
|
12002
|
+
"ANIMATION": 1,
|
|
12003
|
+
"WALLPAPER": 2,
|
|
12004
|
+
"TRANSLATIONS": 3,
|
|
12005
|
+
"ROOBERT": 4,
|
|
12006
|
+
"NOTO": 5
|
|
12007
|
+
}
|
|
12008
|
+
},
|
|
11997
12009
|
"DeviceFirmwareImageInfo": {
|
|
11998
12010
|
"fields": {
|
|
11999
12011
|
"version": {
|
|
@@ -12191,6 +12203,37 @@
|
|
|
12191
12203
|
}
|
|
12192
12204
|
}
|
|
12193
12205
|
},
|
|
12206
|
+
"ResourceInventoryGet": {
|
|
12207
|
+
"fields": {}
|
|
12208
|
+
},
|
|
12209
|
+
"ResourceInventoryItem": {
|
|
12210
|
+
"fields": {
|
|
12211
|
+
"type": {
|
|
12212
|
+
"rule": "required",
|
|
12213
|
+
"type": "ResourceBundleType",
|
|
12214
|
+
"id": 1
|
|
12215
|
+
},
|
|
12216
|
+
"size": {
|
|
12217
|
+
"rule": "required",
|
|
12218
|
+
"type": "uint32",
|
|
12219
|
+
"id": 2
|
|
12220
|
+
},
|
|
12221
|
+
"header_hash": {
|
|
12222
|
+
"rule": "required",
|
|
12223
|
+
"type": "bytes",
|
|
12224
|
+
"id": 3
|
|
12225
|
+
}
|
|
12226
|
+
}
|
|
12227
|
+
},
|
|
12228
|
+
"ResourceInventory": {
|
|
12229
|
+
"fields": {
|
|
12230
|
+
"items": {
|
|
12231
|
+
"rule": "repeated",
|
|
12232
|
+
"type": "ResourceInventoryItem",
|
|
12233
|
+
"id": 1
|
|
12234
|
+
}
|
|
12235
|
+
}
|
|
12236
|
+
},
|
|
12194
12237
|
"DeviceSessionErrorCode": {
|
|
12195
12238
|
"values": {
|
|
12196
12239
|
"DeviceSessionError_None": 0,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from '../utils';
|
|
18
18
|
import { DeviceModelToTypes } from '../types';
|
|
19
19
|
import { findLatestRelease, getReleaseChangelog, getReleaseStatus } from '../utils/release';
|
|
20
|
+
import { parseProtocolV2Resources } from '../protocols/protocol-v2/resources';
|
|
20
21
|
|
|
21
22
|
import type {
|
|
22
23
|
AssetsMap,
|
|
@@ -397,10 +398,10 @@ export default class DataManager {
|
|
|
397
398
|
return enrichedData;
|
|
398
399
|
}
|
|
399
400
|
|
|
400
|
-
static async load(settings: ConnectSettings) {
|
|
401
|
+
static async load(settings: ConnectSettings): Promise<boolean> {
|
|
401
402
|
this.settings = settings;
|
|
402
403
|
if (!settings.fetchConfig) {
|
|
403
|
-
return;
|
|
404
|
+
return false;
|
|
404
405
|
}
|
|
405
406
|
|
|
406
407
|
const url = settings.preRelease
|
|
@@ -445,6 +446,9 @@ export default class DataManager {
|
|
|
445
446
|
|
|
446
447
|
// 3. Apply config if available
|
|
447
448
|
if (data) {
|
|
449
|
+
const pro2Resources = parseProtocolV2Resources(
|
|
450
|
+
(data.pro2 as { resources?: unknown } | undefined)?.resources
|
|
451
|
+
);
|
|
448
452
|
Log.log(`[DataConfig] Config loaded successfully via [${fetchMethod}]`);
|
|
449
453
|
this.deviceMap = {
|
|
450
454
|
[EDeviceType.Classic]: this.enrichFirmwareReleaseInfo(data.classic),
|
|
@@ -453,14 +457,18 @@ export default class DataManager {
|
|
|
453
457
|
[EDeviceType.Mini]: this.enrichFirmwareReleaseInfo(data.mini),
|
|
454
458
|
[EDeviceType.Touch]: this.enrichFirmwareReleaseInfo(data.touch),
|
|
455
459
|
[EDeviceType.Pro]: this.enrichFirmwareReleaseInfo(data.pro),
|
|
456
|
-
[EDeviceType.Pro2]:
|
|
460
|
+
[EDeviceType.Pro2]: {
|
|
461
|
+
...this.enrichFirmwareReleaseInfo(data.pro2),
|
|
462
|
+
...(pro2Resources ? { resources: pro2Resources } : undefined),
|
|
463
|
+
},
|
|
457
464
|
};
|
|
458
465
|
this.assets = {
|
|
459
466
|
bridge: data.bridge,
|
|
460
467
|
};
|
|
461
|
-
|
|
462
|
-
Log.warn('[DataConfig] All fetch methods failed, using built-in default config');
|
|
468
|
+
return true;
|
|
463
469
|
}
|
|
470
|
+
Log.warn('[DataConfig] All fetch methods failed, using built-in default config');
|
|
471
|
+
return false;
|
|
464
472
|
}
|
|
465
473
|
|
|
466
474
|
static updateEnv(newEnv: ConnectSettings['env']) {
|
|
@@ -478,10 +486,27 @@ export default class DataManager {
|
|
|
478
486
|
|
|
479
487
|
static async checkAndReloadData() {
|
|
480
488
|
if (getTimeStamp() - this.lastCheckTimestamp > 1000 * 60 * 60 * 3) {
|
|
481
|
-
await this.load(this.settings)
|
|
489
|
+
const loaded = await this.load(this.settings);
|
|
490
|
+
if (loaded) {
|
|
482
491
|
this.lastCheckTimestamp = getTimeStamp();
|
|
483
|
-
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/** Force a fresh remote config before an update is allowed to mutate the device. */
|
|
497
|
+
static async forceReloadData(): Promise<void> {
|
|
498
|
+
if (!this.settings) {
|
|
499
|
+
throw new Error('Remote config settings are not initialized');
|
|
500
|
+
}
|
|
501
|
+
const loaded = await this.load(this.settings);
|
|
502
|
+
if (!loaded) {
|
|
503
|
+
throw new Error('Unable to refresh the latest remote config');
|
|
484
504
|
}
|
|
505
|
+
this.lastCheckTimestamp = getTimeStamp();
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
static getProtocolV2Resources() {
|
|
509
|
+
return this.deviceMap[EDeviceType.Pro2]?.resources?.stable;
|
|
485
510
|
}
|
|
486
511
|
|
|
487
512
|
static getProtobufMessages(schema: ProtobufMessageSchema = 'v1CurrentSchema'): JSON {
|