use super::Ax25Error;
use super::addr::{ADDRESS_LEN, Address, PathHop};
pub const CONTROL_UI: u8 = 0x03;
pub const CONTROL_PF_MASK: u8 = !0x10;
pub const PID_NO_LAYER3: u8 = 0xF0;
pub const MAX_DIGIPEATERS: usize = 8;
pub const MIN_FRAME_LEN: usize = 2 * ADDRESS_LEN + 2;
const PLACEHOLDER: Address = match Address::new(b"N0CALL", 0) {
Ok(a) => a,
Err(_) => panic!("placeholder address must be valid"),
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UiFrame<'a> {
pub dest: Address,
pub src: Address,
digipeaters: [Address; MAX_DIGIPEATERS],
digipeater_count: usize,
repeated_bits: u8,
pub info: &'a [u8],
}
impl<'a> UiFrame<'a> {
#[must_use]
pub const fn new(dest: Address, src: Address, info: &'a [u8]) -> Self {
Self {
dest,
src,
digipeaters: [PLACEHOLDER; MAX_DIGIPEATERS],
digipeater_count: 0,
repeated_bits: 0,
info,
}
}
pub fn with_path(
dest: Address,
src: Address,
path: &[Address],
info: &'a [u8],
) -> Result<Self, Ax25Error> {
if path.len() > MAX_DIGIPEATERS {
return Err(Ax25Error::TooManyDigipeaters {
got: path.len(),
max: MAX_DIGIPEATERS,
});
}
let mut frame = Self::new(dest, src, info);
for (slot, addr) in frame.digipeaters.iter_mut().zip(path.iter()) {
*slot = *addr;
}
frame.digipeater_count = path.len();
Ok(frame)
}
pub fn with_hops(
dest: Address,
src: Address,
hops: &[PathHop],
info: &'a [u8],
) -> Result<Self, Ax25Error> {
if hops.len() > MAX_DIGIPEATERS {
return Err(Ax25Error::TooManyDigipeaters {
got: hops.len(),
max: MAX_DIGIPEATERS,
});
}
let mut frame = Self::new(dest, src, info);
for (i, hop) in hops.iter().enumerate() {
if let Some(slot) = frame.digipeaters.get_mut(i) {
*slot = hop.address;
}
if hop.repeated {
frame.repeated_bits |= 1 << i;
}
}
frame.digipeater_count = hops.len();
Ok(frame)
}
#[must_use]
pub fn path(&self) -> &[Address] {
self.digipeaters
.get(..self.digipeater_count.min(MAX_DIGIPEATERS))
.unwrap_or(&[])
}
pub fn hops(&self) -> impl Iterator<Item = PathHop> + '_ {
self.path().iter().enumerate().map(|(i, &address)| PathHop {
address,
repeated: self.repeated_bits & (1 << i) != 0,
})
}
#[must_use]
pub const fn encoded_len(&self) -> usize {
(2 + self.digipeater_count) * ADDRESS_LEN + 2 + self.info.len()
}
#[cfg(feature = "alloc")]
#[must_use]
pub fn to_vec(&self) -> alloc::vec::Vec<u8> {
let mut out = alloc::vec![0u8; self.encoded_len()];
let n = self
.build(&mut out)
.expect("a buffer of encoded_len() always fits");
out.truncate(n);
out
}
pub fn build(&self, buf: &mut [u8]) -> Result<usize, Ax25Error> {
let needed = self.encoded_len();
if buf.len() < needed {
return Err(Ax25Error::FrameTooLarge {
len: needed,
max: buf.len(),
});
}
let mut pos = 0usize;
let mut put = |bytes: &[u8], pos: &mut usize| -> bool {
match buf.get_mut(*pos..*pos + bytes.len()) {
Some(slot) => {
slot.copy_from_slice(bytes);
*pos += bytes.len();
true
}
None => false,
}
};
let path = self.path();
let src_is_last = path.is_empty();
let mut ok = put(&self.dest.encode(true, false), &mut pos);
ok &= put(&self.src.encode(false, src_is_last), &mut pos);
for (i, digi) in path.iter().enumerate() {
let repeated = self.repeated_bits & (1 << i) != 0;
ok &= put(&digi.encode(repeated, i + 1 == path.len()), &mut pos);
}
ok &= put(&[CONTROL_UI, PID_NO_LAYER3], &mut pos);
ok &= put(self.info, &mut pos);
if ok {
Ok(pos)
} else {
Err(Ax25Error::FrameTooLarge {
len: needed,
max: buf.len(),
})
}
}
pub fn parse(bytes: &'a [u8]) -> Result<Self, Ax25Error> {
if bytes.len() < MIN_FRAME_LEN {
return Err(Ax25Error::FrameTooShort {
len: bytes.len(),
min: MIN_FRAME_LEN,
});
}
let mut pos = 0usize;
let next_hop = |pos: &mut usize| -> Result<(PathHop, bool), Ax25Error> {
let field: &[u8; ADDRESS_LEN] = bytes
.get(*pos..*pos + ADDRESS_LEN)
.and_then(|s| s.try_into().ok())
.ok_or(Ax25Error::FrameTooShort {
len: bytes.len(),
min: *pos + ADDRESS_LEN + 2,
})?;
*pos += ADDRESS_LEN;
PathHop::decode(field)
};
let (dest_hop, dest_last) = next_hop(&mut pos)?;
let dest = dest_hop.address;
if dest_last {
return Err(Ax25Error::FrameTooShort {
len: bytes.len(),
min: MIN_FRAME_LEN,
});
}
let (src_hop, mut last) = next_hop(&mut pos)?;
let mut frame = Self::new(dest, src_hop.address, &[]);
while !last {
if frame.digipeater_count == MAX_DIGIPEATERS {
return Err(Ax25Error::TooManyDigipeaters {
got: MAX_DIGIPEATERS + 1,
max: MAX_DIGIPEATERS,
});
}
let (digi, digi_last) = next_hop(&mut pos)?;
if let Some(slot) = frame.digipeaters.get_mut(frame.digipeater_count) {
*slot = digi.address;
}
if digi.repeated {
frame.repeated_bits |= 1 << frame.digipeater_count;
}
frame.digipeater_count += 1;
last = digi_last;
}
let control = bytes.get(pos).copied().ok_or(Ax25Error::FrameTooShort {
len: bytes.len(),
min: pos + 2,
})?;
if control & CONTROL_PF_MASK != CONTROL_UI {
return Err(Ax25Error::InvalidControl { got: control });
}
let pid = bytes
.get(pos + 1)
.copied()
.ok_or(Ax25Error::FrameTooShort {
len: bytes.len(),
min: pos + 2,
})?;
if pid != PID_NO_LAYER3 {
return Err(Ax25Error::InvalidPid { got: pid });
}
frame.info = bytes.get(pos + 2..).unwrap_or(&[]);
Ok(frame)
}
}
#[cfg(test)]
mod tests {
extern crate std;
use std::vec::Vec;
use super::super::addr::Address;
use super::*;
fn addr(call: &[u8], ssid: u8) -> Address {
match Address::new(call, ssid) {
Ok(a) => a,
Err(e) => panic!("{e}"),
}
}
#[test]
fn build_parse_round_trip_no_path() {
let frame = UiFrame::new(addr(b"APRS", 0), addr(b"N0CALL", 7), b"hello world");
let mut buf = [0u8; 64];
let len = match frame.build(&mut buf) {
Ok(n) => n,
Err(e) => panic!("{e}"),
};
assert_eq!(len, frame.encoded_len());
let parsed = match UiFrame::parse(&buf[..len]) {
Ok(p) => p,
Err(e) => panic!("{e}"),
};
assert_eq!(parsed.dest, frame.dest);
assert_eq!(parsed.src, frame.src);
assert_eq!(parsed.path(), &[]);
assert_eq!(parsed.info, b"hello world");
}
#[test]
fn build_parse_round_trip_with_path() {
let path = [addr(b"WIDE1", 1), addr(b"WIDE2", 2)];
let frame = match UiFrame::with_path(addr(b"APRS", 0), addr(b"K1ABC", 15), &path, b">test")
{
Ok(f) => f,
Err(e) => panic!("{e}"),
};
let mut buf = [0u8; 128];
let len = match frame.build(&mut buf) {
Ok(n) => n,
Err(e) => panic!("{e}"),
};
let parsed = match UiFrame::parse(&buf[..len]) {
Ok(p) => p,
Err(e) => panic!("{e}"),
};
assert_eq!(parsed.path(), &path);
assert_eq!(parsed.info, b">test");
assert_eq!(parsed.src, frame.src);
}
#[test]
fn wire_layout_control_and_pid() {
let frame = UiFrame::new(addr(b"APRS", 0), addr(b"N0CALL", 0), b"x");
let mut buf = [0u8; 32];
let len = match frame.build(&mut buf) {
Ok(n) => n,
Err(e) => panic!("{e}"),
};
assert_eq!(len, 17);
assert_eq!(buf[14], 0x03);
assert_eq!(buf[15], 0xF0);
assert_eq!(buf[16], b'x');
assert_eq!(buf[6] & 0x80, 0x80);
assert_eq!(buf[6] & 0x01, 0x00);
assert_eq!(buf[13] & 0x80, 0x00);
assert_eq!(buf[13] & 0x01, 0x01);
}
#[test]
fn build_rejects_small_buffer() {
let frame = UiFrame::new(addr(b"APRS", 0), addr(b"N0CALL", 0), b"payload");
let mut buf = [0u8; 10];
assert_eq!(
frame.build(&mut buf),
Err(Ax25Error::FrameTooLarge { len: 23, max: 10 })
);
}
#[test]
fn with_path_rejects_too_many() {
let digi = addr(b"WIDE1", 1);
let path = [digi; MAX_DIGIPEATERS + 1];
assert_eq!(
UiFrame::with_path(addr(b"APRS", 0), addr(b"N0CALL", 0), &path, b""),
Err(Ax25Error::TooManyDigipeaters {
got: MAX_DIGIPEATERS + 1,
max: MAX_DIGIPEATERS,
})
);
}
#[test]
fn parse_rejects_short() {
assert_eq!(
UiFrame::parse(&[0u8; 5]),
Err(Ax25Error::FrameTooShort { len: 5, min: 16 })
);
}
#[test]
fn parse_rejects_bad_control_and_pid() {
let frame = UiFrame::new(addr(b"APRS", 0), addr(b"N0CALL", 0), b"");
let mut buf = [0u8; 32];
let len = match frame.build(&mut buf) {
Ok(n) => n,
Err(e) => panic!("{e}"),
};
let mut bad_control = buf;
bad_control[14] = 0x2F;
assert_eq!(
UiFrame::parse(&bad_control[..len]),
Err(Ax25Error::InvalidControl { got: 0x2F })
);
let mut bad_pid = buf;
bad_pid[15] = 0xCC;
assert_eq!(
UiFrame::parse(&bad_pid[..len]),
Err(Ax25Error::InvalidPid { got: 0xCC })
);
}
#[test]
fn parse_accepts_ui_with_poll_final_bit_set() {
let frame = UiFrame::new(addr(b"APRS", 0), addr(b"N0CALL", 0), b"payload");
let mut buf = [0u8; 32];
let len = match frame.build(&mut buf) {
Ok(n) => n,
Err(e) => panic!("{e}"),
};
assert_eq!(buf[14], CONTROL_UI);
let mut pf_set = buf;
pf_set[14] = 0x13;
let parsed = match UiFrame::parse(&pf_set[..len]) {
Ok(f) => f,
Err(e) => panic!("0x13 must parse as a UI frame: {e}"),
};
assert_eq!(parsed.info, b"payload");
for control in [0x2F_u8, 0x3F, 0x43, 0x53, 0x63, 0x73, 0x87, 0x00] {
let mut other = buf;
other[14] = control;
assert_eq!(
UiFrame::parse(&other[..len]),
Err(Ax25Error::InvalidControl { got: control }),
"control {control:#04x} must not parse as UI"
);
}
}
#[test]
fn parse_rejects_unterminated_address_field() {
let a = addr(b"WIDE1", 1);
let mut bytes = Vec::new();
for _ in 0..11 {
bytes.extend_from_slice(&a.encode(false, false));
}
bytes.extend_from_slice(&[0x03, 0xF0]);
assert_eq!(
UiFrame::parse(&bytes),
Err(Ax25Error::TooManyDigipeaters {
got: MAX_DIGIPEATERS + 1,
max: MAX_DIGIPEATERS,
})
);
}
#[test]
fn parse_rejects_truncated_after_addresses() {
let frame = UiFrame::new(addr(b"APRS", 0), addr(b"N0CALL", 0), b"");
let mut buf = [0u8; 32];
let len = match frame.build(&mut buf) {
Ok(n) => n,
Err(e) => panic!("{e}"),
};
assert!(UiFrame::parse(&buf[..len - 1]).is_err());
}
#[test]
fn h_bits_round_trip_and_default_clear() {
use super::super::addr::PathHop;
let hops = [
PathHop {
address: addr(b"N0CALL", 1),
repeated: true,
},
PathHop::unused(addr(b"WIDE2", 1)),
];
let frame = match UiFrame::with_hops(addr(b"APRS", 0), addr(b"K1ABC", 0), &hops, b">h") {
Ok(f) => f,
Err(e) => panic!("{e}"),
};
let mut buf = [0u8; 64];
let len = match frame.build(&mut buf) {
Ok(n) => n,
Err(e) => panic!("{e}"),
};
assert_eq!(buf[20] & 0x80, 0x80);
assert_eq!(buf[27] & 0x80, 0x00);
let parsed = match UiFrame::parse(&buf[..len]) {
Ok(p) => p,
Err(e) => panic!("{e}"),
};
let parsed_hops: Vec<PathHop> = parsed.hops().collect();
assert_eq!(parsed_hops, hops);
let path = [hops[0].address, hops[1].address];
let plain = match UiFrame::with_path(addr(b"APRS", 0), addr(b"K1ABC", 0), &path, b">h") {
Ok(f) => f,
Err(e) => panic!("{e}"),
};
let mut plain_buf = [0u8; 64];
let plain_len = match plain.build(&mut plain_buf) {
Ok(n) => n,
Err(e) => panic!("{e}"),
};
assert_eq!(plain_buf[20] & 0x80, 0x00);
assert!(plain.hops().all(|h| !h.repeated));
let all_clear = [PathHop::unused(path[0]), PathHop::unused(path[1])];
let via_hops =
match UiFrame::with_hops(addr(b"APRS", 0), addr(b"K1ABC", 0), &all_clear, b">h") {
Ok(f) => f,
Err(e) => panic!("{e}"),
};
let mut hops_buf = [0u8; 64];
let hops_len = match via_hops.build(&mut hops_buf) {
Ok(n) => n,
Err(e) => panic!("{e}"),
};
assert_eq!(&plain_buf[..plain_len], &hops_buf[..hops_len]);
}
#[test]
fn max_digipeaters_round_trip() {
let digi = addr(b"WIDE2", 2);
let path = [digi; MAX_DIGIPEATERS];
let frame = match UiFrame::with_path(addr(b"APRS", 0), addr(b"N0CALL", 3), &path, b"deep") {
Ok(f) => f,
Err(e) => panic!("{e}"),
};
let mut buf = [0u8; 128];
let len = match frame.build(&mut buf) {
Ok(n) => n,
Err(e) => panic!("{e}"),
};
let parsed = match UiFrame::parse(&buf[..len]) {
Ok(p) => p,
Err(e) => panic!("{e}"),
};
assert_eq!(parsed.path().len(), MAX_DIGIPEATERS);
assert_eq!(parsed.info, b"deep");
}
}