@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
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
IProtocolV2Resource,
|
|
5
|
+
IProtocolV2ResourceType,
|
|
6
|
+
IProtocolV2Resources,
|
|
7
|
+
} from '../../types';
|
|
8
|
+
import type { DeviceCommands } from '../../device/DeviceCommands';
|
|
9
|
+
import type { ResourceInventory } from '@onekeyfe/hd-transport';
|
|
10
|
+
|
|
11
|
+
export const PROTOCOL_V2_RESOURCE_TYPES = [
|
|
12
|
+
'images',
|
|
13
|
+
'animation',
|
|
14
|
+
'wallpaper',
|
|
15
|
+
'translations',
|
|
16
|
+
'roobert',
|
|
17
|
+
'noto',
|
|
18
|
+
] as const satisfies readonly IProtocolV2ResourceType[];
|
|
19
|
+
|
|
20
|
+
export const PROTOCOL_V2_RESOURCE_DEVICE_PATHS: Readonly<Record<IProtocolV2ResourceType, string>> =
|
|
21
|
+
{
|
|
22
|
+
images: 'vol0:/bundles/images/images.okpkg',
|
|
23
|
+
animation: 'vol0:/bundles/images/animation.okpkg',
|
|
24
|
+
wallpaper: 'vol0:/bundles/images/wallpaper.okpkg',
|
|
25
|
+
translations: 'vol0:/bundles/translations/translations.okpkg',
|
|
26
|
+
roobert: 'vol0:/bundles/font/roobert.okpkg',
|
|
27
|
+
noto: 'vol0:/bundles/font/noto.okpkg',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const RESOURCE_TYPE_SET = new Set<string>(PROTOCOL_V2_RESOURCE_TYPES);
|
|
31
|
+
const SHA256_HEX_LENGTH = 64;
|
|
32
|
+
const SHA3_512_HEX_LENGTH = 128;
|
|
33
|
+
export const PROTOCOL_V2_RESOURCE_INVENTORY_TIMEOUT_MS = 5 * 1000;
|
|
34
|
+
|
|
35
|
+
const RESOURCE_TYPE_BY_DEVICE_VALUE: Readonly<Record<string, IProtocolV2ResourceType>> = {
|
|
36
|
+
'0': 'images',
|
|
37
|
+
IMAGES: 'images',
|
|
38
|
+
'1': 'animation',
|
|
39
|
+
ANIMATION: 'animation',
|
|
40
|
+
'2': 'wallpaper',
|
|
41
|
+
WALLPAPER: 'wallpaper',
|
|
42
|
+
'3': 'translations',
|
|
43
|
+
TRANSLATIONS: 'translations',
|
|
44
|
+
'4': 'roobert',
|
|
45
|
+
ROOBERT: 'roobert',
|
|
46
|
+
'5': 'noto',
|
|
47
|
+
NOTO: 'noto',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type ProtocolV2ResourceInventoryItem = {
|
|
51
|
+
type: IProtocolV2ResourceType;
|
|
52
|
+
size: number;
|
|
53
|
+
headerHash: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type ProtocolV2ResourceUpdateMode = 'application' | 'bootloader-recovery';
|
|
57
|
+
|
|
58
|
+
export type ProtocolV2ResourceUpdatePlan = {
|
|
59
|
+
status: 'valid' | 'outdated' | 'unknown';
|
|
60
|
+
resources: IProtocolV2Resource[];
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** Normalize the success-only device response into the SDK resource identity shape. */
|
|
64
|
+
export function parseProtocolV2ResourceInventory(
|
|
65
|
+
value: ResourceInventory | unknown
|
|
66
|
+
): ProtocolV2ResourceInventoryItem[] {
|
|
67
|
+
const items = (value as { items?: unknown })?.items;
|
|
68
|
+
if (!Array.isArray(items)) {
|
|
69
|
+
throw new Error('Invalid Pro2 resource inventory: items must be an array');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const inventory = items.map((item, index) => {
|
|
73
|
+
if (!item || typeof item !== 'object') {
|
|
74
|
+
throw new Error(`Invalid Pro2 resource inventory item at ${index}`);
|
|
75
|
+
}
|
|
76
|
+
const raw = item as { type?: unknown; size?: unknown; header_hash?: unknown };
|
|
77
|
+
const type = RESOURCE_TYPE_BY_DEVICE_VALUE[String(raw.type).toUpperCase()];
|
|
78
|
+
if (!type) {
|
|
79
|
+
throw new Error(`Invalid Pro2 resource inventory type at ${index}`);
|
|
80
|
+
}
|
|
81
|
+
if (!Number.isSafeInteger(raw.size) || Number(raw.size) <= 0) {
|
|
82
|
+
throw new Error(`Invalid Pro2 resource inventory size at ${index}`);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
type,
|
|
86
|
+
size: Number(raw.size),
|
|
87
|
+
headerHash: normalizeHex(raw.header_hash, SHA3_512_HEX_LENGTH, 'inventory headerHash'),
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (new Set(inventory.map(item => item.type)).size !== inventory.length) {
|
|
92
|
+
throw new Error('Invalid Pro2 resource inventory: duplicate resource type');
|
|
93
|
+
}
|
|
94
|
+
return PROTOCOL_V2_RESOURCE_TYPES.flatMap(type => {
|
|
95
|
+
const item = inventory.find(candidate => candidate.type === type);
|
|
96
|
+
return item ? [item] : [];
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function requestProtocolV2ResourceInventory({
|
|
101
|
+
commands,
|
|
102
|
+
timeoutMs = PROTOCOL_V2_RESOURCE_INVENTORY_TIMEOUT_MS,
|
|
103
|
+
}: {
|
|
104
|
+
commands: DeviceCommands;
|
|
105
|
+
timeoutMs?: number;
|
|
106
|
+
}): Promise<ProtocolV2ResourceInventoryItem[]> {
|
|
107
|
+
const { message } = await commands.typedCall(
|
|
108
|
+
'ResourceInventoryGet',
|
|
109
|
+
'ResourceInventory',
|
|
110
|
+
{},
|
|
111
|
+
{ timeoutMs }
|
|
112
|
+
);
|
|
113
|
+
return parseProtocolV2ResourceInventory(message);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function normalizeHex(value: unknown, expectedLength: number, field: string): string {
|
|
117
|
+
if (typeof value !== 'string') {
|
|
118
|
+
throw new Error(`Invalid Pro2 resource ${field}: expected a hexadecimal string`);
|
|
119
|
+
}
|
|
120
|
+
const normalized = value.replace(/^0x/i, '').toLowerCase();
|
|
121
|
+
if (normalized.length !== expectedLength || !/^[0-9a-f]+$/.test(normalized)) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Invalid Pro2 resource ${field}: expected ${expectedLength} hexadecimal characters`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return normalized;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function validateResource(value: unknown, index: number): IProtocolV2Resource {
|
|
130
|
+
if (!value || typeof value !== 'object') {
|
|
131
|
+
throw new Error(`Invalid Pro2 resource at stable[${index}]`);
|
|
132
|
+
}
|
|
133
|
+
const resource = value as Partial<IProtocolV2Resource>;
|
|
134
|
+
if (typeof resource.type !== 'string' || !RESOURCE_TYPE_SET.has(resource.type)) {
|
|
135
|
+
throw new Error(`Invalid Pro2 resource type at stable[${index}]`);
|
|
136
|
+
}
|
|
137
|
+
if (typeof resource.url !== 'string' || !resource.url.startsWith('https://')) {
|
|
138
|
+
throw new Error(`Invalid Pro2 resource url at stable[${index}]`);
|
|
139
|
+
}
|
|
140
|
+
if (!Number.isSafeInteger(resource.size) || Number(resource.size) <= 0) {
|
|
141
|
+
throw new Error(`Invalid Pro2 resource size at stable[${index}]`);
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
type: resource.type,
|
|
145
|
+
url: resource.url,
|
|
146
|
+
size: Number(resource.size),
|
|
147
|
+
fileHash: normalizeHex(resource.fileHash, SHA256_HEX_LENGTH, 'fileHash'),
|
|
148
|
+
headerHash: normalizeHex(resource.headerHash, SHA3_512_HEX_LENGTH, 'headerHash'),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Validate a complete Pro2 stable resource set from remote configuration. */
|
|
153
|
+
export function parseProtocolV2Resources(value: unknown): IProtocolV2Resources | undefined {
|
|
154
|
+
if (value === undefined) return undefined;
|
|
155
|
+
if (
|
|
156
|
+
!value ||
|
|
157
|
+
typeof value !== 'object' ||
|
|
158
|
+
!Array.isArray((value as { stable?: unknown }).stable)
|
|
159
|
+
) {
|
|
160
|
+
throw new Error('Invalid Pro2 resources config: stable must be an array');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const stable = (value as { stable: unknown[] }).stable.map(validateResource);
|
|
164
|
+
const types = new Set(stable.map(resource => resource.type));
|
|
165
|
+
if (stable.length !== PROTOCOL_V2_RESOURCE_TYPES.length || types.size !== stable.length) {
|
|
166
|
+
throw new Error('Invalid Pro2 resources config: stable must contain six unique resource types');
|
|
167
|
+
}
|
|
168
|
+
for (const type of PROTOCOL_V2_RESOURCE_TYPES) {
|
|
169
|
+
if (!types.has(type)) {
|
|
170
|
+
throw new Error(`Invalid Pro2 resources config: stable is missing ${type}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
stable: PROTOCOL_V2_RESOURCE_TYPES.map(type => {
|
|
175
|
+
const resource = stable.find(item => item.type === type);
|
|
176
|
+
if (!resource) {
|
|
177
|
+
throw new Error(`Invalid Pro2 resources config: stable is missing ${type}`);
|
|
178
|
+
}
|
|
179
|
+
return resource;
|
|
180
|
+
}),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Compare the application inventory or select the full set for bootloader recovery. */
|
|
185
|
+
export function buildProtocolV2ResourceUpdatePlan({
|
|
186
|
+
resources,
|
|
187
|
+
inventory,
|
|
188
|
+
mode,
|
|
189
|
+
forced = false,
|
|
190
|
+
}: {
|
|
191
|
+
resources: readonly IProtocolV2Resource[];
|
|
192
|
+
inventory?: readonly ProtocolV2ResourceInventoryItem[];
|
|
193
|
+
mode: ProtocolV2ResourceUpdateMode;
|
|
194
|
+
forced?: boolean;
|
|
195
|
+
}): ProtocolV2ResourceUpdatePlan {
|
|
196
|
+
if (mode === 'bootloader-recovery' || forced) {
|
|
197
|
+
return {
|
|
198
|
+
status: resources.length > 0 ? 'outdated' : 'valid',
|
|
199
|
+
resources: [...resources],
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (!inventory) {
|
|
203
|
+
return { status: 'unknown', resources: [] };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const inventoryByType = new Map(inventory.map(item => [item.type, item]));
|
|
207
|
+
const changedResources = resources.filter(resource => {
|
|
208
|
+
const current = inventoryByType.get(resource.type);
|
|
209
|
+
return (
|
|
210
|
+
!current ||
|
|
211
|
+
current.size !== resource.size ||
|
|
212
|
+
current.headerHash.toLowerCase() !== resource.headerHash.toLowerCase()
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
status: changedResources.length === 0 ? 'valid' : 'outdated',
|
|
218
|
+
resources: changedResources,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function bytesToHex(bytes: Uint8Array): string {
|
|
223
|
+
return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Verify the complete downloaded file before any device mutation. */
|
|
227
|
+
export function isProtocolV2ResourceFileValid(
|
|
228
|
+
binary: ArrayBuffer,
|
|
229
|
+
resource: Pick<IProtocolV2Resource, 'size' | 'fileHash'>
|
|
230
|
+
): boolean {
|
|
231
|
+
if (binary.byteLength !== resource.size) return false;
|
|
232
|
+
return bytesToHex(sha256(new Uint8Array(binary))) === resource.fileHash.toLowerCase();
|
|
233
|
+
}
|
|
@@ -59,6 +59,7 @@ export type AllFirmwareRelease = {
|
|
|
59
59
|
status?: ProtocolV2FirmwareReleaseStatus;
|
|
60
60
|
hasUpgrade?: boolean;
|
|
61
61
|
required?: boolean;
|
|
62
|
+
resourceStatus?: 'valid' | 'outdated' | 'unknown';
|
|
62
63
|
currentVersions?: DeviceStateVersions;
|
|
63
64
|
components?: ProtocolV2FirmwareComponentRelease[];
|
|
64
65
|
targetsToUpdate?: FirmwareUpdateV4Target[];
|
package/src/types/settings.ts
CHANGED
|
@@ -61,6 +61,30 @@ export type IProtocolV2FirmwareComponent = {
|
|
|
61
61
|
version?: IVersionArray;
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
+
export type IProtocolV2ResourceType =
|
|
65
|
+
| 'images'
|
|
66
|
+
| 'animation'
|
|
67
|
+
| 'wallpaper'
|
|
68
|
+
| 'translations'
|
|
69
|
+
| 'roobert'
|
|
70
|
+
| 'noto';
|
|
71
|
+
|
|
72
|
+
/** Pro2 RES package descriptor. Hashes identify content without a human-managed version. */
|
|
73
|
+
export type IProtocolV2Resource = {
|
|
74
|
+
type: IProtocolV2ResourceType;
|
|
75
|
+
url: string;
|
|
76
|
+
/** Complete okpkg file size in bytes. */
|
|
77
|
+
size: number;
|
|
78
|
+
/** SHA-256 of the complete okpkg file. */
|
|
79
|
+
fileHash: string;
|
|
80
|
+
/** SHA3-512 header_hash from the signed okpkg header. */
|
|
81
|
+
headerHash: string;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type IProtocolV2Resources = {
|
|
85
|
+
stable: IProtocolV2Resource[];
|
|
86
|
+
};
|
|
87
|
+
|
|
64
88
|
/** Pro2 RESC bundle okpkg descriptor for incremental FileWrite synchronization. */
|
|
65
89
|
export type IProtocolV2ResourceBundle = {
|
|
66
90
|
/** Bundle name, such as images, animation, translations, or fonts_roobert. */
|
|
@@ -136,6 +160,8 @@ export type DeviceTypeMap = {
|
|
|
136
160
|
'firmware-v8'?: IFirmwareReleaseInfo[];
|
|
137
161
|
'firmware-btc-v8'?: IFirmwareReleaseInfo[];
|
|
138
162
|
ble: IBLEFirmwareReleaseInfo[];
|
|
163
|
+
/** Independent Pro2 resource release configuration. */
|
|
164
|
+
resources?: IProtocolV2Resources;
|
|
139
165
|
};
|
|
140
166
|
};
|
|
141
167
|
|