Massive restructuring and refactor

Although functional changes are limited, I've massively refactored and restructured the codebase into several separate crates.
- tetra-core features functionality and data that are used throughout the stack
- tetra-saps contains definitions for all SAP message types
- tetra-config contains everything that has to do with toml-based config file parsing and conversion to the internally-used datastructures
- tetra-pdus features entity-related PDU definitions, including field struct types and enums
- tetra-entities features implementations for TETRA entities, split between BS and MS use cases. It also holds subcomponents like schedulers and decoding tools.

A separate bins folder is made, which may hold any current and future binary targets we want to compile. Right now we have:
- tetra-bluestation-bs, which compiles the core base station binary
- pdu-tool, which is a crude, buggy, ad hoc tool to parse certain pdu types for stack debugging purposes
This commit is contained in:
Wouter Bokslag
2026-01-17 15:24:52 +01:00
parent ad4402352b
commit c9c20a9abe
273 changed files with 2896 additions and 2143 deletions

78
Cargo.lock generated
View File

@@ -99,6 +99,20 @@ version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "bluestation-bs"
version = "0.3.1"
dependencies = [
"clap",
"tetra-config",
"tetra-core",
"tetra-entities",
"tetra-saps",
"tracing",
"tracing-appender",
"tracing-subscriber",
]
[[package]]
name = "cc"
version = "1.2.51"
@@ -434,6 +448,20 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "pdu-tool"
version = "0.3.1"
dependencies = [
"clap",
"tetra-config",
"tetra-core",
"tetra-pdus",
"tetra-saps",
"tracing",
"tracing-appender",
"tracing-subscriber",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
@@ -684,22 +712,58 @@ dependencies = [
]
[[package]]
name = "tetra-bluestation"
version = "0.3.0"
name = "tetra-config"
version = "0.3.1"
dependencies = [
"serde",
"tetra-core",
"toml",
]
[[package]]
name = "tetra-core"
version = "0.3.1"
dependencies = [
"serde",
"tracing",
"tracing-appender",
"tracing-subscriber",
]
[[package]]
name = "tetra-entities"
version = "0.3.1"
dependencies = [
"as-any",
"clap",
"crossbeam-channel",
"num",
"num-complex",
"rand",
"rustfft",
"serde",
"soapysdr",
"toml",
"tetra-config",
"tetra-core",
"tetra-pdus",
"tetra-saps",
"tracing",
]
[[package]]
name = "tetra-pdus"
version = "0.3.1"
dependencies = [
"tetra-config",
"tetra-core",
"tetra-saps",
"tracing",
]
[[package]]
name = "tetra-saps"
version = "0.3.1"
dependencies = [
"tetra-core",
"tracing",
"tracing-appender",
"tracing-subscriber",
]
[[package]]

View File

@@ -1,17 +1,34 @@
[package]
name = "tetra-bluestation"
version = "0.3.0"
[workspace]
resolver = "2"
members = [
# Core libraries
"crates/tetra-core",
"crates/tetra-saps",
"crates/tetra-config",
"crates/tetra-pdus",
"crates/tetra-entities",
# Binaries
"bins/bluestation-bs",
"bins/pdu-tool",
]
[workspace.package]
version = "0.3.1"
edition = "2024"
authors = ["Wouter Bokslag / Midnight Blue"]
license = "MIT"
[[bin]]
name = "tetra-bluestation"
path = "src/main.rs"
[workspace.dependencies]
# Internal crates
tetra-core = { path = "crates/tetra-core" }
tetra-config = { path = "crates/tetra-config" }
tetra-saps = { path = "crates/tetra-saps" }
tetra-pdus = { path = "crates/tetra-pdus" }
tetra-entities = { path = "crates/tetra-entities" }
[[bin]]
name = "tool"
path = "src/tool.rs"
[dependencies]
# External dependencies
as-any = "0.3.2"
num = "0.4.3"
num-complex = "0.4.6"

View File

@@ -0,0 +1,19 @@
[package]
name = "bluestation-bs"
version.workspace = true
edition.workspace = true
[[bin]]
name = "tetra-bluestation"
path = "src/main.rs"
[dependencies]
tetra-core = { workspace = true }
tetra-config = { workspace = true }
tetra-saps = { workspace = true }
tetra-entities = { workspace = true }
clap = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
tracing-appender = { workspace = true }

View File

@@ -1,29 +1,10 @@
#![allow(dead_code)]
mod config;
mod common;
mod entities;
mod saps;
#[cfg(test)]
mod testing;
use clap::Parser;
use common::debug::setup_logging_default;
use common::tdma_time::TdmaTime;
use common::messagerouter::MessageRouter;
use config::stack_config::*;
use config::toml_config;
use crate::entities::cmce::cmce_bs::CmceBs;
use crate::entities::mle::mle_bs_ms::Mle;
use crate::entities::phy::components::soapy_dev::RxTxDevSoapySdr;
use crate::entities::sndcp::sndcp_bs::Sndcp;
use crate::entities::lmac::lmac_bs::LmacBs;
use crate::entities::mm::mm_bs::MmBs;
use crate::entities::phy::phy_bs::PhyBs;
use crate::entities::llc::llc_bs_ms::Llc;
use crate::entities::umac::umac_bs::UmacBs;
use tetra_config::{PhyBackend, SharedConfig, StackMode, toml_config};
use tetra_core::{TdmaTime, debug};
use tetra_entities::{cmce::cmce_bs::CmceBs, llc::llc_bs_ms::Llc, lmac::lmac_bs::LmacBs, mle::mle_bs_ms::Mle, mm::mm_bs::MmBs, phy::{components::soapy_dev::RxTxDevSoapySdr, phy_bs::PhyBs}, sndcp::sndcp_bs::Sndcp, umac::umac_bs::UmacBs};
use tetra_entities::MessageRouter;
/// Load configuration file
fn load_config_from_toml(cfg_path: &str) -> SharedConfig {
@@ -104,7 +85,7 @@ fn main() {
let args = Args::parse();
let mut cfg = load_config_from_toml(&args.config);
let _log_guard = setup_logging_default(cfg.config().debug_log.clone());
let _log_guard = debug::setup_logging_default(cfg.config().debug_log.clone());
let mut router = match cfg.config().stack_mode {
StackMode::Mon => {

19
bins/pdu-tool/Cargo.toml Normal file
View File

@@ -0,0 +1,19 @@
[package]
name = "pdu-tool"
version.workspace = true
edition.workspace = true
[[bin]]
name = "pdu-tool"
path = "src/main.rs"
[dependencies]
tetra-core = { workspace = true }
tetra-config = { workspace = true }
tetra-saps = { workspace = true }
tetra-pdus = { workspace = true }
clap = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
tracing-appender = { workspace = true }

View File

@@ -0,0 +1 @@
pub mod umac;

View File

@@ -0,0 +1,698 @@
use tetra_core::BitBuffer;
use tetra_saps::tmv::enums::logical_chans::LogicalChannel;
use tetra_pdus::umac::{
enums::{
broadcast_type::BroadcastType,
mac_pdu_type::MacPduType,
},
pdus::{
// Uplink PDUs
mac_access::MacAccess,
mac_data::MacData,
mac_end_hu::MacEndHu,
mac_end_ul::MacEndUl,
mac_frag_ul::MacFragUl,
mac_u_blck::MacUBlck,
mac_u_signal::MacUSignal,
// Downlink PDUs
mac_resource::MacResource,
mac_end_dl::MacEndDl,
mac_frag_dl::MacFragDl,
mac_d_blck::MacDBlck,
mac_sysinfo::MacSysinfo,
mac_sync::MacSync,
access_define::AccessDefine,
},
};
/// Result of length_ind interpretation
#[derive(Debug)]
pub struct LengthIndInfo {
/// PDU payload length in bits (0 for null PDU or fragmentation)
pub pdu_len_bits: usize,
/// Whether this is a null PDU
pub is_null_pdu: bool,
/// Whether this is fragmentation start (no length known)
pub is_frag_start: bool,
/// Whether second half slot is stolen (STCH)
pub second_half_stolen: bool,
}
/// UMAC parser for standalone PDU debugging
pub struct UmacParser;
impl UmacParser {
/// Parse an uplink MAC PDU and print the result
/// Follows the structure of UmacBs::rx_tmv_unitdata_ind and rx_tmv_sch
pub fn parse_ul(mut pdu: BitBuffer, logical_channel: LogicalChannel) {
println!("=== UMAC UL Parser ===");
println!("Logical channel: {:?}", logical_channel);
println!("Input bits: {}", pdu.dump_bin());
println!();
// Iterate until no more messages left in mac block
loop {
let Some(bits) = pdu.peek_bits(3) else {
println!("[!] Insufficient bits remaining: {}", pdu.dump_bin());
return;
};
let orig_start = pdu.get_raw_start();
// Clause 21.4.1; handling differs between SCH_HU and others
match logical_channel {
LogicalChannel::SchF | LogicalChannel::Stch => {
// First two bits are MAC PDU type
let Ok(pdu_type) = MacPduType::try_from(bits >> 1) else {
println!("[!] Invalid PDU type: {}", bits >> 1);
return;
};
println!("MAC PDU Type: {:?} ({})", pdu_type, bits >> 1);
match pdu_type {
MacPduType::MacResourceMacData => {
// On uplink this is MAC-DATA
Self::parse_mac_data(&mut pdu);
}
MacPduType::MacFragMacEnd => {
// Third bit distinguishes mac-frag (0) from mac-end (1)
if bits & 1 == 0 {
Self::parse_mac_frag_ul(&mut pdu);
} else {
Self::parse_mac_end_ul(&mut pdu);
}
}
MacPduType::SuppMacUSignal => {
// STCH determines which subtype is relevant
if logical_channel == LogicalChannel::Stch {
Self::parse_mac_u_signal(&mut pdu);
} else {
// Supplementary MAC PDU - third bit distinguishes
if bits & 1 == 0 {
Self::parse_mac_u_blck(&mut pdu);
} else {
println!("[!] Unexpected supplementary PDU subtype");
return;
}
}
}
MacPduType::Broadcast => {
println!("[!] Broadcast PDU not expected on uplink");
return;
}
}
}
LogicalChannel::SchHu => {
// Only 1 bit needed for subtype distinction on SCH/HU
let pdu_type = (bits >> 2) & 1;
println!("SCH/HU PDU Type: {} ({})",
if pdu_type == 0 { "MAC-ACCESS" } else { "MAC-END-HU" },
pdu_type);
match pdu_type {
0 => Self::parse_mac_access(&mut pdu),
1 => Self::parse_mac_end_hu(&mut pdu),
_ => unreachable!()
}
}
_ => {
println!("[!] Unknown/unsupported logical channel for UL: {:?}", logical_channel);
return;
}
}
// Check if more PDUs remain in MAC block
if !Self::check_continue(&pdu, orig_start) {
break;
}
}
}
/// Parse a downlink MAC PDU and print the result
/// Follows the structure of UmacMs::rx_tmv_unitdata_ind and rx_tmv_sch
pub fn parse_dl(mut pdu: BitBuffer, logical_channel: LogicalChannel) {
println!("=== UMAC DL Parser ===");
println!("Logical channel: {:?}", logical_channel);
println!("Input bits: {}", pdu.dump_bin());
println!();
// Handle special channels first
match logical_channel {
LogicalChannel::Aach => {
println!("--- AACH (Access Assignment Channel) ---");
println!("[!] AACH parsing not implemented in standalone tool");
return;
}
LogicalChannel::Bsch => {
Self::parse_mac_sync(&mut pdu);
return;
}
_ => {}
}
assert!(logical_channel == LogicalChannel::SchF
|| logical_channel == LogicalChannel::SchHd,
"Unsupported logical channel for DL: {:?}", logical_channel);
// Iterate until no more messages left in mac block
loop {
let Some(bits) = pdu.peek_bits(3) else {
println!("[!] Insufficient bits remaining: {}", pdu.dump_bin());
return;
};
let orig_start = pdu.get_raw_start();
// First two bits are MAC PDU type
let Ok(pdu_type) = MacPduType::try_from(bits >> 1) else {
println!("[!] Invalid PDU type: {}", bits >> 1);
return;
};
println!("MAC PDU Type: {:?} ({})", pdu_type, bits >> 1);
match pdu_type {
MacPduType::MacResourceMacData => {
// On downlink this is MAC-RESOURCE
Self::parse_mac_resource(&mut pdu);
}
MacPduType::MacFragMacEnd => {
// Third bit distinguishes mac-frag (0) from mac-end (1)
if bits & 1 == 0 {
Self::parse_mac_frag_dl(&mut pdu);
} else {
Self::parse_mac_end_dl(&mut pdu);
}
}
MacPduType::Broadcast => {
Self::parse_broadcast(&mut pdu);
}
MacPduType::SuppMacUSignal => {
if logical_channel == LogicalChannel::Stch {
// U-SIGNAL on stealing channel
Self::parse_mac_u_signal(&mut pdu);
} else {
// Supplementary PDU - third bit distinguishes
if bits & 1 == 0 {
Self::parse_mac_d_blck(&mut pdu);
} else {
println!("[!] Unexpected supplementary PDU subtype on DL");
return;
}
}
}
}
// Check if more PDUs remain in MAC block
if !Self::check_continue(&pdu, orig_start) {
break;
}
}
}
fn parse_mac_data(pdu: &mut BitBuffer) {
println!("--- Parsing MAC-DATA ---");
match MacData::from_bitbuf(pdu) {
Ok(mac_data) => {
println!("{:#?}", mac_data);
// Print SDU preview if we have a length
if let Some(len) = mac_data.length_ind {
let info = Self::interpret_length_ind(len, pdu.get_len_remaining());
if !info.is_null_pdu && !info.is_frag_start && !info.second_half_stolen && info.pdu_len_bits > 0 {
Self::print_sdu(pdu, info.pdu_len_bits.min(pdu.get_len_remaining()), "TM-SDU");
} else if info.is_frag_start {
println!("Fragment data: {} bits remaining", pdu.get_len_remaining());
}
}
// Apply PDU association
Self::apply_pdu_association(pdu, mac_data.length_ind, mac_data.fill_bits);
}
Err(e) => println!("[!] Failed to parse MAC-DATA: {:?}", e),
}
}
fn parse_mac_access(pdu: &mut BitBuffer) {
println!("--- Parsing MAC-ACCESS ---");
match MacAccess::from_bitbuf(pdu) {
Ok(mac_access) => {
println!("{:#?}", mac_access);
// Print SDU preview if we have a length
if let Some(len) = mac_access.length_ind {
let info = Self::interpret_length_ind(len, pdu.get_len_remaining());
if !info.is_null_pdu && !info.is_frag_start && !info.second_half_stolen && info.pdu_len_bits > 0 {
Self::print_sdu(pdu, info.pdu_len_bits.min(pdu.get_len_remaining()), "TM-SDU");
} else if info.is_frag_start {
println!("Fragment data: {} bits remaining", pdu.get_len_remaining());
}
}
// Apply PDU association
Self::apply_pdu_association(pdu, mac_access.length_ind, mac_access.fill_bits);
}
Err(e) => println!("[!] Failed to parse MAC-ACCESS: {:?}", e),
}
}
fn parse_mac_frag_ul(pdu: &mut BitBuffer) {
println!("--- Parsing MAC-FRAG (UL) ---");
match MacFragUl::from_bitbuf(pdu) {
Ok(mac_frag) => {
println!("{:#?}", mac_frag);
let remaining = pdu.get_len_remaining();
println!("TM-SDU fragment: {} bits remaining", remaining);
if remaining > 0 && remaining <= 64 {
if let Some(frag_bits) = pdu.peek_bits(remaining) {
println!("Fragment data: {:0width$b}", frag_bits, width = remaining);
}
}
println!("BitBuffer: {}", pdu.dump_bin());
}
Err(e) => println!("[!] Failed to parse MAC-FRAG: {:?}", e),
}
}
fn parse_mac_end_ul(pdu: &mut BitBuffer) {
println!("--- Parsing MAC-END (UL) ---");
match MacEndUl::from_bitbuf(pdu) {
Ok(mac_end) => {
println!("{:#?}", mac_end);
// Print SDU preview if we have a length
if let Some(len) = mac_end.length_ind {
let info = Self::interpret_length_ind(len, pdu.get_len_remaining());
if !info.is_null_pdu && !info.is_frag_start && !info.second_half_stolen && info.pdu_len_bits > 0 {
Self::print_sdu(pdu, info.pdu_len_bits.min(pdu.get_len_remaining()), "TM-SDU");
} else if info.is_frag_start {
println!("Fragment data: {} bits remaining", pdu.get_len_remaining());
}
}
// Apply PDU association
Self::apply_pdu_association(pdu, mac_end.length_ind, mac_end.fill_bits);
}
Err(e) => println!("[!] Failed to parse MAC-END: {:?}", e),
}
}
fn parse_mac_end_hu(pdu: &mut BitBuffer) {
println!("--- Parsing MAC-END-HU ---");
match MacEndHu::from_bitbuf(pdu) {
Ok(mac_end) => {
println!("{:#?}", mac_end);
// Print SDU preview if we have a length
if let Some(len) = mac_end.length_ind {
let info = Self::interpret_length_ind(len, pdu.get_len_remaining());
if !info.is_null_pdu && !info.is_frag_start && !info.second_half_stolen && info.pdu_len_bits > 0 {
Self::print_sdu(pdu, info.pdu_len_bits.min(pdu.get_len_remaining()), "TM-SDU");
} else if info.is_frag_start {
println!("Fragment data: {} bits remaining", pdu.get_len_remaining());
}
}
// Apply PDU association
Self::apply_pdu_association(pdu, mac_end.length_ind, mac_end.fill_bits);
}
Err(e) => println!("[!] Failed to parse MAC-END-HU: {:?}", e),
}
}
fn parse_mac_u_blck(pdu: &mut BitBuffer) {
println!("--- Parsing MAC-U-BLCK ---");
match MacUBlck::from_bitbuf(pdu) {
Ok(mac_u_blck) => {
println!("{:#?}", mac_u_blck);
let remaining = pdu.get_len_remaining();
println!("TM-SDU: {} bits remaining", remaining);
println!("BitBuffer: {}", pdu.dump_bin());
}
Err(e) => println!("[!] Failed to parse MAC-U-BLCK: {:?}", e),
}
}
fn parse_mac_u_signal(pdu: &mut BitBuffer) {
println!("--- Parsing MAC-U-SIGNAL ---");
match MacUSignal::from_bitbuf(pdu) {
Ok(mac_u_signal) => {
println!("{:#?}", mac_u_signal);
println!("BitBuffer: {}", pdu.dump_bin());
}
Err(e) => println!("[!] Failed to parse MAC-U-SIGNAL: {:?}", e),
}
}
// ========================================================================
// DOWNLINK PDU PARSERS
// ========================================================================
fn parse_mac_resource(pdu: &mut BitBuffer) {
println!("--- Parsing MAC-RESOURCE ---");
match MacResource::from_bitbuf(pdu) {
Ok(mac_res) => {
println!("{:#?}", mac_res);
// Print SDU preview
let info = Self::interpret_length_ind(mac_res.length_ind, pdu.get_len_remaining());
if !info.is_null_pdu && !info.is_frag_start && !info.second_half_stolen && info.pdu_len_bits > 0 {
Self::print_sdu(pdu, info.pdu_len_bits.min(pdu.get_len_remaining()), "TM-SDU");
} else if info.is_frag_start {
let remaining = pdu.get_len_remaining();
println!("Fragment data: {} bits remaining", remaining);
}
// Apply PDU association
Self::apply_pdu_association(pdu, Some(mac_res.length_ind), mac_res.fill_bits);
}
Err(e) => println!("[!] Failed to parse MAC-RESOURCE: {:?}", e),
}
}
fn parse_mac_frag_dl(pdu: &mut BitBuffer) {
println!("--- Parsing MAC-FRAG (DL) ---");
match MacFragDl::from_bitbuf(pdu) {
Ok(mac_frag) => {
println!("{:#?}", mac_frag);
let remaining = pdu.get_len_remaining();
println!("TM-SDU fragment: {} bits remaining", remaining);
println!("BitBuffer: {}", pdu.dump_bin());
}
Err(e) => println!("[!] Failed to parse MAC-FRAG (DL): {:?}", e),
}
}
fn parse_mac_end_dl(pdu: &mut BitBuffer) {
println!("--- Parsing MAC-END (DL) ---");
match MacEndDl::from_bitbuf(pdu) {
Ok(mac_end) => {
println!("{:#?}", mac_end);
// Print SDU preview
let info = Self::interpret_length_ind(mac_end.length_ind, pdu.get_len_remaining());
if !info.is_null_pdu && !info.is_frag_start && !info.second_half_stolen && info.pdu_len_bits > 0 {
Self::print_sdu(pdu, info.pdu_len_bits.min(pdu.get_len_remaining()), "TM-SDU");
} else if info.is_frag_start {
println!("Fragment data: {} bits remaining", pdu.get_len_remaining());
}
// Apply PDU association
Self::apply_pdu_association(pdu, Some(mac_end.length_ind), mac_end.fill_bits);
}
Err(e) => println!("[!] Failed to parse MAC-END (DL): {:?}", e),
}
}
fn parse_mac_d_blck(pdu: &mut BitBuffer) {
println!("--- Parsing MAC-D-BLCK ---");
match MacDBlck::from_bitbuf(pdu) {
Ok(mac_d_blck) => {
println!("{:#?}", mac_d_blck);
let remaining = pdu.get_len_remaining();
println!("TM-SDU: {} bits remaining", remaining);
println!("BitBuffer: {}", pdu.dump_bin());
}
Err(e) => println!("[!] Failed to parse MAC-D-BLCK: {:?}", e),
}
}
// ========================================================================
// BROADCAST PDU PARSERS
// ========================================================================
fn parse_broadcast(pdu: &mut BitBuffer) {
// Peek broadcast type (bits 2-3 after MAC PDU type)
let Some(bits) = pdu.peek_bits_posoffset(2, 2) else {
println!("[!] Insufficient bits for broadcast type");
return;
};
let Ok(bcast_type) = BroadcastType::try_from(bits) else {
println!("[!] Invalid broadcast type: {}", bits);
return;
};
println!("Broadcast Type: {:?}", bcast_type);
match bcast_type {
BroadcastType::Sysinfo => Self::parse_mac_sysinfo(pdu),
BroadcastType::AccessDefine => Self::parse_access_define(pdu),
BroadcastType::SysinfoDa => {
println!("[!] SYSINFO-DA parsing not implemented");
}
}
}
fn parse_mac_sync(pdu: &mut BitBuffer) {
println!("--- Parsing MAC-SYNC (BSCH) ---");
match MacSync::from_bitbuf(pdu) {
Ok(mac_sync) => {
println!("{:#?}", mac_sync);
println!("BitBuffer: {}", pdu.dump_bin());
}
Err(e) => println!("[!] Failed to parse MAC-SYNC: {:?}", e),
}
}
fn parse_mac_sysinfo(pdu: &mut BitBuffer) {
println!("--- Parsing MAC-SYSINFO ---");
match MacSysinfo::from_bitbuf(pdu) {
Ok(mac_sysinfo) => {
println!("{:#?}", mac_sysinfo);
let remaining = pdu.get_len_remaining();
if remaining > 0 {
println!("MLE PDU follows: {} bits remaining", remaining);
}
println!("BitBuffer: {}", pdu.dump_bin());
}
Err(e) => println!("[!] Failed to parse MAC-SYSINFO: {:?}", e),
}
}
fn parse_access_define(pdu: &mut BitBuffer) {
println!("--- Parsing ACCESS-DEFINE ---");
match AccessDefine::from_bitbuf(pdu) {
Ok(access_def) => {
println!("{:#?}", access_def);
println!("BitBuffer: {}", pdu.dump_bin());
}
Err(e) => println!("[!] Failed to parse ACCESS-DEFINE: {:?}", e),
}
}
// ========================================================================
// HELPER FUNCTIONS
// ========================================================================
/// Interpret length_ind value according to ETSI EN 300 392-2 clause 21.4.3.1
pub fn interpret_length_ind(length_ind: u8, remaining_bits: usize) -> LengthIndInfo {
match length_ind {
0b000000 => {
// Null PDU
LengthIndInfo {
pdu_len_bits: 0,
is_null_pdu: true,
is_frag_start: false,
second_half_stolen: false,
}
}
0b000001 => {
// Reserved
println!("[!] Reserved length_ind value: 1");
LengthIndInfo {
pdu_len_bits: 0,
is_null_pdu: false,
is_frag_start: false,
second_half_stolen: false,
}
}
0b000010..=0b111001 => {
// Valid length: 2-57 octets (16-456 bits)
let len_bits = length_ind as usize * 8;
LengthIndInfo {
pdu_len_bits: len_bits.min(remaining_bits),
is_null_pdu: false,
is_frag_start: false,
second_half_stolen: false,
}
}
0b111010..=0b111101 => {
// Reserved
println!("[!] Reserved length_ind value: {}", length_ind);
LengthIndInfo {
pdu_len_bits: 0,
is_null_pdu: false,
is_frag_start: false,
second_half_stolen: false,
}
}
0b111110 => {
// Second half slot stolen (STCH)
LengthIndInfo {
pdu_len_bits: remaining_bits,
is_null_pdu: false,
is_frag_start: false,
second_half_stolen: true,
}
}
0b111111 => {
// Start of TL-SDU which extends in one or more subsequent MAC PDUs (fragmentation)
LengthIndInfo {
pdu_len_bits: remaining_bits,
is_null_pdu: false,
is_frag_start: true,
second_half_stolen: false,
}
}
_ => {
// Should not happen for 6-bit value
println!("[!] Invalid length_ind value: {}", length_ind);
LengthIndInfo {
pdu_len_bits: 0,
is_null_pdu: false,
is_frag_start: false,
second_half_stolen: false,
}
}
}
}
/// Count fill bits at the end of a PDU segment.
/// Fill bits are: a single '1' followed by zero or more '0' bits.
/// Returns the number of fill bits (including the leading '1').
pub fn count_fill_bits(pdu: &BitBuffer, pdu_len_bits: usize) -> usize {
let start = pdu.get_raw_start();
let mut index = pdu_len_bits as isize - 1;
// Walk backwards from end looking for the '1' that marks start of fill
while index >= 0 {
let bit = pdu.peek_bits_startoffset(start + index as usize, 1);
if let Some(bit) = bit {
if bit == 0 {
index -= 1;
} else {
// Found the '1' bit - fill bits are from here to end
return (pdu_len_bits as isize - index) as usize;
}
} else {
break;
}
}
// No fill bits found (all zeros or empty)
0
}
/// Apply PDU association: truncate buffer to PDU boundary, strip fill bits,
/// and check for remaining data that could be another PDU.
///
/// Returns the remaining bits string if there's a "next block", None otherwise.
pub fn apply_pdu_association(
pdu: &mut BitBuffer,
length_ind: Option<u8>,
has_fill_bits: bool,
) -> Option<String> {
let Some(length_ind) = length_ind else {
println!(" [No length_ind present - cannot apply PDU association]");
return None;
};
let remaining_bits = pdu.get_len_remaining();
let info = Self::interpret_length_ind(length_ind, remaining_bits);
println!();
println!("=== PDU Association ===");
println!("length_ind: {} (0b{:06b})", length_ind, length_ind);
if info.is_null_pdu {
println!(" Null PDU");
return None;
}
if info.is_frag_start {
println!(" Fragmentation start (TL-SDU extends to subsequent MAC PDUs)");
println!(" Remaining {} bits are fragment data", remaining_bits);
return None;
}
if info.second_half_stolen {
println!(" Second half slot stolen (STCH signalling)");
return None;
}
let pdu_len_bits = info.pdu_len_bits;
println!(" TM-SDU length: {} bits ({} bytes)", pdu_len_bits, length_ind);
// Calculate fill bits if requested
let fill_bits = if has_fill_bits {
let fb = Self::count_fill_bits(pdu, pdu_len_bits);
if fb > 0 {
println!(" Fill bits detected: {} bits", fb);
}
fb
} else {
0
};
let sdu_len_bits = pdu_len_bits.saturating_sub(fill_bits);
println!(" Effective SDU length: {} bits", sdu_len_bits);
// Check what's left after this PDU
let orig_end = pdu.get_raw_end();
let pdu_start = pdu.get_raw_start();
let next_pdu_start = pdu_start + pdu_len_bits;
let remaining_after_pdu = orig_end.saturating_sub(next_pdu_start);
if remaining_after_pdu >= 16 {
// Minimum MAC PDU is ~16 bits (null PDU), so there could be another
println!();
println!("=== Next Block Available ===");
println!(" {} bits remaining after this PDU", remaining_after_pdu);
// Extract the remaining bits as a string
let mut next_block = String::new();
for i in 0..remaining_after_pdu {
if let Some(bit) = pdu.peek_bits_startoffset(next_pdu_start + i, 1) {
next_block.push(if bit == 1 { '1' } else { '0' });
}
}
println!(" Next block: {}", next_block);
println!();
println!("To decode next PDU, run:");
println!(" pdu-tool <direction> tmv umac \"{}\"", next_block);
return Some(next_block);
} else if remaining_after_pdu > 0 {
println!();
println!(" {} bits remaining (too few for another PDU)", remaining_after_pdu);
}
None
}
/// Check if we should continue parsing more PDUs in this MAC block
fn check_continue(pdu: &BitBuffer, orig_start: usize) -> bool {
// If start was not updated, we also consider it end of message
// If 16 or more bits remain (len of null pdu), we continue parsing
if pdu.get_raw_start() != orig_start && pdu.get_len() >= 16 {
println!();
println!("--- Remaining {} bits, continuing parse ---", pdu.get_len_remaining());
println!("Remaining: {}", pdu.dump_bin_full(true));
println!();
true
} else {
println!();
println!("=== End of MAC block ===");
false
}
}
/// Print SDU data if available
fn print_sdu(pdu: &mut BitBuffer, bit_len: usize, label: &str) {
println!("{} length: {} bits ({} bytes)", label, bit_len, bit_len / 8);
if let Some(sdu_bits) = pdu.peek_bits(bit_len) {
println!("{}: {:0width$b}", label, sdu_bits, width = bit_len);
}
pdu.read_bits(bit_len); // Advance past SDU
}
}

96
bins/pdu-tool/src/main.rs Normal file
View File

@@ -0,0 +1,96 @@
use clap::Parser;
use tetra_core::BitBuffer;
use tetra_saps::tmv::enums::logical_chans::LogicalChannel;
mod entities;
use entities::umac::UmacParser;
#[derive(Parser, Debug)]
#[command(
author,
version,
about = "TETRA Raw PDU Decoder",
long_about = "Decodes a raw bitstring as a PDU for the specified SAP and destination component"
)]
struct Args {
/// Direction: uplink or downlink
#[arg(
help = "Direction: [ ul | dl ]"
)]
direction: String,
/// SAP (Service Access Point) name
#[arg(
help = "SAP name: [ tmv ]"
)]
sap: String,
/// Destination component name
#[arg(
help = "Destination component: [ umac ]"
)]
destination: String,
/// Raw bitstring to decode
#[arg(
help = "Raw bitstring (binary representation) to parse as PDU"
)]
bitstring: String,
#[arg(
short = 'c',
long = "channel",
default_value = "schf",
help = "Logical channel (for tmv sap): [ schf | schhu | schhd | stch | bnch | bsch | aach ]"
)]
channel: String,
}
fn main() {
eprintln!("[+] TETRA PDU Decoding tool");
eprintln!(" Wouter Bokslag / Midnight Blue");
eprintln!(" * This tool is a MESS and is for testing only *");
eprintln!(" * There be bugs.. *");
let args = Args::parse();
let logical_channel = match args.channel.to_lowercase().as_str() {
"schf" | "sch_f" | "sch/f" => LogicalChannel::SchF,
"schhu" | "sch_hu" | "sch/hu" => LogicalChannel::SchHu,
"schhd" | "sch_hd" | "sch/hd" => LogicalChannel::SchHd,
"stch" => LogicalChannel::Stch,
"bnch" => LogicalChannel::Bnch,
"bsch" => LogicalChannel::Bsch,
"aach" => LogicalChannel::Aach,
_ => {
eprintln!("Error: Unsupported logical channel '{}'. Use: schf, schhu, schhd, stch, bnch, bsch, aach", args.channel);
std::process::exit(1);
}
};
let is_downlink = match args.direction.to_lowercase().as_str() {
"ul" | "uplink" => false,
"dl" | "downlink" => true,
_ => {
eprintln!("Error: Unsupported direction '{}'. Use: ul, dl", args.direction);
std::process::exit(1);
}
};
match (args.sap.to_lowercase().as_str(), args.destination.to_lowercase().as_str()) {
("tmv", "umac") => {
let pdu = BitBuffer::from_bitstr(args.bitstring.as_str());
if is_downlink {
UmacParser::parse_dl(pdu, logical_channel);
} else {
UmacParser::parse_ul(pdu, logical_channel);
}
},
_ => {
eprintln!("Error: Unsupported SAP '{}' or destination '{}'", args.sap, args.destination);
eprintln!("Supported: tmv umac");
std::process::exit(1);
}
};
}

View File

@@ -0,0 +1,9 @@
[package]
name = "tetra-config"
version.workspace = true
edition.workspace = true
[dependencies]
tetra-core = { workspace = true }
serde = { workspace = true }
toml = { workspace = true }

View File

@@ -0,0 +1,13 @@
//! TETRA configuration management
//!
//! This crate provides configuration loading and parsing for TETRA BlueStation:
//! - TOML configuration file parsing
//! - Stack configuration structures
//! - SoapySDR-specific configuration
pub mod stack_config;
pub mod stack_config_soapy;
pub mod toml_config;
pub use stack_config::*;
pub use toml_config::*;

View File

@@ -1,7 +1,9 @@
use std::sync::{Arc, RwLock};
use serde::Deserialize;
use tetra_core::freqs::FreqInfo;
use super::stack_config_soapy::CfgSoapySdr;
use crate::{common::freqs::FreqInfo, config::stack_config_soapy::CfgSoapySdr, entities::lmac::components::scramble::scrambler};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "PascalCase")]
@@ -274,11 +276,6 @@ impl StackConfig {
Ok(())
}
/// Useful shorthand to get scrambling code for the current configuration.
pub fn scrambling_code(&self) -> u32 {
scrambler::tetra_scramb_get_init(self.net.mcc, self.net.mnc, self.cell.colour_code)
}
}
/// Mutable, stack-editable state (mutex-protected).

View File

@@ -6,7 +6,7 @@ use std::path::Path;
use serde::Deserialize;
use toml::Value;
use crate::config::stack_config::{CfgPhyIo, PhyBackend, CfgCellInfo, CfgNetInfo, SharedConfig, StackConfig, StackMode, StackState};
use super::stack_config::{CfgPhyIo, PhyBackend, CfgCellInfo, CfgNetInfo, SharedConfig, StackConfig, StackMode, StackState};
use super::stack_config_soapy::{CfgSoapySdr, LimeSdrCfg, SXceiverCfg, UsrpB2xxCfg};
/// Build `SharedConfig` from a TOML configuration file
@@ -260,9 +260,6 @@ struct PhyIoDto {
ul_input_file: Option<String>,
dl_input_file: Option<String>,
#[serde(default)]
pub input_file: Option<String>,
#[serde(default)]
pub soapysdr: Option<SoapySdrDto>,

View File

@@ -0,0 +1,11 @@
[package]
name = "tetra-core"
version.workspace = true
edition.workspace = true
[dependencies]
serde = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
tracing-appender = { workspace = true }
# as-any = { workspace = true }

View File

@@ -3,12 +3,16 @@
#[derive(Copy, Debug, Clone, PartialEq)]
pub enum SsiType {
Unknown,
Ssi, // Generic for other types
/// Generic type when specific type unknown. Avoid using where possible.
Ssi,
/// Individual Short Subscriber Identity
Issi,
/// Group Short Subscriber Identity
Gssi,
Ussi,
Smi,
EventLabel, // Only usable in Umac, needs to be replaced with true SSI
/// Only usable in Umac, needs to be replaced with true SSI
EventLabel,
}
impl core::fmt::Display for SsiType {
@@ -27,21 +31,27 @@ impl core::fmt::Display for SsiType {
#[derive(Copy, Debug, Clone)]
pub struct TetraAddress {
pub ssi: u32,
pub ssi_type: SsiType,
/// Set to true if the address is an ESI (Encrypted Subscriber Identity)
/// We maintain this field to allow us to pass still-encrypted SSIs up the stack if we want to
pub encrypted: bool,
pub ssi_type: SsiType,
pub ssi: u32,
}
impl Default for TetraAddress {
fn default() -> Self {
TetraAddress {
impl TetraAddress {
pub fn new(ssi: u32, ssi_type: SsiType) -> Self {
Self {
ssi,
ssi_type,
encrypted: false,
ssi_type: SsiType::Unknown,
ssi: 0,
}
}
/// Convenience constructor to create ISSI type address
pub fn issi(ssi: u32) -> Self {
Self::new(ssi, SsiType::Issi)
}
}
impl core::fmt::Display for TetraAddress {

View File

@@ -1,8 +1,6 @@
use std::{cmp::{max, min}, fmt};
use crate::common::pdu_parse_error::PduParseErr;
use crate::pdu_parse_error::PduParseErr;
pub struct BitBuffer {
buffer: Vec<u8>,

View File

@@ -1,6 +1,6 @@
use core::fmt;
use std::sync::Once;
use std::fs::OpenOptions;
use std::fmt;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::{fmt as tracingfmt, EnvFilter};
use tracing_subscriber::prelude::*;
@@ -119,33 +119,28 @@ pub fn get_default_stdout_filter() -> EnvFilter {
EnvFilter::new("info")
// Hide continuous logs from lower layers
.add_directive("tetra_bluestation::common::messagerouter=warn".parse().unwrap())
.add_directive("tetra_bluestation::common::bitbuffer=warn".parse().unwrap())
// Basic level for tetra entities
// .add_directive("tetra_bluestation::entities=info".parse().unwrap())
.add_directive("tetra_entities::messagerouter=warn".parse().unwrap())
.add_directive("tetra_core::bitbuffer=warn".parse().unwrap())
// Phy
.add_directive("tetra_bluestation::entities::phy::components=warn".parse().unwrap())
.add_directive("tetra_bluestation::entities::phy::phy_bs=debug".parse().unwrap())
.add_directive("tetra_entities::phy::components=warn".parse().unwrap())
.add_directive("tetra_entities::phy::phy_bs=debug".parse().unwrap())
// Lmac
.add_directive("tetra_bluestation::entities::lmac=info".parse().unwrap())
.add_directive("tetra_bluestation::entities::lmac::components=info".parse().unwrap())
.add_directive("tetra_entities::lmac=info".parse().unwrap())
// Umac
.add_directive("tetra_bluestation::entities::umac::subcomp::slotter=debug".parse().unwrap())
.add_directive("tetra_bluestation::entities::umac=debug".parse().unwrap())
.add_directive("tetra_entities::umac::subcomp::slotter=debug".parse().unwrap())
.add_directive("tetra_entities::umac=debug".parse().unwrap())
// Llc
.add_directive("tetra_bluestation::entities::llc=debug".parse().unwrap())
.add_directive("tetra_entities::llc=debug".parse().unwrap())
// Higher layers
.add_directive("tetra_bluestation::entities::mle=trace".parse().unwrap())
.add_directive("tetra_bluestation::entities::cmce=trace".parse().unwrap())
.add_directive("tetra_bluestation::entities::sndcp=trace".parse().unwrap())
.add_directive("tetra_bluestation::entities::mm=trace".parse().unwrap())
.add_directive("tetra_entities::mle=trace".parse().unwrap())
.add_directive("tetra_entities::cmce=trace".parse().unwrap())
.add_directive("tetra_entities::sndcp=trace".parse().unwrap())
.add_directive("tetra_entities::mm=trace".parse().unwrap())
}

View File

@@ -51,57 +51,6 @@ impl FreqInfo {
}
}
// /// Construct FreqInfo based on DL frequency, duplex spacing and reverse operation flag.
// pub fn from_dlfreq(freq: u32, reverse_operation: bool, duplex_spacing_setting: u8, duplex_hz: Option<u32>) -> Result<Self, String> {
// if freq % 6250 != 0 {
// return Err(format!("Invalid frequency {}", freq));
// }
// let band = freq / 100000000;
// if band > 8 {
// return Err(format!("Invalid frequency band {}", band));
// };
// let mut remainder = freq as i32 % 100000000;
// let mut carrier = remainder / 25000;
// if remainder == 18750 {
// carrier += 1; // Adjust carrier for negative offset
// remainder -= 25000; // Remainder becomes -6250
// };
// if carrier >= 4000 {
// return Err(format!("Invalid carrier number {}", carrier));
// };
// let Some(freq_offset_setting) = Self::freq_offset_hz_to_val(remainder) else {
// return Err(format!("Invalid frequency offset in frequency {}", freq));
// };
// let ret = Self {
// band: band as u8,
// carrier: carrier as u16,
// freq_offset_index: freq_offset_setting,
// duplex_spacing_: duplex_spacing_setting,
// duplex_spacing_override: None,
// reverse_operation,
// };
// Ok(ret)
// }
// /// Construct freqinfo based on DL and UL frequencies
// pub fn from_dlul_freqs(dlfreq: u32, ulfreq: u32) -> Result<Self, String> {
// let (reverse_operation, duplex_spacing) = if ulfreq < dlfreq {
// (false, dlfreq - ulfreq)
// } else {
// (true, ulfreq - dlfreq)
// }
// let duplex_spacing_setting = Self::freq_offset_hz_to_val(offset_hz)
// Self::from_dlfreq(dlfreq, reverse_operation, duplex_spacing, None)
// }
/// Construct FreqInfo from band, carrier, frequency offset, duplex spacing index and reverse operation flag.
/// Optionally accepts a custom duplex spacing value in Hz, if a duplex spacing table is used by the radios.
pub fn from_components(band: u8, carrier: u16, freq_offset_val: i16, reverse_operation: bool, duplex_index: u8, custom_duplex_spacing: Option<u32>) -> Result<Self, String> {

View File

@@ -0,0 +1,27 @@
//! Core utilities for TETRA BlueStation
//!
//! This crate provides fundamental types and utilities used across the TETRA stack:
//! - BitBuffer for bit-level PDU manipulation
//! - TdmaTime for TDMA frame timing
//! - Address types (ISSI, GSSI, etc.)
//! - PHY types (PhyBlockNum, BurstType, etc.)
//! - Common macros and debug utilities
pub mod address;
pub mod bitbuffer;
pub mod debug;
pub mod freqs;
pub mod pdu_parse_error;
pub mod phy_types;
pub mod tdma_time;
pub mod tetra_common;
pub mod tetra_entities;
pub mod typed_pdu_fields;
// Re-export commonly used items
pub use address::*;
pub use bitbuffer::BitBuffer;
pub use pdu_parse_error::PduParseErr;
pub use phy_types::*;
pub use tdma_time::TdmaTime;
pub use tetra_common::*;

View File

@@ -19,7 +19,7 @@ macro_rules! expect_pdu_type {
if $value == raw_expected {
Ok(())
} else {
Err($crate::common::pdu_parse_error::PduParseErr::InvalidPduType {
Err(PduParseErr::InvalidPduType {
expected: raw_expected as u64,
found: $value,
})
@@ -42,7 +42,7 @@ macro_rules! expect_value {
if val == $expected {
Ok(())
} else {
Err($crate::common::pdu_parse_error::PduParseErr::InvalidValue {
Err(PduParseErr::InvalidValue {
field: $field,
value: val.into(),
})
@@ -61,7 +61,7 @@ macro_rules! expect_failed {
};
(@inner $value:expr, $field:expr) => {{
Err($crate::common::pdu_parse_error::PduParseErr::InvalidValue {
Err(PduParseErr::InvalidValue {
field: $field,
value: $value,
})

View File

@@ -1,3 +1,22 @@
//! PHY-layer types that are used across multiple layers
//!
//! These types originate from the PHY layer but are referenced by LMAC, UMAC,
//! and SAP primitives, so they live in tetra-core to avoid circular dependencies.
/// Identifies which block(s) within a timeslot
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum PhyBlockNum {
/// Both half-slots combined (full slot)
Both,
/// First half-slot only
Block1,
/// Second half-slot only
Block2,
/// Block number not determined
Undefined,
}
/// Physical block types
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum PhyBlockType {
BBK,
@@ -5,38 +24,26 @@ pub enum PhyBlockType {
SB1,
SB2,
NDB,
NUB,
SSN1,
SSN2
SSN2,
}
/// Clause 9.4.4.1
/// Burst types (Clause 9.4.4.1)
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum BurstType {
/// Control Uplink Burst
CUB,
/// Normal Uplink Burst
NUB,
/// Normal Downlink Burst. We don't differentiate between continuous and discontinuous bursts
NDB,
/// Syncrhonization Downlink Burst. We don't differentiate between continuous and discontinuous bursts
SDB,
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum PhyBlockNum {
Both,
Block1,
Block2,
Undefined
/// Normal Downlink Burst (continuous and discontinuous)
NDB,
/// Synchronization Downlink Burst (continuous and discontinuous)
SDB,
}
/// Training sequences
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Default)]
#[derive(Debug, Copy, Clone, PartialEq, Default)]
pub enum TrainingSequence {
/// 22 n bits
NormalTrainSeq1 = 1,
@@ -52,5 +59,3 @@ pub enum TrainingSequence {
#[default]
NotFound = 0,
}

View File

@@ -17,7 +17,7 @@ pub struct Type3FieldGeneric {
/// Helper functions for dealing with type2, type3 and type4 fields for MLE, CMCE, MM and SNDCP PDUs.
pub mod delimiters {
use crate::common::{bitbuffer::BitBuffer, pdu_parse_error::PduParseErr};
use crate::{bitbuffer::BitBuffer, pdu_parse_error::PduParseErr};
/// Read the o-bit between type1 and type2/type3 elements
pub fn read_obit(buffer: &mut BitBuffer) -> Result<bool, PduParseErr> {
@@ -51,7 +51,7 @@ pub mod delimiters {
}
pub mod typed {
use crate::common::{bitbuffer::BitBuffer, pdu_parse_error::PduParseErr, typed_pdu_fields::{Type3FieldGeneric, Type4FieldGeneric, delimiters}};
use crate::{bitbuffer::BitBuffer, pdu_parse_error::PduParseErr, typed_pdu_fields::{Type3FieldGeneric, Type4FieldGeneric, delimiters}};
pub fn parse_type2_generic(
obit: bool,

View File

@@ -0,0 +1,18 @@
[package]
name = "tetra-entities"
version.workspace = true
edition.workspace = true
[dependencies]
tetra-core = { workspace = true }
tetra-config = { workspace = true }
tetra-saps = { workspace = true }
tetra-pdus = { workspace = true }
as-any = { workspace = true }
num = { workspace = true }
num-complex = { workspace = true }
rustfft = { workspace = true }
rand = { workspace = true }
tracing = { workspace = true }
crossbeam-channel = { workspace = true }
soapysdr = { workspace = true }

View File

@@ -1,11 +1,11 @@
use crate::config::stack_config::SharedConfig;
use crate::common::messagerouter::MessageQueue;
use crate::entities::cmce::enums::cmce_pdu_type_ul::CmcePduTypeUl;
use crate::saps::sapmsg::{SapMsg, SapMsgInner};
use crate::common::tetra_entities::TetraEntity;
use crate::entities::TetraEntityTrait;
use crate::common::tetra_common::Sap;
use crate::unimplemented_log;
use tetra_config::SharedConfig;
use tetra_core::tetra_entities::TetraEntity;
use tetra_core::{Sap, unimplemented_log};
use crate::{MessageQueue, TetraEntityTrait};
use tetra_saps::{SapMsg, SapMsgInner};
use tetra_pdus::cmce::enums::cmce_pdu_type_ul::CmcePduTypeUl;
use super::subentities::cc::CcSubentity;
use super::subentities::sds::SdsSubentity;

View File

@@ -1,10 +1,10 @@
use crate::config::stack_config::SharedConfig;
use crate::common::messagerouter::MessageQueue;
use crate::saps::sapmsg::{SapMsg, SapMsgInner};
use crate::common::tetra_entities::TetraEntity;
use crate::entities::cmce::enums::cmce_pdu_type_dl::CmcePduTypeDl;
use crate::entities::TetraEntityTrait;
use crate::common::tetra_common::Sap;
use tetra_config::SharedConfig;
use tetra_core::tetra_entities::TetraEntity;
use tetra_core::Sap;
use crate::{MessageQueue, TetraEntityTrait};
use tetra_saps::{SapMsg, SapMsgInner};
use tetra_pdus::cmce::enums::cmce_pdu_type_dl::CmcePduTypeDl;
use super::subentities::cc::CcSubentity;
use super::subentities::sds::SdsSubentity;

View File

@@ -1,7 +1,3 @@
pub mod subentities;
pub mod components;
pub mod pdus;
pub mod enums;
pub mod cmce_bs;
pub mod cmce_ms;

View File

@@ -1,4 +1,9 @@
use crate::{common::messagerouter::MessageQueue, saps::sapmsg::{SapMsg, SapMsgInner}, entities::cmce::enums::cmce_pdu_type_dl::CmcePduTypeDl, unimplemented_log};
use tetra_core::unimplemented_log;
use crate::MessageQueue;
use tetra_saps::{SapMsg, SapMsgInner};
use tetra_pdus::cmce::enums::cmce_pdu_type_dl::CmcePduTypeDl;
/// Clause 11 Call Control CMCE sub-entity
pub struct CcSubentity{

View File

@@ -1,4 +1,8 @@
use crate::{common::messagerouter::MessageQueue, saps::sapmsg::{SapMsg, SapMsgInner}, entities::cmce::{enums::cmce_pdu_type_dl::CmcePduTypeDl, pdus::d_sds_data::DSdsData}, unimplemented_log};
use tetra_core::unimplemented_log;
use crate::MessageQueue;
use tetra_saps::{SapMsg, SapMsgInner};
use tetra_pdus::cmce::{enums::cmce_pdu_type_dl::CmcePduTypeDl, pdus::d_sds_data::DSdsData};
/// Clause 13 Short Data Service CMCE sub-entity
pub struct SdsSubentity{

View File

@@ -1,4 +1,6 @@
use crate::{common::messagerouter::MessageQueue, saps::sapmsg::SapMsg};
use crate::MessageQueue;
use tetra_saps::SapMsg;
/// Clause 12 Supplementary Services CMCE sub-entity
pub struct SsSubentity{

View File

@@ -0,0 +1,25 @@
use as_any::AsAny;
use tetra_core::{TdmaTime, tetra_entities::TetraEntity};
use tetra_config::SharedConfig;
use tetra_saps::SapMsg;
use crate::MessageQueue;
/// Trait for TETRA entities
/// Used by MessageRouter for passing messages between entities
pub trait TetraEntityTrait: Send + AsAny {
/// Returns the entity type identifier
fn entity(&self) -> TetraEntity;
/// Handle incoming SAP primitive
fn rx_prim(&mut self, queue: &mut MessageQueue, message: SapMsg);
/// Update configuration (optional)
#[allow(dead_code)]
fn set_config(&mut self, _config: SharedConfig) {}
/// Called at the start of each TDMA tick
fn tick_start(&mut self, _queue: &mut MessageQueue, _ts: Option<TdmaTime>) { }
/// Called at the end of each TDMA tick
fn tick_end(&mut self, _queue: &mut MessageQueue, _ts: Option<TdmaTime>) -> bool { false }
}

View File

@@ -0,0 +1,16 @@
#![allow(dead_code)]
pub mod cmce;
pub mod entity_trait;
pub mod llc;
pub mod lmac;
pub mod messagerouter;
pub mod mle;
pub mod mm;
pub mod phy;
pub mod sndcp;
pub mod umac;
// Re-export commonly used items from router
pub use entity_trait::TetraEntityTrait;
pub use messagerouter::{MessagePrio, MessageQueue, MessageRouter};

View File

@@ -1,4 +1,4 @@
use crate::common::bitbuffer::BitBuffer;
use tetra_core::BitBuffer;
/// Compute FCS checksum for a range of bits in a BitBuffer
/// Offsets are relative to the bitbuffer window start.
@@ -41,7 +41,7 @@ pub fn check_fcs(bitbuf: &BitBuffer) -> bool {
#[cfg(test)]
mod tests {
use crate::entities::llc::pdus::bl_data::BlData;
use tetra_pdus::llc::pdus::bl_data::BlData;
use super::*;

View File

@@ -0,0 +1,2 @@
pub mod fcs;

View File

@@ -1,23 +1,19 @@
use std::panic;
use crate::common::messagerouter::MessageQueue;
use crate::saps::tla::{TlaTlDataIndBl, TlaTlUnitdataIndBl};
use crate::saps::tma::TmaUnitdataReq;
use crate::common::address::TetraAddress;
use crate::common::bitbuffer::BitBuffer;
use crate::common::tdma_time::TdmaTime;
use crate::common::tetra_common::Sap;
use crate::common::tetra_entities::TetraEntity;
use crate::entities::llc::components::fcs::check_fcs;
use crate::entities::llc::enums::llc_pdu_type::LlcPduType;
use crate::entities::llc::pdus::bl_ack::BlAck;
use crate::entities::llc::pdus::bl_adata::BlAdata;
use crate::entities::llc::pdus::bl_data::BlData;
use crate::entities::llc::pdus::bl_udata::BlUdata;
use crate::entities::TetraEntityTrait;
use crate::saps::sapmsg::{SapMsg, SapMsgInner};
use crate::config::stack_config::*;
use crate::unimplemented_log;
use tetra_config::SharedConfig;
use tetra_core::tetra_entities::TetraEntity;
use tetra_core::{BitBuffer, Sap, TdmaTime, TetraAddress, unimplemented_log};
use crate::{MessageQueue, TetraEntityTrait};
use tetra_saps::tla::{TlaTlDataIndBl, TlaTlUnitdataIndBl};
use tetra_saps::tma::TmaUnitdataReq;
use tetra_saps::{SapMsg, SapMsgInner};
use crate::llc::components::fcs;
use tetra_pdus::llc::enums::llc_pdu_type::LlcPduType;
use tetra_pdus::llc::pdus::bl_ack::BlAck;
use tetra_pdus::llc::pdus::bl_adata::BlAdata;
use tetra_pdus::llc::pdus::bl_data::BlData;
use tetra_pdus::llc::pdus::bl_udata::BlUdata;
pub struct AckData {
pub addr: TetraAddress,
@@ -176,7 +172,7 @@ impl Llc {
req_handle: prim.req_handle,
pdu: pdu_buf,
main_address: prim.main_address,
scrambling_code: prim.scrambling_code,
// scrambling_code: prim.scrambling_code,
endpoint_id: prim.endpoint_id,
stealing_permission: prim.stealing_permission,
subscriber_class: prim.subscriber_class,
@@ -319,7 +315,7 @@ impl Llc {
};
// If FCS is present, check it. If wrong, we bail here
if has_fcs && !check_fcs(&pdu) {
if has_fcs && !fcs::check_fcs(&pdu) {
tracing::warn!("FCS check failed");
return;
}
@@ -458,7 +454,7 @@ impl TetraEntityTrait for Llc {
req_handle: 0, // TODO FIXME
pdu: pdu_buf,
main_address: ack.addr,
scrambling_code: self.config.config().scrambling_code(),
// scrambling_code: self.config.config().scrambling_code(),
endpoint_id: ack.t_start.t as i32, // TODO FIXME
stealing_permission: false, // TODO FIXME
subscriber_class: 0, // TODO FIXME

View File

@@ -1,4 +1,2 @@
pub mod components;
pub mod enums;
pub mod pdus;
pub mod llc_bs_ms;

View File

@@ -1,15 +1,12 @@
use crate::common::bitbuffer::BitBuffer;
use crate::entities::lmac::components::convenc::{self, get_punctured_rate, ConvEncState, RcpcPunctMode};
use crate::entities::lmac::components::crc16;
use crate::entities::lmac::components::errorcontrol_params;
use crate::entities::lmac::components::interleave::interleaver;
use crate::entities::lmac::components::rm3014::{tetra_rm3014_compute, tetra_rm3014_decode_limited_ecc};
use crate::entities::lmac::components::scramble::{scrambler, SCRAMB_INIT};
use crate::entities::lmac::components::viterbi::dec_sb1;
use crate::entities::phy::enums::burst::PhyBlockType;
use crate::saps::tmv::enums::logical_chans::LogicalChannel;
use crate::saps::tmv::TmvUnitdataReq;
use crate::saps::tp::TpUnitdataInd;
use tetra_core::{BitBuffer, PhyBlockType};
use tetra_saps::tmv::TmvUnitdataReq;
use tetra_saps::tmv::enums::logical_chans::LogicalChannel;
use tetra_saps::tp::TpUnitdataInd;
use crate::lmac::components::convenc::{self, ConvEncState, RcpcPunctMode};
use crate::lmac::components::{crc16, errorcontrol_params, interleaver, rm3014, viterbi};
use crate::lmac::components::scrambler;
/// Encodes control plane message from type1 to type5 bits
@@ -47,7 +44,7 @@ pub fn encode_cp(mut prim: TmvUnitdataReq) -> BitBuffer {
// Puncturing, type3dp -> type3
let mut type3_arr = [0u8; 432]; // Need params.type345_bits
get_punctured_rate(RcpcPunctMode::Rate2_3, &type3dp_arr, &mut type3_arr);
convenc::get_punctured_rate(RcpcPunctMode::Rate2_3, &type3dp_arr, &mut type3_arr);
tracing::trace!("encode_cp {:?} type3: {:?}", lchan, BitBuffer::from_bitarr(&type3_arr[0..params.type345_bits]).dump_bin());
// Interleaving, type3 -> type4
@@ -89,7 +86,7 @@ pub fn decode_cp(lchan: LogicalChannel, prim: TpUnitdataInd, default_scramb_code
// Get scrambling code. For sync block, we use the default scranbling code.
// For others, we use the scrambling code previously retrieved from SYNC.
let scrambling_code = if prim.block_type == PhyBlockType::SB1 {
SCRAMB_INIT
scrambler::SCRAMB_INIT
} else if let Some(scrambling_code) = default_scramb_code {
scrambling_code
} else {
@@ -112,7 +109,7 @@ pub fn decode_cp(lchan: LogicalChannel, prim: TpUnitdataInd, default_scramb_code
// Viterbi, type3dp -> type2
// viterbi_dec_sb1_wrapper(&type3dp_arr, &mut type2_arr, params.type2_bits);
dec_sb1(&type3dp_arr, &mut type2_arr, params.type2_bits);
viterbi::dec_sb1(&type3dp_arr, &mut type2_arr, params.type2_bits);
tracing::trace!("decode_cp {:?} type2: {:?}", lchan, BitBuffer::from_bitarr(&type2_arr[0..params.type2_bits]).dump_bin());
// CRC check, type2 -> type1
@@ -140,7 +137,7 @@ pub fn encode_aach(buf: BitBuffer, scrambling_code: u32) -> BitBuffer {
// RM code type1 -> type2
let type1_int = type1.read_bits(14).unwrap() as u16; // Guaranteed
let type2_int = tetra_rm3014_compute(type1_int);
let type2_int = rm3014::tetra_rm3014_compute(type1_int);
let mut type2 = BitBuffer::new(30);
type2.write_bits(type2_int as u64, 30);
@@ -177,7 +174,7 @@ pub fn decode_aach(buf: BitBuffer, scrambling_code: u32) -> BitBuffer {
// Convert to int and perform single-bit error correction
// TODO FIXME: Multi-bit error correction (Clause 8.3.1.1)
let x = type2.read_bits(30).unwrap() as u32; // Guaranteed
let y = tetra_rm3014_decode_limited_ecc(x);
let y = rm3014::tetra_rm3014_decode_limited_ecc(x);
// Write error-corrected data to type1 and return
let mut type1 = type2;
@@ -193,7 +190,7 @@ pub fn decode_aach(buf: BitBuffer, scrambling_code: u32) -> BitBuffer {
#[cfg(test)]
mod tests {
use crate::{common::debug::setup_logging_verbose, entities::{lmac::components::scramble::scrambler::tetra_scramb_get_init, phy::enums::burst::*}};
use tetra_core::{BurstType, PhyBlockNum, TrainingSequence, debug::setup_logging_verbose};
use super::*;
@@ -206,7 +203,7 @@ mod tests {
let type5vec = "001101111110011111000110100001101110011100110000111100011000011100101011111100010101101001101001001110011100001010001101101010100000000011010001001101001010101100100110011001111100001011000001010010000011010110110110";
let bb = BitBuffer::from_bitstr(type1vec);
let lchan = LogicalChannel::Bnch;
let scramb_code = tetra_scramb_get_init(204, 1337, 1);
let scramb_code = scrambler::tetra_scramb_get_init(204, 1337, 1);
// println!("start: {}", bb.dump_bin());
let prim_req = TmvUnitdataReq {
mac_block: bb,
@@ -240,7 +237,7 @@ mod tests {
let type1_vec = "000100000111000010000010000000000110011000001010011100110001";
let bb = BitBuffer::from_bitstr(type1_vec);
let lchan = LogicalChannel::Bsch;
let scramb_code = SCRAMB_INIT;
let scramb_code = scrambler::SCRAMB_INIT;
let prim_req = TmvUnitdataReq {
mac_block: bb,
logical_channel: lchan,
@@ -265,7 +262,7 @@ mod tests {
#[test]
fn test_encdec_aach() {
// setup_logging_verbose();
let scramb_code = tetra_scramb_get_init(204, 1337, 1);
let scramb_code = scrambler::tetra_scramb_get_init(204, 1337, 1);
let type5vec = "100100100001011110111010111011";
let type1vec = "00001010001010";
@@ -287,7 +284,7 @@ mod tests {
let type1_vec = "0000000000110001000000000010011100010001000001110010000010000001000000000010011100010001010000000000001000110110011011100000100110000001011100000000110101000110011100000100000000000000000100001000000000000000000000000000000000000000000000000000000000000000000000000000";
let bb = BitBuffer::from_bitstr(type1_vec);
let lchan = LogicalChannel::SchF;
let scramb_code = tetra_scramb_get_init(204, 1337, 1);
let scramb_code = scrambler::tetra_scramb_get_init(204, 1337, 1);
let prim_req = TmvUnitdataReq {
mac_block: bb,
logical_channel: lchan,

View File

@@ -1,4 +1,6 @@
use crate::saps::tmv::enums::logical_chans::LogicalChannel;
use tetra_saps::tmv::enums::logical_chans::LogicalChannel;
/// Each LogicalChannel is associated with a set of error control parameters.
#[derive(Debug)]

View File

@@ -0,0 +1,71 @@
pub const fn block_interl_func(k: u32, a: u32, i: u32) -> u32 {
1 + ((a.wrapping_mul(i)) % k)
}
pub fn block_interleave(k: usize, a: usize, input: &[u8], output: &mut [u8]) {
assert!(input.len() >= k && output.len() >= k);
for i in 1..=k {
let k = block_interl_func(k as u32, a as u32, i as u32) as usize;
output[k - 1] = input[i - 1];
}
}
pub fn block_deinterleave(k: usize, a: usize, input: &[u8], output: &mut [u8]) {
assert!(input.len() >= k && output.len() >= k);
for i in 1..=k {
let k = block_interl_func(k as u32, a as u32, i as u32) as usize;
output[i - 1] = input[k - 1];
}
}
pub fn matrix_interleave(lines: usize, columns: usize, input: &[u8], output: &mut [u8]) {
let total = lines.checked_mul(columns).expect("overflow");
assert!(input.len() >= total && output.len() >= total);
for i in 0..columns {
for j in 0..lines {
output[i * lines + j] = input[j * columns + i];
}
}
}
pub fn matrix_deinterleave(lines: usize, columns: usize, input: &[u8], output: &mut [u8]) {
let total = lines.checked_mul(columns).expect("overflow");
assert!(input.len() >= total && output.len() >= total);
for i in 0..columns {
for j in 0..lines {
output[j * columns + i] = input[i * lines + j];
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_block_interleave_roundtrip() {
let k = 10;
let a = 3;
let data: Vec<u8> = (0..k as u8).collect();
let mut tmp = vec![0u8; k];
let mut out = vec![0u8; k];
block_interleave(k, a, &data, &mut tmp);
block_deinterleave(k, a, &tmp, &mut out);
assert_eq!(data, out);
}
#[test]
fn test_matrix_interleave_roundtrip() {
let lines = 4;
let columns = 3;
let data: Vec<u8> = (0..(lines*columns) as u8).collect();
let mut tmp = vec![0u8; lines*columns];
let mut out = vec![0u8; lines*columns];
matrix_interleave(lines, columns, &data, &mut tmp);
matrix_deinterleave(lines, columns, &tmp, &mut out);
assert_eq!(data, out);
}
}

View File

@@ -1,5 +1,5 @@
pub mod scramble;
pub mod interleave;
pub mod scrambler;
pub mod interleaver;
pub mod convenc;
pub mod viterbi;
pub mod crc16;

View File

@@ -0,0 +1,62 @@
use tetra_core::BitBuffer;
/// Scrambling/unscrambling functions type5 <-> type4
/// See Clause 8.3
/// Default scrambling code for BSCH channel
pub const SCRAMB_INIT: u32 = 3;
/// Generate one LFSR bit (Fibonacci form, taps at 32,26,23,22,16,12,11,10,8,7,5,4,2,1).
#[inline]
fn next_lfsr_bit(lfsr: &mut u32) -> u8 {
let x = *lfsr;
let bit = (
x ^ (x >> (32 - 26)) ^ (x >> (32 - 23)) ^
(x >> (32 - 22)) ^ (x >> (32 - 16)) ^ (x >> (32 - 12)) ^
(x >> (32 - 11)) ^ (x >> (32 - 10)) ^ (x >> (32 - 8)) ^
(x >> (32 - 7)) ^ (x >> (32 - 5)) ^ (x >> (32 - 4)) ^
(x >> (32 - 2)) ^ (x >> (32 - 1))
) & 1;
*lfsr = (x >> 1) | (bit << 31);
bit as u8
}
/// Fill `out[0..]` with `len = out.len()` raw scrambling bits
pub fn tetra_scramb_get_bits(mut lfsr_init: u32, out: &mut [u8]) {
for slot in out.iter_mut() {
*slot = next_lfsr_bit(&mut lfsr_init);
}
}
// /// XOR `out[0..]` inplace with the scrambling bits
// pub fn tetra_scramb_bits(mut lfsr_init: u32, out: &mut [u8]) {
// for byte in out.iter_mut() {
// *byte ^= next_lfsr_bit(&mut lfsr_init);
// }
// }
/// Scramble or unscramble the given BitBuffer.
/// Generate lfsr sequence for given lfsr initialization value
/// and XOR it with the given buffer, for all bits from the current
/// position to the end of the buffer. Resets position to the old
/// initial position when done.
pub fn tetra_scramb_bits(mut lfsr_init: u32, buf: &mut BitBuffer) {
let num_bits = buf.get_len_remaining() as isize;
for _ in 0..num_bits {
let bit = next_lfsr_bit(&mut lfsr_init);
buf.xor_bit(bit);
}
// Reset pos
buf.seek_rel(-num_bits);
}
/// Compute the initial LFSR state from (mcc, mnc, colour).
pub fn tetra_scramb_get_init(mcc: u16, mnc: u16, colour: u8) -> u32 {
if colour == 0 {
// See Clause 21.4.4.2, cc 0 means all 30 bits of scrambling code are 0
return 0;
}
(((colour as u32) | ((mnc as u32) << 6) | ((mcc as u32) << 20)) << 2) | SCRAMB_INIT
}

View File

@@ -1,17 +1,14 @@
use crate::common::messagerouter::MessageQueue;
use crate::entities::phy::enums::burst::{BurstType, PhyBlockNum, TrainingSequence};
use crate::saps::sapmsg::{SapMsg, SapMsgInner};
use crate::config::stack_config::*;
use crate::saps::tmv::enums::logical_chans::LogicalChannel;
use crate::saps::tmv::TmvUnitdataInd;
use crate::saps::tp::{TpUnitdataInd, TpUnitdataReqSlot};
use crate::common::tdma_time::TdmaTime;
use crate::common::tetra_common::Sap;
use crate::common::tetra_entities::TetraEntity;
use crate::entities::lmac::components::errorcontrol;
use crate::entities::lmac::components::scramble::scrambler;
use crate::entities::TetraEntityTrait;
use crate::unimplemented_log;
use tetra_config::{SharedConfig, StackMode};
use tetra_core::tetra_entities::TetraEntity;
use tetra_core::{BurstType, PhyBlockNum, Sap, TdmaTime, TrainingSequence, unimplemented_log};
use crate::{MessageQueue, TetraEntityTrait};
use tetra_saps::tmv::TmvUnitdataInd;
use tetra_saps::tmv::enums::logical_chans::LogicalChannel;
use tetra_saps::tp::{TpUnitdataInd, TpUnitdataReqSlot};
use tetra_saps::{SapMsg, SapMsgInner};
use crate::lmac::components::{errorcontrol, scrambler};
#[derive(Debug, Clone, Copy)]

View File

@@ -1,18 +1,16 @@
use crate::common::messagerouter::{MessagePrio, MessageQueue};
use crate::saps::sapmsg::{SapMsg, SapMsgInner};
use crate::config::stack_config::*;
use crate::saps::tmv::enums::logical_chans::LogicalChannel;
use crate::saps::tmv::TmvUnitdataInd;
use crate::saps::tp::TpUnitdataInd;
use crate::common::tdma_time::TdmaTime;
use crate::common::tetra_common::Sap;
use crate::common::tetra_entities::TetraEntity;
use crate::entities::lmac::components::errorcontrol;
use crate::entities::phy::enums::burst::{PhyBlockNum, PhyBlockType};
use crate::entities::TetraEntityTrait;
use crate::unimplemented_log;
use tetra_config::SharedConfig;
use tetra_core::tetra_entities::TetraEntity;
use tetra_core::{PhyBlockNum, PhyBlockType, Sap, TdmaTime, unimplemented_log};
use crate::{MessagePrio, MessageQueue, TetraEntityTrait};
use tetra_saps::tmv::TmvUnitdataInd;
use tetra_saps::tmv::enums::logical_chans::LogicalChannel;
use tetra_saps::tp::TpUnitdataInd;
use tetra_saps::{SapMsg, SapMsgInner};
use crate::lmac::components::{errorcontrol, scrambler};
use super::components::scramble::SCRAMB_INIT;
#[derive(Debug, Clone, Copy)]
pub struct LmacTrafficChan {
@@ -175,7 +173,7 @@ impl LmacMs {
// TODO FIXME maybe consider returning scramb_code from decode_cp
let scramb_code = if lchan == LogicalChannel::Bsch {
SCRAMB_INIT
scrambler::SCRAMB_INIT
} else {
self.scrambling_code.unwrap() // Guaranteed since we were able to decode
};

View File

@@ -1,10 +1,10 @@
use std::collections::{HashMap, VecDeque};
use crate::config::stack_config::SharedConfig;
use crate::saps::sapmsg::SapMsg;
use crate::common::tdma_time::TdmaTime;
use crate::common::tetra_entities::TetraEntity;
use crate::entities::TetraEntityTrait;
use tetra_config::SharedConfig;
use tetra_core::{TdmaTime, tetra_entities::TetraEntity};
use tetra_saps::SapMsg;
use crate::TetraEntityTrait;
#[derive(Default)]
@@ -50,7 +50,7 @@ impl MessageQueue {
pub struct MessageRouter {
/// While currently unused by the MessageRouter, this may change in the future
/// As such, we provide the MessageRouter with a copy of the SharedConfig
config: SharedConfig,
_config: SharedConfig,
entities: HashMap<TetraEntity, Box<dyn TetraEntityTrait>>,
msg_queue: MessageQueue,
@@ -68,7 +68,7 @@ impl MessageRouter {
msg_queue: MessageQueue {
messages: VecDeque::new(),
},
config,
_config: config,
ts: None,
}
}

View File

@@ -1,20 +1,18 @@
use crate::common::address::TetraAddress;
use crate::common::messagerouter::MessageQueue;
use crate::saps::lcmc::LcmcMleUnitdataInd;
use crate::saps::lmm::LmmMleUnitdataInd;
use crate::saps::ltpd::LtpdMleUnitdataInd;
use crate::saps::tla::TlaTlDataReqBl;
use crate::entities::mle::enums::mle_pdu_type_dl::MlePduTypeDl;
use crate::entities::mle::enums::mle_protocol_discriminator::MleProtocolDiscriminator;
use crate::entities::mle::pdus::d_mle_sync::DMleSync;
use crate::entities::mle::pdus::d_mle_sysinfo::DMleSysinfo;
use crate::common::bitbuffer::BitBuffer;
use crate::common::tetra_common::Sap;
use crate::common::tetra_entities::TetraEntity;
use crate::entities::TetraEntityTrait;
use crate::saps::sapmsg::{SapMsg, SapMsgInner};
use crate::config::stack_config::*;
use crate::unimplemented_log;
use tetra_config::SharedConfig;
use tetra_core::tetra_entities::TetraEntity;
use tetra_core::{BitBuffer, Sap, TetraAddress, unimplemented_log};
use crate::{MessageQueue, TetraEntityTrait};
use tetra_saps::lcmc::LcmcMleUnitdataInd;
use tetra_saps::lmm::LmmMleUnitdataInd;
use tetra_saps::ltpd::LtpdMleUnitdataInd;
use tetra_saps::tla::TlaTlDataReqBl;
use tetra_saps::{SapMsg, SapMsgInner};
use tetra_pdus::mle::enums::mle_pdu_type_dl::MlePduTypeDl;
use tetra_pdus::mle::enums::mle_protocol_discriminator::MleProtocolDiscriminator;
use tetra_pdus::mle::pdus::d_mle_sync::DMleSync;
use tetra_pdus::mle::pdus::d_mle_sysinfo::DMleSysinfo;
pub struct Mle {
// config: Option<SharedConfig>,
@@ -208,7 +206,7 @@ impl Mle {
handle: 0, // TODO FIXME
endpoint_id: 0, // TODO FIXME
link_id: 0, // TODO FIXME,
received_tetra_address: TetraAddress::default(), // TODO FIXME
received_tetra_address: TetraAddress::issi(0), // TODO FIXME
// received_address_type: 0,
chan_change_resp_req: false, // TODO FIXME
chan_change_handle: None, // TODO FIXME
@@ -412,7 +410,7 @@ impl Mle {
link_id: 0,
endpoint_id: 0,
tl_sdu: pdu,
scrambling_code: self.config.config().scrambling_code(), // TODO cache
// scrambling_code: self.config.config().scrambling_code(), // TODO cache
// pdu_prio: todo!(),
stealing_permission: false,
subscriber_class: 0, // TODO fixme

View File

@@ -0,0 +1 @@
pub mod mle_bs_ms;

View File

@@ -1,4 +1,8 @@
use crate::{common::{address::{SsiType, TetraAddress}, bitbuffer::BitBuffer, tdma_time::TdmaTime, tetra_common::Sap, tetra_entities::TetraEntity}, entities::mm::{enums::mm_pdu_type_ul::MmPduTypeUl, pdus::mm_pdu_function_not_supported::MmPduFunctionNotSupported}, saps::{lmm::LmmMleUnitdataReq, sapmsg::{SapMsg, SapMsgInner}}};
use tetra_core::{BitBuffer, Sap, SsiType, TdmaTime, TetraAddress, tetra_entities::TetraEntity};
use tetra_saps::{SapMsg, SapMsgInner, lmm::LmmMleUnitdataReq};
use tetra_pdus::mm::{enums::mm_pdu_type_ul::MmPduTypeUl, pdus::mm_pdu_function_not_supported::MmPduFunctionNotSupported};
pub fn make_ul_mm_pdu_function_not_supported(

View File

@@ -1,28 +1,27 @@
use crate::config::stack_config::SharedConfig;
use crate::common::messagerouter::MessageQueue;
use crate::entities::mm::components::not_supported::make_ul_mm_pdu_function_not_supported;
use crate::entities::mm::enums::location_update_type::LocationUpdateType;
use crate::entities::mm::enums::status_uplink::StatusUplink;
use crate::entities::mm::fields::group_identity_location_accept::GroupIdentityLocationAccept;
use crate::entities::mm::fields::group_identity_uplink::GroupIdentityUplink;
use crate::entities::mm::pdus::u_mm_status::UMmStatus;
use crate::saps::lmm::LmmMleUnitdataReq;
use crate::saps::sapmsg::{SapMsg, SapMsgInner};
use crate::common::address::{SsiType, TetraAddress};
use crate::common::bitbuffer::BitBuffer;
use crate::entities::mm::components::client_state::MmClientMgr;
use crate::entities::mm::enums::mm_pdu_type_ul::MmPduTypeUl;
use crate::entities::mm::fields::group_identity_attachment::GroupIdentityAttachment;
use crate::entities::mm::fields::group_identity_downlink::GroupIdentityDownlink;
use crate::entities::mm::pdus::d_attach_detach_group_identity_acknowledgement::DAttachDetachGroupIdentityAcknowledgement;
use crate::entities::mm::pdus::d_location_update_accept::DLocationUpdateAccept;
use crate::entities::mm::pdus::u_attach_detach_group_identity::UAttachDetachGroupIdentity;
use crate::entities::mm::pdus::u_itsi_detach::UItsiDetach;
use crate::entities::mm::pdus::u_location_update_demand::ULocationUpdateDemand;
use crate::entities::TetraEntityTrait;
use crate::common::tetra_common::Sap;
use crate::common::tetra_entities::TetraEntity;
use crate::{assert_warn, unimplemented_log};
use tetra_config::SharedConfig;
use tetra_core::tetra_entities::TetraEntity;
use tetra_core::{BitBuffer, Sap, SsiType, TetraAddress, assert_warn, unimplemented_log};
use crate::{MessageQueue, TetraEntityTrait};
use tetra_saps::lmm::LmmMleUnitdataReq;
use tetra_saps::{SapMsg, SapMsgInner};
use crate::mm::components::client_state::MmClientMgr;
use crate::mm::components::not_supported::make_ul_mm_pdu_function_not_supported;
use tetra_pdus::mm::enums::location_update_type::LocationUpdateType;
use tetra_pdus::mm::enums::mm_pdu_type_ul::MmPduTypeUl;
use tetra_pdus::mm::enums::status_uplink::StatusUplink;
use tetra_pdus::mm::fields::group_identity_attachment::GroupIdentityAttachment;
use tetra_pdus::mm::fields::group_identity_downlink::GroupIdentityDownlink;
use tetra_pdus::mm::fields::group_identity_location_accept::GroupIdentityLocationAccept;
use tetra_pdus::mm::fields::group_identity_uplink::GroupIdentityUplink;
use tetra_pdus::mm::pdus::d_attach_detach_group_identity_acknowledgement::DAttachDetachGroupIdentityAcknowledgement;
use tetra_pdus::mm::pdus::d_location_update_accept::DLocationUpdateAccept;
use tetra_pdus::mm::pdus::u_attach_detach_group_identity::UAttachDetachGroupIdentity;
use tetra_pdus::mm::pdus::u_itsi_detach::UItsiDetach;
use tetra_pdus::mm::pdus::u_location_update_demand::ULocationUpdateDemand;
use tetra_pdus::mm::pdus::u_mm_status::UMmStatus;
pub struct MmBs {
config: SharedConfig,

View File

@@ -1,11 +1,11 @@
use crate::config::stack_config::SharedConfig;
use crate::common::messagerouter::MessageQueue;
use crate::saps::sapmsg::{SapMsg, SapMsgInner};
use crate::entities::mm::enums::mm_pdu_type_dl::MmPduTypeDl;
use crate::entities::TetraEntityTrait;
use crate::common::tetra_common::{Sap};
use crate::common::tetra_entities::TetraEntity;
use crate::unimplemented_log;
use tetra_config::SharedConfig;
use tetra_core::tetra_entities::TetraEntity;
use tetra_core::{Sap, unimplemented_log};
use crate::{MessageQueue, TetraEntityTrait};
use tetra_saps::{SapMsg, SapMsgInner};
use tetra_pdus::mm::enums::mm_pdu_type_dl::MmPduTypeDl;
pub struct MmMs {
// config: Option<SharedConfig>,

View File

@@ -1,7 +1,4 @@
pub mod components;
pub mod enums;
pub mod fields;
pub mod pdus;
pub mod mm_ms;
pub mod mm_bs;

View File

@@ -1,10 +1,12 @@
use num;
use num::complex::ComplexFloat;
use crate::common::tdma_time::TdmaTime;
use crate::entities::phy::components::train_consts;
use crate::entities::phy::traits::rxtx_dev::{RxBurstBits, RxSlotBits};
use crate::entities::phy::enums::burst::TrainingSequence;
use tetra_core::TdmaTime;
use tetra_core::TrainingSequence;
use tetra_pdus::phy::traits::rxtx_dev::RxBurstBits;
use tetra_pdus::phy::traits::rxtx_dev::RxSlotBits;
use crate::phy::components::train_consts;
use super::dsp_types::*;
use super::fir;

View File

@@ -1,9 +1,12 @@
use crate::common::tdma_time::TdmaTime;
use crate::entities::phy::components::fir;
use crate::entities::phy::traits::rxtx_dev::TxSlotBits;
use super::dsp_types::*;
use super::modem_common::*;
use tetra_core::TdmaTime;
use tetra_pdus::phy::traits::rxtx_dev::TxSlotBits;
use crate::phy::components::fir;
use crate::phy::components::dsp_types::*;
use crate::phy::components::modem_common::*;
/// Samples per symbol

View File

@@ -1,4 +1,7 @@
use crate::entities::phy::{components::{burst_consts::*, train_consts::*}, enums::burst::TrainingSequence};
use tetra_core::TrainingSequence;
use crate::phy::components::{burst_consts::*, train_consts::*};
#[allow(non_upper_case_globals)]
pub mod bitseq {
@@ -191,7 +194,9 @@ pub fn build_ndb(train_seq: TrainingSequence, blk1: &[u8;NDB_BLK_BITS], bbk: &[u
#[cfg(test)]
mod tests {
use crate::common::bitbuffer::BitBuffer;
use tetra_core::bitbuffer::BitBuffer;
use crate::phy::components::train_consts::*;
use super::*;

View File

@@ -2,9 +2,15 @@
//! between SDR device and modulator/demodulator code.
use rustfft;
use crate::config::stack_config::{SharedConfig, StackMode};
use crate::entities::phy::components::soapy_dev;
use crate::entities::phy::traits::rxtx_dev::{RxTxDev, RxTxDevError, TxSlotBits, RxSlotBits};
use tetra_config::SharedConfig;
use tetra_config::StackMode;
use tetra_pdus::phy::traits::rxtx_dev::RxSlotBits;
use tetra_pdus::phy::traits::rxtx_dev::RxTxDev;
use tetra_pdus::phy::traits::rxtx_dev::RxTxDevError;
use tetra_pdus::phy::traits::rxtx_dev::TxSlotBits;
use crate::phy::components::soapy_dev;
use super::demodulator;
use super::modulator;

View File

@@ -1,7 +1,8 @@
use soapysdr;
use tetra_config::SharedConfig;
use tetra_pdus::phy::traits::rxtx_dev::RxTxDevError;
use crate::config::stack_config::SharedConfig;
use crate::entities::phy::traits::rxtx_dev::RxTxDevError;
use super::dsp_types;
use super::soapy_time::{ticks_to_time_ns, time_ns_to_ticks};
use super::dsp_types::*;

View File

@@ -1,5 +1,3 @@
pub mod enums;
pub mod components;
pub mod traits;
pub mod phy_bs;

View File

@@ -1,24 +1,20 @@
use std::panic;
use crate::config::stack_config::SharedConfig;
use crate::common::messagerouter::MessageQueue;
use crate::entities::phy::components::burst_consts::*;
use crate::entities::phy::components::phy_io_file::{PhyIoFileMode, FileWriteMsg};
use crate::entities::phy::components::train_consts::TIMESLOT_TYPE4_BITS;
use crate::saps::sapmsg::{SapMsg, SapMsgInner};
use crate::saps::tp::TpUnitdataInd;
use crate::common::bitbuffer::BitBuffer;
use crate::common::tdma_time::TdmaTime;
use crate::common::tetra_common::Sap;
use crate::common::tetra_entities::TetraEntity;
use crate::entities::phy::enums::burst::*;
use crate::entities::phy::components::slotter::{build_ndb, build_sdb};
use crate::entities::phy::traits::rxtx_dev::RxBurstBits;
use crate::entities::TetraEntityTrait;
use crate::entities::umac::subcomp::bs_sched::MACSCHED_TX_AHEAD;
use crossbeam_channel::Sender;
use super::traits::rxtx_dev::{RxTxDev, TxSlotBits};
use tetra_config::SharedConfig;
use tetra_core::{BitBuffer, BurstType, PhyBlockNum, PhyBlockType, Sap, TdmaTime, TrainingSequence};
use tetra_core::tetra_entities::TetraEntity;
use tetra_saps::{SapMsg, SapMsgInner};
use tetra_saps::tp::TpUnitdataInd;
use tetra_pdus::phy::traits::rxtx_dev::RxBurstBits;
use tetra_pdus::phy::traits::rxtx_dev::{RxTxDev, TxSlotBits};
use crate::{MessageQueue, TetraEntityTrait};
use crate::phy::components::{burst_consts::*, train_consts::*, slotter};
use crate::phy::components::phy_io_file::{FileWriteMsg, PhyIoFileMode};
use crate::umac::subcomp::bs_sched::MACSCHED_TX_AHEAD;
use super::components::phy_io_file::PhyIoFile;
pub struct PhyBs<D: RxTxDev> {
@@ -171,7 +167,7 @@ impl <D: RxTxDev>PhyBs<D> {
prim.blk1.unwrap().to_bitarr(&mut blk1); // Guaranteed for SDB
prim.blk2.unwrap().to_bitarr(&mut blk2); // Guaranteed for SDB
build_sdb(&blk1, &bbk, &blk2)
slotter::build_sdb(&blk1, &bbk, &blk2)
}
BurstType::NDB => {
@@ -195,7 +191,7 @@ impl <D: RxTxDev>PhyBs<D> {
_ => panic!("Unsupported training sequence for NDB burst")
}
build_ndb(prim.train_type, &blk1, &bbk, &blk2)
slotter::build_ndb(prim.train_type, &blk1, &bbk, &blk2)
}
_ => panic!()
};

View File

@@ -1,10 +1,9 @@
use crate::common::messagerouter::MessageQueue;
use crate::common::tetra_common::Sap;
use crate::common::tetra_entities::TetraEntity;
use crate::config::stack_config::SharedConfig;
use crate::entities::TetraEntityTrait;
use crate::saps::sapmsg::SapMsg;
use crate::unimplemented_log;
use tetra_config::SharedConfig;
use tetra_core::{Sap, unimplemented_log};
use tetra_core::tetra_entities::TetraEntity;
use crate::{MessageQueue, TetraEntityTrait};
use tetra_saps::SapMsg;
pub struct Sndcp {
// config: Option<SharedConfig>,

View File

@@ -1,6 +1,3 @@
pub mod enums;
pub mod fields;
pub mod pdus;
pub mod subcomp;
pub mod umac_bs;

View File

@@ -1,6 +1,10 @@
use crate::{common::{address::TetraAddress, bitbuffer::BitBuffer, tdma_time::TdmaTime, tetra_common::Todo}, entities::umac::subcomp::defrag::{DefragBuffer, DefragBufferState}};
use std::collections::HashMap;
use tetra_core::{BitBuffer, TdmaTime, TetraAddress, Todo};
use crate::umac::subcomp::defrag::{DefragBuffer, DefragBufferState};
const DEFRAG_BUF_MAX_LEN: usize = 4096;
const DEFRAG_TS_BEFORE_TIMEOUT: i32 = 10*4; // TODO check documentation. 10 frames.
@@ -150,7 +154,7 @@ impl BsDefrag {
#[cfg(test)]
mod tests {
use super::*;
use crate::common::{address::SsiType, bitbuffer::BitBuffer, debug};
use tetra_core::{address::SsiType, bitbuffer::BitBuffer, debug};
#[test]

View File

@@ -1,7 +1,12 @@
use std::cmp::min;
use crate::entities::umac::{pdus::{mac_end_dl::MacEndDl, mac_frag_dl::MacFragDl, mac_resource::MacResource}, subcomp::fillbits};
use crate::common::bitbuffer::BitBuffer;
use tetra_core::BitBuffer;
use tetra_pdus::umac::pdus::{mac_end_dl::MacEndDl, mac_frag_dl::MacFragDl, mac_resource::MacResource};
use crate::umac::subcomp::fillbits;
#[derive(Debug)]
@@ -152,8 +157,6 @@ impl BsFragger {
let sdu_bits_in_frag = min(slot_cap - macfrag_hdr_len, sdu_bits);
let num_fill_bits = slot_cap - macfrag_hdr_len - sdu_bits_in_frag;
let pdu = MacFragDl {
fill_bits: num_fill_bits > 0,
};
@@ -191,11 +194,13 @@ impl BsFragger {
}
#[cfg(test)]
mod tests {
use crate::{common::{address::{SsiType, TetraAddress}, bitbuffer::BitBuffer, debug}, entities::umac::{pdus::{mac_end_dl::MacEndDl, mac_frag_dl::MacFragDl, mac_resource::MacResource}, subcomp::{bs_sched::{SCH_F_CAP, SCH_HD_CAP}, bs_frag::BsFragger}}};
use tetra_core::{address::{SsiType, TetraAddress}, debug};
use crate::umac::subcomp::bs_sched::{SCH_F_CAP, SCH_HD_CAP};
use super::*;
fn get_default_resource() -> MacResource {
MacResource {
fill_bits: false,

View File

@@ -1,4 +1,10 @@
use crate::{common::{address::TetraAddress, bitbuffer::BitBuffer, tdma_time::TdmaTime, tetra_common::Todo}, entities::{lmac::components::scramble::SCRAMB_INIT, mle::pdus::{d_mle_sync::DMleSync, d_mle_sysinfo::DMleSysinfo}, phy::enums::burst::PhyBlockNum, umac::{enums::{access_assign_dl_usage::AccessAssignDlUsage, access_assign_ul_usage::AccessAssignUlUsage, basic_slotgrant_cap_alloc::BasicSlotgrantCapAlloc, basic_slotgrant_granting_delay::BasicSlotgrantGrantingDelay, reservation_requirement::ReservationRequirement}, fields::basic_slotgrant::BasicSlotgrant, pdus::{access_assign::{AccessAssign, AccessField}, access_assign_fr18::AccessAssignFr18, mac_resource::MacResource, mac_sync::MacSync, mac_sysinfo::MacSysinfo}, subcomp::{bs_frag::BsFragger, fillbits}}}, saps::tmv::{TmvUnitdataReq, TmvUnitdataReqSlot, enums::logical_chans::LogicalChannel}, unimplemented_log};
use tetra_core::{BitBuffer, PhyBlockNum, TdmaTime, TetraAddress, Todo, unimplemented_log};
use tetra_saps::tmv::{TmvUnitdataReq, TmvUnitdataReqSlot, enums::logical_chans::LogicalChannel};
use tetra_pdus::{mle::pdus::{d_mle_sync::DMleSync, d_mle_sysinfo::DMleSysinfo}, umac::{enums::{access_assign_dl_usage::AccessAssignDlUsage, access_assign_ul_usage::AccessAssignUlUsage, basic_slotgrant_cap_alloc::BasicSlotgrantCapAlloc, basic_slotgrant_granting_delay::BasicSlotgrantGrantingDelay, reservation_requirement::ReservationRequirement}, fields::basic_slotgrant::BasicSlotgrant, pdus::{access_assign::{AccessAssign, AccessField}, access_assign_fr18::AccessAssignFr18, mac_resource::MacResource, mac_sync::MacSync, mac_sysinfo::MacSysinfo}}};
use crate::{lmac::components::scrambler, umac::subcomp::{fillbits, bs_frag::BsFragger}};
/// We submit this many TX timeslots ahead of the current time
pub const MACSCHED_TX_AHEAD: usize = 1;
@@ -830,7 +836,7 @@ impl BsChannelScheduler {
TmvUnitdataReq {
logical_channel: LogicalChannel::Bsch,
mac_block: buf,
scrambling_code: SCRAMB_INIT,
scrambling_code: scrambler::SCRAMB_INIT,
}
},
@@ -921,7 +927,9 @@ impl BsChannelScheduler {
#[cfg(test)]
mod tests {
use crate::{common::{address::{SsiType, TetraAddress}, debug::setup_logging_default}, entities::{mle::fields::bs_service_details::BsServiceDetails, umac::{enums::sysinfo_opt_field_flag::SysinfoOptFieldFlag, fields::{sysinfo_default_def_for_access_code_a::SysinfoDefaultDefForAccessCodeA, sysinfo_ext_services::SysinfoExtendedServices}}}};
use tetra_core::{address::{SsiType, TetraAddress}, debug::setup_logging_default};
use tetra_pdus::{mle::{fields::bs_service_details::BsServiceDetails, pdus::{d_mle_sync::DMleSync, d_mle_sysinfo::DMleSysinfo}}, umac::{enums::sysinfo_opt_field_flag::SysinfoOptFieldFlag, fields::{sysinfo_default_def_for_access_code_a::SysinfoDefaultDefForAccessCodeA, sysinfo_ext_services::SysinfoExtendedServices}, pdus::{mac_sync::MacSync, mac_sysinfo::MacSysinfo}}};
use super::*;
@@ -1160,6 +1168,4 @@ mod tests {
// we dont start fragmenting a second resource before the first one is full sent (and maybe acknowledged?).
unimplemented!("write tests for downlink fragmentation")
}
}

View File

@@ -1,4 +1,5 @@
use crate::common::{address::TetraAddress, bitbuffer::BitBuffer, tdma_time::TdmaTime, tetra_common::Todo};
use tetra_core::{BitBuffer, SsiType, TdmaTime, TetraAddress, Todo};
const DEFRAG_BUF_INITIAL_LEN: usize = 512;
@@ -23,7 +24,7 @@ impl DefragBuffer {
pub fn new() -> Self {
Self {
state: DefragBufferState::Inactive,
addr: TetraAddress::default(),
addr: TetraAddress {ssi: 0, ssi_type: SsiType::Unknown, encrypted: false},
t_first: TdmaTime::default(),
t_last: TdmaTime::default(),
num_frags: 0,
@@ -34,7 +35,7 @@ impl DefragBuffer {
pub fn reset(&mut self) {
self.state = DefragBufferState::Inactive;
self.addr = TetraAddress::default();
self.addr = TetraAddress {ssi: 0, ssi_type: SsiType::Unknown, encrypted: false};
self.t_first = TdmaTime::default();
self.t_last = TdmaTime::default();
self.num_frags = 0;

View File

@@ -1,6 +1,5 @@
use crate::common::address::TetraAddress;
pub type EventLabel = u16;
use tetra_core::TetraAddress;
use tetra_pdus::umac::fields::EventLabel;
pub struct EventLabelMapping {
// pub valid_until: TdmaTime,

View File

@@ -1,6 +1,6 @@
pub mod removal {
use crate::common::bitbuffer::BitBuffer;
use tetra_core::bitbuffer::BitBuffer;
/// Returns the number of fill bits at the end of the PDU in bitbuf, given the total pdu_len_bits.
pub fn get_num_fill_bits(bitbuf: &BitBuffer, pdu_len_bits: usize, suppress_warning: bool) -> usize {
@@ -25,7 +25,7 @@ pub mod removal {
}
pub mod addition {
use crate::common::bitbuffer::BitBuffer;
use tetra_core::bitbuffer::BitBuffer;
/// Compute how many fill bits need to be added in order to reach the next byte boundary

View File

@@ -5,5 +5,5 @@ pub mod bs_sched;
pub mod ms_defrag;
pub mod event_labels;
pub mod event_label_store;
pub mod fillbits;

View File

@@ -1,4 +1,6 @@
use crate::{common::{address::TetraAddress, bitbuffer::BitBuffer, tdma_time::TdmaTime, tetra_common::Todo}, entities::umac::subcomp::defrag::{DefragBuffer, DefragBufferState}};
use tetra_core::{BitBuffer, TdmaTime, TetraAddress, Todo};
use crate::umac::subcomp::defrag::{DefragBuffer, DefragBufferState};
const DEFRAG_BUF_MAX_LEN: usize = 4096;
const DEFRAG_TS_BEFORE_TIMEOUT: i32 = 10*4; // TODO check documentation. 10 frames.
@@ -149,7 +151,7 @@ impl MsDefrag {
#[cfg(test)]
mod tests {
use super::*;
use crate::common::{address::SsiType, bitbuffer::BitBuffer, debug};
use tetra_core::{address::SsiType, bitbuffer::BitBuffer, debug};
#[test]

View File

@@ -1,44 +1,37 @@
use std::panic;
use crate::common::freqs::FreqInfo;
use crate::common::messagerouter::MessageQueue;
use tetra_config::{SharedConfig, StackMode};
use tetra_core::freqs::FreqInfo;
use tetra_core::{BitBuffer, PhyBlockNum, Sap, TdmaTime, Todo, assert_warn, unimplemented_log};
use tetra_core::tetra_entities::TetraEntity;
use tetra_saps::tma::TmaUnitdataInd;
use tetra_saps::tmv::enums::logical_chans::LogicalChannel;
use tetra_saps::{SapMsg, SapMsgInner};
use crate::entities::phy::enums::burst::PhyBlockNum;
use crate::saps::tmv::enums::logical_chans::LogicalChannel;
use tetra_pdus::mle::fields::bs_service_details::BsServiceDetails;
use tetra_pdus::mle::pdus::d_mle_sync::DMleSync;
use tetra_pdus::mle::pdus::d_mle_sysinfo::DMleSysinfo;
use tetra_pdus::umac::enums::broadcast_type::BroadcastType;
use tetra_pdus::umac::enums::mac_pdu_type::MacPduType;
use tetra_pdus::umac::enums::sysinfo_opt_field_flag::SysinfoOptFieldFlag;
use tetra_pdus::umac::fields::sysinfo_default_def_for_access_code_a::SysinfoDefaultDefForAccessCodeA;
use tetra_pdus::umac::fields::sysinfo_ext_services::SysinfoExtendedServices;
use tetra_pdus::umac::pdus::access_define::AccessDefine;
use tetra_pdus::umac::pdus::mac_access::MacAccess;
use tetra_pdus::umac::pdus::mac_data::MacData;
use tetra_pdus::umac::pdus::mac_end_hu::MacEndHu;
use tetra_pdus::umac::pdus::mac_end_ul::MacEndUl;
use tetra_pdus::umac::pdus::mac_frag_ul::MacFragUl;
use tetra_pdus::umac::pdus::mac_resource::MacResource;
use tetra_pdus::umac::pdus::mac_sync::MacSync;
use tetra_pdus::umac::pdus::mac_sysinfo::MacSysinfo;
use tetra_pdus::umac::pdus::mac_u_blck::MacUBlck;
use tetra_pdus::umac::pdus::mac_u_signal::MacUSignal;
use crate::saps::tma::TmaUnitdataInd;
use crate::common::bitbuffer::BitBuffer;
use crate::common::tdma_time::TdmaTime;
use crate::common::tetra_common::{Sap, Todo};
use crate::common::tetra_entities::TetraEntity;
use crate::entities::mle::fields::bs_service_details::BsServiceDetails;
use crate::entities::mle::pdus::d_mle_sync::DMleSync;
use crate::entities::mle::pdus::d_mle_sysinfo::DMleSysinfo;
use crate::entities::TetraEntityTrait;
use crate::saps::sapmsg::{SapMsg, SapMsgInner};
use crate::config::stack_config::*;
use crate::entities::umac::enums::broadcast_type::BroadcastType;
use crate::entities::umac::enums::mac_pdu_type::MacPduType;
use crate::entities::umac::enums::sysinfo_opt_field_flag::SysinfoOptFieldFlag;
use crate::entities::umac::fields::sysinfo_default_def_for_access_code_a::SysinfoDefaultDefForAccessCodeA;
use crate::entities::umac::fields::sysinfo_ext_services::SysinfoExtendedServices;
use crate::entities::umac::pdus::access_define::AccessDefine;
use crate::entities::umac::pdus::mac_access::MacAccess;
use crate::entities::umac::pdus::mac_data::MacData;
use crate::entities::umac::pdus::mac_end_hu::MacEndHu;
use crate::entities::umac::pdus::mac_end_ul::MacEndUl;
use crate::entities::umac::pdus::mac_frag_ul::MacFragUl;
use crate::entities::umac::pdus::mac_resource::MacResource;
use crate::entities::umac::pdus::mac_sync::MacSync;
use crate::entities::umac::pdus::mac_sysinfo::MacSysinfo;
use crate::entities::umac::pdus::mac_u_blck::MacUBlck;
use crate::entities::umac::pdus::mac_u_signal::MacUSignal;
use crate::entities::umac::subcomp::event_labels::EventLabelStore;
use crate::entities::umac::subcomp::fillbits;
use crate::entities::umac::subcomp::bs_sched::{BsChannelScheduler, PrecomputedUmacPdus};
use crate::{assert_warn, unimplemented_log};
use crate::{MessageQueue, TetraEntityTrait};
use crate::lmac::components::scrambler;
use crate::umac::subcomp::bs_sched::{BsChannelScheduler, PrecomputedUmacPdus};
use crate::umac::subcomp::fillbits;
use super::subcomp::bs_defrag::BsDefrag;
@@ -58,7 +51,7 @@ pub struct UmacBs {
/// Subcomponents
defrag: BsDefrag,
event_label_store: EventLabelStore,
// event_label_store: EventLabelStore,
/// Contains UL/DL scheduling logic
/// Access to this field is used only by testing code
@@ -68,15 +61,15 @@ pub struct UmacBs {
impl UmacBs {
pub fn new(config: SharedConfig) -> Self {
let scrambling_code = config.config().scrambling_code();
let c = config.config();
let scrambling_code = scrambler::tetra_scramb_get_init(c.net.mcc, c.net.mnc, c.cell.colour_code);
let precomps = Self::generate_precomps(&config);
Self {
self_component: TetraEntity::Umac,
config,
endpoint_id: 0,
defrag: BsDefrag::new(),
event_label_store: EventLabelStore::new(),
// event_label_store: EventLabelStore::new(),
channel_scheduler: BsChannelScheduler::new(scrambling_code, precomps),
}
}
@@ -420,9 +413,12 @@ impl UmacBs {
}
};
// Get addr, either from pdu addr field or by resolving the event label
if pdu.event_label.is_some() {
tracing::warn!("event labels not implemented");
unimplemented_log!("event labels not implemented");
return;
}
let addr = pdu.addr.unwrap();
// Compute len and extract flags
let (mut pdu_len_bits, is_frag_start, second_half_stolen, is_null_pdu) = {
@@ -512,10 +508,10 @@ impl UmacBs {
if let Some(res_req) = &pdu.reservation_req {
tracing::error!("rx_mac_data: time {:?} ul {:?}", message.dltime, ul_time);
let grant = self.channel_scheduler.ul_process_cap_req(ul_time.t, pdu.addr, res_req);
let grant = self.channel_scheduler.ul_process_cap_req(ul_time.t, addr, res_req);
if let Some(grant) = grant {
// Schedule grant
self.channel_scheduler.dl_enqueue_grant(ul_time.t, pdu.addr, grant);
self.channel_scheduler.dl_enqueue_grant(ul_time.t, addr, grant);
} else {
tracing::warn!("rx_mac_data: No grant for reservation request {:?}", res_req);
}
@@ -525,7 +521,7 @@ impl UmacBs {
tracing::debug!("rx_mac_data: {}", prim.pdu.dump_bin_full(true));
if is_frag_start {
// Fragmentation start, add to defragmenter
self.defrag.insert_first(&mut prim.pdu, ul_time, pdu.addr, None);
self.defrag.insert_first(&mut prim.pdu, ul_time, addr, None);
} else if second_half_stolen {
@@ -565,7 +561,7 @@ impl UmacBs {
msg: SapMsgInner::TmaUnitdataInd(
TmaUnitdataInd {
pdu: sdu,
main_address: pdu.addr,
main_address: addr,
scrambling_code: prim.scrambling_code,
endpoint_id: 0, // TODO FIXME
new_endpoint_id: None, // TODO FIXME
@@ -611,15 +607,16 @@ impl UmacBs {
};
// Resolve event label (if supplied)
let addr = if let Some(label) = pdu.event_label {
let addr = if let Some(_label) = pdu.event_label {
tracing::warn!("event labels not implemented");
let ret = self.event_label_store.get_addr_by_label(label);
if let Some(ssi) = ret {
ssi
} else {
tracing::warn!("Could not resolve event label for {}", label);
return;
}
return;
// let ret = self.event_label_store.get_addr_by_label(label);
// if let Some(ssi) = ret {
// ssi
// } else {
// tracing::warn!("Could not resolve event label for {}", label);
// return;
// }
} else if let Some(addr) = pdu.addr {
addr
} else { panic!() };

View File

@@ -1,34 +1,28 @@
use std::panic;
use crate::common::messagerouter::{MessagePrio, MessageQueue};
use tetra_config::SharedConfig;
use tetra_core::{BitBuffer, PhyBlockNum, Sap, TdmaTime, Todo, unimplemented_log};
use tetra_core::tetra_entities::TetraEntity;
use tetra_saps::tma::TmaUnitdataInd;
use tetra_saps::tmv::enums::logical_chans::LogicalChannel;
use tetra_saps::tmv::TmvConfigureReq;
use tetra_saps::tlmb::{TlmbSysinfoInd};
use tetra_saps::{SapMsg, SapMsgInner};
use crate::saps::tlmb::TlmbSysinfoInd;
use crate::saps::tmv::enums::logical_chans::LogicalChannel;
use crate::saps::tma::TmaUnitdataInd;
use crate::entities::phy::enums::burst::PhyBlockNum;
use crate::saps::tmv::TmvConfigureReq;
use tetra_pdus::umac::enums::broadcast_type::BroadcastType;
use tetra_pdus::umac::enums::mac_pdu_type::MacPduType;
use tetra_pdus::umac::pdus::access_assign::AccessAssign;
use tetra_pdus::umac::pdus::access_assign_fr18::AccessAssignFr18;
use tetra_pdus::umac::pdus::mac_end_dl::MacEndDl;
use tetra_pdus::umac::pdus::mac_frag_dl::MacFragDl;
use tetra_pdus::umac::pdus::mac_resource::MacResource;
use tetra_pdus::umac::pdus::mac_sync::MacSync;
use tetra_pdus::umac::pdus::mac_sysinfo::MacSysinfo;
use crate::common::bitbuffer::BitBuffer;
use crate::common::tdma_time::TdmaTime;
use crate::{MessagePrio, MessageQueue, TetraEntityTrait};
use crate::umac::subcomp::fillbits;
use crate::umac::subcomp::ms_defrag::MsDefrag;
use crate::common::tetra_common::{Sap, Todo};
use crate::common::tetra_entities::TetraEntity;
use crate::entities::TetraEntityTrait;
use crate::saps::sapmsg::{SapMsg, SapMsgInner};
use crate::config::stack_config::*;
use crate::entities::umac::enums::broadcast_type::BroadcastType;
use crate::entities::umac::enums::mac_pdu_type::MacPduType;
use crate::entities::umac::pdus::access_assign::AccessAssign;
use crate::entities::umac::pdus::access_assign_fr18::AccessAssignFr18;
use crate::entities::umac::pdus::mac_end_dl::MacEndDl;
use crate::entities::umac::pdus::mac_frag_dl::MacFragDl;
use crate::entities::umac::pdus::mac_resource::MacResource;
use crate::entities::umac::pdus::mac_sync::MacSync;
use crate::entities::umac::pdus::mac_sysinfo::MacSysinfo;
use crate::unimplemented_log;
use super::subcomp::ms_defrag::MsDefrag;
use super::subcomp::fillbits;
pub struct UmacMs {

View File

@@ -1,24 +1,23 @@
use crate::common::freqs::FreqInfo;
use crate::common::tdma_time::TdmaTime;
use crate::config::stack_config::{CfgCellInfo, CfgNetInfo, CfgPhyIo, PhyBackend, SharedConfig, StackConfig, StackMode, StackState};
use crate::common::messagerouter::MessageRouter;
use crate::entities::cmce::cmce_ms::CmceMs;
use crate::entities::TetraEntityTrait;
use crate::saps::sapmsg::SapMsg;
use crate::common::tetra_entities::TetraEntity;
use tetra_core::freqs::FreqInfo;
use tetra_core::tetra_entities::TetraEntity;
use tetra_core::TdmaTime;
use tetra_config::{CfgCellInfo, CfgNetInfo, CfgPhyIo, PhyBackend, SharedConfig, StackConfig, StackMode, StackState};
use tetra_entities::{MessageRouter, TetraEntityTrait};
use tetra_saps::sapmsg::SapMsg;
// BS imports
use crate::entities::cmce::cmce_bs::CmceBs;
use crate::entities::mle::mle_bs_ms::Mle;
use crate::entities::sndcp::sndcp_bs::Sndcp;
use crate::entities::lmac::lmac_bs::LmacBs;
use crate::entities::mm::mm_bs::MmBs;
use crate::entities::llc::llc_bs_ms::Llc;
use crate::entities::umac::umac_bs::UmacBs;
use tetra_entities::cmce::cmce_bs::CmceBs;
use tetra_entities::cmce::cmce_ms::CmceMs;
use tetra_entities::mle::mle_bs_ms::Mle;
use tetra_entities::sndcp::sndcp_bs::Sndcp;
use tetra_entities::lmac::lmac_bs::LmacBs;
use tetra_entities::mm::mm_bs::MmBs;
use tetra_entities::llc::llc_bs_ms::Llc;
use tetra_entities::umac::umac_bs::UmacBs;
// MS imports
use crate::entities::umac::umac_ms::UmacMs;
use crate::entities::lmac::lmac_ms::LmacMs;
use tetra_entities::umac::umac_ms::UmacMs;
use tetra_entities::lmac::lmac_ms::LmacMs;
use super::sink::Sink;

View File

@@ -0,0 +1,6 @@
#![allow(dead_code)]
pub mod component_test;
pub mod sink;
pub use component_test::{ComponentTest, default_test_config};

View File

@@ -1,4 +1,6 @@
use crate::{common::messagerouter::MessageQueue, saps::sapmsg::SapMsg, common::{tetra_entities::TetraEntity}, entities::TetraEntityTrait};
use tetra_core::tetra_entities::TetraEntity;
use tetra_entities::{MessageQueue, TetraEntityTrait};
use tetra_saps::sapmsg::SapMsg;
/// A TETRA component sink for testing purposes
/// Collects all received SapMsg messages for later inspection

View File

@@ -0,0 +1,59 @@
mod common;
use tetra_core::{BitBuffer, debug, Sap, SsiType, TdmaTime, TetraAddress};
use tetra_core::tetra_entities::TetraEntity;
use tetra_config::StackMode;
use tetra_saps::sapmsg::{SapMsg, SapMsgInner};
use tetra_saps::tma::TmaUnitdataInd;
use common::{ComponentTest, default_test_config};
#[test]
fn test_udata_with_broken_mm_payload() {
// INCOMPLETE VECTOR replace with something more meaningful
debug::setup_logging_verbose();
// FIXME make proper vec here that can be passed onwards
let test_vec = "00011001011100111000000011111100001000010000000000000000"; // INCOMPLETE
let time_vec = TdmaTime::default().add_timeslots(2); // Uplink time: 0/1/1/1
let test_prim = TmaUnitdataInd {
pdu: Some(BitBuffer::from_bitstr(test_vec)),
main_address: TetraAddress{ ssi: 2065022, ssi_type: SsiType::Issi, encrypted: false },
scrambling_code: 864282631,
endpoint_id: 0,
new_endpoint_id: None,
css_endpoint_id: None,
air_interface_encryption: 0,
chan_change_response_req: false,
chan_change_handle: None,
chan_info: None};
let test_sapmsg = SapMsg {
sap: Sap::TmaSap,
src: TetraEntity::Umac,
dest: TetraEntity::Llc,
dltime: time_vec,
msg: SapMsgInner::TmaUnitdataInd(test_prim)};
// Setup testing stack
let config = default_test_config(StackMode::Bs);
let mut test = ComponentTest::new(config, Some(time_vec));
let components = vec![
TetraEntity::Llc,
TetraEntity::Mle,
TetraEntity::Mm,
];
let sinks: Vec<TetraEntity> = vec![
TetraEntity::Umac,
];
test.populate_entities(components, sinks);
// Submit and process message
test.submit_message(test_sapmsg);
test.run_stack(Some(1));
let sink_msgs = test.dump_sinks();
// Evaluate results
assert_eq!(sink_msgs.len(), 1);
tracing::warn!("Validation of result not implemented");
}

View File

@@ -0,0 +1,49 @@
mod common;
use tetra_core::{BitBuffer, debug, Sap, SsiType, TdmaTime, TetraAddress};
use tetra_core::tetra_entities::TetraEntity;
use tetra_config::StackMode;
use tetra_saps::lmm::LmmMleUnitdataInd;
use tetra_saps::sapmsg::{SapMsg, SapMsgInner};
use common::{ComponentTest, default_test_config};
#[test]
fn test_unsupported_u_mm_status() {
// Motorola requesting power management
debug::setup_logging_verbose();
let test_vec1 = "00110000010010";
let time_vec1 = TdmaTime::default().add_timeslots(2); // Uplink time: 0/1/1/1, dl time 0/1/1/3
let test_prim1 = LmmMleUnitdataInd{
sdu: BitBuffer::from_bitstr(test_vec1),
handle: 0,
received_address: TetraAddress { encrypted: false, ssi_type: SsiType::Issi, ssi: 2040814 },
};
let test_sapmsg1 = SapMsg {
sap: Sap::LmmSap,
src: TetraEntity::Mle,
dest: TetraEntity::Mm,
dltime: time_vec1,
msg: SapMsgInner::LmmMleUnitdataInd(test_prim1)};
// Setup testing stack
let config = default_test_config(StackMode::Bs);
let mut test = ComponentTest::new(config, Some(time_vec1));
let components = vec![
TetraEntity::Mm,
];
let sinks: Vec<TetraEntity> = vec![
TetraEntity::Mle,
];
test.populate_entities(components, sinks);
// Submit and process message
test.submit_message(test_sapmsg1);
test.run_stack(Some(1));
let sink_msgs = test.dump_sinks();
// Evaluate results. We should have an MM message in the sink
assert_eq!(sink_msgs.len(), 1);
tracing::info!("We have the expected MM message, but full validation of result not implemented");
}

View File

@@ -0,0 +1,122 @@
mod common;
use tetra_core::debug;
use tetra_config::{PhyBackend, SharedConfig, StackMode};
use tetra_config::stack_config_soapy::{CfgSoapySdr, LimeSdrCfg, UsrpB2xxCfg};
use tetra_entities::{MessageRouter, TetraEntityTrait};
use tetra_entities::mle::mle_bs_ms::Mle;
use tetra_entities::lmac::lmac_bs::LmacBs;
use tetra_entities::mm::mm_bs::MmBs;
use tetra_entities::llc::llc_bs_ms::Llc;
use tetra_entities::phy::components::soapy_dev::RxTxDevSoapySdr;
use tetra_entities::phy::phy_bs::PhyBs;
use tetra_entities::umac::umac_bs::UmacBs;
use common::{ComponentTest, default_test_config};
const DL_FREQ: f64 = 438.025e6;
const UL_FREQ: f64 = DL_FREQ - 5.0e6;
/// Builds a message router with the necessary components for a base station stack.
#[allow(dead_code)]
fn build_bs_stack_components(
shared_config: &SharedConfig,
phy_component: Box<dyn TetraEntityTrait>,
) -> MessageRouter {
let mut router = MessageRouter::new(shared_config.clone());
let lmac = LmacBs::new(shared_config.clone());
let umac = UmacBs::new(shared_config.clone());
let llc = Llc::new(shared_config.clone());
let mle = Mle::new(shared_config.clone());
let mm: MmBs = MmBs::new(shared_config.clone());
router.register_entity(phy_component);
router.register_entity(Box::new(lmac));
router.register_entity(Box::new(umac));
router.register_entity(Box::new(llc));
router.register_entity(Box::new(mle));
router.register_entity(Box::new(mm));
router
}
/// Calls tick() on all components and subsequently delivers all messages
/// Either infinitely (num_ticks is None) or for a specified number of ticks.
#[allow(dead_code)]
fn run_stack(_config: &mut SharedConfig, router: &mut MessageRouter, num_ticks: Option<u64>) {
let mut ticks: u64 = 0;
loop {
router.tick_all();
router.deliver_all_messages();
ticks += 1;
if let Some(num_ticks) = num_ticks {
if ticks >= num_ticks {
break;
}
}
}
}
#[test]
#[ignore] // Requires LimeSDR hardware
fn test_limesdr_bs() {
// Setup logging and make default stack configuration
debug::setup_logging_default(None);
let mut raw_config = default_test_config(StackMode::Bs);
// Update default config to suit our needs
raw_config.phy_io.backend = PhyBackend::SoapySdr;
let mut soapy_cfg = CfgSoapySdr::default();
soapy_cfg.ul_freq = UL_FREQ;
soapy_cfg.dl_freq = DL_FREQ;
soapy_cfg.io_cfg.iocfg_limesdr = Some(LimeSdrCfg {
rx_ant: None,
tx_ant: None,
rx_gain_lna: None,
rx_gain_tia: None,
rx_gain_pga: None,
tx_gain_pad: None,
tx_gain_iamp: None
});
raw_config.phy_io.soapysdr = Some(soapy_cfg);
let mut test = ComponentTest::new(raw_config, None);
// Create PHY and insert it into the message router
let rxdev = RxTxDevSoapySdr::new(&test.config);
let phy = PhyBs::new(test.config.clone(), rxdev);
test.register_entity(phy);
test.run_stack(None);
}
#[test]
#[ignore] // Requires USRP hardware
fn test_usrp_bs() {
// Setup logging and make default stack configuration
debug::setup_logging_default(None);
let mut raw_config = default_test_config(StackMode::Bs);
// Update default config to suit our needs
raw_config.phy_io.backend = PhyBackend::SoapySdr;
let mut soapy_cfg = CfgSoapySdr::default();
soapy_cfg.ul_freq = UL_FREQ;
soapy_cfg.dl_freq = DL_FREQ;
soapy_cfg.io_cfg.iocfg_usrpb2xx = Some(UsrpB2xxCfg {
rx_ant: None,
tx_ant: None,
rx_gain_pga: None,
tx_gain_pga: None,
});
raw_config.phy_io.soapysdr = Some(soapy_cfg);
let mut test = ComponentTest::new(raw_config, None);
// Create PHY and insert it into the message router
let rxdev = RxTxDevSoapySdr::new(&test.config);
let phy = PhyBs::new(test.config.clone(), rxdev);
test.register_entity(phy);
test.run_stack(None);
}

View File

@@ -0,0 +1,179 @@
mod common;
use tetra_core::{BitBuffer, debug, PhyBlockNum, Sap, SsiType, TdmaTime, TetraAddress};
use tetra_core::tetra_entities::TetraEntity;
use tetra_config::StackMode;
use tetra_saps::lmm::LmmMleUnitdataReq;
use tetra_saps::sapmsg::{SapMsg, SapMsgInner};
use tetra_saps::tmv::{TmvUnitdataInd, enums::logical_chans::LogicalChannel};
use common::{ComponentTest, default_test_config};
#[test]
fn test_in_fragmented_sch_hu_and_sch_f() {
// Receive SCH/HU containing MAC-ACCESS with fragmentation start
// Then receive SCH-F containing MAC-END (UL)
debug::setup_logging_verbose();
let test_vec1 = "00000000111111000001001111110111000100011001011100111000000011111100001000010000000000000000";
let test_vec2 = "0110001110000000000010010000000000000000000000000100010000000000000000000000000110010000000000000000000000001000001000000111111000001001111110000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
let time_vec1 = TdmaTime::default().add_timeslots(2); // Uplink time: 0/1/1/1, dl time 0/1/1/3
let test_prim1 = TmvUnitdataInd {
pdu: BitBuffer::from_bitstr(test_vec1),
block_num: PhyBlockNum::Block1,
logical_channel: LogicalChannel::SchHu,
crc_pass: true,
scrambling_code: 864282631};
let test_sapmsg1 = SapMsg {
sap: Sap::TmvSap,
src: TetraEntity::Lmac,
dest: TetraEntity::Umac,
dltime: time_vec1,
msg: SapMsgInner::TmvUnitdataInd(test_prim1)};
let test_prim2 = TmvUnitdataInd {
pdu: BitBuffer::from_bitstr(test_vec2),
block_num: PhyBlockNum::Both,
logical_channel: LogicalChannel::SchF,
crc_pass: true,
scrambling_code: 864282631};
let test_sapmsg2 = SapMsg {
sap: Sap::TmvSap,
src: TetraEntity::Lmac,
dest: TetraEntity::Umac,
dltime: time_vec1.add_timeslots(4), // Uplink time: 0/1/2/1
msg: SapMsgInner::TmvUnitdataInd(test_prim2)};
// Setup testing stack
let config = default_test_config(StackMode::Bs);
let mut test = ComponentTest::new(config, Some(time_vec1));
let components = vec![
TetraEntity::Umac,
TetraEntity::Llc,
TetraEntity::Mle,
];
let sinks: Vec<TetraEntity> = vec![
// TetraEntity::Lmac, // Simply discard
TetraEntity::Mm,
];
test.populate_entities(components, sinks);
// Submit and process message
test.submit_message(test_sapmsg1);
test.run_stack(Some(4));
test.submit_message(test_sapmsg2);
test.run_stack(Some(1));
let sink_msgs = test.dump_sinks();
// Evaluate results. We should have an MM message in the sink
assert_eq!(sink_msgs.len(), 1);
tracing::info!("We have the expected MM message, but full validation of result not implemented");
}
#[test]
fn test_in_fragmented_sch_hu_and_sch_hu() {
// Receive SCH/HU containing MAC-ACCESS with fragmentation start
// Then receive SCH-HU containing MAC-END-HU
// Message ultimately contains CMCE SDS message
debug::setup_logging_verbose();
let test_vec1 = "00000000111110010001111101110111000000010010011110000010000001100010001001001111100001010100";
let test_vec2 = "10011000000101000110000000000000000000000000000000000000000000000000111111111111110100000010";
let time_vec1 = TdmaTime::default().add_timeslots(2); // Uplink time: 0/1/1/1, dl time 0/1/1/3
let test_prim1 = TmvUnitdataInd {
pdu: BitBuffer::from_bitstr(test_vec1),
block_num: PhyBlockNum::Block1,
logical_channel: LogicalChannel::SchHu,
crc_pass: true,
scrambling_code: 864282631};
let test_sapmsg1 = SapMsg {
sap: Sap::TmvSap,
src: TetraEntity::Lmac,
dest: TetraEntity::Umac,
dltime: time_vec1,
msg: SapMsgInner::TmvUnitdataInd(test_prim1)};
let test_prim2 = TmvUnitdataInd {
pdu: BitBuffer::from_bitstr(test_vec2),
block_num: PhyBlockNum::Block1,
logical_channel: LogicalChannel::SchHu,
crc_pass: true,
scrambling_code: 864282631};
let test_sapmsg2 = SapMsg {
sap: Sap::TmvSap,
src: TetraEntity::Lmac,
dest: TetraEntity::Umac,
dltime: time_vec1.add_timeslots(4), // Uplink time: 0/1/2/1
msg: SapMsgInner::TmvUnitdataInd(test_prim2)};
// Setup testing stack
let config = default_test_config(StackMode::Bs);
let mut test = ComponentTest::new(config, Some(time_vec1));
let components = vec![
TetraEntity::Umac,
TetraEntity::Llc,
TetraEntity::Mle,
];
let sinks: Vec<TetraEntity> = vec![
// TetraEntity::Lmac, // Simply discard
TetraEntity::Cmce,
];
test.populate_entities(components, sinks);
// Submit and process message
test.submit_message(test_sapmsg1);
test.run_stack(Some(4));
test.submit_message(test_sapmsg2);
test.run_stack(Some(1));
// Evaluate results. We should have an CMCE message in the sink
let sink_msgs = test.dump_sinks();
assert_eq!(sink_msgs.len(), 1);
tracing::info!("We have the expected CMCE message, but full validation of result not implemented");
}
#[test]
fn test_out_fragmented_resource() {
// Test for UMAC (and LLC/MLE)
// The vector is an MM DAttachDetachGroupIdentityAcknowledgement which contains a lot of groups.
// As it is very large, it needs to be fragmented at the MAC layer.
debug::setup_logging_verbose();
let test_vec = "10110011011100110100110001101011100000000000011101010011001110110100000000000111010100111111101101000000000001110101010000000011010000000000011101010100000010110100000000000111010101000001001101000000000001110101010000011011010000000000011101010100001000110100000000000111010101000010101101000000000001110101010000110011010000000000011101010100001110110100000000000111010101000100001101000000000001110101010001001011010000000000011101010100010100";
let test_t = TdmaTime::default().add_timeslots(2); // Uplink time: 0/1/1/1, dl time 0/1/1/3
let test_prim = LmmMleUnitdataReq {
sdu: BitBuffer::from_bitstr(test_vec),
handle: 0,
address: TetraAddress { encrypted: false, ssi_type: SsiType::Ssi, ssi: 30128 },
layer2service: 0,
stealing_permission: false,
stealing_repeats_flag: false,
encryption_flag: false,
is_null_pdu: false,
};
let test_sapmsg = SapMsg {
sap: Sap::LmmSap,
src: TetraEntity::Mm,
dest: TetraEntity::Mle,
dltime: test_t,
msg: SapMsgInner::LmmMleUnitdataReq(test_prim)};
// Setup testing stack
let config = default_test_config(StackMode::Bs);
let mut test = ComponentTest::new(config, Some(test_t));
let components = vec![
TetraEntity::Umac,
TetraEntity::Llc,
TetraEntity::Mle,
];
let sinks: Vec<TetraEntity> = vec![
TetraEntity::Lmac,
];
test.populate_entities(components, sinks);
// Submit and process message
test.submit_message(test_sapmsg);
test.run_stack(Some(8));
tracing::info!("Validation of result not implemented");
}

View File

@@ -0,0 +1,243 @@
mod common;
use tetra_core::{BitBuffer, debug, PhyBlockNum, Sap, TdmaTime};
use tetra_core::tetra_entities::TetraEntity;
use tetra_config::StackMode;
use tetra_saps::sapmsg::{SapMsg, SapMsgInner};
use tetra_saps::tmv::{TmvUnitdataInd, enums::logical_chans::LogicalChannel};
use common::{ComponentTest, default_test_config};
#[test]
/// A test containing a single Lmac frame, containing a MAC-RESOURCE with no SDU, and a NULL pdu
fn test_umac_ms() {
debug::setup_logging_verbose();
let config = default_test_config(StackMode::Ms);
let mut test = ComponentTest::new(config, None);
let components = vec![
TetraEntity::Umac,
];
let sinks: Vec<TetraEntity> = vec![];
test.populate_entities(components, sinks);
let m = SapMsg {
sap: Sap::TmvSap,
src: TetraEntity::Lmac,
dest: TetraEntity::Umac,
dltime: TdmaTime::default(),
msg: SapMsgInner::TmvUnitdataInd(
TmvUnitdataInd {
pdu: BitBuffer::from_bitstr("0010001000110001011010110000101010001010000100000000110000010000100000000000000000000000000000000000000000000000000000000000"),
block_num: PhyBlockNum::Block1,
logical_channel: LogicalChannel::SchHd,
crc_pass: true,
scrambling_code: 0,
}
)
};
// Submit and process message
test.submit_message(m);
test.deliver_all_messages();
let sink_msgs = test.dump_sinks();
// Evaluate results
assert_eq!(sink_msgs.len(), 0);
tracing::warn!("Validation of result not implemented");
}
#[test]
/// A test containing a 3-fragment message, which is reassembled by the UMAC
/// The message ultimately contains an SDS message, which is reconstructed in the CMCE.
/// Also tests the in-between LLC and MLE.
fn test_umac_frag() {
debug::setup_logging_verbose();
let config = default_test_config(StackMode::Ms);
let mut test = ComponentTest::new(config, None);
let components = vec![
TetraEntity::Umac,
TetraEntity::Llc,
TetraEntity::Mle,
TetraEntity::Cmce,
];
let sinks = vec![
];
test.populate_entities(components, sinks);
// NDB 56/18/1/000 type1: 0000000111111001011010110000101001100011000000110100111101011010111110000100110000110000100100011000000000001100010101000000
// NDB 57/01/1/000 type1: 0111000100110000000000010011001000110000001101000010110000110001010000000000110000010000100000000000000000000000000000000000
let m = SapMsg {
sap: Sap::TmvSap,
src: TetraEntity::Lmac,
dest: TetraEntity::Umac,
dltime: TdmaTime{h:0,m:56,f:18,t:1},
msg: SapMsgInner::TmvUnitdataInd(
TmvUnitdataInd {
pdu: BitBuffer::from_bitstr("0000000111111001011010110000101001100011000000110100111101011010111110000100110000110000100100011000000000001100010101000000"),
block_num: PhyBlockNum::Block1,
logical_channel: LogicalChannel::SchHd,
crc_pass: true,
scrambling_code: 0,
}
)
};
test.submit_message(m);
test.deliver_all_messages();
let m = SapMsg {
sap: Sap::TmvSap,
src: TetraEntity::Lmac,
dest: TetraEntity::Umac,
dltime: TdmaTime{h:0,m:56,f:18,t:1},
msg: SapMsgInner::TmvUnitdataInd(
TmvUnitdataInd {
pdu: BitBuffer::from_bitstr("0111000100110000000000010011001000110000001101000010110000110001010000000000110000010000100000000000000000000000000000000000"),
block_num: PhyBlockNum::Block1,
logical_channel: LogicalChannel::SchHd,
crc_pass: true,
scrambling_code: 0,
}
)
};
test.submit_message(m);
test.deliver_all_messages();
let msgs = test.dump_sinks();
for msg in msgs.iter() {
tracing::info!("\nSink message: {:?}", msg);
}
tracing::warn!("Validation of result not implemented");
}
#[test]
/// A test containing a SYSINFO frame, parsed by UMAC and MLE
fn test_sysinfo() {
debug::setup_logging_verbose();
let config = default_test_config(StackMode::Ms);
let mut test = ComponentTest::new(config, None);
let components = vec![
TetraEntity::Umac,
TetraEntity::Llc,
TetraEntity::Mle,
];
let sinks = vec![
// TetraComponent::Mle
];
test.populate_entities(components, sinks);
// Sysinfo test
let m = SapMsg {
sap: Sap::TmvSap,
src: TetraEntity::Lmac,
dest: TetraEntity::Umac,
dltime: TdmaTime::default(),
msg: SapMsgInner::TmvUnitdataInd(
TmvUnitdataInd {
// mac_block: BitBuffer::from_bitstr("1000001100101010010000000000110001101001011100000000001110001111100000100000000000010111100001100000111111000000110101100111"),
pdu: BitBuffer::from_bitstr("1000010000111111010001000000100001101001111100000000000000011101000011100000000000000000000000101111111111100101110101110111"),
block_num: PhyBlockNum::Block2,
logical_channel: LogicalChannel::Bnch,
crc_pass: true,
scrambling_code: 0,
}
)
};
test.submit_message(m);
test.deliver_all_messages();
let msgs = test.dump_sinks();
for msg in msgs.iter() {
tracing::info!("\nSink message: {:?}", msg);
}
tracing::warn!("Validation of result not implemented");
}
#[test]
/// A test containing a SYNC frame, parsed by UMAC and MLE
fn test_sync() {
debug::setup_logging_verbose();
let config = default_test_config(StackMode::Ms);
let mut test = ComponentTest::new(config, None);
let components = vec![
TetraEntity::Umac,
TetraEntity::Llc,
TetraEntity::Mle,
];
let sinks = vec![
TetraEntity::Lmac,
];
test.populate_entities(components, sinks);
// SB1 09/11/4/000 type1: 000100000111010110010010000000001101001000000100010101110011
// TMB-SAP SYNC CC 000001(0x01) TN 11(4) FN 01011(11) MN 001001( 9) MCC 0110100100(420) MNC 00001000101011(555)
let m = SapMsg {
sap: Sap::TmvSap,
src: TetraEntity::Lmac,
dest: TetraEntity::Umac,
dltime: TdmaTime::default(),
msg: SapMsgInner::TmvUnitdataInd(
TmvUnitdataInd {
pdu: BitBuffer::from_bitstr("000100000111010110010010000000001101001000000100010101110011"),
// pdu: BitBuffer::from_bitstr("000100000111100100111110000000000110011000000000000101111001"),
block_num: PhyBlockNum::Block1,
logical_channel: LogicalChannel::Bsch,
crc_pass: true,
scrambling_code: 0,
}
)
};
test.submit_message(m);
test.deliver_all_messages();
let msgs = test.dump_sinks();
for msg in msgs.iter() {
tracing::info!("\nSink message: {:?}", msg);
}
tracing::warn!("Validation of result not implemented");
}
#[test]
fn test_resource() {
debug::setup_logging_verbose();
let config = default_test_config(StackMode::Ms);
let mut test = ComponentTest::new(config, None);
let components = vec![
TetraEntity::Umac,
TetraEntity::Llc,
TetraEntity::Mle,
TetraEntity::Cmce,
];
let sinks = vec![];
test.populate_entities(components, sinks);
let m = SapMsg {
sap: Sap::TmvSap,
src: TetraEntity::Lmac,
dest: TetraEntity::Umac,
dltime: TdmaTime::default(),
msg: SapMsgInner::TmvUnitdataInd(
TmvUnitdataInd {
pdu: BitBuffer::from_bitstr("0010000010001110000000000000000001100101110110001000100110001001010001101100100100011110001110010011000000000001001100111110000000001000000000000001000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
block_num: PhyBlockNum::Both,
logical_channel: LogicalChannel::SchF,
crc_pass: true,
scrambling_code: 0,
}
)
};
test.submit_message(m);
test.deliver_all_messages();
let msgs = test.dump_sinks();
for msg in msgs.iter() {
tracing::info!("\nSink message: {:?}", msg);
}
tracing::warn!("Validation of result not implemented");
}

View File

@@ -0,0 +1,10 @@
[package]
name = "tetra-pdus"
version.workspace = true
edition.workspace = true
[dependencies]
tetra-core = { workspace = true }
tetra-saps = { workspace = true }
tetra-config = { workspace = true }
tracing = { workspace = true }

Some files were not shown because too many files have changed in this diff Show More