Added parsing for U-STATUS and D-STATUS pre-coded status field

This commit is contained in:
Wouter Bokslag
2026-03-11 09:53:19 +01:00
parent d5ec440a46
commit 1b4dc0421d
9 changed files with 194 additions and 9 deletions

View File

@@ -1,5 +1,6 @@
use tetra_config::bluestation::SharedConfig;
use tetra_core::{BitBuffer, Sap, SsiType, TetraAddress, tetra_entities::TetraEntity, unimplemented_log};
use tetra_pdus::cmce::enums::pre_coded_status::PreCodedStatus;
use tetra_saps::control::enums::sds_user_data::SdsUserData;
use tetra_saps::control::sds::CmceSdsData;
use tetra_saps::lcmc::LcmcMleUnitdataReq;
@@ -174,7 +175,7 @@ impl SdsBsSubentity {
dltime: tetra_core::TdmaTime,
source_issi: u32,
dest_issi: u32,
pre_coded_status: u16,
pre_coded_status: PreCodedStatus,
) {
let pdu = DStatus {
calling_party_type_identifier: PartyTypeIdentifier::Ssi,

View File

@@ -6,6 +6,7 @@ use tetra_config::bluestation::{CfgBrew, StackMode};
use tetra_core::tetra_entities::TetraEntity;
use tetra_core::{BitBuffer, Sap, SsiType, TdmaTime, TetraAddress, debug};
use tetra_pdus::cmce::enums::party_type_identifier::PartyTypeIdentifier;
use tetra_pdus::cmce::enums::pre_coded_status::PreCodedStatus;
use tetra_pdus::cmce::pdus::u_sds_data::USdsData;
use tetra_pdus::cmce::pdus::u_status::UStatus;
use tetra_saps::control::enums::sds_user_data::SdsUserData;
@@ -268,7 +269,7 @@ fn test_u_status_forwarded_as_d_status() {
called_party_short_number_address: None,
called_party_ssi: Some(2000001),
called_party_extension: None,
pre_coded_status: 0x8210,
pre_coded_status: PreCodedStatus::try_from(0x8210).unwrap(),
external_subscriber_number: None,
dm_ms_address: None,
};
@@ -331,7 +332,7 @@ fn test_u_status_unregistered_dest_dropped() {
called_party_short_number_address: None,
called_party_ssi: Some(9999999),
called_party_extension: None,
pre_coded_status: 0x8210,
pre_coded_status: PreCodedStatus::from(0x8210),
external_subscriber_number: None,
dm_ms_address: None,
};

View File

@@ -5,6 +5,8 @@ pub mod cmce_pdu_type_dl;
pub mod cmce_pdu_type_ul;
pub mod disconnect_cause;
pub mod party_type_identifier;
pub mod pre_coded_status;
pub mod sds_protocol_id;
pub mod short_report_type;
pub mod transmission_grant;
pub mod type3_elem_id;

View File

@@ -0,0 +1,54 @@
use crate::cmce::fields::sds_short_report::SdsShortReport;
/// Clause 14.8.34 Pre-coded status
/// The pre-coded status information element shall define general purpose status messages known to all TETRA systems as
/// defined in table 14.72 and shall provide support for the SDS-TL "short reporting" protocol.
/// Bits: 2
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u8)]
pub enum PreCodedStatus {
Emergency,
Reserved(u16),
SdsTl(SdsShortReport),
NetworkUserSpecific(u16),
}
impl From<u16> for PreCodedStatus {
fn from(x: u16) -> Self {
match x {
0 => PreCodedStatus::Emergency,
1..=31742 => PreCodedStatus::Reserved(x),
31743..=32767 => PreCodedStatus::SdsTl(SdsShortReport::from_u16(x).unwrap()),
32768..=65535 => PreCodedStatus::NetworkUserSpecific(x),
}
}
}
impl PreCodedStatus {
/// Convert this enum back into the raw integer value
pub fn into_raw(self) -> u16 {
match self {
PreCodedStatus::Emergency => 0,
PreCodedStatus::Reserved(x) => x,
PreCodedStatus::SdsTl(x) => x.to_u16(),
PreCodedStatus::NetworkUserSpecific(x) => x,
}
}
}
impl From<PreCodedStatus> for u16 {
fn from(e: PreCodedStatus) -> Self {
e.into_raw()
}
}
impl core::fmt::Display for PreCodedStatus {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
PreCodedStatus::Emergency => write!(f, "Emergency"),
PreCodedStatus::Reserved(x) => write!(f, "Reserved({})", x),
PreCodedStatus::SdsTl(x) => write!(f, "SdsTl({})", x),
PreCodedStatus::NetworkUserSpecific(x) => write!(f, "NetworkUserSpecific({})", x),
}
}
}

View File

@@ -0,0 +1,53 @@
/// Clause 29.4.3.11 Short report type
/// The Short report type information element shall indicate the reason for report as defined in table 29.23.
/// Bits: 2
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ShortReportType {
ProtOrEncodingNotSupported = 0,
DestMemFull = 1,
MessageReceived = 2,
MessageConsumed = 3,
}
impl std::convert::TryFrom<u64> for ShortReportType {
type Error = ();
fn try_from(x: u64) -> Result<Self, Self::Error> {
match x {
0 => Ok(ShortReportType::ProtOrEncodingNotSupported),
1 => Ok(ShortReportType::DestMemFull),
2 => Ok(ShortReportType::MessageReceived),
3 => Ok(ShortReportType::MessageConsumed),
_ => Err(()),
}
}
}
impl ShortReportType {
/// Convert this enum back into the raw integer value
pub fn into_raw(self) -> u64 {
match self {
ShortReportType::ProtOrEncodingNotSupported => 0,
ShortReportType::DestMemFull => 1,
ShortReportType::MessageReceived => 2,
ShortReportType::MessageConsumed => 3,
}
}
}
impl From<ShortReportType> for u64 {
fn from(e: ShortReportType) -> Self {
e.into_raw()
}
}
impl core::fmt::Display for ShortReportType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ShortReportType::ProtOrEncodingNotSupported => write!(f, "ProtOrEncodingNotSupported"),
ShortReportType::DestMemFull => write!(f, "DestMemFull"),
ShortReportType::MessageReceived => write!(f, "MessageReceived"),
ShortReportType::MessageConsumed => write!(f, "MessageConsumed"),
}
}
}

View File

@@ -1 +1,2 @@
pub mod basic_service_information;
pub mod sds_short_report;

View File

@@ -0,0 +1,69 @@
use core::fmt;
use tetra_core::{PduParseErr, expect_value};
use crate::cmce::enums::short_report_type::ShortReportType;
/// Clause 29.4.2.3 SDS-SHORT REPORT
/// This PDU shall be used to report on the progress of previously received SDS data
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SdsShortReport {
/// 2 bits
short_report_type: ShortReportType,
/// 8 bits. The same value as in the corresponding request PDU
message_reference: u8,
}
impl SdsShortReport {
// No from_bitbuf, to_bitbuf functions, as we'll parse this in a bit of a different way originating from an enum field in the U-STATUS PDU pre-coded status field
pub fn from_u16(val: u16) -> Result<Self, PduParseErr> {
// TODO FIXME implement parsing of the pre-coded status field into this struct, as defined in table 14.72
let pdu_type = val >> 10;
expect_value!(pdu_type, 0b011111)?;
let raw = ((val >> 8) & 0x3) as u64;
let short_report_type = ShortReportType::try_from(raw).unwrap(); // never fails
let message_reference = (val & 0xFF) as u8;
Ok(SdsShortReport {
short_report_type,
message_reference,
})
}
pub fn to_u16(&self) -> u16 {
// TODO FIXME implement conversion of this struct into the pre-coded status field, as defined in table 14.72
assert!(self.short_report_type.into_raw() <= 0b11, "short_report_type must be 2 bits");
(0b011111 << 10) | ((self.short_report_type.into_raw() as u16) << 8) | (self.message_reference as u16 & 0xFF)
}
}
impl fmt::Display for SdsShortReport {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"SdsShortReport {{ short_report_type: {:?}, message_reference: {:?} }}",
self.short_report_type, self.message_reference,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_u16_roundtrip() {
// Synthetic test, not real world data
let sds = SdsShortReport {
short_report_type: ShortReportType::MessageReceived,
message_reference: 0b00000001,
};
let converted = sds.to_u16();
assert_eq!(converted, 0b0111111000000001);
let parsed = SdsShortReport::from_u16(converted).unwrap();
assert_eq!(parsed.short_report_type, sds.short_report_type);
assert_eq!(parsed.message_reference, sds.message_reference);
}
}

View File

@@ -1,5 +1,6 @@
use core::fmt;
use crate::cmce::enums::pre_coded_status::PreCodedStatus;
use crate::cmce::enums::{cmce_pdu_type_dl::CmcePduTypeDl, party_type_identifier::PartyTypeIdentifier, type3_elem_id::CmceType3ElemId};
use tetra_core::typed_pdu_fields::*;
use tetra_core::{BitBuffer, expect_pdu_type, pdu_parse_error::PduParseErr};
@@ -19,7 +20,7 @@ pub struct DStatus {
/// Conditional 24 bits, Calling party extension condition: calling_party_type_identifier == 2
pub calling_party_extension: Option<u64>,
/// Type1, 16 bits, Pre-coded status
pub pre_coded_status: u16,
pub pre_coded_status: PreCodedStatus,
/// Type3, External subscriber number
pub external_subscriber_number: Option<Type3FieldGeneric>,
/// Type3, DM-MS address
@@ -53,7 +54,8 @@ impl DStatus {
None
};
// Type1
let pre_coded_status = buffer.read_field(16, "pre_coded_status")? as u16;
let val = buffer.read_field(16, "pre_coded_status")? as u16;
let pre_coded_status = PreCodedStatus::from(val);
// obit designates presence of any further type2, type3 or type4 fields
let mut obit = delimiters::read_obit(buffer)?;
@@ -95,7 +97,7 @@ impl DStatus {
buffer.write_bits(*value, 24);
}
// Type1
buffer.write_bits(self.pre_coded_status as u64, 16);
buffer.write_bits(self.pre_coded_status.into_raw().into(), 16);
// Check if any optional field present and place o-bit
let obit = self.external_subscriber_number.is_some() || self.dm_ms_address.is_some();

View File

@@ -1,5 +1,6 @@
use core::fmt;
use crate::cmce::enums::pre_coded_status::PreCodedStatus;
use crate::cmce::enums::{cmce_pdu_type_ul::CmcePduTypeUl, party_type_identifier::PartyTypeIdentifier, type3_elem_id::CmceType3ElemId};
use tetra_core::typed_pdu_fields::*;
use tetra_core::{BitBuffer, expect_pdu_type, pdu_parse_error::PduParseErr};
@@ -24,7 +25,7 @@ pub struct UStatus {
/// Conditional 24 bits, See note 2, condition: called_party_type_identifier == 2
pub called_party_extension: Option<u64>,
/// Type1, 16 bits, Pre-coded status
pub pre_coded_status: u16,
pub pre_coded_status: PreCodedStatus,
/// Type3, External subscriber number
pub external_subscriber_number: Option<Type3FieldGeneric>,
/// Type3, DM-MS address
@@ -66,7 +67,8 @@ impl UStatus {
None
};
// Type1
let pre_coded_status = buffer.read_field(16, "pre_coded_status")? as u16;
let val = buffer.read_field(16, "pre_coded_status")? as u16;
let pre_coded_status = PreCodedStatus::from(val);
// obit designates presence of any further type2, type3 or type4 fields
let mut obit = delimiters::read_obit(buffer)?;
@@ -116,7 +118,7 @@ impl UStatus {
buffer.write_bits(*value, 24);
}
// Type1
buffer.write_bits(self.pre_coded_status as u64, 16);
buffer.write_bits(self.pre_coded_status.into_raw().into(), 16);
// Check if any optional field present and place o-bit
let obit = self.external_subscriber_number.is_some() || self.dm_ms_address.is_some();