@onekeyfe/hd-core 1.2.2-alpha.100 → 1.2.2-alpha.11
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__/AllNetworkGetAddressBase.tracing.test.ts +20 -4
- package/__tests__/DeviceCommands.test.ts +13 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +0 -2
- package/__tests__/check-firmware-release-protocol-v2.test.ts +0 -2
- package/__tests__/device-lifecycle-events.test.ts +86 -0
- package/__tests__/deviceUploadNft.test.ts +68 -0
- package/__tests__/evmSignTypedData.test.ts +42 -0
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +825 -12
- package/__tests__/logBlockEvent.test.ts +45 -122
- package/__tests__/open-wallet-session.test.ts +781 -69
- package/__tests__/pro2HostAssetPackage.test.ts +165 -0
- package/__tests__/protocol-v2-resources.test.ts +7 -4
- package/__tests__/protocol-v2-unlock-policy.test.ts +35 -1
- package/__tests__/protocol-v2.test.ts +925 -97
- package/__tests__/protocolV2FileWrite.test.ts +40 -0
- package/dist/api/FirmwareUpdateV4.d.ts +5 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +2 -2
- package/dist/api/helpers/protocolV2FileWrite.d.ts +1 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/core/RequestQueue.d.ts +2 -0
- package/dist/core/RequestQueue.d.ts.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +1 -1
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceConnector.d.ts +1 -1
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +3 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +21 -5
- package/dist/index.js +2511 -1538
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +2 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/settings.d.ts +2 -2
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2HostAssetPackage.d.ts +8 -0
- package/dist/utils/pro2HostAssetPackage.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +474 -83
- package/src/api/OpenWalletSession.ts +39 -12
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +5 -2
- package/src/api/evm/EVMSignTypedData.ts +1 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +9 -3
- package/src/api/helpers/protocolV2FileWrite.ts +11 -5
- package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
- package/src/api/protocol-v2/DeviceUploadNft.ts +31 -8
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +33 -8
- package/src/api/protocol-v2/helpers.ts +8 -0
- package/src/core/RequestQueue.ts +16 -1
- package/src/core/index.ts +164 -26
- package/src/data/messages/messages-protocol-v2.json +1589 -1375
- package/src/device/Device.ts +3 -1
- package/src/device/DeviceConnector.ts +7 -3
- package/src/events/logBlockEvent.ts +6 -75
- package/src/events/ui-request.ts +3 -0
- package/src/protocols/protocol-v2/resources.ts +9 -1
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +8 -1
- package/src/protocols/protocol-v2/walletSession.ts +223 -56
- package/src/types/settings.ts +4 -2
- package/src/utils/deviceFeaturesUtils.ts +4 -0
- package/src/utils/pro2HostAssetPackage.ts +400 -0
package/src/types/settings.ts
CHANGED
|
@@ -77,7 +77,8 @@ export type IProtocolV2Resources = {
|
|
|
77
77
|
/** STM32 firmware config */
|
|
78
78
|
export type IFirmwareReleaseInfo = {
|
|
79
79
|
required: boolean;
|
|
80
|
-
|
|
80
|
+
/** Legacy release artifact URL. Protocol V2 releases use component URLs. */
|
|
81
|
+
url?: string;
|
|
81
82
|
/**
|
|
82
83
|
* Firmware type (bitcoinonly or universal)
|
|
83
84
|
* This field is not present in the remote config, but will be inferred from the firmware field name
|
|
@@ -106,7 +107,8 @@ export type IFirmwareReleaseInfo = {
|
|
|
106
107
|
bootloaderChangelog?: {
|
|
107
108
|
[k in ILocale]: string;
|
|
108
109
|
};
|
|
109
|
-
|
|
110
|
+
/** Legacy release artifact SHA-256. Protocol V2 releases use component fingerprints. */
|
|
111
|
+
fingerprint?: string;
|
|
110
112
|
expectedSize?: number;
|
|
111
113
|
version: IVersionArray;
|
|
112
114
|
changelog: {
|
|
@@ -110,6 +110,7 @@ export const getPassphraseStateWithRefreshDeviceInfo = async (
|
|
|
110
110
|
initSession?: boolean;
|
|
111
111
|
deriveCardano?: boolean;
|
|
112
112
|
rejectAttachPinForMainWallet?: boolean;
|
|
113
|
+
mainPinSelected?: boolean;
|
|
113
114
|
}
|
|
114
115
|
) => {
|
|
115
116
|
if (device.isProtocolV2()) {
|
|
@@ -127,6 +128,7 @@ export const getPassphraseStateWithRefreshDeviceInfo = async (
|
|
|
127
128
|
onlyMainPin: options?.onlyMainPin,
|
|
128
129
|
deriveCardano: options?.deriveCardano,
|
|
129
130
|
rejectAttachPinForMainWallet: options?.rejectAttachPinForMainWallet,
|
|
131
|
+
mainPinSelected: options?.mainPinSelected,
|
|
130
132
|
});
|
|
131
133
|
}
|
|
132
134
|
|
|
@@ -194,6 +196,7 @@ export const getPassphraseState = async (
|
|
|
194
196
|
initSession?: boolean;
|
|
195
197
|
deriveCardano?: boolean;
|
|
196
198
|
rejectAttachPinForMainWallet?: boolean;
|
|
199
|
+
mainPinSelected?: boolean;
|
|
197
200
|
}
|
|
198
201
|
): Promise<{
|
|
199
202
|
passphraseState: string | undefined;
|
|
@@ -216,6 +219,7 @@ export const getPassphraseState = async (
|
|
|
216
219
|
onlyMainPin: options?.onlyMainPin,
|
|
217
220
|
deriveCardano: options?.deriveCardano,
|
|
218
221
|
rejectAttachPinForMainWallet: options?.rejectAttachPinForMainWallet,
|
|
222
|
+
mainPinSelected: options?.mainPinSelected,
|
|
219
223
|
});
|
|
220
224
|
}
|
|
221
225
|
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { sha3_512 } from '@noble/hashes/sha3';
|
|
2
|
+
import semver from 'semver';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds the unsigned host-asset package consumed by Pro2 firmware:
|
|
6
|
+
*
|
|
7
|
+
* OKPP RESOURCE container
|
|
8
|
+
* └── OKAR archive
|
|
9
|
+
* └── independently compressed raw LZ4 blocks
|
|
10
|
+
*
|
|
11
|
+
* OKPP and OKAR are OneKey formats. Their constants mirror firmware-pro2's
|
|
12
|
+
* payload_package headers; the LZ4 bytes inside each block follow the standard
|
|
13
|
+
* raw block format and deliberately do not use an LZ4 frame or size prefix.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/* eslint-disable no-bitwise */
|
|
17
|
+
|
|
18
|
+
export const PRO2_HOST_ASSET_PACKAGE_MIN_VERSION = '1.0.1';
|
|
19
|
+
|
|
20
|
+
// OKPP container layout. The fixed header has seven empty signature slots even
|
|
21
|
+
// for a host-generated unsigned package.
|
|
22
|
+
const CONTAINER_HEADER_SIZE = 0x5f90;
|
|
23
|
+
const CONTAINER_HEADER_HASH_INPUT_LENGTH = 0x240;
|
|
24
|
+
const CONTAINER_HASH_SECTION_OFFSET = 0x200;
|
|
25
|
+
const CONTAINER_SIGNATURE_ALGORITHM_OFFSET = 0x408;
|
|
26
|
+
const CONTAINER_HEADER_MAGIC = 0x50504b4f;
|
|
27
|
+
const CONTAINER_HEADER_VERSION = 1;
|
|
28
|
+
const CONTAINER_RESOURCE_TYPE_MAGIC = 0x43534552;
|
|
29
|
+
const CONTAINER_ED25519_SIGNATURE_ALGORITHM = 0x71717171;
|
|
30
|
+
const HOST_ASSET_PACKAGE_MAX_SIZE = 4 * 1024 * 1024;
|
|
31
|
+
|
|
32
|
+
// OKAR archive layout.
|
|
33
|
+
const ARCHIVE_MAGIC = 0x52414b4f;
|
|
34
|
+
const ARCHIVE_VERSION = 1;
|
|
35
|
+
const ARCHIVE_HEADER_SIZE = 42;
|
|
36
|
+
const ARCHIVE_ENTRY_SIZE = 296;
|
|
37
|
+
const ARCHIVE_ENTRY_NAME_MAX_LENGTH = 255;
|
|
38
|
+
const ARCHIVE_COMPRESS_LZ4_BLOCKED = 1;
|
|
39
|
+
const ARCHIVE_ALIGNMENT = 4;
|
|
40
|
+
const LZ4_PREFERRED_BLOCK_SIZE_LOG2 = 14;
|
|
41
|
+
const LZ4_FALLBACK_BLOCK_SIZE_LOG2 = 13;
|
|
42
|
+
const LZ4_COMPRESSED_BLOCK_SIZE_MAX = 1 << 14;
|
|
43
|
+
|
|
44
|
+
export type Pro2HostAssetPackageEntry = {
|
|
45
|
+
name: string;
|
|
46
|
+
data: Uint8Array;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type EncodedArchiveEntry = Pro2HostAssetPackageEntry & {
|
|
50
|
+
nameBytes: Uint8Array;
|
|
51
|
+
compressed: Uint8Array;
|
|
52
|
+
offset: number;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export function supportsPro2HostAssetPackage(firmwareVersion: string | undefined): boolean {
|
|
56
|
+
return Boolean(
|
|
57
|
+
firmwareVersion &&
|
|
58
|
+
semver.valid(firmwareVersion) &&
|
|
59
|
+
semver.gte(firmwareVersion, PRO2_HOST_ASSET_PACKAGE_MIN_VERSION)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function concatBytes(parts: Uint8Array[]): Uint8Array {
|
|
64
|
+
const output = new Uint8Array(parts.reduce((length, part) => length + part.byteLength, 0));
|
|
65
|
+
let offset = 0;
|
|
66
|
+
for (const part of parts) {
|
|
67
|
+
output.set(part, offset);
|
|
68
|
+
offset += part.byteLength;
|
|
69
|
+
}
|
|
70
|
+
return output;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Raw LZ4 block encoder
|
|
74
|
+
// ---------------------
|
|
75
|
+
//
|
|
76
|
+
// This encoder is adapted from lz4-lite 1.1.2 and intentionally kept local so
|
|
77
|
+
// every SDK runtime uses the same dependency-free implementation. Keep this
|
|
78
|
+
// section isolated from the OneKey package writer below. Firmware requires raw
|
|
79
|
+
// blocks here; replacing it with an LZ4 frame encoder is not compatible.
|
|
80
|
+
//
|
|
81
|
+
// MIT License
|
|
82
|
+
// Copyright (c) 2026 Alexander Vukov
|
|
83
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
84
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
85
|
+
// in the Software without restriction, including without limitation the rights
|
|
86
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
87
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
88
|
+
// furnished to do so, subject to the following conditions:
|
|
89
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
90
|
+
// copies or substantial portions of the Software.
|
|
91
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
92
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
93
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
94
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
95
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
96
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
97
|
+
// SOFTWARE.
|
|
98
|
+
|
|
99
|
+
const LZ4_MIN_MATCH = 4;
|
|
100
|
+
const LZ4_LAST_LITERALS = 5;
|
|
101
|
+
const LZ4_MATCH_FIND_LIMIT = 12;
|
|
102
|
+
const LZ4_MAX_OFFSET = 0xffff;
|
|
103
|
+
const LZ4_LENGTH_MASK = 15;
|
|
104
|
+
const LZ4_HASH_LOG = 16;
|
|
105
|
+
const LZ4_HASH_MULTIPLIER = 2654435761;
|
|
106
|
+
const LZ4_MAX_SEARCH_DEPTH = 64;
|
|
107
|
+
|
|
108
|
+
function writeExtendedLength(output: Uint8Array, offset: number, length: number): number {
|
|
109
|
+
let remaining = length;
|
|
110
|
+
let nextOffset = offset;
|
|
111
|
+
while (remaining >= 255) {
|
|
112
|
+
output[nextOffset] = 255;
|
|
113
|
+
nextOffset += 1;
|
|
114
|
+
remaining -= 255;
|
|
115
|
+
}
|
|
116
|
+
output[nextOffset] = remaining;
|
|
117
|
+
return nextOffset + 1;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function copyBytes(
|
|
121
|
+
output: Uint8Array,
|
|
122
|
+
outputOffset: number,
|
|
123
|
+
input: Uint8Array,
|
|
124
|
+
inputOffset: number,
|
|
125
|
+
length: number
|
|
126
|
+
): number {
|
|
127
|
+
output.set(input.subarray(inputOffset, inputOffset + length), outputOffset);
|
|
128
|
+
return outputOffset + length;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function emitSequence(
|
|
132
|
+
output: Uint8Array,
|
|
133
|
+
outputOffset: number,
|
|
134
|
+
input: Uint8Array,
|
|
135
|
+
anchor: number,
|
|
136
|
+
literalLength: number,
|
|
137
|
+
matchOffset: number,
|
|
138
|
+
matchLengthCode: number
|
|
139
|
+
): number {
|
|
140
|
+
const tokenOffset = outputOffset;
|
|
141
|
+
let nextOffset = outputOffset + 1;
|
|
142
|
+
let token = 0;
|
|
143
|
+
|
|
144
|
+
if (literalLength >= LZ4_LENGTH_MASK) {
|
|
145
|
+
token = LZ4_LENGTH_MASK << 4;
|
|
146
|
+
nextOffset = writeExtendedLength(output, nextOffset, literalLength - LZ4_LENGTH_MASK);
|
|
147
|
+
} else {
|
|
148
|
+
token = literalLength << 4;
|
|
149
|
+
}
|
|
150
|
+
nextOffset = copyBytes(output, nextOffset, input, anchor, literalLength);
|
|
151
|
+
output[nextOffset] = matchOffset & 0xff;
|
|
152
|
+
output[nextOffset + 1] = (matchOffset >>> 8) & 0xff;
|
|
153
|
+
nextOffset += 2;
|
|
154
|
+
|
|
155
|
+
if (matchLengthCode >= LZ4_LENGTH_MASK) {
|
|
156
|
+
token |= LZ4_LENGTH_MASK;
|
|
157
|
+
nextOffset = writeExtendedLength(output, nextOffset, matchLengthCode - LZ4_LENGTH_MASK);
|
|
158
|
+
} else {
|
|
159
|
+
token |= matchLengthCode;
|
|
160
|
+
}
|
|
161
|
+
output[tokenOffset] = token;
|
|
162
|
+
return nextOffset;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function emitLastLiterals(
|
|
166
|
+
output: Uint8Array,
|
|
167
|
+
outputOffset: number,
|
|
168
|
+
input: Uint8Array,
|
|
169
|
+
anchor: number,
|
|
170
|
+
literalLength: number
|
|
171
|
+
): number {
|
|
172
|
+
const tokenOffset = outputOffset;
|
|
173
|
+
let nextOffset = outputOffset + 1;
|
|
174
|
+
if (literalLength >= LZ4_LENGTH_MASK) {
|
|
175
|
+
output[tokenOffset] = LZ4_LENGTH_MASK << 4;
|
|
176
|
+
nextOffset = writeExtendedLength(output, nextOffset, literalLength - LZ4_LENGTH_MASK);
|
|
177
|
+
} else {
|
|
178
|
+
output[tokenOffset] = literalLength << 4;
|
|
179
|
+
}
|
|
180
|
+
return copyBytes(output, nextOffset, input, anchor, literalLength);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function compressRawLz4Block(
|
|
184
|
+
input: Uint8Array,
|
|
185
|
+
hashTable: Uint32Array,
|
|
186
|
+
matchChain: Int32Array
|
|
187
|
+
): Uint8Array {
|
|
188
|
+
const output = new Uint8Array(input.byteLength + Math.floor(input.byteLength / 255) + 16);
|
|
189
|
+
const inputView = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
|
190
|
+
const matchFindLimit = input.byteLength - LZ4_MATCH_FIND_LIMIT;
|
|
191
|
+
const matchExtendLimit = input.byteLength - LZ4_LAST_LITERALS;
|
|
192
|
+
let anchor = 0;
|
|
193
|
+
let inputOffset = 0;
|
|
194
|
+
let outputOffset = 0;
|
|
195
|
+
|
|
196
|
+
hashTable.fill(0);
|
|
197
|
+
while (inputOffset < matchFindLimit) {
|
|
198
|
+
const sequence = inputView.getUint32(inputOffset, true);
|
|
199
|
+
const hash = Math.imul(sequence, LZ4_HASH_MULTIPLIER) >>> (32 - LZ4_HASH_LOG);
|
|
200
|
+
let candidate = hashTable[hash] - 1;
|
|
201
|
+
matchChain[inputOffset] = candidate;
|
|
202
|
+
hashTable[hash] = inputOffset + 1;
|
|
203
|
+
|
|
204
|
+
let bestCandidate = -1;
|
|
205
|
+
let bestMatchEnd = inputOffset;
|
|
206
|
+
let searchDepth = 0;
|
|
207
|
+
while (
|
|
208
|
+
candidate >= 0 &&
|
|
209
|
+
inputOffset - candidate <= LZ4_MAX_OFFSET &&
|
|
210
|
+
searchDepth < LZ4_MAX_SEARCH_DEPTH
|
|
211
|
+
) {
|
|
212
|
+
if (inputView.getUint32(candidate, true) === sequence) {
|
|
213
|
+
let matchEnd = inputOffset + LZ4_MIN_MATCH;
|
|
214
|
+
let reference = candidate + LZ4_MIN_MATCH;
|
|
215
|
+
while (matchEnd < matchExtendLimit && input[matchEnd] === input[reference]) {
|
|
216
|
+
matchEnd += 1;
|
|
217
|
+
reference += 1;
|
|
218
|
+
}
|
|
219
|
+
if (matchEnd > bestMatchEnd) {
|
|
220
|
+
bestCandidate = candidate;
|
|
221
|
+
bestMatchEnd = matchEnd;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
candidate = matchChain[candidate];
|
|
225
|
+
searchDepth += 1;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (bestCandidate < 0) {
|
|
229
|
+
inputOffset += 1;
|
|
230
|
+
} else {
|
|
231
|
+
const matchStart = inputOffset;
|
|
232
|
+
outputOffset = emitSequence(
|
|
233
|
+
output,
|
|
234
|
+
outputOffset,
|
|
235
|
+
input,
|
|
236
|
+
anchor,
|
|
237
|
+
inputOffset - anchor,
|
|
238
|
+
inputOffset - bestCandidate,
|
|
239
|
+
bestMatchEnd - inputOffset - LZ4_MIN_MATCH
|
|
240
|
+
);
|
|
241
|
+
inputOffset = bestMatchEnd;
|
|
242
|
+
anchor = inputOffset;
|
|
243
|
+
|
|
244
|
+
for (
|
|
245
|
+
let skippedOffset = matchStart + 1;
|
|
246
|
+
skippedOffset < inputOffset && skippedOffset < matchFindLimit;
|
|
247
|
+
skippedOffset += 1
|
|
248
|
+
) {
|
|
249
|
+
const skippedSequence = inputView.getUint32(skippedOffset, true);
|
|
250
|
+
const skippedHash = Math.imul(skippedSequence, LZ4_HASH_MULTIPLIER) >>> (32 - LZ4_HASH_LOG);
|
|
251
|
+
matchChain[skippedOffset] = hashTable[skippedHash] - 1;
|
|
252
|
+
hashTable[skippedHash] = skippedOffset + 1;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
outputOffset = emitLastLiterals(output, outputOffset, input, anchor, input.byteLength - anchor);
|
|
258
|
+
return output.slice(0, outputOffset);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// OneKey LZ4-blocked wrapper
|
|
262
|
+
// --------------------------
|
|
263
|
+
// The archive stores an 8-byte descriptor, one compressed-size value per
|
|
264
|
+
// block, then the concatenated raw blocks. Blocks are independent so firmware
|
|
265
|
+
// can validate and decompress them with bounded memory.
|
|
266
|
+
function encodeLz4BlockedWithBlockSize(
|
|
267
|
+
data: Uint8Array,
|
|
268
|
+
blockSizeLog2: number
|
|
269
|
+
): Uint8Array | undefined {
|
|
270
|
+
const blockSize = 1 << blockSizeLog2;
|
|
271
|
+
const blockCount = Math.ceil(data.byteLength / blockSize);
|
|
272
|
+
const hashTable = new Uint32Array(1 << LZ4_HASH_LOG);
|
|
273
|
+
const matchChain = new Int32Array(blockSize);
|
|
274
|
+
const blocks: Uint8Array[] = [];
|
|
275
|
+
const header = new Uint8Array(8 + blockCount * 4);
|
|
276
|
+
const headerView = new DataView(header.buffer);
|
|
277
|
+
headerView.setUint16(0, blockCount, true);
|
|
278
|
+
headerView.setUint16(2, blockSizeLog2, true);
|
|
279
|
+
|
|
280
|
+
for (let index = 0; index < blockCount; index += 1) {
|
|
281
|
+
const block = compressRawLz4Block(
|
|
282
|
+
data.subarray(index * blockSize, Math.min((index + 1) * blockSize, data.byteLength)),
|
|
283
|
+
hashTable,
|
|
284
|
+
matchChain
|
|
285
|
+
);
|
|
286
|
+
if (block.byteLength > LZ4_COMPRESSED_BLOCK_SIZE_MAX) return undefined;
|
|
287
|
+
headerView.setUint32(8 + index * 4, block.byteLength, true);
|
|
288
|
+
blocks.push(block);
|
|
289
|
+
}
|
|
290
|
+
return concatBytes([header, ...blocks]);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function encodeLz4Blocked(data: Uint8Array): Uint8Array {
|
|
294
|
+
const preferred = encodeLz4BlockedWithBlockSize(data, LZ4_PREFERRED_BLOCK_SIZE_LOG2);
|
|
295
|
+
if (preferred) return preferred;
|
|
296
|
+
|
|
297
|
+
const fallback = encodeLz4BlockedWithBlockSize(data, LZ4_FALLBACK_BLOCK_SIZE_LOG2);
|
|
298
|
+
if (!fallback) {
|
|
299
|
+
throw new Error('Pro2 host asset package LZ4 block exceeds the firmware buffer limit.');
|
|
300
|
+
}
|
|
301
|
+
return fallback;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// OKAR integrity helpers
|
|
305
|
+
// ----------------------
|
|
306
|
+
const CRC32_TABLE = (() => {
|
|
307
|
+
const table = new Uint32Array(256);
|
|
308
|
+
for (let index = 0; index < table.length; index += 1) {
|
|
309
|
+
let value = index;
|
|
310
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
311
|
+
value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
|
|
312
|
+
}
|
|
313
|
+
table[index] = value >>> 0;
|
|
314
|
+
}
|
|
315
|
+
return table;
|
|
316
|
+
})();
|
|
317
|
+
|
|
318
|
+
function crc32(data: Uint8Array): number {
|
|
319
|
+
let value = 0xffffffff;
|
|
320
|
+
for (const byte of data) {
|
|
321
|
+
value = CRC32_TABLE[(value ^ byte) & 0xff] ^ (value >>> 8);
|
|
322
|
+
}
|
|
323
|
+
return (value ^ 0xffffffff) >>> 0;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function align(value: number): number {
|
|
327
|
+
return (value + ARCHIVE_ALIGNMENT - 1) & ~(ARCHIVE_ALIGNMENT - 1);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// OKAR archive writer
|
|
331
|
+
// -------------------
|
|
332
|
+
function buildArchive(entries: Pro2HostAssetPackageEntry[]): Uint8Array {
|
|
333
|
+
const textEncoder = new TextEncoder();
|
|
334
|
+
let dataOffset = ARCHIVE_HEADER_SIZE + entries.length * ARCHIVE_ENTRY_SIZE;
|
|
335
|
+
const encodedEntries: EncodedArchiveEntry[] = entries.map(entry => {
|
|
336
|
+
const nameBytes = textEncoder.encode(entry.name);
|
|
337
|
+
if (!entry.name || nameBytes.byteLength > ARCHIVE_ENTRY_NAME_MAX_LENGTH) {
|
|
338
|
+
throw new Error('Pro2 host asset package entry names must contain 1 to 255 UTF-8 bytes.');
|
|
339
|
+
}
|
|
340
|
+
if (!(entry.data instanceof Uint8Array) || entry.data.byteLength === 0) {
|
|
341
|
+
throw new Error(`Pro2 host asset package entry [${entry.name}] must not be empty.`);
|
|
342
|
+
}
|
|
343
|
+
const compressed = encodeLz4Blocked(entry.data);
|
|
344
|
+
const offset = align(dataOffset);
|
|
345
|
+
dataOffset = offset + compressed.byteLength;
|
|
346
|
+
return { ...entry, nameBytes, compressed, offset };
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
const archive = new Uint8Array(dataOffset);
|
|
350
|
+
const view = new DataView(archive.buffer);
|
|
351
|
+
view.setUint32(0, ARCHIVE_MAGIC, true);
|
|
352
|
+
view.setUint32(4, ARCHIVE_VERSION, true);
|
|
353
|
+
view.setUint16(8, encodedEntries.length, true);
|
|
354
|
+
|
|
355
|
+
encodedEntries.forEach((entry, index) => {
|
|
356
|
+
const recordOffset = ARCHIVE_HEADER_SIZE + index * ARCHIVE_ENTRY_SIZE;
|
|
357
|
+
archive[recordOffset] = entry.nameBytes.byteLength;
|
|
358
|
+
archive.set(entry.nameBytes, recordOffset + 1);
|
|
359
|
+
view.setUint32(recordOffset + 0x100, entry.offset, true);
|
|
360
|
+
view.setUint32(recordOffset + 0x104, entry.data.byteLength, true);
|
|
361
|
+
view.setUint32(recordOffset + 0x108, entry.compressed.byteLength, true);
|
|
362
|
+
view.setUint32(recordOffset + 0x10c, crc32(entry.data), true);
|
|
363
|
+
view.setUint32(recordOffset + 0x110, crc32(entry.compressed), true);
|
|
364
|
+
archive[recordOffset + 0x114] = ARCHIVE_COMPRESS_LZ4_BLOCKED;
|
|
365
|
+
archive.set(entry.compressed, entry.offset);
|
|
366
|
+
});
|
|
367
|
+
return archive;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// OKPP container writer
|
|
371
|
+
// ---------------------
|
|
372
|
+
export function buildPro2HostAssetPackage(entries: Pro2HostAssetPackageEntry[]): Uint8Array {
|
|
373
|
+
if (entries.length === 0 || new Set(entries.map(entry => entry.name)).size !== entries.length) {
|
|
374
|
+
throw new Error('Pro2 host asset package entries must have unique, non-empty names.');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const payload = buildArchive(entries);
|
|
378
|
+
const header = new Uint8Array(CONTAINER_HEADER_SIZE);
|
|
379
|
+
const view = new DataView(header.buffer);
|
|
380
|
+
view.setUint32(0, CONTAINER_HEADER_MAGIC, true);
|
|
381
|
+
view.setUint32(4, CONTAINER_HEADER_VERSION, true);
|
|
382
|
+
view.setUint32(8, CONTAINER_RESOURCE_TYPE_MAGIC, true);
|
|
383
|
+
view.setUint32(0x0c, CONTAINER_HEADER_SIZE, true);
|
|
384
|
+
view.setUint32(0x10, 1, true);
|
|
385
|
+
view.setUint32(0x14, payload.byteLength, true);
|
|
386
|
+
|
|
387
|
+
// Host asset packages are unsigned, but firmware still requires a valid
|
|
388
|
+
// payload hash, header hash, and the Ed25519 algorithm discriminator. The
|
|
389
|
+
// zero-initialized header intentionally leaves sig_used_count and every
|
|
390
|
+
// signature slot empty.
|
|
391
|
+
header.set(sha3_512(payload), CONTAINER_HASH_SECTION_OFFSET);
|
|
392
|
+
header.set(sha3_512(header.subarray(0, CONTAINER_HEADER_HASH_INPUT_LENGTH)), 0x240);
|
|
393
|
+
view.setUint32(CONTAINER_SIGNATURE_ALGORITHM_OFFSET, CONTAINER_ED25519_SIGNATURE_ALGORITHM, true);
|
|
394
|
+
|
|
395
|
+
const packageData = concatBytes([header, payload]);
|
|
396
|
+
if (packageData.byteLength > HOST_ASSET_PACKAGE_MAX_SIZE) {
|
|
397
|
+
throw new Error('Pro2 host asset package exceeds the firmware 4 MiB limit.');
|
|
398
|
+
}
|
|
399
|
+
return packageData;
|
|
400
|
+
}
|