mirror of
https://github.com/MidnightBlueLabs/tetra-bluestation.git
synced 2026-03-29 05:09:51 +00:00
Refactors in SDS handling code
* SDS user defined data types now enums * Improved state SubscriberRegistry * Small printing and formatting fixes
This commit is contained in:
@@ -1,17 +1,27 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use tetra_core::TimeslotAllocator;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Subscriber {
|
||||
pub issi: u32,
|
||||
// Set of attached GSSIs
|
||||
pub attached_groups: HashSet<u32>,
|
||||
}
|
||||
|
||||
/// Centralized subscriber registry tracking locally registered ISSIs and their group affiliations.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SubscriberRegistry {
|
||||
/// Registered ISSIs → affiliated GSSIs
|
||||
subscribers: HashMap<u32, HashSet<u32>>,
|
||||
/// Registered ISSIs → Subscriber information
|
||||
subscribers: HashMap<u32, Subscriber>,
|
||||
/// Set of all GSSIs with at least one local affiliate
|
||||
all_attached_groups: HashSet<u32>,
|
||||
}
|
||||
|
||||
impl SubscriberRegistry {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
subscribers: HashMap::new(),
|
||||
all_attached_groups: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,26 +29,62 @@ impl SubscriberRegistry {
|
||||
self.subscribers.contains_key(&issi)
|
||||
}
|
||||
|
||||
/// Tolerant registration; if ISSI already registered, we overwrite it with a fresh Subscriber struct
|
||||
pub fn register(&mut self, issi: u32) {
|
||||
self.subscribers.entry(issi).or_default();
|
||||
self.deregister(issi); // Clean up any existing registration to prevent stale affiliations
|
||||
self.subscribers.insert(
|
||||
issi,
|
||||
Subscriber {
|
||||
issi,
|
||||
attached_groups: HashSet::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Gets mutable ref to subscriber. If not registered, a default Subscriber is inserted.
|
||||
pub fn get_subscriber_mut(&mut self, issi: u32) -> &mut Subscriber {
|
||||
self.subscribers.entry(issi).or_insert_with(|| Subscriber {
|
||||
issi,
|
||||
attached_groups: HashSet::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Deregister an ISSI, removing it from the registry and cleaning up any group affiliations
|
||||
pub fn deregister(&mut self, issi: u32) {
|
||||
self.subscribers.remove(&issi);
|
||||
}
|
||||
|
||||
pub fn affiliate(&mut self, issi: u32, gssi: u32) {
|
||||
self.subscribers.entry(issi).or_default().insert(gssi);
|
||||
}
|
||||
|
||||
pub fn deaffiliate(&mut self, issi: u32, gssi: u32) {
|
||||
if let Some(groups) = self.subscribers.get_mut(&issi) {
|
||||
groups.remove(&gssi);
|
||||
if let Some(subscriber) = self.subscribers.remove(&issi) {
|
||||
// Clean up global group affiliations for this subscriber
|
||||
for gssi in &subscriber.attached_groups {
|
||||
// Check if any other subscriber is still affiliated with this group
|
||||
let still_has_members = self.subscribers.values().any(|s| s.attached_groups.contains(gssi));
|
||||
if !still_has_members {
|
||||
self.all_attached_groups.remove(gssi);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add GSSI to subscriber's attached groups and global set
|
||||
pub fn affiliate(&mut self, issi: u32, gssi: u32) {
|
||||
let subscriber = self.get_subscriber_mut(issi);
|
||||
subscriber.attached_groups.insert(gssi);
|
||||
self.all_attached_groups.insert(gssi);
|
||||
}
|
||||
|
||||
/// Remove GSSI from subscriber's attached groups. Update global set if no more subscribers are affiliated with this GSSI.
|
||||
pub fn deaffiliate(&mut self, issi: u32, gssi: u32) {
|
||||
let subscriber = self.get_subscriber_mut(issi);
|
||||
if subscriber.attached_groups.remove(&gssi) {
|
||||
// Check if any other subscriber is still affiliated with this group
|
||||
let still_has_members = self.subscribers.values().any(|s| s.attached_groups.contains(&gssi));
|
||||
if !still_has_members {
|
||||
self.all_attached_groups.remove(&gssi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if any subscriber is affiliated with the given GSSI
|
||||
pub fn has_group_members(&self, gssi: u32) -> bool {
|
||||
self.subscribers.values().any(|groups| groups.contains(&gssi))
|
||||
self.all_attached_groups.contains(&gssi)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +136,14 @@ mod tests {
|
||||
// Deaffiliate one, should still have members
|
||||
reg.deaffiliate(1001, 100);
|
||||
assert!(reg.has_group_members(100));
|
||||
|
||||
// Deregister a user, should still have members
|
||||
reg.deregister(1002);
|
||||
assert!(reg.has_group_members(100));
|
||||
|
||||
// Deregister last user, should have no members
|
||||
reg.deregister(1003);
|
||||
assert!(!reg.has_group_members(100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -97,6 +151,20 @@ mod tests {
|
||||
let reg = SubscriberRegistry::new();
|
||||
assert!(!reg.has_group_members(999));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register_overwrites_existing_subscriber() {
|
||||
let mut reg = SubscriberRegistry::new();
|
||||
reg.register(1001);
|
||||
reg.affiliate(1001, 91);
|
||||
assert!(reg.has_group_members(91));
|
||||
|
||||
reg.register(1001);
|
||||
|
||||
assert!(reg.is_registered(1001));
|
||||
reg.deaffiliate(1001, 91);
|
||||
assert!(!reg.has_group_members(91));
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StackState {
|
||||
|
||||
@@ -5,6 +5,8 @@ use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crossbeam_channel::{Receiver, Sender, unbounded};
|
||||
use tetra_saps::control::enums::sds_user_data::SdsUserData;
|
||||
use tetra_saps::control::sds::CmceSdsData;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{MessageQueue, TetraEntityTrait};
|
||||
@@ -1069,7 +1071,7 @@ impl BrewEntity {
|
||||
);
|
||||
|
||||
// Brew always sends SDS Type 4 (variable length) per protocol spec
|
||||
let short_data_type_identifier = 3;
|
||||
let user_defined_data = SdsUserData::Type4(length_bits, data);
|
||||
|
||||
// Forward to CMCE SDS subentity for downlink delivery
|
||||
queue.push_back(SapMsg {
|
||||
@@ -1077,18 +1079,16 @@ impl BrewEntity {
|
||||
src: TetraEntity::Brew,
|
||||
dest: TetraEntity::Cmce,
|
||||
dltime: self.dltime,
|
||||
msg: SapMsgInner::CmceSdsData(tetra_saps::control::sds::CmceSdsData {
|
||||
msg: SapMsgInner::CmceSdsData(CmceSdsData {
|
||||
source_issi: source,
|
||||
dest_issi: destination,
|
||||
short_data_type_identifier,
|
||||
data,
|
||||
length_bits,
|
||||
user_defined_data,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
/// Handle outgoing SDS from CMCE → Brew (local MS → network)
|
||||
fn handle_sds_send(&self, sds: tetra_saps::control::sds::CmceSdsData) {
|
||||
fn handle_sds_send(&self, sds: CmceSdsData) {
|
||||
if !self.connected {
|
||||
tracing::warn!(
|
||||
"BrewEntity: not connected, dropping outgoing SDS {} -> {}",
|
||||
@@ -1100,20 +1100,20 @@ impl BrewEntity {
|
||||
|
||||
let uuid = Uuid::new_v4();
|
||||
tracing::info!(
|
||||
"BrewEntity: sending SDS uuid={} src={} dst={} type={} {} bytes",
|
||||
"BrewEntity: sending SDS uuid={} src={} dst={} type={} {} bits",
|
||||
uuid,
|
||||
sds.source_issi,
|
||||
sds.dest_issi,
|
||||
sds.short_data_type_identifier,
|
||||
sds.data.len()
|
||||
sds.user_defined_data.type_identifier(),
|
||||
sds.user_defined_data.length_bits()
|
||||
);
|
||||
|
||||
let _ = self.command_sender.send(BrewCommand::SendSds {
|
||||
uuid,
|
||||
source: sds.source_issi,
|
||||
destination: sds.dest_issi,
|
||||
data: sds.data,
|
||||
length_bits: sds.length_bits,
|
||||
data: sds.user_defined_data.to_arr(),
|
||||
length_bits: sds.user_defined_data.length_bits(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,7 +404,7 @@ impl CcBsSubentity {
|
||||
|
||||
let pdu = match USetup::from_bitbuf(&mut prim.sdu) {
|
||||
Ok(pdu) => {
|
||||
tracing::debug!("<- U-SETUP {:?}", pdu);
|
||||
tracing::debug!("<- {:?}", pdu);
|
||||
pdu
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -856,7 +856,7 @@ impl CcBsSubentity {
|
||||
|
||||
let pdu = match UTxCeased::from_bitbuf(&mut prim.sdu) {
|
||||
Ok(pdu) => {
|
||||
tracing::debug!("<- U-TX CEASED {:?}", pdu);
|
||||
tracing::debug!("<- {:?}", pdu);
|
||||
pdu
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -944,7 +944,7 @@ impl CcBsSubentity {
|
||||
|
||||
let pdu = match UTxDemand::from_bitbuf(&mut prim.sdu) {
|
||||
Ok(pdu) => {
|
||||
tracing::debug!("<- U-TX DEMAND {:?}", pdu);
|
||||
tracing::debug!("<- {:?}", pdu);
|
||||
pdu
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -1050,7 +1050,7 @@ impl CcBsSubentity {
|
||||
|
||||
let pdu = match URelease::from_bitbuf(&mut prim.sdu) {
|
||||
Ok(pdu) => {
|
||||
tracing::debug!("<- U-RELEASE {:?}", pdu);
|
||||
tracing::debug!("<- {:?}", pdu);
|
||||
pdu
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -1078,7 +1078,7 @@ impl CcBsSubentity {
|
||||
|
||||
let pdu = match UDisconnect::from_bitbuf(&mut prim.sdu) {
|
||||
Ok(pdu) => {
|
||||
tracing::debug!("<- U-DISCONNECT {:?}", pdu);
|
||||
tracing::debug!("<- {:?}", pdu);
|
||||
pdu
|
||||
}
|
||||
Err(e) => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use tetra_config::bluestation::SharedConfig;
|
||||
use tetra_core::{BitBuffer, Sap, SsiType, TetraAddress, tetra_entities::TetraEntity, unimplemented_log};
|
||||
use tetra_saps::control::enums::sds_user_data::SdsUserData;
|
||||
use tetra_saps::control::sds::CmceSdsData;
|
||||
use tetra_saps::lcmc::LcmcMleUnitdataReq;
|
||||
use tetra_saps::{SapMsg, SapMsgInner};
|
||||
@@ -34,7 +35,7 @@ impl SdsBsSubentity {
|
||||
|
||||
let pdu = match USdsData::from_bitbuf(&mut prim.sdu) {
|
||||
Ok(pdu) => {
|
||||
tracing::debug!("<- U-SDS-DATA {:?}", pdu);
|
||||
tracing::debug!("<- {:?}", pdu);
|
||||
pdu
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -57,66 +58,19 @@ impl SdsBsSubentity {
|
||||
"SDS: U-SDS-DATA from ISSI {} to ISSI {}, type={}",
|
||||
source_ssi,
|
||||
dest_ssi,
|
||||
pdu.short_data_type_identifier
|
||||
pdu.user_defined_data.type_identifier()
|
||||
);
|
||||
|
||||
// Extract SDS payload into a uniform representation
|
||||
let (data, length_bits) = match pdu.short_data_type_identifier {
|
||||
0 => {
|
||||
// Type 1: 16 bits
|
||||
let val = pdu.user_defined_data_1.unwrap_or(0);
|
||||
(val.to_be_bytes()[6..8].to_vec(), 16u16)
|
||||
}
|
||||
1 => {
|
||||
// Type 2: 32 bits
|
||||
let val = pdu.user_defined_data_2.unwrap_or(0);
|
||||
(val.to_be_bytes()[4..8].to_vec(), 32u16)
|
||||
}
|
||||
2 => {
|
||||
// Type 3: 64 bits
|
||||
let val = pdu.user_defined_data_3.unwrap_or(0);
|
||||
(val.to_be_bytes().to_vec(), 64u16)
|
||||
}
|
||||
3 => {
|
||||
// Type 4: variable length
|
||||
let len_bits = pdu.length_indicator.unwrap_or(0) as u16;
|
||||
let data = pdu.user_defined_data_4.unwrap_or_default();
|
||||
(data, len_bits)
|
||||
}
|
||||
_ => {
|
||||
tracing::warn!("SDS: invalid short_data_type_identifier={}", pdu.short_data_type_identifier);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Route: local delivery (ISSI or GSSI), Brew forward, or drop
|
||||
let is_local_issi = self.config.state_read().subscribers.is_registered(dest_ssi);
|
||||
let is_local_group = !is_local_issi && self.config.state_read().subscribers.has_group_members(dest_ssi);
|
||||
|
||||
if is_local_issi {
|
||||
tracing::info!("SDS: local delivery: {} -> {}", source_ssi, dest_ssi);
|
||||
self.send_d_sds_data(
|
||||
queue,
|
||||
message.dltime,
|
||||
source_ssi,
|
||||
dest_ssi,
|
||||
SsiType::Issi,
|
||||
pdu.short_data_type_identifier,
|
||||
&data,
|
||||
length_bits,
|
||||
);
|
||||
self.send_d_sds_data(queue, message.dltime, source_ssi, dest_ssi, SsiType::Issi, pdu.user_defined_data);
|
||||
} else if is_local_group {
|
||||
tracing::info!("SDS: group delivery: {} -> GSSI {}", source_ssi, dest_ssi);
|
||||
self.send_d_sds_data(
|
||||
queue,
|
||||
message.dltime,
|
||||
source_ssi,
|
||||
dest_ssi,
|
||||
SsiType::Gssi,
|
||||
pdu.short_data_type_identifier,
|
||||
&data,
|
||||
length_bits,
|
||||
);
|
||||
self.send_d_sds_data(queue, message.dltime, source_ssi, dest_ssi, SsiType::Gssi, pdu.user_defined_data);
|
||||
} else if brew::is_active(&self.config)
|
||||
&& (brew::is_brew_issi_routable(&self.config, dest_ssi) || brew::is_tetrapack_sds_service_issi(&self.config, dest_ssi))
|
||||
{
|
||||
@@ -129,9 +83,7 @@ impl SdsBsSubentity {
|
||||
msg: SapMsgInner::CmceSdsData(CmceSdsData {
|
||||
source_issi: source_ssi,
|
||||
dest_issi: dest_ssi,
|
||||
short_data_type_identifier: pdu.short_data_type_identifier,
|
||||
data,
|
||||
length_bits,
|
||||
user_defined_data: pdu.user_defined_data,
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
@@ -149,8 +101,8 @@ impl SdsBsSubentity {
|
||||
"SDS: received from Brew: {} -> {}, type={}, {} bits",
|
||||
sds.source_issi,
|
||||
sds.dest_issi,
|
||||
sds.short_data_type_identifier,
|
||||
sds.length_bits
|
||||
sds.user_defined_data.type_identifier(),
|
||||
sds.user_defined_data.length_bits()
|
||||
);
|
||||
|
||||
if !self.config.state_read().subscribers.is_registered(sds.dest_issi) {
|
||||
@@ -165,9 +117,7 @@ impl SdsBsSubentity {
|
||||
sds.source_issi,
|
||||
sds.dest_issi,
|
||||
SsiType::Issi,
|
||||
sds.short_data_type_identifier,
|
||||
&sds.data,
|
||||
sds.length_bits,
|
||||
sds.user_defined_data,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -182,7 +132,7 @@ impl SdsBsSubentity {
|
||||
|
||||
let pdu = match UStatus::from_bitbuf(&mut prim.sdu) {
|
||||
Ok(pdu) => {
|
||||
tracing::debug!("<- U-STATUS {:?}", pdu);
|
||||
tracing::debug!("<- {:?}", pdu);
|
||||
pdu
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -276,54 +226,13 @@ impl SdsBsSubentity {
|
||||
source_issi: u32,
|
||||
dest_issi: u32,
|
||||
dest_ssi_type: SsiType,
|
||||
short_data_type_identifier: u8,
|
||||
data: &[u8],
|
||||
length_bits: u16,
|
||||
user_defined_data: SdsUserData,
|
||||
) {
|
||||
// Build D-SDS-DATA PDU
|
||||
let (user_defined_data_1, user_defined_data_2, user_defined_data_3, length_indicator, user_defined_data_4) =
|
||||
match short_data_type_identifier {
|
||||
0 => {
|
||||
let val = if data.len() >= 2 {
|
||||
((data[0] as u64) << 8) | (data[1] as u64)
|
||||
} else if data.len() == 1 {
|
||||
(data[0] as u64) << 8
|
||||
} else {
|
||||
0
|
||||
};
|
||||
(Some(val), None, None, None, None)
|
||||
}
|
||||
1 => {
|
||||
let mut val: u64 = 0;
|
||||
for (i, &b) in data.iter().take(4).enumerate() {
|
||||
val |= (b as u64) << (24 - i * 8);
|
||||
}
|
||||
(None, Some(val), None, None, None)
|
||||
}
|
||||
2 => {
|
||||
let mut val: u64 = 0;
|
||||
for (i, &b) in data.iter().take(8).enumerate() {
|
||||
val |= (b as u64) << (56 - i * 8);
|
||||
}
|
||||
(None, None, Some(val), None, None)
|
||||
}
|
||||
3 => (None, None, None, Some(length_bits as u64), Some(data.to_vec())),
|
||||
_ => {
|
||||
tracing::warn!("SDS: invalid short_data_type_identifier={}", short_data_type_identifier);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let pdu = DSdsData {
|
||||
calling_party_type_identifier: PartyTypeIdentifier::Ssi,
|
||||
calling_party_address_ssi: Some(source_issi as u64),
|
||||
calling_party_extension: None,
|
||||
short_data_type_identifier,
|
||||
user_defined_data_1,
|
||||
user_defined_data_2,
|
||||
user_defined_data_3,
|
||||
length_indicator,
|
||||
user_defined_data_4,
|
||||
user_defined_data,
|
||||
external_subscriber_number: None,
|
||||
dm_ms_address: None,
|
||||
};
|
||||
|
||||
@@ -65,7 +65,7 @@ impl BsDefrag {
|
||||
|
||||
tracing::debug!(
|
||||
"defrag_buffer for ts {} ssi: {}, t: {}-{}, frags: {}: {}",
|
||||
ts,
|
||||
t.t,
|
||||
buf.addr.ssi,
|
||||
buf.t_first,
|
||||
buf.t_last,
|
||||
|
||||
@@ -8,6 +8,7 @@ use tetra_core::{BitBuffer, Sap, SsiType, TdmaTime, TetraAddress, debug};
|
||||
use tetra_pdus::cmce::enums::party_type_identifier::PartyTypeIdentifier;
|
||||
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;
|
||||
use tetra_saps::control::sds::CmceSdsData;
|
||||
use tetra_saps::lcmc::LcmcMleUnitdataInd;
|
||||
use tetra_saps::sapmsg::{SapMsg, SapMsgInner};
|
||||
@@ -21,7 +22,7 @@ fn register_subscriber(test: &mut ComponentTest, issi: u32) {
|
||||
|
||||
/// Helper: affiliate a subscriber with a GSSI in the StackState subscriber registry
|
||||
fn affiliate_subscriber(test: &mut ComponentTest, issi: u32, gssi: u32) {
|
||||
test.config.state_write().subscribers.affiliate(issi, gssi);
|
||||
test.config.state_write().subscribers.affiliate(issi, gssi).unwrap();
|
||||
}
|
||||
|
||||
/// Helper: build a U-SDS-DATA message from a source ISSI to a dest SSI with 16-bit payload
|
||||
@@ -32,12 +33,7 @@ fn build_u_sds_data_msg(dltime: TdmaTime, source_issi: u32, dest_ssi: u32, paylo
|
||||
called_party_short_number_address: None,
|
||||
called_party_ssi: Some(dest_ssi as u64),
|
||||
called_party_extension: None,
|
||||
short_data_type_identifier: 0,
|
||||
user_defined_data_1: Some(payload as u64),
|
||||
user_defined_data_2: None,
|
||||
user_defined_data_3: None,
|
||||
length_indicator: None,
|
||||
user_defined_data_4: None,
|
||||
user_defined_data: SdsUserData::Type1(payload),
|
||||
external_subscriber_number: None,
|
||||
dm_ms_address: None,
|
||||
};
|
||||
@@ -169,9 +165,7 @@ fn test_sds_from_brew_to_local() {
|
||||
msg: SapMsgInner::CmceSdsData(CmceSdsData {
|
||||
source_issi: 3000001,
|
||||
dest_issi: 2000001,
|
||||
short_data_type_identifier: 0,
|
||||
data: vec![0xCA, 0xFE],
|
||||
length_bits: 16,
|
||||
user_defined_data: SdsUserData::Type1(0xCAFE),
|
||||
}),
|
||||
};
|
||||
test.submit_message(msg);
|
||||
@@ -202,9 +196,7 @@ fn test_sds_from_brew_unregistered() {
|
||||
msg: SapMsgInner::CmceSdsData(CmceSdsData {
|
||||
source_issi: 3000001,
|
||||
dest_issi: 9999999,
|
||||
short_data_type_identifier: 0,
|
||||
data: vec![0xDE, 0xAD],
|
||||
length_bits: 16,
|
||||
user_defined_data: SdsUserData::Type1(0xDEAD),
|
||||
}),
|
||||
};
|
||||
test.submit_message(msg);
|
||||
|
||||
@@ -3,6 +3,7 @@ use core::fmt;
|
||||
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};
|
||||
use tetra_saps::control::enums::sds_user_data::SdsUserData;
|
||||
|
||||
/// Representation of the D-SDS-DATA PDU (Clause 14.7.1.10).
|
||||
/// This PDU shall be for receiving user defined SDS data.
|
||||
@@ -19,18 +20,8 @@ pub struct DSdsData {
|
||||
pub calling_party_address_ssi: Option<u64>,
|
||||
/// Conditional 24 bits, See note 1, condition: calling_party_type_identifier == 2
|
||||
pub calling_party_extension: Option<u64>,
|
||||
/// Type1, 2 bits, Short data type identifier
|
||||
pub short_data_type_identifier: u8,
|
||||
/// Conditional 16 bits, See note 2, condition: short_data_type_identifier == 0
|
||||
pub user_defined_data_1: Option<u64>,
|
||||
/// Conditional 32 bits, See note 2, condition: short_data_type_identifier == 1
|
||||
pub user_defined_data_2: Option<u64>,
|
||||
/// Conditional 64 bits, See note 2, condition: short_data_type_identifier == 2
|
||||
pub user_defined_data_3: Option<u64>,
|
||||
/// Conditional 11 bits, See note 2, condition: short_data_type_identifier == 3
|
||||
pub length_indicator: Option<u64>,
|
||||
/// Conditional See note 2, condition: short_data_type_identifier == 3
|
||||
pub user_defined_data_4: Option<Vec<u8>>,
|
||||
/// Either type1, type2, type3 or type4 user data field.
|
||||
pub user_defined_data: SdsUserData,
|
||||
/// Type3, External subscriber number
|
||||
pub external_subscriber_number: Option<Type3FieldGeneric>,
|
||||
/// Type3, DM-MS address
|
||||
@@ -62,43 +53,25 @@ impl DSdsData {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Type1
|
||||
let short_data_type_identifier = buffer.read_field(2, "short_data_type_identifier")? as u8;
|
||||
// Conditional
|
||||
let user_defined_data_1 = if short_data_type_identifier == 0 {
|
||||
Some(buffer.read_field(16, "short_data_type_identifier")?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Conditional
|
||||
let user_defined_data_2 = if short_data_type_identifier == 1 {
|
||||
Some(buffer.read_field(32, "user_defined_data_2")?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Conditional
|
||||
let user_defined_data_3 = if short_data_type_identifier == 2 {
|
||||
Some(buffer.read_field(64, "user_defined_data_3")?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Conditional
|
||||
let length_indicator = if short_data_type_identifier == 3 {
|
||||
Some(buffer.read_field(11, "length_indicator")?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Conditional
|
||||
let user_defined_data_4 = if short_data_type_identifier == 3 {
|
||||
let len_bits = length_indicator.unwrap() as usize;
|
||||
let num_bytes = (len_bits + 7) / 8;
|
||||
let mut data = vec![0u8; num_bytes];
|
||||
buffer.read_bits_into_slice(len_bits, &mut data).ok_or(PduParseErr::BufferEnded {
|
||||
field: Some("user_defined_data_4"),
|
||||
})?;
|
||||
Some(data)
|
||||
} else {
|
||||
None
|
||||
let user_defined_data = match short_data_type_identifier {
|
||||
0 => SdsUserData::Type1(buffer.read_field(16, "user_defined_data_1")? as u16),
|
||||
1 => SdsUserData::Type2(buffer.read_field(32, "user_defined_data_2")? as u32),
|
||||
2 => SdsUserData::Type3(buffer.read_field(64, "user_defined_data_3")?),
|
||||
3 => {
|
||||
let len_bits = buffer.read_field(11, "length_indicator")? as u16;
|
||||
let num_bytes = (len_bits + 7) / 8;
|
||||
let mut data = vec![0u8; num_bytes as usize];
|
||||
buffer
|
||||
.read_bits_into_slice(len_bits as usize, &mut data)
|
||||
.ok_or(PduParseErr::BufferEnded {
|
||||
field: Some("user_defined_data_4"),
|
||||
})?;
|
||||
SdsUserData::Type4(len_bits, data)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
// obit designates presence of any further type2, type3 or type4 fields
|
||||
@@ -120,12 +93,7 @@ impl DSdsData {
|
||||
calling_party_type_identifier,
|
||||
calling_party_address_ssi,
|
||||
calling_party_extension,
|
||||
short_data_type_identifier,
|
||||
user_defined_data_1,
|
||||
user_defined_data_2,
|
||||
user_defined_data_3,
|
||||
length_indicator,
|
||||
user_defined_data_4,
|
||||
user_defined_data,
|
||||
external_subscriber_number,
|
||||
dm_ms_address,
|
||||
})
|
||||
@@ -145,34 +113,25 @@ impl DSdsData {
|
||||
if let Some(ref value) = self.calling_party_extension {
|
||||
buffer.write_bits(*value, 24);
|
||||
}
|
||||
|
||||
// Type1
|
||||
buffer.write_bits(self.short_data_type_identifier as u64, 2);
|
||||
// Conditional
|
||||
if let Some(ref value) = self.user_defined_data_1 {
|
||||
buffer.write_bits(*value, 16);
|
||||
}
|
||||
// Conditional
|
||||
if let Some(ref value) = self.user_defined_data_2 {
|
||||
buffer.write_bits(*value, 32);
|
||||
}
|
||||
// Conditional
|
||||
if let Some(ref value) = self.user_defined_data_3 {
|
||||
buffer.write_bits(*value, 64);
|
||||
}
|
||||
// Conditional
|
||||
if let Some(ref value) = self.length_indicator {
|
||||
buffer.write_bits(*value, 11);
|
||||
}
|
||||
// Conditional
|
||||
if let Some(ref data) = self.user_defined_data_4 {
|
||||
let len_bits = self.length_indicator.unwrap() as usize;
|
||||
let full_bytes = len_bits / 8;
|
||||
let remaining_bits = len_bits % 8;
|
||||
for i in 0..full_bytes {
|
||||
buffer.write_bits(data[i] as u64, 8);
|
||||
}
|
||||
if remaining_bits > 0 {
|
||||
buffer.write_bits((data[full_bytes] >> (8 - remaining_bits)) as u64, remaining_bits);
|
||||
let short_data_type_identifier = self.user_defined_data.type_identifier();
|
||||
buffer.write_bits(short_data_type_identifier as u64, 2);
|
||||
|
||||
match &self.user_defined_data {
|
||||
SdsUserData::Type1(value) => buffer.write_bits(*value as u64, 16),
|
||||
SdsUserData::Type2(value) => buffer.write_bits(*value as u64, 32),
|
||||
SdsUserData::Type3(value) => buffer.write_bits(*value, 64),
|
||||
SdsUserData::Type4(len_bits, data) => {
|
||||
buffer.write_bits(*len_bits as u64, 11);
|
||||
let full_bytes = (*len_bits as usize) / 8;
|
||||
let remaining_bits = len_bits % 8;
|
||||
for i in 0..full_bytes {
|
||||
buffer.write_bits(data[i] as u64, 8);
|
||||
}
|
||||
if remaining_bits > 0 {
|
||||
buffer.write_bits((data[full_bytes] >> (8 - remaining_bits)) as u64, remaining_bits as usize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,16 +158,11 @@ impl fmt::Display for DSdsData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"DSdsData {{ calling_party_type_identifier: {:?} calling_party_address_ssi: {:?} calling_party_extension: {:?} short_data_type_identifier: {:?} user_defined_data_1: {:?} user_defined_data_2: {:?} user_defined_data_3: {:?} length_indicator: {:?} user_defined_data_4: {:?} external_subscriber_number: {:?} dm_ms_address: {:?} }}",
|
||||
"DSdsData {{ calling_party_type_identifier: {:?} calling_party_address_ssi: {:?} calling_party_extension: {:?} user_defined_data: {:?} external_subscriber_number: {:?} dm_ms_address: {:?} }}",
|
||||
self.calling_party_type_identifier,
|
||||
self.calling_party_address_ssi,
|
||||
self.calling_party_extension,
|
||||
self.short_data_type_identifier,
|
||||
self.user_defined_data_1,
|
||||
self.user_defined_data_2,
|
||||
self.user_defined_data_3,
|
||||
self.length_indicator,
|
||||
self.user_defined_data_4,
|
||||
self.user_defined_data,
|
||||
self.external_subscriber_number,
|
||||
self.dm_ms_address,
|
||||
)
|
||||
@@ -233,12 +187,7 @@ mod tests {
|
||||
calling_party_type_identifier: PartyTypeIdentifier::Ssi,
|
||||
calling_party_address_ssi: Some(1000001),
|
||||
calling_party_extension: None,
|
||||
short_data_type_identifier: 0,
|
||||
user_defined_data_1: Some(0xABCD),
|
||||
user_defined_data_2: None,
|
||||
user_defined_data_3: None,
|
||||
length_indicator: None,
|
||||
user_defined_data_4: None,
|
||||
user_defined_data: SdsUserData::Type1(0xABCD),
|
||||
external_subscriber_number: None,
|
||||
dm_ms_address: None,
|
||||
};
|
||||
@@ -246,8 +195,7 @@ mod tests {
|
||||
assert_eq!(parsed.calling_party_type_identifier, PartyTypeIdentifier::Ssi);
|
||||
assert_eq!(parsed.calling_party_address_ssi, Some(1000001));
|
||||
assert_eq!(parsed.calling_party_extension, None);
|
||||
assert_eq!(parsed.short_data_type_identifier, 0);
|
||||
assert_eq!(parsed.user_defined_data_1, Some(0xABCD));
|
||||
assert_eq!(parsed.user_defined_data, SdsUserData::Type1(0xABCD));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -257,21 +205,14 @@ mod tests {
|
||||
calling_party_type_identifier: PartyTypeIdentifier::Ssi,
|
||||
calling_party_address_ssi: Some(2000002),
|
||||
calling_party_extension: None,
|
||||
short_data_type_identifier: 3,
|
||||
user_defined_data_1: None,
|
||||
user_defined_data_2: None,
|
||||
user_defined_data_3: None,
|
||||
length_indicator: Some(40), // 5 bytes = 40 bits
|
||||
user_defined_data_4: Some(payload.clone()),
|
||||
user_defined_data: SdsUserData::Type4(40, payload.clone()), // 5 bytes = 40 bits
|
||||
external_subscriber_number: None,
|
||||
dm_ms_address: None,
|
||||
};
|
||||
let parsed = round_trip(&pdu);
|
||||
assert_eq!(parsed.calling_party_type_identifier, PartyTypeIdentifier::Ssi);
|
||||
assert_eq!(parsed.calling_party_address_ssi, Some(2000002));
|
||||
assert_eq!(parsed.short_data_type_identifier, 3);
|
||||
assert_eq!(parsed.length_indicator, Some(40));
|
||||
assert_eq!(parsed.user_defined_data_4, Some(payload));
|
||||
assert_eq!(parsed.user_defined_data, SdsUserData::Type4(40, payload));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -280,12 +221,7 @@ mod tests {
|
||||
calling_party_type_identifier: PartyTypeIdentifier::Tsi,
|
||||
calling_party_address_ssi: Some(3000003),
|
||||
calling_party_extension: Some(0x123456),
|
||||
short_data_type_identifier: 0,
|
||||
user_defined_data_1: Some(0x1234),
|
||||
user_defined_data_2: None,
|
||||
user_defined_data_3: None,
|
||||
length_indicator: None,
|
||||
user_defined_data_4: None,
|
||||
user_defined_data: SdsUserData::Type1(0x1234),
|
||||
external_subscriber_number: None,
|
||||
dm_ms_address: None,
|
||||
};
|
||||
@@ -293,8 +229,7 @@ mod tests {
|
||||
assert_eq!(parsed.calling_party_type_identifier, PartyTypeIdentifier::Tsi);
|
||||
assert_eq!(parsed.calling_party_address_ssi, Some(3000003));
|
||||
assert_eq!(parsed.calling_party_extension, Some(0x123456));
|
||||
assert_eq!(parsed.short_data_type_identifier, 0);
|
||||
assert_eq!(parsed.user_defined_data_1, Some(0x1234));
|
||||
assert_eq!(parsed.user_defined_data, SdsUserData::Type1(0x1234));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -303,12 +238,7 @@ mod tests {
|
||||
calling_party_type_identifier: PartyTypeIdentifier::Sna,
|
||||
calling_party_address_ssi: None,
|
||||
calling_party_extension: None,
|
||||
short_data_type_identifier: 1,
|
||||
user_defined_data_1: None,
|
||||
user_defined_data_2: Some(0xDEADBEEF),
|
||||
user_defined_data_3: None,
|
||||
length_indicator: None,
|
||||
user_defined_data_4: None,
|
||||
user_defined_data: SdsUserData::Type2(0xDEADBEEF),
|
||||
external_subscriber_number: None,
|
||||
dm_ms_address: None,
|
||||
};
|
||||
@@ -316,7 +246,6 @@ mod tests {
|
||||
assert_eq!(parsed.calling_party_type_identifier, PartyTypeIdentifier::Sna);
|
||||
assert_eq!(parsed.calling_party_address_ssi, None);
|
||||
assert_eq!(parsed.calling_party_extension, None);
|
||||
assert_eq!(parsed.short_data_type_identifier, 1);
|
||||
assert_eq!(parsed.user_defined_data_2, Some(0xDEADBEEF));
|
||||
assert_eq!(parsed.user_defined_data, SdsUserData::Type2(0xDEADBEEF));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use core::fmt;
|
||||
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};
|
||||
use tetra_saps::control::enums::sds_user_data::SdsUserData;
|
||||
|
||||
/// Representation of the U-SDS-DATA PDU (Clause 14.7.2.8).
|
||||
/// This PDU shall be for sending user defined SDS data.
|
||||
@@ -26,18 +27,8 @@ pub struct USdsData {
|
||||
pub called_party_ssi: Option<u64>,
|
||||
/// Conditional 24 bits, See note 2, condition: called_party_type_identifier == 2
|
||||
pub called_party_extension: Option<u64>,
|
||||
/// Type1, 2 bits, See note 4,
|
||||
pub short_data_type_identifier: u8,
|
||||
/// Conditional 16 bits, See note 2, condition: short_data_type_identifier == 0
|
||||
pub user_defined_data_1: Option<u64>,
|
||||
/// Conditional 32 bits, See note 2, condition: short_data_type_identifier == 1
|
||||
pub user_defined_data_2: Option<u64>,
|
||||
/// Conditional 64 bits, See note 2, condition: short_data_type_identifier == 2
|
||||
pub user_defined_data_3: Option<u64>,
|
||||
/// Conditional 11 bits, See note 2, condition: short_data_type_identifier == 3
|
||||
pub length_indicator: Option<u64>,
|
||||
/// Conditional See note 2, condition: short_data_type_identifier == 3
|
||||
pub user_defined_data_4: Option<Vec<u8>>,
|
||||
/// Either type1, type2, type3 or type4 user data field.
|
||||
pub user_defined_data: SdsUserData,
|
||||
/// Type3, External subscriber number
|
||||
pub external_subscriber_number: Option<Type3FieldGeneric>,
|
||||
/// Type3, DM-MS address
|
||||
@@ -77,43 +68,25 @@ impl USdsData {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Type1
|
||||
let short_data_type_identifier = buffer.read_field(2, "short_data_type_identifier")? as u8;
|
||||
// Conditional
|
||||
let user_defined_data_1 = if short_data_type_identifier == 0 {
|
||||
Some(buffer.read_field(16, "user_defined_data_1")?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Conditional
|
||||
let user_defined_data_2 = if short_data_type_identifier == 1 {
|
||||
Some(buffer.read_field(32, "user_defined_data_2")?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Conditional
|
||||
let user_defined_data_3 = if short_data_type_identifier == 2 {
|
||||
Some(buffer.read_field(64, "user_defined_data_3")?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Conditional
|
||||
let length_indicator = if short_data_type_identifier == 3 {
|
||||
Some(buffer.read_field(11, "length_indicator")?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Conditional
|
||||
let user_defined_data_4 = if short_data_type_identifier == 3 {
|
||||
let len_bits = length_indicator.unwrap() as usize;
|
||||
let num_bytes = (len_bits + 7) / 8;
|
||||
let mut data = vec![0u8; num_bytes];
|
||||
buffer.read_bits_into_slice(len_bits, &mut data).ok_or(PduParseErr::BufferEnded {
|
||||
field: Some("user_defined_data_4"),
|
||||
})?;
|
||||
Some(data)
|
||||
} else {
|
||||
None
|
||||
let user_defined_data = match short_data_type_identifier {
|
||||
0 => SdsUserData::Type1(buffer.read_field(16, "user_defined_data_1")? as u16),
|
||||
1 => SdsUserData::Type2(buffer.read_field(32, "user_defined_data_2")? as u32),
|
||||
2 => SdsUserData::Type3(buffer.read_field(64, "user_defined_data_3")?),
|
||||
3 => {
|
||||
let len_bits = buffer.read_field(11, "length_indicator")? as u16;
|
||||
let num_bytes = (len_bits as usize + 7) / 8;
|
||||
let mut data = vec![0u8; num_bytes];
|
||||
buffer
|
||||
.read_bits_into_slice(len_bits as usize, &mut data)
|
||||
.ok_or(PduParseErr::BufferEnded {
|
||||
field: Some("user_defined_data_4"),
|
||||
})?;
|
||||
SdsUserData::Type4(len_bits, data)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
// obit designates presence of any further type2, type3 or type4 fields
|
||||
@@ -137,12 +110,7 @@ impl USdsData {
|
||||
called_party_short_number_address,
|
||||
called_party_ssi,
|
||||
called_party_extension,
|
||||
short_data_type_identifier,
|
||||
user_defined_data_1,
|
||||
user_defined_data_2,
|
||||
user_defined_data_3,
|
||||
length_indicator,
|
||||
user_defined_data_4,
|
||||
user_defined_data,
|
||||
external_subscriber_number,
|
||||
dm_ms_address,
|
||||
})
|
||||
@@ -168,34 +136,25 @@ impl USdsData {
|
||||
if let Some(ref value) = self.called_party_extension {
|
||||
buffer.write_bits(*value, 24);
|
||||
}
|
||||
|
||||
// Type1
|
||||
buffer.write_bits(self.short_data_type_identifier as u64, 2);
|
||||
// Conditional
|
||||
if let Some(ref value) = self.user_defined_data_1 {
|
||||
buffer.write_bits(*value, 16);
|
||||
}
|
||||
// Conditional
|
||||
if let Some(ref value) = self.user_defined_data_2 {
|
||||
buffer.write_bits(*value, 32);
|
||||
}
|
||||
// Conditional
|
||||
if let Some(ref value) = self.user_defined_data_3 {
|
||||
buffer.write_bits(*value, 64);
|
||||
}
|
||||
// Conditional
|
||||
if let Some(ref value) = self.length_indicator {
|
||||
buffer.write_bits(*value, 11);
|
||||
}
|
||||
// Conditional
|
||||
if let Some(ref data) = self.user_defined_data_4 {
|
||||
let len_bits = self.length_indicator.unwrap() as usize;
|
||||
let full_bytes = len_bits / 8;
|
||||
let remaining_bits = len_bits % 8;
|
||||
for i in 0..full_bytes {
|
||||
buffer.write_bits(data[i] as u64, 8);
|
||||
}
|
||||
if remaining_bits > 0 {
|
||||
buffer.write_bits((data[full_bytes] >> (8 - remaining_bits)) as u64, remaining_bits);
|
||||
let short_data_type_identifier = self.user_defined_data.type_identifier();
|
||||
buffer.write_bits(short_data_type_identifier as u64, 2);
|
||||
|
||||
match &self.user_defined_data {
|
||||
SdsUserData::Type1(value) => buffer.write_bits(*value as u64, 16),
|
||||
SdsUserData::Type2(value) => buffer.write_bits(*value as u64, 32),
|
||||
SdsUserData::Type3(value) => buffer.write_bits(*value, 64),
|
||||
SdsUserData::Type4(len_bits, data) => {
|
||||
buffer.write_bits(*len_bits as u64, 11);
|
||||
let full_bytes = (*len_bits as usize) / 8;
|
||||
let remaining_bits = len_bits % 8;
|
||||
for i in 0..full_bytes {
|
||||
buffer.write_bits(data[i] as u64, 8);
|
||||
}
|
||||
if remaining_bits > 0 {
|
||||
buffer.write_bits((data[full_bytes] >> (8 - remaining_bits)) as u64, remaining_bits as usize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,18 +181,13 @@ impl fmt::Display for USdsData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"USdsData {{ area_selection: {:?} called_party_type_identifier: {:?} called_party_short_number_address: {:?} called_party_ssi: {:?} called_party_extension: {:?} short_data_type_identifier: {:?} user_defined_data_1: {:?} user_defined_data_2: {:?} user_defined_data_3: {:?} length_indicator: {:?} user_defined_data_4: {:?} external_subscriber_number: {:?} dm_ms_address: {:?} }}",
|
||||
"USdsData {{ area_selection: {:?} called_party_type_identifier: {:?} called_party_short_number_address: {:?} called_party_ssi: {:?} called_party_extension: {:?} user_defined_data: {:?} external_subscriber_number: {:?} dm_ms_address: {:?} }}",
|
||||
self.area_selection,
|
||||
self.called_party_type_identifier,
|
||||
self.called_party_short_number_address,
|
||||
self.called_party_ssi,
|
||||
self.called_party_extension,
|
||||
self.short_data_type_identifier,
|
||||
self.user_defined_data_1,
|
||||
self.user_defined_data_2,
|
||||
self.user_defined_data_3,
|
||||
self.length_indicator,
|
||||
self.user_defined_data_4,
|
||||
self.user_defined_data,
|
||||
self.external_subscriber_number,
|
||||
self.dm_ms_address,
|
||||
)
|
||||
@@ -260,12 +214,7 @@ mod tests {
|
||||
called_party_short_number_address: None,
|
||||
called_party_ssi: Some(1000001),
|
||||
called_party_extension: None,
|
||||
short_data_type_identifier: 0,
|
||||
user_defined_data_1: Some(0xCAFE),
|
||||
user_defined_data_2: None,
|
||||
user_defined_data_3: None,
|
||||
length_indicator: None,
|
||||
user_defined_data_4: None,
|
||||
user_defined_data: SdsUserData::Type1(0xCAFE),
|
||||
external_subscriber_number: None,
|
||||
dm_ms_address: None,
|
||||
};
|
||||
@@ -274,8 +223,7 @@ mod tests {
|
||||
assert_eq!(parsed.called_party_type_identifier, PartyTypeIdentifier::Ssi);
|
||||
assert_eq!(parsed.called_party_ssi, Some(1000001));
|
||||
assert_eq!(parsed.called_party_extension, None);
|
||||
assert_eq!(parsed.short_data_type_identifier, 0);
|
||||
assert_eq!(parsed.user_defined_data_1, Some(0xCAFE));
|
||||
assert_eq!(parsed.user_defined_data, SdsUserData::Type1(0xCAFE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -287,21 +235,14 @@ mod tests {
|
||||
called_party_short_number_address: None,
|
||||
called_party_ssi: Some(2000002),
|
||||
called_party_extension: None,
|
||||
short_data_type_identifier: 3,
|
||||
user_defined_data_1: None,
|
||||
user_defined_data_2: None,
|
||||
user_defined_data_3: None,
|
||||
length_indicator: Some(24), // 3 bytes
|
||||
user_defined_data_4: Some(payload.clone()),
|
||||
user_defined_data: SdsUserData::Type4(24, payload.clone()),
|
||||
external_subscriber_number: None,
|
||||
dm_ms_address: None,
|
||||
};
|
||||
let parsed = round_trip(&pdu);
|
||||
assert_eq!(parsed.area_selection, 5);
|
||||
assert_eq!(parsed.called_party_ssi, Some(2000002));
|
||||
assert_eq!(parsed.short_data_type_identifier, 3);
|
||||
assert_eq!(parsed.length_indicator, Some(24));
|
||||
assert_eq!(parsed.user_defined_data_4, Some(payload));
|
||||
assert_eq!(parsed.user_defined_data, SdsUserData::Type4(24, payload));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -312,12 +253,7 @@ mod tests {
|
||||
called_party_short_number_address: Some(42),
|
||||
called_party_ssi: None,
|
||||
called_party_extension: None,
|
||||
short_data_type_identifier: 1,
|
||||
user_defined_data_1: None,
|
||||
user_defined_data_2: Some(0x12345678),
|
||||
user_defined_data_3: None,
|
||||
length_indicator: None,
|
||||
user_defined_data_4: None,
|
||||
user_defined_data: SdsUserData::Type2(0x12345678),
|
||||
external_subscriber_number: None,
|
||||
dm_ms_address: None,
|
||||
};
|
||||
@@ -325,7 +261,7 @@ mod tests {
|
||||
assert_eq!(parsed.called_party_type_identifier, PartyTypeIdentifier::Sna);
|
||||
assert_eq!(parsed.called_party_short_number_address, Some(42));
|
||||
assert_eq!(parsed.called_party_ssi, None);
|
||||
assert_eq!(parsed.user_defined_data_2, Some(0x12345678));
|
||||
assert_eq!(parsed.user_defined_data, SdsUserData::Type2(0x12345678));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -336,12 +272,7 @@ mod tests {
|
||||
called_party_short_number_address: None,
|
||||
called_party_ssi: Some(3000003),
|
||||
called_party_extension: Some(0xABCDEF),
|
||||
short_data_type_identifier: 2,
|
||||
user_defined_data_1: None,
|
||||
user_defined_data_2: None,
|
||||
user_defined_data_3: Some(0x0102030405060708),
|
||||
length_indicator: None,
|
||||
user_defined_data_4: None,
|
||||
user_defined_data: SdsUserData::Type3(0x0102030405060708),
|
||||
external_subscriber_number: None,
|
||||
dm_ms_address: None,
|
||||
};
|
||||
@@ -349,7 +280,6 @@ mod tests {
|
||||
assert_eq!(parsed.called_party_type_identifier, PartyTypeIdentifier::Tsi);
|
||||
assert_eq!(parsed.called_party_ssi, Some(3000003));
|
||||
assert_eq!(parsed.called_party_extension, Some(0xABCDEF));
|
||||
assert_eq!(parsed.short_data_type_identifier, 2);
|
||||
assert_eq!(parsed.user_defined_data_3, Some(0x0102030405060708));
|
||||
assert_eq!(parsed.user_defined_data, SdsUserData::Type3(0x0102030405060708));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod circuit_mode_type;
|
||||
pub mod communication_type;
|
||||
pub mod sds_user_data;
|
||||
|
||||
40
crates/tetra-saps/src/control/enums/sds_user_data.rs
Normal file
40
crates/tetra-saps/src/control/enums/sds_user_data.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SdsUserData {
|
||||
/// Type field 0, 16 bits, short_data_type_identifier == 0
|
||||
Type1(u16),
|
||||
/// Type field 1, 32 bits, short_data_type_identifier == 1
|
||||
Type2(u32),
|
||||
/// Type field 2, 64 bits, short_data_type_identifier == 2
|
||||
Type3(u64),
|
||||
/// Type field 3, variable length, short_data_type_identifier == 3
|
||||
Type4(u16, Vec<u8>),
|
||||
}
|
||||
|
||||
impl SdsUserData {
|
||||
pub fn type_identifier(&self) -> u8 {
|
||||
match self {
|
||||
SdsUserData::Type1(_) => 0,
|
||||
SdsUserData::Type2(_) => 1,
|
||||
SdsUserData::Type3(_) => 2,
|
||||
SdsUserData::Type4(_, _) => 3,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn length_bits(&self) -> u16 {
|
||||
match self {
|
||||
SdsUserData::Type1(_) => 16,
|
||||
SdsUserData::Type2(_) => 32,
|
||||
SdsUserData::Type3(_) => 64,
|
||||
SdsUserData::Type4(len_bits, _) => *len_bits,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_arr(&self) -> Vec<u8> {
|
||||
match self {
|
||||
SdsUserData::Type1(value) => value.to_be_bytes().to_vec(),
|
||||
SdsUserData::Type2(value) => value.to_be_bytes().to_vec(),
|
||||
SdsUserData::Type3(value) => value.to_be_bytes().to_vec(),
|
||||
SdsUserData::Type4(_, data) => data.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::control::enums::sds_user_data::SdsUserData;
|
||||
|
||||
/// SDS data routing between CMCE SDS subentity and Brew entity
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CmceSdsData {
|
||||
@@ -5,10 +7,6 @@ pub struct CmceSdsData {
|
||||
pub source_issi: u32,
|
||||
/// Destination ISSI (called party)
|
||||
pub dest_issi: u32,
|
||||
/// Short data type identifier (0-3)
|
||||
pub short_data_type_identifier: u8,
|
||||
/// SDS payload data (raw bytes from user_defined_data_1/2/3/4)
|
||||
pub data: Vec<u8>,
|
||||
/// Length in bits (for type 4, from length_indicator)
|
||||
pub length_bits: u16,
|
||||
/// User-defined data (type1, type2, type3, or type4)
|
||||
pub user_defined_data: SdsUserData,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user