mirror of
https://github.com/chirpstack/chirpstack.git
synced 2025-06-01 23:40:45 +00:00
Initial FUOTA v2 implementation.
This implements selecting the v2.0.0 app-layer package in the device-profile and handling these payloads in the FUOTA flow.
This commit is contained in:
parent
60547ff973
commit
a0f07b5303
9
api/proto/api/device_profile.proto
vendored
9
api/proto/api/device_profile.proto
vendored
@ -104,6 +104,9 @@ enum Ts003Version {
|
|||||||
|
|
||||||
// v1.0.0.
|
// v1.0.0.
|
||||||
TS003_V100 = 1;
|
TS003_V100 = 1;
|
||||||
|
|
||||||
|
// v2.0.0
|
||||||
|
TS003_v200 = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Ts004Version {
|
enum Ts004Version {
|
||||||
@ -112,6 +115,9 @@ enum Ts004Version {
|
|||||||
|
|
||||||
// v1.0.0.
|
// v1.0.0.
|
||||||
TS004_V100 = 1;
|
TS004_V100 = 1;
|
||||||
|
|
||||||
|
// v2.0.0
|
||||||
|
TS004_V200 = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Ts005Version {
|
enum Ts005Version {
|
||||||
@ -120,6 +126,9 @@ enum Ts005Version {
|
|||||||
|
|
||||||
// v1.0.0.
|
// v1.0.0.
|
||||||
TS005_V100 = 1;
|
TS005_V100 = 1;
|
||||||
|
|
||||||
|
// v2.0.0
|
||||||
|
TS005_V200 = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeviceProfileService is the service providing API methods for managing
|
// DeviceProfileService is the service providing API methods for managing
|
||||||
|
@ -104,6 +104,9 @@ enum Ts003Version {
|
|||||||
|
|
||||||
// v1.0.0.
|
// v1.0.0.
|
||||||
TS003_V100 = 1;
|
TS003_V100 = 1;
|
||||||
|
|
||||||
|
// v2.0.0
|
||||||
|
TS003_v200 = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Ts004Version {
|
enum Ts004Version {
|
||||||
@ -112,6 +115,9 @@ enum Ts004Version {
|
|||||||
|
|
||||||
// v1.0.0.
|
// v1.0.0.
|
||||||
TS004_V100 = 1;
|
TS004_V100 = 1;
|
||||||
|
|
||||||
|
// v2.0.0
|
||||||
|
TS004_V200 = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Ts005Version {
|
enum Ts005Version {
|
||||||
@ -120,6 +126,9 @@ enum Ts005Version {
|
|||||||
|
|
||||||
// v1.0.0.
|
// v1.0.0.
|
||||||
TS005_V100 = 1;
|
TS005_V100 = 1;
|
||||||
|
|
||||||
|
// v2.0.0
|
||||||
|
TS005_V200 = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeviceProfileService is the service providing API methods for managing
|
// DeviceProfileService is the service providing API methods for managing
|
||||||
|
@ -292,6 +292,7 @@ impl ToProto<api::Ts003Version> for Option<fields::device_profile::Ts003Version>
|
|||||||
match self {
|
match self {
|
||||||
None => api::Ts003Version::Ts003NotImplemented,
|
None => api::Ts003Version::Ts003NotImplemented,
|
||||||
Some(fields::device_profile::Ts003Version::V100) => api::Ts003Version::Ts003V100,
|
Some(fields::device_profile::Ts003Version::V100) => api::Ts003Version::Ts003V100,
|
||||||
|
Some(fields::device_profile::Ts003Version::V200) => api::Ts003Version::Ts003V200,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -301,6 +302,7 @@ impl FromProto<Option<fields::device_profile::Ts003Version>> for api::Ts003Versi
|
|||||||
match self {
|
match self {
|
||||||
api::Ts003Version::Ts003NotImplemented => None,
|
api::Ts003Version::Ts003NotImplemented => None,
|
||||||
api::Ts003Version::Ts003V100 => Some(fields::device_profile::Ts003Version::V100),
|
api::Ts003Version::Ts003V100 => Some(fields::device_profile::Ts003Version::V100),
|
||||||
|
api::Ts003Version::Ts003V200 => Some(fields::device_profile::Ts003Version::V200),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,6 +312,7 @@ impl ToProto<api::Ts004Version> for Option<fields::device_profile::Ts004Version>
|
|||||||
match self {
|
match self {
|
||||||
None => api::Ts004Version::Ts004NotImplemented,
|
None => api::Ts004Version::Ts004NotImplemented,
|
||||||
Some(fields::device_profile::Ts004Version::V100) => api::Ts004Version::Ts004V100,
|
Some(fields::device_profile::Ts004Version::V100) => api::Ts004Version::Ts004V100,
|
||||||
|
Some(fields::device_profile::Ts004Version::V200) => api::Ts004Version::Ts004V200,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -319,6 +322,7 @@ impl FromProto<Option<fields::device_profile::Ts004Version>> for api::Ts004Versi
|
|||||||
match self {
|
match self {
|
||||||
api::Ts004Version::Ts004NotImplemented => None,
|
api::Ts004Version::Ts004NotImplemented => None,
|
||||||
api::Ts004Version::Ts004V100 => Some(fields::device_profile::Ts004Version::V100),
|
api::Ts004Version::Ts004V100 => Some(fields::device_profile::Ts004Version::V100),
|
||||||
|
api::Ts004Version::Ts004V200 => Some(fields::device_profile::Ts004Version::V200),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -328,6 +332,7 @@ impl ToProto<api::Ts005Version> for Option<fields::device_profile::Ts005Version>
|
|||||||
match self {
|
match self {
|
||||||
None => api::Ts005Version::Ts005NotImplemented,
|
None => api::Ts005Version::Ts005NotImplemented,
|
||||||
Some(fields::device_profile::Ts005Version::V100) => api::Ts005Version::Ts005V100,
|
Some(fields::device_profile::Ts005Version::V100) => api::Ts005Version::Ts005V100,
|
||||||
|
Some(fields::device_profile::Ts005Version::V200) => api::Ts005Version::Ts005V200,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -337,6 +342,7 @@ impl FromProto<Option<fields::device_profile::Ts005Version>> for api::Ts005Versi
|
|||||||
match self {
|
match self {
|
||||||
api::Ts005Version::Ts005NotImplemented => None,
|
api::Ts005Version::Ts005NotImplemented => None,
|
||||||
api::Ts005Version::Ts005V100 => Some(fields::device_profile::Ts005Version::V100),
|
api::Ts005Version::Ts005V100 => Some(fields::device_profile::Ts005Version::V100),
|
||||||
|
api::Ts005Version::Ts005V200 => Some(fields::device_profile::Ts005Version::V200),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ pub async fn handle_uplink(
|
|||||||
|
|
||||||
match version {
|
match version {
|
||||||
Ts003Version::V100 => handle_uplink_v100(dev, dp, rx_info, data).await,
|
Ts003Version::V100 => handle_uplink_v100(dev, dp, rx_info, data).await,
|
||||||
|
Ts003Version::V200 => handle_uplink_v200(dev, dp, rx_info, data).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +43,24 @@ async fn handle_uplink_v100(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_uplink_v200(
|
||||||
|
dev: &device::Device,
|
||||||
|
dp: &device_profile::DeviceProfile,
|
||||||
|
rx_info: &[gw::UplinkRxInfo],
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<()> {
|
||||||
|
let pl = clocksync::v2::Payload::from_slice(true, data)?;
|
||||||
|
|
||||||
|
match pl {
|
||||||
|
clocksync::v2::Payload::AppTimeReq(pl) => {
|
||||||
|
handle_v2_app_time_req(dev, dp, rx_info, pl).await?
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_v1_app_time_req(
|
async fn handle_v1_app_time_req(
|
||||||
dev: &device::Device,
|
dev: &device::Device,
|
||||||
dp: &device_profile::DeviceProfile,
|
dp: &device_profile::DeviceProfile,
|
||||||
@ -91,6 +110,55 @@ async fn handle_v1_app_time_req(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_v2_app_time_req(
|
||||||
|
dev: &device::Device,
|
||||||
|
dp: &device_profile::DeviceProfile,
|
||||||
|
rx_info: &[gw::UplinkRxInfo],
|
||||||
|
pl: clocksync::v2::AppTimeReqPayload,
|
||||||
|
) -> Result<()> {
|
||||||
|
info!("Handling AppTimeReq");
|
||||||
|
|
||||||
|
let now_time_since_gps = if let Some(t) = helpers::get_time_since_gps_epoch(rx_info) {
|
||||||
|
chrono::Duration::from_std(t)?
|
||||||
|
} else {
|
||||||
|
helpers::get_rx_timestamp_chrono(rx_info).to_gps_time()
|
||||||
|
};
|
||||||
|
let dev_time_since_gps = chrono::Duration::seconds(pl.device_time.into());
|
||||||
|
|
||||||
|
let time_diff = (now_time_since_gps - dev_time_since_gps).num_seconds();
|
||||||
|
let time_correction: i32 = if time_diff < 0 {
|
||||||
|
time_diff.try_into().unwrap_or(i32::MIN)
|
||||||
|
} else {
|
||||||
|
time_diff.try_into().unwrap_or(i32::MAX)
|
||||||
|
};
|
||||||
|
|
||||||
|
if time_diff == 0 && !pl.param.ans_required {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(
|
||||||
|
time_correcrtion = time_correction,
|
||||||
|
"Responding with AppTimeAns"
|
||||||
|
);
|
||||||
|
|
||||||
|
let ans = clocksync::v2::Payload::AppTimeAns(clocksync::v2::AppTimeAnsPayload {
|
||||||
|
time_correction,
|
||||||
|
param: clocksync::v2::AppTimeAnsPayloadParam {
|
||||||
|
token_ans: pl.param.token_req,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
device_queue::enqueue_item(device_queue::DeviceQueueItem {
|
||||||
|
dev_eui: dev.dev_eui,
|
||||||
|
f_port: dp.app_layer_params.ts003_f_port.into(),
|
||||||
|
data: ans.to_vec()?,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -248,4 +316,153 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_v2_app_time_req() {
|
||||||
|
struct Test {
|
||||||
|
name: String,
|
||||||
|
rx_info: gw::UplinkRxInfo,
|
||||||
|
req: clocksync::v2::AppTimeReqPayload,
|
||||||
|
expected: Option<clocksync::v2::AppTimeAnsPayload>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let tests = vec![
|
||||||
|
Test {
|
||||||
|
name: "device synced".into(),
|
||||||
|
rx_info: gw::UplinkRxInfo {
|
||||||
|
time_since_gps_epoch: Some(Duration::from_secs(1234).try_into().unwrap()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
req: clocksync::v2::AppTimeReqPayload {
|
||||||
|
device_time: 1234,
|
||||||
|
param: clocksync::v2::AppTimeReqPayloadParam {
|
||||||
|
token_req: 8,
|
||||||
|
ans_required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: None,
|
||||||
|
},
|
||||||
|
Test {
|
||||||
|
name: "device synced - ans required".into(),
|
||||||
|
rx_info: gw::UplinkRxInfo {
|
||||||
|
time_since_gps_epoch: Some(Duration::from_secs(1234).try_into().unwrap()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
req: clocksync::v2::AppTimeReqPayload {
|
||||||
|
device_time: 1234,
|
||||||
|
param: clocksync::v2::AppTimeReqPayloadParam {
|
||||||
|
token_req: 8,
|
||||||
|
ans_required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: Some(clocksync::v2::AppTimeAnsPayload {
|
||||||
|
time_correction: 0,
|
||||||
|
param: clocksync::v2::AppTimeAnsPayloadParam { token_ans: 8 },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Test {
|
||||||
|
name: "device not synced (positive correction)".into(),
|
||||||
|
rx_info: gw::UplinkRxInfo {
|
||||||
|
time_since_gps_epoch: Some(Duration::from_secs(1234).try_into().unwrap()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
req: clocksync::v2::AppTimeReqPayload {
|
||||||
|
device_time: 1200,
|
||||||
|
param: clocksync::v2::AppTimeReqPayloadParam {
|
||||||
|
token_req: 8,
|
||||||
|
ans_required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: Some(clocksync::v2::AppTimeAnsPayload {
|
||||||
|
time_correction: 34,
|
||||||
|
param: clocksync::v2::AppTimeAnsPayloadParam { token_ans: 8 },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Test {
|
||||||
|
name: "device not synced (negative correction)".into(),
|
||||||
|
rx_info: gw::UplinkRxInfo {
|
||||||
|
time_since_gps_epoch: Some(Duration::from_secs(1200).try_into().unwrap()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
req: clocksync::v2::AppTimeReqPayload {
|
||||||
|
device_time: 1234,
|
||||||
|
param: clocksync::v2::AppTimeReqPayloadParam {
|
||||||
|
token_req: 8,
|
||||||
|
ans_required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: Some(clocksync::v2::AppTimeAnsPayload {
|
||||||
|
time_correction: -34,
|
||||||
|
param: clocksync::v2::AppTimeAnsPayloadParam { token_ans: 8 },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let _guard = test::prepare().await;
|
||||||
|
let t = tenant::create(tenant::Tenant {
|
||||||
|
name: "test-tenant".into(),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let app = application::create(application::Application {
|
||||||
|
name: "test-app".into(),
|
||||||
|
tenant_id: t.id,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let dp = device_profile::create(device_profile::DeviceProfile {
|
||||||
|
name: "test-dp".into(),
|
||||||
|
tenant_id: t.id,
|
||||||
|
app_layer_params: fields::AppLayerParams {
|
||||||
|
ts003_version: Some(Ts003Version::V200),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let d = device::create(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();
|
||||||
|
|
||||||
|
for tst in &tests {
|
||||||
|
println!("> {}", tst.name);
|
||||||
|
device_queue::flush_for_dev_eui(&d.dev_eui).await.unwrap();
|
||||||
|
let pl = clocksync::v2::Payload::AppTimeReq(tst.req.clone());
|
||||||
|
|
||||||
|
handle_uplink(
|
||||||
|
&d,
|
||||||
|
&dp,
|
||||||
|
&[tst.rx_info.clone()],
|
||||||
|
dp.app_layer_params.ts003_f_port,
|
||||||
|
&pl.to_vec().unwrap(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let queue_items = device_queue::get_for_dev_eui(&d.dev_eui).await.unwrap();
|
||||||
|
if let Some(expected_pl) = &tst.expected {
|
||||||
|
assert_eq!(1, queue_items.len());
|
||||||
|
let qi = queue_items.first().unwrap();
|
||||||
|
assert_eq!(dp.app_layer_params.ts003_f_port as i16, qi.f_port);
|
||||||
|
|
||||||
|
let qi_pl = clocksync::v2::Payload::from_slice(false, &qi.data).unwrap();
|
||||||
|
let expected_pl = clocksync::v2::Payload::AppTimeAns(expected_pl.clone());
|
||||||
|
|
||||||
|
assert_eq!(expected_pl, qi_pl);
|
||||||
|
} else {
|
||||||
|
assert!(queue_items.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ pub async fn handle_uplink(
|
|||||||
|
|
||||||
match version {
|
match version {
|
||||||
Ts004Version::V100 => handle_uplink_v100(dev, data).await,
|
Ts004Version::V100 => handle_uplink_v100(dev, data).await,
|
||||||
|
Ts004Version::V200 => handle_uplink_v200(dev, data).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,6 +38,22 @@ async fn handle_uplink_v100(dev: &device::Device, data: &[u8]) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_uplink_v200(dev: &device::Device, data: &[u8]) -> Result<()> {
|
||||||
|
let pl = fragmentation::v2::Payload::from_slice(true, data)?;
|
||||||
|
|
||||||
|
match pl {
|
||||||
|
fragmentation::v2::Payload::FragSessionSetupAns(pl) => {
|
||||||
|
handle_v2_frag_session_setup_ans(dev, pl).await?
|
||||||
|
}
|
||||||
|
fragmentation::v2::Payload::FragSessionStatusAns(pl) => {
|
||||||
|
handle_v2_frag_session_status_ans(dev, pl).await?
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_v1_frag_session_setup_ans(
|
async fn handle_v1_frag_session_setup_ans(
|
||||||
dev: &device::Device,
|
dev: &device::Device,
|
||||||
pl: fragmentation::v1::FragSessionSetupAnsPayload,
|
pl: fragmentation::v1::FragSessionSetupAnsPayload,
|
||||||
@ -68,6 +85,39 @@ async fn handle_v1_frag_session_setup_ans(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_v2_frag_session_setup_ans(
|
||||||
|
dev: &device::Device,
|
||||||
|
pl: fragmentation::v2::FragSessionSetupAnsPayload,
|
||||||
|
) -> Result<()> {
|
||||||
|
info!("Handling FragSessionSetupAns");
|
||||||
|
|
||||||
|
let mut fuota_dev = fuota::get_latest_device_by_dev_eui(dev.dev_eui).await?;
|
||||||
|
|
||||||
|
if pl.frag_algo_unsupported
|
||||||
|
| pl.not_enough_memory
|
||||||
|
| pl.frag_index_unsupported
|
||||||
|
| pl.wrong_descriptor
|
||||||
|
| pl.session_cnt_replay
|
||||||
|
{
|
||||||
|
warn!(
|
||||||
|
frag_index = pl.frag_index,
|
||||||
|
frag_algo_unsupported = pl.frag_algo_unsupported,
|
||||||
|
not_enough_memory = pl.not_enough_memory,
|
||||||
|
frag_index_unsupported = pl.frag_index_unsupported,
|
||||||
|
wrong_descriptor = pl.wrong_descriptor,
|
||||||
|
session_cnt_replay = pl.session_cnt_replay,
|
||||||
|
"FragSessionAns contains errors"
|
||||||
|
);
|
||||||
|
fuota_dev.error_msg = format!("Error: FragSessionAns response frag_algo_unsupported={}, not_enough_memory={}, frag_index_unsupported={}, wrong_descriptor={}, session_cnt_replay={}", pl.frag_algo_unsupported, pl.not_enough_memory, pl.frag_index_unsupported, pl.wrong_descriptor, pl.session_cnt_replay);
|
||||||
|
} else {
|
||||||
|
fuota_dev.frag_session_setup_completed_at = Some(Utc::now());
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = fuota::update_device(fuota_dev).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_v1_frag_session_status_ans(
|
async fn handle_v1_frag_session_status_ans(
|
||||||
dev: &device::Device,
|
dev: &device::Device,
|
||||||
pl: fragmentation::v1::FragSessionStatusAnsPayload,
|
pl: fragmentation::v1::FragSessionStatusAnsPayload,
|
||||||
@ -94,3 +144,36 @@ async fn handle_v1_frag_session_status_ans(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_v2_frag_session_status_ans(
|
||||||
|
dev: &device::Device,
|
||||||
|
pl: fragmentation::v2::FragSessionStatusAnsPayload,
|
||||||
|
) -> Result<()> {
|
||||||
|
info!("Handling FragSessionStatusAnsPayload");
|
||||||
|
|
||||||
|
let mut fuota_dev = fuota::get_latest_device_by_dev_eui(dev.dev_eui).await?;
|
||||||
|
|
||||||
|
if pl.missing_frag != 0
|
||||||
|
|| pl.status.memory_error
|
||||||
|
|| pl.status.mic_error
|
||||||
|
|| pl.status.session_does_not_exist
|
||||||
|
{
|
||||||
|
warn!(
|
||||||
|
frag_index = pl.received_and_index.frag_index,
|
||||||
|
nb_frag_received = pl.received_and_index.nb_frag_received,
|
||||||
|
missing_frag = pl.missing_frag,
|
||||||
|
memory_error = pl.status.memory_error,
|
||||||
|
mic_error = pl.status.mic_error,
|
||||||
|
session_does_not_exist = pl.status.session_does_not_exist,
|
||||||
|
"FragSessionStatusAns contains errors"
|
||||||
|
);
|
||||||
|
|
||||||
|
fuota_dev.error_msg = format!("Error: FragSessionStatusAns response nb_frag_received={}, missing_frag={}, memory_error={}, mic_error={}, session_does_not_exist={}", pl.received_and_index.nb_frag_received, pl.missing_frag, pl.status.memory_error, pl.status.mic_error, pl.status.session_does_not_exist);
|
||||||
|
} else {
|
||||||
|
fuota_dev.frag_status_completed_at = Some(Utc::now());
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = fuota::update_device(fuota_dev).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -11,7 +11,10 @@ use lrwn::region::MacVersion;
|
|||||||
use crate::config;
|
use crate::config;
|
||||||
use crate::downlink;
|
use crate::downlink;
|
||||||
use crate::gpstime::ToGpsTime;
|
use crate::gpstime::ToGpsTime;
|
||||||
use crate::storage::fields::{FuotaJob, RequestFragmentationSessionStatus};
|
use crate::storage::fields::{
|
||||||
|
device_profile::Ts004Version, device_profile::Ts005Version, FuotaJob,
|
||||||
|
RequestFragmentationSessionStatus,
|
||||||
|
};
|
||||||
use crate::storage::{device, device_keys, device_profile, device_queue, fuota, multicast};
|
use crate::storage::{device, device_keys, device_profile, device_queue, fuota, multicast};
|
||||||
|
|
||||||
pub struct Flow {
|
pub struct Flow {
|
||||||
@ -109,14 +112,30 @@ impl Flow {
|
|||||||
self.job.attempt_count += 1;
|
self.job.attempt_count += 1;
|
||||||
|
|
||||||
// Get McAppSKey + McNwkSKey.
|
// Get McAppSKey + McNwkSKey.
|
||||||
let mc_app_s_key = multicastsetup::v1::get_mc_app_s_key(
|
let (mc_app_s_key, mc_nwk_s_key) = match self.device_profile.app_layer_params.ts005_version
|
||||||
self.fuota_deployment.multicast_key,
|
{
|
||||||
self.fuota_deployment.multicast_addr,
|
Some(Ts005Version::V100) => (
|
||||||
)?;
|
multicastsetup::v1::get_mc_app_s_key(
|
||||||
let mc_nwk_s_key = multicastsetup::v1::get_mc_net_s_key(
|
self.fuota_deployment.multicast_key,
|
||||||
self.fuota_deployment.multicast_key,
|
self.fuota_deployment.multicast_addr,
|
||||||
self.fuota_deployment.multicast_addr,
|
)?,
|
||||||
)?;
|
multicastsetup::v1::get_mc_net_s_key(
|
||||||
|
self.fuota_deployment.multicast_key,
|
||||||
|
self.fuota_deployment.multicast_addr,
|
||||||
|
)?,
|
||||||
|
),
|
||||||
|
Some(Ts005Version::V200) => (
|
||||||
|
multicastsetup::v2::get_mc_app_s_key(
|
||||||
|
self.fuota_deployment.multicast_key,
|
||||||
|
self.fuota_deployment.multicast_addr,
|
||||||
|
)?,
|
||||||
|
multicastsetup::v2::get_mc_net_s_key(
|
||||||
|
self.fuota_deployment.multicast_key,
|
||||||
|
self.fuota_deployment.multicast_addr,
|
||||||
|
)?,
|
||||||
|
),
|
||||||
|
None => return Err(anyhow!("Device-profile does not support TS005")),
|
||||||
|
};
|
||||||
|
|
||||||
let _ = multicast::create(multicast::MulticastGroup {
|
let _ = multicast::create(multicast::MulticastGroup {
|
||||||
id: self.fuota_deployment.id,
|
id: self.fuota_deployment.id,
|
||||||
@ -201,38 +220,85 @@ impl Flow {
|
|||||||
|
|
||||||
for fuota_dev in &fuota_devices {
|
for fuota_dev in &fuota_devices {
|
||||||
let dev_keys = device_keys::get(&fuota_dev.dev_eui).await?;
|
let dev_keys = device_keys::get(&fuota_dev.dev_eui).await?;
|
||||||
let mc_root_key = match self.device_profile.mac_version {
|
|
||||||
MacVersion::LORAWAN_1_0_0
|
let pl = match self.device_profile.app_layer_params.ts005_version {
|
||||||
| MacVersion::LORAWAN_1_0_1
|
Some(Ts005Version::V100) => {
|
||||||
| MacVersion::LORAWAN_1_0_2
|
let mc_root_key = match self.device_profile.mac_version {
|
||||||
| MacVersion::LORAWAN_1_0_3
|
MacVersion::LORAWAN_1_0_0
|
||||||
| MacVersion::LORAWAN_1_0_4 => {
|
| MacVersion::LORAWAN_1_0_1
|
||||||
multicastsetup::v1::get_mc_root_key_for_gen_app_key(dev_keys.gen_app_key)?
|
| MacVersion::LORAWAN_1_0_2
|
||||||
|
| MacVersion::LORAWAN_1_0_3
|
||||||
|
| MacVersion::LORAWAN_1_0_4 => {
|
||||||
|
multicastsetup::v1::get_mc_root_key_for_gen_app_key(
|
||||||
|
dev_keys.gen_app_key,
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
MacVersion::LORAWAN_1_1_0 | MacVersion::Latest => {
|
||||||
|
multicastsetup::v1::get_mc_root_key_for_app_key(dev_keys.app_key)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mc_ke_key = multicastsetup::v1::get_mc_ke_key(mc_root_key)?;
|
||||||
|
let mc_key_encrypted = multicastsetup::v1::encrypt_mc_key(
|
||||||
|
mc_ke_key,
|
||||||
|
self.fuota_deployment.multicast_key,
|
||||||
|
);
|
||||||
|
|
||||||
|
multicastsetup::v1::Payload::McGroupSetupReq(
|
||||||
|
multicastsetup::v1::McGroupSetupReqPayload {
|
||||||
|
mc_group_id_header:
|
||||||
|
multicastsetup::v1::McGroupSetupReqPayloadMcGroupIdHeader {
|
||||||
|
mc_group_id: 0,
|
||||||
|
},
|
||||||
|
mc_addr: self.fuota_deployment.multicast_addr,
|
||||||
|
mc_key_encrypted,
|
||||||
|
min_mc_f_count: 0,
|
||||||
|
max_mc_f_count: u32::MAX,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.to_vec()?
|
||||||
}
|
}
|
||||||
MacVersion::LORAWAN_1_1_0 | MacVersion::Latest => {
|
Some(Ts005Version::V200) => {
|
||||||
multicastsetup::v1::get_mc_root_key_for_app_key(dev_keys.app_key)?
|
let mc_root_key = match self.device_profile.mac_version {
|
||||||
|
MacVersion::LORAWAN_1_0_0
|
||||||
|
| MacVersion::LORAWAN_1_0_1
|
||||||
|
| MacVersion::LORAWAN_1_0_2
|
||||||
|
| MacVersion::LORAWAN_1_0_3
|
||||||
|
| MacVersion::LORAWAN_1_0_4 => {
|
||||||
|
multicastsetup::v2::get_mc_root_key_for_gen_app_key(
|
||||||
|
dev_keys.gen_app_key,
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
MacVersion::LORAWAN_1_1_0 | MacVersion::Latest => {
|
||||||
|
multicastsetup::v2::get_mc_root_key_for_app_key(dev_keys.app_key)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mc_ke_key = multicastsetup::v2::get_mc_ke_key(mc_root_key)?;
|
||||||
|
let mc_key_encrypted = multicastsetup::v2::encrypt_mc_key(
|
||||||
|
mc_ke_key,
|
||||||
|
self.fuota_deployment.multicast_key,
|
||||||
|
);
|
||||||
|
|
||||||
|
multicastsetup::v2::Payload::McGroupSetupReq(
|
||||||
|
multicastsetup::v2::McGroupSetupReqPayload {
|
||||||
|
mc_group_id_header:
|
||||||
|
multicastsetup::v2::McGroupSetupReqPayloadMcGroupIdHeader {
|
||||||
|
mc_group_id: 0,
|
||||||
|
},
|
||||||
|
mc_addr: self.fuota_deployment.multicast_addr,
|
||||||
|
mc_key_encrypted,
|
||||||
|
min_mc_f_count: 0,
|
||||||
|
max_mc_f_count: u32::MAX,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.to_vec()?
|
||||||
}
|
}
|
||||||
|
None => return Err(anyhow!("Device-profile does not support TS005")),
|
||||||
};
|
};
|
||||||
let mc_ke_key = multicastsetup::v1::get_mc_ke_key(mc_root_key)?;
|
|
||||||
let mc_key_encrypted =
|
|
||||||
multicastsetup::v1::encrypt_mc_key(mc_ke_key, self.fuota_deployment.multicast_key);
|
|
||||||
|
|
||||||
let pl = multicastsetup::v1::Payload::McGroupSetupReq(
|
let _ = device_queue::enqueue_item(device_queue::DeviceQueueItem {
|
||||||
multicastsetup::v1::McGroupSetupReqPayload {
|
|
||||||
mc_group_id_header: multicastsetup::v1::McGroupSetupReqPayloadMcGroupIdHeader {
|
|
||||||
mc_group_id: 0,
|
|
||||||
},
|
|
||||||
mc_addr: self.fuota_deployment.multicast_addr,
|
|
||||||
mc_key_encrypted,
|
|
||||||
min_mc_f_count: 0,
|
|
||||||
max_mc_f_count: u32::MAX,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
device_queue::enqueue_item(device_queue::DeviceQueueItem {
|
|
||||||
dev_eui: fuota_dev.dev_eui,
|
dev_eui: fuota_dev.dev_eui,
|
||||||
f_port: self.device_profile.app_layer_params.ts005_f_port.into(),
|
f_port: self.device_profile.app_layer_params.ts005_f_port.into(),
|
||||||
data: pl.to_vec()?,
|
data: pl,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
@ -285,27 +351,74 @@ impl Flow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for fuota_dev in &fuota_devices {
|
for fuota_dev in &fuota_devices {
|
||||||
let pl = fragmentation::v1::Payload::FragSessionSetupReq(
|
let pl = match self.device_profile.app_layer_params.ts004_version {
|
||||||
fragmentation::v1::FragSessionSetupReqPayload {
|
Some(Ts004Version::V100) => fragmentation::v1::Payload::FragSessionSetupReq(
|
||||||
frag_session: fragmentation::v1::FragSessionSetuReqPayloadFragSession {
|
fragmentation::v1::FragSessionSetupReqPayload {
|
||||||
mc_group_bit_mask: [true, false, false, false],
|
frag_session: fragmentation::v1::FragSessionSetuReqPayloadFragSession {
|
||||||
frag_index: 0,
|
mc_group_bit_mask: [true, false, false, false],
|
||||||
|
frag_index: 0,
|
||||||
|
},
|
||||||
|
nb_frag: fragments as u16,
|
||||||
|
frag_size: fragment_size as u8,
|
||||||
|
padding: padding as u8,
|
||||||
|
control: fragmentation::v1::FragSessionSetuReqPayloadControl {
|
||||||
|
block_ack_delay: 0,
|
||||||
|
fragmentation_matrix: 0,
|
||||||
|
},
|
||||||
|
descriptor: [0, 0, 0, 0],
|
||||||
},
|
},
|
||||||
nb_frag: fragments as u16,
|
)
|
||||||
frag_size: fragment_size as u8,
|
.to_vec()?,
|
||||||
padding: padding as u8,
|
Some(Ts004Version::V200) => {
|
||||||
control: fragmentation::v1::FragSessionSetuReqPayloadControl {
|
let dev_keys = device_keys::get(&fuota_dev.dev_eui).await?;
|
||||||
block_ack_delay: 0,
|
let data_block_int_key = match self.device_profile.mac_version {
|
||||||
fragmentation_matrix: 0,
|
MacVersion::LORAWAN_1_0_0
|
||||||
},
|
| MacVersion::LORAWAN_1_0_1
|
||||||
descriptor: [0, 0, 0, 0],
|
| MacVersion::LORAWAN_1_0_2
|
||||||
},
|
| MacVersion::LORAWAN_1_0_3
|
||||||
);
|
| MacVersion::LORAWAN_1_0_4 => {
|
||||||
|
fragmentation::v2::get_data_block_int_key(dev_keys.gen_app_key)?
|
||||||
|
}
|
||||||
|
MacVersion::LORAWAN_1_1_0 | MacVersion::Latest => {
|
||||||
|
fragmentation::v2::get_data_block_int_key(dev_keys.app_key)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mic = fragmentation::v2::calculate_mic(
|
||||||
|
data_block_int_key,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
&self.fuota_deployment.payload,
|
||||||
|
)?;
|
||||||
|
|
||||||
device_queue::enqueue_item(device_queue::DeviceQueueItem {
|
fragmentation::v2::Payload::FragSessionSetupReq(
|
||||||
|
fragmentation::v2::FragSessionSetupReqPayload {
|
||||||
|
frag_session: fragmentation::v2::FragSessionSetuReqPayloadFragSession {
|
||||||
|
mc_group_bit_mask: [true, false, false, false],
|
||||||
|
frag_index: 0,
|
||||||
|
},
|
||||||
|
nb_frag: fragments as u16,
|
||||||
|
frag_size: fragment_size as u8,
|
||||||
|
padding: padding as u8,
|
||||||
|
control: fragmentation::v2::FragSessionSetuReqPayloadControl {
|
||||||
|
block_ack_delay: 0,
|
||||||
|
frag_algo: 0,
|
||||||
|
ack_reception: false,
|
||||||
|
},
|
||||||
|
descriptor: [0, 0, 0, 0],
|
||||||
|
mic,
|
||||||
|
session_cnt: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.to_vec()?
|
||||||
|
}
|
||||||
|
None => return Err(anyhow!("Device-profile does not support TS004")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = device_queue::enqueue_item(device_queue::DeviceQueueItem {
|
||||||
dev_eui: fuota_dev.dev_eui,
|
dev_eui: fuota_dev.dev_eui,
|
||||||
f_port: self.device_profile.app_layer_params.ts004_f_port.into(),
|
f_port: self.device_profile.app_layer_params.ts004_f_port.into(),
|
||||||
data: pl.to_vec()?,
|
data: pl,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
@ -362,51 +475,98 @@ impl Flow {
|
|||||||
.num_seconds()
|
.num_seconds()
|
||||||
% (1 << 32);
|
% (1 << 32);
|
||||||
|
|
||||||
let pl = match self.fuota_deployment.multicast_group_type.as_ref() {
|
let pl = match self.device_profile.app_layer_params.ts005_version {
|
||||||
"B" => multicastsetup::v1::Payload::McClassBSessionReq(
|
Some(Ts005Version::V100) => {
|
||||||
multicastsetup::v1::McClassBSessionReqPayload {
|
match self.fuota_deployment.multicast_group_type.as_ref() {
|
||||||
mc_group_id_header:
|
"B" => multicastsetup::v1::Payload::McClassBSessionReq(
|
||||||
multicastsetup::v1::McClassBSessionReqPayloadMcGroupIdHeader {
|
multicastsetup::v1::McClassBSessionReqPayload {
|
||||||
mc_group_id: 0,
|
mc_group_id_header:
|
||||||
|
multicastsetup::v1::McClassBSessionReqPayloadMcGroupIdHeader {
|
||||||
|
mc_group_id: 0,
|
||||||
|
},
|
||||||
|
session_time: (session_start - (session_start % 128)) as u32,
|
||||||
|
time_out_periodicity:
|
||||||
|
multicastsetup::v1::McClassBSessionReqPayloadTimeOutPeriodicity {
|
||||||
|
time_out: self.fuota_deployment.multicast_timeout as u8,
|
||||||
|
periodicity: self.fuota_deployment.multicast_class_b_ping_slot_nb_k
|
||||||
|
as u8,
|
||||||
|
},
|
||||||
|
dl_frequ: self.fuota_deployment.multicast_frequency as u32,
|
||||||
|
dr: self.fuota_deployment.multicast_dr as u8,
|
||||||
},
|
},
|
||||||
session_time: (session_start - (session_start % 128)) as u32,
|
).to_vec()?,
|
||||||
time_out_periodicity:
|
"C" => multicastsetup::v1::Payload::McClassCSessionReq(
|
||||||
multicastsetup::v1::McClassBSessionReqPayloadTimeOutPeriodicity {
|
multicastsetup::v1::McClassCSessionReqPayload {
|
||||||
time_out: self.fuota_deployment.multicast_timeout as u8,
|
mc_group_id_header:
|
||||||
periodicity: self.fuota_deployment.multicast_class_b_ping_slot_nb_k
|
multicastsetup::v1::McClassCSessionReqPayloadMcGroupIdHeader {
|
||||||
as u8,
|
mc_group_id: 0,
|
||||||
|
},
|
||||||
|
session_time: session_start as u32,
|
||||||
|
session_time_out:
|
||||||
|
multicastsetup::v1::McClassCSessionReqPayloadSessionTimeOut {
|
||||||
|
time_out: self.fuota_deployment.multicast_timeout as u8,
|
||||||
|
},
|
||||||
|
dl_frequ: self.fuota_deployment.multicast_frequency as u32,
|
||||||
|
dr: self.fuota_deployment.multicast_dr as u8,
|
||||||
},
|
},
|
||||||
dl_frequ: self.fuota_deployment.multicast_frequency as u32,
|
).to_vec()?,
|
||||||
dr: self.fuota_deployment.multicast_dr as u8,
|
_ => {
|
||||||
},
|
return Err(anyhow!(
|
||||||
),
|
"Unsupported group-type: {}",
|
||||||
"C" => multicastsetup::v1::Payload::McClassCSessionReq(
|
self.fuota_deployment.multicast_group_type
|
||||||
multicastsetup::v1::McClassCSessionReqPayload {
|
))
|
||||||
mc_group_id_header:
|
}
|
||||||
multicastsetup::v1::McClassCSessionReqPayloadMcGroupIdHeader {
|
}
|
||||||
mc_group_id: 0,
|
|
||||||
},
|
|
||||||
session_time: session_start as u32,
|
|
||||||
session_time_out:
|
|
||||||
multicastsetup::v1::McClassCSessionReqPayloadSessionTimeOut {
|
|
||||||
time_out: self.fuota_deployment.multicast_timeout as u8,
|
|
||||||
},
|
|
||||||
dl_frequ: self.fuota_deployment.multicast_frequency as u32,
|
|
||||||
dr: self.fuota_deployment.multicast_dr as u8,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_ => {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"Unsupported group-type: {}",
|
|
||||||
self.fuota_deployment.multicast_group_type
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
Some(Ts005Version::V200) => {
|
||||||
|
match self.fuota_deployment.multicast_group_type.as_ref() {
|
||||||
|
"B" => multicastsetup::v2::Payload::McClassBSessionReq(
|
||||||
|
multicastsetup::v2::McClassBSessionReqPayload {
|
||||||
|
mc_group_id_header:
|
||||||
|
multicastsetup::v2::McClassBSessionReqPayloadMcGroupIdHeader {
|
||||||
|
mc_group_id: 0,
|
||||||
|
},
|
||||||
|
session_time: (session_start - (session_start % 128)) as u32,
|
||||||
|
time_out_periodicity:
|
||||||
|
multicastsetup::v2::McClassBSessionReqPayloadTimeOutPeriodicity {
|
||||||
|
time_out: self.fuota_deployment.multicast_timeout as u8,
|
||||||
|
periodicity: self.fuota_deployment.multicast_class_b_ping_slot_nb_k
|
||||||
|
as u8,
|
||||||
|
},
|
||||||
|
dl_frequ: self.fuota_deployment.multicast_frequency as u32,
|
||||||
|
dr: self.fuota_deployment.multicast_dr as u8,
|
||||||
|
},
|
||||||
|
).to_vec()?,
|
||||||
|
"C" => multicastsetup::v2::Payload::McClassCSessionReq(
|
||||||
|
multicastsetup::v2::McClassCSessionReqPayload {
|
||||||
|
mc_group_id_header:
|
||||||
|
multicastsetup::v2::McClassCSessionReqPayloadMcGroupIdHeader {
|
||||||
|
mc_group_id: 0,
|
||||||
|
},
|
||||||
|
session_time: session_start as u32,
|
||||||
|
session_time_out:
|
||||||
|
multicastsetup::v2::McClassCSessionReqPayloadSessionTimeOut {
|
||||||
|
time_out: self.fuota_deployment.multicast_timeout as u8,
|
||||||
|
},
|
||||||
|
dl_frequ: self.fuota_deployment.multicast_frequency as u32,
|
||||||
|
dr: self.fuota_deployment.multicast_dr as u8,
|
||||||
|
},
|
||||||
|
).to_vec()?,
|
||||||
|
_ => {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Unsupported group-type: {}",
|
||||||
|
self.fuota_deployment.multicast_group_type
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => return Err(anyhow!("Device-profile does not support TS005")),
|
||||||
};
|
};
|
||||||
|
|
||||||
device_queue::enqueue_item(device_queue::DeviceQueueItem {
|
device_queue::enqueue_item(device_queue::DeviceQueueItem {
|
||||||
dev_eui: fuota_dev.dev_eui,
|
dev_eui: fuota_dev.dev_eui,
|
||||||
f_port: self.device_profile.app_layer_params.ts005_f_port.into(),
|
f_port: self.device_profile.app_layer_params.ts005_f_port.into(),
|
||||||
data: pl.to_vec()?,
|
data: pl,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
@ -459,22 +619,55 @@ impl Flow {
|
|||||||
let mut payload = self.fuota_deployment.payload.clone();
|
let mut payload = self.fuota_deployment.payload.clone();
|
||||||
payload.extend_from_slice(&vec![0; padding]);
|
payload.extend_from_slice(&vec![0; padding]);
|
||||||
|
|
||||||
let encoded_fragments = fragmentation::v1::encode(&payload, fragment_size, redundancy)?;
|
let payloads = match self.device_profile.app_layer_params.ts004_version {
|
||||||
|
Some(Ts004Version::V100) => {
|
||||||
for (i, frag) in encoded_fragments.iter().enumerate() {
|
let mut payloads = Vec::new();
|
||||||
let pl =
|
let encoded_fragments =
|
||||||
fragmentation::v1::Payload::DataFragment(fragmentation::v1::DataFragmentPayload {
|
fragmentation::v1::encode(&payload, fragment_size, redundancy)?;
|
||||||
index_and_n: fragmentation::v1::DataFragmentPayloadIndexAndN {
|
for (i, frag) in encoded_fragments.iter().enumerate() {
|
||||||
frag_index: 0,
|
payloads.push(
|
||||||
n: (i + 1) as u16,
|
fragmentation::v1::Payload::DataFragment(
|
||||||
},
|
fragmentation::v1::DataFragmentPayload {
|
||||||
data: frag.clone(),
|
index_and_n: fragmentation::v1::DataFragmentPayloadIndexAndN {
|
||||||
});
|
frag_index: 0,
|
||||||
|
n: (i + 1) as u16,
|
||||||
|
},
|
||||||
|
data: frag.clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.to_vec()?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
payloads
|
||||||
|
}
|
||||||
|
Some(Ts004Version::V200) => {
|
||||||
|
let mut payloads = Vec::new();
|
||||||
|
let encoded_fragments =
|
||||||
|
fragmentation::v2::encode(&payload, fragment_size, redundancy)?;
|
||||||
|
for (i, frag) in encoded_fragments.iter().enumerate() {
|
||||||
|
payloads.push(
|
||||||
|
fragmentation::v2::Payload::DataFragment(
|
||||||
|
fragmentation::v2::DataFragmentPayload {
|
||||||
|
index_and_n: fragmentation::v2::DataFragmentPayloadIndexAndN {
|
||||||
|
frag_index: 0,
|
||||||
|
n: (i + 1) as u16,
|
||||||
|
},
|
||||||
|
data: frag.clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.to_vec()?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
payloads
|
||||||
|
}
|
||||||
|
None => return Err(anyhow!("Device-profile does not support TS004")),
|
||||||
|
};
|
||||||
|
|
||||||
|
for pl in payloads {
|
||||||
let _ = downlink::multicast::enqueue(multicast::MulticastGroupQueueItem {
|
let _ = downlink::multicast::enqueue(multicast::MulticastGroupQueueItem {
|
||||||
multicast_group_id: self.fuota_deployment.id,
|
multicast_group_id: self.fuota_deployment.id,
|
||||||
f_port: self.device_profile.app_layer_params.ts004_f_port as i16,
|
f_port: self.device_profile.app_layer_params.ts004_f_port as i16,
|
||||||
data: pl.to_vec()?,
|
data: pl,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
@ -524,17 +717,28 @@ impl Flow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for fuota_dev in &fuota_devices {
|
for fuota_dev in &fuota_devices {
|
||||||
let pl = fragmentation::v1::Payload::FragSessionStatusReq(
|
let pl = match self.device_profile.app_layer_params.ts004_version {
|
||||||
fragmentation::v1::FragSessionStatusReqPayload {
|
Some(Ts004Version::V100) => fragmentation::v1::Payload::FragSessionStatusReq(
|
||||||
participants: true,
|
fragmentation::v1::FragSessionStatusReqPayload {
|
||||||
frag_index: 0,
|
participants: true,
|
||||||
},
|
frag_index: 0,
|
||||||
);
|
},
|
||||||
|
)
|
||||||
|
.to_vec()?,
|
||||||
|
Some(Ts004Version::V200) => fragmentation::v2::Payload::FragSessionStatusReq(
|
||||||
|
fragmentation::v2::FragSessionStatusReqPayload {
|
||||||
|
participants: true,
|
||||||
|
frag_index: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.to_vec()?,
|
||||||
|
None => return Err(anyhow!("Device-profile does not support TS004")),
|
||||||
|
};
|
||||||
|
|
||||||
device_queue::enqueue_item(device_queue::DeviceQueueItem {
|
device_queue::enqueue_item(device_queue::DeviceQueueItem {
|
||||||
dev_eui: fuota_dev.dev_eui,
|
dev_eui: fuota_dev.dev_eui,
|
||||||
f_port: self.device_profile.app_layer_params.ts004_f_port.into(),
|
f_port: self.device_profile.app_layer_params.ts004_f_port.into(),
|
||||||
data: pl.to_vec()?,
|
data: pl,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -18,6 +18,7 @@ pub async fn handle_uplink(
|
|||||||
|
|
||||||
match version {
|
match version {
|
||||||
Ts005Version::V100 => handle_uplink_v100(dev, data).await,
|
Ts005Version::V100 => handle_uplink_v100(dev, data).await,
|
||||||
|
Ts005Version::V200 => handle_uplink_v200(dev, data).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,6 +41,25 @@ async fn handle_uplink_v100(dev: &device::Device, data: &[u8]) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_uplink_v200(dev: &device::Device, data: &[u8]) -> Result<()> {
|
||||||
|
let pl = multicastsetup::v2::Payload::from_slice(true, data)?;
|
||||||
|
|
||||||
|
match pl {
|
||||||
|
multicastsetup::v2::Payload::McGroupSetupAns(pl) => {
|
||||||
|
handle_v2_mc_group_setup_ans(dev, pl).await?
|
||||||
|
}
|
||||||
|
multicastsetup::v2::Payload::McClassBSessionAns(pl) => {
|
||||||
|
handle_v2_mc_class_b_session_ans(dev, pl).await?
|
||||||
|
}
|
||||||
|
multicastsetup::v2::Payload::McClassCSessionAns(pl) => {
|
||||||
|
handle_v2_mc_class_c_session_ans(dev, pl).await?
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_v1_mc_group_setup_ans(
|
async fn handle_v1_mc_group_setup_ans(
|
||||||
dev: &device::Device,
|
dev: &device::Device,
|
||||||
pl: multicastsetup::v1::McGroupSetupAnsPayload,
|
pl: multicastsetup::v1::McGroupSetupAnsPayload,
|
||||||
@ -64,6 +84,30 @@ async fn handle_v1_mc_group_setup_ans(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_v2_mc_group_setup_ans(
|
||||||
|
dev: &device::Device,
|
||||||
|
pl: multicastsetup::v2::McGroupSetupAnsPayload,
|
||||||
|
) -> Result<()> {
|
||||||
|
info!("Handling McGroupSetupAns");
|
||||||
|
|
||||||
|
let mut fuota_dev = fuota::get_latest_device_by_dev_eui(dev.dev_eui).await?;
|
||||||
|
|
||||||
|
if pl.mc_group_id_header.id_error {
|
||||||
|
warn!(
|
||||||
|
mc_group_id = pl.mc_group_id_header.mc_group_id,
|
||||||
|
id_error = true,
|
||||||
|
"McGroupSetupAns contains errors"
|
||||||
|
);
|
||||||
|
fuota_dev.error_msg = "Error: McGroupSetupAns response id_error=true".into();
|
||||||
|
} else {
|
||||||
|
fuota_dev.mc_group_setup_completed_at = Some(Utc::now());
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = fuota::update_device(fuota_dev).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_v1_mc_class_b_session_ans(
|
async fn handle_v1_mc_class_b_session_ans(
|
||||||
dev: &device::Device,
|
dev: &device::Device,
|
||||||
pl: multicastsetup::v1::McClassBSessionAnsPayload,
|
pl: multicastsetup::v1::McClassBSessionAnsPayload,
|
||||||
@ -101,6 +145,46 @@ async fn handle_v1_mc_class_b_session_ans(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_v2_mc_class_b_session_ans(
|
||||||
|
dev: &device::Device,
|
||||||
|
pl: multicastsetup::v2::McClassBSessionAnsPayload,
|
||||||
|
) -> Result<()> {
|
||||||
|
info!("Handling McClassBSessionAns");
|
||||||
|
|
||||||
|
let mut fuota_dev = fuota::get_latest_device_by_dev_eui(dev.dev_eui).await?;
|
||||||
|
|
||||||
|
if pl.status_and_mc_group_id.dr_error
|
||||||
|
| pl.status_and_mc_group_id.freq_error
|
||||||
|
| pl.status_and_mc_group_id.mc_group_undefined
|
||||||
|
| pl.status_and_mc_group_id.start_missed
|
||||||
|
{
|
||||||
|
warn!(
|
||||||
|
dr_error = pl.status_and_mc_group_id.dr_error,
|
||||||
|
freq_error = pl.status_and_mc_group_id.freq_error,
|
||||||
|
mc_group_undefined = pl.status_and_mc_group_id.mc_group_undefined,
|
||||||
|
start_missed = pl.status_and_mc_group_id.start_missed,
|
||||||
|
"McClassBSessionAns contains errors"
|
||||||
|
);
|
||||||
|
|
||||||
|
fuota_dev.error_msg= format!("Error: McClassBSessionAns response dr_error: {}, freq_error: {}, mc_group_undefined: {}, start_missed: {}",
|
||||||
|
pl.status_and_mc_group_id.dr_error,
|
||||||
|
pl.status_and_mc_group_id.freq_error,
|
||||||
|
pl.status_and_mc_group_id.mc_group_undefined,
|
||||||
|
pl.status_and_mc_group_id.start_missed,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
info!(
|
||||||
|
time_to_start = pl.time_to_start.unwrap_or_default(),
|
||||||
|
"McClassBSessionAns OK"
|
||||||
|
);
|
||||||
|
fuota_dev.mc_session_completed_at = Some(Utc::now());
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = fuota::update_device(fuota_dev).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_v1_mc_class_c_session_ans(
|
async fn handle_v1_mc_class_c_session_ans(
|
||||||
dev: &device::Device,
|
dev: &device::Device,
|
||||||
pl: multicastsetup::v1::McClassCSessionAnsPayload,
|
pl: multicastsetup::v1::McClassCSessionAnsPayload,
|
||||||
@ -137,3 +221,43 @@ async fn handle_v1_mc_class_c_session_ans(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_v2_mc_class_c_session_ans(
|
||||||
|
dev: &device::Device,
|
||||||
|
pl: multicastsetup::v2::McClassCSessionAnsPayload,
|
||||||
|
) -> Result<()> {
|
||||||
|
info!("Handling McClassCSessionAns");
|
||||||
|
|
||||||
|
let mut fuota_dev = fuota::get_latest_device_by_dev_eui(dev.dev_eui).await?;
|
||||||
|
|
||||||
|
if pl.status_and_mc_group_id.dr_error
|
||||||
|
| pl.status_and_mc_group_id.freq_error
|
||||||
|
| pl.status_and_mc_group_id.mc_group_undefined
|
||||||
|
| pl.status_and_mc_group_id.start_missed
|
||||||
|
{
|
||||||
|
warn!(
|
||||||
|
dr_error = pl.status_and_mc_group_id.dr_error,
|
||||||
|
freq_error = pl.status_and_mc_group_id.freq_error,
|
||||||
|
mc_group_undefined = pl.status_and_mc_group_id.mc_group_undefined,
|
||||||
|
start_missed = pl.status_and_mc_group_id.start_missed,
|
||||||
|
"McClassCSessionAns contains errors"
|
||||||
|
);
|
||||||
|
|
||||||
|
fuota_dev.error_msg = format!("Error: McClassCSessionAns response dr_error: {}, freq_error: {}, mc_group_undefined: {}, start_missed: {}",
|
||||||
|
pl.status_and_mc_group_id.dr_error,
|
||||||
|
pl.status_and_mc_group_id.freq_error,
|
||||||
|
pl.status_and_mc_group_id.mc_group_undefined,
|
||||||
|
pl.status_and_mc_group_id.start_missed,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
info!(
|
||||||
|
time_to_start = pl.time_to_start.unwrap_or_default(),
|
||||||
|
"McClassCSessionAns OK"
|
||||||
|
);
|
||||||
|
fuota_dev.mc_session_completed_at = Some(Utc::now());
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = fuota::update_device(fuota_dev).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -314,16 +314,19 @@ impl serialize::ToSql<Text, Sqlite> for AppLayerParams {
|
|||||||
pub enum Ts003Version {
|
pub enum Ts003Version {
|
||||||
#[default]
|
#[default]
|
||||||
V100,
|
V100,
|
||||||
|
V200,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
|
||||||
pub enum Ts004Version {
|
pub enum Ts004Version {
|
||||||
#[default]
|
#[default]
|
||||||
V100,
|
V100,
|
||||||
|
V200,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
|
||||||
pub enum Ts005Version {
|
pub enum Ts005Version {
|
||||||
#[default]
|
#[default]
|
||||||
V100,
|
V100,
|
||||||
|
V200,
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
|
#[cfg(feature = "crypto")]
|
||||||
|
use aes::{
|
||||||
|
cipher::generic_array::GenericArray,
|
||||||
|
cipher::{BlockEncrypt, KeyInit},
|
||||||
|
Aes128, Block,
|
||||||
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
#[cfg(feature = "crypto")]
|
||||||
|
use cmac::{Cmac, Mac};
|
||||||
|
|
||||||
use crate::applayer::PayloadCodec;
|
use crate::applayer::PayloadCodec;
|
||||||
|
use crate::AES128Key;
|
||||||
|
|
||||||
pub enum Cid {
|
pub enum Cid {
|
||||||
PackageVersionReq,
|
PackageVersionReq,
|
||||||
@ -662,6 +671,48 @@ fn matrix_line(n: usize, m: usize) -> Vec<usize> {
|
|||||||
line
|
line
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_data_block_int_key(app_key: AES128Key) -> Result<AES128Key> {
|
||||||
|
let mut b: [u8; 16] = [0x30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||||
|
|
||||||
|
let key_bytes = app_key.to_bytes();
|
||||||
|
let key = GenericArray::from_slice(&key_bytes);
|
||||||
|
let cipher = Aes128::new(key);
|
||||||
|
|
||||||
|
let block = Block::from_mut_slice(&mut b);
|
||||||
|
cipher.encrypt_block(block);
|
||||||
|
|
||||||
|
Ok(AES128Key::from_slice(block)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_mic(
|
||||||
|
data_block_int_key: AES128Key,
|
||||||
|
session_cnt: u16,
|
||||||
|
frag_index: u8,
|
||||||
|
descriptor: [u8; 4],
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<[u8; 4]> {
|
||||||
|
let mut b0: [u8; 16] = [0; 16];
|
||||||
|
b0[0] = 0x49;
|
||||||
|
b0[1..3].clone_from_slice(&session_cnt.to_le_bytes());
|
||||||
|
b0[3] = frag_index;
|
||||||
|
b0[4..8].clone_from_slice(&descriptor);
|
||||||
|
b0[12..16].clone_from_slice(&(data.len() as u32).to_le_bytes());
|
||||||
|
|
||||||
|
let mut mac = <Cmac<Aes128> as Mac>::new_from_slice(&data_block_int_key.to_bytes()).unwrap();
|
||||||
|
mac.update(&b0);
|
||||||
|
mac.update(data);
|
||||||
|
|
||||||
|
let cmac = mac.finalize().into_bytes();
|
||||||
|
if cmac.len() < 4 {
|
||||||
|
return Err(anyhow!("cmac is less than 4 bytes"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut mic: [u8; 4] = [0; 4];
|
||||||
|
mic.clone_from_slice(&cmac[0..4]);
|
||||||
|
|
||||||
|
Ok(mic)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use aes::cipher::BlockDecrypt;
|
#[cfg(feature = "crypto")]
|
||||||
use aes::cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit};
|
use aes::{
|
||||||
use aes::{Aes128, Block};
|
cipher::BlockDecrypt,
|
||||||
|
cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit},
|
||||||
|
Aes128, Block,
|
||||||
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::applayer::PayloadCodec;
|
use crate::applayer::PayloadCodec;
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use aes::cipher::BlockDecrypt;
|
#[cfg(feature = "crypto")]
|
||||||
use aes::cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit};
|
use aes::{
|
||||||
use aes::{Aes128, Block};
|
cipher::BlockDecrypt,
|
||||||
|
cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit},
|
||||||
|
Aes128, Block,
|
||||||
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::applayer::PayloadCodec;
|
use crate::applayer::PayloadCodec;
|
||||||
|
@ -1077,6 +1077,7 @@ function DeviceProfileForm(props: IProps) {
|
|||||||
<Select disabled={props.disabled}>
|
<Select disabled={props.disabled}>
|
||||||
<Select.Option value={Ts003Version.TS003_NOT_IMPLEMENTED}>Not implemented</Select.Option>
|
<Select.Option value={Ts003Version.TS003_NOT_IMPLEMENTED}>Not implemented</Select.Option>
|
||||||
<Select.Option value={Ts003Version.TS003_V100}>v1.0.0</Select.Option>
|
<Select.Option value={Ts003Version.TS003_V100}>v1.0.0</Select.Option>
|
||||||
|
<Select.Option value={Ts003Version.TS003_V200}>v2.0.0</Select.Option>
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
@ -1096,6 +1097,7 @@ function DeviceProfileForm(props: IProps) {
|
|||||||
<Select disabled={props.disabled}>
|
<Select disabled={props.disabled}>
|
||||||
<Select.Option value={Ts004Version.TS004_NOT_IMPLEMENTED}>Not implemented</Select.Option>
|
<Select.Option value={Ts004Version.TS004_NOT_IMPLEMENTED}>Not implemented</Select.Option>
|
||||||
<Select.Option value={Ts004Version.TS004_V100}>v1.0.0</Select.Option>
|
<Select.Option value={Ts004Version.TS004_V100}>v1.0.0</Select.Option>
|
||||||
|
<Select.Option value={Ts004Version.TS004_V200}>v2.0.0</Select.Option>
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
@ -1115,6 +1117,7 @@ function DeviceProfileForm(props: IProps) {
|
|||||||
<Select disabled={props.disabled}>
|
<Select disabled={props.disabled}>
|
||||||
<Select.Option value={Ts005Version.TS005_NOT_IMPLEMENTED}>Not implemented</Select.Option>
|
<Select.Option value={Ts005Version.TS005_NOT_IMPLEMENTED}>Not implemented</Select.Option>
|
||||||
<Select.Option value={Ts005Version.TS005_V100}>v1.0.0</Select.Option>
|
<Select.Option value={Ts005Version.TS005_V100}>v1.0.0</Select.Option>
|
||||||
|
<Select.Option value={Ts005Version.TS005_V200}>v2.0.0</Select.Option>
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user