mirror of
https://github.com/chirpstack/chirpstack.git
synced 2024-12-25 07:41:09 +00:00
Migrate code to store device-session in PG (WIP).
This commit is contained in:
parent
5c3624cfbe
commit
fae182aa3d
@ -18,7 +18,7 @@ use crate::backend::{joinserver, keywrap, roaming};
|
|||||||
use crate::downlink::data_fns;
|
use crate::downlink::data_fns;
|
||||||
use crate::helpers::errors::PrintFullError;
|
use crate::helpers::errors::PrintFullError;
|
||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
device_session, error::Error as StorageError, get_async_redis_conn, passive_roaming, redis_key,
|
device, error::Error as StorageError, get_async_redis_conn, passive_roaming, redis_key,
|
||||||
};
|
};
|
||||||
use crate::uplink::{
|
use crate::uplink::{
|
||||||
data_sns, error::Error as UplinkError, helpers, join_sns, RoamingMetaData, UplinkFrameSet,
|
data_sns, error::Error as UplinkError, helpers, join_sns, RoamingMetaData, UplinkFrameSet,
|
||||||
@ -312,7 +312,7 @@ async fn _handle_pr_start_req_data(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// get device-session
|
// get device-session
|
||||||
let ds = device_session::get_for_phypayload(&mut ufs.phy_payload, ufs.dr, ufs.ch as u8).await?;
|
let ds = device::get_for_phypayload(&mut ufs.phy_payload, ufs.dr, ufs.ch as u8).await?;
|
||||||
let pr_lifetime = roaming::get_passive_roaming_lifetime(sender_id)?;
|
let pr_lifetime = roaming::get_passive_roaming_lifetime(sender_id)?;
|
||||||
let kek_label = roaming::get_passive_roaming_kek_label(sender_id)?;
|
let kek_label = roaming::get_passive_roaming_kek_label(sender_id)?;
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ use std::time::SystemTime;
|
|||||||
|
|
||||||
use bigdecimal::ToPrimitive;
|
use bigdecimal::ToPrimitive;
|
||||||
use chrono::{DateTime, Local, Utc};
|
use chrono::{DateTime, Local, Utc};
|
||||||
|
use prost::Message;
|
||||||
use tonic::{Request, Response, Status};
|
use tonic::{Request, Response, Status};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@ -15,10 +16,11 @@ use lrwn::{AES128Key, DevAddr, EUI64};
|
|||||||
use super::auth::validator;
|
use super::auth::validator;
|
||||||
use super::error::ToStatus;
|
use super::error::ToStatus;
|
||||||
use super::helpers::{self, FromProto, ToProto};
|
use super::helpers::{self, FromProto, ToProto};
|
||||||
use crate::storage::error::Error;
|
|
||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
device::{self, DeviceClass},
|
device::{self, DeviceClass},
|
||||||
device_keys, device_profile, device_queue, device_session, fields, metrics,
|
device_keys, device_profile, device_queue,
|
||||||
|
error::Error as StorageError,
|
||||||
|
fields, metrics,
|
||||||
};
|
};
|
||||||
use crate::{codec, devaddr::get_random_dev_addr};
|
use crate::{codec, devaddr::get_random_dev_addr};
|
||||||
|
|
||||||
@ -532,12 +534,12 @@ impl DeviceService for Device {
|
|||||||
};
|
};
|
||||||
dp.reset_session_to_boot_params(&mut ds);
|
dp.reset_session_to_boot_params(&mut ds);
|
||||||
|
|
||||||
device_session::save(&ds).await.map_err(|e| e.status())?;
|
let mut device_changeset = device::DeviceChangeset {
|
||||||
|
device_session: Some(Some(ds.encode_to_vec())),
|
||||||
// Set device DevAddr.
|
dev_addr: Some(Some(dev_addr)),
|
||||||
device::set_dev_addr(dev_eui, dev_addr)
|
secondary_dev_addr: Some(None),
|
||||||
.await
|
..Default::default()
|
||||||
.map_err(|e| e.status())?;
|
};
|
||||||
|
|
||||||
// Flush queue (if configured).
|
// Flush queue (if configured).
|
||||||
if dp.flush_queue_on_activate {
|
if dp.flush_queue_on_activate {
|
||||||
@ -548,15 +550,15 @@ impl DeviceService for Device {
|
|||||||
|
|
||||||
// LoRaWAN 1.1 devices send a mac-command when changing to Class-C. Change the class here for LoRaWAN 1.0 devices.
|
// LoRaWAN 1.1 devices send a mac-command when changing to Class-C. Change the class here for LoRaWAN 1.0 devices.
|
||||||
if dp.supports_class_c && dp.mac_version.to_string().starts_with("1.0") {
|
if dp.supports_class_c && dp.mac_version.to_string().starts_with("1.0") {
|
||||||
let _ = device::set_enabled_class(&dev_eui, DeviceClass::C)
|
device_changeset.enabled_class = Some(DeviceClass::C);
|
||||||
.await
|
|
||||||
.map_err(|e| e.status())?;
|
|
||||||
} else {
|
} else {
|
||||||
let _ = device::set_enabled_class(&dev_eui, DeviceClass::A)
|
device_changeset.enabled_class = Some(DeviceClass::A);
|
||||||
.await
|
|
||||||
.map_err(|e| e.status())?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
device::partial_update(dev_eui, &device_changeset)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.status())?;
|
||||||
|
|
||||||
let mut resp = Response::new(());
|
let mut resp = Response::new(());
|
||||||
resp.metadata_mut()
|
resp.metadata_mut()
|
||||||
.insert("x-log-dev_eui", req_da.dev_eui.parse().unwrap());
|
.insert("x-log-dev_eui", req_da.dev_eui.parse().unwrap());
|
||||||
@ -581,9 +583,18 @@ impl DeviceService for Device {
|
|||||||
device_queue::flush_for_dev_eui(&dev_eui)
|
device_queue::flush_for_dev_eui(&dev_eui)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.status())?;
|
.map_err(|e| e.status())?;
|
||||||
device_session::delete(&dev_eui)
|
|
||||||
.await
|
device::partial_update(
|
||||||
.map_err(|e| e.status())?;
|
dev_eui,
|
||||||
|
&device::DeviceChangeset {
|
||||||
|
dev_addr: Some(None),
|
||||||
|
secondary_dev_addr: Some(None),
|
||||||
|
device_session: Some(None),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.status())?;
|
||||||
|
|
||||||
let mut resp = Response::new(());
|
let mut resp = Response::new(());
|
||||||
resp.metadata_mut()
|
resp.metadata_mut()
|
||||||
@ -606,19 +617,18 @@ impl DeviceService for Device {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let ds = match device_session::get(&dev_eui).await {
|
let d = device::get(&dev_eui).await.map_err(|e| e.status())?;
|
||||||
|
let ds = match d.get_device_session() {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => match e {
|
Err(StorageError::NotFound(_)) => {
|
||||||
Error::NotFound(_) => {
|
return Ok(Response::new(api::GetDeviceActivationResponse {
|
||||||
return Ok(Response::new(api::GetDeviceActivationResponse {
|
device_activation: None,
|
||||||
device_activation: None,
|
join_server_context: None,
|
||||||
join_server_context: None,
|
}));
|
||||||
}));
|
}
|
||||||
}
|
Err(e) => {
|
||||||
_ => {
|
return Err(e.status());
|
||||||
return Err(e.status());
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut resp = Response::new(api::GetDeviceActivationResponse {
|
let mut resp = Response::new(api::GetDeviceActivationResponse {
|
||||||
@ -1188,7 +1198,14 @@ impl DeviceService for Device {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let ds = device_session::get(&dev_eui).await.unwrap_or_default();
|
let d = device::get(&dev_eui).await.map_err(|e| e.status())?;
|
||||||
|
let ds = match d.get_device_session() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(StorageError::NotFound(_)) => Default::default(),
|
||||||
|
Err(e) => {
|
||||||
|
return Err(e.status());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let max_f_cnt_down_queue = device_queue::get_max_f_cnt_down(dev_eui)
|
let max_f_cnt_down_queue = device_queue::get_max_f_cnt_down(dev_eui)
|
||||||
.await
|
.await
|
||||||
@ -1210,7 +1227,7 @@ pub mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::api::auth::validator::RequestValidator;
|
use crate::api::auth::validator::RequestValidator;
|
||||||
use crate::api::auth::AuthID;
|
use crate::api::auth::AuthID;
|
||||||
use crate::storage::{application, tenant, user};
|
use crate::storage::{application, device, tenant, user};
|
||||||
use crate::test;
|
use crate::test;
|
||||||
use lrwn::NetID;
|
use lrwn::NetID;
|
||||||
|
|
||||||
@ -1526,12 +1543,21 @@ pub mod test {
|
|||||||
assert!(get_activation_resp.get_ref().device_activation.is_none());
|
assert!(get_activation_resp.get_ref().device_activation.is_none());
|
||||||
|
|
||||||
// test get activation with JS session-key ID.
|
// test get activation with JS session-key ID.
|
||||||
device_session::save(&internal::DeviceSession {
|
device::partial_update(
|
||||||
dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8],
|
dev.dev_eui,
|
||||||
dev_addr: vec![1, 2, 3, 4],
|
&device::DeviceChangeset {
|
||||||
js_session_key_id: vec![8, 7, 6, 5, 4, 3, 2, 1],
|
device_session: Some(Some(
|
||||||
..Default::default()
|
internal::DeviceSession {
|
||||||
})
|
dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8],
|
||||||
|
dev_addr: vec![1, 2, 3, 4],
|
||||||
|
js_session_key_id: vec![8, 7, 6, 5, 4, 3, 2, 1],
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.encode_to_vec(),
|
||||||
|
)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let get_activation_req = get_request(
|
let get_activation_req = get_request(
|
||||||
@ -1550,17 +1576,27 @@ pub mod test {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// test activation with AppSKey key-envelope.
|
// test activation with AppSKey key-envelope.
|
||||||
device_session::save(&internal::DeviceSession {
|
device::partial_update(
|
||||||
dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8],
|
dev.dev_eui,
|
||||||
dev_addr: vec![1, 2, 3, 4],
|
&device::DeviceChangeset {
|
||||||
app_s_key: Some(common::KeyEnvelope {
|
device_session: Some(Some(
|
||||||
kek_label: "test-key".into(),
|
internal::DeviceSession {
|
||||||
aes_key: vec![8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1],
|
dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8],
|
||||||
}),
|
dev_addr: vec![1, 2, 3, 4],
|
||||||
..Default::default()
|
app_s_key: Some(common::KeyEnvelope {
|
||||||
})
|
kek_label: "test-key".into(),
|
||||||
|
aes_key: vec![8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1],
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.encode_to_vec(),
|
||||||
|
)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let get_activation_req = get_request(
|
let get_activation_req = get_request(
|
||||||
&u.id,
|
&u.id,
|
||||||
api::GetDeviceActivationRequest {
|
api::GetDeviceActivationRequest {
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use prost::Message;
|
||||||
|
|
||||||
use crate::storage;
|
use crate::storage;
|
||||||
use crate::storage::device_session;
|
use crate::storage::device;
|
||||||
|
use chirpstack_api::internal;
|
||||||
use lrwn::EUI64;
|
use lrwn::EUI64;
|
||||||
|
|
||||||
pub async fn run(dev_eui: &EUI64) -> Result<()> {
|
pub async fn run(dev_eui: &EUI64) -> Result<()> {
|
||||||
storage::setup().await.context("Setup storage")?;
|
storage::setup().await.context("Setup storage")?;
|
||||||
let ds = device_session::get(dev_eui)
|
|
||||||
.await
|
let d = device::get(dev_eui).await.context("Get device")?;
|
||||||
.context("Get device-session")?;
|
let ds = match d.device_session {
|
||||||
|
Some(v) => internal::DeviceSession::decode(&mut Cursor::new(&v))
|
||||||
|
.context("Decode device-session")?,
|
||||||
|
None => return Err(anyhow!("No device-session")),
|
||||||
|
};
|
||||||
|
|
||||||
let json = serde_json::to_string_pretty(&ds)?;
|
let json = serde_json::to_string_pretty(&ds)?;
|
||||||
println!("{}", json);
|
println!("{}", json);
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use prost::Message;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use tracing::{debug, span, trace, warn, Instrument, Level};
|
use tracing::{debug, span, trace, warn, Instrument, Level};
|
||||||
|
|
||||||
@ -16,7 +17,7 @@ use crate::storage;
|
|||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
application,
|
application,
|
||||||
device::{self, DeviceClass},
|
device::{self, DeviceClass},
|
||||||
device_gateway, device_profile, device_queue, device_session, downlink_frame,
|
device_gateway, device_profile, device_queue, downlink_frame,
|
||||||
helpers::get_all_device_data,
|
helpers::get_all_device_data,
|
||||||
mac_command, relay, tenant,
|
mac_command, relay, tenant,
|
||||||
};
|
};
|
||||||
@ -216,13 +217,13 @@ impl Data {
|
|||||||
ctx.set_phy_payloads()?;
|
ctx.set_phy_payloads()?;
|
||||||
ctx.update_device_queue_item().await?;
|
ctx.update_device_queue_item().await?;
|
||||||
ctx.save_downlink_frame().await?;
|
ctx.save_downlink_frame().await?;
|
||||||
|
// Some mac-commands set their state (e.g. last requested) to the
|
||||||
|
// device-session.
|
||||||
|
ctx.save_device_session().await?;
|
||||||
if ctx._is_roaming() {
|
if ctx._is_roaming() {
|
||||||
ctx.save_device_session().await?;
|
|
||||||
ctx.send_downlink_frame_passive_roaming().await?;
|
ctx.send_downlink_frame_passive_roaming().await?;
|
||||||
ctx.handle_passive_roaming_tx_ack().await?;
|
ctx.handle_passive_roaming_tx_ack().await?;
|
||||||
} else {
|
} else {
|
||||||
// Some mac-commands set their state (e.g. last requested) to the device-session.
|
|
||||||
ctx.save_device_session().await?;
|
|
||||||
ctx.send_downlink_frame().await?;
|
ctx.send_downlink_frame().await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,8 +300,8 @@ impl Data {
|
|||||||
async fn _handle_schedule_next_queue_item(downlink_id: u32, dev: device::Device) -> Result<()> {
|
async fn _handle_schedule_next_queue_item(downlink_id: u32, dev: device::Device) -> Result<()> {
|
||||||
trace!("Handle schedule next-queue item flow");
|
trace!("Handle schedule next-queue item flow");
|
||||||
|
|
||||||
let (_, app, ten, dp) = get_all_device_data(dev.dev_eui).await?;
|
let (dev, app, ten, dp) = get_all_device_data(dev.dev_eui).await?;
|
||||||
let ds = device_session::get(&dev.dev_eui).await?;
|
let ds = dev.get_device_session()?;
|
||||||
let rc = region::get(&ds.region_config_id)?;
|
let rc = region::get(&ds.region_config_id)?;
|
||||||
let rn = config::get_region_network(&ds.region_config_id)?;
|
let rn = config::get_region_network(&ds.region_config_id)?;
|
||||||
let dev_gw = device_gateway::get_rx_info(&dev.dev_eui).await?;
|
let dev_gw = device_gateway::get_rx_info(&dev.dev_eui).await?;
|
||||||
@ -1030,9 +1031,15 @@ impl Data {
|
|||||||
async fn save_device_session(&self) -> Result<()> {
|
async fn save_device_session(&self) -> Result<()> {
|
||||||
trace!("Saving device-session");
|
trace!("Saving device-session");
|
||||||
|
|
||||||
device_session::save(&self.device_session)
|
device::partial_update(
|
||||||
.await
|
self.device.dev_eui,
|
||||||
.context("Save device-session")?;
|
&device::DeviceChangeset {
|
||||||
|
device_session: Some(Some(self.device_session.encode_to_vec())),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1524,7 +1531,8 @@ impl Data {
|
|||||||
|| rd.uplink_limit_reload_rate
|
|| rd.uplink_limit_reload_rate
|
||||||
!= device.relay_ed_uplink_limit_reload_rate as u32
|
!= device.relay_ed_uplink_limit_reload_rate as u32
|
||||||
{
|
{
|
||||||
let ds = match device_session::get(&device.dev_eui).await {
|
let d = device::get(&device.dev_eui).await?;
|
||||||
|
let ds = match d.get_device_session() {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// It is valid that the device is no longer activated.
|
// It is valid that the device is no longer activated.
|
||||||
@ -1583,7 +1591,8 @@ impl Data {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ds = match device_session::get(&device.dev_eui).await {
|
let d = device::get(&device.dev_eui).await?;
|
||||||
|
let ds = match d.get_device_session() {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// It is valid that the device is no longer activated.
|
// It is valid that the device is no longer activated.
|
||||||
@ -2412,9 +2421,14 @@ impl Data {
|
|||||||
let conf = config::get();
|
let conf = config::get();
|
||||||
let scheduler_run_after_ts = Utc::now() + conf.network.scheduler.class_c_lock_duration;
|
let scheduler_run_after_ts = Utc::now() + conf.network.scheduler.class_c_lock_duration;
|
||||||
|
|
||||||
self.device =
|
self.device = device::partial_update(
|
||||||
device::set_scheduler_run_after(&self.device.dev_eui, Some(scheduler_run_after_ts))
|
self.device.dev_eui,
|
||||||
.await?;
|
&device::DeviceChangeset {
|
||||||
|
scheduler_run_after: Some(Some(scheduler_run_after_ts)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -2465,9 +2479,14 @@ impl Data {
|
|||||||
// Update the device next scheduler run.
|
// Update the device next scheduler run.
|
||||||
let scheduler_run_after_ts = ping_slot_ts.to_date_time();
|
let scheduler_run_after_ts = ping_slot_ts.to_date_time();
|
||||||
trace!(scheduler_run_after = %scheduler_run_after_ts, "Setting scheduler_run_after for device");
|
trace!(scheduler_run_after = %scheduler_run_after_ts, "Setting scheduler_run_after for device");
|
||||||
self.device =
|
self.device = device::partial_update(
|
||||||
device::set_scheduler_run_after(&self.device.dev_eui, Some(scheduler_run_after_ts))
|
self.device.dev_eui,
|
||||||
.await?;
|
&device::DeviceChangeset {
|
||||||
|
scheduler_run_after: Some(Some(scheduler_run_after_ts)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Use default frequency if not configured. Based on the configured region this will use
|
// Use default frequency if not configured. Based on the configured region this will use
|
||||||
// channel-hopping.
|
// channel-hopping.
|
||||||
@ -3380,15 +3399,15 @@ mod test {
|
|||||||
dev_addr: Some(*dev_addr),
|
dev_addr: Some(*dev_addr),
|
||||||
application_id: app.id,
|
application_id: app.id,
|
||||||
device_profile_id: dp_ed.id,
|
device_profile_id: dp_ed.id,
|
||||||
..Default::default()
|
device_session: Some(
|
||||||
})
|
internal::DeviceSession {
|
||||||
.await
|
dev_addr: dev_addr.to_vec(),
|
||||||
.unwrap();
|
dev_eui: dev_eui.to_vec(),
|
||||||
|
nwk_s_enc_key: vec![0; 16],
|
||||||
let _ = device_session::save(&internal::DeviceSession {
|
..Default::default()
|
||||||
dev_addr: dev_addr.to_vec(),
|
}
|
||||||
dev_eui: dev_eui.to_vec(),
|
.encode_to_vec(),
|
||||||
nwk_s_enc_key: vec![0; 16],
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
|
use prost::Message;
|
||||||
use tracing::{error, info, span, trace, Instrument, Level};
|
use tracing::{error, info, span, trace, Instrument, Level};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@ -9,7 +10,9 @@ use crate::api::helpers::ToProto;
|
|||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
application,
|
application,
|
||||||
device::{self, DeviceClass},
|
device::{self, DeviceClass},
|
||||||
device_profile, device_queue, device_session, downlink_frame, multicast, tenant,
|
device_profile, device_queue, downlink_frame,
|
||||||
|
helpers::get_all_device_data,
|
||||||
|
multicast, tenant,
|
||||||
};
|
};
|
||||||
use crate::{integration, stream};
|
use crate::{integration, stream};
|
||||||
use chirpstack_api::{common, gw, integration as integration_pb, internal, stream as stream_pb};
|
use chirpstack_api::{common, gw, integration as integration_pb, internal, stream as stream_pb};
|
||||||
@ -88,10 +91,7 @@ impl TxAck {
|
|||||||
|
|
||||||
if ctx.is_error() {
|
if ctx.is_error() {
|
||||||
if ctx.is_application_payload() || ctx.is_mac_only_downlink() {
|
if ctx.is_application_payload() || ctx.is_mac_only_downlink() {
|
||||||
ctx.get_device().await?;
|
ctx.get_device_data().await?;
|
||||||
ctx.get_device_profile().await?;
|
|
||||||
ctx.get_application().await?;
|
|
||||||
ctx.get_tenant().await?;
|
|
||||||
ctx.log_tx_ack_error().await?;
|
ctx.log_tx_ack_error().await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,30 +99,28 @@ impl TxAck {
|
|||||||
ctx.delete_multicast_group_queue_item().await?;
|
ctx.delete_multicast_group_queue_item().await?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ctx.is_application_payload() {
|
if ctx.is_application_payload() || ctx.is_mac_only_downlink() {
|
||||||
ctx.get_device().await?;
|
ctx.get_device_data().await?;
|
||||||
ctx.get_device_profile().await?;
|
|
||||||
ctx.get_application().await?;
|
if ctx.is_application_payload() {
|
||||||
ctx.get_tenant().await?;
|
ctx.get_device_queue_item().await?;
|
||||||
ctx.get_device_session().await?;
|
if ctx.is_unconfirmed_downlink() {
|
||||||
ctx.get_device_queue_item().await?;
|
ctx.delete_device_queue_item().await?;
|
||||||
if ctx.is_unconfirmed_downlink() {
|
}
|
||||||
ctx.delete_device_queue_item().await?;
|
|
||||||
|
if ctx.is_confirmed_downlink() {
|
||||||
|
ctx.set_device_queue_item_pending().await?;
|
||||||
|
ctx.set_device_session_conf_f_cnt()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.increment_a_f_cnt_down()?;
|
||||||
|
ctx.send_tx_ack_event().await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.is_confirmed_downlink() {
|
if ctx.is_mac_only_downlink() {
|
||||||
ctx.set_device_queue_item_pending().await?;
|
ctx.increment_n_f_cnt_down()?;
|
||||||
ctx.set_device_session_conf_f_cnt()?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.increment_a_f_cnt_down()?;
|
|
||||||
ctx.save_device_session().await?;
|
|
||||||
ctx.send_tx_ack_event().await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.is_mac_only_downlink() {
|
|
||||||
ctx.get_device_session().await?;
|
|
||||||
ctx.increment_n_f_cnt_down()?;
|
|
||||||
ctx.save_device_session().await?;
|
ctx.save_device_session().await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,24 +138,22 @@ impl TxAck {
|
|||||||
|
|
||||||
async fn _handle_relayed(&mut self) -> Result<()> {
|
async fn _handle_relayed(&mut self) -> Result<()> {
|
||||||
self.get_phy_payload_relayed()?;
|
self.get_phy_payload_relayed()?;
|
||||||
|
self.get_device_data().await?; // the device-data of the relay
|
||||||
|
|
||||||
if self.is_error() {
|
if self.is_error() {
|
||||||
// We log the tx ack error under the relay as this is the device to which the downlink
|
// We log the tx ack error under the relay as this is the device to which the downlink
|
||||||
// is sent.
|
// is sent.
|
||||||
self.get_device().await?;
|
|
||||||
self.get_device_profile().await?;
|
|
||||||
self.get_application().await?;
|
|
||||||
self.get_tenant().await?;
|
|
||||||
self.log_tx_ack_error().await?;
|
self.log_tx_ack_error().await?;
|
||||||
} else {
|
} else {
|
||||||
// First handle the relay frame-counter increment.
|
// First handle the relay frame-counter increment.
|
||||||
self.get_device_session().await?;
|
|
||||||
self.increment_a_f_cnt_down()?;
|
self.increment_a_f_cnt_down()?;
|
||||||
self.save_device_session().await?;
|
self.save_device_session().await?;
|
||||||
|
|
||||||
|
// Get data of relayed device.
|
||||||
|
self.get_device_data_relayed().await?;
|
||||||
|
|
||||||
// Handle end-device frame-counter increment + queue item.
|
// Handle end-device frame-counter increment + queue item.
|
||||||
if self.is_application_payload_relayed() {
|
if self.is_application_payload_relayed() {
|
||||||
self.get_device_session_relayed().await?;
|
|
||||||
self.get_device_queue_item().await?;
|
self.get_device_queue_item().await?;
|
||||||
if self.is_unconfirmed_downlink_relayed() {
|
if self.is_unconfirmed_downlink_relayed() {
|
||||||
self.delete_device_queue_item().await?;
|
self.delete_device_queue_item().await?;
|
||||||
@ -172,13 +168,9 @@ impl TxAck {
|
|||||||
self.save_device_session_relayed().await?;
|
self.save_device_session_relayed().await?;
|
||||||
|
|
||||||
// Log tx ack event.
|
// Log tx ack event.
|
||||||
self.get_device_relayed().await?;
|
self.get_device_data_relayed().await?;
|
||||||
self.get_device_profile_relayed().await?;
|
|
||||||
self.get_application_relayed().await?;
|
|
||||||
self.get_tenant_relayed().await?;
|
|
||||||
self.send_tx_ack_event_relayed().await?;
|
self.send_tx_ack_event_relayed().await?;
|
||||||
} else if self.is_mac_only_downlink_relayed() {
|
} else if self.is_mac_only_downlink_relayed() {
|
||||||
self.get_device_session_relayed().await?;
|
|
||||||
self.increment_n_f_cnt_down_relayed()?;
|
self.increment_n_f_cnt_down_relayed()?;
|
||||||
self.save_device_session_relayed().await?;
|
self.save_device_session_relayed().await?;
|
||||||
}
|
}
|
||||||
@ -225,75 +217,31 @@ impl TxAck {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_device_session(&mut self) -> Result<()> {
|
async fn get_device_data(&mut self) -> Result<()> {
|
||||||
trace!("Getting device-session");
|
trace!("Getting device data");
|
||||||
let dev_eui = EUI64::from_slice(&self.downlink_frame.as_ref().unwrap().dev_eui)?;
|
let dev_eui = EUI64::from_slice(&self.downlink_frame.as_ref().unwrap().dev_eui)?;
|
||||||
self.device_session = Some(device_session::get(&dev_eui).await?);
|
let (dev, app, t, dp) = get_all_device_data(dev_eui).await?;
|
||||||
|
|
||||||
|
self.device_session = Some(dev.get_device_session()?);
|
||||||
|
self.tenant = Some(t);
|
||||||
|
self.application = Some(app);
|
||||||
|
self.device_profile = Some(dp);
|
||||||
|
self.device = Some(dev);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_device_session_relayed(&mut self) -> Result<()> {
|
async fn get_device_data_relayed(&mut self) -> Result<()> {
|
||||||
trace!("Getting relayed device-session");
|
trace!("Getting relayed device data");
|
||||||
let dev_eui = EUI64::from_slice(&self.downlink_frame.as_ref().unwrap().dev_eui_relayed)?;
|
let dev_eui = EUI64::from_slice(&self.downlink_frame.as_ref().unwrap().dev_eui_relayed)?;
|
||||||
self.device_session_relayed = Some(device_session::get(&dev_eui).await?);
|
let (dev, app, t, dp) = get_all_device_data(dev_eui).await?;
|
||||||
|
|
||||||
Ok(())
|
self.device_session_relayed = Some(dev.get_device_session()?);
|
||||||
}
|
self.tenant_relayed = Some(t);
|
||||||
|
self.application_relayed = Some(app);
|
||||||
|
self.device_profile_relayed = Some(dp);
|
||||||
|
self.device_relayed = Some(dev);
|
||||||
|
|
||||||
async fn get_device(&mut self) -> Result<()> {
|
|
||||||
trace!("Getting device");
|
|
||||||
let dev_eui = EUI64::from_slice(&self.downlink_frame.as_ref().unwrap().dev_eui)?;
|
|
||||||
self.device = Some(device::get(&dev_eui).await?);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_device_relayed(&mut self) -> Result<()> {
|
|
||||||
trace!("Getting relayed device");
|
|
||||||
let dev_eui = EUI64::from_slice(&self.downlink_frame.as_ref().unwrap().dev_eui_relayed)?;
|
|
||||||
self.device_relayed = Some(device::get(&dev_eui).await?);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_device_profile(&mut self) -> Result<()> {
|
|
||||||
trace!("Getting device-profile");
|
|
||||||
self.device_profile =
|
|
||||||
Some(device_profile::get(&self.device.as_ref().unwrap().device_profile_id).await?);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_device_profile_relayed(&mut self) -> Result<()> {
|
|
||||||
trace!("Getting relayed device-profile");
|
|
||||||
self.device_profile_relayed = Some(
|
|
||||||
device_profile::get(&self.device_relayed.as_ref().unwrap().device_profile_id).await?,
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_application(&mut self) -> Result<()> {
|
|
||||||
trace!("Getting application");
|
|
||||||
self.application =
|
|
||||||
Some(application::get(&self.device.as_ref().unwrap().application_id).await?);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_application_relayed(&mut self) -> Result<()> {
|
|
||||||
trace!("Getting relayed application");
|
|
||||||
self.application_relayed =
|
|
||||||
Some(application::get(&self.device_relayed.as_ref().unwrap().application_id).await?);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_tenant(&mut self) -> Result<()> {
|
|
||||||
trace!("Getting tenant");
|
|
||||||
self.tenant = Some(tenant::get(&self.application.as_ref().unwrap().tenant_id).await?);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_tenant_relayed(&mut self) -> Result<()> {
|
|
||||||
trace!("Getting relayed tenant");
|
|
||||||
self.tenant_relayed =
|
|
||||||
Some(tenant::get(&self.application_relayed.as_ref().unwrap().tenant_id).await?);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,13 +370,36 @@ impl TxAck {
|
|||||||
|
|
||||||
async fn save_device_session(&self) -> Result<()> {
|
async fn save_device_session(&self) -> Result<()> {
|
||||||
trace!("Saving device-session");
|
trace!("Saving device-session");
|
||||||
device_session::save(self.device_session.as_ref().unwrap()).await?;
|
|
||||||
|
device::partial_update(
|
||||||
|
self.device.as_ref().unwrap().dev_eui,
|
||||||
|
&device::DeviceChangeset {
|
||||||
|
device_session: Some(Some(self.device_session.as_ref().unwrap().encode_to_vec())),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn save_device_session_relayed(&self) -> Result<()> {
|
async fn save_device_session_relayed(&self) -> Result<()> {
|
||||||
trace!("Saving relayed device-session");
|
trace!("Saving relayed device-session");
|
||||||
device_session::save(self.device_session_relayed.as_ref().unwrap()).await?;
|
|
||||||
|
device::partial_update(
|
||||||
|
self.device.as_ref().unwrap().dev_eui,
|
||||||
|
&device::DeviceChangeset {
|
||||||
|
device_session: Some(Some(
|
||||||
|
self.device_session_relayed
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.encode_to_vec(),
|
||||||
|
)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use std::iter::zip;
|
use std::iter::zip;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use crate::storage::{device, device_session};
|
use crate::storage::device;
|
||||||
use chirpstack_api::internal;
|
use chirpstack_api::internal;
|
||||||
use lrwn::EUI64;
|
use lrwn::EUI64;
|
||||||
|
|
||||||
@ -59,12 +60,20 @@ pub async fn handle(
|
|||||||
if action == 0 {
|
if action == 0 {
|
||||||
for rd in &relay.devices {
|
for rd in &relay.devices {
|
||||||
if req_pl.ctrl_uplink_action.uplink_list_idx as u32 == rd.index {
|
if req_pl.ctrl_uplink_action.uplink_list_idx as u32 == rd.index {
|
||||||
let mut ds =
|
let dev_eui = EUI64::from_slice(&rd.dev_eui)?;
|
||||||
device_session::get(&EUI64::from_slice(&rd.dev_eui)?).await?;
|
let d = device::get(&dev_eui).await?;
|
||||||
|
let mut ds = d.get_device_session()?;
|
||||||
if let Some(relay) = &mut ds.relay {
|
if let Some(relay) = &mut ds.relay {
|
||||||
relay.w_f_cnt = ans_pl.w_fcnt;
|
relay.w_f_cnt = ans_pl.w_fcnt;
|
||||||
};
|
};
|
||||||
device_session::save(&ds).await?;
|
device::partial_update(
|
||||||
|
dev_eui,
|
||||||
|
&device::DeviceChangeset {
|
||||||
|
device_session: Some(Some(ds.encode_to_vec())),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if action == 1 {
|
} else if action == 1 {
|
||||||
@ -88,6 +97,7 @@ pub async fn handle(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::storage;
|
||||||
use crate::test;
|
use crate::test;
|
||||||
|
|
||||||
struct Test {
|
struct Test {
|
||||||
@ -104,6 +114,39 @@ mod test {
|
|||||||
async fn test_response() {
|
async fn test_response() {
|
||||||
let _handle = test::prepare().await;
|
let _handle = test::prepare().await;
|
||||||
|
|
||||||
|
let t = storage::tenant::create(storage::tenant::Tenant {
|
||||||
|
name: "test-tenant".into(),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let app = storage::application::create(storage::application::Application {
|
||||||
|
name: "test-app".into(),
|
||||||
|
tenant_id: t.id,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let dp = storage::device_profile::create(storage::device_profile::DeviceProfile {
|
||||||
|
name: "test-dp".into(),
|
||||||
|
tenant_id: t.id,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let dev = storage::device::create(storage::device::Device {
|
||||||
|
name: "test-dev".into(),
|
||||||
|
dev_eui: EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
|
application_id: app.id,
|
||||||
|
device_profile_id: dp.id,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "acked, nothing pending".into(),
|
name: "acked, nothing pending".into(),
|
||||||
@ -236,7 +279,15 @@ mod test {
|
|||||||
for tst in &tests {
|
for tst in &tests {
|
||||||
println!("> {}", tst.name);
|
println!("> {}", tst.name);
|
||||||
|
|
||||||
device_session::save(&tst.device_session_ed).await.unwrap();
|
device::partial_update(
|
||||||
|
dev.dev_eui,
|
||||||
|
&device::DeviceChangeset {
|
||||||
|
device_session: Some(Some(tst.device_session_ed.encode_to_vec())),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut ds = tst.device_session.clone();
|
let mut ds = tst.device_session.clone();
|
||||||
let resp = handle(
|
let resp = handle(
|
||||||
@ -254,10 +305,11 @@ mod test {
|
|||||||
assert_eq!(true, resp.unwrap().is_none());
|
assert_eq!(true, resp.unwrap().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
let ds =
|
let ds = device::get(&dev.dev_eui)
|
||||||
device_session::get(&EUI64::from_slice(&tst.device_session_ed.dev_eui).unwrap())
|
.await
|
||||||
.await
|
.unwrap()
|
||||||
.unwrap();
|
.get_device_session()
|
||||||
|
.unwrap();
|
||||||
assert_eq!(tst.expected_device_session_ed, ds);
|
assert_eq!(tst.expected_device_session_ed, ds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,15 +23,18 @@ pub async fn handle(
|
|||||||
if let lrwn::MACCommand::DevStatusAns(pl) = mac {
|
if let lrwn::MACCommand::DevStatusAns(pl) = mac {
|
||||||
info!(dev_eui = %dev.dev_eui, battery = pl.battery, margin = pl.margin, "DevStatusAns received");
|
info!(dev_eui = %dev.dev_eui, battery = pl.battery, margin = pl.margin, "DevStatusAns received");
|
||||||
|
|
||||||
device::set_status(
|
device::partial_update(
|
||||||
&dev.dev_eui,
|
dev.dev_eui,
|
||||||
pl.margin as i32,
|
&device::DeviceChangeset {
|
||||||
pl.battery == 0,
|
margin: Some(pl.margin as i32),
|
||||||
if pl.battery > 0 && pl.battery < 255 {
|
external_power_source: Some(pl.battery == 0),
|
||||||
let v: BigDecimal = ((pl.battery as f32) / 254.0 * 100.0).try_into()?;
|
battery_level: Some(if pl.battery > 0 && pl.battery < 255 {
|
||||||
Some(v.with_scale(2))
|
let v: BigDecimal = ((pl.battery as f32) / 254.0 * 100.0).try_into()?;
|
||||||
} else {
|
Some(v.with_scale(2))
|
||||||
None
|
} else {
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -10,11 +10,14 @@ pub async fn handle(
|
|||||||
.first()
|
.first()
|
||||||
.ok_or_else(|| anyhow!("Expected DeviceModeInd"))?;
|
.ok_or_else(|| anyhow!("Expected DeviceModeInd"))?;
|
||||||
if let lrwn::MACCommand::DeviceModeInd(pl) = mac {
|
if let lrwn::MACCommand::DeviceModeInd(pl) = mac {
|
||||||
device::set_enabled_class(
|
device::partial_update(
|
||||||
&dev.dev_eui,
|
dev.dev_eui,
|
||||||
match pl.class {
|
&device::DeviceChangeset {
|
||||||
lrwn::DeviceModeClass::ClassA => DeviceClass::A,
|
enabled_class: Some(match pl.class {
|
||||||
lrwn::DeviceModeClass::ClassC => DeviceClass::C,
|
lrwn::DeviceModeClass::ClassA => DeviceClass::A,
|
||||||
|
lrwn::DeviceModeClass::ClassC => DeviceClass::C,
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -119,6 +119,10 @@ pub struct DeviceChangeset {
|
|||||||
pub join_eui: Option<EUI64>,
|
pub join_eui: Option<EUI64>,
|
||||||
pub secondary_dev_addr: Option<Option<DevAddr>>,
|
pub secondary_dev_addr: Option<Option<DevAddr>>,
|
||||||
pub device_session: Option<Option<Vec<u8>>>,
|
pub device_session: Option<Option<Vec<u8>>>,
|
||||||
|
pub margin: Option<i32>,
|
||||||
|
pub external_power_source: Option<bool>,
|
||||||
|
pub battery_level: Option<Option<BigDecimal>>,
|
||||||
|
pub scheduler_run_after: Option<Option<DateTime<Utc>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device {
|
impl Device {
|
||||||
@ -128,6 +132,17 @@ impl Device {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_device_session(&mut self, ds: &internal::DeviceSession) {
|
||||||
|
self.device_session = Some(ds.encode_to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_device_session(&self) -> Result<internal::DeviceSession, Error> {
|
||||||
|
match &self.device_session {
|
||||||
|
None => Err(Error::NotFound(self.dev_eui.to_string())),
|
||||||
|
Some(v) => Ok(internal::DeviceSession::decode(&mut Cursor::new(&v))?),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Device {
|
impl Default for Device {
|
||||||
@ -276,16 +291,12 @@ pub async fn get_for_phypayload_and_incr_f_cnt_up(
|
|||||||
tx_dr: u8,
|
tx_dr: u8,
|
||||||
tx_ch: u8,
|
tx_ch: u8,
|
||||||
) -> Result<ValidationStatus, Error> {
|
) -> Result<ValidationStatus, Error> {
|
||||||
let mut dev_addr = lrwn::DevAddr::from_be_bytes([0x00, 0x00, 0x00, 0x00]);
|
|
||||||
let mut f_cnt_orig = 0;
|
|
||||||
|
|
||||||
// Get the dev_addr and original f_cnt.
|
// Get the dev_addr and original f_cnt.
|
||||||
if let lrwn::Payload::MACPayload(pl) = &phy.payload {
|
let (dev_addr, f_cnt_orig) = if let lrwn::Payload::MACPayload(pl) = &phy.payload {
|
||||||
dev_addr = pl.fhdr.devaddr;
|
(pl.fhdr.devaddr, pl.fhdr.f_cnt)
|
||||||
f_cnt_orig = pl.fhdr.f_cnt;
|
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::InvalidPayload("MacPayload".to_string()));
|
return Err(Error::InvalidPayload("MacPayload".to_string()));
|
||||||
}
|
};
|
||||||
|
|
||||||
let mut c = get_async_db_conn().await?;
|
let mut c = get_async_db_conn().await?;
|
||||||
|
|
||||||
@ -299,18 +310,36 @@ pub async fn get_for_phypayload_and_incr_f_cnt_up(
|
|||||||
.eq(&dev_addr)
|
.eq(&dev_addr)
|
||||||
.or(device::dsl::secondary_dev_addr.eq(&dev_addr)),
|
.or(device::dsl::secondary_dev_addr.eq(&dev_addr)),
|
||||||
)
|
)
|
||||||
|
.filter(device::dsl::is_disabled.eq(false))
|
||||||
.for_update()
|
.for_update()
|
||||||
.load(c)
|
.load(c)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if devices.is_empty() {
|
||||||
|
return Err(Error::NotFound(dev_addr.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sessions: Vec<(EUI64, internal::DeviceSession)> = Vec::new();
|
||||||
|
|
||||||
for d in &devices {
|
for d in &devices {
|
||||||
if d.1.is_none() {
|
if d.1.is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ds =
|
let ds =
|
||||||
internal::DeviceSession::decode(&mut Cursor::new(d.1.as_ref().unwrap()))?;
|
internal::DeviceSession::decode(&mut Cursor::new(d.1.as_ref().unwrap()))?;
|
||||||
|
|
||||||
|
if let Some(pending_ds) = &ds.pending_rejoin_device_session {
|
||||||
|
sessions.push((d.0, *pending_ds.clone()));
|
||||||
|
}
|
||||||
|
sessions.push((d.0, ds));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (dev_eui, ds) in &mut sessions {
|
||||||
|
if ds.dev_addr != dev_addr.to_vec() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the full 32bit frame-counter.
|
// Get the full 32bit frame-counter.
|
||||||
let full_f_cnt = get_full_f_cnt_up(ds.f_cnt_up, f_cnt_orig);
|
let full_f_cnt = get_full_f_cnt_up(ds.f_cnt_up, f_cnt_orig);
|
||||||
let f_nwk_s_int_key = lrwn::AES128Key::from_slice(&ds.f_nwk_s_int_key)?;
|
let f_nwk_s_int_key = lrwn::AES128Key::from_slice(&ds.f_nwk_s_int_key)?;
|
||||||
@ -354,7 +383,7 @@ pub async fn get_for_phypayload_and_incr_f_cnt_up(
|
|||||||
if let Some(relay) = &ds.relay {
|
if let Some(relay) = &ds.relay {
|
||||||
if !relayed && relay.ed_relay_only {
|
if !relayed && relay.ed_relay_only {
|
||||||
info!(
|
info!(
|
||||||
dev_eui = %d.0,
|
dev_eui = %dev_eui,
|
||||||
"Only communication through relay is allowed"
|
"Only communication through relay is allowed"
|
||||||
);
|
);
|
||||||
return Err(Error::NotFound(dev_addr.to_string()));
|
return Err(Error::NotFound(dev_addr.to_string()));
|
||||||
@ -367,7 +396,7 @@ pub async fn get_for_phypayload_and_incr_f_cnt_up(
|
|||||||
let ds_f_cnt_up = ds.f_cnt_up;
|
let ds_f_cnt_up = ds.f_cnt_up;
|
||||||
ds.f_cnt_up = full_f_cnt + 1;
|
ds.f_cnt_up = full_f_cnt + 1;
|
||||||
|
|
||||||
let _ = diesel::update(device::dsl::device.find(&d.0))
|
let _ = diesel::update(device::dsl::device.find(*dev_eui))
|
||||||
.set(device::device_session.eq(&ds.encode_to_vec()))
|
.set(device::device_session.eq(&ds.encode_to_vec()))
|
||||||
.execute(c)
|
.execute(c)
|
||||||
.await?;
|
.await?;
|
||||||
@ -375,16 +404,16 @@ pub async fn get_for_phypayload_and_incr_f_cnt_up(
|
|||||||
// We do return the device-session with original frame-counter
|
// We do return the device-session with original frame-counter
|
||||||
ds.f_cnt_up = ds_f_cnt_up;
|
ds.f_cnt_up = ds_f_cnt_up;
|
||||||
|
|
||||||
return Ok(ValidationStatus::Ok(full_f_cnt, ds));
|
return Ok(ValidationStatus::Ok(full_f_cnt, ds.clone()));
|
||||||
} else if ds.skip_f_cnt_check {
|
} else if ds.skip_f_cnt_check {
|
||||||
// re-transmission or frame-counter reset
|
// re-transmission or frame-counter reset
|
||||||
ds.f_cnt_up = 0;
|
ds.f_cnt_up = 0;
|
||||||
return Ok(ValidationStatus::Ok(full_f_cnt, ds));
|
return Ok(ValidationStatus::Ok(full_f_cnt, ds.clone()));
|
||||||
} else if full_f_cnt == (ds.f_cnt_up - 1) {
|
} else if full_f_cnt == (ds.f_cnt_up - 1) {
|
||||||
// re-transmission, the frame-counter did not increment
|
// re-transmission, the frame-counter did not increment
|
||||||
return Ok(ValidationStatus::Retransmission(full_f_cnt, ds));
|
return Ok(ValidationStatus::Retransmission(full_f_cnt, ds.clone()));
|
||||||
} else {
|
} else {
|
||||||
return Ok(ValidationStatus::Reset(full_f_cnt, ds));
|
return Ok(ValidationStatus::Reset(full_f_cnt, ds.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,6 +429,74 @@ pub async fn get_for_phypayload_and_incr_f_cnt_up(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_for_phypayload(
|
||||||
|
phy: &mut lrwn::PhyPayload,
|
||||||
|
tx_dr: u8,
|
||||||
|
tx_ch: u8,
|
||||||
|
) -> Result<internal::DeviceSession, Error> {
|
||||||
|
// Get the dev_addr and original f_cnt.
|
||||||
|
let (dev_addr, f_cnt_orig) = if let lrwn::Payload::MACPayload(pl) = &phy.payload {
|
||||||
|
(pl.fhdr.devaddr, pl.fhdr.f_cnt)
|
||||||
|
} else {
|
||||||
|
return Err(Error::InvalidPayload("MacPayload".to_string()));
|
||||||
|
};
|
||||||
|
|
||||||
|
let devices: Vec<(EUI64, Option<Vec<u8>>)> = device::dsl::device
|
||||||
|
.select((device::dev_eui, device::device_session))
|
||||||
|
.filter(
|
||||||
|
device::dsl::dev_addr
|
||||||
|
.eq(&dev_addr)
|
||||||
|
.or(device::dsl::secondary_dev_addr.eq(&dev_addr)),
|
||||||
|
)
|
||||||
|
.for_update()
|
||||||
|
.load(&mut get_async_db_conn().await?)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if devices.is_empty() {
|
||||||
|
return Err(Error::NotFound(dev_addr.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for d in &devices {
|
||||||
|
if d.1.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ds = internal::DeviceSession::decode(&mut Cursor::new(&d.1.as_ref().unwrap()))?;
|
||||||
|
|
||||||
|
// Get the full 32bit frame-counter.
|
||||||
|
let full_f_cnt = get_full_f_cnt_up(ds.f_cnt_up, f_cnt_orig);
|
||||||
|
let f_nwk_s_int_key = lrwn::AES128Key::from_slice(&ds.f_nwk_s_int_key)?;
|
||||||
|
let s_nwk_s_int_key = lrwn::AES128Key::from_slice(&ds.s_nwk_s_int_key)?;
|
||||||
|
|
||||||
|
// Set the full f_cnt
|
||||||
|
if let lrwn::Payload::MACPayload(pl) = &mut phy.payload {
|
||||||
|
pl.fhdr.f_cnt = full_f_cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mic_ok = phy
|
||||||
|
.validate_uplink_data_mic(
|
||||||
|
ds.mac_version().from_proto(),
|
||||||
|
ds.conf_f_cnt,
|
||||||
|
tx_dr,
|
||||||
|
tx_ch,
|
||||||
|
&f_nwk_s_int_key,
|
||||||
|
&s_nwk_s_int_key,
|
||||||
|
)
|
||||||
|
.context("Validate MIC")?;
|
||||||
|
|
||||||
|
if mic_ok && full_f_cnt >= ds.f_cnt_up {
|
||||||
|
return Ok(ds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore the original f_cnt.
|
||||||
|
if let lrwn::Payload::MACPayload(pl) = &mut phy.payload {
|
||||||
|
pl.fhdr.f_cnt = f_cnt_orig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::InvalidMIC)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn update(d: Device) -> Result<Device, Error> {
|
pub async fn update(d: Device) -> Result<Device, Error> {
|
||||||
d.validate()?;
|
d.validate()?;
|
||||||
|
|
||||||
@ -434,83 +531,6 @@ pub async fn partial_update(dev_eui: EUI64, d: &DeviceChangeset) -> Result<Devic
|
|||||||
Ok(d)
|
Ok(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_enabled_class(dev_eui: &EUI64, mode: DeviceClass) -> Result<Device, Error> {
|
|
||||||
let d: Device = diesel::update(device::dsl::device.find(&dev_eui))
|
|
||||||
.set(device::enabled_class.eq(&mode))
|
|
||||||
.get_result(&mut get_async_db_conn().await?)
|
|
||||||
.await
|
|
||||||
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
|
|
||||||
info!(dev_eui = %dev_eui, enabled_class = %mode, "Enabled class updated");
|
|
||||||
Ok(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn set_join_eui(dev_eui: EUI64, join_eui: EUI64) -> Result<Device, Error> {
|
|
||||||
let d: Device = diesel::update(device::dsl::device.find(&dev_eui))
|
|
||||||
.set(device::join_eui.eq(&join_eui))
|
|
||||||
.get_result(&mut get_async_db_conn().await?)
|
|
||||||
.await
|
|
||||||
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
|
|
||||||
info!(dev_eui = %dev_eui, join_eui = %join_eui, "Updated JoinEUI");
|
|
||||||
Ok(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn set_dev_addr(dev_eui: EUI64, dev_addr: DevAddr) -> Result<Device, Error> {
|
|
||||||
let d: Device = diesel::update(device::dsl::device.find(&dev_eui))
|
|
||||||
.set(device::dev_addr.eq(&dev_addr))
|
|
||||||
.get_result(&mut get_async_db_conn().await?)
|
|
||||||
.await
|
|
||||||
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
|
|
||||||
info!(dev_eui = %dev_eui, dev_addr = %dev_addr, "Updated DevAddr");
|
|
||||||
Ok(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case the current_ts has been updated during the last device get and calling this update
|
|
||||||
// function, this will return a NotFound error. The purpose of this error is to catch concurrent
|
|
||||||
// scheduling, e.g. Class-A downlink and Class-B/C downlink. In such case we want to terminate one
|
|
||||||
// of the downlinks.
|
|
||||||
pub async fn set_scheduler_run_after(
|
|
||||||
dev_eui: &EUI64,
|
|
||||||
new_ts: Option<DateTime<Utc>>,
|
|
||||||
) -> Result<Device, Error> {
|
|
||||||
diesel::update(device::dsl::device.find(&dev_eui))
|
|
||||||
.set(device::scheduler_run_after.eq(&new_ts))
|
|
||||||
.get_result(&mut get_async_db_conn().await?)
|
|
||||||
.await
|
|
||||||
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn set_last_seen_dr(dev_eui: &EUI64, dr: u8) -> Result<Device, Error> {
|
|
||||||
let d: Device = diesel::update(device::dsl::device.find(&dev_eui))
|
|
||||||
.set((
|
|
||||||
device::last_seen_at.eq(Utc::now()),
|
|
||||||
device::dr.eq(dr as i16),
|
|
||||||
))
|
|
||||||
.get_result(&mut get_async_db_conn().await?)
|
|
||||||
.await
|
|
||||||
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
|
|
||||||
info!(dev_eui = %dev_eui, dr = dr, "Data-rate updated");
|
|
||||||
Ok(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn set_status(
|
|
||||||
dev_eui: &EUI64,
|
|
||||||
margin: i32,
|
|
||||||
external_power_source: bool,
|
|
||||||
battery_level: Option<BigDecimal>,
|
|
||||||
) -> Result<Device, Error> {
|
|
||||||
let d: Device = diesel::update(device::dsl::device.find(&dev_eui))
|
|
||||||
.set((
|
|
||||||
device::margin.eq(Some(margin)),
|
|
||||||
device::external_power_source.eq(external_power_source),
|
|
||||||
device::battery_level.eq(battery_level),
|
|
||||||
))
|
|
||||||
.get_result(&mut get_async_db_conn().await?)
|
|
||||||
.await
|
|
||||||
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
|
|
||||||
info!(dev_eui = %dev_eui, "Device status updated");
|
|
||||||
Ok(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn delete(dev_eui: &EUI64) -> Result<(), Error> {
|
pub async fn delete(dev_eui: &EUI64) -> Result<(), Error> {
|
||||||
let ra = diesel::delete(device::dsl::device.find(&dev_eui))
|
let ra = diesel::delete(device::dsl::device.find(&dev_eui))
|
||||||
.execute(&mut get_async_db_conn().await?)
|
.execute(&mut get_async_db_conn().await?)
|
||||||
@ -733,6 +753,7 @@ pub mod test {
|
|||||||
use crate::storage;
|
use crate::storage;
|
||||||
use crate::storage::device_queue;
|
use crate::storage::device_queue;
|
||||||
use crate::test;
|
use crate::test;
|
||||||
|
use lrwn::AES128Key;
|
||||||
|
|
||||||
struct FilterTest<'a> {
|
struct FilterTest<'a> {
|
||||||
filters: Filters,
|
filters: Filters,
|
||||||
@ -893,13 +914,37 @@ pub mod test {
|
|||||||
assert_eq!(0, res.len());
|
assert_eq!(0, res.len());
|
||||||
|
|
||||||
// device in Class-B.
|
// device in Class-B.
|
||||||
let d = set_enabled_class(&d.dev_eui, DeviceClass::B).await.unwrap();
|
let d = partial_update(
|
||||||
|
d.dev_eui,
|
||||||
|
&DeviceChangeset {
|
||||||
|
enabled_class: Some(DeviceClass::B),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let res = get_with_class_b_c_queue_items(10).await.unwrap();
|
let res = get_with_class_b_c_queue_items(10).await.unwrap();
|
||||||
let d = set_scheduler_run_after(&d.dev_eui, None).await.unwrap();
|
let d = partial_update(
|
||||||
|
d.dev_eui,
|
||||||
|
&DeviceChangeset {
|
||||||
|
scheduler_run_after: Some(None),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
assert_eq!(1, res.len());
|
assert_eq!(1, res.len());
|
||||||
|
|
||||||
// device in Class-C
|
// device in Class-C
|
||||||
let d = set_enabled_class(&d.dev_eui, DeviceClass::C).await.unwrap();
|
let d = partial_update(
|
||||||
|
d.dev_eui,
|
||||||
|
&DeviceChangeset {
|
||||||
|
enabled_class: Some(DeviceClass::C),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let res = get_with_class_b_c_queue_items(10).await.unwrap();
|
let res = get_with_class_b_c_queue_items(10).await.unwrap();
|
||||||
assert_eq!(1, res.len());
|
assert_eq!(1, res.len());
|
||||||
|
|
||||||
@ -909,7 +954,15 @@ pub mod test {
|
|||||||
assert_eq!(0, res.len());
|
assert_eq!(0, res.len());
|
||||||
|
|
||||||
// device in class C / downlink is pending.
|
// device in class C / downlink is pending.
|
||||||
let _ = set_scheduler_run_after(&d.dev_eui, None).await.unwrap();
|
let _ = partial_update(
|
||||||
|
d.dev_eui,
|
||||||
|
&DeviceChangeset {
|
||||||
|
scheduler_run_after: Some(None),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
qi.is_pending = true;
|
qi.is_pending = true;
|
||||||
qi.timeout_after = Some(Utc::now() + Duration::seconds(10));
|
qi.timeout_after = Some(Utc::now() + Duration::seconds(10));
|
||||||
qi = device_queue::update_item(qi).await.unwrap();
|
qi = device_queue::update_item(qi).await.unwrap();
|
||||||
@ -923,4 +976,372 @@ pub mod test {
|
|||||||
let res = get_with_class_b_c_queue_items(10).await.unwrap();
|
let res = get_with_class_b_c_queue_items(10).await.unwrap();
|
||||||
assert_eq!(1, res.len());
|
assert_eq!(1, res.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_full_f_cnt_up() {
|
||||||
|
// server, device, expected
|
||||||
|
let tests = vec![
|
||||||
|
(1, 1, 1), // frame-counter is as expected
|
||||||
|
(1 << 16, 0, 1 << 16), // frame-counter is as expected
|
||||||
|
((1 << 16) + 1, 1, (1 << 16) + 1), // frame-counter is as expected
|
||||||
|
(0, 1, 1), // one frame packet-loss
|
||||||
|
((1 << 16) + 1, 2, (1 << 16) + 2), // one frame packet-loss
|
||||||
|
(2, 1, 1), // re-transmission of previous frame
|
||||||
|
((1 << 16) + 1, 0, (1 << 16)), // re-transmission of previous frame
|
||||||
|
((1 << 16), (1 << 16) - 1, (1 << 16) - 1), // re-transmission of previous frame
|
||||||
|
(u32::MAX, 0, 0), // 32bit frame-counter rollover
|
||||||
|
];
|
||||||
|
|
||||||
|
for (i, tst) in tests.iter().enumerate() {
|
||||||
|
let out = get_full_f_cnt_up(tst.0, tst.1);
|
||||||
|
assert_eq!(tst.2, out, "Test: {}, expected: {}, got: {}", i, tst.2, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_device_session() {
|
||||||
|
let _guard = test::prepare().await;
|
||||||
|
|
||||||
|
let device_sessions = vec![
|
||||||
|
internal::DeviceSession {
|
||||||
|
dev_addr: vec![0x01, 0x02, 0x03, 0x04],
|
||||||
|
dev_eui: vec![0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
|
||||||
|
s_nwk_s_int_key: vec![
|
||||||
|
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||||
|
0x01, 0x01, 0x01,
|
||||||
|
],
|
||||||
|
f_nwk_s_int_key: vec![
|
||||||
|
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||||
|
0x01, 0x01, 0x01,
|
||||||
|
],
|
||||||
|
nwk_s_enc_key: vec![
|
||||||
|
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||||
|
0x01, 0x01, 0x01,
|
||||||
|
],
|
||||||
|
f_cnt_up: 100,
|
||||||
|
skip_f_cnt_check: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
internal::DeviceSession {
|
||||||
|
dev_addr: vec![0x01, 0x02, 0x03, 0x04],
|
||||||
|
dev_eui: vec![0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02],
|
||||||
|
s_nwk_s_int_key: vec![
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02, 0x02,
|
||||||
|
],
|
||||||
|
f_nwk_s_int_key: vec![
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02, 0x02,
|
||||||
|
],
|
||||||
|
nwk_s_enc_key: vec![
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02, 0x02,
|
||||||
|
],
|
||||||
|
f_cnt_up: 200,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
internal::DeviceSession {
|
||||||
|
dev_addr: vec![0x01, 0x02, 0x03, 0x04],
|
||||||
|
dev_eui: vec![0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03],
|
||||||
|
s_nwk_s_int_key: vec![
|
||||||
|
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
||||||
|
0x03, 0x03, 0x03,
|
||||||
|
],
|
||||||
|
f_nwk_s_int_key: vec![
|
||||||
|
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
||||||
|
0x03, 0x03, 0x03,
|
||||||
|
],
|
||||||
|
nwk_s_enc_key: vec![
|
||||||
|
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
||||||
|
0x03, 0x03, 0x03,
|
||||||
|
],
|
||||||
|
f_cnt_up: 300,
|
||||||
|
pending_rejoin_device_session: Some(Box::new(internal::DeviceSession {
|
||||||
|
dev_addr: vec![0x04, 0x03, 0x02, 0x01],
|
||||||
|
dev_eui: vec![0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03],
|
||||||
|
s_nwk_s_int_key: vec![
|
||||||
|
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||||
|
0x04, 0x04, 0x04, 0x04,
|
||||||
|
],
|
||||||
|
f_nwk_s_int_key: vec![
|
||||||
|
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||||
|
0x04, 0x04, 0x04, 0x04,
|
||||||
|
],
|
||||||
|
nwk_s_enc_key: vec![
|
||||||
|
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||||
|
0x04, 0x04, 0x04, 0x04,
|
||||||
|
],
|
||||||
|
f_cnt_up: 0,
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
internal::DeviceSession {
|
||||||
|
dev_addr: vec![0x01, 0x02, 0x03, 0x04],
|
||||||
|
dev_eui: vec![0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05],
|
||||||
|
s_nwk_s_int_key: vec![
|
||||||
|
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
||||||
|
0x05, 0x05, 0x05,
|
||||||
|
],
|
||||||
|
f_nwk_s_int_key: vec![
|
||||||
|
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
||||||
|
0x05, 0x05, 0x05,
|
||||||
|
],
|
||||||
|
nwk_s_enc_key: vec![
|
||||||
|
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
||||||
|
0x05, 0x05, 0x05,
|
||||||
|
],
|
||||||
|
f_cnt_up: (1 << 16) + 1,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let t = storage::tenant::create(storage::tenant::Tenant {
|
||||||
|
name: "test-tenant".into(),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let dp = storage::device_profile::create(storage::device_profile::DeviceProfile {
|
||||||
|
name: "test-dp".into(),
|
||||||
|
tenant_id: t.id,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let app = storage::application::create(storage::application::Application {
|
||||||
|
name: "test-app".into(),
|
||||||
|
tenant_id: t.id,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for ds in &device_sessions {
|
||||||
|
create(Device {
|
||||||
|
dev_eui: EUI64::from_slice(&ds.dev_eui).unwrap(),
|
||||||
|
dev_addr: Some(DevAddr::from_slice(&ds.dev_addr).unwrap()),
|
||||||
|
secondary_dev_addr: ds
|
||||||
|
.pending_rejoin_device_session
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| DevAddr::from_slice(&v.dev_addr).unwrap()),
|
||||||
|
name: hex::encode(&ds.dev_eui),
|
||||||
|
application_id: app.id,
|
||||||
|
device_profile_id: dp.id,
|
||||||
|
device_session: Some(ds.encode_to_vec()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Test {
|
||||||
|
name: String,
|
||||||
|
dev_addr: DevAddr,
|
||||||
|
s_nwk_s_int_key: AES128Key,
|
||||||
|
f_nwk_s_int_key: AES128Key,
|
||||||
|
f_cnt: u32,
|
||||||
|
expected_retransmission: bool,
|
||||||
|
expected_reset: bool,
|
||||||
|
expected_dev_eui: EUI64,
|
||||||
|
expected_fcnt_up: u32,
|
||||||
|
expected_error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let tests = vec![
|
||||||
|
Test {
|
||||||
|
name: "matching dev_eui 0101010101010101".to_string(),
|
||||||
|
dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]),
|
||||||
|
f_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].f_nwk_s_int_key)
|
||||||
|
.unwrap(),
|
||||||
|
s_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].s_nwk_s_int_key)
|
||||||
|
.unwrap(),
|
||||||
|
f_cnt: device_sessions[0].f_cnt_up,
|
||||||
|
expected_retransmission: false,
|
||||||
|
expected_reset: false,
|
||||||
|
expected_fcnt_up: device_sessions[0].f_cnt_up,
|
||||||
|
expected_dev_eui: EUI64::from_slice(&device_sessions[0].dev_eui).unwrap(),
|
||||||
|
expected_error: None,
|
||||||
|
},
|
||||||
|
Test {
|
||||||
|
name: "matching dev_eui 0202020202020202".to_string(),
|
||||||
|
dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]),
|
||||||
|
f_nwk_s_int_key: AES128Key::from_slice(&device_sessions[1].f_nwk_s_int_key)
|
||||||
|
.unwrap(),
|
||||||
|
s_nwk_s_int_key: AES128Key::from_slice(&device_sessions[1].s_nwk_s_int_key)
|
||||||
|
.unwrap(),
|
||||||
|
f_cnt: device_sessions[1].f_cnt_up,
|
||||||
|
expected_retransmission: false,
|
||||||
|
expected_reset: false,
|
||||||
|
expected_fcnt_up: device_sessions[1].f_cnt_up,
|
||||||
|
expected_dev_eui: EUI64::from_slice(&device_sessions[1].dev_eui).unwrap(),
|
||||||
|
expected_error: None,
|
||||||
|
},
|
||||||
|
Test {
|
||||||
|
name: "matching dev_eui 0101010101010101 with frame-counter reset".to_string(),
|
||||||
|
dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]),
|
||||||
|
f_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].f_nwk_s_int_key)
|
||||||
|
.unwrap(),
|
||||||
|
s_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].s_nwk_s_int_key)
|
||||||
|
.unwrap(),
|
||||||
|
f_cnt: 0,
|
||||||
|
expected_retransmission: false,
|
||||||
|
expected_reset: false,
|
||||||
|
expected_fcnt_up: 0,
|
||||||
|
expected_dev_eui: EUI64::from_slice(&device_sessions[0].dev_eui).unwrap(),
|
||||||
|
expected_error: None,
|
||||||
|
},
|
||||||
|
Test {
|
||||||
|
name: "matching dev_eui 0202020202020202 with invalid frame-counter".to_string(),
|
||||||
|
dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]),
|
||||||
|
f_nwk_s_int_key: AES128Key::from_slice(&device_sessions[1].f_nwk_s_int_key)
|
||||||
|
.unwrap(),
|
||||||
|
s_nwk_s_int_key: AES128Key::from_slice(&device_sessions[1].s_nwk_s_int_key)
|
||||||
|
.unwrap(),
|
||||||
|
f_cnt: 0,
|
||||||
|
expected_reset: true,
|
||||||
|
expected_dev_eui: EUI64::from_slice(&device_sessions[1].dev_eui).unwrap(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Test {
|
||||||
|
name: "invalid DevAddr".to_string(),
|
||||||
|
dev_addr: DevAddr::from_be_bytes([0x01, 0x01, 0x01, 0x01]),
|
||||||
|
f_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].f_nwk_s_int_key)
|
||||||
|
.unwrap(),
|
||||||
|
s_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].s_nwk_s_int_key)
|
||||||
|
.unwrap(),
|
||||||
|
f_cnt: device_sessions[0].f_cnt_up,
|
||||||
|
expected_error: Some("Object does not exist (id: 01010101)".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Test {
|
||||||
|
name: "invalid nwk_s_key".to_string(),
|
||||||
|
dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]),
|
||||||
|
f_nwk_s_int_key: AES128Key::from_bytes([
|
||||||
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||||
|
]),
|
||||||
|
s_nwk_s_int_key: AES128Key::from_bytes([
|
||||||
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||||
|
]),
|
||||||
|
f_cnt: device_sessions[0].f_cnt_up,
|
||||||
|
expected_error: Some("Invalid MIC".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Test {
|
||||||
|
name: "matching pending rejoin device-session".to_string(),
|
||||||
|
dev_addr: DevAddr::from_be_bytes([0x04, 0x03, 0x02, 0x01]),
|
||||||
|
f_nwk_s_int_key: AES128Key::from_bytes([
|
||||||
|
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||||
|
0x04, 0x04, 0x04,
|
||||||
|
]),
|
||||||
|
s_nwk_s_int_key: AES128Key::from_bytes([
|
||||||
|
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||||
|
0x04, 0x04, 0x04,
|
||||||
|
]),
|
||||||
|
f_cnt: 0,
|
||||||
|
expected_dev_eui: EUI64::from_slice(&device_sessions[2].dev_eui).unwrap(),
|
||||||
|
expected_fcnt_up: 0,
|
||||||
|
expected_retransmission: false,
|
||||||
|
expected_error: None,
|
||||||
|
expected_reset: false,
|
||||||
|
},
|
||||||
|
Test {
|
||||||
|
name: "frame-counter rollover (16lsb)".to_string(),
|
||||||
|
dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]),
|
||||||
|
f_nwk_s_int_key: AES128Key::from_bytes([
|
||||||
|
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
||||||
|
0x05, 0x05, 0x05,
|
||||||
|
]),
|
||||||
|
s_nwk_s_int_key: AES128Key::from_bytes([
|
||||||
|
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
||||||
|
0x05, 0x05, 0x05,
|
||||||
|
]),
|
||||||
|
f_cnt: (1 << 16) + 11,
|
||||||
|
expected_dev_eui: EUI64::from_slice(&device_sessions[3].dev_eui).unwrap(),
|
||||||
|
expected_fcnt_up: (1 << 16) + 11,
|
||||||
|
expected_retransmission: false,
|
||||||
|
expected_error: None,
|
||||||
|
expected_reset: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for tst in &tests {
|
||||||
|
println!("> {}", tst.name);
|
||||||
|
let mut phy = lrwn::PhyPayload {
|
||||||
|
mhdr: lrwn::MHDR {
|
||||||
|
m_type: lrwn::MType::UnconfirmedDataUp,
|
||||||
|
major: lrwn::Major::LoRaWANR1,
|
||||||
|
},
|
||||||
|
payload: lrwn::Payload::MACPayload(lrwn::MACPayload {
|
||||||
|
fhdr: lrwn::FHDR {
|
||||||
|
devaddr: tst.dev_addr,
|
||||||
|
f_ctrl: lrwn::FCtrl::default(),
|
||||||
|
f_cnt: tst.f_cnt,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
mic: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
phy.set_uplink_data_mic(
|
||||||
|
lrwn::MACVersion::LoRaWAN1_0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&tst.f_nwk_s_int_key,
|
||||||
|
&tst.s_nwk_s_int_key,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Truncate to 16LSB (as it would be transmitted over the air).
|
||||||
|
if let lrwn::Payload::MACPayload(pl) = &mut phy.payload {
|
||||||
|
pl.fhdr.f_cnt = tst.f_cnt % (1 << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ds_res = get_for_phypayload_and_incr_f_cnt_up(false, &mut phy, 0, 0).await;
|
||||||
|
if tst.expected_error.is_some() {
|
||||||
|
assert_eq!(true, ds_res.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
tst.expected_error.as_ref().unwrap(),
|
||||||
|
&ds_res.err().unwrap().to_string()
|
||||||
|
);
|
||||||
|
if let lrwn::Payload::MACPayload(pl) = &phy.payload {
|
||||||
|
assert_eq!(tst.f_cnt, pl.fhdr.f_cnt);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let ds = ds_res.unwrap();
|
||||||
|
|
||||||
|
// Validate that the f_cnt of the PhyPayload was set to the full frame-counter.
|
||||||
|
if let lrwn::Payload::MACPayload(pl) = &phy.payload {
|
||||||
|
assert_eq!(tst.expected_fcnt_up, pl.fhdr.f_cnt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let ValidationStatus::Ok(full_f_cnt, ds) = ds {
|
||||||
|
assert_eq!(false, tst.expected_retransmission);
|
||||||
|
assert_eq!(
|
||||||
|
tst.expected_dev_eui,
|
||||||
|
EUI64::from_slice(&ds.dev_eui).unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(tst.expected_fcnt_up, full_f_cnt);
|
||||||
|
} else if let ValidationStatus::Retransmission(full_f_cnt, ds) = ds {
|
||||||
|
assert_eq!(true, tst.expected_retransmission);
|
||||||
|
assert_eq!(
|
||||||
|
tst.expected_dev_eui,
|
||||||
|
EUI64::from_slice(&ds.dev_eui).unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(tst.expected_fcnt_up, full_f_cnt);
|
||||||
|
} else if let ValidationStatus::Reset(_, ds) = ds {
|
||||||
|
assert_eq!(true, tst.expected_reset);
|
||||||
|
assert_eq!(
|
||||||
|
tst.expected_dev_eui,
|
||||||
|
EUI64::from_slice(&ds.dev_eui).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,80 +1,14 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use tracing::{error, info, trace};
|
|
||||||
|
|
||||||
use super::error::Error;
|
use super::error::Error;
|
||||||
use super::{get_async_redis_conn, redis_key};
|
use super::{get_async_redis_conn, redis_key};
|
||||||
use crate::api::helpers::FromProto;
|
|
||||||
use crate::config;
|
|
||||||
use crate::helpers::errors::PrintFullError;
|
|
||||||
use chirpstack_api::internal;
|
use chirpstack_api::internal;
|
||||||
use lrwn::{AES128Key, DevAddr, Payload, PhyPayload, EUI64};
|
use lrwn::{EUI64};
|
||||||
|
|
||||||
pub enum ValidationStatus {
|
pub async fn get(dev_eui: &EUI64) -> Result<internal::DeviceSession, Error> {
|
||||||
Ok(u32, internal::DeviceSession),
|
|
||||||
Retransmission(u32, internal::DeviceSession),
|
|
||||||
Reset(u32, internal::DeviceSession),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn save(ds: &internal::DeviceSession) -> Result<()> {
|
|
||||||
let eui = EUI64::from_slice(&ds.dev_eui)?;
|
|
||||||
let addr = DevAddr::from_slice(&ds.dev_addr)?;
|
|
||||||
|
|
||||||
let conf = config::get();
|
|
||||||
let addr_key = redis_key(format!("devaddr:{{{}}}", addr));
|
|
||||||
let ds_key = redis_key(format!("device:{{{}}}:ds", eui));
|
|
||||||
let b = ds.encode_to_vec();
|
|
||||||
let ttl = conf.network.device_session_ttl.as_millis() as usize;
|
|
||||||
|
|
||||||
// Atomic add and pexpire.
|
|
||||||
redis::pipe()
|
|
||||||
.atomic()
|
|
||||||
.cmd("SADD")
|
|
||||||
.arg(&addr_key)
|
|
||||||
.arg(&eui.to_be_bytes())
|
|
||||||
.ignore()
|
|
||||||
.cmd("PEXPIRE")
|
|
||||||
.arg(&addr_key)
|
|
||||||
.arg(ttl)
|
|
||||||
.ignore()
|
|
||||||
.query_async(&mut get_async_redis_conn().await?)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// In case there is a pending rejoin session, make sure that the new
|
|
||||||
// DevAddr also resolves to the device-session.
|
|
||||||
if let Some(pending_ds) = &ds.pending_rejoin_device_session {
|
|
||||||
let pending_addr = DevAddr::from_slice(&pending_ds.dev_addr)?;
|
|
||||||
let pending_addr_key = redis_key(format!("devaddr:{{{}}}", pending_addr));
|
|
||||||
|
|
||||||
redis::pipe()
|
|
||||||
.atomic()
|
|
||||||
.cmd("SADD")
|
|
||||||
.arg(&pending_addr_key)
|
|
||||||
.arg(&eui.to_be_bytes())
|
|
||||||
.ignore()
|
|
||||||
.cmd("PEXPIRE")
|
|
||||||
.arg(&pending_addr_key)
|
|
||||||
.arg(ttl)
|
|
||||||
.ignore()
|
|
||||||
.query_async(&mut get_async_redis_conn().await?)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
redis::cmd("PSETEX")
|
|
||||||
.arg(ds_key)
|
|
||||||
.arg(ttl)
|
|
||||||
.arg(b)
|
|
||||||
.query_async(&mut get_async_redis_conn().await?)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
info!(dev_eui = %eui, dev_addr = %addr, "Device-session saved");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get(dev_eui: &EUI64) -> Result<chirpstack_api::internal::DeviceSession, Error> {
|
|
||||||
let key = redis_key(format!("device:{{{}}}:ds", dev_eui));
|
let key = redis_key(format!("device:{{{}}}:ds", dev_eui));
|
||||||
|
|
||||||
let v: Vec<u8> = redis::cmd("GET")
|
let v: Vec<u8> = redis::cmd("GET")
|
||||||
@ -85,687 +19,7 @@ pub async fn get(dev_eui: &EUI64) -> Result<chirpstack_api::internal::DeviceSess
|
|||||||
if v.is_empty() {
|
if v.is_empty() {
|
||||||
return Err(Error::NotFound(dev_eui.to_string()));
|
return Err(Error::NotFound(dev_eui.to_string()));
|
||||||
}
|
}
|
||||||
let ds = chirpstack_api::internal::DeviceSession::decode(&mut Cursor::new(v))
|
let ds = internal::DeviceSession::decode(&mut Cursor::new(v))
|
||||||
.context("Decode device-session")?;
|
.context("Decode device-session")?;
|
||||||
Ok(ds)
|
Ok(ds)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(dev_eui: &EUI64) -> Result<()> {
|
|
||||||
let key = redis_key(format!("device:{{{}}}:ds", dev_eui));
|
|
||||||
|
|
||||||
redis::cmd("DEL")
|
|
||||||
.arg(&key)
|
|
||||||
.query_async(&mut get_async_redis_conn().await?)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
info!(dev_eui = %dev_eui, "Device-session deleted");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the device-session matching the given PhyPayload. This will fetch all device-session
|
|
||||||
// associated with the used DevAddr and based on f_cont and mic, decides which one to use.
|
|
||||||
// This function will increment the uplink frame-counter and will immediately update the
|
|
||||||
// device-session in the database, to make sure that in case this function is called multiple
|
|
||||||
// times, at most one will be valid.
|
|
||||||
// On Ok response, the PhyPayload f_cnt will be set to the full 32bit frame-counter based on the
|
|
||||||
// device-session context.
|
|
||||||
pub async fn get_for_phypayload_and_incr_f_cnt_up(
|
|
||||||
relayed: bool,
|
|
||||||
phy: &mut PhyPayload,
|
|
||||||
tx_dr: u8,
|
|
||||||
tx_ch: u8,
|
|
||||||
) -> Result<ValidationStatus, Error> {
|
|
||||||
let mut _dev_addr = DevAddr::from_be_bytes([0x00, 0x00, 0x00, 0x00]);
|
|
||||||
let mut _f_cnt_orig = 0;
|
|
||||||
|
|
||||||
// Get the dev_addr and original f_cnt.
|
|
||||||
if let Payload::MACPayload(pl) = &phy.payload {
|
|
||||||
_dev_addr = pl.fhdr.devaddr;
|
|
||||||
_f_cnt_orig = pl.fhdr.f_cnt;
|
|
||||||
} else {
|
|
||||||
return Err(Error::InvalidPayload("MacPayload".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let device_sessions = get_for_dev_addr(_dev_addr)
|
|
||||||
.await
|
|
||||||
.context("Get device-sessions for DevAddr")?;
|
|
||||||
if device_sessions.is_empty() {
|
|
||||||
return Err(Error::NotFound(_dev_addr.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
for mut ds in device_sessions {
|
|
||||||
// Get the full 32bit frame-counter.
|
|
||||||
let full_f_cnt = get_full_f_cnt_up(ds.f_cnt_up, _f_cnt_orig);
|
|
||||||
let f_nwk_s_int_key = AES128Key::from_slice(&ds.f_nwk_s_int_key)?;
|
|
||||||
let s_nwk_s_int_key = AES128Key::from_slice(&ds.s_nwk_s_int_key)?;
|
|
||||||
|
|
||||||
// Check both the full frame-counter and the received frame-counter
|
|
||||||
// truncated to the 16LSB.
|
|
||||||
// The latter is needed in case of a frame-counter reset as the
|
|
||||||
// GetFullFCntUp will think the 16LSB has rolled over and will
|
|
||||||
// increment the 16MSB bit.
|
|
||||||
let mut mic_ok = false;
|
|
||||||
for f_cnt in &[full_f_cnt, _f_cnt_orig] {
|
|
||||||
// Set the full f_cnt.
|
|
||||||
if let Payload::MACPayload(pl) = &mut phy.payload {
|
|
||||||
pl.fhdr.f_cnt = *f_cnt;
|
|
||||||
}
|
|
||||||
|
|
||||||
mic_ok = phy
|
|
||||||
.validate_uplink_data_mic(
|
|
||||||
ds.mac_version().from_proto(),
|
|
||||||
ds.conf_f_cnt,
|
|
||||||
tx_dr,
|
|
||||||
tx_ch,
|
|
||||||
&f_nwk_s_int_key,
|
|
||||||
&s_nwk_s_int_key,
|
|
||||||
)
|
|
||||||
.context("Validate MIC")?;
|
|
||||||
|
|
||||||
if mic_ok {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if mic_ok {
|
|
||||||
let full_f_cnt = if let Payload::MACPayload(pl) = &phy.payload {
|
|
||||||
pl.fhdr.f_cnt
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(relay) = &ds.relay {
|
|
||||||
if !relayed && relay.ed_relay_only {
|
|
||||||
info!(
|
|
||||||
dev_eui = hex::encode(ds.dev_eui),
|
|
||||||
"Only communication through relay is allowed"
|
|
||||||
);
|
|
||||||
return Err(Error::NotFound(_dev_addr.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if full_f_cnt >= ds.f_cnt_up {
|
|
||||||
// Make sure that in case of concurrent calls for the same uplink only one will
|
|
||||||
// pass. Either the concurrent call would read the incremented uplink frame-counter
|
|
||||||
// or it is unable to aquire the lock.
|
|
||||||
|
|
||||||
let lock_key = redis_key(format!(
|
|
||||||
"device:{{{}}}:ds:lock:{}",
|
|
||||||
hex::encode(&ds.dev_eui),
|
|
||||||
full_f_cnt,
|
|
||||||
));
|
|
||||||
let set: bool = redis::cmd("SET")
|
|
||||||
.arg(&lock_key)
|
|
||||||
.arg("lock")
|
|
||||||
.arg("EX")
|
|
||||||
.arg(1_usize)
|
|
||||||
.arg("NX")
|
|
||||||
.query_async(&mut get_async_redis_conn().await?)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if !set {
|
|
||||||
return Ok(ValidationStatus::Retransmission(full_f_cnt, ds));
|
|
||||||
}
|
|
||||||
|
|
||||||
// We immediately save the device-session to make sure that concurrent calls for
|
|
||||||
// the same uplink will fail on the frame-counter validation.
|
|
||||||
let ds_f_cnt_up = ds.f_cnt_up;
|
|
||||||
ds.f_cnt_up = full_f_cnt + 1;
|
|
||||||
save(&ds).await?;
|
|
||||||
ds.f_cnt_up = ds_f_cnt_up;
|
|
||||||
|
|
||||||
return Ok(ValidationStatus::Ok(full_f_cnt, ds));
|
|
||||||
} else if ds.skip_f_cnt_check {
|
|
||||||
// re-transmission or frame-counter reset
|
|
||||||
ds.f_cnt_up = 0;
|
|
||||||
return Ok(ValidationStatus::Ok(full_f_cnt, ds));
|
|
||||||
} else if full_f_cnt == (ds.f_cnt_up - 1) {
|
|
||||||
// re-transmission, the frame-counter did not increment
|
|
||||||
return Ok(ValidationStatus::Retransmission(full_f_cnt, ds));
|
|
||||||
} else {
|
|
||||||
return Ok(ValidationStatus::Reset(full_f_cnt, ds));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore the original f_cnt.
|
|
||||||
if let Payload::MACPayload(pl) = &mut phy.payload {
|
|
||||||
pl.fhdr.f_cnt = _f_cnt_orig;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::InvalidMIC)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simmilar to get_for_phypayload_and_incr_f_cnt_up, but only retrieves the device-session for the
|
|
||||||
// given PhyPayload. As it does not return the ValidationStatus, it only returns the DeviceSession
|
|
||||||
// in case of a valid frame-counter.
|
|
||||||
// On Ok response, the PhyPayload f_cnt will be set to the full 32bit frame-counter based on the
|
|
||||||
// device-session context.
|
|
||||||
pub async fn get_for_phypayload(
|
|
||||||
phy: &mut PhyPayload,
|
|
||||||
tx_dr: u8,
|
|
||||||
tx_ch: u8,
|
|
||||||
) -> Result<internal::DeviceSession, Error> {
|
|
||||||
// Get the dev_addr and original f_cnt.
|
|
||||||
let (dev_addr, f_cnt_orig) = if let Payload::MACPayload(pl) = &phy.payload {
|
|
||||||
(pl.fhdr.devaddr, pl.fhdr.f_cnt)
|
|
||||||
} else {
|
|
||||||
return Err(Error::InvalidPayload("MacPayload".to_string()));
|
|
||||||
};
|
|
||||||
|
|
||||||
let device_sessions = get_for_dev_addr(dev_addr)
|
|
||||||
.await
|
|
||||||
.context("Get device-sessions for DevAddr")?;
|
|
||||||
if device_sessions.is_empty() {
|
|
||||||
return Err(Error::NotFound(dev_addr.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
for ds in device_sessions {
|
|
||||||
// Get the full 32bit frame-counter.
|
|
||||||
let full_f_cnt = get_full_f_cnt_up(ds.f_cnt_up, f_cnt_orig);
|
|
||||||
let f_nwk_s_int_key = AES128Key::from_slice(&ds.f_nwk_s_int_key)?;
|
|
||||||
let s_nwk_s_int_key = AES128Key::from_slice(&ds.s_nwk_s_int_key)?;
|
|
||||||
|
|
||||||
// Set the full f_cnt
|
|
||||||
if let Payload::MACPayload(pl) = &mut phy.payload {
|
|
||||||
pl.fhdr.f_cnt = full_f_cnt;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mic_ok = phy
|
|
||||||
.validate_uplink_data_mic(
|
|
||||||
ds.mac_version().from_proto(),
|
|
||||||
ds.conf_f_cnt,
|
|
||||||
tx_dr,
|
|
||||||
tx_ch,
|
|
||||||
&f_nwk_s_int_key,
|
|
||||||
&s_nwk_s_int_key,
|
|
||||||
)
|
|
||||||
.context("Validate MIC")?;
|
|
||||||
|
|
||||||
if mic_ok && full_f_cnt >= ds.f_cnt_up {
|
|
||||||
return Ok(ds);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore the original f_cnt.
|
|
||||||
if let Payload::MACPayload(pl) = &mut phy.payload {
|
|
||||||
pl.fhdr.f_cnt = f_cnt_orig;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::InvalidMIC)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_dev_euis_for_dev_addr(dev_addr: DevAddr) -> Result<Vec<EUI64>> {
|
|
||||||
let key = redis_key(format!("devaddr:{{{}}}", dev_addr));
|
|
||||||
|
|
||||||
let dev_euis: HashSet<Vec<u8>> = redis::cmd("SMEMBERS")
|
|
||||||
.arg(key)
|
|
||||||
.query_async(&mut get_async_redis_conn().await?)
|
|
||||||
.await
|
|
||||||
.context("Get DevEUIs for DevAddr")?;
|
|
||||||
|
|
||||||
let mut out = Vec::new();
|
|
||||||
for dev_eui in &dev_euis {
|
|
||||||
out.push(EUI64::from_slice(dev_eui)?);
|
|
||||||
}
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn remove_dev_eui_from_dev_addr_set(dev_addr: DevAddr, dev_eui: EUI64) -> Result<()> {
|
|
||||||
let key = redis_key(format!("devaddr:{{{}}}", dev_addr));
|
|
||||||
|
|
||||||
redis::cmd("SREM")
|
|
||||||
.arg(key)
|
|
||||||
.arg(&dev_eui.to_be_bytes())
|
|
||||||
.query_async(&mut get_async_redis_conn().await?)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_for_dev_addr(dev_addr: DevAddr) -> Result<Vec<internal::DeviceSession>> {
|
|
||||||
trace!(dev_addr = %dev_addr, "Getting device-session for DevAddr");
|
|
||||||
let dev_euis = get_dev_euis_for_dev_addr(dev_addr).await?;
|
|
||||||
|
|
||||||
let mut out = Vec::new();
|
|
||||||
for dev_eui in dev_euis {
|
|
||||||
let ds = match get(&dev_eui).await {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => {
|
|
||||||
if let Error::NotFound(_) = e {
|
|
||||||
if let Err(e) = remove_dev_eui_from_dev_addr_set(dev_addr, dev_eui).await {
|
|
||||||
error!(dev_addr = %dev_addr, dev_eui = %dev_eui, error = %e.full(), "Remove DevEUI from DevAddr->DevEUI set error");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error!(dev_addr = %dev_addr, dev_eui = %dev_eui, error = %e.full(), "Get device-session for DevEUI error");
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let ds_dev_addr = DevAddr::from_slice(&ds.dev_addr)?;
|
|
||||||
|
|
||||||
// When a pending rejoin device-session context is set and it has
|
|
||||||
// the given devAddr, add it to the items list.
|
|
||||||
if let Some(pending_ds) = &ds.pending_rejoin_device_session {
|
|
||||||
let pending_dev_addr = DevAddr::from_slice(&pending_ds.dev_addr)?;
|
|
||||||
if pending_dev_addr == dev_addr {
|
|
||||||
out.push(*pending_ds.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// It is possible that the "main" device-session maps to a different
|
|
||||||
// devAddr as the PendingRejoinDeviceSession is set (using the devAddr
|
|
||||||
// that is used for the lookup).
|
|
||||||
if ds_dev_addr == dev_addr {
|
|
||||||
out.push(ds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFullFCntUp returns the full 32bit frame-counter, given the fCntUp which
|
|
||||||
// has been truncated to the last 16 LSB.
|
|
||||||
// Notes:
|
|
||||||
// * After a succesful validation of the FCntUp and the MIC, don't forget
|
|
||||||
// to synchronize the device FCntUp with the packet FCnt.
|
|
||||||
// * In case of a frame-counter rollover, the returned values will be less
|
|
||||||
// than the given DeviceSession FCntUp. This must be validated outside this
|
|
||||||
// function!
|
|
||||||
// * In case of a re-transmission, the returned frame-counter equals
|
|
||||||
// DeviceSession.FCntUp - 1, as the FCntUp value holds the next expected
|
|
||||||
// frame-counter, not the FCntUp which was last seen.
|
|
||||||
fn get_full_f_cnt_up(next_expected_full_fcnt: u32, truncated_f_cnt: u32) -> u32 {
|
|
||||||
// Handle re-transmission.
|
|
||||||
if truncated_f_cnt == (((next_expected_full_fcnt % (1 << 16)) as u16).wrapping_sub(1)) as u32 {
|
|
||||||
return next_expected_full_fcnt - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let gap = ((truncated_f_cnt as u16).wrapping_sub((next_expected_full_fcnt % (1 << 16)) as u16))
|
|
||||||
as u32;
|
|
||||||
|
|
||||||
next_expected_full_fcnt.wrapping_add(gap)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub mod test {
|
|
||||||
use super::*;
|
|
||||||
use crate::test;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_full_f_cnt_up() {
|
|
||||||
// server, device, expected
|
|
||||||
let tests = vec![
|
|
||||||
(1, 1, 1), // frame-counter is as expected
|
|
||||||
(1 << 16, 0, 1 << 16), // frame-counter is as expected
|
|
||||||
((1 << 16) + 1, 1, (1 << 16) + 1), // frame-counter is as expected
|
|
||||||
(0, 1, 1), // one frame packet-loss
|
|
||||||
((1 << 16) + 1, 2, (1 << 16) + 2), // one frame packet-loss
|
|
||||||
(2, 1, 1), // re-transmission of previous frame
|
|
||||||
((1 << 16) + 1, 0, (1 << 16)), // re-transmission of previous frame
|
|
||||||
((1 << 16), (1 << 16) - 1, (1 << 16) - 1), // re-transmission of previous frame
|
|
||||||
(u32::MAX, 0, 0), // 32bit frame-counter rollover
|
|
||||||
];
|
|
||||||
|
|
||||||
for (i, tst) in tests.iter().enumerate() {
|
|
||||||
let out = get_full_f_cnt_up(tst.0, tst.1);
|
|
||||||
assert_eq!(tst.2, out, "Test: {}, expected: {}, got: {}", i, tst.2, out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_device_session() {
|
|
||||||
let _guard = test::prepare().await;
|
|
||||||
|
|
||||||
let device_sessions = vec![
|
|
||||||
internal::DeviceSession {
|
|
||||||
dev_addr: vec![0x01, 0x02, 0x03, 0x04],
|
|
||||||
dev_eui: vec![0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
|
|
||||||
s_nwk_s_int_key: vec![
|
|
||||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
|
||||||
0x01, 0x01, 0x01,
|
|
||||||
],
|
|
||||||
f_nwk_s_int_key: vec![
|
|
||||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
|
||||||
0x01, 0x01, 0x01,
|
|
||||||
],
|
|
||||||
nwk_s_enc_key: vec![
|
|
||||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
|
||||||
0x01, 0x01, 0x01,
|
|
||||||
],
|
|
||||||
f_cnt_up: 100,
|
|
||||||
skip_f_cnt_check: true,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
internal::DeviceSession {
|
|
||||||
dev_addr: vec![0x01, 0x02, 0x03, 0x04],
|
|
||||||
dev_eui: vec![0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02],
|
|
||||||
s_nwk_s_int_key: vec![
|
|
||||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
|
||||||
0x02, 0x02, 0x02,
|
|
||||||
],
|
|
||||||
f_nwk_s_int_key: vec![
|
|
||||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
|
||||||
0x02, 0x02, 0x02,
|
|
||||||
],
|
|
||||||
nwk_s_enc_key: vec![
|
|
||||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
|
||||||
0x02, 0x02, 0x02,
|
|
||||||
],
|
|
||||||
f_cnt_up: 200,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
internal::DeviceSession {
|
|
||||||
dev_addr: vec![0x01, 0x02, 0x03, 0x04],
|
|
||||||
dev_eui: vec![0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03],
|
|
||||||
s_nwk_s_int_key: vec![
|
|
||||||
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
|
||||||
0x03, 0x03, 0x03,
|
|
||||||
],
|
|
||||||
f_nwk_s_int_key: vec![
|
|
||||||
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
|
||||||
0x03, 0x03, 0x03,
|
|
||||||
],
|
|
||||||
nwk_s_enc_key: vec![
|
|
||||||
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
|
||||||
0x03, 0x03, 0x03,
|
|
||||||
],
|
|
||||||
f_cnt_up: 300,
|
|
||||||
pending_rejoin_device_session: Some(Box::new(internal::DeviceSession {
|
|
||||||
dev_addr: vec![0x04, 0x03, 0x02, 0x01],
|
|
||||||
dev_eui: vec![0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03],
|
|
||||||
s_nwk_s_int_key: vec![
|
|
||||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
|
||||||
0x04, 0x04, 0x04, 0x04,
|
|
||||||
],
|
|
||||||
f_nwk_s_int_key: vec![
|
|
||||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
|
||||||
0x04, 0x04, 0x04, 0x04,
|
|
||||||
],
|
|
||||||
nwk_s_enc_key: vec![
|
|
||||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
|
||||||
0x04, 0x04, 0x04, 0x04,
|
|
||||||
],
|
|
||||||
f_cnt_up: 0,
|
|
||||||
..Default::default()
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
internal::DeviceSession {
|
|
||||||
dev_addr: vec![0x01, 0x02, 0x03, 0x04],
|
|
||||||
dev_eui: vec![0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05],
|
|
||||||
s_nwk_s_int_key: vec![
|
|
||||||
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
|
||||||
0x05, 0x05, 0x05,
|
|
||||||
],
|
|
||||||
f_nwk_s_int_key: vec![
|
|
||||||
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
|
||||||
0x05, 0x05, 0x05,
|
|
||||||
],
|
|
||||||
nwk_s_enc_key: vec![
|
|
||||||
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
|
||||||
0x05, 0x05, 0x05,
|
|
||||||
],
|
|
||||||
f_cnt_up: (1 << 16) + 1,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for ds in &device_sessions {
|
|
||||||
save(ds).await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct Test {
|
|
||||||
name: String,
|
|
||||||
dev_addr: DevAddr,
|
|
||||||
s_nwk_s_int_key: AES128Key,
|
|
||||||
f_nwk_s_int_key: AES128Key,
|
|
||||||
f_cnt: u32,
|
|
||||||
expected_retransmission: bool,
|
|
||||||
expected_reset: bool,
|
|
||||||
expected_dev_eui: EUI64,
|
|
||||||
expected_fcnt_up: u32,
|
|
||||||
expected_error: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
let tests = vec![
|
|
||||||
Test {
|
|
||||||
name: "matching dev_eui 0101010101010101".to_string(),
|
|
||||||
dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]),
|
|
||||||
f_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].f_nwk_s_int_key)
|
|
||||||
.unwrap(),
|
|
||||||
s_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].s_nwk_s_int_key)
|
|
||||||
.unwrap(),
|
|
||||||
f_cnt: device_sessions[0].f_cnt_up,
|
|
||||||
expected_retransmission: false,
|
|
||||||
expected_reset: false,
|
|
||||||
expected_fcnt_up: device_sessions[0].f_cnt_up,
|
|
||||||
expected_dev_eui: EUI64::from_slice(&device_sessions[0].dev_eui).unwrap(),
|
|
||||||
expected_error: None,
|
|
||||||
},
|
|
||||||
Test {
|
|
||||||
name: "matching dev_eui 0202020202020202".to_string(),
|
|
||||||
dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]),
|
|
||||||
f_nwk_s_int_key: AES128Key::from_slice(&device_sessions[1].f_nwk_s_int_key)
|
|
||||||
.unwrap(),
|
|
||||||
s_nwk_s_int_key: AES128Key::from_slice(&device_sessions[1].s_nwk_s_int_key)
|
|
||||||
.unwrap(),
|
|
||||||
f_cnt: device_sessions[1].f_cnt_up,
|
|
||||||
expected_retransmission: false,
|
|
||||||
expected_reset: false,
|
|
||||||
expected_fcnt_up: device_sessions[1].f_cnt_up,
|
|
||||||
expected_dev_eui: EUI64::from_slice(&device_sessions[1].dev_eui).unwrap(),
|
|
||||||
expected_error: None,
|
|
||||||
},
|
|
||||||
Test {
|
|
||||||
name: "matching dev_eui 0101010101010101 with frame-counter reset".to_string(),
|
|
||||||
dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]),
|
|
||||||
f_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].f_nwk_s_int_key)
|
|
||||||
.unwrap(),
|
|
||||||
s_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].s_nwk_s_int_key)
|
|
||||||
.unwrap(),
|
|
||||||
f_cnt: 0,
|
|
||||||
expected_retransmission: false,
|
|
||||||
expected_reset: false,
|
|
||||||
expected_fcnt_up: 0,
|
|
||||||
expected_dev_eui: EUI64::from_slice(&device_sessions[0].dev_eui).unwrap(),
|
|
||||||
expected_error: None,
|
|
||||||
},
|
|
||||||
Test {
|
|
||||||
name: "matching dev_eui 0202020202020202 with invalid frame-counter".to_string(),
|
|
||||||
dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]),
|
|
||||||
f_nwk_s_int_key: AES128Key::from_slice(&device_sessions[1].f_nwk_s_int_key)
|
|
||||||
.unwrap(),
|
|
||||||
s_nwk_s_int_key: AES128Key::from_slice(&device_sessions[1].s_nwk_s_int_key)
|
|
||||||
.unwrap(),
|
|
||||||
f_cnt: 0,
|
|
||||||
expected_reset: true,
|
|
||||||
expected_dev_eui: EUI64::from_slice(&device_sessions[1].dev_eui).unwrap(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Test {
|
|
||||||
name: "invalid DevAddr".to_string(),
|
|
||||||
dev_addr: DevAddr::from_be_bytes([0x01, 0x01, 0x01, 0x01]),
|
|
||||||
f_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].f_nwk_s_int_key)
|
|
||||||
.unwrap(),
|
|
||||||
s_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].s_nwk_s_int_key)
|
|
||||||
.unwrap(),
|
|
||||||
f_cnt: device_sessions[0].f_cnt_up,
|
|
||||||
expected_error: Some("Object does not exist (id: 01010101)".to_string()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Test {
|
|
||||||
name: "invalid nwk_s_key".to_string(),
|
|
||||||
dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]),
|
|
||||||
f_nwk_s_int_key: AES128Key::from_bytes([
|
|
||||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
|
||||||
]),
|
|
||||||
s_nwk_s_int_key: AES128Key::from_bytes([
|
|
||||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
|
||||||
]),
|
|
||||||
f_cnt: device_sessions[0].f_cnt_up,
|
|
||||||
expected_error: Some("Invalid MIC".to_string()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Test {
|
|
||||||
name: "matching pending rejoin device-session".to_string(),
|
|
||||||
dev_addr: DevAddr::from_be_bytes([0x04, 0x03, 0x02, 0x01]),
|
|
||||||
f_nwk_s_int_key: AES128Key::from_bytes([
|
|
||||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
|
||||||
0x04, 0x04, 0x04,
|
|
||||||
]),
|
|
||||||
s_nwk_s_int_key: AES128Key::from_bytes([
|
|
||||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
|
||||||
0x04, 0x04, 0x04,
|
|
||||||
]),
|
|
||||||
f_cnt: 0,
|
|
||||||
expected_dev_eui: EUI64::from_slice(&device_sessions[2].dev_eui).unwrap(),
|
|
||||||
expected_fcnt_up: 0,
|
|
||||||
expected_retransmission: false,
|
|
||||||
expected_error: None,
|
|
||||||
expected_reset: false,
|
|
||||||
},
|
|
||||||
Test {
|
|
||||||
name: "frame-counter rollover (16lsb)".to_string(),
|
|
||||||
dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]),
|
|
||||||
f_nwk_s_int_key: AES128Key::from_bytes([
|
|
||||||
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
|
||||||
0x05, 0x05, 0x05,
|
|
||||||
]),
|
|
||||||
s_nwk_s_int_key: AES128Key::from_bytes([
|
|
||||||
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
|
||||||
0x05, 0x05, 0x05,
|
|
||||||
]),
|
|
||||||
f_cnt: (1 << 16) + 11,
|
|
||||||
expected_dev_eui: EUI64::from_slice(&device_sessions[3].dev_eui).unwrap(),
|
|
||||||
expected_fcnt_up: (1 << 16) + 11,
|
|
||||||
expected_retransmission: false,
|
|
||||||
expected_error: None,
|
|
||||||
expected_reset: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for tst in &tests {
|
|
||||||
println!("> {}", tst.name);
|
|
||||||
let mut phy = lrwn::PhyPayload {
|
|
||||||
mhdr: lrwn::MHDR {
|
|
||||||
m_type: lrwn::MType::UnconfirmedDataUp,
|
|
||||||
major: lrwn::Major::LoRaWANR1,
|
|
||||||
},
|
|
||||||
payload: lrwn::Payload::MACPayload(lrwn::MACPayload {
|
|
||||||
fhdr: lrwn::FHDR {
|
|
||||||
devaddr: tst.dev_addr,
|
|
||||||
f_ctrl: lrwn::FCtrl::default(),
|
|
||||||
f_cnt: tst.f_cnt,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
mic: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
phy.set_uplink_data_mic(
|
|
||||||
lrwn::MACVersion::LoRaWAN1_0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
&tst.f_nwk_s_int_key,
|
|
||||||
&tst.s_nwk_s_int_key,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Truncate to 16LSB (as it would be transmitted over the air).
|
|
||||||
if let lrwn::Payload::MACPayload(pl) = &mut phy.payload {
|
|
||||||
pl.fhdr.f_cnt = tst.f_cnt % (1 << 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ds_res = get_for_phypayload_and_incr_f_cnt_up(false, &mut phy, 0, 0).await;
|
|
||||||
if tst.expected_error.is_some() {
|
|
||||||
assert_eq!(true, ds_res.is_err());
|
|
||||||
assert_eq!(
|
|
||||||
tst.expected_error.as_ref().unwrap(),
|
|
||||||
&ds_res.err().unwrap().to_string()
|
|
||||||
);
|
|
||||||
if let lrwn::Payload::MACPayload(pl) = &phy.payload {
|
|
||||||
assert_eq!(tst.f_cnt, pl.fhdr.f_cnt);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let ds = ds_res.unwrap();
|
|
||||||
|
|
||||||
// Validate that the f_cnt of the PhyPayload was set to the full frame-counter.
|
|
||||||
if let lrwn::Payload::MACPayload(pl) = &phy.payload {
|
|
||||||
assert_eq!(tst.expected_fcnt_up, pl.fhdr.f_cnt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let ValidationStatus::Ok(full_f_cnt, ds) = ds {
|
|
||||||
assert_eq!(false, tst.expected_retransmission);
|
|
||||||
assert_eq!(
|
|
||||||
tst.expected_dev_eui,
|
|
||||||
EUI64::from_slice(&ds.dev_eui).unwrap()
|
|
||||||
);
|
|
||||||
assert_eq!(tst.expected_fcnt_up, full_f_cnt);
|
|
||||||
} else if let ValidationStatus::Retransmission(full_f_cnt, ds) = ds {
|
|
||||||
assert_eq!(true, tst.expected_retransmission);
|
|
||||||
assert_eq!(
|
|
||||||
tst.expected_dev_eui,
|
|
||||||
EUI64::from_slice(&ds.dev_eui).unwrap()
|
|
||||||
);
|
|
||||||
assert_eq!(tst.expected_fcnt_up, full_f_cnt);
|
|
||||||
} else if let ValidationStatus::Reset(_, ds) = ds {
|
|
||||||
assert_eq!(true, tst.expected_reset);
|
|
||||||
assert_eq!(
|
|
||||||
tst.expected_dev_eui,
|
|
||||||
EUI64::from_slice(&ds.dev_eui).unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_get_for_dev_addr() {
|
|
||||||
let _guard = test::prepare().await;
|
|
||||||
|
|
||||||
let dev_eui_1 = EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 1]);
|
|
||||||
let dev_eui_2 = EUI64::from_be_bytes([2, 2, 2, 2, 2, 2, 2, 2]);
|
|
||||||
let dev_addr = DevAddr::from_be_bytes([1, 2, 3, 4]);
|
|
||||||
|
|
||||||
let ds_1 = internal::DeviceSession {
|
|
||||||
dev_addr: dev_addr.to_vec(),
|
|
||||||
dev_eui: dev_eui_1.to_vec(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let ds_2 = internal::DeviceSession {
|
|
||||||
dev_addr: dev_addr.to_vec(),
|
|
||||||
dev_eui: dev_eui_2.to_vec(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
save(&ds_1).await.unwrap();
|
|
||||||
save(&ds_2).await.unwrap();
|
|
||||||
|
|
||||||
let dss = get_for_dev_addr(dev_addr).await.unwrap();
|
|
||||||
assert_eq!(2, dss.len());
|
|
||||||
|
|
||||||
let dev_euis = get_dev_euis_for_dev_addr(dev_addr).await.unwrap();
|
|
||||||
assert_eq!(2, dev_euis.len());
|
|
||||||
|
|
||||||
// At this point there is still a 'dangling' pointer from DevAddr->DevEUI.
|
|
||||||
delete(&dev_eui_2).await.unwrap();
|
|
||||||
let dev_euis = get_dev_euis_for_dev_addr(dev_addr).await.unwrap();
|
|
||||||
assert_eq!(2, dev_euis.len());
|
|
||||||
|
|
||||||
// This should only return one device-session.
|
|
||||||
let dss = get_for_dev_addr(dev_addr).await.unwrap();
|
|
||||||
assert_eq!(1, dss.len());
|
|
||||||
assert_eq!(dev_eui_1.to_vec(), dss[0].dev_eui);
|
|
||||||
|
|
||||||
// 'dangling' DevAddr->DevEUI pointers have been cleaned up.
|
|
||||||
let dev_euis = get_dev_euis_for_dev_addr(dev_addr).await.unwrap();
|
|
||||||
assert_eq!(1, dev_euis.len());
|
|
||||||
assert_eq!(dev_eui_1, dev_euis[0]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,7 +12,7 @@ use crate::gateway::backend::mock as gateway_mock;
|
|||||||
use crate::integration::mock;
|
use crate::integration::mock;
|
||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
device::{self, DeviceClass},
|
device::{self, DeviceClass},
|
||||||
device_queue, device_session, downlink_frame, get_async_redis_conn, redis_key,
|
device_queue, downlink_frame, get_async_redis_conn, redis_key,
|
||||||
};
|
};
|
||||||
use chirpstack_api::{gw, integration as integration_pb, internal, stream};
|
use chirpstack_api::{gw, integration as integration_pb, internal, stream};
|
||||||
use lrwn::EUI64;
|
use lrwn::EUI64;
|
||||||
@ -27,7 +27,11 @@ pub fn f_cnt_up(dev_eui: EUI64, f_cnt: u32) -> Validator {
|
|||||||
Box::new(move || {
|
Box::new(move || {
|
||||||
let dev_eui = dev_eui.clone();
|
let dev_eui = dev_eui.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let ds = device_session::get(&dev_eui).await.unwrap();
|
let ds = device::get(&dev_eui)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.get_device_session()
|
||||||
|
.unwrap();
|
||||||
assert_eq!(f_cnt, ds.f_cnt_up);
|
assert_eq!(f_cnt, ds.f_cnt_up);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -37,7 +41,11 @@ pub fn n_f_cnt_down(dev_eui: EUI64, f_cnt: u32) -> Validator {
|
|||||||
Box::new(move || {
|
Box::new(move || {
|
||||||
let dev_eui = dev_eui.clone();
|
let dev_eui = dev_eui.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let ds = device_session::get(&dev_eui).await.unwrap();
|
let ds = device::get(&dev_eui)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.get_device_session()
|
||||||
|
.unwrap();
|
||||||
assert_eq!(f_cnt, ds.n_f_cnt_down);
|
assert_eq!(f_cnt, ds.n_f_cnt_down);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -47,7 +55,11 @@ pub fn a_f_cnt_down(dev_eui: EUI64, f_cnt: u32) -> Validator {
|
|||||||
Box::new(move || {
|
Box::new(move || {
|
||||||
let dev_eui = dev_eui.clone();
|
let dev_eui = dev_eui.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let ds = device_session::get(&dev_eui).await.unwrap();
|
let ds = device::get(&dev_eui)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.get_device_session()
|
||||||
|
.unwrap();
|
||||||
assert_eq!(f_cnt, ds.a_f_cnt_down);
|
assert_eq!(f_cnt, ds.a_f_cnt_down);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -57,7 +69,11 @@ pub fn tx_power_index(dev_eui: EUI64, tx_power: u32) -> Validator {
|
|||||||
Box::new(move || {
|
Box::new(move || {
|
||||||
let dev_eui = dev_eui.clone();
|
let dev_eui = dev_eui.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let ds = device_session::get(&dev_eui).await.unwrap();
|
let ds = device::get(&dev_eui)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.get_device_session()
|
||||||
|
.unwrap();
|
||||||
assert_eq!(tx_power, ds.tx_power_index);
|
assert_eq!(tx_power, ds.tx_power_index);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -67,7 +83,11 @@ pub fn nb_trans(dev_eui: EUI64, nb_trans: u32) -> Validator {
|
|||||||
Box::new(move || {
|
Box::new(move || {
|
||||||
let dev_eui = dev_eui.clone();
|
let dev_eui = dev_eui.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let ds = device_session::get(&dev_eui).await.unwrap();
|
let ds = device::get(&dev_eui)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.get_device_session()
|
||||||
|
.unwrap();
|
||||||
assert_eq!(nb_trans, ds.nb_trans);
|
assert_eq!(nb_trans, ds.nb_trans);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -78,7 +98,11 @@ pub fn enabled_uplink_channel_indices(dev_eui: EUI64, channels: Vec<u32>) -> Val
|
|||||||
let dev_eui = dev_eui.clone();
|
let dev_eui = dev_eui.clone();
|
||||||
let channels = channels.clone();
|
let channels = channels.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let ds = device_session::get(&dev_eui).await.unwrap();
|
let ds = device::get(&dev_eui)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.get_device_session()
|
||||||
|
.unwrap();
|
||||||
assert_eq!(channels, ds.enabled_uplink_channel_indices);
|
assert_eq!(channels, ds.enabled_uplink_channel_indices);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -88,7 +112,11 @@ pub fn dr(dev_eui: EUI64, dr: u32) -> Validator {
|
|||||||
Box::new(move || {
|
Box::new(move || {
|
||||||
let dev_eui = dev_eui.clone();
|
let dev_eui = dev_eui.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let ds = device_session::get(&dev_eui).await.unwrap();
|
let ds = device::get(&dev_eui)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.get_device_session()
|
||||||
|
.unwrap();
|
||||||
assert_eq!(dr, ds.dr);
|
assert_eq!(dr, ds.dr);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -98,7 +126,11 @@ pub fn mac_command_error_count(dev_eui: EUI64, cid: lrwn::CID, count: u32) -> Va
|
|||||||
Box::new(move || {
|
Box::new(move || {
|
||||||
let dev_eui = dev_eui.clone();
|
let dev_eui = dev_eui.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let ds = device_session::get(&dev_eui).await.unwrap();
|
let ds = device::get(&dev_eui)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.get_device_session()
|
||||||
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
count,
|
count,
|
||||||
ds.mac_command_error_count
|
ds.mac_command_error_count
|
||||||
@ -115,7 +147,11 @@ pub fn uplink_adr_history(dev_eui: EUI64, uh: Vec<internal::UplinkAdrHistory>) -
|
|||||||
let dev_eui = dev_eui.clone();
|
let dev_eui = dev_eui.clone();
|
||||||
let uh = uh.clone();
|
let uh = uh.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let ds = device_session::get(&dev_eui).await.unwrap();
|
let ds = device::get(&dev_eui)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.get_device_session()
|
||||||
|
.unwrap();
|
||||||
assert_eq!(uh, ds.uplink_adr_history);
|
assert_eq!(uh, ds.uplink_adr_history);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -12,7 +12,7 @@ use crate::gateway::backend as gateway_backend;
|
|||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
application,
|
application,
|
||||||
device::{self, DeviceClass},
|
device::{self, DeviceClass},
|
||||||
device_profile, device_queue, device_session, gateway, tenant,
|
device_profile, device_queue, gateway, tenant,
|
||||||
};
|
};
|
||||||
use crate::{config, test, uplink};
|
use crate::{config, test, uplink};
|
||||||
use chirpstack_api::{common, gw, internal};
|
use chirpstack_api::{common, gw, internal};
|
||||||
@ -219,17 +219,46 @@ async fn test_sns_uplink() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let mut dev_addr = lrwn::DevAddr::from_be_bytes([0, 0, 0, 0]);
|
||||||
|
dev_addr.set_dev_addr_prefix(lrwn::NetID::from_str("000505").unwrap().dev_addr_prefix());
|
||||||
|
|
||||||
let dev = device::create(device::Device {
|
let dev = device::create(device::Device {
|
||||||
name: "device".into(),
|
name: "device".into(),
|
||||||
application_id: app.id.clone(),
|
application_id: app.id.clone(),
|
||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::B,
|
enabled_class: DeviceClass::B,
|
||||||
|
dev_addr: Some(dev_addr),
|
||||||
|
device_session: Some(
|
||||||
|
internal::DeviceSession {
|
||||||
|
dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8],
|
||||||
|
mac_version: common::MacVersion::Lorawan104.into(),
|
||||||
|
join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1],
|
||||||
|
dev_addr: dev_addr.to_vec(),
|
||||||
|
f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||||
|
s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||||
|
nwk_s_enc_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||||
|
app_s_key: Some(common::KeyEnvelope {
|
||||||
|
kek_label: "".into(),
|
||||||
|
aes_key: vec![16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
|
||||||
|
}),
|
||||||
|
f_cnt_up: 8,
|
||||||
|
n_f_cnt_down: 5,
|
||||||
|
enabled_uplink_channel_indices: vec![0, 1, 2],
|
||||||
|
rx1_delay: 1,
|
||||||
|
rx2_frequency: 869525000,
|
||||||
|
region_config_id: "eu868".into(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.encode_to_vec(),
|
||||||
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let ds = dev.get_device_session().unwrap();
|
||||||
|
|
||||||
device_queue::enqueue_item(device_queue::DeviceQueueItem {
|
device_queue::enqueue_item(device_queue::DeviceQueueItem {
|
||||||
dev_eui: dev.dev_eui,
|
dev_eui: dev.dev_eui,
|
||||||
f_port: 10,
|
f_port: 10,
|
||||||
@ -239,31 +268,6 @@ async fn test_sns_uplink() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut dev_addr = lrwn::DevAddr::from_be_bytes([0, 0, 0, 0]);
|
|
||||||
dev_addr.set_dev_addr_prefix(lrwn::NetID::from_str("000505").unwrap().dev_addr_prefix());
|
|
||||||
|
|
||||||
let ds = internal::DeviceSession {
|
|
||||||
dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8],
|
|
||||||
mac_version: common::MacVersion::Lorawan104.into(),
|
|
||||||
join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1],
|
|
||||||
dev_addr: dev_addr.to_vec(),
|
|
||||||
f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
|
||||||
s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
|
||||||
nwk_s_enc_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
|
||||||
app_s_key: Some(common::KeyEnvelope {
|
|
||||||
kek_label: "".into(),
|
|
||||||
aes_key: vec![16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
|
|
||||||
}),
|
|
||||||
f_cnt_up: 8,
|
|
||||||
n_f_cnt_down: 5,
|
|
||||||
enabled_uplink_channel_indices: vec![0, 1, 2],
|
|
||||||
rx1_delay: 1,
|
|
||||||
rx2_frequency: 869525000,
|
|
||||||
region_config_id: "eu868".into(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
device_session::save(&ds).await.unwrap();
|
|
||||||
|
|
||||||
let mut data_phy = lrwn::PhyPayload {
|
let mut data_phy = lrwn::PhyPayload {
|
||||||
mhdr: lrwn::MHDR {
|
mhdr: lrwn::MHDR {
|
||||||
m_type: lrwn::MType::UnconfirmedDataUp,
|
m_type: lrwn::MType::UnconfirmedDataUp,
|
||||||
@ -466,41 +470,45 @@ async fn test_sns_roaming_not_allowed() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let _dev = device::create(device::Device {
|
let mut dev_addr = lrwn::DevAddr::from_be_bytes([0, 0, 0, 0]);
|
||||||
|
dev_addr.set_dev_addr_prefix(lrwn::NetID::from_str("000505").unwrap().dev_addr_prefix());
|
||||||
|
|
||||||
|
let dev = device::create(device::Device {
|
||||||
name: "device".into(),
|
name: "device".into(),
|
||||||
application_id: app.id.clone(),
|
application_id: app.id.clone(),
|
||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::B,
|
enabled_class: DeviceClass::B,
|
||||||
|
dev_addr: Some(dev_addr),
|
||||||
|
device_session: Some(
|
||||||
|
internal::DeviceSession {
|
||||||
|
dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8],
|
||||||
|
mac_version: common::MacVersion::Lorawan104.into(),
|
||||||
|
join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1],
|
||||||
|
dev_addr: dev_addr.to_vec(),
|
||||||
|
f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||||
|
s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||||
|
nwk_s_enc_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||||
|
app_s_key: Some(common::KeyEnvelope {
|
||||||
|
kek_label: "".into(),
|
||||||
|
aes_key: vec![16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
|
||||||
|
}),
|
||||||
|
f_cnt_up: 8,
|
||||||
|
n_f_cnt_down: 5,
|
||||||
|
enabled_uplink_channel_indices: vec![0, 1, 2],
|
||||||
|
rx1_delay: 1,
|
||||||
|
rx2_frequency: 869525000,
|
||||||
|
region_config_id: "eu868".into(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.encode_to_vec(),
|
||||||
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut dev_addr = lrwn::DevAddr::from_be_bytes([0, 0, 0, 0]);
|
let ds = dev.get_device_session().unwrap();
|
||||||
dev_addr.set_dev_addr_prefix(lrwn::NetID::from_str("000505").unwrap().dev_addr_prefix());
|
|
||||||
|
|
||||||
let ds = internal::DeviceSession {
|
|
||||||
dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8],
|
|
||||||
mac_version: common::MacVersion::Lorawan104.into(),
|
|
||||||
join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1],
|
|
||||||
dev_addr: dev_addr.to_vec(),
|
|
||||||
f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
|
||||||
s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
|
||||||
nwk_s_enc_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
|
||||||
app_s_key: Some(common::KeyEnvelope {
|
|
||||||
kek_label: "".into(),
|
|
||||||
aes_key: vec![16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
|
|
||||||
}),
|
|
||||||
f_cnt_up: 8,
|
|
||||||
n_f_cnt_down: 5,
|
|
||||||
enabled_uplink_channel_indices: vec![0, 1, 2],
|
|
||||||
rx1_delay: 1,
|
|
||||||
rx2_frequency: 869525000,
|
|
||||||
region_config_id: "eu868".into(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
device_session::save(&ds).await.unwrap();
|
|
||||||
|
|
||||||
let mut data_phy = lrwn::PhyPayload {
|
let mut data_phy = lrwn::PhyPayload {
|
||||||
mhdr: lrwn::MHDR {
|
mhdr: lrwn::MHDR {
|
||||||
|
@ -2,22 +2,24 @@ use std::future::Future;
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use prost::Message;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::assert;
|
use super::assert;
|
||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
application,
|
application,
|
||||||
device::{self, DeviceClass},
|
device::{self, DeviceClass},
|
||||||
device_profile, device_queue, device_session, gateway, mac_command, reset_redis, tenant,
|
device_profile, device_queue, gateway, mac_command, reset_redis, tenant,
|
||||||
};
|
};
|
||||||
use crate::{config, gateway::backend as gateway_backend, integration, region, test, uplink};
|
use crate::{config, gateway::backend as gateway_backend, integration, region, test, uplink};
|
||||||
use chirpstack_api::{common, gw, integration as integration_pb, internal, stream};
|
use chirpstack_api::{common, gw, integration as integration_pb, internal, stream};
|
||||||
use lrwn::{AES128Key, EUI64};
|
use lrwn::{AES128Key, DevAddr, EUI64};
|
||||||
|
|
||||||
type Function = Box<dyn Fn() -> Pin<Box<dyn Future<Output = ()>>>>;
|
type Function = Box<dyn Fn() -> Pin<Box<dyn Future<Output = ()>>>>;
|
||||||
|
|
||||||
struct Test {
|
struct Test {
|
||||||
name: String,
|
name: String,
|
||||||
|
dev_eui: EUI64,
|
||||||
device_queue_items: Vec<device_queue::DeviceQueueItem>,
|
device_queue_items: Vec<device_queue::DeviceQueueItem>,
|
||||||
before_func: Option<Function>,
|
before_func: Option<Function>,
|
||||||
after_func: Option<Function>,
|
after_func: Option<Function>,
|
||||||
@ -93,11 +95,33 @@ async fn test_gateway_filtering() {
|
|||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::B,
|
enabled_class: DeviceClass::B,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
|
device_session: Some(
|
||||||
|
internal::DeviceSession {
|
||||||
|
dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8],
|
||||||
|
mac_version: common::MacVersion::Lorawan102.into(),
|
||||||
|
join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1],
|
||||||
|
dev_addr: vec![1, 2, 3, 4],
|
||||||
|
f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||||
|
s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||||
|
nwk_s_enc_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||||
|
f_cnt_up: 7,
|
||||||
|
n_f_cnt_down: 5,
|
||||||
|
enabled_uplink_channel_indices: vec![0, 1, 2],
|
||||||
|
rx1_delay: 1,
|
||||||
|
rx2_frequency: 869525000,
|
||||||
|
region_config_id: "eu868".into(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.encode_to_vec(),
|
||||||
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let ds = dev.get_device_session().unwrap();
|
||||||
|
|
||||||
let mut rx_info_a = gw::UplinkRxInfo {
|
let mut rx_info_a = gw::UplinkRxInfo {
|
||||||
gateway_id: gw_a.gateway_id.to_string(),
|
gateway_id: gw_a.gateway_id.to_string(),
|
||||||
location: Some(Default::default()),
|
location: Some(Default::default()),
|
||||||
@ -128,26 +152,10 @@ async fn test_gateway_filtering() {
|
|||||||
};
|
};
|
||||||
uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info, 0).unwrap();
|
uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info, 0).unwrap();
|
||||||
|
|
||||||
let ds = internal::DeviceSession {
|
|
||||||
dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8],
|
|
||||||
mac_version: common::MacVersion::Lorawan102.into(),
|
|
||||||
join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1],
|
|
||||||
dev_addr: vec![1, 2, 3, 4],
|
|
||||||
f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
|
||||||
s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
|
||||||
nwk_s_enc_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
|
||||||
f_cnt_up: 7,
|
|
||||||
n_f_cnt_down: 5,
|
|
||||||
enabled_uplink_channel_indices: vec![0, 1, 2],
|
|
||||||
rx1_delay: 1,
|
|
||||||
rx2_frequency: 869525000,
|
|
||||||
region_config_id: "eu868".into(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "private gateway of same tenant".into(),
|
name: "private gateway of same tenant".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -174,6 +182,7 @@ async fn test_gateway_filtering() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "private gateway other tenant".into(),
|
name: "private gateway other tenant".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -257,6 +266,7 @@ async fn test_region_config_id_filtering() {
|
|||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -312,6 +322,7 @@ async fn test_region_config_id_filtering() {
|
|||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "matching config id".into(),
|
name: "matching config id".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -338,6 +349,7 @@ async fn test_region_config_id_filtering() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "non-matching configuration id".into(),
|
name: "non-matching configuration id".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -410,12 +422,13 @@ async fn test_lorawan_10_errors() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let _dev = device::create(device::Device {
|
let dev = device::create(device::Device {
|
||||||
name: "device".into(),
|
name: "device".into(),
|
||||||
application_id: app.id.clone(),
|
application_id: app.id.clone(),
|
||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -459,6 +472,7 @@ async fn test_lorawan_10_errors() {
|
|||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "invalid frame-counter (did not increment)".into(),
|
name: "invalid frame-counter (did not increment)".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -491,6 +505,7 @@ async fn test_lorawan_10_errors() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "invalid frame-counter (reset)".into(),
|
name: "invalid frame-counter (reset)".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -522,6 +537,7 @@ async fn test_lorawan_10_errors() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "invalid mic".into(),
|
name: "invalid mic".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -605,12 +621,13 @@ async fn test_lorawan_11_errors() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let _dev = device::create(device::Device {
|
let dev = device::create(device::Device {
|
||||||
name: "device".into(),
|
name: "device".into(),
|
||||||
application_id: app.id.clone(),
|
application_id: app.id.clone(),
|
||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -660,6 +677,7 @@ async fn test_lorawan_11_errors() {
|
|||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "invalid frequency (MIC)".into(),
|
name: "invalid frequency (MIC)".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -686,6 +704,7 @@ async fn test_lorawan_11_errors() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "invalid frequency (MIC)".into(),
|
name: "invalid frequency (MIC)".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -765,6 +784,7 @@ async fn test_lorawan_10_skip_f_cnt() {
|
|||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
skip_fcnt_check: true,
|
skip_fcnt_check: true,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -809,6 +829,7 @@ async fn test_lorawan_10_skip_f_cnt() {
|
|||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "frame-counter is invalid but not 0".into(),
|
name: "frame-counter is invalid but not 0".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -857,6 +878,7 @@ async fn test_lorawan_10_skip_f_cnt() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "frame-counter is invalid and 0".into(),
|
name: "frame-counter is invalid and 0".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -958,6 +980,7 @@ async fn test_lorawan_10_device_disabled() {
|
|||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
is_disabled: true,
|
is_disabled: true,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -1000,6 +1023,7 @@ async fn test_lorawan_10_device_disabled() {
|
|||||||
|
|
||||||
let tests = vec![Test {
|
let tests = vec![Test {
|
||||||
name: "uplink ignored".into(),
|
name: "uplink ignored".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -1081,6 +1105,7 @@ async fn test_lorawan_10_uplink() {
|
|||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -1134,6 +1159,7 @@ async fn test_lorawan_10_uplink() {
|
|||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink with payload".into(),
|
name: "unconfirmed uplink with payload".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -1184,6 +1210,7 @@ async fn test_lorawan_10_uplink() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink with payload using LR-FHSS dr".into(),
|
name: "unconfirmed uplink with payload using LR-FHSS dr".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: Some(Box::new(move || {
|
before_func: Some(Box::new(move || {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
@ -1255,6 +1282,7 @@ async fn test_lorawan_10_uplink() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink with payload + ACK".into(),
|
name: "unconfirmed uplink with payload + ACK".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -1333,6 +1361,7 @@ async fn test_lorawan_10_uplink() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink without payload (just FPort)".into(),
|
name: "unconfirmed uplink without payload (just FPort)".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -1383,6 +1412,7 @@ async fn test_lorawan_10_uplink() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "confirmed uplink with payload".into(),
|
name: "confirmed uplink with payload".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -1494,6 +1524,7 @@ async fn test_lorawan_10_uplink() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "confirmed uplink without payload".into(),
|
name: "confirmed uplink without payload".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -1604,21 +1635,34 @@ async fn test_lorawan_10_uplink() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "uplink of class-c device updates scheduler_run_after".into(),
|
name: "uplink of class-c device updates scheduler_run_after".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: Some(Box::new(move || {
|
before_func: Some(Box::new(move || {
|
||||||
let dev_eui = dev.dev_eui;
|
let dev_eui = dev.dev_eui;
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
device::set_enabled_class(&dev_eui, device::DeviceClass::C)
|
device::partial_update(
|
||||||
.await
|
dev_eui,
|
||||||
.unwrap();
|
&device::DeviceChangeset {
|
||||||
|
enabled_class: Some(device::DeviceClass::C),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
})
|
})
|
||||||
})),
|
})),
|
||||||
after_func: Some(Box::new(move || {
|
after_func: Some(Box::new(move || {
|
||||||
let dev_eui = dev.dev_eui;
|
let dev_eui = dev.dev_eui;
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
device::set_enabled_class(&dev_eui, device::DeviceClass::A)
|
device::partial_update(
|
||||||
.await
|
dev_eui,
|
||||||
.unwrap();
|
&device::DeviceChangeset {
|
||||||
|
enabled_class: Some(device::DeviceClass::A),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
})
|
})
|
||||||
})),
|
})),
|
||||||
device_session: Some(ds.clone()),
|
device_session: Some(ds.clone()),
|
||||||
@ -1700,6 +1744,7 @@ async fn test_lorawan_10_end_to_end_enc() {
|
|||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -1766,6 +1811,7 @@ async fn test_lorawan_10_end_to_end_enc() {
|
|||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "end-to-end encryption with session key id".into(),
|
name: "end-to-end encryption with session key id".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -1816,6 +1862,7 @@ async fn test_lorawan_10_end_to_end_enc() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "end-to-end encryption with AppSKey".into(),
|
name: "end-to-end encryption with AppSKey".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -1869,6 +1916,7 @@ async fn test_lorawan_10_end_to_end_enc() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "end-to-end encryption using AppSkey + encrypted downlink".into(),
|
name: "end-to-end encryption using AppSkey + encrypted downlink".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -2028,6 +2076,7 @@ async fn test_lorawan_11_uplink() {
|
|||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -2082,6 +2131,7 @@ async fn test_lorawan_11_uplink() {
|
|||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink with payload".into(),
|
name: "unconfirmed uplink with payload".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -2132,6 +2182,7 @@ async fn test_lorawan_11_uplink() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink with payload + ACK".into(),
|
name: "unconfirmed uplink with payload + ACK".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -2266,6 +2317,7 @@ async fn test_lorawan_10_rx_delay() {
|
|||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -2318,6 +2370,7 @@ async fn test_lorawan_10_rx_delay() {
|
|||||||
|
|
||||||
let tests = vec![Test {
|
let tests = vec![Test {
|
||||||
name: "confirmed uplink without payload (rx_delay = 3)".into(),
|
name: "confirmed uplink without payload (rx_delay = 3)".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -2479,6 +2532,7 @@ async fn test_lorawan_10_mac_commands() {
|
|||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -2532,6 +2586,7 @@ async fn test_lorawan_10_mac_commands() {
|
|||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink + device-status request downlink (FOpts)".into(),
|
name: "unconfirmed uplink + device-status request downlink (FOpts)".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: Some(Box::new(move || {
|
before_func: Some(Box::new(move || {
|
||||||
let dp_id = dp.id.clone();
|
let dp_id = dp.id.clone();
|
||||||
@ -2624,6 +2679,7 @@ async fn test_lorawan_10_mac_commands() {
|
|||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink + device-status request downlink (FOpts) + downlink payload"
|
name: "unconfirmed uplink + device-status request downlink (FOpts) + downlink payload"
|
||||||
.into(),
|
.into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -2721,6 +2777,7 @@ async fn test_lorawan_10_mac_commands() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "RxTimingSetupAns is answered with an empty downlink".into(),
|
name: "RxTimingSetupAns is answered with an empty downlink".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -2848,6 +2905,7 @@ async fn test_lorawan_11_mac_commands() {
|
|||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -2928,6 +2986,7 @@ async fn test_lorawan_11_mac_commands() {
|
|||||||
|
|
||||||
let tests = vec![Test {
|
let tests = vec![Test {
|
||||||
name: "uplink mac-command (encrypted fopts)".into(),
|
name: "uplink mac-command (encrypted fopts)".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -3042,6 +3101,7 @@ async fn test_lorawan_10_device_queue() {
|
|||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -3089,6 +3149,7 @@ async fn test_lorawan_10_device_queue() {
|
|||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink + one unconfirmed downlink payload in queue".into(),
|
name: "unconfirmed uplink + one unconfirmed downlink payload in queue".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -3166,6 +3227,7 @@ async fn test_lorawan_10_device_queue() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink + two unconfirmed downlinks payload in queue".into(),
|
name: "unconfirmed uplink + two unconfirmed downlinks payload in queue".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![
|
device_queue_items: vec![
|
||||||
device_queue::DeviceQueueItem {
|
device_queue::DeviceQueueItem {
|
||||||
id: Uuid::new_v4(),
|
id: Uuid::new_v4(),
|
||||||
@ -3256,6 +3318,7 @@ async fn test_lorawan_10_device_queue() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink + one confirmed downlink payload in queue".into(),
|
name: "unconfirmed uplink + one confirmed downlink payload in queue".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -3334,6 +3397,7 @@ async fn test_lorawan_10_device_queue() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink data + downlink payload which exceeds the max payload size (for dr 0)".into(),
|
name: "unconfirmed uplink data + downlink payload which exceeds the max payload size (for dr 0)".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -3370,6 +3434,7 @@ async fn test_lorawan_10_device_queue() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink data + one unconfirmed downlink payload in queue (exactly max size for dr 0) + one mac command".into(),
|
name: "unconfirmed uplink data + one unconfirmed downlink payload in queue (exactly max size for dr 0) + one mac command".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -3515,6 +3580,7 @@ async fn test_lorawan_11_device_queue() {
|
|||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -3563,6 +3629,7 @@ async fn test_lorawan_11_device_queue() {
|
|||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink + one unconfirmed downlink payload in queue".into(),
|
name: "unconfirmed uplink + one unconfirmed downlink payload in queue".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -3640,6 +3707,7 @@ async fn test_lorawan_11_device_queue() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink + two unconfirmed downlinks payload in queue".into(),
|
name: "unconfirmed uplink + two unconfirmed downlinks payload in queue".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![
|
device_queue_items: vec![
|
||||||
device_queue::DeviceQueueItem {
|
device_queue::DeviceQueueItem {
|
||||||
id: Uuid::new_v4(),
|
id: Uuid::new_v4(),
|
||||||
@ -3730,6 +3798,7 @@ async fn test_lorawan_11_device_queue() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink + one confirmed downlink payload in queue".into(),
|
name: "unconfirmed uplink + one confirmed downlink payload in queue".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -3808,6 +3877,7 @@ async fn test_lorawan_11_device_queue() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink data + downlink payload which exceeds the max payload size (for dr 0)".into(),
|
name: "unconfirmed uplink data + downlink payload which exceeds the max payload size (for dr 0)".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -3844,6 +3914,7 @@ async fn test_lorawan_11_device_queue() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "unconfirmed uplink data + one unconfirmed downlink payload in queue (exactly max size for dr 0) + one mac command".into(),
|
name: "unconfirmed uplink data + one unconfirmed downlink payload in queue (exactly max size for dr 0) + one mac command".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -3992,6 +4063,7 @@ async fn test_lorawan_10_adr() {
|
|||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -4057,6 +4129,7 @@ async fn test_lorawan_10_adr() {
|
|||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "adr triggered".into(),
|
name: "adr triggered".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -4160,6 +4233,7 @@ async fn test_lorawan_10_adr() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "device has adr disabled".into(),
|
name: "device has adr disabled".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -4193,6 +4267,7 @@ async fn test_lorawan_10_adr() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "acknowledgement of pending adr request".into(),
|
name: "acknowledgement of pending adr request".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: Some(Box::new(move || {
|
before_func: Some(Box::new(move || {
|
||||||
let dev_eui = dev.dev_eui.clone();
|
let dev_eui = dev.dev_eui.clone();
|
||||||
@ -4260,6 +4335,7 @@ async fn test_lorawan_10_adr() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "negative acknowledgement of pending adr request".into(),
|
name: "negative acknowledgement of pending adr request".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: Some(Box::new(move || {
|
before_func: Some(Box::new(move || {
|
||||||
let dev_eui = dev.dev_eui.clone();
|
let dev_eui = dev.dev_eui.clone();
|
||||||
@ -4327,6 +4403,7 @@ async fn test_lorawan_10_adr() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "adr ack requested".into(),
|
name: "adr ack requested".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -4403,6 +4480,7 @@ async fn test_lorawan_10_adr() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "channel re-configuration triggered".into(),
|
name: "channel re-configuration triggered".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -4502,6 +4580,7 @@ async fn test_lorawan_10_adr() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "new channel re-configuration ack-ed".into(),
|
name: "new channel re-configuration ack-ed".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: Some(Box::new(move || {
|
before_func: Some(Box::new(move || {
|
||||||
let dev_eui = dev.dev_eui.clone();
|
let dev_eui = dev.dev_eui.clone();
|
||||||
@ -4567,6 +4646,7 @@ async fn test_lorawan_10_adr() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "new channel re-configuration not ack-ed".into(),
|
name: "new channel re-configuration not ack-ed".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: Some(Box::new(move || {
|
before_func: Some(Box::new(move || {
|
||||||
let dev_eui = dev.dev_eui.clone();
|
let dev_eui = dev.dev_eui.clone();
|
||||||
@ -4634,6 +4714,7 @@ async fn test_lorawan_10_adr() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "channel re-configuration and adr triggered".into(),
|
name: "channel re-configuration and adr triggered".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -4738,6 +4819,7 @@ async fn test_lorawan_10_adr() {
|
|||||||
// adr backoff triggered
|
// adr backoff triggered
|
||||||
Test {
|
Test {
|
||||||
name: "adr backoff triggered".into(),
|
name: "adr backoff triggered".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -4835,6 +4917,7 @@ async fn test_lorawan_10_device_status_request() {
|
|||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -4887,6 +4970,7 @@ async fn test_lorawan_10_device_status_request() {
|
|||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "must request device-status".into(),
|
name: "must request device-status".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -4963,6 +5047,7 @@ async fn test_lorawan_10_device_status_request() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "interval has not yet expired".into(),
|
name: "interval has not yet expired".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -4993,6 +5078,7 @@ async fn test_lorawan_10_device_status_request() {
|
|||||||
// reporting device-status
|
// reporting device-status
|
||||||
Test {
|
Test {
|
||||||
name: "reporting device-status".into(),
|
name: "reporting device-status".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
@ -5098,6 +5184,7 @@ async fn test_lorawan_11_receive_window_selection() {
|
|||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -5155,6 +5242,7 @@ async fn test_lorawan_11_receive_window_selection() {
|
|||||||
|
|
||||||
run_test(&Test {
|
run_test(&Test {
|
||||||
name: "unconfirmed uplink with payload (rx1)".into(),
|
name: "unconfirmed uplink with payload (rx1)".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -5224,6 +5312,7 @@ async fn test_lorawan_11_receive_window_selection() {
|
|||||||
|
|
||||||
run_test(&Test {
|
run_test(&Test {
|
||||||
name: "unconfirmed uplink with payload (rx2)".into(),
|
name: "unconfirmed uplink with payload (rx2)".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -5293,6 +5382,7 @@ async fn test_lorawan_11_receive_window_selection() {
|
|||||||
|
|
||||||
run_test(&Test {
|
run_test(&Test {
|
||||||
name: "unconfirmed uplink with payload (rx1 + rx2)".into(),
|
name: "unconfirmed uplink with payload (rx1 + rx2)".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -5391,6 +5481,7 @@ async fn test_lorawan_11_receive_window_selection() {
|
|||||||
|
|
||||||
run_test(&Test {
|
run_test(&Test {
|
||||||
name: "unconfirmed uplink with payload (rx1, payload exceeds rx2 limit)".into(),
|
name: "unconfirmed uplink with payload (rx1, payload exceeds rx2 limit)".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -5497,7 +5588,6 @@ async fn test_lorawan_11_receive_window_selection() {
|
|||||||
|
|
||||||
async fn run_test(t: &Test) {
|
async fn run_test(t: &Test) {
|
||||||
println!("> {}", t.name);
|
println!("> {}", t.name);
|
||||||
|
|
||||||
reset_redis().await.unwrap();
|
reset_redis().await.unwrap();
|
||||||
|
|
||||||
integration::set_mock().await;
|
integration::set_mock().await;
|
||||||
@ -5506,12 +5596,16 @@ async fn run_test(t: &Test) {
|
|||||||
integration::mock::reset().await;
|
integration::mock::reset().await;
|
||||||
gateway_backend::mock::reset().await;
|
gateway_backend::mock::reset().await;
|
||||||
|
|
||||||
if let Some(ds) = &t.device_session {
|
device_queue::flush_for_dev_eui(&t.dev_eui).await.unwrap();
|
||||||
let _ = device_session::save(&ds).await.unwrap();
|
device::partial_update(
|
||||||
|
t.dev_eui,
|
||||||
let dev_eui = EUI64::from_slice(&ds.dev_eui).unwrap();
|
&device::DeviceChangeset {
|
||||||
device_queue::flush_for_dev_eui(&dev_eui).await.unwrap();
|
device_session: Some(t.device_session.as_ref().map(|v| v.encode_to_vec())),
|
||||||
}
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
if let Some(f) = &t.before_func {
|
if let Some(f) = &t.before_func {
|
||||||
f().await;
|
f().await;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use prost::Message;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::assert;
|
use super::assert;
|
||||||
@ -5,7 +6,7 @@ use crate::gpstime::ToGpsTime;
|
|||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
application,
|
application,
|
||||||
device::{self, DeviceClass},
|
device::{self, DeviceClass},
|
||||||
device_gateway, device_profile, device_queue, device_session, gateway, reset_redis, tenant,
|
device_gateway, device_profile, device_queue, gateway, reset_redis, tenant,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
config, downlink, downlink::classb, gateway::backend as gateway_backend, integration, test,
|
config, downlink, downlink::classb, gateway::backend as gateway_backend, integration, test,
|
||||||
@ -16,6 +17,7 @@ use lrwn::{DevAddr, EUI64};
|
|||||||
|
|
||||||
struct UplinkTest {
|
struct UplinkTest {
|
||||||
name: String,
|
name: String,
|
||||||
|
dev_eui: EUI64,
|
||||||
device_queue_items: Vec<device_queue::DeviceQueueItem>,
|
device_queue_items: Vec<device_queue::DeviceQueueItem>,
|
||||||
device_session: Option<internal::DeviceSession>,
|
device_session: Option<internal::DeviceSession>,
|
||||||
tx_info: gw::UplinkTxInfo,
|
tx_info: gw::UplinkTxInfo,
|
||||||
@ -26,6 +28,7 @@ struct UplinkTest {
|
|||||||
|
|
||||||
struct DownlinkTest {
|
struct DownlinkTest {
|
||||||
name: String,
|
name: String,
|
||||||
|
dev_eui: EUI64,
|
||||||
device_queue_items: Vec<device_queue::DeviceQueueItem>,
|
device_queue_items: Vec<device_queue::DeviceQueueItem>,
|
||||||
device_session: Option<internal::DeviceSession>,
|
device_session: Option<internal::DeviceSession>,
|
||||||
device_gateway_rx_info: Option<internal::DeviceGatewayRxInfo>,
|
device_gateway_rx_info: Option<internal::DeviceGatewayRxInfo>,
|
||||||
@ -80,6 +83,7 @@ async fn test_uplink() {
|
|||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -130,6 +134,7 @@ async fn test_uplink() {
|
|||||||
// trigger beacon locked
|
// trigger beacon locked
|
||||||
run_uplink_test(&UplinkTest {
|
run_uplink_test(&UplinkTest {
|
||||||
name: "trigger beacon locked".into(),
|
name: "trigger beacon locked".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
device_session: Some(ds.clone()),
|
device_session: Some(ds.clone()),
|
||||||
tx_info: tx_info.clone(),
|
tx_info: tx_info.clone(),
|
||||||
@ -164,6 +169,7 @@ async fn test_uplink() {
|
|||||||
// trigger beacon unlocked
|
// trigger beacon unlocked
|
||||||
run_uplink_test(&UplinkTest {
|
run_uplink_test(&UplinkTest {
|
||||||
name: "trigger beacon locked".into(),
|
name: "trigger beacon locked".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![],
|
device_queue_items: vec![],
|
||||||
device_session: Some(ds.clone()),
|
device_session: Some(ds.clone()),
|
||||||
tx_info: tx_info.clone(),
|
tx_info: tx_info.clone(),
|
||||||
@ -244,6 +250,7 @@ async fn test_downlink_scheduler() {
|
|||||||
device_profile_id: dp.id.clone(),
|
device_profile_id: dp.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
|
||||||
enabled_class: DeviceClass::B,
|
enabled_class: DeviceClass::B,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -291,6 +298,7 @@ async fn test_downlink_scheduler() {
|
|||||||
|
|
||||||
run_scheduler_test(&DownlinkTest {
|
run_scheduler_test(&DownlinkTest {
|
||||||
name: "class-b downlink".into(),
|
name: "class-b downlink".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -342,6 +350,7 @@ async fn test_downlink_scheduler() {
|
|||||||
|
|
||||||
run_scheduler_test(&DownlinkTest {
|
run_scheduler_test(&DownlinkTest {
|
||||||
name: "scheduler_run_after has not yet expired".into(),
|
name: "scheduler_run_after has not yet expired".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -356,12 +365,19 @@ async fn test_downlink_scheduler() {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
// remove the schedule run after
|
// remove the schedule run after
|
||||||
device::set_scheduler_run_after(&dev.dev_eui.clone(), None)
|
device::partial_update(
|
||||||
.await
|
dev.dev_eui,
|
||||||
.unwrap();
|
&device::DeviceChangeset {
|
||||||
|
scheduler_run_after: Some(None),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
run_scheduler_test(&DownlinkTest {
|
run_scheduler_test(&DownlinkTest {
|
||||||
name: "class-b downlink with more data".into(),
|
name: "class-b downlink with more data".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![
|
device_queue_items: vec![
|
||||||
device_queue::DeviceQueueItem {
|
device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
@ -434,12 +450,16 @@ async fn run_uplink_test(t: &UplinkTest) {
|
|||||||
integration::mock::reset().await;
|
integration::mock::reset().await;
|
||||||
gateway_backend::mock::reset().await;
|
gateway_backend::mock::reset().await;
|
||||||
|
|
||||||
if let Some(ds) = &t.device_session {
|
device_queue::flush_for_dev_eui(&t.dev_eui).await.unwrap();
|
||||||
let _ = device_session::save(&ds).await.unwrap();
|
device::partial_update(
|
||||||
|
t.dev_eui,
|
||||||
let dev_eui = EUI64::from_slice(&ds.dev_eui).unwrap();
|
&device::DeviceChangeset {
|
||||||
device_queue::flush_for_dev_eui(&dev_eui).await.unwrap();
|
device_session: Some(t.device_session.as_ref().map(|v| v.encode_to_vec())),
|
||||||
}
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
for qi in &t.device_queue_items {
|
for qi in &t.device_queue_items {
|
||||||
let _ = device_queue::enqueue_item(qi.clone()).await.unwrap();
|
let _ = device_queue::enqueue_item(qi.clone()).await.unwrap();
|
||||||
@ -471,13 +491,16 @@ async fn run_scheduler_test(t: &DownlinkTest) {
|
|||||||
|
|
||||||
integration::mock::reset().await;
|
integration::mock::reset().await;
|
||||||
gateway_backend::mock::reset().await;
|
gateway_backend::mock::reset().await;
|
||||||
|
device_queue::flush_for_dev_eui(&t.dev_eui).await.unwrap();
|
||||||
if let Some(ds) = &t.device_session {
|
device::partial_update(
|
||||||
let _ = device_session::save(&ds).await.unwrap();
|
t.dev_eui,
|
||||||
|
&device::DeviceChangeset {
|
||||||
let dev_eui = EUI64::from_slice(&ds.dev_eui).unwrap();
|
device_session: Some(t.device_session.as_ref().map(|v| v.encode_to_vec())),
|
||||||
device_queue::flush_for_dev_eui(&dev_eui).await.unwrap();
|
..Default::default()
|
||||||
}
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
if let Some(rx_info) = &t.device_gateway_rx_info {
|
if let Some(rx_info) = &t.device_gateway_rx_info {
|
||||||
let _ = device_gateway::save_rx_info(rx_info).await.unwrap();
|
let _ = device_gateway::save_rx_info(rx_info).await.unwrap();
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
use prost::Message;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::assert;
|
use super::assert;
|
||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
application,
|
application,
|
||||||
device::{self, DeviceClass},
|
device::{self, DeviceClass},
|
||||||
device_gateway, device_profile, device_queue, device_session, gateway, reset_redis, tenant,
|
device_gateway, device_profile, device_queue, gateway, reset_redis, tenant,
|
||||||
};
|
};
|
||||||
use crate::{downlink, gateway::backend as gateway_backend, integration, test};
|
use crate::{downlink, gateway::backend as gateway_backend, integration, test};
|
||||||
use chirpstack_api::{common, gw, internal};
|
use chirpstack_api::{common, gw, internal};
|
||||||
@ -12,6 +13,7 @@ use lrwn::EUI64;
|
|||||||
|
|
||||||
struct DownlinkTest {
|
struct DownlinkTest {
|
||||||
name: String,
|
name: String,
|
||||||
|
dev_eui: EUI64,
|
||||||
device_queue_items: Vec<device_queue::DeviceQueueItem>,
|
device_queue_items: Vec<device_queue::DeviceQueueItem>,
|
||||||
device_session: Option<internal::DeviceSession>,
|
device_session: Option<internal::DeviceSession>,
|
||||||
device_gateway_rx_info: Option<internal::DeviceGatewayRxInfo>,
|
device_gateway_rx_info: Option<internal::DeviceGatewayRxInfo>,
|
||||||
@ -112,6 +114,7 @@ async fn test_downlink_scheduler() {
|
|||||||
|
|
||||||
run_scheduler_test(&DownlinkTest {
|
run_scheduler_test(&DownlinkTest {
|
||||||
name: "device has not yet sent an uplink".into(),
|
name: "device has not yet sent an uplink".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -126,12 +129,19 @@ async fn test_downlink_scheduler() {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
// remove the schedule run after
|
// remove the schedule run after
|
||||||
device::set_scheduler_run_after(&dev.dev_eui.clone(), None)
|
device::partial_update(
|
||||||
.await
|
dev.dev_eui,
|
||||||
.unwrap();
|
&device::DeviceChangeset {
|
||||||
|
scheduler_run_after: Some(None),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
run_scheduler_test(&DownlinkTest {
|
run_scheduler_test(&DownlinkTest {
|
||||||
name: "unconfirmed data".into(),
|
name: "unconfirmed data".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -178,6 +188,7 @@ async fn test_downlink_scheduler() {
|
|||||||
|
|
||||||
run_scheduler_test(&DownlinkTest {
|
run_scheduler_test(&DownlinkTest {
|
||||||
name: "scheduler_run_after has not yet expired".into(),
|
name: "scheduler_run_after has not yet expired".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -192,12 +203,19 @@ async fn test_downlink_scheduler() {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
// remove the schedule run after
|
// remove the schedule run after
|
||||||
device::set_scheduler_run_after(&dev.dev_eui.clone(), None)
|
device::partial_update(
|
||||||
.await
|
dev.dev_eui,
|
||||||
.unwrap();
|
&device::DeviceChangeset {
|
||||||
|
scheduler_run_after: Some(None),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
run_scheduler_test(&DownlinkTest {
|
run_scheduler_test(&DownlinkTest {
|
||||||
name: "unconfirmed data".into(),
|
name: "unconfirmed data".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -246,12 +264,19 @@ async fn test_downlink_scheduler() {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
// remove the schedule run after
|
// remove the schedule run after
|
||||||
device::set_scheduler_run_after(&dev.dev_eui.clone(), None)
|
device::partial_update(
|
||||||
.await
|
dev.dev_eui,
|
||||||
.unwrap();
|
&device::DeviceChangeset {
|
||||||
|
scheduler_run_after: Some(None),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
run_scheduler_test(&DownlinkTest {
|
run_scheduler_test(&DownlinkTest {
|
||||||
name: "unconfirmed data".into(),
|
name: "unconfirmed data".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
device_queue_items: vec![device_queue::DeviceQueueItem {
|
device_queue_items: vec![device_queue::DeviceQueueItem {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
dev_eui: dev.dev_eui.clone(),
|
dev_eui: dev.dev_eui.clone(),
|
||||||
@ -276,13 +301,16 @@ async fn run_scheduler_test(t: &DownlinkTest) {
|
|||||||
|
|
||||||
integration::mock::reset().await;
|
integration::mock::reset().await;
|
||||||
gateway_backend::mock::reset().await;
|
gateway_backend::mock::reset().await;
|
||||||
|
device_queue::flush_for_dev_eui(&t.dev_eui).await.unwrap();
|
||||||
if let Some(ds) = &t.device_session {
|
device::partial_update(
|
||||||
let _ = device_session::save(&ds).await.unwrap();
|
t.dev_eui,
|
||||||
|
&device::DeviceChangeset {
|
||||||
let dev_eui = EUI64::from_slice(&ds.dev_eui).unwrap();
|
device_session: Some(t.device_session.as_ref().map(|v| v.encode_to_vec())),
|
||||||
device_queue::flush_for_dev_eui(&dev_eui).await.unwrap();
|
..Default::default()
|
||||||
}
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
if let Some(rx_info) = &t.device_gateway_rx_info {
|
if let Some(rx_info) = &t.device_gateway_rx_info {
|
||||||
let _ = device_gateway::save_rx_info(rx_info).await.unwrap();
|
let _ = device_gateway::save_rx_info(rx_info).await.unwrap();
|
||||||
|
@ -19,6 +19,7 @@ type Function = Box<dyn Fn() -> Pin<Box<dyn Future<Output = ()>>>>;
|
|||||||
|
|
||||||
struct Test {
|
struct Test {
|
||||||
name: String,
|
name: String,
|
||||||
|
dev_eui: EUI64,
|
||||||
before_func: Option<Function>,
|
before_func: Option<Function>,
|
||||||
after_func: Option<Function>,
|
after_func: Option<Function>,
|
||||||
tx_info: gw::UplinkTxInfo,
|
tx_info: gw::UplinkTxInfo,
|
||||||
@ -153,6 +154,7 @@ async fn test_gateway_filtering() {
|
|||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "private gateway of same tenant".into(),
|
name: "private gateway of same tenant".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
before_func: Some(Box::new(move || {
|
before_func: Some(Box::new(move || {
|
||||||
let dev_eui = dev.dev_eui.clone();
|
let dev_eui = dev.dev_eui.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
@ -198,6 +200,7 @@ async fn test_gateway_filtering() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "private gateway other tenant".into(),
|
name: "private gateway other tenant".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
before_func: Some(Box::new(move || {
|
before_func: Some(Box::new(move || {
|
||||||
let dev_eui = dev.dev_eui.clone();
|
let dev_eui = dev.dev_eui.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
@ -375,6 +378,7 @@ async fn test_lorawan_10() {
|
|||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "dev-nonce already used".into(),
|
name: "dev-nonce already used".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
rx_info: rx_info.clone(),
|
rx_info: rx_info.clone(),
|
||||||
@ -387,6 +391,7 @@ async fn test_lorawan_10() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "join-request accepted".into(),
|
name: "join-request accepted".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
before_func: Some(Box::new(move || {
|
before_func: Some(Box::new(move || {
|
||||||
let dev_eui = dev.dev_eui.clone();
|
let dev_eui = dev.dev_eui.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
@ -576,6 +581,7 @@ async fn test_lorawan_10() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "join-request accepted + skip fcnt check".into(),
|
name: "join-request accepted + skip fcnt check".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
before_func: Some(Box::new(move || {
|
before_func: Some(Box::new(move || {
|
||||||
let dev_eui = dev.dev_eui.clone();
|
let dev_eui = dev.dev_eui.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
@ -633,6 +639,7 @@ async fn test_lorawan_10() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "join-request accepted + cflist".into(),
|
name: "join-request accepted + cflist".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
before_func: Some(Box::new(move || {
|
before_func: Some(Box::new(move || {
|
||||||
let dev_eui = dev.dev_eui.clone();
|
let dev_eui = dev.dev_eui.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
@ -786,6 +793,7 @@ async fn test_lorawan_10() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "join-request accepted + class-b supported".into(),
|
name: "join-request accepted + class-b supported".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
before_func: Some(Box::new(move || {
|
before_func: Some(Box::new(move || {
|
||||||
let dev_eui = dev.dev_eui.clone();
|
let dev_eui = dev.dev_eui.clone();
|
||||||
let dp_id = dp.id.clone();
|
let dp_id = dp.id.clone();
|
||||||
@ -813,6 +821,7 @@ async fn test_lorawan_10() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "join-request accepted + class-c supported".into(),
|
name: "join-request accepted + class-c supported".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
before_func: Some(Box::new(move || {
|
before_func: Some(Box::new(move || {
|
||||||
let dev_eui = dev.dev_eui.clone();
|
let dev_eui = dev.dev_eui.clone();
|
||||||
let dp_id = dp.id.clone();
|
let dp_id = dp.id.clone();
|
||||||
@ -840,6 +849,7 @@ async fn test_lorawan_10() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "device disabled".into(),
|
name: "device disabled".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
before_func: Some(Box::new(move || {
|
before_func: Some(Box::new(move || {
|
||||||
let dev_eui = dev.dev_eui.clone();
|
let dev_eui = dev.dev_eui.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
@ -997,6 +1007,7 @@ async fn test_lorawan_11() {
|
|||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "dev-nonce already used".into(),
|
name: "dev-nonce already used".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
before_func: None,
|
before_func: None,
|
||||||
after_func: None,
|
after_func: None,
|
||||||
rx_info: rx_info.clone(),
|
rx_info: rx_info.clone(),
|
||||||
@ -1009,6 +1020,7 @@ async fn test_lorawan_11() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "join-request accepted".into(),
|
name: "join-request accepted".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
before_func: Some(Box::new(move || {
|
before_func: Some(Box::new(move || {
|
||||||
let dev_eui = dev.dev_eui.clone();
|
let dev_eui = dev.dev_eui.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
@ -1189,6 +1201,7 @@ async fn test_lorawan_11() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "join-request accepted + class-c supported".into(),
|
name: "join-request accepted + class-c supported".into(),
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
before_func: Some(Box::new(move || {
|
before_func: Some(Box::new(move || {
|
||||||
let dev_eui = dev.dev_eui.clone();
|
let dev_eui = dev.dev_eui.clone();
|
||||||
let dp_id = dp.id.clone();
|
let dp_id = dp.id.clone();
|
||||||
@ -1224,8 +1237,6 @@ async fn test_lorawan_11() {
|
|||||||
async fn run_test(t: &Test) {
|
async fn run_test(t: &Test) {
|
||||||
println!("> {}", t.name);
|
println!("> {}", t.name);
|
||||||
|
|
||||||
reset_db().await.unwrap();
|
|
||||||
|
|
||||||
let mut conf: config::Configuration = (*config::get()).clone();
|
let mut conf: config::Configuration = (*config::get()).clone();
|
||||||
for f in &t.extra_uplink_channels {
|
for f in &t.extra_uplink_channels {
|
||||||
conf.regions[0]
|
conf.regions[0]
|
||||||
@ -1246,6 +1257,17 @@ async fn run_test(t: &Test) {
|
|||||||
integration::mock::reset().await;
|
integration::mock::reset().await;
|
||||||
gateway_backend::mock::reset().await;
|
gateway_backend::mock::reset().await;
|
||||||
|
|
||||||
|
device::partial_update(
|
||||||
|
t.dev_eui,
|
||||||
|
&device::DeviceChangeset {
|
||||||
|
dev_addr: Some(None),
|
||||||
|
device_session: Some(None),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
if let Some(f) = &t.before_func {
|
if let Some(f) = &t.before_func {
|
||||||
f().await;
|
f().await;
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use prost::Message;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::assert;
|
use super::assert;
|
||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
application,
|
application,
|
||||||
device::{self, DeviceClass},
|
device::{self, DeviceClass},
|
||||||
device_profile, device_queue, device_session, gateway, reset_redis, tenant,
|
device_profile, device_queue, gateway, reset_redis, tenant,
|
||||||
};
|
};
|
||||||
use crate::{gateway::backend as gateway_backend, integration, test, uplink};
|
use crate::{gateway::backend as gateway_backend, integration, test, uplink};
|
||||||
use chirpstack_api::{common, gw, integration as integration_pb, internal};
|
use chirpstack_api::{common, gw, integration as integration_pb, internal};
|
||||||
use lrwn::{AES128Key, EUI64};
|
use lrwn::{AES128Key, DevAddr, EUI64};
|
||||||
|
|
||||||
struct Test {
|
struct Test {
|
||||||
name: String,
|
name: String,
|
||||||
|
dev_eui_relay: EUI64,
|
||||||
|
dev_eui_relay_ed: EUI64,
|
||||||
device_queue_items_relay_ed: Vec<device_queue::DeviceQueueItem>,
|
device_queue_items_relay_ed: Vec<device_queue::DeviceQueueItem>,
|
||||||
device_session_relay: Option<internal::DeviceSession>,
|
device_session_relay: Option<internal::DeviceSession>,
|
||||||
device_session_relay_ed: Option<internal::DeviceSession>,
|
device_session_relay_ed: Option<internal::DeviceSession>,
|
||||||
@ -85,6 +88,7 @@ async fn test_lorawan_10() {
|
|||||||
device_profile_id: dp_relay.id,
|
device_profile_id: dp_relay.id,
|
||||||
dev_eui: EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 1]),
|
dev_eui: EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 1]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([1, 1, 1, 1])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -96,6 +100,7 @@ async fn test_lorawan_10() {
|
|||||||
device_profile_id: dp_relay_ed.id,
|
device_profile_id: dp_relay_ed.id,
|
||||||
dev_eui: EUI64::from_be_bytes([2, 2, 2, 2, 2, 2, 2, 2]),
|
dev_eui: EUI64::from_be_bytes([2, 2, 2, 2, 2, 2, 2, 2]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([2, 2, 2, 2])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -439,6 +444,8 @@ async fn test_lorawan_10() {
|
|||||||
let tests = vec![
|
let tests = vec![
|
||||||
Test {
|
Test {
|
||||||
name: "relayed unconfirmed uplink".into(),
|
name: "relayed unconfirmed uplink".into(),
|
||||||
|
dev_eui_relay: dev_relay.dev_eui,
|
||||||
|
dev_eui_relay_ed: dev_relay_ed.dev_eui,
|
||||||
device_queue_items_relay_ed: vec![],
|
device_queue_items_relay_ed: vec![],
|
||||||
device_session_relay: Some(ds_relay.clone()),
|
device_session_relay: Some(ds_relay.clone()),
|
||||||
device_session_relay_ed: Some(ds_relay_ed.clone()),
|
device_session_relay_ed: Some(ds_relay_ed.clone()),
|
||||||
@ -503,6 +510,8 @@ async fn test_lorawan_10() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "relayed confirmed uplink".into(),
|
name: "relayed confirmed uplink".into(),
|
||||||
|
dev_eui_relay: dev_relay.dev_eui,
|
||||||
|
dev_eui_relay_ed: dev_relay_ed.dev_eui,
|
||||||
device_queue_items_relay_ed: vec![],
|
device_queue_items_relay_ed: vec![],
|
||||||
device_session_relay: Some(ds_relay.clone()),
|
device_session_relay: Some(ds_relay.clone()),
|
||||||
device_session_relay_ed: Some(ds_relay_ed.clone()),
|
device_session_relay_ed: Some(ds_relay_ed.clone()),
|
||||||
@ -627,6 +636,8 @@ async fn test_lorawan_10() {
|
|||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
name: "relayed unconfirmed uplink + adr_ack_req".into(),
|
name: "relayed unconfirmed uplink + adr_ack_req".into(),
|
||||||
|
dev_eui_relay: dev_relay.dev_eui,
|
||||||
|
dev_eui_relay_ed: dev_relay_ed.dev_eui,
|
||||||
device_queue_items_relay_ed: vec![],
|
device_queue_items_relay_ed: vec![],
|
||||||
device_session_relay: Some(ds_relay.clone()),
|
device_session_relay: Some(ds_relay.clone()),
|
||||||
device_session_relay_ed: Some(ds_relay_ed.clone()),
|
device_session_relay_ed: Some(ds_relay_ed.clone()),
|
||||||
@ -765,20 +776,34 @@ async fn run_test(t: &Test) {
|
|||||||
|
|
||||||
integration::mock::reset().await;
|
integration::mock::reset().await;
|
||||||
gateway_backend::mock::reset().await;
|
gateway_backend::mock::reset().await;
|
||||||
|
device_queue::flush_for_dev_eui(&t.dev_eui_relay)
|
||||||
if let Some(ds) = &t.device_session_relay {
|
.await
|
||||||
let _ = device_session::save(&ds).await.unwrap();
|
.unwrap();
|
||||||
|
device_queue::flush_for_dev_eui(&t.dev_eui_relay_ed)
|
||||||
let dev_eui = EUI64::from_slice(&ds.dev_eui).unwrap();
|
.await
|
||||||
device_queue::flush_for_dev_eui(&dev_eui).await.unwrap();
|
.unwrap();
|
||||||
}
|
device::partial_update(
|
||||||
|
t.dev_eui_relay,
|
||||||
if let Some(ds) = &t.device_session_relay_ed {
|
&device::DeviceChangeset {
|
||||||
let _ = device_session::save(&ds).await.unwrap();
|
device_session: Some(t.device_session_relay.as_ref().map(|v| v.encode_to_vec())),
|
||||||
|
..Default::default()
|
||||||
let dev_eui = EUI64::from_slice(&ds.dev_eui).unwrap();
|
},
|
||||||
device_queue::flush_for_dev_eui(&dev_eui).await.unwrap();
|
)
|
||||||
}
|
.await
|
||||||
|
.unwrap();
|
||||||
|
device::partial_update(
|
||||||
|
t.dev_eui_relay_ed,
|
||||||
|
&device::DeviceChangeset {
|
||||||
|
device_session: Some(
|
||||||
|
t.device_session_relay_ed
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.encode_to_vec()),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
for qi in &t.device_queue_items_relay_ed {
|
for qi in &t.device_queue_items_relay_ed {
|
||||||
let _ = device_queue::enqueue_item(qi.clone()).await.unwrap();
|
let _ = device_queue::enqueue_item(qi.clone()).await.unwrap();
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use prost::Message;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::assert;
|
use super::assert;
|
||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
application,
|
application,
|
||||||
device::{self, DeviceClass},
|
device::{self, DeviceClass},
|
||||||
device_keys, device_profile, device_session, gateway, tenant,
|
device_keys, device_profile, gateway, tenant,
|
||||||
};
|
};
|
||||||
use crate::{gateway::backend as gateway_backend, integration, test, uplink};
|
use crate::{gateway::backend as gateway_backend, integration, test, uplink};
|
||||||
use chirpstack_api::{common, gw, internal};
|
use chirpstack_api::{common, gw, internal};
|
||||||
use lrwn::{AES128Key, EUI64};
|
use lrwn::{AES128Key, DevAddr, EUI64};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_lorawan_10() {
|
async fn test_lorawan_10() {
|
||||||
@ -96,26 +97,30 @@ async fn test_lorawan_10() {
|
|||||||
device_profile_id: dp_relay.id.clone(),
|
device_profile_id: dp_relay.id.clone(),
|
||||||
dev_eui: EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 2]),
|
dev_eui: EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 2]),
|
||||||
enabled_class: DeviceClass::A,
|
enabled_class: DeviceClass::A,
|
||||||
|
dev_addr: Some(DevAddr::from_be_bytes([4, 3, 2, 1])),
|
||||||
|
device_session: Some(
|
||||||
|
internal::DeviceSession {
|
||||||
|
dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 2],
|
||||||
|
mac_version: common::MacVersion::Lorawan102.into(),
|
||||||
|
dev_addr: vec![4, 3, 2, 1],
|
||||||
|
f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||||
|
s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||||
|
nwk_s_enc_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||||
|
f_cnt_up: 10,
|
||||||
|
n_f_cnt_down: 5,
|
||||||
|
rx1_delay: 1,
|
||||||
|
rx2_frequency: 869525000,
|
||||||
|
region_config_id: "eu868".into(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.encode_to_vec(),
|
||||||
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let ds_relay = internal::DeviceSession {
|
let ds_relay = dev_relay.get_device_session().unwrap();
|
||||||
dev_eui: dev_relay.dev_eui.to_vec(),
|
|
||||||
mac_version: common::MacVersion::Lorawan102.into(),
|
|
||||||
dev_addr: vec![4, 3, 2, 1],
|
|
||||||
f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
|
||||||
s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
|
||||||
nwk_s_enc_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
|
||||||
f_cnt_up: 10,
|
|
||||||
n_f_cnt_down: 5,
|
|
||||||
rx1_delay: 1,
|
|
||||||
rx2_frequency: 869525000,
|
|
||||||
region_config_id: "eu868".into(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
device_session::save(&ds_relay).await.unwrap();
|
|
||||||
|
|
||||||
let mut rx_info = gw::UplinkRxInfo {
|
let mut rx_info = gw::UplinkRxInfo {
|
||||||
gateway_id: gw.gateway_id.to_string(),
|
gateway_id: gw.gateway_id.to_string(),
|
||||||
|
@ -18,7 +18,7 @@ use crate::storage::error::Error as StorageError;
|
|||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
application,
|
application,
|
||||||
device::{self, DeviceClass},
|
device::{self, DeviceClass},
|
||||||
device_gateway, device_profile, device_queue, device_session, fields,
|
device_gateway, device_profile, device_queue, fields,
|
||||||
helpers::get_all_device_data,
|
helpers::get_all_device_data,
|
||||||
metrics, tenant,
|
metrics, tenant,
|
||||||
};
|
};
|
||||||
@ -123,7 +123,6 @@ impl Data {
|
|||||||
let span = tracing::Span::current();
|
let span = tracing::Span::current();
|
||||||
span.record("dev_eui", ctx.device.as_ref().unwrap().dev_eui.to_string());
|
span.record("dev_eui", ctx.device.as_ref().unwrap().dev_eui.to_string());
|
||||||
|
|
||||||
ctx.abort_on_device_is_disabled().await?;
|
|
||||||
ctx.set_device_info()?;
|
ctx.set_device_info()?;
|
||||||
ctx.set_device_gateway_rx_info()?;
|
ctx.set_device_gateway_rx_info()?;
|
||||||
ctx.handle_retransmission_reset().await?;
|
ctx.handle_retransmission_reset().await?;
|
||||||
@ -193,7 +192,6 @@ impl Data {
|
|||||||
|
|
||||||
ctx.get_device_session_relayed().await?;
|
ctx.get_device_session_relayed().await?;
|
||||||
ctx.get_device_data().await?;
|
ctx.get_device_data().await?;
|
||||||
ctx.abort_on_device_is_disabled().await?;
|
|
||||||
ctx.set_device_info()?;
|
ctx.set_device_info()?;
|
||||||
ctx.set_relay_rx_info()?;
|
ctx.set_relay_rx_info()?;
|
||||||
ctx.handle_retransmission_reset().await?;
|
ctx.handle_retransmission_reset().await?;
|
||||||
@ -210,6 +208,7 @@ impl Data {
|
|||||||
ctx.sync_uplink_f_cnt()?;
|
ctx.sync_uplink_f_cnt()?;
|
||||||
ctx.set_region_config_id()?;
|
ctx.set_region_config_id()?;
|
||||||
ctx.save_device_session().await?;
|
ctx.save_device_session().await?;
|
||||||
|
ctx.update_device().await?;
|
||||||
ctx.handle_uplink_ack().await?;
|
ctx.handle_uplink_ack().await?;
|
||||||
ctx.save_metrics_relayed().await?;
|
ctx.save_metrics_relayed().await?;
|
||||||
ctx.start_downlink_data_flow_relayed().await?;
|
ctx.start_downlink_data_flow_relayed().await?;
|
||||||
@ -312,25 +311,20 @@ impl Data {
|
|||||||
dr,
|
dr,
|
||||||
)? as u8;
|
)? as u8;
|
||||||
|
|
||||||
match device_session::get_for_phypayload_and_incr_f_cnt_up(
|
match device::get_for_phypayload_and_incr_f_cnt_up(true, &mut self.phy_payload, dr, ch)
|
||||||
true,
|
.await
|
||||||
&mut self.phy_payload,
|
|
||||||
dr,
|
|
||||||
ch,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
{
|
||||||
Ok(v) => match v {
|
Ok(v) => match v {
|
||||||
device_session::ValidationStatus::Ok(f_cnt, ds) => {
|
device::ValidationStatus::Ok(f_cnt, ds) => {
|
||||||
self.device_session = Some(ds);
|
self.device_session = Some(ds);
|
||||||
self.f_cnt_up_full = f_cnt;
|
self.f_cnt_up_full = f_cnt;
|
||||||
}
|
}
|
||||||
device_session::ValidationStatus::Retransmission(f_cnt, ds) => {
|
device::ValidationStatus::Retransmission(f_cnt, ds) => {
|
||||||
self.retransmission = true;
|
self.retransmission = true;
|
||||||
self.device_session = Some(ds);
|
self.device_session = Some(ds);
|
||||||
self.f_cnt_up_full = f_cnt;
|
self.f_cnt_up_full = f_cnt;
|
||||||
}
|
}
|
||||||
device_session::ValidationStatus::Reset(f_cnt, ds) => {
|
device::ValidationStatus::Reset(f_cnt, ds) => {
|
||||||
self.reset = true;
|
self.reset = true;
|
||||||
self.device_session = Some(ds);
|
self.device_session = Some(ds);
|
||||||
self.f_cnt_up_full = f_cnt;
|
self.f_cnt_up_full = f_cnt;
|
||||||
@ -474,24 +468,6 @@ impl Data {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn abort_on_device_is_disabled(&self) -> Result<(), Error> {
|
|
||||||
let device = self.device.as_ref().unwrap();
|
|
||||||
|
|
||||||
if device.is_disabled {
|
|
||||||
// Restore the device-session in case the device is disabled.
|
|
||||||
// This is because during the fcnt validation, we immediately store the
|
|
||||||
// device-session with incremented fcnt to avoid race conditions.
|
|
||||||
device_session::save(self.device_session.as_ref().unwrap())
|
|
||||||
.await
|
|
||||||
.context("Savel device-session")?;
|
|
||||||
|
|
||||||
info!(dev_eui = %device.dev_eui, "Device is disabled, aborting flow");
|
|
||||||
return Err(Error::Abort);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_retransmission_reset(&self) -> Result<(), Error> {
|
async fn handle_retransmission_reset(&self) -> Result<(), Error> {
|
||||||
trace!("Handle retransmission and reset");
|
trace!("Handle retransmission and reset");
|
||||||
let dev = self.device.as_ref().unwrap();
|
let dev = self.device.as_ref().unwrap();
|
||||||
@ -560,8 +536,14 @@ impl Data {
|
|||||||
if dev.scheduler_run_after.is_none()
|
if dev.scheduler_run_after.is_none()
|
||||||
|| scheduler_run_after > dev.scheduler_run_after.unwrap()
|
|| scheduler_run_after > dev.scheduler_run_after.unwrap()
|
||||||
{
|
{
|
||||||
*dev = device::set_scheduler_run_after(&dev.dev_eui, Some(scheduler_run_after))
|
*dev = device::partial_update(
|
||||||
.await?;
|
dev.dev_eui,
|
||||||
|
&device::DeviceChangeset {
|
||||||
|
scheduler_run_after: Some(Some(scheduler_run_after)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -580,9 +562,16 @@ impl Data {
|
|||||||
// Restore the device-session in case of an error (no gateways available).
|
// Restore the device-session in case of an error (no gateways available).
|
||||||
// This is because during the fcnt validation, we immediately store the
|
// This is because during the fcnt validation, we immediately store the
|
||||||
// device-session with incremented fcnt to avoid race conditions.
|
// device-session with incremented fcnt to avoid race conditions.
|
||||||
device_session::save(self.device_session.as_ref().unwrap())
|
device::partial_update(
|
||||||
.await
|
self.device.as_ref().unwrap().dev_eui,
|
||||||
.context("Save device-session")?;
|
&device::DeviceChangeset {
|
||||||
|
device_session: Some(Some(
|
||||||
|
self.device_session.as_ref().unwrap().encode_to_vec(),
|
||||||
|
)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Err(v)
|
Err(v)
|
||||||
}
|
}
|
||||||
@ -670,8 +659,12 @@ impl Data {
|
|||||||
|
|
||||||
async fn set_uplink_data_rate(&mut self) -> Result<()> {
|
async fn set_uplink_data_rate(&mut self) -> Result<()> {
|
||||||
trace!("Set uplink data-rate and reset tx-power on change");
|
trace!("Set uplink data-rate and reset tx-power on change");
|
||||||
let device = self.device.as_mut().unwrap();
|
let device = self.device.as_ref().unwrap();
|
||||||
*device = device::set_last_seen_dr(&device.dev_eui, self.uplink_frame_set.dr).await?;
|
|
||||||
|
self.device_changeset.last_seen_at = Some(Some(Utc::now()));
|
||||||
|
if device.dr.is_none() || self.uplink_frame_set.dr as i16 != device.dr.unwrap_or_default() {
|
||||||
|
self.device_changeset.dr = Some(Some(self.uplink_frame_set.dr.into()));
|
||||||
|
}
|
||||||
|
|
||||||
let ds = self.device_session.as_mut().unwrap();
|
let ds = self.device_session.as_mut().unwrap();
|
||||||
// The node changed its data-rate. Possibly the node did also reset its
|
// The node changed its data-rate. Possibly the node did also reset its
|
||||||
@ -682,6 +675,7 @@ impl Data {
|
|||||||
ds.uplink_adr_history = Vec::new();
|
ds.uplink_adr_history = Vec::new();
|
||||||
}
|
}
|
||||||
ds.dr = self.uplink_frame_set.dr as u32;
|
ds.dr = self.uplink_frame_set.dr as u32;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -689,7 +683,11 @@ impl Data {
|
|||||||
trace!("Set relayed uplink data-rate and reset tx-power on change");
|
trace!("Set relayed uplink data-rate and reset tx-power on change");
|
||||||
let device = self.device.as_mut().unwrap();
|
let device = self.device.as_mut().unwrap();
|
||||||
let relay_ctx = self.relay_context.as_ref().unwrap();
|
let relay_ctx = self.relay_context.as_ref().unwrap();
|
||||||
*device = device::set_last_seen_dr(&device.dev_eui, self.uplink_frame_set.dr).await?;
|
|
||||||
|
self.device_changeset.last_seen_at = Some(Some(Utc::now()));
|
||||||
|
if device.dr.is_none() || self.uplink_frame_set.dr as i16 != device.dr.unwrap_or_default() {
|
||||||
|
self.device_changeset.dr = Some(Some(self.uplink_frame_set.dr.into()));
|
||||||
|
}
|
||||||
|
|
||||||
let ds = self.device_session.as_mut().unwrap();
|
let ds = self.device_session.as_mut().unwrap();
|
||||||
// The node changed its data-rate. Possibly the node did also reset its
|
// The node changed its data-rate. Possibly the node did also reset its
|
||||||
@ -722,7 +720,7 @@ impl Data {
|
|||||||
|
|
||||||
// Update if the enabled class has changed.
|
// Update if the enabled class has changed.
|
||||||
if dev.enabled_class != enabled_class {
|
if dev.enabled_class != enabled_class {
|
||||||
*dev = device::set_enabled_class(&dev.dev_eui, enabled_class).await?;
|
self.device_changeset.enabled_class = Some(enabled_class);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -2,6 +2,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use chrono::{DateTime, Local, Utc};
|
use chrono::{DateTime, Local, Utc};
|
||||||
|
use prost::Message;
|
||||||
use tracing::{span, trace, Instrument, Level};
|
use tracing::{span, trace, Instrument, Level};
|
||||||
|
|
||||||
use super::{error::Error, helpers, UplinkFrameSet};
|
use super::{error::Error, helpers, UplinkFrameSet};
|
||||||
@ -10,7 +11,7 @@ use crate::backend::{joinserver, keywrap, roaming};
|
|||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
application,
|
application,
|
||||||
device::{self, DeviceClass},
|
device::{self, DeviceClass},
|
||||||
device_keys, device_profile, device_queue, device_session,
|
device_keys, device_profile, device_queue,
|
||||||
error::Error as StorageError,
|
error::Error as StorageError,
|
||||||
helpers::get_all_device_data,
|
helpers::get_all_device_data,
|
||||||
metrics, tenant,
|
metrics, tenant,
|
||||||
@ -101,8 +102,7 @@ impl JoinRequest {
|
|||||||
ctx.log_uplink_meta().await?;
|
ctx.log_uplink_meta().await?;
|
||||||
ctx.create_device_session().await?;
|
ctx.create_device_session().await?;
|
||||||
ctx.flush_device_queue().await?;
|
ctx.flush_device_queue().await?;
|
||||||
ctx.set_device_mode().await?;
|
ctx.update_device().await?;
|
||||||
ctx.set_join_eui().await?;
|
|
||||||
ctx.send_join_event().await?;
|
ctx.send_join_event().await?;
|
||||||
ctx.set_pr_start_ans_payload()?;
|
ctx.set_pr_start_ans_payload()?;
|
||||||
|
|
||||||
@ -627,10 +627,6 @@ impl JoinRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
device_session::save(&ds)
|
|
||||||
.await
|
|
||||||
.context("Saving device-session failed")?;
|
|
||||||
|
|
||||||
self.device_session = Some(ds);
|
self.device_session = Some(ds);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -648,25 +644,32 @@ impl JoinRequest {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_device_mode(&mut self) -> Result<()> {
|
async fn update_device(&mut self) -> Result<()> {
|
||||||
|
trace!("Updating device");
|
||||||
let dp = self.device_profile.as_ref().unwrap();
|
let dp = self.device_profile.as_ref().unwrap();
|
||||||
let device = self.device.as_mut().unwrap();
|
|
||||||
|
|
||||||
// LoRaWAN 1.1 devices send a mac-command when changing to Class-C.
|
self.device = Some(
|
||||||
if dp.supports_class_c && dp.mac_version.to_string().starts_with("1.0") {
|
device::partial_update(
|
||||||
*device = device::set_enabled_class(&device.dev_eui, DeviceClass::C).await?;
|
self.device.as_ref().unwrap().dev_eui,
|
||||||
} else {
|
&device::DeviceChangeset {
|
||||||
*device = device::set_enabled_class(&device.dev_eui, DeviceClass::A).await?;
|
device_session: Some(Some(
|
||||||
}
|
self.device_session.as_ref().unwrap().encode_to_vec(),
|
||||||
Ok(())
|
)),
|
||||||
}
|
join_eui: Some(self.join_request.as_ref().unwrap().join_eui),
|
||||||
|
dev_addr: Some(Some(self.dev_addr.unwrap())),
|
||||||
async fn set_join_eui(&mut self) -> Result<()> {
|
secondary_dev_addr: Some(None),
|
||||||
trace!("Setting JoinEUI");
|
enabled_class: Some(
|
||||||
let dev = self.device.as_mut().unwrap();
|
if dp.supports_class_c && dp.mac_version.to_string().starts_with("1.0") {
|
||||||
let req = self.join_request.as_ref().unwrap();
|
DeviceClass::C
|
||||||
|
} else {
|
||||||
*dev = device::set_join_eui(dev.dev_eui, req.join_eui).await?;
|
DeviceClass::A
|
||||||
|
},
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user