ccsds_rs/
spp.rs

1//! Provides functionality to create, encode, and decode Space Packet Protocol
2//! packets specified by the CCSDS 133.0-B-2 June 2020 Standard.
3//!
4//! General Usage:
5//! ``` rust
6//! use ccsds_rs::spp::{SpacePacket, PacketType, SequenceFlag};
7//!
8//! # fn main() {
9//!     // Define user specified data.
10//!     let my_payload = "Hello, world!".as_bytes().to_vec();
11//!
12//!     // Generate SpacePacket
13//!     let my_space_packet = SpacePacket::new(
14//!         PacketType::Telecommand,
15//!         false,
16//!         67,
17//!         SequenceFlag::Unsegmented,
18//!         0,
19//!         my_payload // User data (includes secondary header if used)
20//!     );
21//!
22//!     // Encode SpacePacket as vector of bytes for transmission
23//!     let encoded = my_space_packet.encode();
24//!
25//!     // Do something with space packet....
26//!
27//!     // Decoding a space packet.
28//!     let decoded = SpacePacket::decode(&encoded)
29//!     .expect("Failed to decode SpacePacket!");
30//! # }
31//!
32//! ```
33
34use thiserror::Error;
35
36#[derive(Debug, PartialEq, Clone)]
37/// SPP Packet as defined by the CCSDS 133.0-B-2 Standard.
38pub struct SpacePacket {
39    /// [PrimaryHeader] of the Space Packet Protocol
40    pub primary_header: PrimaryHeader,
41
42    /// Payload of Space Packet Protocol
43    pub payload: Vec<u8>
44}
45
46impl SpacePacket {
47    /// size of the user data length field 
48    const DATA_LENGTH_SIZE: usize = 2;
49
50    /// Index of user data
51    const DATA_IDX: usize = 6;
52
53    pub fn new(
54        packet_type: PacketType, 
55        secondary_header: bool,
56        apid: u16,
57        sequence_flag: SequenceFlag,
58        sequence_number: u16,
59        payload: Vec<u8>
60    ) -> Self {
61        assert!(payload.len() <= u16::MAX as usize, "user data must be less than 65536");
62        assert!(!payload.is_empty(), "user data cannot be left empty");
63        assert!(apid <= PrimaryHeader::APID_MASK, "application process ID is invalid");
64        assert!(sequence_number <= PrimaryHeader::SEQUENCE_NUMBER_MASK, "sequence number is invalid");
65
66        let primary_header = PrimaryHeader {
67            version: PrimaryHeader::VERSION,
68            packet_type,
69            secondary_header,
70            apid,
71            sequence_flag,
72            sequence_number,
73        };
74
75        Self { primary_header, payload }
76    }
77
78    /// Encodes the [SpacePacket] as a vector of bytes.
79    pub fn encode(&self) -> Vec<u8> {
80        let mut encoded = self.primary_header.encode();
81        // Subtract 1 from user data field as specified in CCSDS 133.0-B-2 Standard
82        encoded.extend_from_slice(&u16::to_be_bytes((self.payload.len() - 1) as u16));
83        encoded.extend_from_slice(&self.payload);
84        encoded
85    }
86
87    /// Decodes the primary header from a slice of bytes. Returns the result of the
88    /// operation, on success giving the decoded [SpacePacket].
89    ///
90    /// Decoding can fail for the following reasons:
91    /// - [Incomplete header][Error::IncompleteHeader] resulting in failure to parse [PrimaryHeader] 
92    /// - [Insufficient data][Error::InsufficientData] resulting in failure to parse the user data
93    /// - [Unsupported Space Packet Protocol][Error::Unsupported] resulting in failure to parse [SpacePacket]
94    ///
95    /// Both [Incomplete header][Error::IncompleteHeader] and [Insufficient data][Error::InsufficientData] errors are recoverable.
96    /// In most cases its the result of either corrupted data, or the source not supplying enough
97    /// data to realize the completed [SpacePacket]. [Unsupported Space Packet Protocol][Error::Unsupported] is recoverable
98    /// but the crate does not support any Space Packet Protocol other then version 0.
99    pub fn decode(buf: &[u8]) -> Result<Self, Error> {
100        let primary_header = PrimaryHeader::decode(buf)?;
101
102        let data_len_bytes = buf
103            .get(PrimaryHeader::PRIMARY_HEADER_LEN..(PrimaryHeader::PRIMARY_HEADER_LEN + Self::DATA_LENGTH_SIZE))
104            .ok_or(Error::IncompleteHeader)?;
105
106        // Add single byte back to payload length that we subtracted during encoding
107        let payload_len = u16::from_be_bytes([data_len_bytes[0], data_len_bytes[1]]) + 1;
108
109        let payload = buf
110            .get(Self::DATA_IDX..(Self::DATA_IDX + payload_len as usize))
111            .ok_or(Error::InsufficientData { expected: payload_len as usize, found: buf[Self::DATA_IDX..].len() })?
112            .to_vec();
113
114        Ok( Self { primary_header, payload } )
115    }
116}
117
118
119/// Indicates if the [SpacePacket] packet is of the Telemetry or Telecommand type.
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum PacketType {
122    Telemetry = 0,
123    Telecommand = 1,
124}
125
126impl PacketType {
127    /// Converts the [PacketType] enum into its bitwise representation to be used in the SPP
128    /// primary header.
129    pub fn to_bits(&self) -> u16 {
130        match self {
131            Self::Telemetry => 0b0,
132            Self::Telecommand => 0b1,
133        }
134    }
135
136    /// Converts the raw bits (after being shifted) from the packet ID portion of the primary
137    /// header into [PacketType].
138    pub fn from_bits(bits: u16) -> Self {
139        match bits & 0b1 {
140            0b0 => Self::Telemetry,
141            0b1 => Self::Telecommand,
142            _ => unreachable!()
143        }
144    }
145
146    /// returns boolean indicating if instance of [PacketType] is [PacketType::Telecommand]
147    pub fn is_telecommand(&self) -> bool {
148        matches!(self, Self::Telecommand)
149    }
150
151    /// returns boolean indicating if instance of [PacketType] is [PacketType::Telemetry]
152    pub fn is_telemetry(&self) -> bool {
153        matches!(self, Self::Telemetry)
154    }
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158/// Sequence flag indicating if the packet is the start, end, or continuation in a sequence of
159/// [SpacePacket]s, or is the packet is unsegmented.
160pub enum SequenceFlag {
161    Continuation = 0,
162    Start = 1,
163    End = 2,
164    Unsegmented = 3,
165}
166
167impl SequenceFlag {
168    /// Converts the [SequenceFlag] enum into its bitwise representation to be used in the SPP
169    /// primary header.
170    pub fn to_bits(&self) -> u16 {
171        match self {
172            Self::Continuation => 0b00,
173            Self::Start => 0b01,
174            Self::End => 0b10,
175            Self::Unsegmented => 0b11,
176        }
177    }
178
179    /// Converts the raw bits (after being shifted) from the sequence control portion of the primary
180    /// header into [SequenceFlag].
181    pub fn from_bits(bits: u16) -> Self {
182        match bits & 0b11 {
183            0b00 => Self::Continuation,
184            0b01 => Self::Start,
185            0b10 => Self::End,
186            0b11 => Self::Unsegmented,
187            _ => unreachable!()
188        }
189    }
190
191    /// returns boolean indicating if instance of [SequenceFlag] is [SequenceFlag::Continuation]
192    pub fn is_continuation(&self) -> bool {
193        matches!(self, Self::Continuation)
194    }
195
196    /// returns boolean indicating if instance of [SequenceFlag] is [SequenceFlag::Start]
197    pub fn is_start(&self) -> bool {
198        matches!(self, Self::Start)
199    }
200
201    /// returns boolean indicating if instance of [SequenceFlag] is [SequenceFlag::End]
202    pub fn is_end(&self) -> bool {
203        matches!(self, Self::End)
204    }
205    /// returns boolean indicating if instance of [SequenceFlag] is [SequenceFlag::Unsegmented]
206    pub fn is_unsegmented(&self) -> bool {
207        matches!(self, Self::Unsegmented)
208    }
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq)]
212/// Primary Header used in the Space Packet Protocol.
213///
214/// This data structure encapsulates the packet version number, packet identification, 
215/// and sequence control field of the primary header of a [SpacePacket]. It is possible, although
216/// not neccessary to work with the [PrimaryHeader] struct directly. 
217///
218/// Typical usage involves creating a SpacePacket, which internally constructs the [PrimaryHeader]
219/// using the arguments passed.
220/// ``` rust
221/// use ccsds_rs::spp::{SpacePacket, PacketType, SequenceFlag};
222/// # fn main () {
223/// // generates new SpacePacket, internally constructing the PrimaryHeader.
224/// let my_space_packet = SpacePacket::new(
225///     PacketType::Telecommand,
226///     false,
227///     17,
228///     SequenceFlag::Unsegmented,
229///     0,
230///     "Cool Space Data".as_bytes().to_vec()
231/// );
232/// # }
233/// ```
234///
235/// Note that the user data length field is not included as a field within [PrimaryHeader],
236/// The data length field is generated at encoding time of the [SpacePacket].
237pub struct PrimaryHeader {
238
239    /// Hardcoded to 0b000, but here incase standard changes in the future (3 bits)
240    pub version: u8,
241
242    /// Packet type defined by [PacketType] enum (1 bit)
243    pub packet_type: PacketType,
244
245    /// Indicates if secondary header is used (1 bit)
246    pub secondary_header: bool,
247
248    /// Application process ID of the packet (11 bits)
249    pub apid: u16,
250
251    /// Sequence flag defined by [SequenceFlag] (2 bits)
252    pub sequence_flag: SequenceFlag,
253
254    /// Sequence number (14 bits)
255    pub sequence_number: u16,
256}
257
258impl PrimaryHeader {
259
260    /// Size of the primary header
261    const PRIMARY_HEADER_LEN: usize = 4;
262
263    /// Hardcoded version number for SPP
264    const VERSION: u8 = 0b000;
265
266    /// Number of bits the VERSION needs to be shifted in the
267    /// encode function.
268    const VERSION_SHIFT: usize = 13;
269    /// Number of bits the [PacketType] bit needs to be shifted in the
270    /// encode function.
271    const PACKET_TYPE_SHIFT: usize = 12;
272    /// Number of bits the secondary_header bit needs to be shifted in the
273    /// encode function.
274    const SECONDARY_HEADER_SHIFT: usize = 11;
275    /// Number of bits the apid needs to be shifted in the
276    /// encode function.
277    const APID_SHIFT: usize = 0;
278    /// Number of bits the [SequenceFlag] bits needs to be shifted in the
279    /// encode function.
280    const SEQUENCE_FLAG_SHIFT: usize = 14;
281
282    /// Mask of [PacketType] bit in the decode function.
283    const PACKET_TYPE_MASK: u16 = 0x1000;
284    /// Mask of secondary_header bit in the decode function.
285    const SECONDARY_HEADER_MASK: u16 = 0x0800;
286    /// Mask of apid bits in the decode function.
287    const APID_MASK: u16 = 0x03FF;
288    /// Mask of [SequenceFlag] bits in the decode function.
289    const SEQUENCE_FLAG_MASK: u16 = 0xC000;
290    /// Mask of sequence_number bits in the decode function.
291    const SEQUENCE_NUMBER_MASK: u16 = 0x3FFF;
292
293    /// Encodes the [PrimaryHeader] into a vector of big endian bytes as described by CCSDS 133.0-B-2.
294    pub fn encode(&self) -> Vec<u8> {
295        let packet_id =
296            u16::from(self.version) << Self::VERSION_SHIFT |
297            self.packet_type.to_bits() << Self::PACKET_TYPE_SHIFT |
298            u16::from(self.secondary_header) << Self::SECONDARY_HEADER_SHIFT |
299            self.apid & Self::APID_MASK << Self::APID_SHIFT;
300
301        let sequence_ctl =
302            self.sequence_flag.to_bits() << Self::SEQUENCE_FLAG_SHIFT |
303            self.sequence_number & Self::SEQUENCE_NUMBER_MASK;
304
305        let mut encoded = Vec::new();
306        encoded.extend_from_slice(&u16::to_be_bytes(packet_id));
307        encoded.extend_from_slice(&u16::to_be_bytes(sequence_ctl));
308
309        encoded
310    }
311
312    /// Decodes the [PrimaryHeader] from a slice of bytes. Returns the result of the
313    /// operation, on success giving the decoded [PrimaryHeader].
314    ///
315    /// Decoding can fail for the following reasons:
316    /// - [Unsupported Space Packet Protocol version][Error::Unsupported] if the version number found in the primary header is something other then version 0.
317    /// - [Incomplete header][Error::IncompleteHeader] if the byte slice supplied as an argument is less than 4 bytes in length.
318    pub fn decode(buf: &[u8]) -> Result<Self, Error> {
319        let bytes = buf.get(0..Self::PRIMARY_HEADER_LEN).ok_or(Error::IncompleteHeader)?;
320
321        let packet_id = u16::from_be_bytes([bytes[0], bytes[1]]);
322        let sequence_ctl = u16::from_be_bytes([bytes[2], bytes[3]]);
323
324        let (version, packet_type, secondary_header, apid) = (
325            (packet_id >> Self::VERSION_SHIFT) as u8,
326            PacketType::from_bits((packet_id & Self::PACKET_TYPE_MASK) >> Self::PACKET_TYPE_SHIFT),
327            packet_id & Self::SECONDARY_HEADER_MASK != 0,
328            packet_id & Self::APID_MASK,
329        );
330
331        if version != Self::VERSION {
332            return Err(Error::Unsupported(version))
333        }
334
335        let (sequence_flag, sequence_number) = (
336            SequenceFlag::from_bits((sequence_ctl & Self::SEQUENCE_FLAG_MASK) >> Self::SEQUENCE_FLAG_SHIFT),
337            sequence_ctl & Self::SEQUENCE_NUMBER_MASK
338        );
339
340        Ok(Self {version, packet_type, secondary_header, apid, sequence_flag, sequence_number})
341    }
342}
343
344#[derive(Debug, Error, PartialEq)]
345/// Enum protraying various errors encountered during decoding of [PrimaryHeader] and
346/// [SpacePacket].
347pub enum Error {
348
349    /// Occurs when Space Packet Protocol version number is something other than 0. currently only
350    /// version 0 is supported by the ccsds-rs crate.
351    #[error("space packet protocol version {} not supported", .0)]    
352    Unsupported(u8),
353
354    /// Occurs when insufficient data is supplied to fully parse [PrimaryHeader]
355    #[error("incomplete primary header")]
356    IncompleteHeader,
357
358    /// Occurs when user data length field in Space Packet Protocol packet is greater than the
359    /// actual payload length.
360    #[error("insufficient data to complete decoding, found {}B but expected {}B", .found, .expected)]
361    InsufficientData{ expected: usize, found: usize },
362}
363
364
365#[cfg(test)]
366pub mod tests {
367    use super::*;
368    use rstest::rstest;
369
370    #[rstest]
371    fn test_spp_primary_header_codec(
372        #[values(PacketType::Telecommand, PacketType::Telemetry)]
373        packet_type: PacketType,
374        #[values(true, false)]
375        secondary_header: bool,
376        #[values(SequenceFlag::Continuation, SequenceFlag::Start, SequenceFlag::End, SequenceFlag::Unsegmented)]
377        sequence_flag: SequenceFlag,
378    ) {
379        let expected = PrimaryHeader {
380            version: PrimaryHeader::VERSION,
381            packet_type,
382            secondary_header,
383            apid: 0,
384            sequence_flag,
385            sequence_number: 0
386        };
387        let encoded = expected.encode();
388        let found = PrimaryHeader::decode(&encoded).unwrap();
389        assert_eq!(expected, found)
390    }
391
392    #[rstest]
393    #[case("Hello, World!".as_bytes().to_vec())]
394    #[case(vec![0])]
395    #[case(vec![0u8; u16::MAX as usize])]
396    fn test_test_spp_packet_codec(
397        #[values(PacketType::Telecommand, PacketType::Telemetry)]
398        packet_type: PacketType,
399        #[values(true, false)]
400        secondary_header: bool,
401        #[values(SequenceFlag::Continuation, SequenceFlag::Start, SequenceFlag::End, SequenceFlag::Unsegmented)]
402        sequence_flag: SequenceFlag,
403        #[case] payload: Vec<u8>,
404    ) {
405        let expected = SpacePacket::new(packet_type, secondary_header, 0, sequence_flag, 0, payload);
406        let encoded = expected.encode();
407        let found = SpacePacket::decode(&encoded).unwrap();
408        assert_eq!(expected.primary_header, found.primary_header);
409        assert_eq!(expected.payload, found.payload)
410    }
411
412    #[rstest]
413    #[should_panic]
414    fn test_empty_user_data() {
415        let expected = SpacePacket::new(PacketType::Telemetry, false, 0, SequenceFlag::Continuation, 0, vec![]);
416        let encoded = expected.encode();
417        let found = SpacePacket::decode(&encoded).unwrap();
418        assert_eq!(expected.primary_header, found.primary_header);
419        assert_eq!(expected.payload, found.payload)
420    }
421
422    #[rstest]
423    fn test_incomplete_header_err(
424        #[values(1, 2, 3, 4, 5)] header_len: usize
425    ) {
426        let forged_header_packet = vec![0u8; header_len];
427        assert_eq!(SpacePacket::decode(&forged_header_packet), Err(Error::IncompleteHeader))
428    }
429
430    #[rstest]
431    #[case(vec![1; 5])]
432    #[case(vec![1; 1])]
433    #[case(vec![1; 128])]
434    #[case(vec![1; 12048])]
435    #[case(vec![1; 60000])]
436    fn test_insufficient_data_err(#[case] payload: Vec<u8>) {
437        let mut packet = PrimaryHeader {
438            version: PrimaryHeader::VERSION,
439            packet_type: PacketType::Telecommand,
440            secondary_header: false,
441            apid: 0,
442            sequence_flag: SequenceFlag::End,
443            sequence_number: 0
444        }.encode();
445
446        let bad_payload_len = payload.len() as u16 + 5 - 1;
447        packet.extend_from_slice(&u16::to_be_bytes(bad_payload_len));
448        packet.extend_from_slice(&payload);
449
450        assert_eq!(SpacePacket::decode(&packet), Err(Error::InsufficientData { expected: (bad_payload_len + 1) as usize, found: payload.len()  }))
451    }
452
453
454    #[rstest]
455    fn test_unsupported_err(#[values(1, 2, 3, 4, 5, 6, 7)] version: u8) {
456        let mut packet = SpacePacket::new(
457            PacketType::Telemetry,
458            false,
459            0,
460            SequenceFlag::Continuation,
461            0,
462            vec![1]
463        );
464
465        packet.primary_header.version = version;
466
467        let encoded = packet.encode();
468
469        assert_eq!(SpacePacket::decode(&encoded), Err(Error::Unsupported(version)))
470    }
471}