xlsbye-biff12 0.1.0

BIFF12 binary record parser for XLSB files
Documentation
pub fn decode_wide_string(data: &[u8]) -> Option<(String, usize)> {
    if data.len() < 4 {
        return None;
    }
    let len = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
    let byte_len = len.checked_mul(2)?;
    let total = 4usize.checked_add(byte_len)?;
    if data.len() < total {
        return None;
    }
    let chars: Vec<u16> = data[4..total]
        .chunks_exact(2)
        .map(|c| u16::from_le_bytes([c[0], c[1]]))
        .collect();
    let s = String::from_utf16_lossy(&chars);
    Some((s, total))
}

pub fn decode_short_string(data: &[u8]) -> Option<(String, usize)> {
    if data.is_empty() {
        return None;
    }

    decode_utf16_with_len(data, 1, usize::from(data[0]))
}

pub fn decode_short_string_u16(data: &[u8]) -> Option<(String, usize)> {
    if data.len() < 2 {
        return None;
    }

    let len = usize::from(u16::from_le_bytes([data[0], data[1]]));
    decode_utf16_with_len(data, 2, len)
}

fn decode_utf16_with_len(data: &[u8], prefix_len: usize, char_len: usize) -> Option<(String, usize)> {
    let byte_len = char_len.checked_mul(2)?;
    let total = prefix_len.checked_add(byte_len)?;
    if data.len() < total {
        return None;
    }

    let chars: Vec<u16> = data[prefix_len..total]
        .chunks_exact(2)
        .map(|c| u16::from_le_bytes([c[0], c[1]]))
        .collect();
    let s = String::from_utf16_lossy(&chars);
    Some((s, total))
}

#[cfg(test)]
mod tests {
    use super::*;

    fn utf16le(s: &str) -> Vec<u8> {
        s.encode_utf16()
            .flat_map(u16::to_le_bytes)
            .collect::<Vec<_>>()
    }

    #[test]
    fn decode_wide_string_valid_and_truncated() {
        let mut bytes = Vec::new();
        bytes.extend_from_slice(&(2u32).to_le_bytes());
        bytes.extend_from_slice(&utf16le("Hi"));

        let decoded = decode_wide_string(&bytes).unwrap();
        assert_eq!(decoded.0, "Hi");
        assert_eq!(decoded.1, 8);

        assert!(decode_wide_string(&bytes[..7]).is_none());
    }

    #[test]
    fn decode_short_string_valid_and_truncated() {
        let mut bytes = Vec::new();
        bytes.push(2);
        bytes.extend_from_slice(&utf16le("Yo"));

        let decoded = decode_short_string(&bytes).unwrap();
        assert_eq!(decoded.0, "Yo");
        assert_eq!(decoded.1, 5);

        assert!(decode_short_string(&bytes[..4]).is_none());
    }

    #[test]
    fn decode_short_string_u16_valid_and_truncated() {
        let mut bytes = Vec::new();
        bytes.extend_from_slice(&(2u16).to_le_bytes());
        bytes.extend_from_slice(&utf16le("Ok"));

        let decoded = decode_short_string_u16(&bytes).unwrap();
        assert_eq!(decoded.0, "Ok");
        assert_eq!(decoded.1, 6);

        assert!(decode_short_string_u16(&bytes[..5]).is_none());
    }
}