pub fn decode_armor(payload: &str, fill_bits: u8) -> Option<Vec<u8>> {
let mut bits = Vec::with_capacity(payload.len() * 6);
for ch in payload.bytes() {
let mut val = ch.checked_sub(48)?; if val > 40 {
val -= 8; }
if val > 63 {
return None; }
for i in (0..6).rev() {
bits.push((val >> i) & 1);
}
}
let fill = fill_bits as usize;
if fill > 0 && bits.len() >= fill {
bits.truncate(bits.len() - fill);
}
Some(bits)
}
pub fn extract_u32(bits: &[u8], offset: usize, len: usize) -> Option<u32> {
if offset + len > bits.len() || len > 32 {
return None;
}
let mut val: u32 = 0;
for i in 0..len {
val = (val << 1) | u32::from(bits[offset + i]);
}
Some(val)
}
pub fn extract_i32(bits: &[u8], offset: usize, len: usize) -> Option<i32> {
let raw = extract_u32(bits, offset, len)?;
if len > 0 && (raw >> (len - 1)) & 1 == 1 {
let mask = u32::MAX << len;
Some((raw | mask) as i32)
} else {
Some(raw as i32)
}
}
pub fn extract_string(bits: &[u8], offset: usize, num_chars: usize) -> Option<String> {
let mut s = String::with_capacity(num_chars);
for i in 0..num_chars {
let char_offset = offset + i * 6;
let val = extract_u32(bits, char_offset, 6)? as u8;
let ch = match val {
0 => '@',
1..=26 => (b'A' + val - 1) as char,
27..=31 => (b'[' + val - 27) as char, 32 => ' ',
33..=57 => (b'!' + val - 33) as char, 58..=63 => (b':' + val - 58) as char, _ => '?',
};
s.push(ch);
}
let trimmed = s.trim_end_matches(['@', ' ']);
Some(trimmed.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_simple() {
let bits = decode_armor("01", 0).expect("valid");
assert_eq!(bits.len(), 12);
assert_eq!(&bits[..6], &[0, 0, 0, 0, 0, 0]);
assert_eq!(&bits[6..12], &[0, 0, 0, 0, 0, 1]);
}
#[test]
fn decode_with_fill_bits() {
let bits = decode_armor("0", 2).expect("valid");
assert_eq!(bits.len(), 4); }
#[test]
fn extract_signed_negative() {
let bits = vec![1, 1, 1, 1, 1, 1, 1, 1];
assert_eq!(extract_i32(&bits, 0, 8), Some(-1));
}
#[test]
fn extract_signed_positive() {
let bits = vec![0, 0, 0, 0, 0, 0, 0, 1];
assert_eq!(extract_i32(&bits, 0, 8), Some(1));
}
#[test]
fn extract_text() {
let bits = vec![0; 24];
assert_eq!(extract_string(&bits, 0, 4), Some(String::new()));
}
#[test]
fn extract_unsigned() {
let bits = decode_armor("15RTgt0PAso;90TKcjM8h6g208CQ", 0).expect("valid");
let msg_type = extract_u32(&bits, 0, 6).expect("valid");
assert_eq!(msg_type, 1);
let mmsi = extract_u32(&bits, 8, 30).expect("valid");
assert!(mmsi > 0);
}
#[test]
fn extract_unsigned_out_of_bounds() {
let bits = vec![0, 1, 0];
assert_eq!(extract_u32(&bits, 0, 4), None); }
#[test]
fn decode_empty_payload() {
let bits = decode_armor("", 0).expect("valid");
assert!(bits.is_empty());
}
#[test]
fn decode_invalid_character() {
assert!(decode_armor("~", 0).is_none());
}
#[test]
fn decode_low_byte_rejected() {
assert!(decode_armor("\x20", 0).is_none());
}
#[test]
fn decode_single_character() {
let bits = decode_armor("0", 0).expect("valid");
assert_eq!(bits.len(), 6);
assert_eq!(&bits, &[0, 0, 0, 0, 0, 0]);
}
}