diff --git a/lib/app-image/parser.ts b/lib/app-image/parser.ts index 00a0b42..a401740 100644 --- a/lib/app-image/parser.ts +++ b/lib/app-image/parser.ts @@ -15,13 +15,13 @@ import { */ export function parseAppImage(data: Uint8Array): AppImageInfo { if (data.length < IMAGE_HEADER_SIZE + EXTENDED_HEADER_SIZE) { - throw new Error(`数据太短: ${data.length} 字节 (最少需要 ${IMAGE_HEADER_SIZE + EXTENDED_HEADER_SIZE} 字节)`); + throw new Error(`Image too short: ${data.length}B`); } // ── Image Header (8 bytes: magic + segments + spi_mode + spi_speed_size + entry_addr) ── const magic = readU8(data, 0); if (magic !== IMAGE_MAGIC) { - throw new Error(`无效的魔数: 0x${magic.toString(16)} (应为 0xE9)`); + throw new Error(`Invalid image magic: 0x${magic.toString(16)}`); } const segmentCount = readU8(data, 1); diff --git a/lib/nvs/nvs-binary-parser.ts b/lib/nvs/nvs-binary-parser.ts index 2ed9030..ec0af06 100644 --- a/lib/nvs/nvs-binary-parser.ts +++ b/lib/nvs/nvs-binary-parser.ts @@ -66,10 +66,10 @@ interface ParsedPage { */ export function parseBinary(data: Uint8Array): NvsPartition { if (data.length % PAGE_SIZE !== 0) { - throw new Error(`二进制数据大小 (${data.length}) 不是页大小 (${PAGE_SIZE}) 的倍数`); + throw new Error(`Invalid NVS size: ${data.length}B`); } if (data.length === 0) { - throw new Error('二进制数据为空'); + throw new Error('NVS data is empty'); } const pageCount = data.length / PAGE_SIZE; diff --git a/lib/nvs/nvs-binary-serializer.ts b/lib/nvs/nvs-binary-serializer.ts index 02377f6..8d48ead 100644 --- a/lib/nvs/nvs-binary-serializer.ts +++ b/lib/nvs/nvs-binary-serializer.ts @@ -62,10 +62,10 @@ interface PlannedEntry { */ export function serializeBinary(partition: NvsPartition, targetSize: number): Uint8Array { if (targetSize % PAGE_SIZE !== 0) { - throw new Error(`目标大小 (${targetSize}) 不是页大小 (${PAGE_SIZE}) 的倍数`); + throw new Error(`Target size (${targetSize}) is not a multiple of page size (${PAGE_SIZE})`); } if (targetSize < MIN_PARTITION_SIZE) { - throw new Error(`目标大小 (${targetSize}) 小于最小分区大小 (${MIN_PARTITION_SIZE})`); + throw new Error(`Target size (${targetSize}) is less than minimum partition size (${MIN_PARTITION_SIZE})`); } // Allocate buffer filled with 0xFF (erased flash state) @@ -328,8 +328,8 @@ export function serializeBinary(partition: NvsPartition, targetSize: number): Ui if (plannedIdx < planned.length) { throw new Error( - `分区空间不足: 还有 ${planned.length - plannedIdx} 个条目无法写入。` + - `请增大分区大小。` + `Partition space exhausted: ${planned.length - plannedIdx} entries could not be written. ` + + `Increase partition size.` ); } diff --git a/lib/nvs/nvs-csv-parser.ts b/lib/nvs/nvs-csv-parser.ts index 29ce262..8642ba4 100644 --- a/lib/nvs/nvs-csv-parser.ts +++ b/lib/nvs/nvs-csv-parser.ts @@ -40,17 +40,17 @@ function parseIntValue(str: string): number { let val: number; if (str.startsWith('0x') || str.startsWith('0X')) { if (!/^-?0[xX][0-9a-fA-F]+$/.test(str)) { - throw new Error(`无效的整数值: "${str}"`); + throw new Error(`Invalid integer value: "${str}"`); } val = parseInt(str, 16); } else { if (!/^-?\d+$/.test(str)) { - throw new Error(`无效的整数值: "${str}"`); + throw new Error(`Invalid integer value: "${str}"`); } val = parseInt(str, 10); } if (Number.isNaN(val)) { - throw new Error(`无效的整数值: "${str}"`); + throw new Error(`Invalid integer value: "${str}"`); } return val; } @@ -62,14 +62,14 @@ function parseBigIntValue(str: string): bigint { // Handle negative hex explicitly. if (str.startsWith('-0x') || str.startsWith('-0X')) { if (!/^-0[xX][0-9a-fA-F]+$/.test(str)) { - throw new Error(`无效的整数值: "${str}"`); + throw new Error(`Invalid integer value: "${str}"`); } return -BigInt(str.slice(1)); } try { return BigInt(str); } catch { - throw new Error(`无效的整数值: "${str}"`); + throw new Error(`Invalid integer value: "${str}"`); } } @@ -169,20 +169,20 @@ export function parseCsv(text: string): NvsPartition { } if (!currentNamespace) { - throw new Error(`行 ${i + 1}: 数据条目 "${key}" 出现在任何命名空间之前`); + throw new Error(`Line ${i + 1}: data entry "${key}" appears before any namespace`); } if (type !== 'data' && type !== 'file') { - throw new Error(`行 ${i + 1}: 未知类型 "${type}"`); + throw new Error(`Line ${i + 1}: unknown type "${type}"`); } if (!encoding) { - throw new Error(`行 ${i + 1}: 键 "${key}" 缺少编码类型`); + throw new Error(`Line ${i + 1}: key "${key}" missing encoding`); } const nvsType = ENCODING_TO_TYPE[encoding as NvsEncoding]; if (nvsType === undefined) { - throw new Error(`行 ${i + 1}: 未知编码 "${encoding}"`); + throw new Error(`Line ${i + 1}: unknown encoding "${encoding}"`); } let parsedValue: number | bigint | string | Uint8Array; diff --git a/lib/nvs/nvs-partition.ts b/lib/nvs/nvs-partition.ts index 51a578a..5da696f 100644 --- a/lib/nvs/nvs-partition.ts +++ b/lib/nvs/nvs-partition.ts @@ -182,27 +182,27 @@ export function validatePartition(partition: NvsPartition): string[] { const errors: string[] = []; if (partition.namespaces.length > MAX_NAMESPACES) { - errors.push(`命名空间数量超过上限 ${MAX_NAMESPACES}`); + errors.push(`Namespace count exceeds limit ${MAX_NAMESPACES}`); } for (const ns of partition.namespaces) { if (ns.length === 0) { - errors.push('命名空间名称不能为空'); + errors.push('Namespace name cannot be empty'); } if (ns.length > MAX_KEY_LENGTH) { - errors.push(`命名空间 "${ns}" 名称超过 ${MAX_KEY_LENGTH} 字符`); + errors.push(`Namespace "${ns}" exceeds ${MAX_KEY_LENGTH} characters`); } } for (const entry of partition.entries) { if (entry.key.length === 0) { - errors.push(`在命名空间 "${entry.namespace}" 中存在空键名`); + errors.push(`Empty key in namespace "${entry.namespace}"`); } if (entry.key.length > MAX_KEY_LENGTH) { - errors.push(`键 "${entry.key}" 名称超过 ${MAX_KEY_LENGTH} 字符`); + errors.push(`Key "${entry.key}" exceeds ${MAX_KEY_LENGTH} characters`); } if (!partition.namespaces.includes(entry.namespace)) { - errors.push(`键 "${entry.key}" 的命名空间 "${entry.namespace}" 未注册`); + errors.push(`Key "${entry.key}" references unregistered namespace "${entry.namespace}"`); } // Validate value ranges for primitive types @@ -210,21 +210,21 @@ export function validatePartition(partition: NvsPartition): string[] { if (typeof entry.value === 'number') { const v = entry.value; switch (entry.type) { - case NvsType.U8: if (v < 0 || v > 0xFF) errors.push(`"${entry.key}" U8 值超出范围`); break; - case NvsType.I8: if (v < -128 || v > 127) errors.push(`"${entry.key}" I8 值超出范围`); break; - case NvsType.U16: if (v < 0 || v > 0xFFFF) errors.push(`"${entry.key}" U16 值超出范围`); break; - case NvsType.I16: if (v < -32768 || v > 32767) errors.push(`"${entry.key}" I16 值超出范围`); break; - case NvsType.U32: if (v < 0 || v > 0xFFFFFFFF) errors.push(`"${entry.key}" U32 值超出范围`); break; - case NvsType.I32: if (v < -2147483648 || v > 2147483647) errors.push(`"${entry.key}" I32 值超出范围`); break; + case NvsType.U8: if (v < 0 || v > 0xFF) errors.push(`"${entry.key}" U8 value out of range`); break; + case NvsType.I8: if (v < -128 || v > 127) errors.push(`"${entry.key}" I8 value out of range`); break; + case NvsType.U16: if (v < 0 || v > 0xFFFF) errors.push(`"${entry.key}" U16 value out of range`); break; + case NvsType.I16: if (v < -32768 || v > 32767) errors.push(`"${entry.key}" I16 value out of range`); break; + case NvsType.U32: if (v < 0 || v > 0xFFFFFFFF) errors.push(`"${entry.key}" U32 value out of range`); break; + case NvsType.I32: if (v < -2147483648 || v > 2147483647) errors.push(`"${entry.key}" I32 value out of range`); break; } } else if (typeof entry.value === 'bigint') { const v = entry.value; switch (entry.type) { case NvsType.U64: - if (v < 0n || v > 0xFFFFFFFFFFFFFFFFn) errors.push(`"${entry.key}" U64 值超出范围`); + if (v < 0n || v > 0xFFFFFFFFFFFFFFFFn) errors.push(`"${entry.key}" U64 value out of range`); break; case NvsType.I64: - if (v < -9223372036854775808n || v > 9223372036854775807n) errors.push(`"${entry.key}" I64 值超出范围`); + if (v < -9223372036854775808n || v > 9223372036854775807n) errors.push(`"${entry.key}" I64 value out of range`); break; } } @@ -234,7 +234,7 @@ export function validatePartition(partition: NvsPartition): string[] { if (entry.type === NvsType.SZ && typeof entry.value === 'string') { const byteLen = new TextEncoder().encode(entry.value).length; if (byteLen >= MAX_STRING_LENGTH) { - errors.push(`"${entry.key}" 字符串长度 ${byteLen} 字节超过上限 ${MAX_STRING_LENGTH - 1}`); + errors.push(`"${entry.key}" string length ${byteLen} bytes exceeds limit ${MAX_STRING_LENGTH - 1}`); } } @@ -244,11 +244,11 @@ export function validatePartition(partition: NvsPartition): string[] { // NvsType.BLOB_DATA uses the V2 chunked format and is capped at MAX_BLOB_SIZE_V2. if (entry.type === NvsType.BLOB && entry.value instanceof Uint8Array) { if (entry.value.length > MAX_BLOB_SIZE_V1) { - errors.push(`"${entry.key}" BLOB ${entry.value.length} 字节超过上限 ${MAX_BLOB_SIZE_V1}`); + errors.push(`"${entry.key}" BLOB ${entry.value.length} bytes exceeds limit ${MAX_BLOB_SIZE_V1}`); } } else if (entry.type === NvsType.BLOB_DATA && entry.value instanceof Uint8Array) { if (entry.value.length > MAX_BLOB_SIZE_V2) { - errors.push(`"${entry.key}" BLOB ${entry.value.length} 字节超过 V2 上限 ${MAX_BLOB_SIZE_V2}`); + errors.push(`"${entry.key}" BLOB ${entry.value.length} bytes exceeds V2 limit ${MAX_BLOB_SIZE_V2}`); } } } @@ -258,7 +258,7 @@ export function validatePartition(partition: NvsPartition): string[] { for (const entry of partition.entries) { const k = `${entry.namespace}::${entry.key}`; if (seen.has(k)) { - errors.push(`重复键: ${entry.namespace}/${entry.key}`); + errors.push(`Duplicate key: ${entry.namespace}/${entry.key}`); } seen.add(k); } diff --git a/lib/ota-data/parser.ts b/lib/ota-data/parser.ts index 1e043cb..b5aebc5 100644 --- a/lib/ota-data/parser.ts +++ b/lib/ota-data/parser.ts @@ -44,7 +44,7 @@ function isEntryValid(entry: OtaSelectEntry): boolean { */ export function parseOtaData(data: Uint8Array, numOtaPartitions = DEFAULT_NUM_OTA_PARTITIONS): OtaData { if (data.length < OTA_DATA_MIN_SIZE) { - throw new Error(`OTA data too short: ${data.length} bytes, need at least ${OTA_DATA_MIN_SIZE}`); + throw new Error(`OTA data too short: ${data.length}B`); } const entry0 = parseEntry(data, 0); diff --git a/lib/partition-table/csv-parser.ts b/lib/partition-table/csv-parser.ts index 7221b22..451da8f 100644 --- a/lib/partition-table/csv-parser.ts +++ b/lib/partition-table/csv-parser.ts @@ -16,7 +16,7 @@ function parseFlags(str: string): number { for (const part of normalized.split(/[\s|,]+/).filter(Boolean)) { if (part === 'encrypted') result |= PartitionFlags.ENCRYPTED; else if (part === 'readonly') result |= PartitionFlags.READONLY; - else throw new Error(`未知标志: "${part}"`); + else throw new Error(`Unknown flag: "${part}"`); } return result; } @@ -26,9 +26,9 @@ function parseSize(str: string): number { str = str.trim(); if (!str) return 0; if (str.startsWith('0x') || str.startsWith('0X')) { - if (!/^0x[0-9a-f]+$/i.test(str)) throw new Error(`无效的大小/偏移值: "${str}"`); + if (!/^0x[0-9a-f]+$/i.test(str)) throw new Error(`Invalid size/offset value: "${str}"`); const v = parseInt(str, 16); - if (isNaN(v) || v < 0 || v > U32_MAX) throw new Error(`无效的大小/偏移值: "${str}"`); + if (isNaN(v) || v < 0 || v > U32_MAX) throw new Error(`Invalid size/offset value: "${str}"`); return v; } @@ -40,11 +40,11 @@ function parseSize(str: string): number { if (unit === 'K') result = Math.floor(num * 1024); else if (unit === 'M') result = Math.floor(num * 1024 * 1024); else result = Math.floor(num); - if (result > U32_MAX) throw new Error(`无效的大小/偏移值: "${str}" (超出 32 位范围)`); + if (result > U32_MAX) throw new Error(`Invalid size/offset value: "${str}" (exceeds 32-bit range)`); return result; } - throw new Error(`无效的大小/偏移值: "${str}"`); + throw new Error(`Invalid size/offset value: "${str}"`); } /** @@ -86,7 +86,7 @@ function splitCsvLine(line: string): string[] { } } } - if (inQuotes) throw new Error('引号未闭合'); + if (inQuotes) throw new Error('Unclosed quote'); fields.push(current.trim()); return fields; } @@ -111,7 +111,7 @@ export function parseCsv(text: string, onWarning?: (line: number, message: strin try { const fields = splitCsvLine(line); if (fields.length < 5) { - onWarning?.(lineIdx + 1, `字段数量不足 (需要 5,实际 ${fields.length}): "${line}"`); + onWarning?.(lineIdx + 1, `Not enough fields (need 5, got ${fields.length}): "${line}"`); continue; } @@ -127,7 +127,7 @@ export function parseCsv(text: string, onWarning?: (line: number, message: strin const type = NAME_TO_TYPE[typeName]; if (type === undefined) { - onWarning?.(lineIdx + 1, `未知分区类型: "${typeName}"`); + onWarning?.(lineIdx + 1, `Unknown partition type: "${typeName}"`); continue; } @@ -137,7 +137,7 @@ export function parseCsv(text: string, onWarning?: (line: number, message: strin const flags = parseFlags(flagsStr); entries.push({ name, type, subtype, offset, size, flags }); } catch (e) { - onWarning?.(lineIdx + 1, `解析失败: ${(e as Error).message}`); + onWarning?.(lineIdx + 1, `Parse failed: ${(e as Error).message}`); } } diff --git a/lib/partition-table/serializer.ts b/lib/partition-table/serializer.ts index b2cfdb6..3d43ffc 100644 --- a/lib/partition-table/serializer.ts +++ b/lib/partition-table/serializer.ts @@ -7,12 +7,12 @@ const U32_MAX = 0xFFFF_FFFF; function assertU8(val: number, field: string): void { if (!Number.isInteger(val) || val < 0 || val > 0xFF) - throw new Error(`"${field}" 不是有效的字节值 (0–255): ${val}`); + throw new Error(`"${field}" is not a valid byte value (0–255): ${val}`); } function assertU32(val: number, field: string): void { if (!Number.isInteger(val) || val < 0 || val > U32_MAX) - throw new Error(`"${field}" 不是有效的 32 位无符号整数 (0–0xFFFFFFFF): ${val}`); + throw new Error(`"${field}" is not a valid uint32 (0–0xFFFFFFFF): ${val}`); } /** @@ -26,14 +26,14 @@ export function serializeBinary(table: PartitionTable): Uint8Array { let offset = 0; for (const entry of table.entries) { if (offset + ENTRY_SIZE > TABLE_MAX_SIZE - ENTRY_SIZE) { - throw new Error(`分区表条目过多,超过最大容量 (最多 ${Math.floor((TABLE_MAX_SIZE - ENTRY_SIZE) / ENTRY_SIZE)} 条)`); + throw new Error(`Too many partition table entries (max ${Math.floor((TABLE_MAX_SIZE - ENTRY_SIZE) / ENTRY_SIZE)})`); } - assertU8(entry.type, '类型'); - assertU8(entry.subtype, '子类型'); - assertU32(entry.offset, '偏移量'); - assertU32(entry.size, '大小'); - assertU32(entry.flags, '标志'); + assertU8(entry.type, 'type'); + assertU8(entry.subtype, 'subtype'); + assertU32(entry.offset, 'offset'); + assertU32(entry.size, 'size'); + assertU32(entry.flags, 'flags'); writeU16(buf, offset, ENTRY_MAGIC); buf[offset + 2] = entry.type; diff --git a/lib/partition-table/validator.ts b/lib/partition-table/validator.ts index 613155e..cc713eb 100644 --- a/lib/partition-table/validator.ts +++ b/lib/partition-table/validator.ts @@ -19,7 +19,7 @@ export function validateTable(table: PartitionTable): PartitionValidationError[] if (names.has(entry.name)) { errors.push({ type: 'duplicate_name', - message: `分区名称重复: "${entry.name}"`, + message: `Duplicate partition name: "${entry.name}"`, entryA: names.get(entry.name), entryB: entry, }); @@ -33,14 +33,14 @@ export function validateTable(table: PartitionTable): PartitionValidationError[] if (entry.offset !== 0 && entry.offset % SECTOR_SIZE !== 0) { errors.push({ type: 'alignment', - message: `"${entry.name}" 偏移 0x${entry.offset.toString(16)} 未对齐到 4KB 边界`, + message: `"${entry.name}" offset 0x${entry.offset.toString(16)} not aligned to 4KB boundary`, entryA: entry, }); } if (entry.size !== 0 && entry.size % SECTOR_SIZE !== 0) { errors.push({ type: 'alignment', - message: `"${entry.name}" 大小 0x${entry.size.toString(16)} 未对齐到 4KB 边界`, + message: `"${entry.name}" size 0x${entry.size.toString(16)} not aligned to 4KB boundary`, entryA: entry, }); } @@ -57,7 +57,7 @@ export function validateTable(table: PartitionTable): PartitionValidationError[] if (a.offset < bEnd && b.offset < aEnd) { errors.push({ type: 'overlap', - message: `"${a.name}" [0x${a.offset.toString(16)}..0x${aEnd.toString(16)}] 与 "${b.name}" [0x${b.offset.toString(16)}..0x${bEnd.toString(16)}] 重叠`, + message: `"${a.name}" [0x${a.offset.toString(16)}..0x${aEnd.toString(16)}] overlaps "${b.name}" [0x${b.offset.toString(16)}..0x${bEnd.toString(16)}]`, entryA: a, entryB: b, });