79 lines
2.5 KiB
TypeScript
79 lines
2.5 KiB
TypeScript
import { readU32 } from '../shared/binary-reader';
|
|
import { readNullTermString } from '../shared/binary-reader';
|
|
import { crc32 } from '../shared/crc32';
|
|
import { OTA_SELECT_ENTRY_SIZE, OTA_DATA_MIN_SIZE, DEFAULT_NUM_OTA_PARTITIONS } from './constants';
|
|
import { OtaImgState } from './types';
|
|
import type { OtaSelectEntry, OtaData } from './types';
|
|
|
|
/**
|
|
* Parse a single esp_ota_select_entry_t (32 bytes):
|
|
* uint32_t ota_seq [0..3]
|
|
* uint8_t seq_label[20] [4..23]
|
|
* uint32_t ota_state [24..27]
|
|
* uint32_t crc [28..31]
|
|
*
|
|
* CRC is computed over the first 28 bytes.
|
|
*/
|
|
function parseEntry(data: Uint8Array, offset: number): OtaSelectEntry {
|
|
const seq = readU32(data, offset);
|
|
const label = readNullTermString(data, offset + 4, 20);
|
|
const state = readU32(data, offset + 24) as OtaImgState;
|
|
const storedCrc = readU32(data, offset + 28);
|
|
const computedCrc = crc32(data.subarray(offset, offset + 28));
|
|
|
|
return {
|
|
seq,
|
|
label,
|
|
state,
|
|
crc: storedCrc,
|
|
crcValid: storedCrc === computedCrc,
|
|
};
|
|
}
|
|
|
|
/** Check if an entry represents a valid (non-empty) OTA selection */
|
|
function isEntryValid(entry: OtaSelectEntry): boolean {
|
|
// seq of 0 or 0xFFFFFFFF means empty/erased
|
|
return entry.crcValid && entry.seq !== 0 && entry.seq !== 0xFFFFFFFF;
|
|
}
|
|
|
|
/**
|
|
* Parse OTA data partition binary.
|
|
*
|
|
* @param data - Raw bytes of the otadata partition (typically 0x2000 bytes, only first 64 used)
|
|
* @param numOtaPartitions - Number of OTA app partitions (default 2, used for partition name derivation)
|
|
*/
|
|
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}B`);
|
|
}
|
|
|
|
const entry0 = parseEntry(data, 0);
|
|
const entry1 = parseEntry(data, OTA_SELECT_ENTRY_SIZE);
|
|
|
|
// Determine active entry: the one with the higher valid seq
|
|
let activeIndex: number | null = null;
|
|
const valid0 = isEntryValid(entry0);
|
|
const valid1 = isEntryValid(entry1);
|
|
|
|
if (valid0 && valid1) {
|
|
activeIndex = entry0.seq >= entry1.seq ? 0 : 1;
|
|
} else if (valid0) {
|
|
activeIndex = 0;
|
|
} else if (valid1) {
|
|
activeIndex = 1;
|
|
}
|
|
|
|
let activeOtaPartition: string | null = null;
|
|
if (activeIndex !== null) {
|
|
const activeSeq = activeIndex === 0 ? entry0.seq : entry1.seq;
|
|
const partitionIndex = (activeSeq - 1) % numOtaPartitions;
|
|
activeOtaPartition = `ota_${partitionIndex}`;
|
|
}
|
|
|
|
return {
|
|
entries: [entry0, entry1],
|
|
activeIndex,
|
|
activeOtaPartition,
|
|
};
|
|
}
|