removeHeaderProtection function
Reverses the Header Protection mechanism, unmasking the first header byte and the Packet Number field.
The array is modified in place.
Returns the Packet Number Length (1, 2, 3, or 4 bytes).
Implementation
int removeHeaderProtection({
required Uint8List array,
required int pnOffset,
required Uint8List hpKey,
}) {
if (array.isEmpty) {
throw StateError("Packet is empty");
}
// Header form bit is NOT header-protected, so this is safe now.
final isShort = (array[0] & 0x80) == 0;
const sampleLength = 16;
final sampleOffset = pnOffset + 4;
if (sampleOffset + sampleLength > array.length) {
throw StateError(
"Not enough bytes for header protection sample "
"(need ${sampleOffset + sampleLength}, have ${array.length})",
);
}
final sample = array.sublist(sampleOffset, sampleOffset + sampleLength);
// IMPORTANT:
// Use the SAME helper/signature as the encrypt path.
final maskFull = aesEcbEncrypt(hpKey, sample);
final mask = maskFull.sublist(0, 5);
// ------------------------------------------------------------
// Unmask first byte
// ------------------------------------------------------------
if (isShort) {
// Short header: unmask low 5 bits
array[0] ^= (mask[0] & 0x1f);
// Reserved bits for short header are bits 4-3 and MUST be zero
final reservedBits = (array[0] >> 3) & 0x03;
if (reservedBits != 0) {
throw StateError(
"Invalid short-header reserved bits after HP removal: "
"${reservedBits.toRadixString(2).padLeft(2, '0')} "
"(firstByte=0x${array[0].toRadixString(16)})",
);
}
} else {
// Long header: unmask low 4 bits
array[0] ^= (mask[0] & 0x0f);
// Reserved bits for long header are bits 3-2 and MUST be zero
final reservedBits = (array[0] >> 2) & 0x03;
if (reservedBits != 0) {
throw StateError(
"Invalid long-header reserved bits after HP removal: "
"${reservedBits.toRadixString(2).padLeft(2, '0')} "
"(firstByte=0x${array[0].toRadixString(16)})",
);
}
}
// ------------------------------------------------------------
// Determine packet number length from unmasked first byte
// ------------------------------------------------------------
final pnLength = (array[0] & 0x03) + 1;
if (pnOffset + pnLength > array.length) {
throw StateError(
"Packet number field extends beyond packet length after HP removal "
"(pnOffset=$pnOffset, pnLength=$pnLength, packetLen=${array.length})",
);
}
// ------------------------------------------------------------
// Unmask packet number bytes
// ------------------------------------------------------------
for (int i = 0; i < pnLength; i++) {
array[pnOffset + i] ^= mask[1 + i];
}
return pnLength;
}