Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds TC/TM support #2

Merged
merged 10 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## Unreleased

### Added
- TC Frame support (CCSDS 232.0-B-4)
- CLTU Generation only with BCH encoding (CCSDS 231.0-B-4)
- TM Frame support (CCSDS 132.0-B-3)
- Randomization support (CCSDS 131.0-B-5, CCSDS 231.0-B-4)

## v0.1.0

### Added
Expand Down
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
edition = "2021"
rust-version = "1.63"
license-file = "LICENSE"
license = "MIT"
categories = [ "aerospace::space-protocols" ]
keywords = [ "ccsds", "bytes", "parser", "packets" ]
exclude = [ ".github" ]
Expand All @@ -18,6 +19,7 @@
async-codec = [ "asynchronous-codec", "bytes" ]
tokio-codec = [ "bytes", "tokio-util/codec" ]
crc = [ "dep:crc" ]
tctm = [ "dep:lazy_static" ]

# docs.rs-specific configuration
[package.metadata.docs.rs]
Expand All @@ -31,9 +33,11 @@
byteorder = "~1.4"
bytes = { version = "~1.4", optional = true }
crc = { version = "3.0", optional = true }
lazy_static = { version = "1.5.0", optional = true }
tokio-util = { version = "~0.7", optional = true, features = [ "codec" ] }


[dev-dependencies]
rstest = "~0.15"
futures = "~0.3"
rstest = "~0.15"
futures = "~0.3"
spacepacket = { path = ".", features = [ "async-codec", "crc", "tctm" ] }
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The full packet is parsed into a SpacePacket struct, all overhead for the payloa

This crate was created after viewing other rust-based CCSDS crates and wanting a more friendly user interface for packet interaction.

Currently this crate assumes Big Endian for all byte streams. Though this may change to be generic over endian in the future.
Currently this crate assumes Big Endian for all byte streams. Though this may change to be generic over endianness in the future.


## Optional Features
Expand All @@ -17,6 +17,12 @@ This crate provides data validation via CRC-16 calculation through the [crc crat
#### Sink/Stream Support
Another optional feature this crate provides is support for for sapcepacket I/O via sinks and stream through the async-codec and tokio-codec features.
This allows users to easily create asynchronous listeners for spacepackets with optional sync markers and CRC support.
#### TC/TM Support and CLTU Generation
TeleComamand (TC) and Telemetry (TM) Frames are supported when the `tctm` feature is enabled.

Communications Link Transmission Unit (CLTU) packets can also be constructed
when this feature is enabled. Currently only BCH and Randomized BCH
encoding is supported however LDPC encoding is planned as a future enhancement.


# Examples
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ use byteorder::{BigEndian, ReadBytesExt};
#[cfg_attr(docsrs, doc(cfg(feature = "crc")))]
use crc::Crc;

#[cfg(feature = "tctm")]
#[cfg_attr(docsrs, doc(cfg(feature = "tctm")))]
pub mod tctm;

#[cfg(feature = "crc")]
use std::fmt::Display;

Expand Down
7 changes: 7 additions & 0 deletions src/tctm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//! TeleCommand (TC; CCSDS 231.0-B-4 )
//! and Telemetry (TM; CCSDS 132.0-B-3 ) Transfer Frame
//! definitions, en/de-coding.
pub mod cltu;
pub(crate) mod randomizer;
pub mod tc;
pub mod tm;
104 changes: 104 additions & 0 deletions src/tctm/cltu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//! Generate Communications Link Transmission Unit (CLTU) packets
//! as defined in CCSDS 231.0-B-4

use crate::tctm::randomizer::{apply_randomization, Randomization};

mod bch;

#[derive(Debug, Clone, Copy)]
/// Possible CCSDS 231.0-B-4 CLTU encoding types
pub enum EncodingScheme {
/// A modified (63, 56) Bose-Chaudhuri-Hocquenghem code.
/// Generates 7 parity bits for every 56 data bits
BCH,
/// The same as the [Self::BCH] but with CCSDS 231.0-B-4
/// randomized applied to the TC frame before BCH encoding.
BCHRandomized,
}

/// Generates a Communications Link Transmission Unit (CLTU) from an input
/// byte stream using the chosen encoding scheme.
pub fn generate_ctlu<P: AsRef<[u8]>>(bytes: P, encoding: EncodingScheme) -> Vec<u8> {
let bytes = bytes.as_ref();
match encoding {
EncodingScheme::BCH => bch::encode_bch_ctlu(bytes),
EncodingScheme::BCHRandomized => {
bch::encode_bch_ctlu(apply_randomization(bytes, Randomization::TC).as_slice())
}
}
}

#[cfg(test)]
mod test {
use super::*;

use rstest::rstest;

// test values derived from https://github.com/yamcs/yamcs/blob/78b9553caf3c9f7ef7a6e6897d236a69aeed8190/yamcs-core/src/test/java/org/yamcs/tctm/ccsds/error/BchCltuGeneratorTest.java
// and by extension from SpacePyLibrary
// https://github.com/Stefan-Korner/SpacePyLibrary/blob/master/UnitTest/testData.py
const TC_FRAME_01: &[u8] = &[
0x22, 0xF6, 0x00, 0xFF, 0x00, 0x42, 0x1A, 0x8C, 0xC0, 0x0E, 0x01, 0x0D, 0x19, 0x06, 0x02,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x0F, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x02, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x0F, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x04, 0xFF, 0x00,
0x00, 0x00, 0x00, 0x0F, 0x00, 0x05, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x06, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x07, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x08,
0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x09, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00,
0x0A, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x0B, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F,
0x00, 0x0C, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x0D, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x0F, 0x00, 0x0E, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x0F, 0x00, 0x10, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x11, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x0F, 0x00, 0x12, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x13, 0xFF, 0x00,
0x00, 0x00, 0x00, 0x0F, 0x00, 0x14, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x15, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x16, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x17,
0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x18, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00,
0x19, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x1A, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F,
0x00, 0x1B, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x1C, 0xFF, 0x00, 0x00, 0x00, 0xAD,
0x1A,
];

const CLTU_01: &[u8] = &[
0xEB, 0x90, 0x22, 0xF6, 0x00, 0xFF, 0x00, 0x42, 0x1A, 0x12, 0x8C, 0xC0, 0x0E, 0x01, 0x0D,
0x19, 0x06, 0x5A, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x8A, 0x00, 0x01, 0x00, 0x00,
0x00, 0xFF, 0x00, 0xCC, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x01, 0xFF, 0x28, 0x00, 0x00, 0x00,
0x00, 0x0F, 0x00, 0x02, 0x5A, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x92, 0x03, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x0F, 0xD6, 0x00, 0x04, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xD4, 0x0F,
0x00, 0x05, 0xFF, 0x00, 0x00, 0x00, 0xA8, 0x00, 0x0F, 0x00, 0x06, 0xFF, 0x00, 0x00, 0xC8,
0x00, 0x00, 0x0F, 0x00, 0x07, 0xFF, 0x00, 0xCA, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x08, 0xFF,
0x66, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x09, 0xA8, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F,
0x00, 0x92, 0x0A, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF4, 0x00, 0x0B, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x5A, 0x0F, 0x00, 0x0C, 0xFF, 0x00, 0x00, 0x00, 0xB0, 0x00, 0x0F, 0x00, 0x0D,
0xFF, 0x00, 0x00, 0xD8, 0x00, 0x00, 0x0F, 0x00, 0x0E, 0xFF, 0x00, 0x96, 0x00, 0x00, 0x00,
0x0F, 0x00, 0x0F, 0xFF, 0xDA, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x10, 0x82, 0xFF, 0x00,
0x00, 0x00, 0x00, 0x0F, 0x00, 0x92, 0x11, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x92, 0x00,
0x12, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x0F, 0x00, 0x13, 0xFF, 0x00, 0x00, 0x00, 0x24,
0x00, 0x0F, 0x00, 0x14, 0xFF, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x0F, 0x00, 0x15, 0xFF, 0x00,
0x72, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x16, 0xFF, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00,
0x17, 0x20, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x92, 0x18, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x0F, 0xB0, 0x00, 0x19, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x90, 0x0F, 0x00, 0x1A, 0xFF,
0x00, 0x00, 0x00, 0x3C, 0x00, 0x0F, 0x00, 0x1B, 0xFF, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x0F,
0x00, 0x1C, 0xFF, 0x00, 0x2E, 0x00, 0x00, 0xAD, 0x1A, 0x55, 0x55, 0x55, 0xEC, 0xC5, 0xC5,
0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0x79,
];

const TC_FRAME_02: &[u8] = &[
0x22, 0xF6, 0x00, 0x23, 0x00, 0x82, 0x00, 0x0F, 0x00, 0x1D, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x0F, 0x00, 0x1E, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x1F, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x0F, 0xAC, 0x8F, 0x00, 0x68,
];

const CLTU_02: &[u8] = &[
0xEB, 0x90, 0x22, 0xF6, 0x00, 0x23, 0x00, 0x82, 0x00, 0x24, 0x0F, 0x00, 0x1D, 0xFF, 0x00,
0x00, 0x00, 0x34, 0x00, 0x0F, 0x00, 0x1E, 0xFF, 0x00, 0x00, 0x10, 0x00, 0x00, 0x0F, 0x00,
0x1F, 0xFF, 0x00, 0xD8, 0x00, 0x00, 0x00, 0x0F, 0xAC, 0x8F, 0x00, 0x90, 0x68, 0x55, 0x55,
0x55, 0x55, 0x55, 0x55, 0x06, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0x79,
];

#[rstest]
#[case(TC_FRAME_01, CLTU_01)]
#[case(TC_FRAME_02, CLTU_02)]
fn cltu_gen(#[case] tc_frame: &[u8], #[case] cltu: &[u8]) {
assert_eq!(cltu, generate_ctlu(tc_frame, EncodingScheme::BCH))
}
}
82 changes: 82 additions & 0 deletions src/tctm/cltu/bch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use lazy_static::lazy_static;
/// CCSDS BCH polynomial x^7 + x^6 + x^2 + 1
/// is then left shifted 1 bit
const CCSDS_POLYNOMIAL: u8 = 0x8A_u8;
const START_SEQUNCE: &[u8] = &[0xEB, 0x90];
const TAIL_SEQUENCE: &[u8] = &[0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0x79];

lazy_static! {
static ref LOOKUP_TALBE: [u8; 256] = (0_u8..=255)
.map(|val| {
(0..8_u8).fold(val, |val, _| {
if val & 0x80 == 0 {
val << 1
} else {
(val << 1) ^ CCSDS_POLYNOMIAL
}
})
})
.collect::<Vec<_>>()
.try_into()
.unwrap();
}

/// Compute BCH codeword as defined in CCSDS 232.0-B-4 with polynomial
/// polynomial x^7 + x^6 + x^2 + 1
pub fn compute_bch_parity(bytes: &[u8; 7]) -> u8 {
// bch encoding takes 7 byte chunks of data then computes 1 parity byte

let mut remainder = bytes
.iter()
.fold(0, |acc, val| LOOKUP_TALBE[(val ^ acc) as usize]);
// logical complement of the remainder
remainder ^= 0xFF;
// force the 0th byte to be 0 since there are only 7 parity bits.
remainder &= 0xFE;
remainder
}

pub(crate) fn encode_bch_ctlu(bytes: &[u8]) -> Vec<u8> {
let mut output = START_SEQUNCE.to_vec();

let mut iter = bytes.chunks_exact(7);

(&mut iter).for_each(|chunk| {
output.extend_from_slice(chunk);
// unwraping is safe here because we have forced chunks of length 7
output.push(compute_bch_parity(chunk.try_into().unwrap()));
});

// handle any remainder by resizing to 7-bytes chunk
if !iter.remainder().is_empty() {
let mut remainder = iter.remainder().to_vec();
// pad with bits of alternating 0 and 1s starting with 0
remainder.resize(7, 0x55_u8);
output.extend_from_slice(&remainder);
// unwraping is safe here because we have forced a length of 7
output.push(compute_bch_parity(remainder.as_slice().try_into().unwrap()));
}
output.extend_from_slice(TAIL_SEQUENCE);

output
}

#[cfg(test)]
mod test {
use super::*;

use rstest::rstest;

// test values derived from https://github.com/yamcs/yamcs/blob/78b9553caf3c9f7ef7a6e6897d236a69aeed8190/yamcs-core/src/test/java/org/yamcs/tctm/ccsds/error/BchCltuGeneratorTest.java
// and by extension from SpacePyLibrary
// https://github.com/Stefan-Korner/SpacePyLibrary/blob/master/UnitTest/testData.py

#[rstest]
#[case([0x22, 0xF6, 0x00, 0xFF, 0x00, 0x42, 0x1A], 0x12)]
#[case([0x8C, 0xC0, 0x0E, 0x01, 0x0D, 0x19, 0x06], 0x5A)]
#[case([0x30, 0x1B, 0x00, 0x09, 0x00, 0x82, 0x00], 0x54)]
#[case([0x10, 0xE4, 0xC1, 0x55, 0x55, 0x55, 0x55], 0x3E)]
fn bch_encoding(#[case] input: [u8; 7], #[case] parity: u8) {
assert_eq!(parity, compute_bch_parity(&input))
}
}
Loading
Loading