/** Partition type (top-level: app or data) */ export enum PartitionType { APP = 0x00, DATA = 0x01, } /** App subtypes */ export enum AppSubtype { FACTORY = 0x00, OTA_0 = 0x10, OTA_1 = 0x11, OTA_2 = 0x12, OTA_3 = 0x13, OTA_4 = 0x14, OTA_5 = 0x15, OTA_6 = 0x16, OTA_7 = 0x17, OTA_8 = 0x18, OTA_9 = 0x19, OTA_10 = 0x1A, OTA_11 = 0x1B, OTA_12 = 0x1C, OTA_13 = 0x1D, OTA_14 = 0x1E, OTA_15 = 0x1F, TEST = 0x20, } /** Data subtypes */ export enum DataSubtype { OTA = 0x00, PHY = 0x01, NVS = 0x02, COREDUMP = 0x03, NVS_KEYS = 0x04, EFUSE_EM = 0x05, UNDEFINED = 0x06, FAT = 0x81, SPIFFS = 0x82, LITTLEFS = 0x83, } /** Partition flags */ export enum PartitionFlags { NONE = 0x00, ENCRYPTED = 0x01, READONLY = 0x02, } /** A single parsed partition entry */ export interface PartitionEntry { name: string; type: PartitionType; subtype: number; offset: number; size: number; flags: number; } /** The complete partition table */ export interface PartitionTable { entries: PartitionEntry[]; md5Valid: boolean; /** True if an unexpected magic value was found mid-table (indicates binary corruption) */ corrupted?: boolean; } /** Human-readable type name map */ export const TYPE_NAMES: Record = { [PartitionType.APP]: 'app', [PartitionType.DATA]: 'data', }; /** Human-readable app subtype name map */ export const APP_SUBTYPE_NAMES: Record = { [AppSubtype.FACTORY]: 'factory', [AppSubtype.TEST]: 'test', }; // OTA_0..OTA_15 for (let i = 0; i <= 15; i++) { APP_SUBTYPE_NAMES[0x10 + i] = `ota_${i}`; } /** Human-readable data subtype name map */ export const DATA_SUBTYPE_NAMES: Record = { [DataSubtype.OTA]: 'ota', [DataSubtype.PHY]: 'phy', [DataSubtype.NVS]: 'nvs', [DataSubtype.COREDUMP]: 'coredump', [DataSubtype.NVS_KEYS]: 'nvs_keys', [DataSubtype.EFUSE_EM]: 'efuse', [DataSubtype.UNDEFINED]: 'undefined', [DataSubtype.FAT]: 'fat', [DataSubtype.SPIFFS]: 'spiffs', [DataSubtype.LITTLEFS]: 'littlefs', }; /** Get human-readable subtype name */ export function getSubtypeName(type: PartitionType, subtype: number): string { if (type === PartitionType.APP) { return APP_SUBTYPE_NAMES[subtype] ?? `0x${subtype.toString(16).padStart(2, '0')}`; } return DATA_SUBTYPE_NAMES[subtype] ?? `0x${subtype.toString(16).padStart(2, '0')}`; } /** Reverse lookup: type name string → PartitionType */ export const NAME_TO_TYPE: Record = { 'app': PartitionType.APP, 'data': PartitionType.DATA, }; /** Reverse lookup: subtype name → number, keyed by parent type */ export function subtypeFromName(type: PartitionType, name: string): number { const normalized = name.trim().toLowerCase(); if (type === PartitionType.APP) { for (const [val, n] of Object.entries(APP_SUBTYPE_NAMES)) { if (n === normalized) return Number(val); } } else { for (const [val, n] of Object.entries(DATA_SUBTYPE_NAMES)) { if (n === normalized) return Number(val); } } // Try numeric parse (decimal or hex) — full-string, byte-range validated if (normalized.startsWith('0x')) { if (!/^0x[0-9a-f]+$/i.test(normalized)) throw new Error(`Unknown partition subtype: "${name}"`); const v = parseInt(normalized, 16); if (isNaN(v) || v < 0 || v > 255) throw new Error(`Subtype value out of byte range: "${name}"`); return v; } else { if (!/^\d+$/.test(normalized)) throw new Error(`Unknown partition subtype: "${name}"`); const v = parseInt(normalized, 10); if (v > 255) throw new Error(`Subtype value out of byte range: "${name}"`); return v; } throw new Error(`Unknown partition subtype: "${name}"`); }