1use serde::{Deserialize, Serialize};
2
3use crate::commands::{
4 CountingWriter, data_too_large_error,
5 macros::{impl_deserialize_from_empty_map_and_into_unit, impl_serialize_as_empty_map},
6};
7
8fn serialize_option_hex<S, T>(data: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
9where
10 S: serde::Serializer,
11 T: hex::ToHex,
12{
13 data.as_ref()
14 .map(|val| val.encode_hex::<String>())
15 .serialize(serializer)
16}
17
18#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
20pub struct ImageState {
21 #[serde(default)]
23 pub image: u32,
24 pub slot: u32,
26 pub version: String,
28 #[serde(serialize_with = "serialize_option_hex")] pub hash: Option<[u8; 32]>,
35 #[serde(default)]
37 pub bootable: bool,
38 #[serde(default)]
40 pub pending: bool,
41 #[serde(default)]
43 pub confirmed: bool,
44 #[serde(default)]
46 pub active: bool,
47 #[serde(default)]
49 pub permanent: bool,
50}
51
52#[derive(Debug, Eq, PartialEq)]
54pub struct GetImageState;
55impl_serialize_as_empty_map!(GetImageState);
56
57#[derive(Debug, Deserialize, Eq, PartialEq)]
59pub struct ImageStateResponse {
60 pub images: Vec<ImageState>,
62 }
65
66#[derive(Debug, Serialize, Eq, PartialEq)]
68pub struct SetImageState<'a> {
69 #[serde(skip_serializing_if = "Option::is_none")]
77 #[serde(with = "serde_bytes")]
78 pub hash: Option<&'a [u8; 32]>,
79 pub confirm: bool,
84}
85
86#[derive(Debug, Serialize, Eq, PartialEq)]
88pub struct ImageUpload<'a, 'b> {
89 #[serde(skip_serializing_if = "Option::is_none")]
93 pub image: Option<u32>,
94 #[serde(skip_serializing_if = "Option::is_none")]
98 pub len: Option<u64>,
99 pub off: u64,
101 #[serde(skip_serializing_if = "Option::is_none")]
108 #[serde(with = "serde_bytes")]
109 pub sha: Option<&'a [u8; 32]>,
110 #[serde(with = "serde_bytes")]
112 pub data: &'b [u8],
113 #[serde(skip_serializing_if = "Option::is_none")]
118 pub upgrade: Option<bool>,
119}
120
121#[derive(Debug, Deserialize, Eq, PartialEq)]
123pub struct ImageUploadResponse {
124 pub off: u64,
126 pub r#match: Option<bool>,
128}
129
130pub fn image_upload_max_data_chunk_size(smp_frame_size: usize) -> std::io::Result<usize> {
137 const MGMT_HDR_SIZE: usize = 8; let mut size_counter = CountingWriter::new();
140 ciborium::into_writer(
141 &ImageUpload {
142 off: u64::MAX,
143 data: &[0u8],
144 len: Some(u64::MAX),
145 image: Some(u32::MAX),
146 sha: Some(&[42; 32]),
147 upgrade: Some(true),
148 },
149 &mut size_counter,
150 )
151 .map_err(|_| data_too_large_error())?;
152
153 let size_with_one_byte = size_counter.bytes_written;
154 let size_without_data = size_with_one_byte - 1;
155
156 let estimated_data_size = smp_frame_size
157 .checked_sub(MGMT_HDR_SIZE)
158 .ok_or_else(data_too_large_error)?
159 .checked_sub(size_without_data)
160 .ok_or_else(data_too_large_error)?;
161
162 let data_length_bytes = if estimated_data_size == 0 {
163 return Err(data_too_large_error());
164 } else if estimated_data_size <= u8::MAX as usize {
165 1
166 } else if estimated_data_size <= u16::MAX as usize {
167 2
168 } else if estimated_data_size <= u32::MAX as usize {
169 4
170 } else {
171 8
172 };
173
174 let actual_data_size = estimated_data_size
176 .checked_sub(data_length_bytes as usize)
177 .ok_or_else(data_too_large_error)?;
178
179 if actual_data_size == 0 {
180 return Err(data_too_large_error());
181 }
182
183 Ok(actual_data_size)
184}
185
186#[derive(Debug, Serialize, Eq, PartialEq)]
188pub struct ImageErase {
189 #[serde(skip_serializing_if = "Option::is_none")]
191 pub slot: Option<u32>,
192}
193
194#[derive(Default, Debug, Eq, PartialEq)]
196pub struct ImageEraseResponse;
197impl_deserialize_from_empty_map_and_into_unit!(ImageEraseResponse);
198
199#[derive(Debug, Eq, PartialEq)]
201pub struct SlotInfo;
202impl_serialize_as_empty_map!(SlotInfo);
203
204#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
206pub struct SlotInfoImage {
207 pub image: u32,
209 pub slots: Vec<SlotInfoImageSlot>,
211 #[serde(skip_serializing_if = "Option::is_none")]
213 pub max_image_size: Option<u64>,
214}
215
216#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
218pub struct SlotInfoImageSlot {
219 pub slot: u32,
221 pub size: u64,
223 #[serde(skip_serializing_if = "Option::is_none")]
225 pub upload_image_id: Option<u32>,
226}
227
228#[derive(Debug, Deserialize, Eq, PartialEq)]
230pub struct SlotInfoResponse {
231 pub images: Vec<SlotInfoImage>,
233}
234
235#[cfg(test)]
236mod tests {
237 use super::super::macros::command_encode_decode_test;
238 use super::*;
239 use ciborium::cbor;
240
241 command_encode_decode_test! {
242 get_image_state,
243 (0, 1, 0),
244 GetImageState,
245 cbor!({}),
246 cbor!({
247 "images" => [
248 {
249 "image" => 3,
250 "slot" => 5,
251 "version" => "v1.2.3",
252 "hash" => ciborium::Value::Bytes(vec![1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]),
253 "bootable" => true,
254 "pending" => true,
255 "confirmed" => true,
256 "active" => true,
257 "permanent" => true,
258 },
259 {
260 "image" => 4,
261 "slot" => 6,
262 "version" => "v5.5.5",
263 "bootable" => false,
264 "pending" => false,
265 "confirmed" => false,
266 "active" => false,
267 "permanent" => false,
268 },
269 {
270 "slot" => 9,
271 "version" => "8.6.4",
272 },
273 ],
274 "splitStatus" => 42,
275 }),
276 ImageStateResponse{
277 images: vec![
278 ImageState{
279 image: 3,
280 slot: 5,
281 version: "v1.2.3".to_string(),
282 hash: Some([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]),
283 bootable: true,
284 pending: true,
285 confirmed: true,
286 active: true,
287 permanent: true,
288 },
289 ImageState{
290 image: 4,
291 slot: 6,
292 version: "v5.5.5".to_string(),
293 hash: None,
294 bootable: false,
295 pending: false,
296 confirmed: false,
297 active: false,
298 permanent: false,
299 },
300 ImageState{
301 image: 0,
302 slot: 9,
303 version: "8.6.4".to_string(),
304 hash: None,
305 bootable: false,
306 pending: false,
307 confirmed: false,
308 active: false,
309 permanent: false,
310 }
311 ],
312 },
313 }
314
315 command_encode_decode_test! {
316 set_image_state_temp,
317 (2, 1, 0),
318 SetImageState {
319 confirm: false,
320 hash: Some(&[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]),
321 },
322 cbor!({
323 "hash" => ciborium::Value::Bytes(vec![1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]),
324 "confirm" => false,
325 }),
326 cbor!({
327 "images" => [],
328 }),
329 ImageStateResponse{
330 images: vec![],
331 },
332 }
333
334 command_encode_decode_test! {
335 set_image_state_perm,
336 (2, 1, 0),
337 SetImageState {
338 confirm: true,
339 hash: None,
340 },
341 cbor!({
342 "confirm" => true,
343 }),
344 cbor!({
345 "images" => [],
346 }),
347 ImageStateResponse{
348 images: vec![],
349 },
350 }
351
352 command_encode_decode_test! {
353 upload_image_first,
354 (2, 1, 1),
355 ImageUpload{
356 image: Some(2),
357 len: Some(123456789123),
358 off: 0,
359 sha: Some(&[0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1]),
360 data: &[5,6,7,8],
361 upgrade: Some(false),
362 },
363 cbor!({
364 "image" => 2,
365 "len" => 123456789123u64,
366 "off" => 0,
367 "sha" => ciborium::Value::Bytes(vec![0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1]),
368 "data" => ciborium::Value::Bytes(vec![5,6,7,8]),
369 "upgrade" => false,
370 }),
371 cbor!({
372 "off" => 4,
373 }),
374 ImageUploadResponse {
375 off: 4,
376 r#match: None,
377 },
378 }
379
380 command_encode_decode_test! {
381 upload_image_last,
382 (2, 1, 1),
383 ImageUpload{
384 image: None,
385 len: None,
386 off: 123456789118,
387 sha: None,
388 data: &[100, 101, 102, 103, 104],
389 upgrade: None,
390 },
391 cbor!({
392 "off" => 123456789118u64,
393 "data" => ciborium::Value::Bytes(vec![100, 101, 102, 103, 104]),
394 }),
395 cbor!({
396 "off" => 123456789123u64,
397 "match" => false,
398 }),
399 ImageUploadResponse {
400 off: 123456789123,
401 r#match: Some(false),
402 },
403 }
404
405 command_encode_decode_test! {
406 image_erase,
407 (2, 1, 5),
408 ImageErase{
409 slot: None
410 },
411 cbor!({}),
412 cbor!({}),
413 ImageEraseResponse,
414 }
415
416 command_encode_decode_test! {
417 image_erase_with_slot_number,
418 (2, 1, 5),
419 ImageErase{
420 slot: Some(42)
421 },
422 cbor!({
423 "slot" => 42,
424 }),
425 cbor!({}),
426 ImageEraseResponse,
427 }
428
429 command_encode_decode_test! {
430 slot_info,
431 (0, 1, 6),
432 SlotInfo,
433 cbor!({}),
434 cbor!({
435 "images" => [
436 {
437 "image" => 0,
438 "slots" => [
439 {
440 "slot" => 0,
441 "size" => 42,
442 "upload_image_id" => 2,
443 },
444 {
445 "slot" => 1,
446 "size" => 123456789012u64,
447 },
448 ],
449 "max_image_size" => 123456789987u64,
450 },
451 {
452 "image" => 1,
453 "slots" => [
454 ],
455 },
456 ],
457 }),
458 SlotInfoResponse{
459 images: vec![
460 SlotInfoImage {
461 image: 0,
462 slots: vec![
463 SlotInfoImageSlot {
464 slot: 0,
465 size: 42,
466 upload_image_id: Some(2),
467 },
468 SlotInfoImageSlot {
469 slot: 1,
470 size: 123456789012,
471 upload_image_id: None,
472 }
473 ],
474 max_image_size: Some(123456789987)
475 },
476 SlotInfoImage {
477 image: 1,
478 slots: vec![],
479 max_image_size: None,
480 }
481 ],
482 },
483 }
484
485 #[test]
486 fn image_upload_max_data_chunk_size() {
487 for smp_frame_size in 101..100000 {
488 let smp_payload_size = smp_frame_size - 8 ;
489
490 let max_data_size = super::image_upload_max_data_chunk_size(smp_frame_size).unwrap();
491
492 let cmd = ImageUpload {
493 off: u64::MAX,
494 data: &vec![0; max_data_size],
495 len: Some(u64::MAX),
496 image: Some(u32::MAX),
497 sha: Some(&[u8::MAX; 32]),
498 upgrade: Some(true),
499 };
500
501 let mut cbor_data = vec![];
502 ciborium::into_writer(&cmd, &mut cbor_data).unwrap();
503
504 assert!(
505 smp_payload_size - 2 <= cbor_data.len() && cbor_data.len() <= smp_payload_size,
506 "Failed at frame size {}: actual={}, max={}",
507 smp_frame_size,
508 cbor_data.len(),
509 smp_payload_size,
510 );
511 }
512 }
513
514 #[test]
515 fn image_upload_max_data_chunk_size_too_small() {
516 for smp_frame_size in 0..101 {
517 let max_data_size = super::image_upload_max_data_chunk_size(smp_frame_size);
518
519 assert!(max_data_size.is_err());
520 }
521 }
522}