// A deliberately DIFFERENT safe teaching format, not Goose wire compatibility. // RIDX, u32 count, then (u8 byte length, bytes, i32 count, u32 left/right). // Child indices use u32::MAX for None. No Rust object representation is read. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Node { pub word: Vec, pub count: i32, pub left: Option, pub right: Option } pub fn encode_nodes(nodes: &[Node]) -> Result, &'static str> { let count = u32::try_from(nodes.len()).map_err(|_| "too many nodes")?; let mut bytes=b"RIDX".to_vec(); bytes.extend_from_slice(&count.to_le_bytes()); for node in nodes { if node.word.len()>24 {return Err("word too long");} for child in [node.left,node.right].into_iter().flatten() { if child>=count {return Err("invalid child");} } bytes.push(u8::try_from(node.word.len()).unwrap()); bytes.extend_from_slice(&node.word); bytes.extend_from_slice(&node.count.to_le_bytes()); for child in [node.left,node.right] { bytes.extend_from_slice(&child.unwrap_or(u32::MAX).to_le_bytes()); } } Ok(bytes) } fn take<'a>(bytes: &'a [u8], at: &mut usize, len: usize) -> Result<&'a [u8], &'static str> { let end=at.checked_add(len).ok_or("offset overflow")?; let part=bytes.get(*at..end).ok_or("truncated image")?; *at=end; Ok(part) } fn u32_at(bytes:&[u8],at:&mut usize)->Result { Ok(u32::from_le_bytes(take(bytes,at,4)?.try_into().unwrap())) } pub fn decode_nodes(bytes: &[u8]) -> Result, &'static str> { let mut at=0; if take(bytes,&mut at,4)?!=b"RIDX" {return Err("bad magic");} let n=u32_at(bytes,&mut at)?; let n_usize=usize::try_from(n).map_err(|_|"node count overflow")?; if n_usize>(bytes.len()-at)/13 {return Err("invalid count");} let mut nodes=Vec::new(); for _ in 0..n { let len=usize::from(take(bytes,&mut at,1)?[0]); if len>24 {return Err("word too long");} let word=take(bytes,&mut at,len)?.to_vec(); let count=i32::from_le_bytes(take(bytes,&mut at,4)?.try_into().unwrap()); let mut children=[None;2]; for child in &mut children { let index=u32_at(bytes,&mut at)?; *child=if index==u32::MAX {None} else if index i32 { let mut at=Some(0_u32); // A bounded walk tolerates cycles in an otherwise bounds-safe image. for _ in 0..nodes.len() { let Some(index)=at else {return 0;}; let Some(node)=nodes.get(index as usize) else {return 0;}; match word.cmp(node.word.as_slice()) { std::cmp::Ordering::Equal=>return node.count, std::cmp::Ordering::Less=>at=node.left, std::cmp::Ordering::Greater=>at=node.right, } } 0 }