//! Bounded HAP TLV8 primitives. use std::collections::BTreeMap; use crate::error::HapError; pub const TLV_METHOD: u8 = 0x00; pub const TLV_IDENTIFIER: u8 = 0x01; pub const TLV_SALT: u8 = 0x02; pub const TLV_PUBLIC_KEY: u8 = 0x03; pub const TLV_PROOF: u8 = 0x04; pub const TLV_ENCRYPTED_DATA: u8 = 0x05; pub const TLV_STATE: u8 = 0x06; pub const TLV_ERROR: u8 = 0x07; pub const TLV_SIGNATURE: u8 = 0x0a; pub const TLV_PERMISSIONS: u8 = 0x0b; pub const TLV_FLAGS: u8 = 0x13; pub const TLV_SEPARATOR: u8 = 0xff; pub const TLV_ERROR_UNKNOWN: u8 = 0x01; pub const TLV_ERROR_AUTHENTICATION: u8 = 0x02; pub const TLV_ERROR_MAX_PEERS: u8 = 0x04; pub const TLV_ERROR_MAX_TRIES: u8 = 0x05; pub const TLV_ERROR_UNAVAILABLE: u8 = 0x06; pub const TLV_ERROR_BUSY: u8 = 0x07; const MAX_TLV_BYTES: usize = 4096; const MAX_TLV_TYPES: usize = 32; /// Parsed TLV8 values. Repeated fragments of a type are concatenated. #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct Tlv8 { values: BTreeMap>, } impl Tlv8 { pub fn parse(input: &[u8]) -> Result { if input.len() > MAX_TLV_BYTES { return Err(HapError::Protocol("TLV8 body exceeds 4096 bytes".into())); } let mut values: BTreeMap> = BTreeMap::new(); let mut offset = 0usize; while offset < input.len() { if input.len() - offset < 2 { return Err(HapError::Protocol("truncated TLV8 header".into())); } let kind = input[offset]; let len = input[offset + 1] as usize; offset += 2; let end = offset .checked_add(len) .filter(|end| *end <= input.len()) .ok_or_else(|| HapError::Protocol("truncated TLV8 value".into()))?; if !values.contains_key(&kind) && values.len() == MAX_TLV_TYPES { return Err(HapError::Protocol("too many TLV8 types".into())); } values .entry(kind) .or_default() .extend_from_slice(&input[offset..end]); offset = end; } Ok(Self { values }) } pub fn get(&self, kind: u8) -> Option<&[u8]> { self.values.get(&kind).map(Vec::as_slice) } pub fn byte(&self, kind: u8) -> Option { let value = self.get(kind)?; (value.len() == 1).then_some(value[0]) } pub fn insert(&mut self, kind: u8, value: impl Into>) { self.values.insert(kind, value.into()); } pub fn encode(&self) -> Vec { encode_items( self.values .iter() .map(|(&kind, value)| (kind, value.as_slice())), ) } } /// Encode ordered TLV8 items, including repeated items separated by a /// zero-length `Separator` for `/pairings` list responses. pub fn encode_items<'a>(items: impl IntoIterator) -> Vec { let mut encoded = Vec::new(); for (kind, value) in items { if value.is_empty() { encoded.extend_from_slice(&[kind, 0]); continue; } for chunk in value.chunks(u8::MAX as usize) { encoded.push(kind); encoded.push(chunk.len() as u8); encoded.extend_from_slice(chunk); } } encoded } pub fn error_response(state: u8, error: u8) -> Vec { encode_items([ (TLV_STATE, [state].as_slice()), (TLV_ERROR, [error].as_slice()), ]) } #[cfg(test)] mod tests { use super::*; #[test] fn tlv8_roundtrip_supports_fragmented_values() { let mut tlv = Tlv8::default(); tlv.insert(TLV_PUBLIC_KEY, vec![3; 300]); tlv.insert(TLV_STATE, vec![1]); let encoded = tlv.encode(); let decoded = Tlv8::parse(&encoded).unwrap(); assert_eq!(decoded, tlv); } #[test] fn malformed_or_oversized_tlv_is_rejected() { assert!(Tlv8::parse(&[TLV_STATE]).is_err()); assert!(Tlv8::parse(&[TLV_STATE, 2, 1]).is_err()); assert!(Tlv8::parse(&vec![0; MAX_TLV_BYTES + 1]).is_err()); } #[test] fn error_response_is_not_success_shaped() { let response = error_response(2, TLV_ERROR_UNAVAILABLE); let decoded = Tlv8::parse(&response).unwrap(); assert_eq!(decoded.byte(TLV_STATE), Some(2)); assert_eq!(decoded.byte(TLV_ERROR), Some(TLV_ERROR_UNAVAILABLE)); } }