Send CtrlUplinkAction=1 for deleted Relay devices.

This commit is contained in:
Orne Brocaar 2023-08-01 14:39:48 +01:00
parent 4359e90613
commit ae8c732895
2 changed files with 239 additions and 49 deletions

View File

@ -1346,12 +1346,12 @@ impl Data {
async fn _update_uplink_list(&mut self) -> Result<()> {
trace!("Updating Relay uplink list");
// Get the current relay state.
let mut relay = if let Some(r) = &self.device_session.relay {
r.clone()
} else {
internal::Relay::default()
};
if self.device_session.relay.is_none() {
self.device_session.relay = Some(internal::Relay::default());
}
// Get a copy of the current relay state.
let relay = self.device_session.relay.as_ref().unwrap().clone();
// Get devices that must be configured on the relay.
let relay_devices = relay::list_devices(
@ -1363,23 +1363,10 @@ impl Data {
)
.await?;
// We filter out the devices that are no longer configured on the relay.
// This way we can combine the delete + add by just overwriting an old slot.
let relay_devices_dev_euis: Vec<Vec<u8>> =
relay_devices.iter().map(|d| d.dev_eui.to_vec()).collect();
relay
.devices
.retain(|rd| relay_devices_dev_euis.contains(&rd.dev_eui));
// Calculate free slots.
// If we need to add a device, we can use the first slot available.
// Note that there can be gaps in the indicex if devices have been removed.
// Calculate unused slots.
let used_slots: Vec<u32> = relay.devices.iter().map(|d| d.index).collect();
let free_slots: Vec<u32> = (0..15).filter(|x| !used_slots.contains(x)).collect();
// Update device-session.
self.device_session.relay = Some(relay);
// Iterate over the list of devices under this relay.
for device in &relay_devices {
// We need a dev_addr for the filter. Ignore devices that do not have a DevAddr (e.g.
@ -1520,18 +1507,60 @@ impl Data {
async fn _request_ctrl_uplink_list(&mut self) -> Result<()> {
trace!("Requesting CtrlUplinkList to sync WFCnt");
if self.device_session.relay.is_none() {
self.device_session.relay = Some(internal::Relay::default());
}
// Get a copy of the current relay state.
let mut relay = self.device_session.relay.as_ref().unwrap().clone();
// Get devices that must be configured on the relay.
let relay_devices = relay::list_devices(
15,
0,
&relay::DeviceFilters {
relay_dev_eui: Some(self.device.dev_eui),
},
)
.await?;
// Get DevEUIs of Relay EDs.
let relay_devices_dev_euis: Vec<Vec<u8>> =
relay_devices.iter().map(|d| d.dev_eui.to_vec()).collect();
// Calculate removed slots.
let removed_slots: Vec<u32> = relay
.devices
.iter()
.filter(|d| !relay_devices_dev_euis.contains(&d.dev_eui))
.map(|f| f.index)
.collect();
let max_count = 3;
let mut counter = 0;
let mut commands: Vec<lrwn::MACCommand> = vec![];
// Get the current relay state.
let mut relay = if let Some(r) = &self.device_session.relay {
r.clone()
} else {
internal::Relay::default()
};
// Delete end-device from trusted list.
for slot in &removed_slots {
if counter < max_count {
counter += 1;
commands.push(lrwn::MACCommand::CtrlUplinkListReq(
lrwn::CtrlUplinkListReqPayload {
ctrl_uplink_action: lrwn::CtrlUplinkActionPL {
uplink_list_idx: *slot as u8,
ctrl_uplink_action: 1,
},
},
));
}
}
// Sync WFCnt.
for rd in &mut relay.devices {
if removed_slots.contains(&rd.index) {
continue;
}
match &rd.w_f_cnt_last_request {
Some(v) => {
let last_req: DateTime<Utc> = v.clone().try_into()?;
@ -2646,6 +2675,7 @@ mod test {
relay_devices: vec![],
expected_mac_commands: vec![],
expected_device_session: internal::DeviceSession {
relay: Some(internal::Relay::default()),
..Default::default()
},
},
@ -2979,7 +3009,15 @@ mod test {
device::delete(dev_eui).await.unwrap();
}
// We can not predict the w_f_cnt_last_request timestamp.
if let Some(relay) = &mut ctx.device_session.relay {
for rd in &mut relay.devices {
rd.w_f_cnt_last_request = None;
}
}
assert_eq!(test.expected_mac_commands, ctx.mac_commands);
assert_eq!(test.expected_device_session, ctx.device_session);
}
}
@ -3753,6 +3791,7 @@ mod test {
async fn test_request_ctrl_uplink_list() {
struct Test {
name: String,
relay_devices: Vec<EUI64>,
device_session: internal::DeviceSession,
expected_mac_commands: Vec<lrwn::MACCommandSet>,
}
@ -3760,11 +3799,14 @@ mod test {
let tests = vec![
Test {
name: "w_f_cnt has been recently requested".into(),
relay_devices: vec![EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 2])],
device_session: internal::DeviceSession {
dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 1],
relay: Some(internal::Relay {
devices: vec![internal::RelayDevice {
index: 1,
w_f_cnt_last_request: Some(Utc::now().into()),
dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 2],
..Default::default()
}],
..Default::default()
@ -3775,11 +3817,14 @@ mod test {
},
Test {
name: "w_f_cnt has never been requested".into(),
relay_devices: vec![EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 2])],
device_session: internal::DeviceSession {
dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 1],
relay: Some(internal::Relay {
devices: vec![internal::RelayDevice {
index: 1,
w_f_cnt_last_request: None,
dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 2],
..Default::default()
}],
..Default::default()
@ -3797,7 +3842,9 @@ mod test {
},
Test {
name: "w_f_cnt has been requested two days ago".into(),
relay_devices: vec![EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 2])],
device_session: internal::DeviceSession {
dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 1],
relay: Some(internal::Relay {
devices: vec![internal::RelayDevice {
index: 1,
@ -3807,6 +3854,7 @@ mod test {
.unwrap()
.into(),
),
dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 2],
..Default::default()
}],
..Default::default()
@ -3824,27 +3872,38 @@ mod test {
},
Test {
name: "more than three devices have outdated w_f_cnt".into(),
relay_devices: vec![
EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 2]),
EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 3]),
EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 4]),
EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 5]),
],
device_session: internal::DeviceSession {
dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 1],
relay: Some(internal::Relay {
devices: vec![
internal::RelayDevice {
index: 1,
w_f_cnt_last_request: None,
dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 2],
..Default::default()
},
internal::RelayDevice {
index: 2,
w_f_cnt_last_request: None,
dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 3],
..Default::default()
},
internal::RelayDevice {
index: 3,
w_f_cnt_last_request: None,
dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 4],
..Default::default()
},
internal::RelayDevice {
index: 4,
w_f_cnt_last_request: None,
dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 5],
..Default::default()
},
],
@ -3874,20 +3933,102 @@ mod test {
// The 4th is truncated
])],
},
Test {
name: "device has been removed".into(),
relay_devices: vec![],
device_session: internal::DeviceSession {
dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 1],
relay: Some(internal::Relay {
devices: vec![internal::RelayDevice {
index: 1,
w_f_cnt_last_request: None,
dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 2],
..Default::default()
}],
..Default::default()
}),
..Default::default()
},
expected_mac_commands: vec![lrwn::MACCommandSet::new(vec![
lrwn::MACCommand::CtrlUplinkListReq(lrwn::CtrlUplinkListReqPayload {
ctrl_uplink_action: lrwn::CtrlUplinkActionPL {
uplink_list_idx: 1,
ctrl_uplink_action: 1,
},
}),
])],
},
];
let _guard = test::prepare().await;
let t = tenant::create(tenant::Tenant {
name: "test-tenant".into(),
..Default::default()
})
.await
.unwrap();
let dp_relay = device_profile::create(device_profile::DeviceProfile {
name: "dp-relay".into(),
tenant_id: t.id,
is_relay: true,
..Default::default()
})
.await
.unwrap();
let dp_ed = device_profile::create(device_profile::DeviceProfile {
name: "dp-ed".into(),
tenant_id: t.id,
..Default::default()
})
.await
.unwrap();
let app = application::create(application::Application {
name: "test-app".into(),
tenant_id: t.id,
..Default::default()
})
.await
.unwrap();
let d_relay = device::create(device::Device {
dev_eui: EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 1]),
name: "relay".into(),
application_id: app.id,
device_profile_id: dp_relay.id,
..Default::default()
})
.await
.unwrap();
for test in &tests {
println!("> {}", test.name);
// create devices + add to relay
for dev_eui in &test.relay_devices {
let d = device::create(device::Device {
name: dev_eui.to_string(),
dev_eui: *dev_eui,
application_id: app.id,
device_profile_id: dp_ed.id,
..Default::default()
})
.await
.unwrap();
relay::add_device(d_relay.dev_eui, d.dev_eui).await.unwrap();
}
let mut ctx = Data {
relay_context: None,
uplink_frame_set: None,
tenant: tenant::Tenant::default(),
application: application::Application::default(),
device_profile: device_profile::DeviceProfile::default(),
device: device::Device::default(),
device: d_relay.clone(),
device_session: test.device_session.clone(),
network_conf: config::get_region_network("eu868").unwrap(),
region_conf: region::get("eu868").unwrap(),
@ -3905,6 +4046,11 @@ mod test {
ctx._request_ctrl_uplink_list().await.unwrap();
// cleanup devices
for dev_eui in &test.relay_devices {
device::delete(dev_eui).await.unwrap();
}
assert_eq!(test.expected_mac_commands, ctx.mac_commands);
}
}

View File

@ -43,27 +43,34 @@ pub async fn handle(
return Err(anyhow!("CtrlUplinkListAns mac-command count does not equal CtrlUplinkListReq mac-command count"));
}
let relay = match &ds.relay {
Some(v) => v.clone(),
None => Default::default(),
};
for (req_pl, ans_pl) in zip(req_pls, ans_pls) {
if ans_pl.uplink_list_idx_ack {
info!(
dev_eui = %dev.dev_eui,
uplink_list_idx = req_pl.ctrl_uplink_action.uplink_list_idx,
w_f_cnt = ans_pl.w_fcnt,
"CtrlUplinkListReq acknowledged",
);
let action = req_pl.ctrl_uplink_action.ctrl_uplink_action;
for rd in &relay.devices {
if req_pl.ctrl_uplink_action.uplink_list_idx as u32 == rd.index {
let mut ds = device_session::get(&EUI64::from_slice(&rd.dev_eui)?).await?;
if let Some(relay) = &mut ds.relay {
relay.w_f_cnt = ans_pl.w_fcnt;
};
device_session::save(&ds).await?;
if ans_pl.uplink_list_idx_ack {
if let Some(relay) = &mut ds.relay {
info!(
dev_eui = %dev.dev_eui,
uplink_list_idx = req_pl.ctrl_uplink_action.uplink_list_idx,
ctrl_uplink_action = action,
w_f_cnt = ans_pl.w_fcnt,
"CtrlUplinkListReq acknowledged",
);
if action == 0 {
for rd in &relay.devices {
if req_pl.ctrl_uplink_action.uplink_list_idx as u32 == rd.index {
let mut ds =
device_session::get(&EUI64::from_slice(&rd.dev_eui)?).await?;
if let Some(relay) = &mut ds.relay {
relay.w_f_cnt = ans_pl.w_fcnt;
};
device_session::save(&ds).await?;
}
}
} else if action == 1 {
relay
.devices
.retain(|d| d.index != req_pl.ctrl_uplink_action.uplink_list_idx as u32);
}
}
} else {
@ -75,8 +82,6 @@ pub async fn handle(
}
}
ds.relay = Some(relay);
Ok(None)
}
@ -141,7 +146,7 @@ mod test {
expected_error: Some("Expected pending CtrlUplinkListReq mac-command".to_string()),
},
Test {
name: "acked".into(),
name: "acked WFCnt sync".into(),
device_session: internal::DeviceSession {
relay: Some(internal::Relay {
devices: vec![internal::RelayDevice {
@ -187,6 +192,45 @@ mod test {
},
expected_error: None,
},
Test {
name: "acked delete".into(),
device_session: internal::DeviceSession {
relay: Some(internal::Relay {
devices: vec![internal::RelayDevice {
index: 1,
dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8],
..Default::default()
}],
..Default::default()
}),
..Default::default()
},
device_session_ed: internal::DeviceSession {
dev_addr: vec![1, 2, 3, 4],
dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8],
..Default::default()
},
ctrl_uplink_list_req: Some(lrwn::MACCommandSet::new(vec![
lrwn::MACCommand::CtrlUplinkListReq(lrwn::CtrlUplinkListReqPayload {
ctrl_uplink_action: lrwn::CtrlUplinkActionPL {
uplink_list_idx: 1,
ctrl_uplink_action: 1,
},
}),
])),
ctrl_uplink_list_ans: lrwn::MACCommandSet::new(vec![
lrwn::MACCommand::CtrlUplinkListAns(lrwn::CtrlUplinkListAnsPayload {
uplink_list_idx_ack: true,
w_fcnt: 10,
}),
]),
expected_device_session_ed: internal::DeviceSession {
dev_addr: vec![1, 2, 3, 4],
dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8],
..Default::default()
},
expected_error: None,
},
];
for tst in &tests {