Skip to content

Commit

Permalink
Merge pull request #2 from tweedegolf/fix-beacon-request-response
Browse files Browse the repository at this point in the history
Make coordinator respond to beacon requests when required
  • Loading branch information
diondokter authored Feb 20, 2025
2 parents 4ede6cd + 73849a8 commit 6393dc8
Show file tree
Hide file tree
Showing 12 changed files with 301 additions and 124 deletions.
2 changes: 1 addition & 1 deletion lr-wpan-rs-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"

[dependencies]
lr-wpan-rs = { path = "../lr-wpan-rs", features = ["std"] }
lr-wpan-rs = { path = "../lr-wpan-rs", features = ["std", "log-04"] }
pcap-file = { version = "2.0.0" }
log = { version = "0.4.22" }
tokio = { version = "1.41.0", default-features = false, features = ["time", "test-util"] }
Expand Down
19 changes: 18 additions & 1 deletion lr-wpan-rs-tests/src/aether/radio.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::sync::{Arc, Mutex, MutexGuard};

use log::trace;
use lr_wpan_rs::{
phy::{ModulationType, Phy, ReceivedMessage, SendContinuation, SendResult},
pib::{PhyPib, PhyPibWrite},
Expand Down Expand Up @@ -51,6 +52,8 @@ impl Phy for AetherRadio {
const MODULATION: ModulationType = ModulationType::BPSK;

async fn reset(&mut self) -> Result<(), Self::Error> {
trace!("Radio reset {:?}", self.node_id);

self.stop_receive().await?;
let new_pib = PhyPib::unspecified_new();
self.with_node(|node| {
Expand All @@ -74,8 +77,10 @@ impl Phy for AetherRadio {
send_time: Option<Instant>,
_ranging: bool,
_use_csma: bool,
_continuation: SendContinuation,
continuation: SendContinuation,
) -> Result<SendResult, Self::Error> {
trace!("Radio send {:?}", self.node_id);

let now = send_time
.unwrap_or_else(|| std::time::Instant::from(tokio::time::Instant::now()).into());
tokio::time::sleep_until(now.into_std().into()).await;
Expand All @@ -84,11 +89,19 @@ impl Phy for AetherRadio {
let channel = self.local_pib.current_channel;
self.aether().send(AirPacket::new(data, now, channel));

match continuation {
SendContinuation::Idle => {}
SendContinuation::WaitForResponse { .. } => todo!(),
SendContinuation::ReceiveContinuous => self.start_receive().await?,
}

// TODO: Handle congestion
Ok(SendResult::Success(now))
}

async fn start_receive(&mut self) -> Result<(), Self::Error> {
trace!("Radio start_receive {:?}", self.node_id);

self.with_node(|node| {
node.rx_enable = true;
});
Expand All @@ -97,6 +110,8 @@ impl Phy for AetherRadio {
}

async fn stop_receive(&mut self) -> Result<(), Self::Error> {
trace!("Radio stop_receive {:?}", self.node_id);

self.with_node(|node| {
node.rx_enable = false;
});
Expand Down Expand Up @@ -134,6 +149,8 @@ impl Phy for AetherRadio {
&mut self,
ctx: Self::ProcessingContext,
) -> Result<Option<ReceivedMessage>, Self::Error> {
trace!("Radio process {:?}", self.node_id);

Ok(Some(ctx))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ use lr_wpan_rs::{
IndicationValue, PanDescriptor, SecurityInfo, Status,
},
time::Instant,
wire::{command::Command, Frame, FrameContent, PanId, ShortAddress},
wire::{
beacon::{BeaconOrder, SuperframeOrder},
command::Command,
Address, Frame, FrameContent, PanId, ShortAddress,
},
ChannelPage,
};
use test_log::test;
Expand All @@ -23,22 +27,29 @@ async fn scan_passive() {

runner.aether.start_trace("scan_passive");

start_beacon(runner.commanders[0], 0).await;
start_beacon(runner.commanders[1], 1).await;
// Start the beacons
start_beacon(runner.commanders[0], 0, true).await;
start_beacon(runner.commanders[1], 1, false).await;

// Perform the scan, passively
let (scan_confirm, notifications) =
perform_scan(runner.commanders[2], ScanType::Passive, &[0, 1, 2], true).await;
let trace = runner.aether.stop_trace();

let mut messages = runner.aether.parse_trace(trace);
// Scan needs to be successful
assert_eq!(scan_confirm.status, Status::Success);
// We should've scanned one device since we've done a passive scan
assert_eq!(scan_confirm.result_list_size, 1);
// All channels should be scanned
assert!(scan_confirm.unscanned_channels.is_empty());

// Auto request was true, so we should've gotten zero beacon notifications
assert!(notifications.is_empty());

let trace = runner.aether.stop_trace();
// All the messages in the aether should be beacons
let mut messages = runner.aether.parse_trace(trace);
assert!(messages.all(|m| matches!(m.content, FrameContent::Beacon(_))));

assert_eq!(scan_confirm.status, Status::Success);
assert_eq!(scan_confirm.result_list_size, 2);
assert!(scan_confirm.unscanned_channels.is_empty());
pretty_assertions::assert_eq!(
scan_confirm.pan_descriptor_list().nth(0).unwrap(),
&PanDescriptor {
Expand All @@ -61,28 +72,7 @@ async fn scan_passive() {
code_list: ()
}
);
pretty_assertions::assert_eq!(
scan_confirm.pan_descriptor_list().nth(1).unwrap(),
&PanDescriptor {
coord_address: lr_wpan_rs::wire::Address::Short(PanId(1), ShortAddress(1)),
channel_number: 0,
channel_page: ChannelPage::Uwb,
super_frame_spec: lr_wpan_rs::wire::beacon::SuperframeSpecification {
beacon_order: lr_wpan_rs::wire::beacon::BeaconOrder::BeaconOrder(10),
superframe_order: lr_wpan_rs::wire::beacon::SuperframeOrder::SuperframeOrder(10),
final_cap_slot: 0,
battery_life_extension: false,
pan_coordinator: true,
association_permit: false
},
gts_permit: false,
link_quality: 255,
timestamp: Instant::from_ticks(9830400000),
security_status: None,
security_info: SecurityInfo::new_none_security(),
code_list: ()
}
);
assert_eq!(scan_confirm.pan_descriptor_list().nth(1), None);
}

#[test(tokio::test(unhandled_panic = "shutdown_runtime", start_paused = true))]
Expand All @@ -91,17 +81,29 @@ async fn scan_active() {

runner.aether.start_trace("scan_active");

start_beacon(runner.commanders[0], 0).await;
start_beacon(runner.commanders[1], 1).await;
// Start the beacons
start_beacon(runner.commanders[0], 0, true).await;
start_beacon(runner.commanders[1], 1, false).await;

let (scan_confirm, notifications) =
// Perform the scan, actively
let (mut scan_confirm, notifications) =
perform_scan(runner.commanders[2], ScanType::Active, &[0], true).await;
let trace = runner.aether.stop_trace();

let mut messages = runner.aether.parse_trace(trace);
// Scan needs to be successful
assert_eq!(scan_confirm.status, Status::Success);
// We should've scanned two devices since we've done an active scan
assert_eq!(scan_confirm.result_list_size, 2);
// All channels should be scanned
assert!(scan_confirm.unscanned_channels.is_empty());

// Auto request was true, so we should've gotten zero beacon notifications
assert!(notifications.is_empty());

let trace = runner.aether.stop_trace();

let mut messages = runner.aether.parse_trace(trace);

// We expect a beacon request and then only beacons
let first_message = messages.next();
assert!(
matches!(
Expand All @@ -115,11 +117,11 @@ async fn scan_active() {
);
assert!(messages.all(|m| matches!(m.content, FrameContent::Beacon(_))));

assert_eq!(scan_confirm.status, Status::Success);
assert_eq!(scan_confirm.result_list_size, 2);
assert!(scan_confirm.unscanned_channels.is_empty());
pretty_assertions::assert_eq!(
scan_confirm.pan_descriptor_list().nth(0).unwrap(),
scan_confirm
.pan_descriptor_list()
.find(|pd| pd.coord_address == Address::Short(PanId(0), ShortAddress(0)))
.unwrap(),
&PanDescriptor {
coord_address: lr_wpan_rs::wire::Address::Short(PanId(0), ShortAddress(0)),
channel_number: 0,
Expand All @@ -140,23 +142,31 @@ async fn scan_active() {
code_list: ()
}
);

let non_beacon_pan = scan_confirm
.pan_descriptor_list_mut()
.find(|pd| pd.coord_address == Address::Short(PanId(1), ShortAddress(1)))
.unwrap();
// We don't want to test the timestamp since that changes (even in simulation)
non_beacon_pan.timestamp = Instant::from_seconds(0);

pretty_assertions::assert_eq!(
scan_confirm.pan_descriptor_list().nth(1).unwrap(),
non_beacon_pan,
&PanDescriptor {
coord_address: lr_wpan_rs::wire::Address::Short(PanId(1), ShortAddress(1)),
channel_number: 0,
channel_page: ChannelPage::Uwb,
super_frame_spec: lr_wpan_rs::wire::beacon::SuperframeSpecification {
beacon_order: lr_wpan_rs::wire::beacon::BeaconOrder::BeaconOrder(10),
superframe_order: lr_wpan_rs::wire::beacon::SuperframeOrder::SuperframeOrder(10),
beacon_order: lr_wpan_rs::wire::beacon::BeaconOrder::OnDemand,
superframe_order: lr_wpan_rs::wire::beacon::SuperframeOrder::Inactive,
final_cap_slot: 0,
battery_life_extension: false,
pan_coordinator: true,
association_permit: false
},
gts_permit: false,
link_quality: 255,
timestamp: Instant::from_ticks(9830400000),
timestamp: Instant::from_ticks(0),
security_status: None,
security_info: SecurityInfo::new_none_security(),
code_list: ()
Expand All @@ -166,21 +176,35 @@ async fn scan_active() {

#[test(tokio::test(unhandled_panic = "shutdown_runtime", start_paused = true))]
async fn scan_passive_no_auto_request() {
// Goal is to scan without auto request which sends out the data as indications
// The indications should be the same as what's being sent out on the aether

let mut runner = lr_wpan_rs_tests::run::run_mac_engine_multi(3);

runner.aether.start_trace("scan_passive_no_auto");

start_beacon(runner.commanders[0], 0).await;
start_beacon(runner.commanders[1], 1).await;
// Start the beacons
start_beacon(runner.commanders[0], 0, true).await;
start_beacon(runner.commanders[1], 1, true).await;

// Do the scan, passively, without auto request
let (scan_confirm, notifications) =
perform_scan(runner.commanders[2], ScanType::Passive, &[0, 1, 2], false).await;
let trace = runner.aether.stop_trace();

let messages = runner.aether.parse_trace(trace);

// Scan must have succeeded
assert_eq!(scan_confirm.status, Status::Success);
// No list, since we should've gotten the info as indications
assert_eq!(scan_confirm.result_list_size, 0);
assert_eq!(scan_confirm.pan_descriptor_list().count(), 0);
// All channels should have been scanned
assert!(scan_confirm.unscanned_channels.is_empty());
// Notifications must NOT be empty
assert!(!notifications.is_empty());

let trace = runner.aether.stop_trace();

// The notifications should follow the messages on the aether
let messages = runner.aether.parse_trace(trace);
for (message, notification) in messages.zip(notifications) {
match message.content {
FrameContent::Beacon(beacon) => {
Expand All @@ -199,13 +223,11 @@ async fn scan_passive_no_auto_request() {
_ => unimplemented!("Not seen in test"),
}
}

assert_eq!(scan_confirm.status, Status::Success);
assert_eq!(scan_confirm.result_list_size, 0);
assert!(scan_confirm.unscanned_channels.is_empty());
}

async fn start_beacon(commander: &MacCommander, id: u16) {
// TODO: A test with auto request enabled and more PANs being scanned than can fit in the allocation

async fn start_beacon(commander: &MacCommander, id: u16, emit_beacons: bool) {
let reset_response = commander
.request(ResetRequest {
set_default_pib: true,
Expand All @@ -227,8 +249,16 @@ async fn start_beacon(commander: &MacCommander, id: u16) {
channel_number: 0,
channel_page: ChannelPage::Uwb,
start_time: 0,
beacon_order: lr_wpan_rs::wire::beacon::BeaconOrder::BeaconOrder(10),
superframe_order: lr_wpan_rs::wire::beacon::SuperframeOrder::SuperframeOrder(10),
beacon_order: if emit_beacons {
BeaconOrder::BeaconOrder(10)
} else {
BeaconOrder::OnDemand
},
superframe_order: if emit_beacons {
SuperframeOrder::SuperframeOrder(10)
} else {
SuperframeOrder::Inactive
},
pan_coordinator: true,
battery_life_extension: false,
coord_realignment: false,
Expand Down Expand Up @@ -257,7 +287,9 @@ async fn perform_scan(
pib_attribute: PibValue::MAC_AUTO_REQUEST,
pib_attribute_value: PibValue::MacAutoRequest(auto_request),
})
.await;
.await
.status
.unwrap();

let mut wait = core::pin::pin!(commander.wait_for_indication().fuse());

Expand Down
1 change: 1 addition & 0 deletions lr-wpan-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ cipher = { version = "0.3.0", default-features = false }
defmt = { version = "0.3.8", optional = true }
log = { version = "0.4.22", optional = true }
rand_core = "0.9.0"
derive_more = { version = "2.0.1", default-features = false, features = ["display"] }

[dev-dependencies]
rand = "0.9.0"
Expand Down
2 changes: 2 additions & 0 deletions lr-wpan-rs/src/mac/commander.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ impl MacCommander {

/// Make a request to the MAC layer. The typed confirm response is returned.
/// This API is cancel-safe, though the request may not have been sent at the point of cancellation.
#[must_use]
pub async fn request<R: Request>(&self, request: R) -> R::Confirm {
self.request_confirm_channel
.request(request.into())
Expand All @@ -33,6 +34,7 @@ impl MacCommander {

/// Make a request to the MAC layer. The typed confirm response is returned.
/// This API is cancel-safe, though the request may not have been sent at the point of cancellation.
#[must_use]
pub async fn request_with_allocation<'a, R: DynamicRequest>(
&self,
mut request: R,
Expand Down
2 changes: 1 addition & 1 deletion lr-wpan-rs/src/mac/mlme_scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ impl ScanProcess<'_> {
.remove(self.skipped_channels);
}
ScanAction::Finish => {
info!("Scan has been finished!")
debug!("Scan has been finished!")
}
}
}
Expand Down
Loading

0 comments on commit 6393dc8

Please sign in to comment.