Skip to main content

mcumgr_toolkit/commands/
os.rs

1use std::collections::HashMap;
2
3use chrono::Timelike;
4use serde::{Deserialize, Serialize};
5
6use super::{
7    is_default,
8    macros::{impl_deserialize_from_empty_map_and_into_unit, impl_serialize_as_empty_map},
9};
10
11/// [Echo](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_groups/smp_group_0.html#echo-command) command
12#[derive(Clone, Debug, Serialize, Eq, PartialEq)]
13pub struct Echo<'a> {
14    /// string to be replied by echo service
15    pub d: &'a str,
16}
17
18/// Response for [`Echo`] command
19#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
20pub struct EchoResponse {
21    /// replying echo string
22    pub r: String,
23}
24
25/// [Task statistics](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_groups/smp_group_0.html#task-statistics-command) command
26#[derive(Clone, Debug, Eq, PartialEq)]
27pub struct TaskStatistics;
28impl_serialize_as_empty_map!(TaskStatistics);
29
30/// Statistics of an MCU task/thread
31#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
32pub struct TaskStatisticsEntry {
33    /// task priority
34    pub prio: i32,
35    /// numeric task ID
36    pub tid: u32,
37    /// numeric task state
38    pub state: u32,
39    /// task’s/thread’s stack usage
40    pub stkuse: Option<u64>,
41    /// task’s/thread’s stack size
42    pub stksiz: Option<u64>,
43    /// task’s/thread’s context switches
44    pub cswcnt: Option<u64>,
45    /// task’s/thread’s runtime in “ticks”
46    pub runtime: Option<u64>,
47}
48
49/// Flags inside of [`TaskStatisticsEntry::state`]
50#[derive(strum::Display, strum::AsRefStr, strum::EnumIter, Debug, Copy, Clone, PartialEq, Eq)]
51#[repr(u8)]
52#[strum(serialize_all = "snake_case")]
53pub enum ThreadStateFlags {
54    /** Not a real thread */
55    DUMMY = 1 << 0,
56
57    /** Thread is waiting on an object */
58    PENDING = 1 << 1,
59
60    /** Thread is sleeping */
61    SLEEPING = 1 << 2,
62
63    /** Thread has terminated */
64    DEAD = 1 << 3,
65
66    /** Thread is suspended */
67    SUSPENDED = 1 << 4,
68
69    /** Thread is in the process of aborting */
70    ABORTING = 1 << 5,
71
72    /** Thread is in the process of suspending */
73    SUSPENDING = 1 << 6,
74
75    /** Thread is present in the ready queue */
76    QUEUED = 1 << 7,
77}
78
79impl ThreadStateFlags {
80    /// Converts the thread state to a human readable string
81    pub fn pretty_print(thread_state: u8) -> String {
82        use strum::IntoEnumIterator;
83
84        let mut bit_names = vec![];
85        for bit in Self::iter() {
86            if (thread_state & bit as u8) != 0 {
87                bit_names.push(format!("{bit}"));
88            }
89        }
90
91        bit_names.join(" | ")
92    }
93}
94
95/// Response for [`TaskStatistics`] command
96#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
97pub struct TaskStatisticsResponse {
98    /// Dictionary of task names with their respective statistics
99    pub tasks: HashMap<String, TaskStatisticsEntry>,
100}
101
102/// [Memory Pool statistics](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_groups/smp_group_0.html#memory-pool-statistics) command
103#[derive(Clone, Debug, Eq, PartialEq)]
104pub struct MemoryPoolStatistics;
105impl_serialize_as_empty_map!(MemoryPoolStatistics);
106
107const fn default_blksiz() -> u64 {
108    1
109}
110
111/// Statistics of a MCU memory pool
112#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
113pub struct MemoryPoolStatisticsEntry {
114    /// size of a memory block in the pool
115    #[serde(default = "default_blksiz")]
116    pub blksiz: u64,
117    /// number of blocks in the pool
118    pub nblks: u64,
119    /// number of free blocks
120    pub nfree: u64,
121    /// lowest number of free blocks the pool reached during runtime
122    pub min: u64,
123}
124
125/// Response for [`MemoryPoolStatistics`] command
126#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
127pub struct MemoryPoolStatisticsResponse {
128    /// Dictionary of pool names with their respective statistics
129    pub pools: HashMap<String, MemoryPoolStatisticsEntry>,
130}
131
132/// Response for [`MemoryPoolStatistics`] command,
133/// after https://github.com/zephyrproject-rtos/zephyr/pull/119769.
134#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
135struct MemoryPoolStatisticsResponseMpools {
136    /// Dictionary of pool names with their respective statistics
137    pub mpools: HashMap<String, MemoryPoolStatisticsEntry>,
138}
139
140/// Response for [`MemoryPoolStatistics`] command,
141/// before https://github.com/zephyrproject-rtos/zephyr/pull/107251.
142#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
143struct MemoryPoolStatisticsResponseTasks {
144    /// Dictionary of pool names with their respective statistics
145    pub tasks: HashMap<String, MemoryPoolStatisticsEntry>,
146}
147
148impl<'de> serde::Deserialize<'de> for MemoryPoolStatisticsResponse {
149    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
150    where
151        D: serde::Deserializer<'de>,
152    {
153        #[derive(Deserialize)]
154        #[serde(untagged)]
155        enum ResponseVariants {
156            Mpools(MemoryPoolStatisticsResponseMpools),
157            Flat(HashMap<String, MemoryPoolStatisticsEntry>),
158            Tasks(MemoryPoolStatisticsResponseTasks),
159        }
160
161        match ResponseVariants::deserialize(deserializer)? {
162            ResponseVariants::Mpools(response) => Ok(Self {
163                pools: response.mpools,
164            }),
165            ResponseVariants::Flat(pools) => Ok(Self { pools }),
166            ResponseVariants::Tasks(response) => Ok(Self {
167                pools: response.tasks,
168            }),
169        }
170    }
171}
172
173/// Parses a [`chrono::NaiveDateTime`] object with optional timezone specifiers
174fn deserialize_datetime_and_ignore_timezone<'de, D>(
175    de: D,
176) -> Result<chrono::NaiveDateTime, D::Error>
177where
178    D: serde::Deserializer<'de>,
179{
180    #[derive(Deserialize)]
181    #[serde(untagged)]
182    enum NaiveOrFixed {
183        Naive(chrono::NaiveDateTime),
184        Fixed(chrono::DateTime<chrono::FixedOffset>),
185    }
186
187    NaiveOrFixed::deserialize(de).map(|val| match val {
188        NaiveOrFixed::Naive(naive_date_time) => naive_date_time,
189        NaiveOrFixed::Fixed(date_time) => date_time.naive_local(),
190    })
191}
192
193/// Serializes a [`chrono::NaiveDateTime`] object with zero or three fractional digits,
194/// which is most compatible with Zephyr
195fn serialize_datetime_for_zephyr<S>(
196    value: &chrono::NaiveDateTime,
197    serializer: S,
198) -> Result<S::Ok, S::Error>
199where
200    S: serde::Serializer,
201{
202    if value.time().nanosecond() != 0 {
203        serializer.serialize_str(&format!("{}", value.format("%Y-%m-%dT%H:%M:%S%.3f")))
204    } else {
205        serializer.serialize_str(&format!("{}", value.format("%Y-%m-%dT%H:%M:%S")))
206    }
207}
208
209/// [Date-Time Get](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_groups/smp_group_0.html#date-time-get) command
210#[derive(Clone, Debug, Eq, PartialEq)]
211pub struct DateTimeGet;
212impl_serialize_as_empty_map!(DateTimeGet);
213
214/// Response for [`DateTimeGet`] command
215#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
216pub struct DateTimeGetResponse {
217    /// String in format: `yyyy-MM-dd'T'HH:mm:ss.SSS`.
218    #[serde(deserialize_with = "deserialize_datetime_and_ignore_timezone")]
219    pub datetime: chrono::NaiveDateTime,
220}
221
222/// [Date-Time Set](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_groups/smp_group_0.html#date-time-set) command
223#[derive(Clone, Serialize, Debug, Eq, PartialEq)]
224pub struct DateTimeSet {
225    /// String in format: `yyyy-MM-dd'T'HH:mm:ss.SSS`.
226    #[serde(serialize_with = "serialize_datetime_for_zephyr")]
227    pub datetime: chrono::NaiveDateTime,
228}
229
230/// Response for [`DateTimeSet`] command
231#[derive(Clone, Default, Debug, Eq, PartialEq)]
232pub struct DateTimeSetResponse;
233impl_deserialize_from_empty_map_and_into_unit!(DateTimeSetResponse);
234
235/// [System Reset](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_groups/smp_group_0.html#system-reset) command
236#[derive(Clone, Serialize, Debug, Eq, PartialEq)]
237pub struct SystemReset {
238    /// Forces reset
239    #[serde(skip_serializing_if = "is_default")]
240    pub force: bool,
241    /// Boot mode
242    ///
243    /// - 0: Normal boot
244    /// - 1: Bootloader recovery mode
245    ///
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub boot_mode: Option<u8>,
248}
249
250/// Response for [`SystemReset`] command
251#[derive(Clone, Default, Debug, Eq, PartialEq)]
252pub struct SystemResetResponse;
253impl_deserialize_from_empty_map_and_into_unit!(SystemResetResponse);
254
255/// [MCUmgr Parameters](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_groups/smp_group_0.html#mcumgr-parameters) command
256#[derive(Clone, Debug, Eq, PartialEq)]
257pub struct MCUmgrParameters;
258impl_serialize_as_empty_map!(MCUmgrParameters);
259
260/// Response for [`MCUmgrParameters`] command
261#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
262pub struct MCUmgrParametersResponse {
263    /// Single SMP buffer size, this includes SMP header and CBOR payload
264    pub buf_size: u32,
265    /// Number of SMP buffers supported
266    pub buf_count: u32,
267}
268
269/// [OS/Application Info](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_groups/smp_group_0.html#os-application-info) command
270#[derive(Clone, Serialize, Debug, Eq, PartialEq)]
271pub struct ApplicationInfo<'a> {
272    /// Format specifier of returned response
273    ///
274    /// For more info, see [the SMP documentation](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_groups/smp_group_0.html#os-application-info-request).
275    #[serde(skip_serializing_if = "Option::is_none")]
276    pub format: Option<&'a str>,
277}
278
279/// Response for [`ApplicationInfo`] command
280#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
281pub struct ApplicationInfoResponse {
282    /// Text response including requested parameters
283    pub output: String,
284}
285
286/// [Bootloader Information](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_groups/smp_group_0.html#bootloader-information) command
287#[derive(Clone, Debug, Eq, PartialEq)]
288pub struct BootloaderInfo;
289impl_serialize_as_empty_map!(BootloaderInfo);
290
291/// Response for [`BootloaderInfo`] command
292#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
293pub struct BootloaderInfoResponse {
294    /// String representing bootloader name
295    pub bootloader: String,
296}
297
298/// [Bootloader Information MCUboot Mode](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_groups/smp_group_0.html#bootloader-information-mcuboot) subcommand
299#[derive(Clone, Serialize, Debug, Eq, PartialEq)]
300#[serde(tag = "query", rename = "mode")]
301pub struct BootloaderInfoMcubootMode {}
302
303/// Response for [`BootloaderInfoMcubootMode`] command
304#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
305pub struct BootloaderInfoMcubootModeResponse {
306    /// The bootloader mode
307    pub mode: i32,
308    /// MCUboot has downgrade prevention enabled
309    #[serde(default, rename = "no-downgrade")]
310    pub no_downgrade: bool,
311}
312
313#[cfg(test)]
314mod tests {
315    use super::super::macros::command_encode_decode_test;
316    use super::*;
317    use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
318    use ciborium::cbor;
319
320    #[test]
321    fn thread_state_flags_to_string() {
322        assert_eq!(
323            ThreadStateFlags::pretty_print(0xff),
324            "dummy | pending | sleeping | dead | suspended | aborting | suspending | queued"
325        );
326
327        assert_eq!(ThreadStateFlags::pretty_print(0b00000001), "dummy");
328        assert_eq!(ThreadStateFlags::pretty_print(0b00000010), "pending");
329        assert_eq!(ThreadStateFlags::pretty_print(0b00000100), "sleeping");
330        assert_eq!(ThreadStateFlags::pretty_print(0b00001000), "dead");
331        assert_eq!(ThreadStateFlags::pretty_print(0b00010000), "suspended");
332        assert_eq!(ThreadStateFlags::pretty_print(0b00100000), "aborting");
333        assert_eq!(ThreadStateFlags::pretty_print(0b01000000), "suspending");
334        assert_eq!(ThreadStateFlags::pretty_print(0b10000000), "queued");
335
336        assert_eq!(ThreadStateFlags::pretty_print(0), "");
337    }
338
339    command_encode_decode_test! {
340        echo,
341        (0, 0, 0),
342        Echo{d: "Hello World!"},
343        cbor!({"d" => "Hello World!"}),
344        cbor!({"r" => "Hello World!"}),
345        EchoResponse{r: "Hello World!".to_string()},
346    }
347
348    command_encode_decode_test! {
349        task_statistics_empty,
350        (0, 0, 2),
351        TaskStatistics,
352        cbor!({}),
353        cbor!({"tasks" => {}}),
354        TaskStatisticsResponse{ tasks: HashMap::new() },
355    }
356
357    command_encode_decode_test! {
358        task_statistics,
359        (0, 0, 2),
360        TaskStatistics,
361        cbor!({}),
362        cbor!({"tasks" => {
363            "task_a" => {
364                "prio" => 20,
365                "tid" => 5,
366                "state" => 10,
367            },
368            "task_b" => {
369                "prio"         => 30,
370                "tid"          => 31,
371                "state"        => 32,
372                "stkuse"       => 33,
373                "stksiz"       => 34,
374                "cswcnt"       => 35,
375                "runtime"      => 36,
376                "last_checkin" => 0,
377                "next_checkin" => 0,
378            },
379        }}),
380        TaskStatisticsResponse{ tasks: HashMap::from([
381            (
382                "task_a".to_string(),
383                TaskStatisticsEntry{
384                    prio: 20,
385                    tid: 5,
386                    state: 10,
387                    stkuse: None,
388                    stksiz: None,
389                    cswcnt: None,
390                    runtime: None,
391                },
392            ), (
393                "task_b".to_string(),
394                TaskStatisticsEntry{
395                    prio: 30,
396                    tid: 31,
397                    state: 32,
398                    stkuse: Some(33),
399                    stksiz: Some(34),
400                    cswcnt: Some(35),
401                    runtime: Some(36),
402                },
403            ),
404        ]) },
405    }
406
407    command_encode_decode_test! {
408        memory_pool_statistics_empty,
409        (0, 0, 3),
410        MemoryPoolStatistics,
411        cbor!({}),
412        cbor!({"mpools" => {}}),
413        MemoryPoolStatisticsResponse{ pools: HashMap::new() },
414    }
415
416    command_encode_decode_test! {
417        memory_pool_statistics_empty_old_1,
418        (0, 0, 3),
419        MemoryPoolStatistics,
420        cbor!({}),
421        cbor!({}),
422        MemoryPoolStatisticsResponse{ pools: HashMap::new() },
423    }
424
425    command_encode_decode_test! {
426        memory_pool_statistics_empty_old_2,
427        (0, 0, 3),
428        MemoryPoolStatistics,
429        cbor!({}),
430        cbor!({"tasks" => {}}),
431        MemoryPoolStatisticsResponse{ pools: HashMap::new() },
432    }
433
434    command_encode_decode_test! {
435        memory_pool_statistics,
436        (0, 0, 3),
437        MemoryPoolStatistics,
438        cbor!({}),
439        cbor!({ "mpools" => {
440            "pool_a" => {
441                "blksiz" => 8,
442                "nblks" => 20,
443                "nfree" => 10,
444                "min" => 5,
445            },
446            "pool_b" => {
447                "nblks" => 50,
448                "nfree" => 35,
449                "min" => 30,
450            },
451        }}),
452        MemoryPoolStatisticsResponse{ pools: HashMap::from([
453            (
454                "pool_a".to_string(),
455                MemoryPoolStatisticsEntry{
456                    blksiz: 8,
457                    nblks: 20,
458                    nfree: 10,
459                    min: 5,
460                },
461            ), (
462                "pool_b".to_string(),
463                MemoryPoolStatisticsEntry{
464                    blksiz: 1,
465                    nblks: 50,
466                    nfree: 35,
467                    min: 30,
468                },
469            ),
470        ]) },
471    }
472
473    command_encode_decode_test! {
474        memory_pool_statistics_old_1,
475        (0, 0, 3),
476        MemoryPoolStatistics,
477        cbor!({}),
478        cbor!({
479            "pool_a" => {
480                "blksiz" => 8,
481                "nblks" => 20,
482                "nfree" => 10,
483                "min" => 5,
484            },
485            "pool_b" => {
486                "nblks" => 50,
487                "nfree" => 35,
488                "min" => 30,
489            },
490        }),
491        MemoryPoolStatisticsResponse{ pools: HashMap::from([
492            (
493                "pool_a".to_string(),
494                MemoryPoolStatisticsEntry{
495                    blksiz: 8,
496                    nblks: 20,
497                    nfree: 10,
498                    min: 5,
499                },
500            ), (
501                "pool_b".to_string(),
502                MemoryPoolStatisticsEntry{
503                    blksiz: 1,
504                    nblks: 50,
505                    nfree: 35,
506                    min: 30,
507                },
508            ),
509        ]) },
510    }
511
512    command_encode_decode_test! {
513        memory_pool_statistics_old_2,
514        (0, 0, 3),
515        MemoryPoolStatistics,
516        cbor!({}),
517        cbor!({ "tasks" => {
518            "pool_a" => {
519                "blksiz" => 8,
520                "nblks" => 20,
521                "nfree" => 10,
522                "min" => 5,
523            },
524            "pool_b" => {
525                "nblks" => 50,
526                "nfree" => 35,
527                "min" => 30,
528            },
529        }}),
530        MemoryPoolStatisticsResponse{ pools: HashMap::from([
531            (
532                "pool_a".to_string(),
533                MemoryPoolStatisticsEntry{
534                    blksiz: 8,
535                    nblks: 20,
536                    nfree: 10,
537                    min: 5,
538                },
539            ), (
540                "pool_b".to_string(),
541                MemoryPoolStatisticsEntry{
542                    blksiz: 1,
543                    nblks: 50,
544                    nfree: 35,
545                    min: 30,
546                },
547            ),
548        ]) },
549    }
550
551    command_encode_decode_test! {
552        datetime_get_with_timezone,
553        (0, 0, 4),
554        DateTimeGet,
555        cbor!({}),
556        cbor!({
557            "datetime" => "2025-11-20T11:56:05.366345+01:00"
558        }),
559        DateTimeGetResponse{
560            datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_micro_opt(11,56,5,366345).unwrap()),
561        },
562    }
563
564    command_encode_decode_test! {
565        datetime_get_with_millis,
566        (0, 0, 4),
567        DateTimeGet,
568        cbor!({}),
569        cbor!({
570            "datetime" => "2025-11-20T11:56:05.366"
571        }),
572        DateTimeGetResponse{
573            datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_milli_opt(11,56,5,366).unwrap()),
574        },
575    }
576
577    command_encode_decode_test! {
578        datetime_get_without_millis,
579        (0, 0, 4),
580        DateTimeGet,
581        cbor!({}),
582        cbor!({
583            "datetime" => "2025-11-20T11:56:05"
584        }),
585        DateTimeGetResponse{
586            datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_opt(11,56,5).unwrap()),
587        },
588    }
589
590    command_encode_decode_test! {
591        datetime_set_with_millis,
592        (2, 0, 4),
593        DateTimeSet{
594            datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_micro_opt(12,3,56,642133).unwrap())
595        },
596        cbor!({
597            "datetime" => "2025-11-20T12:03:56.642"
598        }),
599        cbor!({}),
600        DateTimeSetResponse,
601    }
602
603    command_encode_decode_test! {
604        datetime_set_without_millis,
605        (2, 0, 4),
606        DateTimeSet{
607            datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_opt(12,3,56).unwrap())
608        },
609        cbor!({
610            "datetime" => "2025-11-20T12:03:56"
611        }),
612        cbor!({}),
613        DateTimeSetResponse,
614    }
615
616    command_encode_decode_test! {
617        system_reset_minimal,
618        (2, 0, 5),
619        SystemReset{
620            force: false,
621            boot_mode: None,
622        },
623        cbor!({}),
624        cbor!({}),
625        SystemResetResponse,
626    }
627
628    command_encode_decode_test! {
629        system_reset_full,
630        (2, 0, 5),
631        SystemReset{
632            force: true,
633            boot_mode: Some(42),
634        },
635        cbor!({
636            "force" => true,
637            "boot_mode" => 42,
638        }),
639        cbor!({}),
640        SystemResetResponse,
641    }
642
643    command_encode_decode_test! {
644        mcumgr_parameters,
645        (0, 0, 6),
646        MCUmgrParameters,
647        cbor!({}),
648        cbor!({"buf_size" => 42, "buf_count" => 69}),
649        MCUmgrParametersResponse{buf_size: 42, buf_count: 69 },
650    }
651
652    command_encode_decode_test! {
653        application_info_without_format,
654        (0, 0, 7),
655        ApplicationInfo{
656            format: None,
657        },
658        cbor!({}),
659        cbor!({
660            "output" => "foo",
661        }),
662        ApplicationInfoResponse{
663            output: "foo".to_string(),
664        }
665    }
666
667    command_encode_decode_test! {
668        application_info_with_format,
669        (0, 0, 7),
670        ApplicationInfo{
671            format: Some("abc"),
672        },
673        cbor!({
674            "format" => "abc",
675        }),
676        cbor!({
677            "output" => "bar",
678        }),
679        ApplicationInfoResponse{
680            output: "bar".to_string(),
681        }
682    }
683
684    command_encode_decode_test! {
685        bootloader_info,
686        (0, 0, 8),
687        BootloaderInfo,
688        cbor!({}),
689        cbor!({
690            "bootloader" => "MCUboot",
691        }),
692        BootloaderInfoResponse{
693            bootloader: "MCUboot".to_string(),
694        }
695    }
696
697    command_encode_decode_test! {
698        bootloader_info_mcuboot_mode,
699        (0, 0, 8),
700        BootloaderInfoMcubootMode{},
701        cbor!({
702            "query" => "mode",
703        }),
704        cbor!({
705            "mode" => 5,
706            "no-downgrade" => true,
707        }),
708        BootloaderInfoMcubootModeResponse{
709            mode: 5,
710            no_downgrade: true,
711        }
712    }
713
714    command_encode_decode_test! {
715        bootloader_info_mcuboot_mode_default_values,
716        (0, 0, 8),
717        BootloaderInfoMcubootMode{},
718        cbor!({
719            "query" => "mode",
720        }),
721        cbor!({
722            "mode" => -1,
723        }),
724        BootloaderInfoMcubootModeResponse{
725            mode: -1,
726            no_downgrade: false,
727        }
728    }
729}