use thiserror::Error;
#[derive(Debug, PartialEq, Clone)]
pub struct SpacePacket {
pub primary_header: PrimaryHeader,
pub payload: Vec<u8>
}
impl SpacePacket {
const DATA_LENGTH_SIZE: usize = 2;
const DATA_IDX: usize = 6;
pub fn new(
packet_type: PacketType,
secondary_header: bool,
apid: u16,
sequence_flag: SequenceFlag,
sequence_number: u16,
payload: Vec<u8>
) -> Self {
assert!(payload.len() <= u16::MAX as usize, "user data must be less than 65536");
assert!(!payload.is_empty(), "user data cannot be left empty");
assert!(apid <= PrimaryHeader::APID_MASK, "application process ID is invalid");
assert!(sequence_number <= PrimaryHeader::SEQUENCE_NUMBER_MASK, "sequence number is invalid");
let primary_header = PrimaryHeader {
version: PrimaryHeader::VERSION,
packet_type,
secondary_header,
apid,
sequence_flag,
sequence_number,
};
Self { primary_header, payload }
}
pub fn encode(&self) -> Vec<u8> {
let mut encoded = self.primary_header.encode();
encoded.extend_from_slice(&u16::to_be_bytes((self.payload.len() - 1) as u16));
encoded.extend_from_slice(&self.payload);
encoded
}
pub fn decode(buf: &[u8]) -> Result<Self, Error> {
let primary_header = PrimaryHeader::decode(buf)?;
let data_len_bytes = buf
.get(PrimaryHeader::PRIMARY_HEADER_LEN..(PrimaryHeader::PRIMARY_HEADER_LEN + Self::DATA_LENGTH_SIZE))
.ok_or(Error::IncompleteHeader)?;
let payload_len = u16::from_be_bytes([data_len_bytes[0], data_len_bytes[1]]) + 1;
let payload = buf
.get(Self::DATA_IDX..(Self::DATA_IDX + payload_len as usize))
.ok_or(Error::InsufficientData { expected: payload_len as usize, found: buf[Self::DATA_IDX..].len() })?
.to_vec();
Ok( Self { primary_header, payload } )
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PacketType {
Telemetry = 0,
Telecommand = 1,
}
impl PacketType {
pub fn to_bits(&self) -> u16 {
match self {
Self::Telemetry => 0b0,
Self::Telecommand => 0b1,
}
}
pub fn from_bits(bits: u16) -> Self {
match bits & 0b1 {
0b0 => Self::Telemetry,
0b1 => Self::Telecommand,
_ => unreachable!()
}
}
pub fn is_telecommand(&self) -> bool {
matches!(self, Self::Telecommand)
}
pub fn is_telemetry(&self) -> bool {
matches!(self, Self::Telemetry)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SequenceFlag {
Continuation = 0,
Start = 1,
End = 2,
Unsegmented = 3,
}
impl SequenceFlag {
pub fn to_bits(&self) -> u16 {
match self {
Self::Continuation => 0b00,
Self::Start => 0b01,
Self::End => 0b10,
Self::Unsegmented => 0b11,
}
}
pub fn from_bits(bits: u16) -> Self {
match bits & 0b11 {
0b00 => Self::Continuation,
0b01 => Self::Start,
0b10 => Self::End,
0b11 => Self::Unsegmented,
_ => unreachable!()
}
}
pub fn is_continuation(&self) -> bool {
matches!(self, Self::Continuation)
}
pub fn is_start(&self) -> bool {
matches!(self, Self::Start)
}
pub fn is_end(&self) -> bool {
matches!(self, Self::End)
}
pub fn is_unsegmented(&self) -> bool {
matches!(self, Self::Unsegmented)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PrimaryHeader {
pub version: u8,
pub packet_type: PacketType,
pub secondary_header: bool,
pub apid: u16,
pub sequence_flag: SequenceFlag,
pub sequence_number: u16,
}
impl PrimaryHeader {
const PRIMARY_HEADER_LEN: usize = 4;
const VERSION: u8 = 0b000;
const VERSION_SHIFT: usize = 13;
const PACKET_TYPE_SHIFT: usize = 12;
const SECONDARY_HEADER_SHIFT: usize = 11;
const APID_SHIFT: usize = 0;
const SEQUENCE_FLAG_SHIFT: usize = 14;
const PACKET_TYPE_MASK: u16 = 0x1000;
const SECONDARY_HEADER_MASK: u16 = 0x0800;
const APID_MASK: u16 = 0x03FF;
const SEQUENCE_FLAG_MASK: u16 = 0xC000;
const SEQUENCE_NUMBER_MASK: u16 = 0x3FFF;
pub fn encode(&self) -> Vec<u8> {
let packet_id =
u16::from(self.version) << Self::VERSION_SHIFT |
self.packet_type.to_bits() << Self::PACKET_TYPE_SHIFT |
u16::from(self.secondary_header) << Self::SECONDARY_HEADER_SHIFT |
self.apid & Self::APID_MASK << Self::APID_SHIFT;
let sequence_ctl =
self.sequence_flag.to_bits() << Self::SEQUENCE_FLAG_SHIFT |
self.sequence_number & Self::SEQUENCE_NUMBER_MASK;
let mut encoded = Vec::new();
encoded.extend_from_slice(&u16::to_be_bytes(packet_id));
encoded.extend_from_slice(&u16::to_be_bytes(sequence_ctl));
encoded
}
pub fn decode(buf: &[u8]) -> Result<Self, Error> {
let bytes = buf.get(0..Self::PRIMARY_HEADER_LEN).ok_or(Error::IncompleteHeader)?;
let packet_id = u16::from_be_bytes([bytes[0], bytes[1]]);
let sequence_ctl = u16::from_be_bytes([bytes[2], bytes[3]]);
let (version, packet_type, secondary_header, apid) = (
(packet_id >> Self::VERSION_SHIFT) as u8,
PacketType::from_bits((packet_id & Self::PACKET_TYPE_MASK) >> Self::PACKET_TYPE_SHIFT),
packet_id & Self::SECONDARY_HEADER_MASK != 0,
packet_id & Self::APID_MASK,
);
if version != Self::VERSION {
return Err(Error::Unsupported(version))
}
let (sequence_flag, sequence_number) = (
SequenceFlag::from_bits((sequence_ctl & Self::SEQUENCE_FLAG_MASK) >> Self::SEQUENCE_FLAG_SHIFT),
sequence_ctl & Self::SEQUENCE_NUMBER_MASK
);
Ok(Self {version, packet_type, secondary_header, apid, sequence_flag, sequence_number})
}
}
#[derive(Debug, Error, PartialEq)]
pub enum Error {
#[error("space packet protocol version {} not supported", .0)]
Unsupported(u8),
#[error("incomplete primary header")]
IncompleteHeader,
#[error("insufficient data to complete decoding, found {}B but expected {}B", .found, .expected)]
InsufficientData{ expected: usize, found: usize },
}
#[cfg(test)]
pub mod tests {
use super::*;
use rstest::rstest;
#[rstest]
fn test_spp_primary_header_codec(
#[values(PacketType::Telecommand, PacketType::Telemetry)]
packet_type: PacketType,
#[values(true, false)]
secondary_header: bool,
#[values(SequenceFlag::Continuation, SequenceFlag::Start, SequenceFlag::End, SequenceFlag::Unsegmented)]
sequence_flag: SequenceFlag,
) {
let expected = PrimaryHeader {
version: PrimaryHeader::VERSION,
packet_type,
secondary_header,
apid: 0,
sequence_flag,
sequence_number: 0
};
let encoded = expected.encode();
let found = PrimaryHeader::decode(&encoded).unwrap();
assert_eq!(expected, found)
}
#[rstest]
#[case("Hello, World!".as_bytes().to_vec())]
#[case(vec![0])]
#[case(vec![0u8; u16::MAX as usize])]
fn test_test_spp_packet_codec(
#[values(PacketType::Telecommand, PacketType::Telemetry)]
packet_type: PacketType,
#[values(true, false)]
secondary_header: bool,
#[values(SequenceFlag::Continuation, SequenceFlag::Start, SequenceFlag::End, SequenceFlag::Unsegmented)]
sequence_flag: SequenceFlag,
#[case] payload: Vec<u8>,
) {
let expected = SpacePacket::new(packet_type, secondary_header, 0, sequence_flag, 0, payload);
let encoded = expected.encode();
let found = SpacePacket::decode(&encoded).unwrap();
assert_eq!(expected.primary_header, found.primary_header);
assert_eq!(expected.payload, found.payload)
}
#[rstest]
#[should_panic]
fn test_empty_user_data() {
let expected = SpacePacket::new(PacketType::Telemetry, false, 0, SequenceFlag::Continuation, 0, vec![]);
let encoded = expected.encode();
let found = SpacePacket::decode(&encoded).unwrap();
assert_eq!(expected.primary_header, found.primary_header);
assert_eq!(expected.payload, found.payload)
}
#[rstest]
fn test_incomplete_header_err(
#[values(1, 2, 3, 4, 5)] header_len: usize
) {
let forged_header_packet = vec![0u8; header_len];
assert_eq!(SpacePacket::decode(&forged_header_packet), Err(Error::IncompleteHeader))
}
#[rstest]
#[case(vec![1; 5])]
#[case(vec![1; 1])]
#[case(vec![1; 128])]
#[case(vec![1; 12048])]
#[case(vec![1; 60000])]
fn test_insufficient_data_err(#[case] payload: Vec<u8>) {
let mut packet = PrimaryHeader {
version: PrimaryHeader::VERSION,
packet_type: PacketType::Telecommand,
secondary_header: false,
apid: 0,
sequence_flag: SequenceFlag::End,
sequence_number: 0
}.encode();
let bad_payload_len = payload.len() as u16 + 5 - 1;
packet.extend_from_slice(&u16::to_be_bytes(bad_payload_len));
packet.extend_from_slice(&payload);
assert_eq!(SpacePacket::decode(&packet), Err(Error::InsufficientData { expected: (bad_payload_len + 1) as usize, found: payload.len() }))
}
#[rstest]
fn test_unsupported_err(#[values(1, 2, 3, 4, 5, 6, 7)] version: u8) {
let mut packet = SpacePacket::new(
PacketType::Telemetry,
false,
0,
SequenceFlag::Continuation,
0,
vec![1]
);
packet.primary_header.version = version;
let encoded = packet.encode();
assert_eq!(SpacePacket::decode(&encoded), Err(Error::Unsupported(version)))
}
}