mirror of
https://github.com/misadeks/tetra-bluestation.git
synced 2026-10-23 18:02:21 +00:00
implemented prototype neighbor cell broadcast
This commit is contained in:
1 parent
71d299390a
commit
d645f74c4f
7 files changed
+540
-18
No files matched your search
@@ -60,6 +60,67 @@ pub struct CfgNetInfo {
|
||||
pub mnc: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
pub struct CfgBsServiceDetails {
|
||||
#[serde(default)]
|
||||
pub registration: bool,
|
||||
#[serde(default)]
|
||||
pub deregistration: bool,
|
||||
#[serde(default)]
|
||||
pub priority_cell: bool,
|
||||
#[serde(default)]
|
||||
pub no_minimum_mode: bool,
|
||||
#[serde(default)]
|
||||
pub migration: bool,
|
||||
#[serde(default)]
|
||||
pub system_wide_services: bool,
|
||||
#[serde(default)]
|
||||
pub voice_service: bool,
|
||||
#[serde(default)]
|
||||
pub circuit_mode_data_service: bool,
|
||||
#[serde(default)]
|
||||
pub sndcp_service: bool,
|
||||
#[serde(default)]
|
||||
pub aie_service: bool,
|
||||
#[serde(default)]
|
||||
pub advanced_link: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct CfgNeighborCellCa {
|
||||
/// Type1, 5 bits
|
||||
pub cell_identifier_ca: u8,
|
||||
/// Type1, 2 bits
|
||||
pub cell_reselection_types_supported: u8,
|
||||
/// Type1, 1 bit
|
||||
pub neighbor_cell_synchronized: bool,
|
||||
/// Type1, 2 bits
|
||||
pub cell_load_ca: u8,
|
||||
/// Type1, 12 bits
|
||||
pub main_carrier_number: u16,
|
||||
|
||||
/// Type2, 10 bits
|
||||
pub main_carrier_number_extension: Option<u16>,
|
||||
/// Type2, 10 bits
|
||||
pub mcc: Option<u16>,
|
||||
/// Type2, 14 bits
|
||||
pub mnc: Option<u16>,
|
||||
/// Type2, 14 bits
|
||||
pub location_area: Option<u16>,
|
||||
/// Type2, 3 bits
|
||||
pub maximum_ms_transmit_power: Option<u8>,
|
||||
/// Type2, 4 bits
|
||||
pub minimum_rx_access_level: Option<u8>,
|
||||
/// Type2, 16 bits
|
||||
pub subscriber_class: Option<u16>,
|
||||
/// Type2, 12 bits
|
||||
pub bs_service_details: Option<CfgBsServiceDetails>,
|
||||
/// Type2, 5 bits
|
||||
pub timeshare_cell_information_or_security_parameters: Option<u8>,
|
||||
/// Type2, 6 bits
|
||||
pub tdma_frame_offset: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct CfgCellInfo {
|
||||
// 2 bits, from 18.4.2.1 D-MLE-SYNC
|
||||
@@ -137,6 +198,10 @@ pub struct CfgCellInfo {
|
||||
pub u_plane_dtx: bool,
|
||||
#[serde(default)]
|
||||
pub frame_18_ext: bool,
|
||||
|
||||
/// Neighbour cell list for D-NWRK-BROADCAST (CA)
|
||||
#[serde(default)]
|
||||
pub neighbor_cells_ca: Vec<CfgNeighborCellCa>,
|
||||
}
|
||||
|
||||
impl Default for CfgCellInfo {
|
||||
@@ -172,6 +237,8 @@ impl Default for CfgCellInfo {
|
||||
ts_reserved_frames: 0,
|
||||
u_plane_dtx: false,
|
||||
frame_18_ext: false,
|
||||
|
||||
neighbor_cells_ca: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,6 +355,64 @@ impl StackConfig {
|
||||
};
|
||||
}
|
||||
|
||||
if self.cell.neighbor_cells_ca.len() > 7 {
|
||||
return Err("cell_info.neighbor_cells_ca supports at most 7 entries");
|
||||
}
|
||||
for cell in &self.cell.neighbor_cells_ca {
|
||||
if cell.cell_identifier_ca > 0x1F {
|
||||
return Err("cell_info.neighbor_cells_ca.cell_identifier_ca must be <= 31");
|
||||
}
|
||||
if cell.cell_reselection_types_supported > 0x3 {
|
||||
return Err("cell_info.neighbor_cells_ca.cell_reselection_types_supported must be <= 3");
|
||||
}
|
||||
if cell.cell_load_ca > 0x3 {
|
||||
return Err("cell_info.neighbor_cells_ca.cell_load_ca must be <= 3");
|
||||
}
|
||||
if cell.main_carrier_number > 0x0FFF {
|
||||
return Err("cell_info.neighbor_cells_ca.main_carrier_number must be <= 4095");
|
||||
}
|
||||
if let Some(v) = cell.main_carrier_number_extension {
|
||||
if v > 0x03FF {
|
||||
return Err("cell_info.neighbor_cells_ca.main_carrier_number_extension must be <= 1023");
|
||||
}
|
||||
}
|
||||
if let Some(v) = cell.mcc {
|
||||
if v > 0x03FF {
|
||||
return Err("cell_info.neighbor_cells_ca.mcc must be <= 1023");
|
||||
}
|
||||
}
|
||||
if let Some(v) = cell.mnc {
|
||||
if v > 0x3FFF {
|
||||
return Err("cell_info.neighbor_cells_ca.mnc must be <= 16383");
|
||||
}
|
||||
}
|
||||
if let Some(v) = cell.location_area {
|
||||
if v > 0x3FFF {
|
||||
return Err("cell_info.neighbor_cells_ca.location_area must be <= 16383");
|
||||
}
|
||||
}
|
||||
if let Some(v) = cell.maximum_ms_transmit_power {
|
||||
if v > 0x7 {
|
||||
return Err("cell_info.neighbor_cells_ca.maximum_ms_transmit_power must be <= 7");
|
||||
}
|
||||
}
|
||||
if let Some(v) = cell.minimum_rx_access_level {
|
||||
if v > 0xF {
|
||||
return Err("cell_info.neighbor_cells_ca.minimum_rx_access_level must be <= 15");
|
||||
}
|
||||
}
|
||||
if let Some(v) = cell.timeshare_cell_information_or_security_parameters {
|
||||
if v > 0x1F {
|
||||
return Err("cell_info.neighbor_cells_ca.timeshare_cell_information_or_security_parameters must be <= 31");
|
||||
}
|
||||
}
|
||||
if let Some(v) = cell.tdma_frame_offset {
|
||||
if v > 0x3F {
|
||||
return Err("cell_info.neighbor_cells_ca.tdma_frame_offset must be <= 63");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,18 @@ use toml::Value;
|
||||
|
||||
use super::stack_config_brew::{CfgBrewDto, apply_brew_patch};
|
||||
|
||||
use super::stack_config::{CfgCellInfo, CfgNetInfo, CfgPhyIo, PhyBackend, SharedConfig, StackConfig, StackMode, StackState};
|
||||
use super::stack_config::{
|
||||
CfgBsServiceDetails,
|
||||
CfgCellInfo,
|
||||
CfgNeighborCellCa,
|
||||
CfgNetInfo,
|
||||
CfgPhyIo,
|
||||
PhyBackend,
|
||||
SharedConfig,
|
||||
StackConfig,
|
||||
StackMode,
|
||||
StackState,
|
||||
};
|
||||
use super::stack_config_soapy::{CfgSoapySdr, LimeSdrCfg, SXceiverCfg, UsrpB2xxCfg};
|
||||
|
||||
/// Build `SharedConfig` from a TOML configuration file
|
||||
@@ -237,6 +248,9 @@ fn apply_cell_info_patch(dst: &mut CfgCellInfo, ci: CellInfoDto) {
|
||||
if let Some(v) = ci.frame_18_ext {
|
||||
dst.frame_18_ext = v;
|
||||
}
|
||||
if let Some(v) = ci.neighbor_cells_ca {
|
||||
dst.neighbor_cells_ca = v.into_iter().map(Into::into).collect();
|
||||
}
|
||||
}
|
||||
|
||||
fn sorted_keys(map: &HashMap<String, Value>) -> Vec<&str> {
|
||||
@@ -382,6 +396,8 @@ struct CellInfoDto {
|
||||
pub u_plane_dtx: Option<bool>,
|
||||
pub frame_18_ext: Option<bool>,
|
||||
|
||||
pub neighbor_cells_ca: Option<Vec<NeighborCellCaDto>>,
|
||||
|
||||
#[serde(flatten)]
|
||||
extra: HashMap<String, Value>,
|
||||
}
|
||||
@@ -393,3 +409,90 @@ struct StackStatePatch {
|
||||
#[serde(flatten)]
|
||||
extra: HashMap<String, Value>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct NeighborCellCaDto {
|
||||
pub cell_identifier_ca: u8,
|
||||
pub cell_reselection_types_supported: u8,
|
||||
pub neighbor_cell_synchronized: bool,
|
||||
pub cell_load_ca: u8,
|
||||
pub main_carrier_number: u16,
|
||||
|
||||
pub main_carrier_number_extension: Option<u16>,
|
||||
pub mcc: Option<u16>,
|
||||
pub mnc: Option<u16>,
|
||||
pub location_area: Option<u16>,
|
||||
pub maximum_ms_transmit_power: Option<u8>,
|
||||
pub minimum_rx_access_level: Option<u8>,
|
||||
pub subscriber_class: Option<u16>,
|
||||
pub bs_service_details: Option<BsServiceDetailsDto>,
|
||||
pub timeshare_cell_information_or_security_parameters: Option<u8>,
|
||||
pub tdma_frame_offset: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize)]
|
||||
struct BsServiceDetailsDto {
|
||||
#[serde(default)]
|
||||
pub registration: bool,
|
||||
#[serde(default)]
|
||||
pub deregistration: bool,
|
||||
#[serde(default)]
|
||||
pub priority_cell: bool,
|
||||
#[serde(default)]
|
||||
pub no_minimum_mode: bool,
|
||||
#[serde(default)]
|
||||
pub migration: bool,
|
||||
#[serde(default)]
|
||||
pub system_wide_services: bool,
|
||||
#[serde(default)]
|
||||
pub voice_service: bool,
|
||||
#[serde(default)]
|
||||
pub circuit_mode_data_service: bool,
|
||||
#[serde(default)]
|
||||
pub sndcp_service: bool,
|
||||
#[serde(default)]
|
||||
pub aie_service: bool,
|
||||
#[serde(default)]
|
||||
pub advanced_link: bool,
|
||||
}
|
||||
|
||||
impl From<NeighborCellCaDto> for CfgNeighborCellCa {
|
||||
fn from(dto: NeighborCellCaDto) -> Self {
|
||||
Self {
|
||||
cell_identifier_ca: dto.cell_identifier_ca,
|
||||
cell_reselection_types_supported: dto.cell_reselection_types_supported,
|
||||
neighbor_cell_synchronized: dto.neighbor_cell_synchronized,
|
||||
cell_load_ca: dto.cell_load_ca,
|
||||
main_carrier_number: dto.main_carrier_number,
|
||||
main_carrier_number_extension: dto.main_carrier_number_extension,
|
||||
mcc: dto.mcc,
|
||||
mnc: dto.mnc,
|
||||
location_area: dto.location_area,
|
||||
maximum_ms_transmit_power: dto.maximum_ms_transmit_power,
|
||||
minimum_rx_access_level: dto.minimum_rx_access_level,
|
||||
subscriber_class: dto.subscriber_class,
|
||||
bs_service_details: dto.bs_service_details.map(Into::into),
|
||||
timeshare_cell_information_or_security_parameters: dto
|
||||
.timeshare_cell_information_or_security_parameters,
|
||||
tdma_frame_offset: dto.tdma_frame_offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BsServiceDetailsDto> for CfgBsServiceDetails {
|
||||
fn from(dto: BsServiceDetailsDto) -> Self {
|
||||
Self {
|
||||
registration: dto.registration,
|
||||
deregistration: dto.deregistration,
|
||||
priority_cell: dto.priority_cell,
|
||||
no_minimum_mode: dto.no_minimum_mode,
|
||||
migration: dto.migration,
|
||||
system_wide_services: dto.system_wide_services,
|
||||
voice_service: dto.voice_service,
|
||||
circuit_mode_data_service: dto.circuit_mode_data_service,
|
||||
sndcp_service: dto.sndcp_service,
|
||||
aie_service: dto.aie_service,
|
||||
advanced_link: dto.advanced_link,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ 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::fields::bs_service_details::BsServiceDetails;
|
||||
use tetra_pdus::mle::fields::neighbour_cell_information_for_ca::NeighbourCellInformationForCa;
|
||||
use tetra_pdus::mle::pdus::d_mle_sync::DMleSync;
|
||||
use tetra_pdus::mle::pdus::d_mle_sysinfo::DMleSysinfo;
|
||||
use tetra_pdus::mle::pdus::d_nwrk_broadcast::DNwrkBroadcast;
|
||||
@@ -627,13 +629,50 @@ impl Mle {
|
||||
let tetra_network_time = Self::encode_tetra_network_time(local_now);
|
||||
|
||||
let cell_load_ca = self.config.state_read().cell_load_ca & 0x3;
|
||||
let neighbour_cells: Vec<NeighbourCellInformationForCa> = self
|
||||
.config
|
||||
.config()
|
||||
.cell
|
||||
.neighbor_cells_ca
|
||||
.iter()
|
||||
.map(|cfg| NeighbourCellInformationForCa {
|
||||
cell_identifier_ca: cfg.cell_identifier_ca,
|
||||
cell_reselection_types_supported: cfg.cell_reselection_types_supported,
|
||||
neighbour_cell_synchronized: cfg.neighbor_cell_synchronized,
|
||||
cell_load_ca: cfg.cell_load_ca,
|
||||
main_carrier_number: cfg.main_carrier_number,
|
||||
main_carrier_number_extension: cfg.main_carrier_number_extension,
|
||||
mcc: cfg.mcc,
|
||||
mnc: cfg.mnc,
|
||||
location_area: cfg.location_area,
|
||||
maximum_ms_transmit_power: cfg.maximum_ms_transmit_power,
|
||||
minimum_rx_access_level: cfg.minimum_rx_access_level,
|
||||
subscriber_class: cfg.subscriber_class,
|
||||
bs_service_details: cfg.bs_service_details.as_ref().map(|bs| BsServiceDetails {
|
||||
registration: bs.registration,
|
||||
deregistration: bs.deregistration,
|
||||
priority_cell: bs.priority_cell,
|
||||
no_minimum_mode: bs.no_minimum_mode,
|
||||
migration: bs.migration,
|
||||
system_wide_services: bs.system_wide_services,
|
||||
voice_service: bs.voice_service,
|
||||
circuit_mode_data_service: bs.circuit_mode_data_service,
|
||||
sndcp_service: bs.sndcp_service,
|
||||
aie_service: bs.aie_service,
|
||||
advanced_link: bs.advanced_link,
|
||||
}),
|
||||
timeshare_cell_information_or_security_parameters: cfg
|
||||
.timeshare_cell_information_or_security_parameters,
|
||||
tdma_frame_offset: cfg.tdma_frame_offset,
|
||||
})
|
||||
.collect();
|
||||
let pdu = DNwrkBroadcast {
|
||||
cell_re_select_parameters: 0,
|
||||
cell_load_ca,
|
||||
tetra_network_time: Some(tetra_network_time),
|
||||
// Explicitly signal "no neighbour cell information" (P-bit set, value 0).
|
||||
number_of_ca_neighbour_cells: Some(0),
|
||||
neighbour_cell_information_for_ca: None,
|
||||
// Explicitly signal "no neighbour cell information" when empty.
|
||||
number_of_ca_neighbour_cells: Some(neighbour_cells.len() as u8),
|
||||
neighbour_cell_information_for_ca: neighbour_cells,
|
||||
};
|
||||
|
||||
tracing::debug!("DNwrkBroadcast {:?}", &pdu);
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod bs_service_details;
|
||||
pub mod neighbour_cell_information_for_ca;
|
||||
@@ -0,0 +1,202 @@
|
||||
use core::fmt;
|
||||
|
||||
use tetra_core::typed_pdu_fields::typed;
|
||||
use tetra_core::{BitBuffer, pdu_parse_error::PduParseErr};
|
||||
|
||||
use crate::mle::fields::bs_service_details::BsServiceDetails;
|
||||
|
||||
/// Clause 18.5.17: D-NWRK-BROADCAST Neighbor cell information for CA element (Table 18.64).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NeighbourCellInformationForCa {
|
||||
/// Type1, 5 bits
|
||||
pub cell_identifier_ca: u8,
|
||||
/// Type1, 2 bits
|
||||
pub cell_reselection_types_supported: u8,
|
||||
/// Type1, 1 bit
|
||||
pub neighbour_cell_synchronized: bool,
|
||||
/// Type1, 2 bits
|
||||
pub cell_load_ca: u8,
|
||||
/// Type1, 12 bits
|
||||
pub main_carrier_number: u16,
|
||||
/// Type2, 10 bits
|
||||
pub main_carrier_number_extension: Option<u16>,
|
||||
/// Type2, 10 bits
|
||||
pub mcc: Option<u16>,
|
||||
/// Type2, 14 bits
|
||||
pub mnc: Option<u16>,
|
||||
/// Type2, 14 bits
|
||||
pub location_area: Option<u16>,
|
||||
/// Type2, 3 bits
|
||||
pub maximum_ms_transmit_power: Option<u8>,
|
||||
/// Type2, 4 bits
|
||||
pub minimum_rx_access_level: Option<u8>,
|
||||
/// Type2, 16 bits
|
||||
pub subscriber_class: Option<u16>,
|
||||
/// Type2, 12 bits
|
||||
pub bs_service_details: Option<BsServiceDetails>,
|
||||
/// Type2, 5 bits
|
||||
pub timeshare_cell_information_or_security_parameters: Option<u8>,
|
||||
/// Type2, 6 bits
|
||||
pub tdma_frame_offset: Option<u8>,
|
||||
}
|
||||
|
||||
impl NeighbourCellInformationForCa {
|
||||
pub fn from_bitbuf(buf: &mut BitBuffer) -> Result<Self, PduParseErr> {
|
||||
let cell_identifier_ca = buf.read_field(5, "cell_identifier_ca")? as u8;
|
||||
let cell_reselection_types_supported = buf.read_field(2, "cell_reselection_types_supported")? as u8;
|
||||
let neighbour_cell_synchronized = buf.read_field(1, "neighbour_cell_synchronized")? != 0;
|
||||
let cell_load_ca = buf.read_field(2, "cell_load_ca")? as u8;
|
||||
let main_carrier_number = buf.read_field(12, "main_carrier_number")? as u16;
|
||||
|
||||
// For this element, each optional Type2 field is preceded by a P-bit.
|
||||
let main_carrier_number_extension =
|
||||
typed::parse_type2_generic(true, buf, 10, "main_carrier_number_extension")?.map(|v| v as u16);
|
||||
let mcc = typed::parse_type2_generic(true, buf, 10, "mcc")?.map(|v| v as u16);
|
||||
let mnc = typed::parse_type2_generic(true, buf, 14, "mnc")?.map(|v| v as u16);
|
||||
let location_area = typed::parse_type2_generic(true, buf, 14, "location_area")?.map(|v| v as u16);
|
||||
let maximum_ms_transmit_power =
|
||||
typed::parse_type2_generic(true, buf, 3, "maximum_ms_transmit_power")?.map(|v| v as u8);
|
||||
let minimum_rx_access_level =
|
||||
typed::parse_type2_generic(true, buf, 4, "minimum_rx_access_level")?.map(|v| v as u8);
|
||||
let subscriber_class = typed::parse_type2_generic(true, buf, 16, "subscriber_class")?.map(|v| v as u16);
|
||||
let bs_service_details = typed::parse_type2_struct(true, buf, BsServiceDetails::from_bitbuf)?;
|
||||
let timeshare_cell_information_or_security_parameters =
|
||||
typed::parse_type2_generic(true, buf, 5, "timeshare_cell_information_or_security_parameters")?.map(|v| v as u8);
|
||||
let tdma_frame_offset = typed::parse_type2_generic(true, buf, 6, "tdma_frame_offset")?.map(|v| v as u8);
|
||||
|
||||
Ok(NeighbourCellInformationForCa {
|
||||
cell_identifier_ca,
|
||||
cell_reselection_types_supported,
|
||||
neighbour_cell_synchronized,
|
||||
cell_load_ca,
|
||||
main_carrier_number,
|
||||
main_carrier_number_extension,
|
||||
mcc,
|
||||
mnc,
|
||||
location_area,
|
||||
maximum_ms_transmit_power,
|
||||
minimum_rx_access_level,
|
||||
subscriber_class,
|
||||
bs_service_details,
|
||||
timeshare_cell_information_or_security_parameters,
|
||||
tdma_frame_offset,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_bitbuf(&self, buf: &mut BitBuffer) -> Result<(), PduParseErr> {
|
||||
self.ensure_bits(self.cell_identifier_ca as u64, 5, "cell_identifier_ca")?;
|
||||
self.ensure_bits(
|
||||
self.cell_reselection_types_supported as u64,
|
||||
2,
|
||||
"cell_reselection_types_supported",
|
||||
)?;
|
||||
self.ensure_bits(self.cell_load_ca as u64, 2, "cell_load_ca")?;
|
||||
self.ensure_bits(self.main_carrier_number as u64, 12, "main_carrier_number")?;
|
||||
self.ensure_opt_bits(
|
||||
self.main_carrier_number_extension.map(|v| v as u64),
|
||||
10,
|
||||
"main_carrier_number_extension",
|
||||
)?;
|
||||
self.ensure_opt_bits(self.mcc.map(|v| v as u64), 10, "mcc")?;
|
||||
self.ensure_opt_bits(self.mnc.map(|v| v as u64), 14, "mnc")?;
|
||||
self.ensure_opt_bits(self.location_area.map(|v| v as u64), 14, "location_area")?;
|
||||
self.ensure_opt_bits(
|
||||
self.maximum_ms_transmit_power.map(|v| v as u64),
|
||||
3,
|
||||
"maximum_ms_transmit_power",
|
||||
)?;
|
||||
self.ensure_opt_bits(
|
||||
self.minimum_rx_access_level.map(|v| v as u64),
|
||||
4,
|
||||
"minimum_rx_access_level",
|
||||
)?;
|
||||
self.ensure_opt_bits(self.subscriber_class.map(|v| v as u64), 16, "subscriber_class")?;
|
||||
self.ensure_opt_bits(
|
||||
self.timeshare_cell_information_or_security_parameters.map(|v| v as u64),
|
||||
5,
|
||||
"timeshare_cell_information_or_security_parameters",
|
||||
)?;
|
||||
self.ensure_opt_bits(self.tdma_frame_offset.map(|v| v as u64), 6, "tdma_frame_offset")?;
|
||||
|
||||
buf.write_bits(self.cell_identifier_ca as u64, 5);
|
||||
buf.write_bits(self.cell_reselection_types_supported as u64, 2);
|
||||
buf.write_bits(self.neighbour_cell_synchronized as u64, 1);
|
||||
buf.write_bits(self.cell_load_ca as u64, 2);
|
||||
buf.write_bits(self.main_carrier_number as u64, 12);
|
||||
|
||||
typed::write_type2_generic(
|
||||
true,
|
||||
buf,
|
||||
self.main_carrier_number_extension.map(|v| v as u64),
|
||||
10,
|
||||
);
|
||||
typed::write_type2_generic(true, buf, self.mcc.map(|v| v as u64), 10);
|
||||
typed::write_type2_generic(true, buf, self.mnc.map(|v| v as u64), 14);
|
||||
typed::write_type2_generic(true, buf, self.location_area.map(|v| v as u64), 14);
|
||||
typed::write_type2_generic(
|
||||
true,
|
||||
buf,
|
||||
self.maximum_ms_transmit_power.map(|v| v as u64),
|
||||
3,
|
||||
);
|
||||
typed::write_type2_generic(
|
||||
true,
|
||||
buf,
|
||||
self.minimum_rx_access_level.map(|v| v as u64),
|
||||
4,
|
||||
);
|
||||
typed::write_type2_generic(true, buf, self.subscriber_class.map(|v| v as u64), 16);
|
||||
typed::write_type2_struct(true, buf, &self.bs_service_details, |val, inner| {
|
||||
val.to_bitbuf(inner);
|
||||
Ok(())
|
||||
})?;
|
||||
typed::write_type2_generic(
|
||||
true,
|
||||
buf,
|
||||
self.timeshare_cell_information_or_security_parameters.map(|v| v as u64),
|
||||
5,
|
||||
);
|
||||
typed::write_type2_generic(true, buf, self.tdma_frame_offset.map(|v| v as u64), 6);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_bits(&self, value: u64, bits: u8, field: &'static str) -> Result<(), PduParseErr> {
|
||||
let max = if bits >= 64 { u64::MAX } else { (1u64 << bits) - 1 };
|
||||
if value > max {
|
||||
return Err(PduParseErr::InvalidValue { field, value });
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_opt_bits(&self, value: Option<u64>, bits: u8, field: &'static str) -> Result<(), PduParseErr> {
|
||||
if let Some(v) = value {
|
||||
self.ensure_bits(v, bits, field)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NeighbourCellInformationForCa {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"NeighbourCellInformationForCa {{ cell_identifier_ca: {:?} cell_reselection_types_supported: {:?} neighbour_cell_synchronized: {:?} cell_load_ca: {:?} main_carrier_number: {:?} main_carrier_number_extension: {:?} mcc: {:?} mnc: {:?} location_area: {:?} maximum_ms_transmit_power: {:?} minimum_rx_access_level: {:?} subscriber_class: {:?} bs_service_details: {:?} timeshare_cell_information_or_security_parameters: {:?} tdma_frame_offset: {:?} }}",
|
||||
self.cell_identifier_ca,
|
||||
self.cell_reselection_types_supported,
|
||||
self.neighbour_cell_synchronized,
|
||||
self.cell_load_ca,
|
||||
self.main_carrier_number,
|
||||
self.main_carrier_number_extension,
|
||||
self.mcc,
|
||||
self.mnc,
|
||||
self.location_area,
|
||||
self.maximum_ms_transmit_power,
|
||||
self.minimum_rx_access_level,
|
||||
self.subscriber_class,
|
||||
self.bs_service_details,
|
||||
self.timeshare_cell_information_or_security_parameters,
|
||||
self.tdma_frame_offset,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ use tetra_core::typed_pdu_fields::*;
|
||||
use tetra_core::{BitBuffer, expect_pdu_type, pdu_parse_error::PduParseErr};
|
||||
|
||||
use crate::mle::enums::mle_pdu_type_dl::MlePduTypeDl;
|
||||
use crate::mle::fields::neighbour_cell_information_for_ca::NeighbourCellInformationForCa;
|
||||
|
||||
/// Representation of the D-NWRK-BROADCAST PDU (Clause 18.4.1.4.1).
|
||||
/// Upon receipt from the SwMI, the message shall inform the MS-MLE about parameters for the CA serving cell and parameters for one or more CA neighbour cells.
|
||||
@@ -22,12 +23,11 @@ pub struct DNwrkBroadcast {
|
||||
/// Type2, 48 bits, TETRA network time
|
||||
pub tetra_network_time: Option<u64>,
|
||||
/// Type2, 3 bits, See note 2,
|
||||
pub number_of_ca_neighbour_cells: Option<u64>,
|
||||
pub number_of_ca_neighbour_cells: Option<u8>,
|
||||
/// Conditional See note 3, condition: number_of_ca_neighbour_cells > Some(0)
|
||||
pub neighbour_cell_information_for_ca: Option<u64>,
|
||||
pub neighbour_cell_information_for_ca: Vec<NeighbourCellInformationForCa>,
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)] // TODO FIXME review, finalize and remove this
|
||||
impl DNwrkBroadcast {
|
||||
/// Parse from BitBuffer
|
||||
pub fn from_bitbuf(buffer: &mut BitBuffer) -> Result<Self, PduParseErr> {
|
||||
@@ -45,15 +45,22 @@ impl DNwrkBroadcast {
|
||||
// Type2
|
||||
let tetra_network_time = typed::parse_type2_generic(obit, buffer, 48, "tetra_network_time")?;
|
||||
// Type2
|
||||
let number_of_ca_neighbour_cells = typed::parse_type2_generic(obit, buffer, 3, "number_of_ca_neighbour_cells")?;
|
||||
let number_of_ca_neighbour_cells =
|
||||
typed::parse_type2_generic(obit, buffer, 3, "number_of_ca_neighbour_cells")?.map(|v| v as u8);
|
||||
|
||||
// Conditional
|
||||
let neighbour_cell_information_for_ca = if obit && number_of_ca_neighbour_cells > Some(0) {
|
||||
unimplemented!();
|
||||
Some(buffer.read_field(999, "neighbour_cell_information_for_ca")?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut neighbour_cell_information_for_ca = Vec::new();
|
||||
if let Some(count) = number_of_ca_neighbour_cells {
|
||||
if count > 7 {
|
||||
return Err(PduParseErr::InvalidValue {
|
||||
field: "number_of_ca_neighbour_cells",
|
||||
value: count as u64,
|
||||
});
|
||||
}
|
||||
for _ in 0..count {
|
||||
neighbour_cell_information_for_ca.push(NeighbourCellInformationForCa::from_bitbuf(buffer)?);
|
||||
}
|
||||
}
|
||||
|
||||
// Read trailing obit (if not previously encountered)
|
||||
obit = if obit { buffer.read_field(1, "trailing_obit")? == 1 } else { obit };
|
||||
@@ -72,6 +79,27 @@ impl DNwrkBroadcast {
|
||||
|
||||
/// Serialize this PDU into the given BitBuffer.
|
||||
pub fn to_bitbuf(&self, buffer: &mut BitBuffer) -> Result<(), PduParseErr> {
|
||||
let neighbour_count = self.neighbour_cell_information_for_ca.len();
|
||||
if let Some(count) = self.number_of_ca_neighbour_cells {
|
||||
if count as usize != neighbour_count {
|
||||
return Err(PduParseErr::Inconsistency {
|
||||
field: "number_of_ca_neighbour_cells",
|
||||
reason: "count does not match neighbour_cell_information_for_ca length",
|
||||
});
|
||||
}
|
||||
if count > 7 {
|
||||
return Err(PduParseErr::InvalidValue {
|
||||
field: "number_of_ca_neighbour_cells",
|
||||
value: count as u64,
|
||||
});
|
||||
}
|
||||
} else if neighbour_count > 0 {
|
||||
return Err(PduParseErr::Inconsistency {
|
||||
field: "neighbour_cell_information_for_ca",
|
||||
reason: "missing number_of_ca_neighbour_cells",
|
||||
});
|
||||
}
|
||||
|
||||
// PDU Type
|
||||
buffer.write_bits(MlePduTypeDl::DNwrkBroadcast.into_raw(), 3);
|
||||
// Type1
|
||||
@@ -90,12 +118,18 @@ impl DNwrkBroadcast {
|
||||
typed::write_type2_generic(obit, buffer, self.tetra_network_time, 48);
|
||||
|
||||
// Type2
|
||||
typed::write_type2_generic(obit, buffer, self.number_of_ca_neighbour_cells, 3);
|
||||
typed::write_type2_generic(
|
||||
obit,
|
||||
buffer,
|
||||
self.number_of_ca_neighbour_cells.map(|v| v as u64),
|
||||
3,
|
||||
);
|
||||
|
||||
// Conditional
|
||||
if let Some(ref _value) = self.neighbour_cell_information_for_ca {
|
||||
unimplemented!();
|
||||
buffer.write_bits(*_value, 999);
|
||||
if self.number_of_ca_neighbour_cells.unwrap_or(0) > 0 {
|
||||
for neighbour in &self.neighbour_cell_information_for_ca {
|
||||
neighbour.to_bitbuf(buffer)?;
|
||||
}
|
||||
}
|
||||
// Write terminating m-bit
|
||||
delimiters::write_mbit(buffer, 0);
|
||||
|
||||
@@ -105,6 +105,24 @@ colour_code = 1
|
||||
# Neighbor cell broadcast settings
|
||||
neighbor_cell_broadcast = 2
|
||||
|
||||
# Neighbor cells (CA) broadcast list for D-NWRK-BROADCAST
|
||||
# [[cell_info.neighbor_cells_ca]]
|
||||
# cell_identifier_ca = 1
|
||||
# cell_reselection_types_supported = 0
|
||||
# neighbor_cell_synchronized = true
|
||||
# cell_load_ca = 0
|
||||
# main_carrier_number = 1521
|
||||
# main_carrier_number_extension = 0
|
||||
# mcc = 204
|
||||
# mnc = 1337
|
||||
# location_area = 2
|
||||
# maximum_ms_transmit_power = 5
|
||||
# minimum_rx_access_level = 3
|
||||
# subscriber_class = 0xFFFF
|
||||
# bs_service_details = { registration = true, deregistration = true, priority_cell = false, no_minimum_mode = false, migration = false, system_wide_services = true, voice_service = true, circuit_mode_data_service = false, sndcp_service = false, aie_service = false, advanced_link = false }
|
||||
# timeshare_cell_information_or_security_parameters = 0
|
||||
# tdma_frame_offset = 0
|
||||
|
||||
# Cell load (Channel Allocation) - current load
|
||||
# cell_load_ca = 0
|
||||
|
||||
|
||||
Reference in new issue
Block a user