yunsi-toolbox-vue/lib/ota-data/parser.ts

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,
};
}