implemented prototype neighbor cell broadcast

This commit is contained in:
misadeks committed 2026-02-24 16:25:54 +01:00
1 parent 71d299390a
commit d645f74c4f
7 files changed
+540 -18

No files matched your search

+125
View File
@@ -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(())
}
}
+104 -1
View File
@@ -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,
}
}
}
+42 -3
View File
@@ -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
View File
@@ -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);
+18
View File
@@ -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