//! Heat2 varint. //! //! The first byte is the odd one: only six data bits (`0x3F`), with `0x40` as a //! sign flag and `0x80` as "more". Every later byte is a conventional 7-bit //! group. Groups are little-endian: byte 0 holds the low six bits, then seven //! bits at shifts 6, 13, 20, ... //! //! Evidence: validated byte-exact against the FIFA 17 preAuth capture //! (`LANG = 0x656E5553` encodes as `93 d5 f2 d6 0c`). //! //! The sign flag is UNVERIFIED — it never appears in any captured frame. It is //! implemented to match the oracle so negatives round-trip, but no claim is //! made that EA encodes negatives this way. use crate::error::{Error, Result}; /// Append the canonical (shortest) encoding of `value`. pub fn encode(value: i64, out: &mut Vec) { let neg = value < 0; // unsigned_abs, not -value: i64::MIN has no positive counterpart. let mut v = value.unsigned_abs(); let mut first = (v & 0x3F) as u8; v >>= 6; if neg { first |= 0x40; } if v == 0 { out.push(first); return; } out.push(first | 0x80); while v >= 0x80 { out.push(((v & 0x7F) as u8) | 0x80); v >>= 7; } out.push(v as u8); } /// Decode at `pos`, returning the value and the index just past it. pub fn decode(buf: &[u8], pos: usize) -> Result<(i64, usize)> { let mut i = pos; let b = *buf.get(i).ok_or(Error::Truncated { what: "varint", need: 1, have: 0, })?; i += 1; let mut val: u64 = (b & 0x3F) as u64; let neg = b & 0x40 != 0; if b & 0x80 != 0 { let mut shift = 6u32; loop { let b = *buf.get(i).ok_or(Error::Truncated { what: "varint continuation", need: 1, have: 0, })?; i += 1; if shift >= 64 { return Err(Error::VarintOverflow { at: pos }); } val |= ((b & 0x7F) as u64) << shift; shift += 7; if b & 0x80 == 0 { break; } } } let signed = if neg { (val as i64).wrapping_neg() } else { val as i64 }; Ok((signed, i)) } #[cfg(test)] mod tests { use super::*; fn enc(v: i64) -> Vec { let mut o = Vec::new(); encode(v, &mut o); o } #[test] fn single_byte_below_0x40() { assert_eq!(enc(0), vec![0x00]); assert_eq!(enc(1), vec![0x01]); assert_eq!(enc(0x3F), vec![0x3F]); } #[test] fn spills_to_a_second_group_at_0x40() { // Six data bits in byte 0, so 0x40 is the first value that needs two. assert_eq!(enc(0x40), vec![0x80, 0x01]); } #[test] fn matches_the_captured_lang_field() { assert_eq!(enc(0x656E5553), vec![0x93, 0xd5, 0xf2, 0xd6, 0x0c]); } #[test] fn round_trips_boundaries() { for v in [ 0, 1, 0x3F, 0x40, 0x7F, 0x80, 0x1FFF, 0x2000, 0xFFFF_FFFF, i64::MAX, -1, -300, ] { let bytes = enc(v); assert_eq!(decode(&bytes, 0).unwrap(), (v, bytes.len()), "value {v}"); } } #[test] fn rejects_a_never_terminating_varint() { let runaway = vec![0xFF; 32]; assert!(matches!( decode(&runaway, 0), Err(Error::VarintOverflow { .. }) )); } #[test] fn rejects_truncation() { assert!(decode(&[0x80], 0).is_err()); assert!(decode(&[], 0).is_err()); } }