mirror of
https://github.com/MidnightBlueLabs/tetra-bluestation.git
synced 2026-03-29 05:09:51 +00:00
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:
78
Cargo.lock
generated
78
Cargo.lock
generated
@@ -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]]
|
||||
|
||||
39
Cargo.toml
39
Cargo.toml
@@ -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"
|
||||
|
||||
19
bins/bluestation-bs/Cargo.toml
Normal file
19
bins/bluestation-bs/Cargo.toml
Normal 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 }
|
||||
@@ -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
19
bins/pdu-tool/Cargo.toml
Normal 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 }
|
||||
1
bins/pdu-tool/src/entities/mod.rs
Normal file
1
bins/pdu-tool/src/entities/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod umac;
|
||||
698
bins/pdu-tool/src/entities/umac.rs
Normal file
698
bins/pdu-tool/src/entities/umac.rs
Normal 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
96
bins/pdu-tool/src/main.rs
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
9
crates/tetra-config/Cargo.toml
Normal file
9
crates/tetra-config/Cargo.toml
Normal 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 }
|
||||
13
crates/tetra-config/src/lib.rs
Normal file
13
crates/tetra-config/src/lib.rs
Normal 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::*;
|
||||
@@ -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).
|
||||
@@ -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>,
|
||||
|
||||
11
crates/tetra-core/Cargo.toml
Normal file
11
crates/tetra-core/Cargo.toml
Normal 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 }
|
||||
@@ -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 {
|
||||
@@ -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>,
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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> {
|
||||
27
crates/tetra-core/src/lib.rs
Normal file
27
crates/tetra-core/src/lib.rs
Normal 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::*;
|
||||
@@ -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,
|
||||
})
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
18
crates/tetra-entities/Cargo.toml
Normal file
18
crates/tetra-entities/Cargo.toml
Normal 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 }
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,7 +1,3 @@
|
||||
pub mod subentities;
|
||||
pub mod components;
|
||||
pub mod pdus;
|
||||
pub mod enums;
|
||||
|
||||
pub mod cmce_bs;
|
||||
pub mod cmce_ms;
|
||||
@@ -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{
|
||||
@@ -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{
|
||||
@@ -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{
|
||||
25
crates/tetra-entities/src/entity_trait.rs
Normal file
25
crates/tetra-entities/src/entity_trait.rs
Normal 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 }
|
||||
}
|
||||
16
crates/tetra-entities/src/lib.rs
Normal file
16
crates/tetra-entities/src/lib.rs
Normal 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};
|
||||
@@ -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::*;
|
||||
|
||||
2
crates/tetra-entities/src/llc/components/mod.rs
Normal file
2
crates/tetra-entities/src/llc/components/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
pub mod fcs;
|
||||
@@ -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
|
||||
@@ -1,4 +1,2 @@
|
||||
pub mod components;
|
||||
pub mod enums;
|
||||
pub mod pdus;
|
||||
pub mod llc_bs_ms;
|
||||
@@ -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,
|
||||
@@ -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)]
|
||||
71
crates/tetra-entities/src/lmac/components/interleaver.rs
Normal file
71
crates/tetra-entities/src/lmac/components/interleaver.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
62
crates/tetra-entities/src/lmac/components/scrambler.rs
Normal file
62
crates/tetra-entities/src/lmac/components/scrambler.rs
Normal 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..]` in‐place 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
|
||||
}
|
||||
@@ -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)]
|
||||
@@ -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
|
||||
};
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
1
crates/tetra-entities/src/mle/mod.rs
Normal file
1
crates/tetra-entities/src/mle/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod mle_bs_ms;
|
||||
@@ -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(
|
||||
@@ -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,
|
||||
@@ -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>,
|
||||
@@ -1,7 +1,4 @@
|
||||
pub mod components;
|
||||
pub mod enums;
|
||||
pub mod fields;
|
||||
pub mod pdus;
|
||||
|
||||
pub mod mm_ms;
|
||||
pub mod mm_bs;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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;
|
||||
@@ -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::*;
|
||||
@@ -1,5 +1,3 @@
|
||||
pub mod enums;
|
||||
pub mod components;
|
||||
pub mod traits;
|
||||
|
||||
pub mod phy_bs;
|
||||
@@ -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!()
|
||||
};
|
||||
@@ -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>,
|
||||
@@ -1,6 +1,3 @@
|
||||
pub mod enums;
|
||||
pub mod fields;
|
||||
pub mod pdus;
|
||||
pub mod subcomp;
|
||||
|
||||
pub mod umac_bs;
|
||||
@@ -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]
|
||||
@@ -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,
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
@@ -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
|
||||
@@ -5,5 +5,5 @@ pub mod bs_sched;
|
||||
|
||||
pub mod ms_defrag;
|
||||
|
||||
pub mod event_labels;
|
||||
pub mod event_label_store;
|
||||
pub mod fillbits;
|
||||
@@ -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]
|
||||
@@ -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!() };
|
||||
@@ -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 {
|
||||
@@ -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;
|
||||
|
||||
6
crates/tetra-entities/tests/common/mod.rs
Normal file
6
crates/tetra-entities/tests/common/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub mod component_test;
|
||||
pub mod sink;
|
||||
|
||||
pub use component_test::{ComponentTest, default_test_config};
|
||||
@@ -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
|
||||
59
crates/tetra-entities/tests/test_llc_bs.rs
Normal file
59
crates/tetra-entities/tests/test_llc_bs.rs
Normal 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");
|
||||
}
|
||||
49
crates/tetra-entities/tests/test_mm_bs.rs
Normal file
49
crates/tetra-entities/tests/test_mm_bs.rs
Normal 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");
|
||||
}
|
||||
|
||||
122
crates/tetra-entities/tests/test_phy_bs.rs
Normal file
122
crates/tetra-entities/tests/test_phy_bs.rs
Normal 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);
|
||||
}
|
||||
179
crates/tetra-entities/tests/test_umac_bs.rs
Normal file
179
crates/tetra-entities/tests/test_umac_bs.rs
Normal 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");
|
||||
}
|
||||
|
||||
243
crates/tetra-entities/tests/test_umac_ms.rs
Normal file
243
crates/tetra-entities/tests/test_umac_ms.rs
Normal 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");
|
||||
}
|
||||
10
crates/tetra-pdus/Cargo.toml
Normal file
10
crates/tetra-pdus/Cargo.toml
Normal 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
Reference in New Issue
Block a user