mirror of
https://github.com/MidnightBlueLabs/tetra-bluestation.git
synced 2026-09-01 16:58:18 +00:00
Improvements/Refactoring around AACH (#82)
* Add: Refactor AACH behaviour. Main changes here are to split a lot of the integer typed fields out into enums and structs, and make invalid state of the AccessAssign PDU unrepresentable through typing. Studied the BaseFrameLength behaviour and standardised on a sensible set of defaults throughout for that. Other changes include advertising CLCH opportunities via BaseFrameLength::CLCHSubslot where expected and a relaxing of how often SYNC bursts are sent. Using Air Interface monitoring on a few radios indicated that SYNC bursts appearing in non-mandated slots caused decode errors and radio downlink counter decrements. * Fix: Indicate reserved uplink subslots in ACCESS-ASSIGN. This caused issues with multi-fragment messages. Seems like some radios do indeed check the AACH during a multi-slot grant and if they don't see Reserved Subslots (00) abort and re-attempt random-access again. * Refactor: One place to set the default access frame marker in bs_sched * Add: Tests for reserved & CLCH sched subslots
This commit is contained in:
@@ -14,7 +14,7 @@ use tetra_pdus::{
|
||||
},
|
||||
fields::basic_slotgrant::BasicSlotgrant,
|
||||
pdus::{
|
||||
access_assign::{AccessAssign, AccessField},
|
||||
access_assign::{AccessAssign},
|
||||
access_assign_fr18::AccessAssignFr18,
|
||||
mac_resource::MacResource,
|
||||
mac_sync::MacSync,
|
||||
@@ -22,7 +22,9 @@ use tetra_pdus::{
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use tetra_pdus::umac::enums::access_code::AccessCode;
|
||||
use tetra_pdus::umac::structs::access_field::AccessField;
|
||||
use tetra_pdus::umac::structs::base_frame_length::BaseFrameLength;
|
||||
use crate::{
|
||||
lmac::components::scrambler,
|
||||
umac::subcomp::{bs_frag::BsFragger, circuit_mgr::CircuitMgr},
|
||||
@@ -40,6 +42,9 @@ pub const SCH_HD_CAP: usize = 124;
|
||||
pub const SCH_F_CAP: usize = 268;
|
||||
pub const TCH_S_CAP: usize = 274;
|
||||
|
||||
// The default access frame marker used in access fields
|
||||
const DEFAULT_ACCESS_FRAME_MARKER: BaseFrameLength = BaseFrameLength::Subslots2;
|
||||
|
||||
/// Number of timeslots the scheduler operates on. May become larger when secondary carriers are supported.
|
||||
pub const NUM_TIMESLOTS: usize = 4;
|
||||
|
||||
@@ -1077,6 +1082,7 @@ impl BsChannelScheduler {
|
||||
}
|
||||
|
||||
fn generate_bbk_block(&self, ts: TdmaTime) -> TmvUnitdataReq {
|
||||
|
||||
let (ul_traffic_usage, dl_traffic_usage) = if ts.f == 18 {
|
||||
(None, None)
|
||||
} else {
|
||||
@@ -1088,80 +1094,121 @@ impl BsChannelScheduler {
|
||||
|
||||
// Generate BBK block
|
||||
let mut aach_bb = BitBuffer::new(14);
|
||||
|
||||
if ts.f != 18 {
|
||||
let mut aach = AccessAssign::default();
|
||||
|
||||
match ts.t {
|
||||
let aach = match ts.t {
|
||||
|
||||
// MCCH (TS1)
|
||||
1 => {
|
||||
|
||||
// 23.3.1.1.2
|
||||
// "During normal mode operation, it shall always be assumed that slot 1 on the
|
||||
// downlink is for common control as part of the MCCH."
|
||||
assert!(dl_traffic_usage.is_none(), "DL ts 1 can't be traffic");
|
||||
assert!(ul_traffic_usage.is_none(), "UL ts 1 can't be traffic (is this allowed?"); // TODO FIXME check spec
|
||||
|
||||
// Always CommonOnly on TS1 (MCCH). Per ETSI 23.5.2.2.2, the MS
|
||||
// with a grant transmits in granted slots without checking the AACH.
|
||||
aach.dl_usage = AccessAssignDlUsage::CommonControl;
|
||||
aach.ul_usage = AccessAssignUlUsage::CommonOnly;
|
||||
aach.f1_af1 = Some(AccessField {
|
||||
access_code: 0,
|
||||
base_frame_len: 4,
|
||||
});
|
||||
aach.f2_af2 = Some(AccessField {
|
||||
access_code: 0,
|
||||
base_frame_len: 4,
|
||||
});
|
||||
}
|
||||
2..=4 => {
|
||||
// Additional channels (TS2..TS4).
|
||||
// Normal operation: Traffic(usage) when a circuit is active, else Unallocated.
|
||||
// Hangtime: immediately switch AACH to AssignedControl so radios
|
||||
// detect the end of traffic in the same frame as D-TX CEASED.
|
||||
// The timeslot may still be in traffic mode (for STCH delivery) but
|
||||
// the AACH reflects the new channel state.
|
||||
let in_hangtime = (2..=4).contains(&ts.t) && self.hangtime[ts.t as usize - 1];
|
||||
// TODO FIXME: It *is* possible for UL TS1 to carry traffic.
|
||||
//
|
||||
// 23.3.4 Independent allocation of uplink and downlink
|
||||
// "A BS may allocate uplink and downlink channels for different purposes. Some examples are listed below:"
|
||||
// "[...] common control on downlink MCCH (slot 1); uplink slot 1 of main carrier allocated for a circuit mode call;"
|
||||
//
|
||||
// That said, it's not something that tetra-bluestation does right now, so this assert is still sensible.
|
||||
assert!(ul_traffic_usage.is_none(), "UL TS 1 can't currently be traffic");
|
||||
|
||||
if in_hangtime && (dl_traffic_usage.is_some() || ul_traffic_usage.is_some()) {
|
||||
aach.dl_usage = AccessAssignDlUsage::AssignedControl;
|
||||
// AssignedOnly (Header 2) allows random access for MSs on
|
||||
// the assigned channel while blocking common control MSs.
|
||||
aach.ul_usage = AccessAssignUlUsage::AssignedOnly;
|
||||
aach.f2_af = Some(AccessField {
|
||||
access_code: 0,
|
||||
base_frame_len: 4,
|
||||
});
|
||||
} else {
|
||||
aach.dl_usage = if let Some(usage) = dl_traffic_usage {
|
||||
AccessAssignDlUsage::Traffic(usage)
|
||||
} else {
|
||||
AccessAssignDlUsage::Unallocated
|
||||
};
|
||||
aach.ul_usage = if let Some(usage) = ul_traffic_usage {
|
||||
AccessAssignUlUsage::Traffic(usage)
|
||||
} else {
|
||||
AccessAssignUlUsage::Unallocated
|
||||
};
|
||||
// Indicate any reserved slots in the uplink with base_frame_len=ReservedSubslot
|
||||
AccessAssign::DownlinkCommonControlUplinkCommonOnly {
|
||||
access_field_1: AccessField {
|
||||
access_code: AccessCode::AccessCodeA,
|
||||
base_frame_len: if self.ul_get_slot_owner(ts, PhyBlockNum::Block1).is_some() {
|
||||
BaseFrameLength::ReservedSubslot
|
||||
} else {
|
||||
DEFAULT_ACCESS_FRAME_MARKER
|
||||
}
|
||||
},
|
||||
access_field_2: AccessField {
|
||||
access_code: AccessCode::AccessCodeA,
|
||||
base_frame_len: if self.ul_get_slot_owner(ts, PhyBlockNum::Block2).is_some() {
|
||||
BaseFrameLength::ReservedSubslot
|
||||
} else {
|
||||
DEFAULT_ACCESS_FRAME_MARKER
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => panic!("finalize_ts_for_tick: invalid timeslot {}", ts.t),
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// Additional channels (TS2..TS4)
|
||||
2..=4 => {
|
||||
|
||||
if self.is_hangtime(ts.t) && (dl_traffic_usage.is_some() || ul_traffic_usage.is_some()) {
|
||||
|
||||
// Hangtime: immediately switch AACH to AssignedControl so radios
|
||||
// detect the end of traffic in the same frame as D-TX CEASED.
|
||||
// The timeslot may still be in traffic mode (for STCH delivery) but
|
||||
// the AACH reflects the new channel state.
|
||||
AccessAssign::DownlinkDefinedUplinkAssignedOnly {
|
||||
downlink_usage_marker: AccessAssignDlUsage::AssignedControl,
|
||||
access_field: AccessField {
|
||||
access_code: AccessCode::AccessCodeA,
|
||||
base_frame_len: DEFAULT_ACCESS_FRAME_MARKER,
|
||||
},
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Normal operation: Traffic(usage) when a circuit is active, else Unallocated
|
||||
AccessAssign::DownlinkDefinedUplinkDefined {
|
||||
downlink_usage_marker: if let Some(usage) = dl_traffic_usage {
|
||||
AccessAssignDlUsage::Traffic(usage)
|
||||
} else {
|
||||
AccessAssignDlUsage::Unallocated
|
||||
},
|
||||
uplink_usage_marker: if let Some(usage) = ul_traffic_usage {
|
||||
AccessAssignUlUsage::Traffic(usage)
|
||||
} else {
|
||||
AccessAssignUlUsage::Unallocated
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
_ => panic!("finalize_ts_for_tick: invalid timeslot {}", ts.t)
|
||||
};
|
||||
|
||||
aach.to_bitbuf(&mut aach_bb);
|
||||
|
||||
} else {
|
||||
// Fr18
|
||||
|
||||
// Frame 18 is the Control Frame, which cannot contain traffic
|
||||
assert!(ul_traffic_usage.is_none() && dl_traffic_usage.is_none());
|
||||
let aach = AccessAssignFr18 {
|
||||
ul_usage: AccessAssignUlUsage::CommonOnly,
|
||||
f1_af1: Some(AccessField {
|
||||
access_code: 0,
|
||||
base_frame_len: 1,
|
||||
}),
|
||||
f2_af2: Some(AccessField {
|
||||
access_code: 0,
|
||||
base_frame_len: 0,
|
||||
}),
|
||||
..Default::default()
|
||||
|
||||
let aach = AccessAssignFr18::UplinkCommonOnly {
|
||||
access_field_1: AccessField {
|
||||
access_code: AccessCode::AccessCodeA,
|
||||
base_frame_len: if self.ul_get_slot_owner(ts, PhyBlockNum::Block1).is_some() {
|
||||
// Subslot is reserved in the uplink
|
||||
BaseFrameLength::ReservedSubslot
|
||||
} else if ts.is_mandatory_clch() {
|
||||
// CLCH opportunity (which is always in SSN1, see EN 300 392 §9.5.1 Table 9.27)
|
||||
BaseFrameLength::CLCHSubslot
|
||||
} else {
|
||||
DEFAULT_ACCESS_FRAME_MARKER
|
||||
}
|
||||
},
|
||||
access_field_2: AccessField {
|
||||
access_code: AccessCode::AccessCodeA,
|
||||
base_frame_len: if self.ul_get_slot_owner(ts, PhyBlockNum::Block2).is_some() {
|
||||
BaseFrameLength::ReservedSubslot
|
||||
} else {
|
||||
DEFAULT_ACCESS_FRAME_MARKER
|
||||
}
|
||||
},
|
||||
};
|
||||
// TODO FIXME: Access field defaults are possibly not great
|
||||
|
||||
aach.to_bitbuf(&mut aach_bb);
|
||||
}
|
||||
};
|
||||
|
||||
TmvUnitdataReq {
|
||||
logical_channel: LogicalChannel::Aach,
|
||||
@@ -1530,4 +1577,101 @@ mod tests {
|
||||
|
||||
assert!(sched.dltx_queues[ts.t as usize - 1].len() == 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dl_indicates_reserved_subslots() {
|
||||
|
||||
let mut sched = get_testing_slotter();
|
||||
let mut ts = TdmaTime::default();
|
||||
|
||||
// Add a reservation for TS1, both subslots in the third occurrence of TS1
|
||||
sched.ulsched[0][2] = TimeslotSchedule {
|
||||
ul1: Some(1),
|
||||
ul2: Some(1),
|
||||
};
|
||||
|
||||
// Generate the next 4 frames to see how the reserved subslots are indicated in the BBK block
|
||||
for i in 0..4 {
|
||||
let bbk = sched.generate_bbk_block(ts);
|
||||
let mut aach_buf = bbk.mac_block.clone();
|
||||
aach_buf.seek(0);
|
||||
|
||||
// Decode the AACH (which in this test will always be the F1-17 format)
|
||||
let access_assign = AccessAssign::from_bitbuf(&mut aach_buf)
|
||||
.expect("Failed to decode AACH block");
|
||||
tracing::debug!("Decoded AACH: {:?}", access_assign);
|
||||
|
||||
// Third occurrence should have both slots reserved
|
||||
let expected_subslot_bfl = if i == 2 {
|
||||
BaseFrameLength::ReservedSubslot
|
||||
} else {
|
||||
DEFAULT_ACCESS_FRAME_MARKER
|
||||
};
|
||||
|
||||
match access_assign {
|
||||
AccessAssign::DownlinkCommonControlUplinkCommonOnly { access_field_1, access_field_2 } => {
|
||||
assert_eq!(
|
||||
access_field_1.base_frame_len, expected_subslot_bfl,
|
||||
"Unexpected base frame length for access field 1 on TS1 occurrence {}",
|
||||
i + 1
|
||||
);
|
||||
assert_eq!(
|
||||
access_field_2.base_frame_len, expected_subslot_bfl,
|
||||
"Unexpected base frame length for access field 2 on TS1 occurrence {}",
|
||||
i + 1
|
||||
);
|
||||
},
|
||||
_ => panic!("Expected DownlinkCommonControlUplinkCommonOnly format for TS1"),
|
||||
}
|
||||
|
||||
// Move on to the next occurrence of TS1
|
||||
ts = ts.add_timeslots(4);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dl_indicates_clch_opportunities() {
|
||||
let sched = get_testing_slotter();
|
||||
|
||||
// Frame 18
|
||||
let mut ts = TdmaTime {
|
||||
t: 1,
|
||||
f: 18,
|
||||
m: 1,
|
||||
h: 0,
|
||||
};
|
||||
|
||||
// Generate the next 4 frames to make sure CLCH is correctly indicated
|
||||
for _ in 0..4 {
|
||||
|
||||
let bbk = sched.generate_bbk_block(ts);
|
||||
let mut aach_buf = bbk.mac_block.clone();
|
||||
aach_buf.seek(0);
|
||||
|
||||
// Decode the AACH (which in this test will always be the frame 18 format)
|
||||
let access_assign = AccessAssignFr18::from_bitbuf(&mut aach_buf)
|
||||
.expect("Failed to decode AACH block");
|
||||
tracing::debug!("Decoded AACH: {:?}", access_assign);
|
||||
|
||||
// For MN=1, F=18, T=2, SSN1 should be CLCH (when F == 18 and T == 4 - ((M + 1) % 4), otherwise default
|
||||
let expected_subslot_bfl = if ts.t == 2 {
|
||||
BaseFrameLength::CLCHSubslot
|
||||
} else {
|
||||
DEFAULT_ACCESS_FRAME_MARKER
|
||||
};
|
||||
|
||||
match access_assign {
|
||||
AccessAssignFr18::UplinkCommonOnly { access_field_1, .. } => {
|
||||
assert_eq!(
|
||||
access_field_1.base_frame_len, expected_subslot_bfl,
|
||||
"Unexpected base frame length for access field 1 on frame 18"
|
||||
);
|
||||
},
|
||||
_ => panic!("Expected AccessAssignFr18::UplinkCommonOnly format for frame 18"),
|
||||
}
|
||||
|
||||
ts = ts.add_timeslots(1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -568,7 +568,8 @@ impl UmacMs {
|
||||
}
|
||||
};
|
||||
|
||||
pdu.dl_usage.is_traffic()
|
||||
pdu.dl_is_traffic()
|
||||
|
||||
} else {
|
||||
let _pdu = match AccessAssignFr18::from_bitbuf(&mut prim.pdu) {
|
||||
Ok(pdu) => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub mod cmce;
|
||||
pub mod llc;
|
||||
pub mod mle;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
use core::fmt;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum AccessCode {
|
||||
AccessCodeA,
|
||||
AccessCodeB,
|
||||
AccessCodeC,
|
||||
AccessCodeD
|
||||
}
|
||||
|
||||
impl TryFrom<u64> for AccessCode {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u64) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0b00 => Ok(AccessCode::AccessCodeA),
|
||||
0b01 => Ok(AccessCode::AccessCodeB),
|
||||
0b10 => Ok(AccessCode::AccessCodeC),
|
||||
0b11 => Ok(AccessCode::AccessCodeD),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AccessCode {
|
||||
pub fn into_raw(self) -> u64 {
|
||||
match self {
|
||||
AccessCode::AccessCodeA => 0b00,
|
||||
AccessCode::AccessCodeB => 0b01,
|
||||
AccessCode::AccessCodeC => 0b10,
|
||||
AccessCode::AccessCodeD => 0b11,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AccessCode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
AccessCode::AccessCodeA => write!(f, "Access Code A"),
|
||||
AccessCode::AccessCodeB => write!(f, "Access Code B"),
|
||||
AccessCode::AccessCodeC => write!(f, "Access Code C"),
|
||||
AccessCode::AccessCodeD => write!(f, "Access Code D"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,6 @@ pub mod mac_pdu_type;
|
||||
pub mod mac_resource_addr_type;
|
||||
pub mod reservation_requirement;
|
||||
pub mod sysinfo_opt_field_flag;
|
||||
|
||||
pub mod access_assign_dl_usage;
|
||||
pub mod access_assign_ul_usage;
|
||||
pub mod access_code;
|
||||
|
||||
@@ -1,197 +1,218 @@
|
||||
use core::fmt;
|
||||
use std::panic;
|
||||
|
||||
use tetra_core::{BitBuffer, pdu_parse_error::PduParseErr};
|
||||
|
||||
use crate::umac::enums::{access_assign_dl_usage::AccessAssignDlUsage, access_assign_ul_usage::AccessAssignUlUsage};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AccessField {
|
||||
// 2
|
||||
pub access_code: u8,
|
||||
// 4
|
||||
pub base_frame_len: u8,
|
||||
}
|
||||
pub(crate) use crate::umac::enums::access_code::AccessCode;
|
||||
pub(crate) use crate::umac::structs::access_field::AccessField;
|
||||
pub(crate) use crate::umac::structs::base_frame_length::BaseFrameLength;
|
||||
|
||||
/// Clause 21.4.7.2 ACCESS-ASSIGN
|
||||
/// TODO FIXME technically not part of this SAP, but part of the MAC
|
||||
#[derive(Debug)]
|
||||
pub struct AccessAssign {
|
||||
// 2, kept for debugging purposes
|
||||
pub _header: u8,
|
||||
// 6
|
||||
pub dl_usage: AccessAssignDlUsage,
|
||||
pub ul_usage: AccessAssignUlUsage,
|
||||
pub enum AccessAssign {
|
||||
|
||||
// Three valid combinations:
|
||||
// - Only access_field (applies for both subslots)
|
||||
// - Both access_field1 and access_field2
|
||||
// - None (when dl and ul usage need to be sent)
|
||||
// pub access_field: Option<AccessField>,
|
||||
// pub access_field1: Option<AccessField>,
|
||||
// pub access_field2: Option<AccessField>,
|
||||
/// Populated when header == 0
|
||||
/// Provides access rights on UL subslot 1
|
||||
pub f1_af1: Option<AccessField>,
|
||||
// pub f1_dl_um: Option<AccessAssignDlUsage>,
|
||||
/// Populated when header == 0
|
||||
/// Provides access rights on UL subslot 2
|
||||
pub f2_af2: Option<AccessField>,
|
||||
// Header = 00
|
||||
// Downlink usage - common control
|
||||
// Uplink access rights - common only
|
||||
DownlinkCommonControlUplinkCommonOnly {
|
||||
access_field_1: AccessField,
|
||||
access_field_2: AccessField
|
||||
},
|
||||
|
||||
// Header = 01
|
||||
// Downlink usage - defined by field 1
|
||||
// Uplink access rights - common and assigned
|
||||
DownlinkDefinedUplinkCommonAndAssigned {
|
||||
downlink_usage_marker: AccessAssignDlUsage,
|
||||
access_field: AccessField
|
||||
},
|
||||
|
||||
// Header = 10
|
||||
// Downlink usage - defined by field 1
|
||||
// Uplink access rights - assigned only
|
||||
DownlinkDefinedUplinkAssignedOnly {
|
||||
downlink_usage_marker: AccessAssignDlUsage,
|
||||
access_field: AccessField
|
||||
},
|
||||
|
||||
// Header = 11
|
||||
// Downlink usage - defined by field 1
|
||||
// Uplink access rights - defined by field 2
|
||||
DownlinkDefinedUplinkDefined {
|
||||
downlink_usage_marker: AccessAssignDlUsage,
|
||||
uplink_usage_marker: AccessAssignUlUsage
|
||||
}
|
||||
|
||||
/// Populated when header == 1 or 2
|
||||
/// Provides access rights on both UL subslots
|
||||
pub f2_af: Option<AccessField>,
|
||||
// pub f2_ul_um: Option<AccessAssignUlUsage>,
|
||||
}
|
||||
|
||||
impl Default for AccessAssign {
|
||||
fn default() -> Self {
|
||||
AccessAssign {
|
||||
_header: 0,
|
||||
dl_usage: AccessAssignDlUsage::CommonControl,
|
||||
ul_usage: AccessAssignUlUsage::CommonOnly,
|
||||
f1_af1: None,
|
||||
// f1_dl_um: None,
|
||||
f2_af2: None,
|
||||
f2_af: None,
|
||||
// f2_ul_um: None
|
||||
AccessAssign::DownlinkCommonControlUplinkCommonOnly {
|
||||
access_field_1: AccessField {
|
||||
access_code: AccessCode::AccessCodeA,
|
||||
base_frame_len: BaseFrameLength::Subslots4
|
||||
},
|
||||
access_field_2: AccessField {
|
||||
access_code: AccessCode::AccessCodeA,
|
||||
base_frame_len: BaseFrameLength::Subslots4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AccessAssign {
|
||||
|
||||
pub fn dl_is_traffic(&self) -> bool {
|
||||
match self {
|
||||
AccessAssign::DownlinkDefinedUplinkCommonAndAssigned { downlink_usage_marker, .. } |
|
||||
AccessAssign::DownlinkDefinedUplinkAssignedOnly { downlink_usage_marker, .. } |
|
||||
AccessAssign::DownlinkDefinedUplinkDefined { downlink_usage_marker, .. } => {
|
||||
downlink_usage_marker.is_traffic()
|
||||
},
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_bitbuf(buf: &mut BitBuffer) -> Result<Self, PduParseErr> {
|
||||
let mut s = AccessAssign {
|
||||
_header: buf.read_field(2, "_header")? as u8,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let field1 = buf.read_field(6, "field1")? as u8;
|
||||
let field2 = buf.read_field(6, "field2")? as u8;
|
||||
let header = buf.read_field(2, "_header")?;
|
||||
let field1 = buf.read_field(6, "field1")?;
|
||||
let field2 = buf.read_field(6, "field2")?;
|
||||
|
||||
match s._header {
|
||||
0 => {
|
||||
// DL common control
|
||||
// UL access rights - common only
|
||||
s.dl_usage = AccessAssignDlUsage::CommonControl;
|
||||
s.ul_usage = AccessAssignUlUsage::CommonOnly;
|
||||
match header {
|
||||
|
||||
s.f1_af1 = Some(AccessField {
|
||||
access_code: (field1 >> 4) & 0x3,
|
||||
base_frame_len: field1 & 0xF,
|
||||
});
|
||||
s.f2_af2 = Some(AccessField {
|
||||
access_code: (field2 >> 4) & 0x3,
|
||||
base_frame_len: field2 & 0xF,
|
||||
});
|
||||
0b00 => {
|
||||
// Downlink usage - common control
|
||||
// Uplink access rights - common only
|
||||
Ok(AccessAssign::DownlinkCommonControlUplinkCommonOnly {
|
||||
access_field_1: field1.try_into()
|
||||
.map_err(|_| PduParseErr::InvalidValue { field: "access_field_1", value: field1 })?,
|
||||
access_field_2: field2.try_into()
|
||||
.map_err(|_| PduParseErr::InvalidValue { field: "access_field_2", value: field2 })?,
|
||||
})
|
||||
}
|
||||
1 => {
|
||||
// DL defined by field1 usage marker
|
||||
// UL access rights - common and assigned
|
||||
s.dl_usage = AccessAssignDlUsage::from_usage_marker(field1);
|
||||
s.ul_usage = AccessAssignUlUsage::CommonAndAssigned;
|
||||
s.f2_af = Some(AccessField {
|
||||
access_code: (field2 >> 4) & 0x3,
|
||||
base_frame_len: field2 & 0xF,
|
||||
});
|
||||
|
||||
0b01 => {
|
||||
// Downlink usage - defined by field 1
|
||||
// Uplink access rights - common and assigned
|
||||
Ok(AccessAssign::DownlinkDefinedUplinkCommonAndAssigned {
|
||||
downlink_usage_marker: AccessAssignDlUsage::from_usage_marker(field1 as u8),
|
||||
access_field: field2.try_into()
|
||||
.map_err(|_| PduParseErr::InvalidValue { field: "access_field", value: field2 })?,
|
||||
})
|
||||
}
|
||||
2 => {
|
||||
// DL defined by field1 usage marker
|
||||
// UL access rights - assigned only
|
||||
s.dl_usage = AccessAssignDlUsage::from_usage_marker(field1);
|
||||
s.ul_usage = AccessAssignUlUsage::AssignedOnly;
|
||||
s.f2_af = Some(AccessField {
|
||||
access_code: (field2 >> 4) & 0x3,
|
||||
base_frame_len: field2 & 0xF,
|
||||
});
|
||||
|
||||
0b10 => {
|
||||
// Downlink usage - defined by field 1
|
||||
// Uplink access rights - assigned only
|
||||
Ok(AccessAssign::DownlinkDefinedUplinkAssignedOnly {
|
||||
downlink_usage_marker: AccessAssignDlUsage::from_usage_marker(field1 as u8),
|
||||
access_field: field2.try_into()
|
||||
.map_err(|_| PduParseErr::InvalidValue { field: "access_field", value: field2 })?,
|
||||
})
|
||||
}
|
||||
3 => {
|
||||
// DL defined by field1 usage marker
|
||||
// UL defined by field2 usage marker
|
||||
s.dl_usage = AccessAssignDlUsage::from_usage_marker(field1);
|
||||
let ul_usage = AccessAssignUlUsage::from_usage_marker(field2);
|
||||
s.ul_usage = ul_usage.ok_or(PduParseErr::InvalidValue {
|
||||
field: "ul_usage",
|
||||
value: field2 as u64,
|
||||
})?;
|
||||
|
||||
0b11 => {
|
||||
// Downlink usage - defined by field 1
|
||||
// Uplink access rights - defined by field 2
|
||||
Ok(AccessAssign::DownlinkDefinedUplinkDefined {
|
||||
downlink_usage_marker: AccessAssignDlUsage::from_usage_marker(field1 as u8),
|
||||
uplink_usage_marker: AccessAssignUlUsage::from_usage_marker(field2 as u8).unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
_ => {
|
||||
panic!()
|
||||
panic!("Invalid header value for ACCESS-ASSIGN: {}", header);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub fn to_bitbuf(&self, buf: &mut BitBuffer) {
|
||||
// Safe fallback when a caller sets UL usage to CommonAndAssigned / AssignedOnly
|
||||
// but forgets to provide field2 (access field). Scheduler should still
|
||||
// set f2_af explicitly; this just prevents runtime panics.
|
||||
const DEFAULT_AF: AccessField = AccessField {
|
||||
access_code: 0,
|
||||
base_frame_len: 4,
|
||||
};
|
||||
|
||||
if self.dl_usage == AccessAssignDlUsage::CommonControl && self.ul_usage == AccessAssignUlUsage::CommonOnly {
|
||||
assert!(
|
||||
self.f1_af1.is_some() && self.f2_af2.is_some(),
|
||||
"AccessAssign with CommonControl and CommonOnly must have both access fields defined"
|
||||
);
|
||||
assert!(
|
||||
self.f2_af.is_none(),
|
||||
"AccessAssign with CommonControl and CommonOnly must not have f2_af defined"
|
||||
);
|
||||
match self {
|
||||
|
||||
let header = 0;
|
||||
buf.write_bits(header as u64, 2);
|
||||
buf.write_bits(self.f1_af1.as_ref().unwrap().access_code as u64, 2);
|
||||
buf.write_bits(self.f1_af1.as_ref().unwrap().base_frame_len as u64, 4);
|
||||
buf.write_bits(self.f2_af2.as_ref().unwrap().access_code as u64, 2);
|
||||
buf.write_bits(self.f2_af2.as_ref().unwrap().base_frame_len as u64, 4);
|
||||
} else if self.ul_usage == AccessAssignUlUsage::CommonAndAssigned {
|
||||
let header = 1;
|
||||
buf.write_bits(header as u64, 2);
|
||||
AccessAssign::DownlinkCommonControlUplinkCommonOnly {
|
||||
access_field_1,
|
||||
access_field_2
|
||||
} => {
|
||||
|
||||
let dl_usage = self.dl_usage.to_usage_marker();
|
||||
buf.write_bits(dl_usage as u64, 6);
|
||||
let af = self.f2_af.unwrap_or(DEFAULT_AF);
|
||||
buf.write_bits(af.access_code as u64, 2);
|
||||
buf.write_bits(af.base_frame_len as u64, 4);
|
||||
} else if self.ul_usage == AccessAssignUlUsage::AssignedOnly {
|
||||
let header = 2;
|
||||
buf.write_bits(header as u64, 2);
|
||||
let dl_usage = self.dl_usage.to_usage_marker();
|
||||
buf.write_bits(dl_usage as u64, 6);
|
||||
let af = self.f2_af.unwrap_or(DEFAULT_AF);
|
||||
buf.write_bits(af.access_code as u64, 2);
|
||||
buf.write_bits(af.base_frame_len as u64, 4);
|
||||
} else {
|
||||
// Both DL and UL usage given by usage markers
|
||||
let header = 3;
|
||||
buf.write_bits(header as u64, 2);
|
||||
// Header = 00
|
||||
buf.write_bits(0b00, 2);
|
||||
|
||||
let dl_usage = self.dl_usage.to_usage_marker();
|
||||
let ul_usage = self.ul_usage.to_usage_marker().unwrap();
|
||||
// Access field 1
|
||||
buf.write_bits(access_field_1.into_raw(), 6);
|
||||
|
||||
buf.write_bits(dl_usage as u64, 6);
|
||||
buf.write_bits(ul_usage as u64, 6);
|
||||
// Access field 2
|
||||
buf.write_bits(access_field_2.into_raw(), 6);
|
||||
|
||||
},
|
||||
|
||||
AccessAssign::DownlinkDefinedUplinkCommonAndAssigned {
|
||||
downlink_usage_marker,
|
||||
access_field
|
||||
} => {
|
||||
|
||||
// Header = 01
|
||||
buf.write_bits(0b01, 2);
|
||||
|
||||
// Downlink usage marker
|
||||
buf.write_bits(downlink_usage_marker.to_usage_marker() as u64, 6);
|
||||
|
||||
// Access field (both subslots)
|
||||
buf.write_bits(access_field.into_raw(), 6);
|
||||
|
||||
},
|
||||
|
||||
AccessAssign::DownlinkDefinedUplinkAssignedOnly {
|
||||
downlink_usage_marker,
|
||||
access_field
|
||||
} => {
|
||||
|
||||
// Header = 10
|
||||
buf.write_bits(0b10, 2);
|
||||
|
||||
// Downlink usage marker
|
||||
buf.write_bits(downlink_usage_marker.to_usage_marker() as u64, 6);
|
||||
|
||||
// Access field (both subslots)
|
||||
buf.write_bits(access_field.into_raw(), 6);
|
||||
|
||||
},
|
||||
|
||||
AccessAssign::DownlinkDefinedUplinkDefined {
|
||||
downlink_usage_marker,
|
||||
uplink_usage_marker
|
||||
} => {
|
||||
|
||||
// Header = 11
|
||||
buf.write_bits(0b11, 2);
|
||||
|
||||
// Downlink usage marker
|
||||
buf.write_bits(downlink_usage_marker.to_usage_marker() as u64, 6);
|
||||
|
||||
// Uplink usage marker
|
||||
buf.write_bits(uplink_usage_marker.to_usage_marker().unwrap() as u64, 6);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AccessAssign {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "access_assign {{ dl_usage: {}, ul_usage: {}", self.dl_usage, self.ul_usage)?;
|
||||
if let Some(af) = &self.f2_af {
|
||||
write!(f, " access_field code: {} base_frame_len {}", af.access_code, af.base_frame_len)?;
|
||||
};
|
||||
if let Some(af1) = &self.f1_af1 {
|
||||
write!(f, " access_field1 code: {} base_frame_len {}", af1.access_code, af1.base_frame_len)?;
|
||||
};
|
||||
if let Some(af2) = &self.f2_af2 {
|
||||
write!(f, " access_field2 code: {} base_frame_len {}", af2.access_code, af2.base_frame_len)?;
|
||||
};
|
||||
write!(f, " }}")
|
||||
match self {
|
||||
AccessAssign::DownlinkCommonControlUplinkCommonOnly { access_field_1, access_field_2 } => {
|
||||
write!(f, "AccessAssign {{ DL: Common Ctrl, UL: Common Only, af1: {}, af2: {} }}", access_field_1, access_field_2)
|
||||
},
|
||||
AccessAssign::DownlinkDefinedUplinkCommonAndAssigned { downlink_usage_marker, access_field } => {
|
||||
write!(f, "AccessAssign {{ DL: {}, UL: Common & Assigned, af: {} }}", downlink_usage_marker, access_field)
|
||||
},
|
||||
AccessAssign::DownlinkDefinedUplinkAssignedOnly { downlink_usage_marker, access_field } => {
|
||||
write!(f, "AccessAssign {{ DL: {}, UL: Assigned Only, af: {} }}", downlink_usage_marker, access_field)
|
||||
},
|
||||
AccessAssign::DownlinkDefinedUplinkDefined { downlink_usage_marker, uplink_usage_marker } => {
|
||||
write!(f, "AccessAssign {{ DL: {}, UL: {} }}", downlink_usage_marker, uplink_usage_marker)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,171 +1,199 @@
|
||||
use core::fmt;
|
||||
use std::panic;
|
||||
|
||||
use tetra_core::{BitBuffer, pdu_parse_error::PduParseErr};
|
||||
|
||||
use crate::umac::{enums::access_assign_ul_usage::AccessAssignUlUsage, pdus::access_assign::AccessField};
|
||||
use crate::umac::pdus::access_assign::{AccessCode, BaseFrameLength};
|
||||
|
||||
/// Clause 21.4.7.2 ACCESS-ASSIGN
|
||||
/// TODO FIXME technically not part of this SAP, but part of the MAC
|
||||
#[derive(Debug)]
|
||||
pub struct AccessAssignFr18 {
|
||||
// 2, kept for debugging purposes
|
||||
pub _header: u8,
|
||||
// 6
|
||||
// pub dl_usage: AccessAssignDlUsage,
|
||||
pub ul_usage: AccessAssignUlUsage,
|
||||
pub enum AccessAssignFr18 {
|
||||
|
||||
/// Populated when header == 0, 1 or 2
|
||||
/// Provides access rights on UL subslot 1
|
||||
pub f1_af1: Option<AccessField>,
|
||||
// Header = 00
|
||||
// Uplink access rights - common only
|
||||
UplinkCommonOnly {
|
||||
access_field_1: AccessField,
|
||||
access_field_2: AccessField
|
||||
},
|
||||
|
||||
/// Populated when header == 3
|
||||
pub f1_traf_um: Option<AccessAssignUlUsage>,
|
||||
// Header = 01
|
||||
// Uplink access rights - common and assigned
|
||||
UplinkCommonAndAssigned {
|
||||
access_field_1: AccessField,
|
||||
access_field_2: AccessField
|
||||
},
|
||||
|
||||
/// Populated when header == 0, 1 or 2
|
||||
/// Provides access rights on UL subslot 2
|
||||
pub f2_af2: Option<AccessField>,
|
||||
// Header = 10
|
||||
// Uplink access rights - assigned only
|
||||
UplinkAssignedOnly {
|
||||
access_field_1: AccessField,
|
||||
access_field_2: AccessField
|
||||
},
|
||||
|
||||
// Header = 11
|
||||
// Uplink access rights - common and assigned, but with traffic usage marker (UMt) instead of AF1
|
||||
UplinkCommonAndAssignedTraffic {
|
||||
uplink_usage_marker: AccessAssignUlUsage,
|
||||
access_field: AccessField
|
||||
}
|
||||
|
||||
/// Populated when header == 3
|
||||
/// Provides access rights on both UL subslots
|
||||
pub f2_af: Option<AccessField>,
|
||||
// pub f2_ul_um: Option<AccessAssignUlUsage>,
|
||||
}
|
||||
|
||||
impl Default for AccessAssignFr18 {
|
||||
fn default() -> Self {
|
||||
AccessAssignFr18 {
|
||||
_header: 0,
|
||||
|
||||
ul_usage: AccessAssignUlUsage::CommonOnly,
|
||||
|
||||
f1_af1: None,
|
||||
f1_traf_um: None,
|
||||
f2_af2: None,
|
||||
f2_af: None,
|
||||
AccessAssignFr18::UplinkCommonOnly {
|
||||
access_field_1: AccessField {
|
||||
access_code: AccessCode::AccessCodeA,
|
||||
base_frame_len: BaseFrameLength::ReservedSubslot,
|
||||
},
|
||||
access_field_2: AccessField {
|
||||
access_code: AccessCode::AccessCodeA,
|
||||
base_frame_len: BaseFrameLength::ReservedSubslot,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AccessAssignFr18 {
|
||||
|
||||
pub fn from_bitbuf(buf: &mut BitBuffer) -> Result<Self, PduParseErr> {
|
||||
let mut s = AccessAssignFr18 {
|
||||
_header: buf.read_field(2, "_header")? as u8,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let field1 = buf.read_field(6, "field1")? as u8;
|
||||
let field2 = buf.read_field(6, "field2")? as u8;
|
||||
let header = buf.read_field(2, "_header")?;
|
||||
let field1 = buf.read_field(6, "field1")? ;
|
||||
let field2 = buf.read_field(6, "field2")?;
|
||||
|
||||
match s._header {
|
||||
0 => {
|
||||
s.ul_usage = AccessAssignUlUsage::CommonOnly;
|
||||
s.f1_af1 = Some(AccessField {
|
||||
access_code: (field1 >> 4) & 0x3,
|
||||
base_frame_len: field1 & 0xF,
|
||||
});
|
||||
s.f2_af2 = Some(AccessField {
|
||||
access_code: (field2 >> 4) & 0x3,
|
||||
base_frame_len: field2 & 0xF,
|
||||
});
|
||||
}
|
||||
1 => {
|
||||
s.ul_usage = AccessAssignUlUsage::CommonAndAssigned;
|
||||
s.f1_af1 = Some(AccessField {
|
||||
access_code: (field1 >> 4) & 0x3,
|
||||
base_frame_len: field1 & 0xF,
|
||||
});
|
||||
s.f2_af2 = Some(AccessField {
|
||||
access_code: (field2 >> 4) & 0x3,
|
||||
base_frame_len: field2 & 0xF,
|
||||
});
|
||||
}
|
||||
2 => {
|
||||
s.ul_usage = AccessAssignUlUsage::AssignedOnly;
|
||||
s.f1_af1 = Some(AccessField {
|
||||
access_code: (field1 >> 4) & 0x3,
|
||||
base_frame_len: field1 & 0xF,
|
||||
});
|
||||
s.f2_af2 = Some(AccessField {
|
||||
access_code: (field2 >> 4) & 0x3,
|
||||
base_frame_len: field2 & 0xF,
|
||||
});
|
||||
}
|
||||
3 => {
|
||||
// UL usage counts as CommonAndAssigned, but with traffic marker
|
||||
let ul_usage = AccessAssignUlUsage::from_usage_marker(field1);
|
||||
s.ul_usage = ul_usage.ok_or(PduParseErr::InvalidValue {
|
||||
field: "ul_usage",
|
||||
value: field1 as u64,
|
||||
})?;
|
||||
assert!(ul_usage.unwrap().is_traffic());
|
||||
match header {
|
||||
|
||||
s.f2_af = Some(AccessField {
|
||||
access_code: (field2 >> 4) & 0x3,
|
||||
base_frame_len: field2 & 0xF,
|
||||
});
|
||||
0b00 => {
|
||||
// Uplink access rights - common only
|
||||
Ok(AccessAssignFr18::UplinkCommonOnly {
|
||||
access_field_1: field1.try_into()
|
||||
.map_err(|_| PduParseErr::InvalidValue { field: "access_field_1", value: field1 })?,
|
||||
access_field_2: field2.try_into()
|
||||
.map_err(|_| PduParseErr::InvalidValue { field: "access_field_2", value: field2 })?,
|
||||
})
|
||||
}
|
||||
|
||||
0b01 => {
|
||||
// Uplink access rights - common and assigned
|
||||
Ok(AccessAssignFr18::UplinkCommonAndAssigned {
|
||||
access_field_1: field1.try_into()
|
||||
.map_err(|_| PduParseErr::InvalidValue { field: "access_field_1", value: field1 })?,
|
||||
access_field_2: field2.try_into()
|
||||
.map_err(|_| PduParseErr::InvalidValue { field: "access_field_2", value: field2 })?,
|
||||
})
|
||||
}
|
||||
|
||||
0b10 => {
|
||||
// Uplink access rights - assigned only
|
||||
Ok(AccessAssignFr18::UplinkAssignedOnly {
|
||||
access_field_1: field1.try_into()
|
||||
.map_err(|_| PduParseErr::InvalidValue { field: "access_field_1", value: field1 })?,
|
||||
access_field_2: field2.try_into()
|
||||
.map_err(|_| PduParseErr::InvalidValue { field: "access_field_2", value: field2 })?,
|
||||
})
|
||||
}
|
||||
|
||||
0b11 => {
|
||||
// Uplink access rights - common and assigned, but with traffic usage marker (UMt) instead of AF1
|
||||
Ok(AccessAssignFr18::UplinkCommonAndAssignedTraffic {
|
||||
uplink_usage_marker: AccessAssignUlUsage::from_usage_marker(field1 as u8)
|
||||
.ok_or(PduParseErr::InvalidValue { field: "uplink_usage_marker", value: field1 })?,
|
||||
access_field: field2.try_into()
|
||||
.map_err(|_| PduParseErr::InvalidValue { field: "access_field", value: field2 })?,
|
||||
})
|
||||
}
|
||||
|
||||
_ => {
|
||||
panic!()
|
||||
panic!("Invalid header value for Frame 18 ACCESS-ASSIGN: {}", header);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub fn to_bitbuf(&self, buf: &mut BitBuffer) {
|
||||
if self.ul_usage == AccessAssignUlUsage::CommonOnly {
|
||||
let header = 0;
|
||||
buf.write_bits(header as u64, 2);
|
||||
buf.write_bits(self.f1_af1.as_ref().unwrap().access_code as u64, 2);
|
||||
buf.write_bits(self.f1_af1.as_ref().unwrap().base_frame_len as u64, 4);
|
||||
buf.write_bits(self.f2_af2.as_ref().unwrap().access_code as u64, 2);
|
||||
buf.write_bits(self.f2_af2.as_ref().unwrap().base_frame_len as u64, 4);
|
||||
assert!(self.f2_af.is_none());
|
||||
} else if self.ul_usage == AccessAssignUlUsage::CommonAndAssigned {
|
||||
let header = 1;
|
||||
buf.write_bits(header as u64, 2);
|
||||
buf.write_bits(self.f1_af1.as_ref().unwrap().access_code as u64, 2);
|
||||
buf.write_bits(self.f1_af1.as_ref().unwrap().base_frame_len as u64, 4);
|
||||
buf.write_bits(self.f2_af2.as_ref().unwrap().access_code as u64, 2);
|
||||
buf.write_bits(self.f2_af2.as_ref().unwrap().base_frame_len as u64, 4);
|
||||
assert!(self.f2_af.is_none());
|
||||
} else if self.ul_usage == AccessAssignUlUsage::AssignedOnly {
|
||||
let header = 2;
|
||||
buf.write_bits(header as u64, 2);
|
||||
buf.write_bits(self.f1_af1.as_ref().unwrap().access_code as u64, 2);
|
||||
buf.write_bits(self.f1_af1.as_ref().unwrap().base_frame_len as u64, 4);
|
||||
buf.write_bits(self.f2_af2.as_ref().unwrap().access_code as u64, 2);
|
||||
buf.write_bits(self.f2_af2.as_ref().unwrap().base_frame_len as u64, 4);
|
||||
assert!(self.f2_af.is_none());
|
||||
} else if self.ul_usage.is_traffic() {
|
||||
// UL usage counts as common and assigned, but with traffic marker
|
||||
let header = 3;
|
||||
buf.write_bits(header as u64, 2);
|
||||
let ul_usage = self.ul_usage.to_usage_marker().unwrap();
|
||||
buf.write_bits(ul_usage as u64, 6);
|
||||
buf.write_bits(self.f2_af.as_ref().unwrap().access_code as u64, 2);
|
||||
buf.write_bits(self.f2_af.as_ref().unwrap().base_frame_len as u64, 4);
|
||||
assert!(self.f1_af1.is_none());
|
||||
assert!(self.f2_af2.is_none());
|
||||
} else {
|
||||
unimplemented!("AccessAssign::to_bitbuf_fr18 for other cases");
|
||||
|
||||
match self {
|
||||
|
||||
AccessAssignFr18::UplinkCommonOnly {
|
||||
access_field_1,
|
||||
access_field_2
|
||||
} => {
|
||||
|
||||
// Header = 00
|
||||
buf.write_bits(0b00, 2);
|
||||
|
||||
// Access field 1
|
||||
buf.write_bits(access_field_1.into_raw(), 6);
|
||||
|
||||
// Access field 2
|
||||
buf.write_bits(access_field_2.into_raw(), 6);
|
||||
|
||||
},
|
||||
|
||||
AccessAssignFr18::UplinkCommonAndAssigned {
|
||||
access_field_1,
|
||||
access_field_2
|
||||
} => {
|
||||
|
||||
// Header = 01
|
||||
buf.write_bits(0b01, 2);
|
||||
|
||||
// Access field 1
|
||||
buf.write_bits(access_field_1.into_raw(), 6);
|
||||
|
||||
// Access field 2
|
||||
buf.write_bits(access_field_2.into_raw(), 6);
|
||||
|
||||
},
|
||||
|
||||
AccessAssignFr18::UplinkAssignedOnly {
|
||||
access_field_1,
|
||||
access_field_2
|
||||
} => {
|
||||
|
||||
// Header = 10
|
||||
buf.write_bits(0b10, 2);
|
||||
|
||||
// Access field 1
|
||||
buf.write_bits(access_field_1.into_raw(), 6);
|
||||
|
||||
// Access field 2
|
||||
buf.write_bits(access_field_2.into_raw(), 6);
|
||||
|
||||
},
|
||||
|
||||
AccessAssignFr18::UplinkCommonAndAssignedTraffic {
|
||||
uplink_usage_marker,
|
||||
access_field
|
||||
} => {
|
||||
|
||||
// Header = 11
|
||||
buf.write_bits(0b11, 2);
|
||||
|
||||
// Uplink usage marker
|
||||
buf.write_bits(uplink_usage_marker.to_usage_marker().unwrap() as u64, 6);
|
||||
|
||||
// Access field (both subslots)
|
||||
buf.write_bits(access_field.into_raw(), 6);}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AccessAssignFr18 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "access_assign {{ ul_usage: {}", self.ul_usage)?;
|
||||
if let Some(af) = &self.f2_af {
|
||||
write!(f, " AF {}/{}", af.access_code, af.base_frame_len)?;
|
||||
};
|
||||
if let Some(af) = &self.f1_af1 {
|
||||
write!(f, " AF1 {}/{}", af.access_code, af.base_frame_len)?;
|
||||
};
|
||||
if let Some(af) = &self.f2_af2 {
|
||||
write!(f, " AF2 {}/{}", af.access_code, af.base_frame_len)?;
|
||||
};
|
||||
write!(f, " }}")
|
||||
match self {
|
||||
AccessAssignFr18::UplinkCommonOnly { access_field_1, access_field_2 } => {
|
||||
write!(f, "AccessAssignFr18 {{ UplinkCommonOnly: af1: {}, af2: {} }}", access_field_1, access_field_2)
|
||||
},
|
||||
AccessAssignFr18::UplinkCommonAndAssigned { access_field_1, access_field_2 } => {
|
||||
write!(f, "AccessAssignFr18 {{ UplinkCommonAndAssigned: af1: {}, af2: {} }}", access_field_1, access_field_2)
|
||||
},
|
||||
AccessAssignFr18::UplinkAssignedOnly { access_field_1, access_field_2 } => {
|
||||
write!(f, "AccessAssignFr18 {{ UplinkAssignedOnly: af1: {}, af2: {} }}", access_field_1, access_field_2)
|
||||
},
|
||||
AccessAssignFr18::UplinkCommonAndAssignedTraffic { uplink_usage_marker, access_field } => {
|
||||
write!(f, "AccessAssignFr18 {{ UplinkCommonAndAssignedTraffic: uum: {}, af: {} }}", uplink_usage_marker, access_field)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
use core::fmt;
|
||||
use crate::umac::enums::access_code::AccessCode;
|
||||
use crate::umac::structs::base_frame_length::BaseFrameLength;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AccessField {
|
||||
pub access_code: AccessCode,
|
||||
pub base_frame_len: BaseFrameLength,
|
||||
}
|
||||
|
||||
impl TryFrom<u64> for AccessField {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u64) -> Result<Self, Self::Error> {
|
||||
Ok(AccessField {
|
||||
access_code: ((value >> 4) & 0b11).try_into()?,
|
||||
base_frame_len: (value & 0b1111).try_into()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AccessField {
|
||||
pub fn into_raw(self) -> u64 {
|
||||
(self.access_code.into_raw() << 4) | self.base_frame_len.into_raw()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AccessField {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Access Code: {}, Base Frame Length: {}", self.access_code, self.base_frame_len)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
use core::fmt;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BaseFrameLength {
|
||||
|
||||
/// Essentially "already assigned" - Access Code is meaningless in this Access Field
|
||||
ReservedSubslot,
|
||||
|
||||
/// CLCH opportunity - Access Code is meaningless in this Access Field (common access for linearisation only)
|
||||
CLCHSubslot,
|
||||
|
||||
/// Ongoing Frame, a continuation of the ongoing access frame
|
||||
OngoingFrame,
|
||||
|
||||
// The following options indicate the start of a new access frame
|
||||
Subslots1,
|
||||
Subslots2,
|
||||
Subslots3,
|
||||
Subslots4,
|
||||
Subslots5,
|
||||
Subslots6,
|
||||
Subslots8,
|
||||
Subslots10,
|
||||
Subslots12,
|
||||
Subslots16,
|
||||
Subslots20,
|
||||
Subslots24,
|
||||
Subslots32
|
||||
}
|
||||
|
||||
impl TryFrom<u64> for BaseFrameLength {
|
||||
type Error = ();
|
||||
fn try_from(value: u64) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0b0000 => Ok(BaseFrameLength::ReservedSubslot),
|
||||
0b0001 => Ok(BaseFrameLength::CLCHSubslot),
|
||||
0b0010 => Ok(BaseFrameLength::OngoingFrame),
|
||||
0b0011 => Ok(BaseFrameLength::Subslots1),
|
||||
0b0100 => Ok(BaseFrameLength::Subslots2),
|
||||
0b0101 => Ok(BaseFrameLength::Subslots3),
|
||||
0b0110 => Ok(BaseFrameLength::Subslots4),
|
||||
0b0111 => Ok(BaseFrameLength::Subslots5),
|
||||
0b1000 => Ok(BaseFrameLength::Subslots6),
|
||||
0b1001 => Ok(BaseFrameLength::Subslots8),
|
||||
0b1010 => Ok(BaseFrameLength::Subslots10),
|
||||
0b1011 => Ok(BaseFrameLength::Subslots12),
|
||||
0b1100 => Ok(BaseFrameLength::Subslots16),
|
||||
0b1101 => Ok(BaseFrameLength::Subslots20),
|
||||
0b1110 => Ok(BaseFrameLength::Subslots24),
|
||||
0b1111 => Ok(BaseFrameLength::Subslots32),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BaseFrameLength {
|
||||
pub fn into_raw(self) -> u64 {
|
||||
match self {
|
||||
BaseFrameLength::ReservedSubslot => 0b0000,
|
||||
BaseFrameLength::CLCHSubslot => 0b0001,
|
||||
BaseFrameLength::OngoingFrame => 0b0010,
|
||||
BaseFrameLength::Subslots1 => 0b0011,
|
||||
BaseFrameLength::Subslots2 => 0b0100,
|
||||
BaseFrameLength::Subslots3 => 0b0101,
|
||||
BaseFrameLength::Subslots4 => 0b0110,
|
||||
BaseFrameLength::Subslots5 => 0b0111,
|
||||
BaseFrameLength::Subslots6 => 0b1000,
|
||||
BaseFrameLength::Subslots8 => 0b1001,
|
||||
BaseFrameLength::Subslots10 => 0b1010,
|
||||
BaseFrameLength::Subslots12 => 0b1011,
|
||||
BaseFrameLength::Subslots16 => 0b1100,
|
||||
BaseFrameLength::Subslots20 => 0b1101,
|
||||
BaseFrameLength::Subslots24 => 0b1110,
|
||||
BaseFrameLength::Subslots32 => 0b1111
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BaseFrameLength {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
BaseFrameLength::ReservedSubslot => write!(f, "Reserved Subslot"),
|
||||
BaseFrameLength::CLCHSubslot => write!(f, "CLCH Opportunity"),
|
||||
BaseFrameLength::OngoingFrame => write!(f, "Ongoing Frame"),
|
||||
BaseFrameLength::Subslots1 => write!(f, "1 Subslot"),
|
||||
BaseFrameLength::Subslots2 => write!(f, "2 Subslots"),
|
||||
BaseFrameLength::Subslots3 => write!(f, "3 Subslots"),
|
||||
BaseFrameLength::Subslots4 => write!(f, "4 Subslots"),
|
||||
BaseFrameLength::Subslots5 => write!(f, "5 Subslots"),
|
||||
BaseFrameLength::Subslots6 => write!(f, "6 Subslots"),
|
||||
BaseFrameLength::Subslots8 => write!(f, "8 Subslots"),
|
||||
BaseFrameLength::Subslots10 => write!(f, "10 Subslots"),
|
||||
BaseFrameLength::Subslots12 => write!(f, "12 Subslots"),
|
||||
BaseFrameLength::Subslots16 => write!(f, "16 Subslots"),
|
||||
BaseFrameLength::Subslots20 => write!(f, "20 Subslots"),
|
||||
BaseFrameLength::Subslots24 => write!(f, "24 Subslots"),
|
||||
BaseFrameLength::Subslots32 => write!(f, "32 Subslots")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
pub mod umac_circuit;
|
||||
pub mod access_field;
|
||||
pub mod base_frame_length;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::fmt;
|
||||
|
||||
/// Logical channels as defined in the standard
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum LogicalChannel {
|
||||
@@ -94,3 +96,24 @@ impl LogicalChannel {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LogicalChannel {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let name = match self {
|
||||
LogicalChannel::Aach => "AACH",
|
||||
LogicalChannel::SchHd => "SCH (half slot, downlink)",
|
||||
LogicalChannel::SchF => "SCH (full slot)",
|
||||
LogicalChannel::Stch => "STCH",
|
||||
LogicalChannel::SchHu => "SCH (half slot, uplink)",
|
||||
LogicalChannel::TchS => "TCH (Voice)",
|
||||
LogicalChannel::Tch24 => "TCH (2.4 kbps)",
|
||||
LogicalChannel::Tch48 => "TCH (4.8 kbps)",
|
||||
LogicalChannel::Tch72 => "TCH (7.2 kbps)",
|
||||
LogicalChannel::Bsch => "BSCH",
|
||||
LogicalChannel::Bnch => "BNCH",
|
||||
LogicalChannel::Blch => "BLCH",
|
||||
LogicalChannel::Clch => "CLCH",
|
||||
};
|
||||
write!(f, "{}", name)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user