diff --git a/subsys/mgmt/mcumgr/CMakeLists.txt b/subsys/mgmt/mcumgr/CMakeLists.txt index c9b94901f2b3..6d11f2ffde92 100644 --- a/subsys/mgmt/mcumgr/CMakeLists.txt +++ b/subsys/mgmt/mcumgr/CMakeLists.txt @@ -8,8 +8,11 @@ zephyr_library_sources_ifdef(CONFIG_MCUMGR_SMP_SHELL smp_shell.c) zephyr_library_sources_ifdef(CONFIG_MCUMGR_SMP_UART smp_uart.c) zephyr_library_sources_ifdef(CONFIG_MCUMGR_SMP_UDP smp_udp.c) add_subdirectory_ifdef(CONFIG_MCUMGR_GRP_ZEPHYR_BASIC zephyr_grp) + +add_subdirectory(lib) zephyr_library_link_libraries(MCUMGR) if (CONFIG_MCUMGR_SMP_SHELL OR CONFIG_MCUMGR_SMP_UART) zephyr_library_sources(serial_util.c) + endif () diff --git a/subsys/mgmt/mcumgr/Kconfig b/subsys/mgmt/mcumgr/Kconfig index b80c10c13ed8..422f75d1e42e 100644 --- a/subsys/mgmt/mcumgr/Kconfig +++ b/subsys/mgmt/mcumgr/Kconfig @@ -30,6 +30,14 @@ config MGMT_CBORATTR_MAX_SIZE help The maximum size of a CBOR attribute during decoding +config MGMT_CBORATTR_FLOAT_SUPPORT + bool "Enable support for float" + help + The option enables float processing within CBOR attributes. + If your code requires processing of float numbers by CBOR, + the option needs to be enabled. + Disabling the option slightly reduces code footprint. + menu "Command Handlers" menuconfig MCUMGR_CMD_FS_MGMT bool "Enable mcumgr handlers for file management (insecure)" @@ -201,6 +209,14 @@ config IMG_MGMT_REJECT_DIRECT_XIP_MISMATCHED_SLOT The base address can be set, to an image binary header, with imgtool, using the --rom-fixed command line option. +config IMG_MGMT_FRUGAL_LIST + bool "Omit zero, empty or false values from status list" + help + The status list send back from the device will only be filled with data that is non-zero, + non-empty or true. This option slightly reduces number of bytes transferred back from + a device but requires support in client software, which has to default omitted values. + Works correctly with mcumgr-cli. + endif diff --git a/subsys/mgmt/mcumgr/lib/CMakeLists.txt b/subsys/mgmt/mcumgr/lib/CMakeLists.txt new file mode 100644 index 000000000000..efe895727ff7 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/CMakeLists.txt @@ -0,0 +1,20 @@ +# +# Copyright (c) 2018-2021 mcumgr authors +# +# SPDX-License-Identifier: Apache-2.0 +# + +zephyr_interface_library_named(MCUMGR) + +target_include_directories(MCUMGR INTERFACE + mgmt/include + cborattr/include + util/include + smp/include +) + +add_subdirectory(cborattr) +add_subdirectory(cmd) +add_subdirectory(mgmt) +add_subdirectory(smp) +add_subdirectory(util) diff --git a/subsys/mgmt/mcumgr/lib/README.md b/subsys/mgmt/mcumgr/lib/README.md new file mode 100644 index 000000000000..977c052ed164 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/README.md @@ -0,0 +1,141 @@ +# mcumgr + +This is mcumgr, version 0.2.0 + +mcumgr is a management library for 32-bit MCUs. The goal of mcumgr is to +define a common management infrastructure with pluggable transport and encoding +components. In addition, mcumgr provides definitions and handlers for some +core commands: image management, file system management, and OS management. + +mcumgr is operating system and hardware independent. It relies on hardware +porting layers from the operating system it runs on. + +## Configuration + +The `samples/smp_svr/zephyr/prj.conf` file provides a good starting point for +configuring an application to use *mcumgr*. The major configuration settings +are described below: + +| Setting | Description | Default | +| ------------- | ------------- | ------- | +| `CONFIG_MCUMGR` | Enable the mcumgr management library. | n | +| `CONFIG_MCUMGR_CMD_FS_MGMT` | Enable mcumgr handlers for file management | n | +| `CONFIG_MCUMGR_CMD_IMG_MGMT` | Enable mcumgr handlers for image management | n | +| `CONFIG_MCUMGR_CMD_OS_MGMT` | Enable mcumgr handlers for OS management | n | +| `CONFIG_MCUMGR_CMD_STAT_MGMT` | Enable mcumgr handlers for statistics | n | +| `CONFIG_MCUMGR_GRP_ZEPHYR_BASIC` | Enable mcumgr basic commands group | n | + +## Dependencies + +To use mcumgr's image management support, your device must be running version +1.1.0 or later of the [MCUboot boot +loader](https://github.com/mcu-tools/mcuboot). The other mcumgr features do +not require MCUboot. + +## Command line tool + +The `mcumgr` command line tool is available at: +https://github.com/apache/mynewt-mcumgr-cli. The command line tool requires [Go +1.12 or later](https://golang.org/dl/). Once Go is installed and set up on your +system, you can install the mcumgr CLI tool by issuing the following `go get` +command: + +``` +$ go get github.com/apache/mynewt-mcumgr-cli/mcumgr +``` + +The `mcumgr` tool allows you to manage devices running an mcumgr server. + +## Architecture + +The mcumgr stack has the following layout: + +``` ++---------------------+---------------------+ +| | ++---------------------+---------------------+ +| mgmt | ++---------------------+---------------------+ +| | ++---------------------+---------------------+ +| | ++---------------------+---------------------+ +``` + +Items enclosed in angled brackets represent generic components that can be plugged into mcumgr. The items in this stack diagram are defined below: +* *Command handler*: Processes incoming mcumgr requests and generates corresponding responses. A command handler is associated with a single command type, defined by a (group ID, command ID) pair. +* *mgmt*: The core of mcumgr; facilitates the passing of requests and responses between the generic command handlers and the concrete transports and transfer encodings. +* *Transfer encoding*: Defines how mcumgr requests and responses are encoded on the wire. +* *Transport*: Sends and receives mcumgr packets over a particular medium. + +Each transport is configured with a single transfer encoding. + +As an example, the sample application `smp_svr` uses the following components: + +* Command handlers: + * Image management (`img_mgmt`) + * File system management (`fs_mgmt`) + * OS management (`os_mgmt`) +* Transfer/Transports protocols: + * SMP/Bluetooth + * SMP/Shell + +yielding the following stack diagram: + +``` ++----------+----------+----------+----------+ +| img_mgmt | fs_mgmt | os_mgmt | ... | ++----------+----------+----------+----------+ +| mgmt | ++---------------------+---------------------+ +| SMP | SMP | ++---------------------+---------------------+ +| Bluetooth | Shell | ++---------------------+---------------------+ +``` + +## Command definition + +An mcumgr request or response consists of the following two components: +* mcumgr header +* CBOR key-value map + +How these two components are encoded and parsed depends on the transfer +encoding used. + +The mcumgr header structure is defined in `mgmt/include/mgmt/mgmt.h` as +`struct mgmt_hdr`. + +The contents of the CBOR key-value map are specified per command type. + +## Supported transfer encodings + +Mcumgr comes with one built-in transfer encoding: Simple Management Protocol +(SMP). SMP requests and responses have a very basic structure. For details, +see the comments at the top of `smp/include/smp/smp.h`. + +## Supported transports + +The mcumgr project defines two transports: +* [SMP/Console](transport/smp-console.md) +* [SMP/Bluetooth](transport/smp-bluetooth.md) + +Implementations, being hardware- and OS-specific, are not included. + +## Browsing + +Information and documentation for mcumgr is stored within the source. + +For more information in the source, here are some pointers: + +- [cborattr](cborattr): Used for parsing incoming mcumgr requests. Destructures mcumgr packets and populates corresponding field variables. +- [cmd](cmd): Built-in command handlers for the core mcumgr commands. +- [ext](ext): Third-party libraries that mcumgr depends on. +- [mgmt](mgmt): Code implementing the `mgmt` layer of mcumgr. +- [smp](smp): The built-in transfer encoding: Simple management protocol. + +## Joining + +Developers welcome! + +* Discord mcumgr channel: https://discord.com/invite/Ck7jw53nU2 diff --git a/subsys/mgmt/mcumgr/lib/cborattr/CMakeLists.txt b/subsys/mgmt/mcumgr/lib/cborattr/CMakeLists.txt new file mode 100644 index 000000000000..1795a7fa7f20 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/CMakeLists.txt @@ -0,0 +1,13 @@ +# +# Copyright (c) 2018-2021 mcumgr authors +# +# SPDX-License-Identifier: Apache-2.0 +# + +target_include_directories(MCUMGR INTERFACE + include +) + +zephyr_library_sources( + src/cborattr.c +) diff --git a/subsys/mgmt/mcumgr/lib/cborattr/include/cborattr/cborattr.h b/subsys/mgmt/mcumgr/lib/cborattr/include/cborattr/cborattr.h new file mode 100644 index 000000000000..99bd6538145a --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/include/cborattr/cborattr.h @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef CBORATTR_H +#define CBORATTR_H + +#include +#include +#include +#include +#include +#include "tinycbor/cbor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* This library wraps the tinycbor decoder with a attribute based decoder + * suitable for decoding a binary version of json. Specifically, the + * contents of the cbor contains pairs of attributes. where the attribute + * is a key/value pair. keys are always text strings, but values can be + * many different things (enumerated below) + */ + +enum CborAttrType { + CborAttrIntegerType = 1, + CborAttrUnsignedIntegerType, + CborAttrByteStringType, + CborAttrTextStringType, + CborAttrBooleanType, + CborAttrHalfFloatType, + CborAttrFloatType, + CborAttrDoubleType, + CborAttrArrayType, + CborAttrObjectType, + CborAttrStructObjectType, + CborAttrNullType, +}; + +struct cbor_attr_t; + +struct cbor_enum_t { + char *name; + long long value; +}; + +struct cbor_array_t { + enum CborAttrType element_type; + union { + struct { + const struct cbor_attr_t *subtype; + char *base; + size_t stride; + } objects; + struct { + char **ptrs; + char *store; + int storelen; + } strings; + struct { + long long *store; + } integers; + struct { + unsigned long long *store; + } uintegers; + struct { + double *store; + } reals; + struct{ + uint16_t *store; + } halffloats; + struct { + bool *store; + } booleans; + } arr; + int *count; + int maxlen; +}; + +struct cbor_attr_t { + char *attribute; + enum CborAttrType type; + union { + long long *integer; + unsigned long long *uinteger; + uint16_t *halffloat; + double *real; + float *fval; + char *string; + bool *boolean; + struct byte_string { + uint8_t *data; + size_t *len; + } bytestring; + struct cbor_array_t array; + size_t offset; + struct cbor_attr_t *obj; + } addr; + union { + long long integer; + double real; + bool boolean; + float fval; + uint16_t halffloat; + } dflt; + size_t len; + bool nodefault; +}; + +/* + * Use the following macros to declare template initializers for + * CborAttrStructObjectType arrays. Writing the equivalents out by hand is + * error-prone. + * + * CBOR_STRUCT_OBJECT takes a structure name s, and a fieldname f in s. + * + * CBOR_STRUCT_ARRAY takes the name of a structure array, a pointer to a an + * initializer defining the subobject type, and the address of an integer to + * store the length in. + */ +#define CBORATTR_STRUCT_OBJECT(s, f) .addr.offset = offsetof(s, f) +#define CBORATTR_STRUCT_ARRAY(a, e, n) \ + .addr.array.element_type = CborAttrStructObjectType, \ + .addr.array.arr.objects.subtype = e, \ + .addr.array.arr.objects.base = (char *)a, \ + .addr.array.arr.objects.stride = sizeof(a[0]), \ + .addr.array.count = n, \ + .addr.array.maxlen = ARRAY_SIZE(a) + +#define CBORATTR_ATTR_UNNAMED (char *)(-1) + +int cbor_read_object(struct CborValue *val, const struct cbor_attr_t *attr); +int cbor_read_array(struct CborValue *val, const struct cbor_array_t *arr); + +int cbor_read_flat_attrs(const uint8_t *data, int len, + const struct cbor_attr_t *attrs); +#ifdef __cplusplus +} +#endif + +#endif /* CBORATTR_H */ diff --git a/subsys/mgmt/mcumgr/lib/cborattr/src/cborattr.c b/subsys/mgmt/mcumgr/lib/cborattr/src/cborattr.c new file mode 100644 index 000000000000..4ca429ddb708 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/src/cborattr.c @@ -0,0 +1,410 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "cborattr/cborattr.h" +#include "tinycbor/cbor.h" +#include "tinycbor/cbor_buf_reader.h" + +#include +#ifdef CONFIG_MGMT_CBORATTR_MAX_SIZE +#define CBORATTR_MAX_SIZE CONFIG_MGMT_CBORATTR_MAX_SIZE +#else +#define CBORATTR_MAX_SIZE 512 +#endif + +#ifdef CONFIG_MGMT_CBORATTR_FLOAT_SUPPORT +#define CBORATTR_FLOAT_SUPPORT 0 +#else +#define CBORATTR_FLOAT_SUPPORT 1 +#endif + +/* this maps a CborType to a matching CborAtter Type. The mapping is not + * one-to-one because of signedness of integers + * and therefore we need a function to do this trickery + */ +static int +valid_attr_type(CborType ct, enum CborAttrType at) +{ + switch (at) { + case CborAttrIntegerType: + case CborAttrUnsignedIntegerType: + if (ct == CborIntegerType) { + return 1; + } + break; + case CborAttrByteStringType: + if (ct == CborByteStringType) { + return 1; + } + break; + case CborAttrTextStringType: + if (ct == CborTextStringType) { + return 1; + } + break; + case CborAttrBooleanType: + if (ct == CborBooleanType) { + return 1; + } + break; +#if CBORATTR_FLOAT_SUPPORT != 0 + case CborAttrHalfFloatType: + if (ct == CborHalfFloatType) { + return 1; + } + break; + case CborAttrFloatType: + if (ct == CborFloatType) { + return 1; + } + break; + case CborAttrDoubleType: + if (ct == CborDoubleType) { + return 1; + } + break; +#endif + case CborAttrArrayType: + if (ct == CborArrayType) { + return 1; + } + break; + case CborAttrObjectType: + if (ct == CborMapType) { + return 1; + } + break; + case CborAttrNullType: + if (ct == CborNullType) { + return 1; + } + break; + default: + break; + } + return 0; +} + +/* This function find the pointer to the memory location to + * write or read and attribute from the cbor_attr_r structure + */ +static char * +cbor_target_address(const struct cbor_attr_t *cursor, + const struct cbor_array_t *parent, int offset) +{ + char *targetaddr = NULL; + + if (parent == NULL || parent->element_type != CborAttrStructObjectType) { + /* ordinary case - use the address in the cursor structure */ + switch (cursor->type) { + case CborAttrNullType: + targetaddr = NULL; + break; + case CborAttrIntegerType: + targetaddr = (char *)&cursor->addr.integer[offset]; + break; + case CborAttrUnsignedIntegerType: + targetaddr = (char *)&cursor->addr.uinteger[offset]; + break; +#if CBORATTR_FLOAT_SUPPORT != 0 + case CborAttrHalfFloatType: + targetaddr = (char *)&cursor->addr.halffloat[offset]; + break; + case CborAttrFloatType: + targetaddr = (char *)&cursor->addr.fval[offset]; + break; + case CborAttrDoubleType: + targetaddr = (char *)&cursor->addr.real[offset]; + break; +#endif + case CborAttrByteStringType: + targetaddr = (char *) cursor->addr.bytestring.data; + break; + case CborAttrTextStringType: + targetaddr = cursor->addr.string; + break; + case CborAttrBooleanType: + targetaddr = (char *)&cursor->addr.boolean[offset]; + break; + default: + targetaddr = NULL; + break; + } + } else { + /* tricky case - hacking a member in an array of structures */ + targetaddr = + parent->arr.objects.base + (offset * parent->arr.objects.stride) + + cursor->addr.offset; + } + return targetaddr; +} + +static int +cbor_internal_read_object(CborValue *root_value, + const struct cbor_attr_t *attrs, + const struct cbor_array_t *parent, + int offset) +{ + const struct cbor_attr_t *cursor, *best_match; + char attrbuf[CBORATTR_MAX_SIZE + 1]; + void *lptr; + CborValue cur_value; + CborError err = 0; + size_t len = 0; + CborType type = CborInvalidType; + + /* stuff fields with defaults in case they're omitted in the JSON input */ + for (cursor = attrs; cursor->attribute != NULL; cursor++) { + if (!cursor->nodefault) { + lptr = cbor_target_address(cursor, parent, offset); + if (lptr != NULL) { + switch (cursor->type) { + case CborAttrIntegerType: + memcpy(lptr, &cursor->dflt.integer, sizeof(long long)); + break; + case CborAttrUnsignedIntegerType: + memcpy(lptr, &cursor->dflt.integer, + sizeof(unsigned long long)); + break; + case CborAttrBooleanType: + memcpy(lptr, &cursor->dflt.boolean, sizeof(bool)); + break; +#if CBORATTR_FLOAT_SUPPORT != 0 + case CborAttrHalfFloatType: + memcpy(lptr, &cursor->dflt.halffloat, sizeof(uint16_t)); + break; + case CborAttrFloatType: + memcpy(lptr, &cursor->dflt.fval, sizeof(float)); + break; + case CborAttrDoubleType: + memcpy(lptr, &cursor->dflt.real, sizeof(double)); + break; +#endif + default: + break; + } + } + } + } + + if (cbor_value_is_map(root_value)) { + err |= cbor_value_enter_container(root_value, &cur_value); + } else { + err |= CborErrorIllegalType; + return err; + } + + /* contains key value pairs */ + while (cbor_value_is_valid(&cur_value) && !err) { + /* get the attribute */ + if (cbor_value_is_text_string(&cur_value)) { + if (cbor_value_calculate_string_length(&cur_value, &len) == 0) { + if (len > CBORATTR_MAX_SIZE) { + err |= CborErrorDataTooLarge; + break; + } + err |= cbor_value_copy_text_string(&cur_value, attrbuf, &len, NULL); + } + + /* at least get the type of the next value so we can match the + * attribute name and type for a perfect match + */ + err |= cbor_value_advance(&cur_value); + if (cbor_value_is_valid(&cur_value)) { + type = cbor_value_get_type(&cur_value); + } else { + err |= CborErrorIllegalType; + break; + } + } else { + attrbuf[0] = '\0'; + type = cbor_value_get_type(&cur_value); + } + + /* find this attribute in our list */ + best_match = NULL; + for (cursor = attrs; cursor->attribute != NULL; cursor++) { + if (valid_attr_type(type, cursor->type)) { + if (cursor->attribute == CBORATTR_ATTR_UNNAMED && + attrbuf[0] == '\0') { + best_match = cursor; + } else if (strlen(cursor->attribute) == len && + !memcmp(cursor->attribute, attrbuf, len)) { + break; + } + } + } + if (!cursor->attribute && best_match) { + cursor = best_match; + } + /* we found a match */ + if (cursor->attribute != NULL) { + lptr = cbor_target_address(cursor, parent, offset); + switch (cursor->type) { + case CborAttrNullType: + /* nothing to do */ + break; + case CborAttrBooleanType: + err |= cbor_value_get_boolean(&cur_value, lptr); + break; + case CborAttrIntegerType: + err |= cbor_value_get_int64(&cur_value, lptr); + break; + case CborAttrUnsignedIntegerType: + err |= cbor_value_get_uint64(&cur_value, lptr); + break; +#if CBORATTR_FLOAT_SUPPORT != 0 + case CborAttrHalfFloatType: + err |= cbor_value_get_half_float(&cur_value, lptr); + break; + case CborAttrFloatType: + err |= cbor_value_get_float(&cur_value, lptr); + break; + case CborAttrDoubleType: + err |= cbor_value_get_double(&cur_value, lptr); + break; +#endif + case CborAttrByteStringType: { + size_t len = cursor->len; + + err |= cbor_value_copy_byte_string(&cur_value, lptr, &len, NULL); + *cursor->addr.bytestring.len = len; + break; + } + case CborAttrTextStringType: { + size_t len = cursor->len; + + err |= cbor_value_copy_text_string(&cur_value, lptr, &len, NULL); + break; + } + case CborAttrArrayType: + err |= cbor_read_array(&cur_value, &cursor->addr.array); + continue; + case CborAttrObjectType: + err |= cbor_internal_read_object(&cur_value, cursor->addr.obj, + NULL, 0); + continue; + default: + err |= CborErrorIllegalType; + } + } + err = cbor_value_advance(&cur_value); + } + if (!err) { + /* that should be it for this container */ + err |= cbor_value_leave_container(root_value, &cur_value); + } + return err; +} + +int +cbor_read_array(struct CborValue *value, const struct cbor_array_t *arr) +{ + CborError err = 0; + struct CborValue elem; + int off, arrcount; + size_t len; + void *lptr; + char *tp; + + err = cbor_value_enter_container(value, &elem); + if (err) { + return err; + } + arrcount = 0; + tp = arr->arr.strings.store; + for (off = 0; off < arr->maxlen; off++) { + switch (arr->element_type) { + case CborAttrBooleanType: + lptr = &arr->arr.booleans.store[off]; + err |= cbor_value_get_boolean(&elem, lptr); + break; + case CborAttrIntegerType: + lptr = &arr->arr.integers.store[off]; + err |= cbor_value_get_int64(&elem, lptr); + break; + case CborAttrUnsignedIntegerType: + lptr = &arr->arr.uintegers.store[off]; + err |= cbor_value_get_uint64(&elem, lptr); + break; +#if CBORATTR_FLOAT_SUPPORT != 0 + case CborAttrHalfFloatType: + lptr = &arr->arr.halffloats.store[off]; + err |= cbor_value_get_half_float(&elem, lptr); + break; + case CborAttrFloatType: + case CborAttrDoubleType: + lptr = &arr->arr.reals.store[off]; + err |= cbor_value_get_double(&elem, lptr); + break; +#endif + case CborAttrTextStringType: + len = arr->arr.strings.storelen - (tp - arr->arr.strings.store); + err |= cbor_value_copy_text_string(&elem, tp, &len, NULL); + arr->arr.strings.ptrs[off] = tp; + tp += len + 1; + break; + case CborAttrStructObjectType: + err |= cbor_internal_read_object(&elem, arr->arr.objects.subtype, arr, off); + break; + default: + err |= CborErrorIllegalType; + break; + } + arrcount++; + if (arr->element_type != CborAttrStructObjectType) { + err |= cbor_value_advance(&elem); + } + if (!cbor_value_is_valid(&elem)) { + break; + } + } + if (arr->count) { + *arr->count = arrcount; + } + while (!cbor_value_at_end(&elem)) { + err |= CborErrorDataTooLarge; + cbor_value_advance(&elem); + } + err |= cbor_value_leave_container(value, &elem); + return err; +} + +int +cbor_read_object(struct CborValue *value, const struct cbor_attr_t *attrs) +{ + int st; + + st = cbor_internal_read_object(value, attrs, NULL, 0); + return st; +} + +/* + * Read in cbor key/values from flat buffer pointed by data, and fill them + * into attrs. + * + * @param data Pointer to beginning of cbor encoded data + * @param len Number of bytes in the buffer + * @param attrs Array of cbor objects to look for. + * + * @return 0 on success; non-zero on failure. + */ +int +cbor_read_flat_attrs(const uint8_t *data, int len, + const struct cbor_attr_t *attrs) +{ + struct cbor_buf_reader reader; + struct CborParser parser; + struct CborValue value; + CborError err; + + cbor_buf_reader_init(&reader, data, len); + err = cbor_parser_init(&reader.r, 0, &parser, &value); + if (err != CborNoError) { + return -1; + } + return cbor_read_object(&value, attrs); +} diff --git a/subsys/mgmt/mcumgr/lib/cborattr/test/src/test_cborattr.c b/subsys/mgmt/mcumgr/lib/cborattr/test/src/test_cborattr.c new file mode 100644 index 000000000000..9f1bca9290a4 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/test/src/test_cborattr.c @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sysinit/sysinit.h" +#include "syscfg/syscfg.h" +#include "testutil/testutil.h" +#include "test_cborattr.h" + +TEST_SUITE(test_cborattr_suite) +{ + test_cborattr_decode1(); + test_cborattr_decode_partial(); + test_cborattr_decode_simple(); + test_cborattr_decode_object(); + test_cborattr_decode_int_array(); + test_cborattr_decode_bool_array(); + test_cborattr_decode_string_array(); + test_cborattr_decode_object_array(); + test_cborattr_decode_unnamed_array(); + test_cborattr_decode_substring_key(); +} diff --git a/subsys/mgmt/mcumgr/lib/cborattr/test/src/test_cborattr.h b/subsys/mgmt/mcumgr/lib/cborattr/test/src/test_cborattr.h new file mode 100644 index 000000000000..27935cfa383a --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/test/src/test_cborattr.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef TEST_CBORATTR_H +#define TEST_CBORATTR_H + +#include +#include +#include "testutil/testutil.h" +#include "test_cborattr.h" +#include "tinycbor/cbor.h" +#include "cborattr/cborattr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Returns test data. + */ +const uint8_t *test_str1(int *len); + +void cborattr_test_util_encode(const struct cbor_out_attr_t *attrs, const uint8_t *expected, + int len); +/* + * Testcases + */ +TEST_CASE_DECL(test_cborattr_decode1); +TEST_CASE_DECL(test_cborattr_decode_partial); +TEST_CASE_DECL(test_cborattr_decode_simple); +TEST_CASE_DECL(test_cborattr_decode_object); +TEST_CASE_DECL(test_cborattr_decode_int_array); +TEST_CASE_DECL(test_cborattr_decode_bool_array); +TEST_CASE_DECL(test_cborattr_decode_string_array); +TEST_CASE_DECL(test_cborattr_decode_object_array); +TEST_CASE_DECL(test_cborattr_decode_unnamed_array); +TEST_CASE_DECL(test_cborattr_decode_substring_key); + +#ifdef __cplusplus +} +#endif + +#endif /* TEST_CBORATTR_H */ diff --git a/subsys/mgmt/mcumgr/lib/cborattr/test/src/test_cborattr_utils.c b/subsys/mgmt/mcumgr/lib/cborattr/test/src/test_cborattr_utils.c new file mode 100644 index 000000000000..6cb5d2872969 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/test/src/test_cborattr_utils.c @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_cborattr.h" +/* + * {"a": "A", "b": "B", "c": "C", "d": "D", "e": "E"} + */ +static const uint8_t test_data1[] = { + 0xa5, 0x61, 0x61, 0x61, 0x41, 0x61, 0x62, 0x61, + 0x42, 0x61, 0x63, 0x61, 0x43, 0x61, 0x64, 0x61, + 0x44, 0x61, 0x65, 0x61, 0x45 +}; + +const uint8_t * +test_str1(int *len) +{ + *len = sizeof(test_data1); + return test_data1; +} + +void +cborattr_test_util_encode(const struct cbor_out_attr_t *attrs, const uint8_t *expected, int len) +{ + struct os_mbuf *om; + int rc; + + rc = cbor_write_object_msys(attrs, &om); + TEST_ASSERT_FATAL(rc == 0); + + TEST_ASSERT(os_mbuf_cmpf(om, 0, expected, len) == 0); + + os_mbuf_free_chain(om); +} diff --git a/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode1.c b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode1.c new file mode 100644 index 000000000000..138a56fd8c62 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode1.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_cborattr.h" + +/* + * Simple decoding. + */ +TEST_CASE(test_cborattr_decode1) +{ + const uint8_t *data; + int len; + int rc; + char test_str_a[4] = { '\0' }; + char test_str_b[4] = { '\0' }; + char test_str_c[4] = { '\0' }; + char test_str_d[4] = { '\0' }; + char test_str_e[4] = { '\0' }; + struct cbor_attr_t test_attrs[] = { + [0] = { + .attribute = "a", + .type = CborAttrTextStringType, + .addr.string = test_str_a, + .len = sizeof(test_str_a), + .nodefault = true + }, + [1] = { + .attribute = "b", + .type = CborAttrTextStringType, + .addr.string = test_str_b, + .len = sizeof(test_str_b), + .nodefault = true + }, + [2] = { + .attribute = "c", + .type = CborAttrTextStringType, + .addr.string = test_str_c, + .len = sizeof(test_str_c), + .nodefault = true + }, + [3] = { + .attribute = "d", + .type = CborAttrTextStringType, + .addr.string = test_str_d, + .len = sizeof(test_str_d), + .nodefault = true, + }, + [4] = { + .attribute = "e", + .type = CborAttrTextStringType, + .addr.string = test_str_e, + .len = sizeof(test_str_e), + .nodefault = true, + }, + [5] = { + .attribute = NULL + } + }; + + data = test_str1(&len); + rc = cbor_read_flat_attrs(data, len, test_attrs); + TEST_ASSERT(rc == 0); + TEST_ASSERT(!strcmp(test_str_a, "A")); + TEST_ASSERT(!strcmp(test_str_b, "B")); + TEST_ASSERT(!strcmp(test_str_c, "C")); + TEST_ASSERT(!strcmp(test_str_d, "D")); + TEST_ASSERT(!strcmp(test_str_e, "E")); +} diff --git a/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_bool_array.c b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_bool_array.c new file mode 100644 index 000000000000..0bc109087770 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_bool_array.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_cborattr.h" + +/* + * Where we collect cbor data. + */ +static uint8_t test_cbor_buf[1024]; +static int test_cbor_len; + +/* + * CBOR encoder data structures. + */ +static int test_cbor_wr(struct cbor_encoder_writer *, const char *, int); +static CborEncoder test_encoder; +static struct cbor_encoder_writer test_writer = { + .write = test_cbor_wr +}; + +static int +test_cbor_wr(struct cbor_encoder_writer *cew, const char *data, int len) +{ + memcpy(test_cbor_buf + test_cbor_len, data, len); + test_cbor_len += len; + + assert(test_cbor_len < sizeof(test_cbor_buf)); + return 0; +} + +static void +test_encode_bool_array(void) +{ + CborEncoder data; + CborEncoder array; + + cbor_encoder_init(&test_encoder, &test_writer, 0); + + cbor_encoder_create_map(&test_encoder, &data, CborIndefiniteLength); + + /* + * a: [true,true,false] + */ + cbor_encode_text_stringz(&data, "a"); + + cbor_encoder_create_array(&data, &array, CborIndefiniteLength); + cbor_encode_boolean(&array, true); + cbor_encode_boolean(&array, true); + cbor_encode_boolean(&array, false); + cbor_encoder_close_container(&data, &array); + + cbor_encoder_close_container(&test_encoder, &data); +} + +/* + * array of booleans + */ +TEST_CASE(test_cborattr_decode_bool_array) +{ + int rc; + bool arr_data[5]; + int arr_cnt = 0; + struct cbor_attr_t test_attrs[] = { + [0] = { + .attribute = "a", + .type = CborAttrArrayType, + .addr.array.element_type = CborAttrBooleanType, + .addr.array.arr.booleans.store = arr_data, + .addr.array.count = &arr_cnt, + .addr.array.maxlen = ARRAY_SIZE(arr_data), + .nodefault = true + }, + [1] = { + .attribute = NULL + } + }; + + test_encode_bool_array(); + + rc = cbor_read_flat_attrs(test_cbor_buf, test_cbor_len, test_attrs); + TEST_ASSERT(rc == 0); + TEST_ASSERT(arr_cnt == 3); + TEST_ASSERT(arr_data[0] == true); + TEST_ASSERT(arr_data[1] == true); + TEST_ASSERT(arr_data[2] == false); +} diff --git a/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_int_array.c b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_int_array.c new file mode 100644 index 000000000000..cfc0cd8d8dd6 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_int_array.c @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_cborattr.h" + +/* + * Where we collect cbor data. + */ +static uint8_t test_cbor_buf[1024]; +static int test_cbor_len; + +/* + * CBOR encoder data structures. + */ +static int test_cbor_wr(struct cbor_encoder_writer *, const char *, int); +static CborEncoder test_encoder; +static struct cbor_encoder_writer test_writer = { + .write = test_cbor_wr +}; + +static int +test_cbor_wr(struct cbor_encoder_writer *cew, const char *data, int len) +{ + memcpy(test_cbor_buf + test_cbor_len, data, len); + test_cbor_len += len; + + assert(test_cbor_len < sizeof(test_cbor_buf)); + return 0; +} + +static void +test_encode_int_array(void) +{ + CborEncoder data; + CborEncoder array; + + cbor_encoder_init(&test_encoder, &test_writer, 0); + + cbor_encoder_create_map(&test_encoder, &data, CborIndefiniteLength); + + /* + * a: [1,2,33,15,-4] + */ + cbor_encode_text_stringz(&data, "a"); + + cbor_encoder_create_array(&data, &array, CborIndefiniteLength); + cbor_encode_int(&array, 1); + cbor_encode_int(&array, 2); + cbor_encode_int(&array, 33); + cbor_encode_int(&array, 15); + cbor_encode_int(&array, -4); + cbor_encoder_close_container(&data, &array); + + cbor_encoder_close_container(&test_encoder, &data); +} + +/* + * integer array + */ +TEST_CASE(test_cborattr_decode_int_array) +{ + int rc; + int64_t arr_data[5]; + int64_t b_int; + int arr_cnt = 0; + struct cbor_attr_t test_attrs[] = { + [0] = { + .attribute = "a", + .type = CborAttrArrayType, + .addr.array.element_type = CborAttrIntegerType, + .addr.array.arr.integers.store = arr_data, + .addr.array.count = &arr_cnt, + .addr.array.maxlen = ARRAY_SIZE(arr_data), + .nodefault = true + }, + [1] = { + .attribute = "b", + .type = CborAttrIntegerType, + .addr.integer = &b_int, + .dflt.integer = 1 + }, + [2] = { + .attribute = NULL + } + }; + struct cbor_attr_t test_attrs_small[] = { + [0] = { + .attribute = "a", + .type = CborAttrArrayType, + .addr.array.element_type = CborAttrIntegerType, + .addr.array.arr.integers.store = arr_data, + .addr.array.count = &arr_cnt, + .addr.array.maxlen = 1, + .nodefault = true + }, + [1] = { + .attribute = "b", + .type = CborAttrIntegerType, + .addr.integer = &b_int, + .dflt.integer = 1 + }, + [2] = { + .attribute = NULL + } + }; + + test_encode_int_array(); + + rc = cbor_read_flat_attrs(test_cbor_buf, test_cbor_len, test_attrs); + TEST_ASSERT(rc == 0); + TEST_ASSERT(arr_cnt == 5); + TEST_ASSERT(arr_data[0] == 1); + TEST_ASSERT(arr_data[1] == 2); + TEST_ASSERT(arr_data[2] == 33); + TEST_ASSERT(arr_data[3] == 15); + TEST_ASSERT(arr_data[4] == -4); + TEST_ASSERT(b_int == 1); + + memset(arr_data, 0, sizeof(arr_data)); + b_int = 0; + + rc = cbor_read_flat_attrs(test_cbor_buf, test_cbor_len, test_attrs_small); + TEST_ASSERT(rc == CborErrorDataTooLarge); + TEST_ASSERT(arr_cnt == 1); + TEST_ASSERT(arr_data[0] == 1); + TEST_ASSERT(arr_data[1] == 0); + TEST_ASSERT(b_int == 1); +} diff --git a/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_obj_array.c b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_obj_array.c new file mode 100644 index 000000000000..b99c044cbbb0 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_obj_array.c @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_cborattr.h" + +/* + * Where we collect cbor data. + */ +static uint8_t test_cbor_buf[1024]; +static int test_cbor_len; + +/* + * CBOR encoder data structures. + */ +static int test_cbor_wr(struct cbor_encoder_writer *, const char *, int); +static CborEncoder test_encoder; +static struct cbor_encoder_writer test_writer = { + .write = test_cbor_wr +}; + +static int +test_cbor_wr(struct cbor_encoder_writer *cew, const char *data, int len) +{ + memcpy(test_cbor_buf + test_cbor_len, data, len); + test_cbor_len += len; + + assert(test_cbor_len < sizeof(test_cbor_buf)); + return 0; +} + +static void +test_encode_obj_array(void) +{ + CborEncoder data; + CborEncoder array; + CborEncoder obj; + + cbor_encoder_init(&test_encoder, &test_writer, 0); + + cbor_encoder_create_map(&test_encoder, &data, CborIndefiniteLength); + + /* + * a: [{ n:"a", v:1}, {n:"b", v:2} ] + */ + cbor_encode_text_stringz(&data, "a"); + cbor_encoder_create_array(&data, &array, CborIndefiniteLength); + + cbor_encoder_create_map(&array, &obj, CborIndefiniteLength); + cbor_encode_text_stringz(&obj, "n"); + cbor_encode_text_stringz(&obj, "a"); + cbor_encode_text_stringz(&obj, "v"); + cbor_encode_int(&obj, 1); + cbor_encoder_close_container(&array, &obj); + + cbor_encoder_create_map(&array, &obj, CborIndefiniteLength); + cbor_encode_text_stringz(&obj, "n"); + cbor_encode_text_stringz(&obj, "b"); + cbor_encode_text_stringz(&obj, "v"); + cbor_encode_int(&obj, 2); + cbor_encoder_close_container(&array, &obj); + + cbor_encoder_close_container(&data, &array); + cbor_encoder_close_container(&test_encoder, &data); +} + +/* + * object array + */ +TEST_CASE(test_cborattr_decode_obj_array) +{ + int rc; + char arr_data[4]; + int arr_cnt; + struct cbor_attr_t test_attrs[] = { + [0] = { + .attribute = "a", + .type = CborAttrArrayType, + .addr.array.element_type = CborAttrNullType, + .addr.array.arr.objects.base = arr_data, + .addr.array.count = &arr_cnt, + .addr.array.maxlen = 4, + .nodefault = true + }, + [1] = { + .attribute = NULL + } + }; + + test_encode_obj_array(); + + rc = cbor_read_flat_attrs(test_cbor_buf, test_cbor_len, test_attrs); + TEST_ASSERT(rc == 0); + TEST_ASSERT(arr_cnt == 2); +} diff --git a/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_object.c b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_object.c new file mode 100644 index 000000000000..53c7d0027b92 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_object.c @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_cborattr.h" + +/* + * Where we collect cbor data. + */ +static uint8_t test_cbor_buf[1024]; +static int test_cbor_len; + +/* + * CBOR encoder data structures. + */ +static int test_cbor_wr(struct cbor_encoder_writer *, const char *, int); +static CborEncoder test_encoder; +static struct cbor_encoder_writer test_writer = { + .write = test_cbor_wr +}; + +static int +test_cbor_wr(struct cbor_encoder_writer *cew, const char *data, int len) +{ + memcpy(test_cbor_buf + test_cbor_len, data, len); + test_cbor_len += len; + + assert(test_cbor_len < sizeof(test_cbor_buf)); + return 0; +} + +static void +test_encode_data(void) +{ + CborEncoder test_data; + CborEncoder sub_obj; + + test_cbor_len = 0; + cbor_encoder_init(&test_encoder, &test_writer, 0); + cbor_encoder_create_map(&test_encoder, &test_data, CborIndefiniteLength); + + /* + * p: { bm : 7 } + */ + cbor_encode_text_stringz(&test_data, "p"); + + cbor_encoder_create_map(&test_data, &sub_obj, CborIndefiniteLength); + cbor_encode_text_stringz(&test_data, "bm"); + cbor_encode_int(&test_data, 7); + cbor_encoder_close_container(&test_data, &sub_obj); + + cbor_encoder_close_container(&test_encoder, &test_data); +} + +static void +test_encode_data_complex(void) +{ + CborEncoder test_data; + CborEncoder sub_obj; + CborEncoder sub_sub_obj; + + test_cbor_len = 0; + + cbor_encoder_init(&test_encoder, &test_writer, 0); + cbor_encoder_create_map(&test_encoder, &test_data, CborIndefiniteLength); + + /* + * p: { bm : 7 }, c: { d : { i : 1 } }, a: 3 + */ + cbor_encode_text_stringz(&test_data, "p"); + + cbor_encoder_create_map(&test_data, &sub_obj, CborIndefiniteLength); + cbor_encode_text_stringz(&test_data, "bm"); + cbor_encode_int(&test_data, 7); + cbor_encoder_close_container(&test_data, &sub_obj); + + cbor_encode_text_stringz(&test_data, "c"); + cbor_encoder_create_map(&test_data, &sub_obj, CborIndefiniteLength); + cbor_encode_text_stringz(&sub_obj, "d"); + + cbor_encoder_create_map(&sub_obj, &sub_sub_obj, CborIndefiniteLength); + cbor_encode_text_stringz(&sub_sub_obj, "i"); + cbor_encode_int(&sub_sub_obj, 1); + cbor_encoder_close_container(&sub_obj, &sub_sub_obj); + cbor_encoder_close_container(&test_data, &sub_obj); + + cbor_encode_text_stringz(&test_data, "a"); + cbor_encode_int(&test_data, 3); + + cbor_encoder_close_container(&test_encoder, &test_data); +} + +/* + * Simple decoding. + */ +TEST_CASE(test_cborattr_decode_object) +{ + int rc; + int64_t bm_val = 0; + struct cbor_attr_t test_sub_attr_bm[] = { + [0] = { + .attribute = "bm", + .type = CborAttrIntegerType, + .addr.integer = &bm_val, + .nodefault = true + }, + [1] = { + .attribute = NULL + } + }; + struct cbor_attr_t test_attrs[] = { + [0] = { + .attribute = "p", + .type = CborAttrObjectType, + .addr.obj = test_sub_attr_bm, + .nodefault = true + }, + [1] = { + .attribute = NULL + } + }; + int64_t a_val = 0; + int64_t i_val = 0; + struct cbor_attr_t test_sub_sub_attr[] = { + [0] = { + .attribute = "i", + .type = CborAttrIntegerType, + .addr.integer = &i_val, + .nodefault = true + }, + [1] = { + .attribute = NULL + } + }; + struct cbor_attr_t test_sub_attr_d[] = { + [0] = { + .attribute = "d", + .type = CborAttrObjectType, + .addr.obj = test_sub_sub_attr, + .nodefault = true + }, + [1] = { + .attribute = NULL + } + }; + struct cbor_attr_t test_attr_complex[] = { + [0] = { + .attribute = "c", + .type = CborAttrObjectType, + .addr.obj = test_sub_attr_d, + .nodefault = true + }, + [1] = { + .attribute = "a", + .type = CborAttrIntegerType, + .addr.integer = &a_val, + .nodefault = true + }, + [2] = { + .attribute = "p", + .type = CborAttrObjectType, + .addr.obj = test_sub_attr_bm, + .nodefault = true + }, + [3] = { + .attribute = NULL + } + }; + + test_encode_data(); + + rc = cbor_read_flat_attrs(test_cbor_buf, test_cbor_len, test_attrs); + TEST_ASSERT(rc == 0); + TEST_ASSERT(bm_val == 7); + + test_encode_data_complex(); + + bm_val = 0; + i_val = 0; + rc = cbor_read_flat_attrs(test_cbor_buf, test_cbor_len, test_attr_complex); + TEST_ASSERT(rc == 0); + TEST_ASSERT(bm_val == 7); + TEST_ASSERT(i_val == 1); +} diff --git a/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_object_array.c b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_object_array.c new file mode 100644 index 000000000000..74dcccf14170 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_object_array.c @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_cborattr.h" + +/* + * Where we collect cbor data. + */ +static uint8_t test_cbor_buf[1024]; +static int test_cbor_len; + +/* + * CBOR encoder data structures. + */ +static int test_cbor_wr(struct cbor_encoder_writer *, const char *, int); +static CborEncoder test_encoder; +static struct cbor_encoder_writer test_writer = { + .write = test_cbor_wr +}; + +static int +test_cbor_wr(struct cbor_encoder_writer *cew, const char *data, int len) +{ + memcpy(test_cbor_buf + test_cbor_len, data, len); + test_cbor_len += len; + + assert(test_cbor_len < sizeof(test_cbor_buf)); + return 0; +} + +static void +test_encode_object_array(void) +{ + CborEncoder data; + CborEncoder array; + CborEncoder sub_obj; + + test_cbor_len = 0; + cbor_encoder_init(&test_encoder, &test_writer, 0); + + cbor_encoder_create_map(&test_encoder, &data, CborIndefiniteLength); + + /* + * a: [ { h:"str1"}, {h:"2str"}, {h:"str3"}] + */ + cbor_encode_text_stringz(&data, "a"); + + cbor_encoder_create_array(&data, &array, CborIndefiniteLength); + + cbor_encoder_create_map(&array, &sub_obj, CborIndefiniteLength); + cbor_encode_text_stringz(&sub_obj, "h"); + cbor_encode_text_stringz(&sub_obj, "str1"); + cbor_encoder_close_container(&array, &sub_obj); + + cbor_encoder_create_map(&array, &sub_obj, CborIndefiniteLength); + cbor_encode_text_stringz(&sub_obj, "h"); + cbor_encode_text_stringz(&sub_obj, "2str"); + cbor_encoder_close_container(&array, &sub_obj); + + cbor_encoder_create_map(&array, &sub_obj, CborIndefiniteLength); + cbor_encode_text_stringz(&sub_obj, "h"); + cbor_encode_text_stringz(&sub_obj, "str3"); + cbor_encoder_close_container(&array, &sub_obj); + + cbor_encoder_close_container(&data, &array); + + cbor_encoder_close_container(&test_encoder, &data); +} + +/* + * object array + */ +TEST_CASE(test_cborattr_decode_object_array) +{ + int rc; + struct h_obj { + char h_data[32]; + } arr_objs[5]; + int arr_cnt = 0; + struct cbor_attr_t sub_attr[] = { + [0] = { + .attribute = "h", + .type = CborAttrTextStringType, + CBORATTR_STRUCT_OBJECT(struct h_obj, h_data), + .len = sizeof(arr_objs[0].h_data) + }, + [1] = { + .attribute = NULL + } + }; + struct cbor_attr_t test_attrs[] = { + [0] = { + .attribute = "a", + .type = CborAttrArrayType, + CBORATTR_STRUCT_ARRAY(arr_objs, sub_attr, &arr_cnt), + .nodefault = true + }, + [1] = { + .attribute = NULL + } + }; + + test_encode_object_array(); + + rc = cbor_read_flat_attrs(test_cbor_buf, test_cbor_len, test_attrs); + TEST_ASSERT(rc == 0); + TEST_ASSERT(arr_cnt == 3); + TEST_ASSERT(!strcmp(arr_objs[0].h_data, "str1")); + TEST_ASSERT(!strcmp(arr_objs[1].h_data, "2str")); + TEST_ASSERT(!strcmp(arr_objs[2].h_data, "str3")); +} diff --git a/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_partial.c b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_partial.c new file mode 100644 index 000000000000..824ff326d61b --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_partial.c @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_cborattr.h" + +/* + * Simple decoding. Only have key for one of the key/value pairs. + */ +TEST_CASE(test_cborattr_decode_partial) +{ + const uint8_t *data; + int len; + int rc; + char test_str_b[4] = { '\0' }; + struct cbor_attr_t test_attrs[] = { + [0] = { + .attribute = "b", + .type = CborAttrTextStringType, + .addr.string = test_str_b, + .len = sizeof(test_str_b), + .nodefault = true + }, + [1] = { + .attribute = NULL + } + }; + + data = test_str1(&len); + rc = cbor_read_flat_attrs(data, len, test_attrs); + TEST_ASSERT(rc == 0); + TEST_ASSERT(!strcmp(test_str_b, "B")); +} diff --git a/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_simple.c b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_simple.c new file mode 100644 index 000000000000..bc0be2f5f76c --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_simple.c @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_cborattr.h" + +/* + * Where we collect cbor data. + */ +static uint8_t test_cbor_buf[1024]; +static int test_cbor_len; + +/* + * CBOR encoder data structures. + */ +static int test_cbor_wr(struct cbor_encoder_writer *, const char *, int); +static CborEncoder test_encoder; +static struct cbor_encoder_writer test_writer = { + .write = test_cbor_wr +}; + +static int +test_cbor_wr(struct cbor_encoder_writer *cew, const char *data, int len) +{ + memcpy(test_cbor_buf + test_cbor_len, data, len); + test_cbor_len += len; + + assert(test_cbor_len < sizeof(test_cbor_buf)); + return 0; +} + +static void +test_encode_data(void) +{ + CborEncoder test_data; + uint8_t data[4] = { 0, 1, 2 }; + + cbor_encoder_init(&test_encoder, &test_writer, 0); + + cbor_encoder_create_map(&test_encoder, &test_data, CborIndefiniteLength); + /* + * a:22 + */ + cbor_encode_text_stringz(&test_data, "a"); + cbor_encode_uint(&test_data, 22); + + /* + * b:-13 + */ + cbor_encode_text_stringz(&test_data, "b"); + cbor_encode_int(&test_data, -13); + + /* + * c:0x000102 + */ + cbor_encode_text_stringz(&test_data, "c"); + cbor_encode_byte_string(&test_data, data, 3); + cbor_encoder_close_container(&test_encoder, &test_data); + + /* + * XXX add other data types to encode here. + */ + +} + +/* + * Simple decoding. + */ +TEST_CASE(test_cborattr_decode_simple) +{ + int rc; + uint64_t a_val = 0; + int64_t b_val = 0; + uint8_t c_data[4]; + size_t c_len; + struct cbor_attr_t test_attrs[] = { + [0] = { + .attribute = "a", + .type = CborAttrIntegerType, + .addr.uinteger = &a_val, + .nodefault = true + }, + [1] = { + .attribute = "b", + .type = CborAttrIntegerType, + .addr.integer = &b_val, + .nodefault = true + }, + [2] = { + .attribute = "c", + .type = CborAttrByteStringType, + .addr.bytestring.data = c_data, + .addr.bytestring.len = &c_len, + .len = sizeof(c_data) + }, + [3] = { + .attribute = NULL + } + }; + + test_encode_data(); + + rc = cbor_read_flat_attrs(test_cbor_buf, test_cbor_len, test_attrs); + TEST_ASSERT(rc == 0); + TEST_ASSERT(a_val == 22); + TEST_ASSERT(b_val == -13); + TEST_ASSERT(c_len == 3); + TEST_ASSERT(c_data[0] == 0 && c_data[1] == 1 && c_data[2] == 2); +} diff --git a/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_string_array.c b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_string_array.c new file mode 100644 index 000000000000..b60c2979c3a7 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_string_array.c @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_cborattr.h" + +/* + * Where we collect cbor data. + */ +static uint8_t test_cbor_buf[1024]; +static int test_cbor_len; + +/* + * CBOR encoder data structures. + */ +static int test_cbor_wr(struct cbor_encoder_writer *, const char *, int); +static CborEncoder test_encoder; +static struct cbor_encoder_writer test_writer = { + .write = test_cbor_wr +}; + +static int +test_cbor_wr(struct cbor_encoder_writer *cew, const char *data, int len) +{ + memcpy(test_cbor_buf + test_cbor_len, data, len); + test_cbor_len += len; + + assert(test_cbor_len < sizeof(test_cbor_buf)); + return 0; +} + +static void +test_encode_string_array_one(void) +{ + CborEncoder data; + CborEncoder array; + + test_cbor_len = 0; + cbor_encoder_init(&test_encoder, &test_writer, 0); + + cbor_encoder_create_map(&test_encoder, &data, CborIndefiniteLength); + + /* + * a: ["asdf"] + */ + cbor_encode_text_stringz(&data, "a"); + + cbor_encoder_create_array(&data, &array, CborIndefiniteLength); + cbor_encode_text_stringz(&array, "asdf"); + cbor_encoder_close_container(&data, &array); + + cbor_encoder_close_container(&test_encoder, &data); +} + +static void +test_encode_string_array_three(void) +{ + CborEncoder data; + CborEncoder array; + + test_cbor_len = 0; + cbor_encoder_init(&test_encoder, &test_writer, 0); + + cbor_encoder_create_map(&test_encoder, &data, CborIndefiniteLength); + + /* + * a: ["asdf", "k", "blurb"] + */ + cbor_encode_text_stringz(&data, "a"); + + cbor_encoder_create_array(&data, &array, CborIndefiniteLength); + cbor_encode_text_stringz(&array, "asdf"); + cbor_encode_text_stringz(&array, "k"); + cbor_encode_text_stringz(&array, "blurb"); + cbor_encoder_close_container(&data, &array); + + cbor_encoder_close_container(&test_encoder, &data); +} + +/* + * string array + */ +TEST_CASE(test_cborattr_decode_string_array) +{ + int rc; + char *str_ptrs[5]; + char arr_data[256]; + int arr_cnt = 0; + struct cbor_attr_t test_attrs[] = { + [0] = { + .attribute = "a", + .type = CborAttrArrayType, + .addr.array.element_type = CborAttrTextStringType, + .addr.array.arr.strings.ptrs = str_ptrs, + .addr.array.arr.strings.store = arr_data, + .addr.array.arr.strings.storelen = sizeof(arr_data), + .addr.array.count = &arr_cnt, + .addr.array.maxlen = ARRAY_SIZE(arr_data), + .nodefault = true + }, + [1] = { + .attribute = NULL + } + }; + + test_encode_string_array_one(); + + rc = cbor_read_flat_attrs(test_cbor_buf, test_cbor_len, test_attrs); + TEST_ASSERT(rc == 0); + TEST_ASSERT(arr_cnt == 1); + TEST_ASSERT(!strcmp(str_ptrs[0], "asdf")); + + test_encode_string_array_three(); + + rc = cbor_read_flat_attrs(test_cbor_buf, test_cbor_len, test_attrs); + TEST_ASSERT(rc == 0); + TEST_ASSERT(arr_cnt == 3); + TEST_ASSERT(!strcmp(str_ptrs[0], "asdf")); + TEST_ASSERT(!strcmp(str_ptrs[1], "k")); + TEST_ASSERT(!strcmp(str_ptrs[2], "blurb")); +} diff --git a/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_substring_key.c b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_substring_key.c new file mode 100644 index 000000000000..c2744e77beea --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_substring_key.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_cborattr.h" + +/* + * Where we collect cbor data. + */ +static uint8_t test_cbor_buf[1024]; +static int test_cbor_len; + +/* + * CBOR encoder data structures. + */ +static int test_cbor_wr(struct cbor_encoder_writer *, const char *, int); +static CborEncoder test_encoder; +static struct cbor_encoder_writer test_writer = { + .write = test_cbor_wr +}; + +static int +test_cbor_wr(struct cbor_encoder_writer *cew, const char *data, int len) +{ + memcpy(test_cbor_buf + test_cbor_len, data, len); + test_cbor_len += len; + + assert(test_cbor_len < sizeof(test_cbor_buf)); + return 0; +} + +static void +test_encode_substring_key(void) +{ + CborEncoder data; + + cbor_encoder_init(&test_encoder, &test_writer, 0); + + /* + * { "a": "A", "aa": "AA", "aaa" : "AAA" } + */ + cbor_encoder_create_map(&test_encoder, &data, CborIndefiniteLength); + + cbor_encode_text_stringz(&data, "a"); + cbor_encode_text_stringz(&data, "A"); + cbor_encode_text_stringz(&data, "aa"); + cbor_encode_text_stringz(&data, "AA"); + cbor_encode_text_stringz(&data, "aaa"); + cbor_encode_text_stringz(&data, "AAA"); + + cbor_encoder_close_container(&test_encoder, &data); +} + +/* + * substring key + */ +TEST_CASE(test_cborattr_decode_substring_key) +{ + int rc; + char test_str_1a[4] = { '\0' }; + char test_str_2a[4] = { '\0' }; + char test_str_3a[4] = { '\0' }; + struct cbor_attr_t test_attrs[] = { + [0] = { + .attribute = "aaa", + .type = CborAttrTextStringType, + .addr.string = test_str_3a, + .len = sizeof(test_str_3a), + .nodefault = true + }, + [1] = { + .attribute = "aa", + .type = CborAttrTextStringType, + .addr.string = test_str_2a, + .len = sizeof(test_str_2a), + .nodefault = true + }, + [2] = { + .attribute = "a", + .type = CborAttrTextStringType, + .addr.string = test_str_1a, + .len = sizeof(test_str_1a), + .nodefault = true + }, + [3] = { + .attribute = NULL + } + }; + + test_encode_substring_key(); + + rc = cbor_read_flat_attrs(test_cbor_buf, test_cbor_len, test_attrs); + TEST_ASSERT(rc == 0); + TEST_ASSERT(!strcmp(test_str_1a, "A")); + TEST_ASSERT(!strcmp(test_str_2a, "AA")); + TEST_ASSERT(!strcmp(test_str_3a, "AAA")); +} diff --git a/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_unnamed_array.c b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_unnamed_array.c new file mode 100644 index 000000000000..3aff45a9c0db --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_decode_unnamed_array.c @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_cborattr.h" + +/* + * Where we collect cbor data. + */ +static uint8_t test_cbor_buf[1024]; +static int test_cbor_len; + +/* + * CBOR encoder data structures. + */ +static int test_cbor_wr(struct cbor_encoder_writer *, const char *, int); +static CborEncoder test_encoder; +static struct cbor_encoder_writer test_writer = { + .write = test_cbor_wr +}; + +static int +test_cbor_wr(struct cbor_encoder_writer *cew, const char *data, int len) +{ + memcpy(test_cbor_buf + test_cbor_len, data, len); + test_cbor_len += len; + + assert(test_cbor_len < sizeof(test_cbor_buf)); + return 0; +} + +static void +test_encode_unnamed_array(void) +{ + CborEncoder data; + CborEncoder array; + + cbor_encoder_init(&test_encoder, &test_writer, 0); + + cbor_encoder_create_map(&test_encoder, &data, CborIndefiniteLength); + + /* + * [1,2,33] + */ + cbor_encoder_create_array(&data, &array, CborIndefiniteLength); + cbor_encode_int(&array, 1); + cbor_encode_int(&array, 2); + cbor_encode_int(&array, 33); + cbor_encoder_close_container(&data, &array); + + cbor_encoder_close_container(&test_encoder, &data); +} + +/* + * integer array + */ +TEST_CASE(test_cborattr_decode_unnamed_array) +{ + int rc; + int64_t arr_data[5]; + int arr_cnt = 0; + struct cbor_attr_t test_attrs[] = { + [0] = { + .attribute = CBORATTR_ATTR_UNNAMED, + .type = CborAttrArrayType, + .addr.array.element_type = CborAttrIntegerType, + .addr.array.arr.integers.store = arr_data, + .addr.array.count = &arr_cnt, + .addr.array.maxlen = ARRAY_SIZ(arr_data), + .nodefault = true + }, + [1] = { + .attribute = NULL + } + }; + + test_encode_unnamed_array(); + + rc = cbor_read_flat_attrs(test_cbor_buf, test_cbor_len, test_attrs); + TEST_ASSERT(rc == 0); + TEST_ASSERT(arr_cnt == 3); + TEST_ASSERT(arr_data[0] == 1); + TEST_ASSERT(arr_data[1] == 2); + TEST_ASSERT(arr_data[2] == 33); +} diff --git a/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_encode_omit.c b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_encode_omit.c new file mode 100644 index 000000000000..e7735bc7cb3f --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_encode_omit.c @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_cborattr.h" + +TEST_CASE_SELF(test_cborattr_encode_omit) +{ + /* Omit everything except the "str" value. */ + const struct cbor_out_attr_t attrs[11] = { + { + .attribute = "null", + .val = { + .type = CborAttrNullType, + }, + .omit = true, + }, + { + .attribute = "bool", + .val = { + .type = CborAttrBooleanType, + .boolean = true, + }, + .omit = true, + }, + { + .attribute = "int", + .val = { + .type = CborAttrIntegerType, + .integer = -99, + }, + .omit = true, + }, + { + .attribute = "uint", + .val = { + .type = CborAttrUnsignedIntegerType, + .integer = 8442, + }, + .omit = true, + }, + { + .attribute = "float", + .val = { + .type = CborAttrFloatType, + .fval = 8.0, + }, + .omit = true, + }, + { + .attribute = "double", + .val = { + .type = CborAttrDoubleType, + .real = 16.0, + }, + .omit = true, + }, + { + .attribute = "bytes", + .val = { + .type = CborAttrByteStringType, + .bytestring.data = (uint8_t[]) {1, 2, 3}, + .bytestring.len = 3, + }, + .omit = true, + }, + { + .attribute = "str", + .val = { + .type = CborAttrTextStringType, + .string = "mystr", + }, + }, + { + .attribute = "arr", + .val = { + .type = CborAttrArrayType, + .array = { + .elems = (struct cbor_out_val_t[]) { + [0] = { + .type = CborAttrUnsignedIntegerType, + .integer = 4355, + }, + [1] = { + .type = CborAttrBooleanType, + .integer = false, + }, + }, + .len = 2, + }, + }, + .omit = true, + }, + { + .attribute = "obj", + .val = { + .type = CborAttrObjectType, + .obj = (struct cbor_out_attr_t[]) { + { + .attribute = "inner_str", + .val = { + .type = CborAttrTextStringType, + .string = "mystr2", + }, + }, + { + .attribute = "inner_int", + .val = { + .type = CborAttrIntegerType, + .integer = 123, + }, + }, + { 0 } + }, + }, + .omit = true, + }, + { 0 } + }; + const uint8_t exp[] = { + 0xbf, 0x63, 0x73, 0x74, 0x72, 0x65, 0x6d, 0x79, + 0x73, 0x74, 0x72, 0xff, + }; + + cborattr_test_util_encode(attrs, exp, sizeof(exp)); +} diff --git a/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_encode_simple.c b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_encode_simple.c new file mode 100644 index 000000000000..68c746e2cdca --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cborattr/test/src/testcases/cborattr_encode_simple.c @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_cborattr.h" + +TEST_CASE_SELF(test_cborattr_encode_simple) +{ + const struct cbor_out_attr_t attrs[] = { + { + .attribute = "null", + .val = { + .type = CborAttrNullType, + }, + }, + { + .attribute = "bool", + .val = { + .type = CborAttrBooleanType, + .boolean = true, + }, + }, + { + .attribute = "int", + .val = { + .type = CborAttrIntegerType, + .integer = -99, + }, + }, + { + .attribute = "uint", + .val = { + .type = CborAttrUnsignedIntegerType, + .integer = 8442, + }, + }, + { + .attribute = "float", + .val = { + .type = CborAttrFloatType, + .fval = 8.0, + }, + }, + { + .attribute = "double", + .val = { + .type = CborAttrDoubleType, + .real = 16.0, + }, + }, + { + .attribute = "bytes", + .val = { + .type = CborAttrByteStringType, + .bytestring.data = (uint8_t[]) {1, 2, 3}, + .bytestring.len = 3, + }, + }, + { + .attribute = "str", + .val = { + .type = CborAttrTextStringType, + .string = "mystr", + }, + }, + { + .attribute = "arr", + .val = { + .type = CborAttrArrayType, + .array = { + .elems = (struct cbor_out_val_t[]) { + [0] = { + .type = CborAttrUnsignedIntegerType, + .integer = 4355, + }, + [1] = { + .type = CborAttrBooleanType, + .integer = false, + }, + }, + .len = 2, + }, + }, + }, + { + .attribute = "obj", + .val = { + .type = CborAttrObjectType, + .obj = (struct cbor_out_attr_t[]) { + { + .attribute = "inner_str", + .val = { + .type = CborAttrTextStringType, + .string = "mystr2", + }, + }, + { + .attribute = "inner_int", + .val = { + .type = CborAttrIntegerType, + .integer = 123, + }, + }, + { 0 } + }, + }, + }, + { 0 } + }; + const uint8_t exp[] = { + 0xbf, 0x64, 0x6e, 0x75, 0x6c, 0x6c, 0xf6, 0x64, + 0x62, 0x6f, 0x6f, 0x6c, 0xf5, 0x63, 0x69, 0x6e, + 0x74, 0x38, 0x62, 0x64, 0x75, 0x69, 0x6e, 0x74, + 0x19, 0x20, 0xfa, 0x65, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0xfa, 0x41, 0x00, 0x00, 0x00, 0x66, 0x64, + 0x6f, 0x75, 0x62, 0x6c, 0x65, 0xfb, 0x40, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x62, + 0x79, 0x74, 0x65, 0x73, 0x43, 0x01, 0x02, 0x03, + 0x63, 0x73, 0x74, 0x72, 0x65, 0x6d, 0x79, 0x73, + 0x74, 0x72, 0x63, 0x61, 0x72, 0x72, 0x82, 0x19, + 0x11, 0x03, 0xf4, 0x63, 0x6f, 0x62, 0x6a, 0xbf, + 0x69, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x5f, 0x73, + 0x74, 0x72, 0x66, 0x6d, 0x79, 0x73, 0x74, 0x72, + 0x32, 0x69, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x5f, + 0x69, 0x6e, 0x74, 0x18, 0x7b, 0xff, 0xff, + }; + + cborattr_test_util_encode(attrs, exp, sizeof(exp)); +} diff --git a/subsys/mgmt/mcumgr/lib/cmd/CMakeLists.txt b/subsys/mgmt/mcumgr/lib/cmd/CMakeLists.txt new file mode 100644 index 000000000000..76840000a1e0 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# Copyright (c) 2018-2021 mcumgr authors +# +# SPDX-License-Identifier: Apache-2.0 +# + +add_subdirectory_ifdef(CONFIG_MCUMGR_CMD_FS_MGMT fs_mgmt) +add_subdirectory_ifdef(CONFIG_MCUMGR_CMD_IMG_MGMT img_mgmt) +add_subdirectory_ifdef(CONFIG_MCUMGR_CMD_OS_MGMT os_mgmt) +add_subdirectory_ifdef(CONFIG_MCUMGR_CMD_STAT_MGMT stat_mgmt) +add_subdirectory_ifdef(CONFIG_MCUMGR_CMD_SHELL_MGMT shell_mgmt) diff --git a/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/CMakeLists.txt b/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/CMakeLists.txt new file mode 100644 index 000000000000..dffc4a4828a2 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# Copyright (c) 2018-2021 mcumgr authors +# +# SPDX-License-Identifier: Apache-2.0 +# + +target_include_directories(MCUMGR INTERFACE include) + +zephyr_library_sources( + src/zephyr_fs_mgmt.c + src/fs_mgmt.c +) diff --git a/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/include/fs_mgmt/fs_mgmt.h b/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/include/fs_mgmt/fs_mgmt.h new file mode 100644 index 000000000000..68dca68970e7 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/include/fs_mgmt/fs_mgmt.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef H_FS_MGMT_ +#define H_FS_MGMT_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Command IDs for file system management group. + */ +#define FS_MGMT_ID_FILE 0 + +/** + * @brief Registers the file system management command handler group. + */ +void fs_mgmt_register_group(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/include/fs_mgmt/fs_mgmt_config.h b/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/include/fs_mgmt/fs_mgmt_config.h new file mode 100644 index 000000000000..10b1298b5bd8 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/include/fs_mgmt/fs_mgmt_config.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef H_FS_MGMT_CONFIG_ +#define H_FS_MGMT_CONFIG_ + +#define MCUMGR_BUF_SIZE CONFIG_MCUMGR_BUF_SIZE + +/* File chunk needs to fit into MCUGMR_BUF_SZIE with all required headers + * and other data fields; following information takes space off the + * MCUMGR_BUF_SIZE, N is CONFIG_FS_MGMT_MAX_OFFSET_LEN + * MGMT_HDR_SIZE - header that is placed in front of buffer and not + * visible for cbod encoder (see smp_handle_single_req); + * 9 + 1 -- bytes taken by definition of CBOR undefined length map and map + * terminator (break) character; + * 1 + strlen("off") + [1, N] -- CBOR encoded pair of "off" marker and + * offset of the chunk within the file; + * 1 + strlen("data") + [1, N] -- CBOR encoded "data" marker; this marker + * will be followed by file chunk of size FS_MGMT_DL_CHUNK_SIZE + * 1 + strlen("rc") + 1 -- status code of operation; + * 1 + strlen("len") + [1, N] -- CBOR encoded "len" marker and complete + * length of a file; this is only sent once when "off" is 0; + * + * FS_MGMT_DL_CHUNK_SIZE is calculated with most pessimistic estimations, + * that is with headers fields taking most space, i.e. N bytes. + */ +#define CBOR_AND_OTHER_HDR \ + ((9 + 1) + \ + (1 + 3 + CONFIG_FS_MGMT_MAX_OFFSET_LEN) + \ + (1 + 4 + CONFIG_FS_MGMT_MAX_OFFSET_LEN) + \ + (1 + 2 + 1) + \ + (1 + 3 + CONFIG_FS_MGMT_MAX_OFFSET_LEN)) + +#if defined(CONFIG_FS_MGMT_DL_CHUNK_SIZE_LIMIT) +#if (CONFIG_FS_MGMT_DL_CHUNK_SIZE + CBOR_AND_OTHER_HDR) > MCUMGR_BUF_SIZE +#warning FS_MGMT_DL_CHUNK_SIZE too big, rounding it down. +#else +#define FS_MGMT_DL_CHUNK_SIZE (CONFIG_FS_MGMT_DL_CHUNK_SIZE) +#endif +#endif + +#if !defined(FS_MGMT_DL_CHUNK_SIZE) +#define FS_MGMT_DL_CHUNK_SIZE (MCUMGR_BUF_SIZE - CBOR_AND_OTHER_HDR) +#endif + +#define FS_MGMT_PATH_SIZE CONFIG_FS_MGMT_PATH_SIZE +#define FS_MGMT_UL_CHUNK_SIZE CONFIG_FS_MGMT_UL_CHUNK_SIZE + +#endif diff --git a/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/include/fs_mgmt/fs_mgmt_impl.h b/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/include/fs_mgmt/fs_mgmt_impl.h new file mode 100644 index 000000000000..0eeab54e668e --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/include/fs_mgmt/fs_mgmt_impl.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Declares implementation-specific functions required by file system + * management. The default stubs can be overridden with functions that + * are compatible with the host OS. + */ + +#ifndef H_FS_MGMT_IMPL_ +#define H_FS_MGMT_IMPL_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Retrieves the length of the file at the specified path. + * + * @param path The path of the file to query. + * @param out_len On success, the file length gets written here. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +int fs_mgmt_impl_filelen(const char *path, size_t *out_len); + +/** + * @brief Reads the specified chunk of file data. + * + * @param path The path of the file to read from. + * @param offset The byte offset to read from. + * @param len The number of bytes to read. + * @param out_data On success, the file data gets written here. + * @param out_len On success, the number of bytes actually read + * gets written here. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +int fs_mgmt_impl_read(const char *path, size_t offset, size_t len, + void *out_data, size_t *out_len); + +/** + * @brief Writes the specified chunk of file data. A write to offset 0 must + * truncate the file; other offsets must append. + * + * @param path The path of the file to write to. + * @param offset The byte offset to write to. + * @param data The data to write to the file. + * @param len The number of bytes to write. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +int fs_mgmt_impl_write(const char *path, size_t offset, const void *data, + size_t len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/src/fs_mgmt.c b/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/src/fs_mgmt.c new file mode 100644 index 000000000000..eeb3938d6aac --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/src/fs_mgmt.c @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "cborattr/cborattr.h" +#include "mgmt/mgmt.h" +#include "fs_mgmt/fs_mgmt.h" +#include "fs_mgmt/fs_mgmt_impl.h" +#include "fs_mgmt/fs_mgmt_config.h" + +static struct { + /** Whether an upload is currently in progress. */ + bool uploading; + + /** Expected offset of next upload request. */ + size_t off; + + /** Total length of file currently being uploaded. */ + size_t len; +} fs_mgmt_ctxt; + +static const struct mgmt_handler fs_mgmt_handlers[]; + + +/** + * Command handler: fs file (read) + */ +static int +fs_mgmt_file_download(struct mgmt_ctxt *ctxt) +{ + uint8_t file_data[FS_MGMT_DL_CHUNK_SIZE]; + char path[FS_MGMT_PATH_SIZE + 1]; + unsigned long long off; + CborError err; + size_t bytes_read; + size_t file_len; + int rc; + + const struct cbor_attr_t dload_attr[] = { + { + .attribute = "off", + .type = CborAttrUnsignedIntegerType, + .addr.uinteger = &off, + }, + { + .attribute = "name", + .type = CborAttrTextStringType, + .addr.string = path, + .len = sizeof(path), + }, + { 0 }, + }; + + off = ULLONG_MAX; + rc = cbor_read_object(&ctxt->it, dload_attr); + if (rc != 0 || off == ULLONG_MAX) { + return MGMT_ERR_EINVAL; + } + + /* Only the response to the first download request contains the total file + * length. + */ + if (off == 0) { + rc = fs_mgmt_impl_filelen(path, &file_len); + if (rc != 0) { + return rc; + } + } + + /* Read the requested chunk from the file. */ + rc = fs_mgmt_impl_read(path, off, FS_MGMT_DL_CHUNK_SIZE, + file_data, &bytes_read); + if (rc != 0) { + return rc; + } + + /* Encode the response. */ + err = 0; + err |= cbor_encode_text_stringz(&ctxt->encoder, "off"); + err |= cbor_encode_uint(&ctxt->encoder, off); + err |= cbor_encode_text_stringz(&ctxt->encoder, "data"); + err |= cbor_encode_byte_string(&ctxt->encoder, file_data, bytes_read); + err |= cbor_encode_text_stringz(&ctxt->encoder, "rc"); + err |= cbor_encode_int(&ctxt->encoder, MGMT_ERR_EOK); + if (off == 0) { + err |= cbor_encode_text_stringz(&ctxt->encoder, "len"); + err |= cbor_encode_uint(&ctxt->encoder, file_len); + } + + if (err != 0) { + return MGMT_ERR_ENOMEM; + } + + return 0; +} + +/** + * Encodes a file upload response. + */ +static int +fs_mgmt_file_upload_rsp(struct mgmt_ctxt *ctxt, int rc, unsigned long long off) +{ + CborError err; + + err = 0; + err |= cbor_encode_text_stringz(&ctxt->encoder, "rc"); + err |= cbor_encode_int(&ctxt->encoder, rc); + err |= cbor_encode_text_stringz(&ctxt->encoder, "off"); + err |= cbor_encode_uint(&ctxt->encoder, off); + + if (err != 0) { + return MGMT_ERR_ENOMEM; + } + + return 0; +} + +/** + * Command handler: fs file (write) + */ +static int +fs_mgmt_file_upload(struct mgmt_ctxt *ctxt) +{ + uint8_t file_data[FS_MGMT_UL_CHUNK_SIZE]; + char file_name[FS_MGMT_PATH_SIZE + 1]; + unsigned long long len; + unsigned long long off; + size_t data_len; + size_t new_off; + int rc; + + const struct cbor_attr_t uload_attr[5] = { + [0] = { + .attribute = "off", + .type = CborAttrUnsignedIntegerType, + .addr.uinteger = &off, + .nodefault = true + }, + [1] = { + .attribute = "data", + .type = CborAttrByteStringType, + .addr.bytestring.data = file_data, + .addr.bytestring.len = &data_len, + .len = sizeof(file_data) + }, + [2] = { + .attribute = "len", + .type = CborAttrUnsignedIntegerType, + .addr.uinteger = &len, + .nodefault = true + }, + [3] = { + .attribute = "name", + .type = CborAttrTextStringType, + .addr.string = file_name, + .len = sizeof(file_name) + }, + [4] = { 0 }, + }; + + len = ULLONG_MAX; + off = ULLONG_MAX; + rc = cbor_read_object(&ctxt->it, uload_attr); + if (rc != 0 || off == ULLONG_MAX || file_name[0] == '\0') { + return MGMT_ERR_EINVAL; + } + + if (off == 0) { + /* Total file length is a required field in the first chunk request. */ + if (len == ULLONG_MAX) { + return MGMT_ERR_EINVAL; + } + + fs_mgmt_ctxt.uploading = true; + fs_mgmt_ctxt.off = 0; + fs_mgmt_ctxt.len = len; + } else { + if (!fs_mgmt_ctxt.uploading) { + return MGMT_ERR_EINVAL; + } + + if (off != fs_mgmt_ctxt.off) { + /* Invalid offset. Drop the data and send the expected offset. */ + return fs_mgmt_file_upload_rsp(ctxt, MGMT_ERR_EINVAL, fs_mgmt_ctxt.off); + } + } + + new_off = fs_mgmt_ctxt.off + data_len; + if (new_off > fs_mgmt_ctxt.len) { + /* Data exceeds image length. */ + return MGMT_ERR_EINVAL; + } + + if (data_len > 0) { + /* Write the data chunk to the file. */ + rc = fs_mgmt_impl_write(file_name, off, file_data, data_len); + if (rc != 0) { + return rc; + } + fs_mgmt_ctxt.off = new_off; + } + + if (fs_mgmt_ctxt.off == fs_mgmt_ctxt.len) { + /* Upload complete. */ + fs_mgmt_ctxt.uploading = false; + } + + /* Send the response. */ + return fs_mgmt_file_upload_rsp(ctxt, 0, fs_mgmt_ctxt.off); +} + +static const struct mgmt_handler fs_mgmt_handlers[] = { + [FS_MGMT_ID_FILE] = { + .mh_read = fs_mgmt_file_download, + .mh_write = fs_mgmt_file_upload, + }, +}; + +#define FS_MGMT_HANDLER_CNT ARRAY_SIZE(fs_mgmt_handlers) + +static struct mgmt_group fs_mgmt_group = { + .mg_handlers = fs_mgmt_handlers, + .mg_handlers_count = FS_MGMT_HANDLER_CNT, + .mg_group_id = MGMT_GROUP_ID_FS, +}; + +void +fs_mgmt_register_group(void) +{ + mgmt_register_group(&fs_mgmt_group); +} diff --git a/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/src/zephyr_fs_mgmt.c b/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/src/zephyr_fs_mgmt.c new file mode 100644 index 000000000000..c0c29039be65 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/fs_mgmt/src/zephyr_fs_mgmt.c @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +int +fs_mgmt_impl_filelen(const char *path, size_t *out_len) +{ + struct fs_dirent dirent; + int rc; + + rc = fs_stat(path, &dirent); + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } + + if (dirent.type != FS_DIR_ENTRY_FILE) { + return MGMT_ERR_EUNKNOWN; + } + + *out_len = dirent.size; + + return 0; +} + +int +fs_mgmt_impl_read(const char *path, size_t offset, size_t len, + void *out_data, size_t *out_len) +{ + struct fs_file_t file; + ssize_t bytes_read; + int rc; + + fs_file_t_init(&file); + rc = fs_open(&file, path, FS_O_READ); + if (rc != 0) { + return MGMT_ERR_ENOENT; + } + + rc = fs_seek(&file, offset, FS_SEEK_SET); + if (rc != 0) { + goto done; + } + + bytes_read = fs_read(&file, out_data, len); + if (bytes_read < 0) { + goto done; + } + + *out_len = bytes_read; + +done: + fs_close(&file); + + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } else { + return 0; + } +} + +static int +zephyr_fs_mgmt_truncate(const char *path) +{ + size_t len; + int rc; + + /* Attempt to get the length of the file at the specified path. This is a + * quick way to determine if there is already a file there. + */ + rc = fs_mgmt_impl_filelen(path, &len); + if (rc == 0) { + /* There is already a file with the specified path. Unlink it to + * simulate a truncate operation. + * + * XXX: This isn't perfect - if the file is currently open, the unlink + * operation won't actually delete the file. Consequently, the file + * will get partially overwritten rather than truncated. The NFFS port + * doesn't support the truncate operation, so this is an imperfect + * workaround. + */ + rc = fs_unlink(path); + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } + } + + return 0; +} + +int +fs_mgmt_impl_write(const char *path, size_t offset, const void *data, + size_t len) +{ + struct fs_file_t file; + int rc; + + /* Truncate the file before writing the first chunk. This is done to + * properly handle an overwrite of an existing file + */ + if (offset == 0) { + rc = zephyr_fs_mgmt_truncate(path); + if (rc != 0) { + return rc; + } + } + + fs_file_t_init(&file); + rc = fs_open(&file, path, FS_O_CREATE | FS_O_WRITE); + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } + + rc = fs_seek(&file, offset, FS_SEEK_SET); + if (rc != 0) { + goto done; + } + + rc = fs_write(&file, data, len); + if (rc < 0) { + goto done; + } + + rc = 0; + +done: + fs_close(&file); + + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } else { + return 0; + } +} diff --git a/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/CMakeLists.txt b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/CMakeLists.txt new file mode 100644 index 000000000000..af734af63cdf --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright (c) 2018-2021 mcumgr authors +# +# SPDX-License-Identifier: Apache-2.0 +# + +target_include_directories(MCUMGR INTERFACE + include +) + +zephyr_library_sources( + src/zephyr_img_mgmt.c + src/zephyr_img_mgmt_log.c + src/img_mgmt.c + src/img_mgmt_state.c + src/img_mgmt_util.c +) diff --git a/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/include/img_mgmt/image.h b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/include/img_mgmt/image.h new file mode 100644 index 000000000000..7fb6319bd049 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/include/img_mgmt/image.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef H_IMAGE_ +#define H_IMAGE_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define IMAGE_MAGIC 0x96f3b83d +#define IMAGE_TLV_INFO_MAGIC 0x6907 +#define IMAGE_TLV_PROT_INFO_MAGIC 0x6908 + +#define IMAGE_HEADER_SIZE 32 + +/** Image header flags. */ +#define IMAGE_F_NON_BOOTABLE 0x00000010 /* Split image app. */ +#define IMAGE_F_ROM_FIXED_ADDR 0x00000100 + +/** Image trailer TLV types. */ +#define IMAGE_TLV_SHA256 0x10 /* SHA256 of image hdr and body */ + +/** Image TLV-specific definitions. */ +#define IMAGE_HASH_LEN 32 + +struct image_version { + uint8_t iv_major; + uint8_t iv_minor; + uint16_t iv_revision; + uint32_t iv_build_num; +}; + +/** Image header. All fields are in little endian byte order. */ +struct image_header { + uint32_t ih_magic; + uint32_t ih_load_addr; + uint16_t ih_hdr_size; /* Size of image header (bytes). */ + uint16_t _pad2; + uint32_t ih_img_size; /* Does not include header. */ + uint32_t ih_flags; /* IMAGE_F_[...]. */ + struct image_version ih_ver; + uint32_t _pad3; +}; + +/** Image TLV header. All fields in little endian. */ +struct image_tlv_info { + uint16_t it_magic; + uint16_t it_tlv_tot; /* size of TLV area (including tlv_info header) */ +}; + +/** Image trailer TLV format. All fields in little endian. */ +struct image_tlv { + uint8_t it_type; /* IMAGE_TLV_[...]. */ + uint8_t _pad; + uint16_t it_len; /* Data length (not including TLV header). */ +}; + +_Static_assert(sizeof(struct image_header) == IMAGE_HEADER_SIZE, + "struct image_header not required size"); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/include/img_mgmt/img_mgmt.h b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/include/img_mgmt/img_mgmt.h new file mode 100644 index 000000000000..537aa789ad44 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/include/img_mgmt/img_mgmt.h @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef H_IMG_MGMT_ +#define H_IMG_MGMT_ + +#include +#include "img_mgmt_config.h" +#include "mgmt/mgmt.h" + +struct image_version; + +#ifdef __cplusplus +extern "C" { +#endif + +#define IMG_MGMT_HASH_STR 48 +#define IMG_MGMT_HASH_LEN 32 +#define IMG_MGMT_DATA_SHA_LEN 32 /* SHA256 */ + +/* + * Image state flags + */ +#define IMG_MGMT_STATE_F_PENDING 0x01 +#define IMG_MGMT_STATE_F_CONFIRMED 0x02 +#define IMG_MGMT_STATE_F_ACTIVE 0x04 +#define IMG_MGMT_STATE_F_PERMANENT 0x08 + +#define IMG_MGMT_VER_MAX_STR_LEN 25 /* 255.255.65535.4294967295\0 */ + +/* + * Swap Types for image management state machine + */ +#define IMG_MGMT_SWAP_TYPE_NONE 0 +#define IMG_MGMT_SWAP_TYPE_TEST 1 +#define IMG_MGMT_SWAP_TYPE_PERM 2 +#define IMG_MGMT_SWAP_TYPE_REVERT 3 + +/** + * Command IDs for image management group. + */ +#define IMG_MGMT_ID_STATE 0 +#define IMG_MGMT_ID_UPLOAD 1 +#define IMG_MGMT_ID_FILE 2 +#define IMG_MGMT_ID_CORELIST 3 +#define IMG_MGMT_ID_CORELOAD 4 +#define IMG_MGMT_ID_ERASE 5 + +/* + * IMG_MGMT_ID_UPLOAD statuses. + */ +#define IMG_MGMT_ID_UPLOAD_STATUS_START 0 +#define IMG_MGMT_ID_UPLOAD_STATUS_ONGOING 1 +#define IMG_MGMT_ID_UPLOAD_STATUS_COMPLETE 2 + +extern int boot_current_slot; +extern struct img_mgmt_state g_img_mgmt_state; + +/** Represents an individual upload request. */ +struct img_mgmt_upload_req { + unsigned long long image; /* 0 by default */ + unsigned long long off; /* -1 if unspecified */ + unsigned long long size; /* -1 if unspecified */ + size_t data_len; + size_t data_sha_len; + uint8_t img_data[IMG_MGMT_UL_CHUNK_SIZE]; + uint8_t data_sha[IMG_MGMT_DATA_SHA_LEN]; + bool upgrade; /* Only allow greater version numbers. */ +}; + +/** Global state for upload in progress. */ +struct img_mgmt_state { + /** Flash area being written; -1 if no upload in progress. */ + int area_id; + /** Flash offset of next chunk. */ + uint32_t off; + /** Total size of image data. */ + uint32_t size; + /** Hash of image data; used for resumption of a partial upload. */ + uint8_t data_sha_len; + uint8_t data_sha[IMG_MGMT_DATA_SHA_LEN]; +#if IMG_MGMT_LAZY_ERASE + int sector_id; + uint32_t sector_end; +#endif +}; + +/** Describes what to do during processing of an upload request. */ +struct img_mgmt_upload_action { + /** The total size of the image. */ + unsigned long long size; + /** The number of image bytes to write to flash. */ + int write_bytes; + /** The flash area to write to. */ + int area_id; + /** Whether to process the request; false if offset is wrong. */ + bool proceed; + /** Whether to erase the destination flash area. */ + bool erase; +}; + +/** + * @brief Registers the image management command handler group. + */ +void img_mgmt_register_group(void); + +/** + * @brief Unregisters the image management command handler group. + */ +void img_mgmt_unregister_group(void); + +/* + * @brief Read info of an image give the slot number + * + * @param image_slot Image slot to read info from + * @param image_version Image version to be filled up + * @param hash Ptr to the read image hash + * @param flags Ptr to flags filled up from the image + */ +int img_mgmt_read_info(int image_slot, struct image_version *ver, uint8_t *hash, uint32_t *flags); + +/** + * @brief Get the current running image version + * + * @param image_version Given image version + * + * @return 0 on success, non-zero on failure + */ +int img_mgmt_my_version(struct image_version *ver); + +/** + * @brief Get image version in string from image_version + * + * @param image_version Structure filled with image version + * information + * @param dst Destination string created from the given + * in image version + * + * @return 0 on success, non-zero on failure + */ +int img_mgmt_ver_str(const struct image_version *ver, char *dst); + +/** + * @brief Check if the image slot is in use + * + * @param slot Slot to check if its in use + * + * @return 0 on success, non-zero on failure + */ +int img_mgmt_slot_in_use(int slot); + +/** + * @brief Check if the DFU status is pending + * + * @return 1 if there's pending DFU otherwise 0. + */ +int img_mgmt_state_any_pending(void); + +/** + * @brief Collects information about the specified image slot + * + * @param query_slot Slot to read state flags from + * + * @return return the state flags + */ +uint8_t img_mgmt_state_flags(int query_slot); + +/** + * @brief Sets the pending flag for the specified image slot. That is, the system + * will swap to the specified image on the next reboot. If the permanent + * argument is specified, the system doesn't require a confirm after the swap + * occurs. + * + * @param slot Image slot to set pending + * @param permanent If set no confirm is required after image swap + * + * @return 0 on success, non-zero on failure + */ +int img_mgmt_state_set_pending(int slot, int permanent); + +/** + * Confirms the current image state. Prevents a fallback from occurring on the + * next reboot if the active image is currently being tested. + * + * @return 0 on success, non -zero on failure + */ +int img_mgmt_state_confirm(void); + +/** @brief Generic callback function for events */ +typedef void (*img_mgmt_dfu_cb)(void); + +/** Callback function pointers */ +struct img_mgmt_dfu_callbacks_t { + img_mgmt_dfu_cb dfu_started_cb; + img_mgmt_dfu_cb dfu_stopped_cb; + img_mgmt_dfu_cb dfu_pending_cb; + img_mgmt_dfu_cb dfu_confirmed_cb; +}; + +/** @typedef img_mgmt_upload_fn + * @brief Application callback that is executed when an image upload request is + * received. + * + * The callback's return code determines whether the upload request is accepted + * or rejected. If the callback returns 0, processing of the upload request + * proceeds. If the callback returns nonzero, the request is rejected with a + * response containing an `rc` value equal to the return code. + * + * @param offset The offset specified by the incoming request. + * @param size The total size of the image being uploaded. + * @param arg Optional argument specified when the callback + * was configured. + * + * @return 0 if the upload request should be accepted; nonzero to reject the request with the + * specified status. + */ +typedef int (*img_mgmt_upload_fn)(uint32_t offset, uint32_t size, void *arg); + +/** + * @brief Configures a callback that gets called whenever a valid image upload + * request is received. + * + * The callback's return code determines whether the upload request is accepted + * or rejected. If the callback returns 0, processing of the upload request + * proceeds. If the callback returns nonzero, the request is rejected with a + * response containing an `rc` value equal to the return code. + * + * @param cb The callback to execute on rx of an upload request. + * @param arg Optional argument that gets passed to the callback. + */ +void img_mgmt_set_upload_cb(img_mgmt_upload_fn cb, void *arg); +void img_mgmt_register_callbacks(const struct img_mgmt_dfu_callbacks_t *cb_struct); +void img_mgmt_dfu_stopped(void); +void img_mgmt_dfu_started(void); +void img_mgmt_dfu_pending(void); +void img_mgmt_dfu_confirmed(void); + +#if IMG_MGMT_VERBOSE_ERR +int img_mgmt_error_rsp(struct mgmt_ctxt *ctxt, int rc, const char *rsn); +extern const char *img_mgmt_err_str_app_reject; +extern const char *img_mgmt_err_str_hdr_malformed; +extern const char *img_mgmt_err_str_magic_mismatch; +extern const char *img_mgmt_err_str_no_slot; +extern const char *img_mgmt_err_str_flash_open_failed; +extern const char *img_mgmt_err_str_flash_erase_failed; +extern const char *img_mgmt_err_str_flash_write_failed; +extern const char *img_mgmt_err_str_downgrade; +extern const char *img_mgmt_err_str_image_bad_flash_addr; +#else +#define img_mgmt_error_rsp(ctxt, rc, rsn) (rc) +#define img_mgmt_err_str_app_reject NULL +#define img_mgmt_err_str_hdr_malformed NULL +#define img_mgmt_err_str_magic_mismatch NULL +#define img_mgmt_err_str_no_slot NULL +#define img_mgmt_err_str_flash_open_failed NULL +#define img_mgmt_err_str_flash_erase_failed NULL +#define img_mgmt_err_str_flash_write_failed NULL +#define img_mgmt_err_str_downgrade NULL +#define img_mgmt_err_str_image_bad_flash_addr NULL +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* H_IMG_MGMT_ */ diff --git a/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/include/img_mgmt/img_mgmt_config.h b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/include/img_mgmt/img_mgmt_config.h new file mode 100644 index 000000000000..62359f6b27c3 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/include/img_mgmt/img_mgmt_config.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef H_IMG_MGMT_CONFIG_ +#define H_IMG_MGMT_CONFIG_ + +/* Number of updatable images */ +#define IMG_MGMT_UPDATABLE_IMAGE_NUMBER 1 +/* Image status list will only contain image attributes that are true/non-zero */ +#define IMG_MGMT_FRUGAL_LIST 0 + +#define IMG_MGMT_UL_CHUNK_SIZE CONFIG_IMG_MGMT_UL_CHUNK_SIZE +#define IMG_MGMT_VERBOSE_ERR CONFIG_IMG_MGMT_VERBOSE_ERR +#define IMG_MGMT_LAZY_ERASE CONFIG_IMG_ERASE_PROGRESSIVELY +#define IMG_MGMT_DUMMY_HDR CONFIG_IMG_MGMT_DUMMY_HDR +#define IMG_MGMT_BOOT_CURR_SLOT 0 +#ifdef CONFIG_IMG_MGMT_UPDATABLE_IMAGE_NUMBER +/* Up to two images are supported */ +#undef IMG_MGMT_UPDATABLE_IMAGE_NUMBER +#define IMG_MGMT_UPDATABLE_IMAGE_NUMBER CONFIG_IMG_MGMT_UPDATABLE_IMAGE_NUMBER +#endif +#ifdef CONFIG_IMG_MGMT_FRUGAL_LIST +#undef IMG_MGMT_FRUGAL_LIST +#define IMG_MGMT_FRUGAL_LIST CONFIG_IMG_MGMT_FRUGAL_LIST +#endif + +#endif diff --git a/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/include/img_mgmt/img_mgmt_impl.h b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/include/img_mgmt/img_mgmt_impl.h new file mode 100644 index 000000000000..be04a6ab1c5b --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/include/img_mgmt/img_mgmt_impl.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Declares implementation-specific functions required by image management. The default + * stubs can be overridden with functions that are compatible with the host OS. + */ + +#ifndef H_IMG_MGMT_IMPL_ +#define H_IMG_MGMT_IMPL_ + +#include +#include +#include "img_mgmt/img_mgmt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Ensures the spare slot (slot 1) is fully erased. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +int img_mgmt_impl_erase_slot(void); + +/** + * @brief Marks the image in the specified slot as pending. On the next reboot, + * the system will perform a boot of the specified image. + * + * @param slot The slot to mark as pending. In the typical use case, this is 1. + * @param permanent Whether the image should be used permanently or only tested once: + * 0=run image once, then confirm or revert. + * 1=run image forever. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +int img_mgmt_impl_write_pending(int slot, bool permanent); + +/** + * @brief Marks the image in slot 0 as confirmed. The system will continue + * booting into the image in slot 0 until told to boot from a different slot. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +int img_mgmt_impl_write_confirmed(void); + +/** + * @brief Reads the specified chunk of data from an image slot. + * + * @param slot The index of the slot to read from. + * @param offset The offset within the slot to read from. + * @param dst On success, the read data gets written here. + * @param num_bytes The number of byets to read. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +int img_mgmt_impl_read(int slot, unsigned int offset, void *dst, unsigned int num_bytes); + +/** + * @brief Writes the specified chunk of image data to slot 1. + * + * @param offset The offset within slot 1 to write to. + * @param data The image data to write. + * @param num_bytes The number of bytes to read. + * @param last Whether this chunk is the end of the image: + * false=additional image chunks are forthcoming. + * true=last image chunk; flush unwritten data to disk. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +int img_mgmt_impl_write_image_data(unsigned int offset, const void *data, unsigned int num_bytes, + bool last); + +/** + * @brief Indicates the type of swap operation that will occur on the next + * reboot, if any, between provided slot and it's pair. + * Querying any slots of the same pair will give the same result. + * + * @param image An slot number; + * @return An IMG_MGMT_SWAP_TYPE_[...] code. + */ +int img_mgmt_impl_swap_type(int slot); + +/** + * Collects information about the specified image slot. + * + * @return Flags of the specified image slot + */ +uint8_t img_mgmt_state_flags(int query_slot); + +/** + * Erases image data at given offset + * + * @param offset The offset within slot 1 to erase at. + * @param num_bytes The number of bytes to erase. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +int img_mgmt_impl_erase_image_data(unsigned int off, unsigned int num_bytes); + +/** + * Erases a flash sector as image upload crosses a sector boundary. + * Erasing the entire flash size at one time can take significant time, + * causing a bluetooth disconnect or significant battery sag. + * Instead we will erase immediately prior to crossing a sector. + * We could check for empty to increase efficiency, but instead we always erase + * for consistency and simplicity. + * + * @param off Offset that is about to be written + * @param len Number of bytes to be written + * + * @return 0 if success ERROR_CODE if could not erase sector + */ +int img_mgmt_impl_erase_if_needed(uint32_t off, uint32_t len); + +/** + * Verifies an upload request and indicates the actions that should be taken + * during processing of the request. This is a "read only" function in the + * sense that it doesn't write anything to flash and doesn't modify any global + * variables. + * + * @param req The upload request to inspect. + * @param action On success, gets populated with information about how to process the + * request. + * + * @return 0 if processing should occur;A MGMT_ERR code if an error response should be sent instead. + */ +int img_mgmt_impl_upload_inspect(const struct img_mgmt_upload_req *req, + struct img_mgmt_upload_action *action, const char **errstr); + +#define ERASED_VAL_32(x) (((x) << 24) | ((x) << 16) | ((x) << 8) | (x)) +int img_mgmt_impl_erased_val(int slot, uint8_t *erased_val); + +int img_mgmt_impl_log_upload_start(int status); + +int img_mgmt_impl_log_upload_done(int status, const uint8_t *hashp); + +int img_mgmt_impl_log_pending(int status, const uint8_t *hash); + +int img_mgmt_impl_log_confirm(int status, const uint8_t *hash); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/img_mgmt.c b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/img_mgmt.c new file mode 100644 index 000000000000..0c8ea50bc22c --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/img_mgmt.c @@ -0,0 +1,613 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "cborattr/cborattr.h" +#include "mgmt/mgmt.h" + +#include "img_mgmt/image.h" +#include "img_mgmt/img_mgmt.h" +#include "img_mgmt/img_mgmt_impl.h" +#include "img_mgmt_priv.h" +#include "img_mgmt/img_mgmt_config.h" + +static void *img_mgmt_upload_arg; +static img_mgmt_upload_fn img_mgmt_upload_cb; + +const struct img_mgmt_dfu_callbacks_t *img_mgmt_dfu_callbacks_fn; + +struct img_mgmt_state g_img_mgmt_state; + +#if IMG_MGMT_VERBOSE_ERR +const char *img_mgmt_err_str_app_reject = "app reject"; +const char *img_mgmt_err_str_hdr_malformed = "header malformed"; +const char *img_mgmt_err_str_magic_mismatch = "magic mismatch"; +const char *img_mgmt_err_str_no_slot = "no slot"; +const char *img_mgmt_err_str_flash_open_failed = "fa open fail"; +const char *img_mgmt_err_str_flash_erase_failed = "fa erase fail"; +const char *img_mgmt_err_str_flash_write_failed = "fa write fail"; +const char *img_mgmt_err_str_downgrade = "downgrade"; +const char *img_mgmt_err_str_image_bad_flash_addr = "img addr mismatch"; +#endif + +/** + * Finds the TLVs in the specified image slot, if any. + */ +static int +img_mgmt_find_tlvs(int slot, size_t *start_off, size_t *end_off, + uint16_t magic) +{ + struct image_tlv_info tlv_info; + int rc; + + rc = img_mgmt_impl_read(slot, *start_off, &tlv_info, sizeof(tlv_info)); + if (rc != 0) { + /* Read error. */ + return MGMT_ERR_EUNKNOWN; + } + + if (tlv_info.it_magic != magic) { + /* No TLVs. */ + return MGMT_ERR_ENOENT; + } + + *start_off += sizeof(tlv_info); + *end_off = *start_off + tlv_info.it_tlv_tot; + + return 0; +} + +/* + * Reads the version and build hash from the specified image slot. + */ +int +img_mgmt_read_info(int image_slot, struct image_version *ver, uint8_t *hash, + uint32_t *flags) +{ + +#if IMG_MGMT_DUMMY_HDR + uint8_t dummy_hash[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x00, 0x11, 0x22, + 0x33, 0x44, 0x55, 0x66, 0x77}; + + if (!hash && !ver && !flags) { + return 0; + } + + if (hash) { + memcpy(hash, dummy_hash, IMG_MGMT_HASH_LEN); + } + + if (ver) { + memset(ver, 0xff, sizeof(*ver)); + } + + if (flags) { + *flags = 0; + } + + return 0; +#endif + + struct image_header hdr; + struct image_tlv tlv; + size_t data_off; + size_t data_end; + bool hash_found; + uint8_t erased_val; + uint32_t erased_val_32; + int rc; + + rc = img_mgmt_impl_erased_val(image_slot, &erased_val); + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } + + rc = img_mgmt_impl_read(image_slot, 0, &hdr, sizeof(hdr)); + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } + + if (ver != NULL) { + memset(ver, erased_val, sizeof(*ver)); + } + erased_val_32 = ERASED_VAL_32(erased_val); + if (hdr.ih_magic == IMAGE_MAGIC) { + if (ver != NULL) { + memcpy(ver, &hdr.ih_ver, sizeof(*ver)); + } + } else if (hdr.ih_magic == erased_val_32) { + return MGMT_ERR_ENOENT; + } else { + return MGMT_ERR_EUNKNOWN; + } + + if (flags != NULL) { + *flags = hdr.ih_flags; + } + + /* Read the image's TLVs. We first try to find the protected TLVs, if the protected + * TLV does not exist, we try to find non-protected TLV which also contains the hash + * TLV. All images are required to have a hash TLV. If the hash is missing, the image + * is considered invalid. + */ + data_off = hdr.ih_hdr_size + hdr.ih_img_size; + + rc = img_mgmt_find_tlvs(image_slot, &data_off, &data_end, IMAGE_TLV_PROT_INFO_MAGIC); + if (!rc) { + /* The data offset should start after the header bytes after the end of + * the protected TLV, if one exists. + */ + data_off = data_end - sizeof(struct image_tlv_info); + } + + rc = img_mgmt_find_tlvs(image_slot, &data_off, &data_end, IMAGE_TLV_INFO_MAGIC); + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } + + hash_found = false; + while (data_off + sizeof(tlv) <= data_end) { + rc = img_mgmt_impl_read(image_slot, data_off, &tlv, sizeof(tlv)); + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } + if (tlv.it_type == 0xff && tlv.it_len == 0xffff) { + return MGMT_ERR_EUNKNOWN; + } + if (tlv.it_type != IMAGE_TLV_SHA256 || tlv.it_len != IMAGE_HASH_LEN) { + /* Non-hash TLV. Skip it. */ + data_off += sizeof(tlv) + tlv.it_len; + continue; + } + + if (hash_found) { + /* More than one hash. */ + return MGMT_ERR_EUNKNOWN; + } + hash_found = true; + + data_off += sizeof(tlv); + if (hash != NULL) { + if (data_off + IMAGE_HASH_LEN > data_end) { + return MGMT_ERR_EUNKNOWN; + } + rc = img_mgmt_impl_read(image_slot, data_off, hash, + IMAGE_HASH_LEN); + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } + } + } + + if (!hash_found) { + return MGMT_ERR_EUNKNOWN; + } + + return 0; +} + +/* + * Finds image given version number. Returns the slot number image is in, + * or -1 if not found. + */ +int +img_mgmt_find_by_ver(struct image_version *find, uint8_t *hash) +{ + int i; + struct image_version ver; + + for (i = 0; i < 2 * IMG_MGMT_UPDATABLE_IMAGE_NUMBER; i++) { + if (img_mgmt_read_info(i, &ver, hash, NULL) != 0) { + continue; + } + if (!memcmp(find, &ver, sizeof(ver))) { + return i; + } + } + return -1; +} + +/* + * Finds image given hash of the image. Returns the slot number image is in, + * or -1 if not found. + */ +int +img_mgmt_find_by_hash(uint8_t *find, struct image_version *ver) +{ + int i; + uint8_t hash[IMAGE_HASH_LEN]; + + for (i = 0; i < 2 * IMG_MGMT_UPDATABLE_IMAGE_NUMBER; i++) { + if (img_mgmt_read_info(i, ver, hash, NULL) != 0) { + continue; + } + if (!memcmp(hash, find, IMAGE_HASH_LEN)) { + return i; + } + } + return -1; +} + +#if IMG_MGMT_VERBOSE_ERR +int +img_mgmt_error_rsp(struct mgmt_ctxt *ctxt, int rc, const char *rsn) +{ + /* + * This is an error response so returning a different error when failed to + * encode other error probably does not make much sense - just ignore errors + * here. + */ + cbor_encode_text_stringz(&ctxt->encoder, "rsn"); + cbor_encode_text_stringz(&ctxt->encoder, rsn); + return rc; +} +#endif + +/** + * Command handler: image erase + */ +static int +img_mgmt_erase(struct mgmt_ctxt *ctxt) +{ + struct image_version ver; + CborError err; + int rc; + + /* + * First check if image info is valid. + * This check is done incase the flash area has a corrupted image. + */ + rc = img_mgmt_read_info(1, &ver, NULL, NULL); + + if (rc == 0) { + /* Image info is valid. */ + if (img_mgmt_slot_in_use(1)) { + /* No free slot. */ + return MGMT_ERR_EBADSTATE; + } + } + + rc = img_mgmt_impl_erase_slot(); + + if (!rc) { + img_mgmt_dfu_stopped(); + } + + err = 0; + err |= cbor_encode_text_stringz(&ctxt->encoder, "rc"); + err |= cbor_encode_int(&ctxt->encoder, rc); + + if (err != 0) { + return MGMT_ERR_ENOMEM; + } + + return 0; +} + +static int +img_mgmt_upload_good_rsp(struct mgmt_ctxt *ctxt) +{ + CborError err = CborNoError; + + err |= cbor_encode_text_stringz(&ctxt->encoder, "rc"); + err |= cbor_encode_int(&ctxt->encoder, MGMT_ERR_EOK); + err |= cbor_encode_text_stringz(&ctxt->encoder, "off"); + err |= cbor_encode_int(&ctxt->encoder, g_img_mgmt_state.off); + + if (err != 0) { + return MGMT_ERR_ENOMEM; + } + + return 0; +} + +/** + * Logs an upload request if necessary. + * + * @param is_first Whether the request includes the first chunk of the image. + * @param is_last Whether the request includes the last chunk of the image. + * @param status The result of processing the upload request (MGMT_ERR code). + * + * @return 0 on success; nonzero on failure. + */ +static int +img_mgmt_upload_log(bool is_first, bool is_last, int status) +{ + uint8_t hash[IMAGE_HASH_LEN]; + const uint8_t *hashp; + int rc; + + if (is_first) { + return img_mgmt_impl_log_upload_start(status); + } + + if (is_last || status != 0) { + /* Log the image hash if we know it. */ + rc = img_mgmt_read_info(1, NULL, hash, NULL); + if (rc != 0) { + hashp = NULL; + } else { + hashp = hash; + } + + return img_mgmt_impl_log_upload_done(status, hashp); + } + + /* Nothing to log. */ + return 0; +} + +/** + * Command handler: image upload + */ +static int +img_mgmt_upload(struct mgmt_ctxt *ctxt) +{ + struct mgmt_evt_op_cmd_status_arg cmd_status_arg; + struct img_mgmt_upload_req req = { + .off = -1, + .size = -1, + .data_len = 0, + .data_sha_len = 0, + .upgrade = false, + .image = 0, + }; + + const struct cbor_attr_t off_attr[] = { + [0] = { + .attribute = "image", + .type = CborAttrUnsignedIntegerType, + .addr.uinteger = &req.image, + .nodefault = true + }, + [1] = { + .attribute = "data", + .type = CborAttrByteStringType, + .addr.bytestring.data = req.img_data, + .addr.bytestring.len = &req.data_len, + .len = sizeof(req.img_data) + }, + [2] = { + .attribute = "len", + .type = CborAttrUnsignedIntegerType, + .addr.uinteger = &req.size, + .nodefault = true + }, + [3] = { + .attribute = "off", + .type = CborAttrUnsignedIntegerType, + .addr.uinteger = &req.off, + .nodefault = true + }, + [4] = { + .attribute = "sha", + .type = CborAttrByteStringType, + .addr.bytestring.data = req.data_sha, + .addr.bytestring.len = &req.data_sha_len, + .len = sizeof(req.data_sha) + }, + [5] = { + .attribute = "upgrade", + .type = CborAttrBooleanType, + .addr.boolean = &req.upgrade, + .dflt.boolean = false, + }, + [6] = { 0 }, + }; + int rc; + const char *errstr = NULL; + struct img_mgmt_upload_action action; + bool last = false; + + rc = cbor_read_object(&ctxt->it, off_attr); + if (rc != 0) { + return MGMT_ERR_EINVAL; + } + + /* Determine what actions to take as a result of this request. */ + rc = img_mgmt_impl_upload_inspect(&req, &action, &errstr); + if (rc != 0) { + img_mgmt_dfu_stopped(); + return rc; + } + + if (!action.proceed) { + /* Request specifies incorrect offset. Respond with a success code and + * the correct offset. + */ + return img_mgmt_upload_good_rsp(ctxt); + } + + /* Request is valid. Give the application a chance to reject this upload + * request. + */ + if (img_mgmt_upload_cb != NULL) { + rc = img_mgmt_upload_cb(req.off, action.size, img_mgmt_upload_arg); + if (rc != 0) { + errstr = img_mgmt_err_str_app_reject; + goto end; + } + } + + /* Remember flash area ID and image size for subsequent upload requests. */ + g_img_mgmt_state.area_id = action.area_id; + g_img_mgmt_state.size = action.size; + + if (req.off == 0) { + /* + * New upload. + */ + g_img_mgmt_state.off = 0; + + img_mgmt_dfu_started(); + cmd_status_arg.status = IMG_MGMT_ID_UPLOAD_STATUS_START; + + /* + * We accept SHA trimmed to any length by client since it's up to client + * to make sure provided data are good enough to avoid collisions when + * resuming upload. + */ + g_img_mgmt_state.data_sha_len = req.data_sha_len; + memcpy(g_img_mgmt_state.data_sha, req.data_sha, req.data_sha_len); + memset(&g_img_mgmt_state.data_sha[req.data_sha_len], 0, + IMG_MGMT_DATA_SHA_LEN - req.data_sha_len); + +#if IMG_MGMT_LAZY_ERASE + /* setup for lazy sector by sector erase */ + g_img_mgmt_state.sector_id = -1; + g_img_mgmt_state.sector_end = 0; +#else + /* erase the entire req.size all at once */ + if (action.erase) { + rc = img_mgmt_impl_erase_image_data(0, req.size); + if (rc != 0) { + rc = MGMT_ERR_EUNKNOWN; + errstr = img_mgmt_err_str_flash_erase_failed; + goto end; + } + } +#endif + } else { + cmd_status_arg.status = IMG_MGMT_ID_UPLOAD_STATUS_ONGOING; + } + + /* Write the image data to flash. */ + if (req.data_len != 0) { +#if IMG_MGMT_LAZY_ERASE + /* erase as we cross sector boundaries */ + if (img_mgmt_impl_erase_if_needed(req.off, action.write_bytes) != 0) { + rc = MGMT_ERR_EUNKNOWN; + errstr = img_mgmt_err_str_flash_erase_failed; + goto end; + } +#endif + /* If this is the last chunk */ + if (g_img_mgmt_state.off + req.data_len == g_img_mgmt_state.size) { + last = true; + } + + rc = img_mgmt_impl_write_image_data(req.off, req.img_data, action.write_bytes, + last); + if (rc != 0) { + rc = MGMT_ERR_EUNKNOWN; + errstr = img_mgmt_err_str_flash_write_failed; + goto end; + } else { + g_img_mgmt_state.off += action.write_bytes; + if (g_img_mgmt_state.off == g_img_mgmt_state.size) { + /* Done */ + img_mgmt_dfu_pending(); + cmd_status_arg.status = IMG_MGMT_ID_UPLOAD_STATUS_COMPLETE; + g_img_mgmt_state.area_id = -1; + } + } + } +end: + + img_mgmt_upload_log(req.off == 0, g_img_mgmt_state.off == g_img_mgmt_state.size, rc); + mgmt_evt(MGMT_EVT_OP_CMD_STATUS, MGMT_GROUP_ID_IMAGE, IMG_MGMT_ID_UPLOAD, + &cmd_status_arg); + + if (rc != 0) { + img_mgmt_dfu_stopped(); + return img_mgmt_error_rsp(ctxt, rc, errstr); + } + + return img_mgmt_upload_good_rsp(ctxt); +} + +void +img_mgmt_dfu_stopped(void) +{ + if (img_mgmt_dfu_callbacks_fn && img_mgmt_dfu_callbacks_fn->dfu_stopped_cb) { + img_mgmt_dfu_callbacks_fn->dfu_stopped_cb(); + } +} + +void +img_mgmt_dfu_started(void) +{ + if (img_mgmt_dfu_callbacks_fn && img_mgmt_dfu_callbacks_fn->dfu_started_cb) { + img_mgmt_dfu_callbacks_fn->dfu_started_cb(); + } +} + +void +img_mgmt_dfu_pending(void) +{ + if (img_mgmt_dfu_callbacks_fn && img_mgmt_dfu_callbacks_fn->dfu_pending_cb) { + img_mgmt_dfu_callbacks_fn->dfu_pending_cb(); + } +} + +void +img_mgmt_dfu_confirmed(void) +{ + if (img_mgmt_dfu_callbacks_fn && img_mgmt_dfu_callbacks_fn->dfu_confirmed_cb) { + img_mgmt_dfu_callbacks_fn->dfu_confirmed_cb(); + } +} + +void +img_mgmt_set_upload_cb(img_mgmt_upload_fn cb, void *arg) +{ + img_mgmt_upload_cb = cb; + img_mgmt_upload_arg = arg; +} + +void +img_mgmt_register_callbacks(const struct img_mgmt_dfu_callbacks_t *cb_struct) +{ + img_mgmt_dfu_callbacks_fn = cb_struct; +} + +int +img_mgmt_my_version(struct image_version *ver) +{ + return img_mgmt_read_info(IMG_MGMT_BOOT_CURR_SLOT, ver, NULL, NULL); +} + +static const struct mgmt_handler img_mgmt_handlers[] = { + [IMG_MGMT_ID_STATE] = { + .mh_read = img_mgmt_state_read, + .mh_write = img_mgmt_state_write, + }, + [IMG_MGMT_ID_UPLOAD] = { + .mh_read = NULL, + .mh_write = img_mgmt_upload + }, + [IMG_MGMT_ID_ERASE] = { + .mh_read = NULL, + .mh_write = img_mgmt_erase + }, +}; + +static const struct mgmt_handler img_mgmt_handlers[]; + +#define IMG_MGMT_HANDLER_CNT ARRAY_SIZE(img_mgmt_handlers) + +static struct mgmt_group img_mgmt_group = { + .mg_handlers = (struct mgmt_handler *)img_mgmt_handlers, + .mg_handlers_count = IMG_MGMT_HANDLER_CNT, + .mg_group_id = MGMT_GROUP_ID_IMAGE, +}; + + +void +img_mgmt_register_group(void) +{ + mgmt_register_group(&img_mgmt_group); +} + +void +img_mgmt_unregister_group(void) +{ + mgmt_unregister_group(&img_mgmt_group); +} diff --git a/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/img_mgmt_priv.h b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/img_mgmt_priv.h new file mode 100644 index 000000000000..5885a30b3212 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/img_mgmt_priv.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef H_IMG_PRIV_ +#define H_IMG_PRIV_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Response to list: + * { + * "images":[ , ] + * } + * + * + * Request to boot to version: + * { + * "test": + * } + * + * + * Response to boot read: + * { + * "test":, + * "main":, + * "active": + * } + * + * + * Request to image upload: + * { + * "off":, + * "len": inspected when off = 0 + * "data": + * } + * + * + * Response to upload: + * { + * "off": + * } + * + * + * Request to image upload: + * { + * "off": + * "name": inspected when off = 0 + * "len": inspected when off = 0 + * "data": + * } + */ + +struct mgmt_ctxt; + +int img_mgmt_core_erase(struct mgmt_ctxt *ctxt); +int img_mgmt_core_list(struct mgmt_ctxt *ctxt); +int img_mgmt_core_load(struct mgmt_ctxt *ctxt); +int img_mgmt_find_by_hash(uint8_t *find, struct image_version *ver); +int img_mgmt_find_by_ver(struct image_version *find, uint8_t *hash); +int img_mgmt_state_read(struct mgmt_ctxt *ctxt); +int img_mgmt_state_write(struct mgmt_ctxt *njb); +int img_mgmt_ver_str(const struct image_version *ver, char *dst); + +#ifdef __cplusplus +} +#endif + +#endif /* __IMG_PRIV_H */ diff --git a/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/img_mgmt_state.c b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/img_mgmt_state.c new file mode 100644 index 000000000000..b310836aa6e1 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/img_mgmt_state.c @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "tinycbor/cbor.h" +#include "cborattr/cborattr.h" +#include "mgmt/mgmt.h" +#include "img_mgmt/img_mgmt.h" +#include "img_mgmt/image.h" +#include "img_mgmt_priv.h" +#include "img_mgmt/img_mgmt_impl.h" + +/** + * Collects information about the specified image slot. + */ +uint8_t +img_mgmt_state_flags(int query_slot) +{ + uint8_t flags; + int swap_type; + + flags = 0; + + /* Determine if this is is pending or confirmed (only applicable for + * unified images and loaders. + */ + swap_type = img_mgmt_impl_swap_type(query_slot); + switch (swap_type) { + case IMG_MGMT_SWAP_TYPE_NONE: + if (query_slot == IMG_MGMT_BOOT_CURR_SLOT) { + flags |= IMG_MGMT_STATE_F_CONFIRMED; + flags |= IMG_MGMT_STATE_F_ACTIVE; + } + break; + + case IMG_MGMT_SWAP_TYPE_TEST: + if (query_slot == IMG_MGMT_BOOT_CURR_SLOT) { + flags |= IMG_MGMT_STATE_F_CONFIRMED; + } else { + flags |= IMG_MGMT_STATE_F_PENDING; + } + break; + + case IMG_MGMT_SWAP_TYPE_PERM: + if (query_slot == IMG_MGMT_BOOT_CURR_SLOT) { + flags |= IMG_MGMT_STATE_F_CONFIRMED; + } else { + flags |= IMG_MGMT_STATE_F_PENDING | IMG_MGMT_STATE_F_PERMANENT; + } + break; + + case IMG_MGMT_SWAP_TYPE_REVERT: + if (query_slot == IMG_MGMT_BOOT_CURR_SLOT) { + flags |= IMG_MGMT_STATE_F_ACTIVE; + } else { + flags |= IMG_MGMT_STATE_F_CONFIRMED; + } + break; + } + + /* Slot 0 is always active. */ + /* XXX: The slot 0 assumption only holds when running from flash. */ + if (query_slot == IMG_MGMT_BOOT_CURR_SLOT) { + flags |= IMG_MGMT_STATE_F_ACTIVE; + } + + return flags; +} + +/** + * Indicates whether any image slot is pending (i.e., whether a test swap will + * happen on the next reboot. + */ +int +img_mgmt_state_any_pending(void) +{ + return img_mgmt_state_flags(0) & IMG_MGMT_STATE_F_PENDING || + img_mgmt_state_flags(1) & IMG_MGMT_STATE_F_PENDING; +} + +/** + * Indicates whether the specified slot has any flags. If no flags are set, + * the slot can be freely erased. + */ +int +img_mgmt_slot_in_use(int slot) +{ + uint8_t state_flags; + + state_flags = img_mgmt_state_flags(slot); + return state_flags & IMG_MGMT_STATE_F_ACTIVE || + state_flags & IMG_MGMT_STATE_F_CONFIRMED || + state_flags & IMG_MGMT_STATE_F_PENDING; +} + +/** + * Sets the pending flag for the specified image slot. That is, the system + * will swap to the specified image on the next reboot. If the permanent + * argument is specified, the system doesn't require a confirm after the swap + * occurs. + */ +int +img_mgmt_state_set_pending(int slot, int permanent) +{ + uint8_t hash[IMAGE_HASH_LEN]; + uint8_t state_flags; + const uint8_t *hashp; + int rc; + + state_flags = img_mgmt_state_flags(slot); + + /* Unconfirmed slots are always runable. A confirmed slot can only be + * run if it is a loader in a split image setup. + */ + if (state_flags & IMG_MGMT_STATE_F_CONFIRMED && slot != 0) { + rc = MGMT_ERR_EBADSTATE; + goto done; + } + + rc = img_mgmt_impl_write_pending(slot, permanent); + if (rc != 0) { + rc = MGMT_ERR_EUNKNOWN; + } + +done: + /* Log the image hash if we know it. */ + if (img_mgmt_read_info(slot, NULL, hash, NULL)) { + hashp = NULL; + } else { + hashp = hash; + } + + if (permanent) { + (void) img_mgmt_impl_log_confirm(rc, hashp); + } else { + (void) img_mgmt_impl_log_pending(rc, hashp); + } + + return rc; +} + +/** + * Confirms the current image state. Prevents a fallback from occurring on the + * next reboot if the active image is currently being tested. + */ +int +img_mgmt_state_confirm(void) +{ + int rc; + + /* Confirm disallowed if a test is pending. */ + if (img_mgmt_state_any_pending()) { + rc = MGMT_ERR_EBADSTATE; + goto err; + } + + rc = img_mgmt_impl_write_confirmed(); + if (rc != 0) { + rc = MGMT_ERR_EUNKNOWN; + } + + img_mgmt_dfu_confirmed(); +err: + return img_mgmt_impl_log_confirm(rc, NULL); +} + +/** + * Command handler: image state read + */ +int +img_mgmt_state_read(struct mgmt_ctxt *ctxt) +{ + char vers_str[IMG_MGMT_VER_MAX_STR_LEN]; + uint8_t hash[IMAGE_HASH_LEN]; /* SHA256 hash */ + struct image_version ver; + CborEncoder images; + CborEncoder image; + CborError err; + uint32_t flags; + uint8_t state_flags; + int rc; + int i; + + err = 0; + err |= cbor_encode_text_stringz(&ctxt->encoder, "images"); + + err |= cbor_encoder_create_array(&ctxt->encoder, &images, CborIndefiniteLength); + + for (i = 0; i < 2 * IMG_MGMT_UPDATABLE_IMAGE_NUMBER; i++) { + rc = img_mgmt_read_info(i, &ver, hash, &flags); + if (rc != 0) { + continue; + } + + state_flags = img_mgmt_state_flags(i); + + err |= cbor_encoder_create_map(&images, &image, CborIndefiniteLength); + +#if IMG_MGMT_UPDATABLE_IMAGE_NUMBER > 1 + err |= cbor_encode_text_stringz(&image, "image"); + err |= cbor_encode_int(&image, i >> 1); +#endif + err |= cbor_encode_text_stringz(&image, "slot"); + err |= cbor_encode_int(&image, i % 2); + + err |= cbor_encode_text_stringz(&image, "version"); + img_mgmt_ver_str(&ver, vers_str); + err |= cbor_encode_text_stringz(&image, vers_str); + + err |= cbor_encode_text_stringz(&image, "hash"); + err |= cbor_encode_byte_string(&image, hash, IMAGE_HASH_LEN); + + if (!IMG_MGMT_FRUGAL_LIST || !(flags & IMAGE_F_NON_BOOTABLE)) { + err |= cbor_encode_text_stringz(&image, "bootable"); + err |= cbor_encode_boolean(&image, !(flags & IMAGE_F_NON_BOOTABLE)); + } + + if (!IMG_MGMT_FRUGAL_LIST || (state_flags & IMG_MGMT_STATE_F_PENDING)) { + err |= cbor_encode_text_stringz(&image, "pending"); + err |= cbor_encode_boolean(&image, state_flags & IMG_MGMT_STATE_F_PENDING); + } + + if (!IMG_MGMT_FRUGAL_LIST || + (state_flags & IMG_MGMT_STATE_F_CONFIRMED)) { + err |= cbor_encode_text_stringz(&image, "confirmed"); + err |= cbor_encode_boolean(&image, + state_flags & IMG_MGMT_STATE_F_CONFIRMED); + } + + if (!IMG_MGMT_FRUGAL_LIST || (state_flags & IMG_MGMT_STATE_F_ACTIVE)) { + err |= cbor_encode_text_stringz(&image, "active"); + err |= cbor_encode_boolean(&image, state_flags & IMG_MGMT_STATE_F_ACTIVE); + } + + if (!IMG_MGMT_FRUGAL_LIST || + (state_flags & IMG_MGMT_STATE_F_PERMANENT)) { + err |= cbor_encode_text_stringz(&image, "permanent"); + err |= cbor_encode_boolean(&image, + state_flags & IMG_MGMT_STATE_F_PERMANENT); + } + + err |= cbor_encoder_close_container(&images, &image); + } + + err |= cbor_encoder_close_container(&ctxt->encoder, &images); + + /* splitStatus is always 0 so in frugal list it is not present at all */ + if (!IMG_MGMT_FRUGAL_LIST) { + err |= cbor_encode_text_stringz(&ctxt->encoder, "splitStatus"); + err |= cbor_encode_int(&ctxt->encoder, 0); + } + + if (err != 0) { + return MGMT_ERR_ENOMEM; + } + + return 0; +} + +/** + * Command handler: image state write + */ +int +img_mgmt_state_write(struct mgmt_ctxt *ctxt) +{ + /* + * We add 1 to the 32-byte hash buffer as _cbor_value_copy_string() adds + * a null character at the end of the buffer. + */ + uint8_t hash[IMAGE_HASH_LEN + 1]; + size_t hash_len; + bool confirm; + int slot; + int rc; + + const struct cbor_attr_t write_attr[] = { + [0] = { + .attribute = "hash", + .type = CborAttrByteStringType, + .addr.bytestring.data = hash, + .addr.bytestring.len = &hash_len, + .len = sizeof(hash), + }, + [1] = { + .attribute = "confirm", + .type = CborAttrBooleanType, + .addr.boolean = &confirm, + .dflt.boolean = false, + }, + [2] = { 0 }, + }; + + hash_len = 0; + rc = cbor_read_object(&ctxt->it, write_attr); + if (rc != 0) { + return MGMT_ERR_EINVAL; + } + + /* Determine which slot is being operated on. */ + if (hash_len == 0) { + if (confirm) { + slot = IMG_MGMT_BOOT_CURR_SLOT; + } else { + /* A 'test' without a hash is invalid. */ + return MGMT_ERR_EINVAL; + } + } else { + slot = img_mgmt_find_by_hash(hash, NULL); + if (slot < 0) { + return MGMT_ERR_EINVAL; + } + } + + if (slot == IMG_MGMT_BOOT_CURR_SLOT && confirm) { + /* Confirm current setup. */ + rc = img_mgmt_state_confirm(); + } else { + rc = img_mgmt_state_set_pending(slot, confirm); + } + if (rc != 0) { + return rc; + } + + /* Send the current image state in the response. */ + rc = img_mgmt_state_read(ctxt); + if (rc != 0) { + return rc; + } + + return 0; +} diff --git a/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/img_mgmt_util.c b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/img_mgmt_util.c new file mode 100644 index 000000000000..4c6ab5384139 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/img_mgmt_util.c @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "util/mcumgr_util.h" +#include "img_mgmt/image.h" +#include "img_mgmt/img_mgmt.h" + +int +img_mgmt_ver_str(const struct image_version *ver, char *dst) +{ + int off = 0; + + off += ull_to_s(ver->iv_major, INT_MAX, dst + off); + + dst[off++] = '.'; + off += ull_to_s(ver->iv_minor, INT_MAX, dst + off); + + dst[off++] = '.'; + off += ull_to_s(ver->iv_revision, INT_MAX, dst + off); + + if (ver->iv_build_num != 0) { + dst[off++] = '.'; + off += ull_to_s(ver->iv_build_num, INT_MAX, dst + off); + } + + return 0; +} diff --git a/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/zephyr_img_mgmt.c b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/zephyr_img_mgmt.c new file mode 100644 index 000000000000..6a12d259b298 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/zephyr_img_mgmt.c @@ -0,0 +1,710 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define LOG_MODULE_NAME mcumgr_flash_mgmt +#define LOG_LEVEL CONFIG_IMG_MANAGER_LOG_LEVEL +#include +LOG_MODULE_REGISTER(LOG_MODULE_NAME); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "img_mgmt_priv.h" + +BUILD_ASSERT(IMG_MGMT_UPDATABLE_IMAGE_NUMBER == 1 || + (IMG_MGMT_UPDATABLE_IMAGE_NUMBER == 2 && + FLASH_AREA_LABEL_EXISTS(image_2) && + FLASH_AREA_LABEL_EXISTS(image_3)), + "Missing partitions?"); + +static int +zephyr_img_mgmt_slot_to_image(int slot) +{ + switch (slot) { + case 0: + case 1: + return 0; +#if FLASH_AREA_LABEL_EXISTS(image_2) && FLASH_AREA_LABEL_EXISTS(image_3) + case 2: + case 3: + return 1; +#endif + default: + assert(0); + } + return 0; +} +/** + * Determines if the specified area of flash is completely unwritten. + */ +static int +zephyr_img_mgmt_flash_check_empty(uint8_t fa_id, bool *out_empty) +{ + const struct flash_area *fa; + uint32_t data[16]; + off_t addr; + off_t end; + int bytes_to_read; + int rc; + int i; + uint8_t erased_val; + uint32_t erased_val_32; + + rc = flash_area_open(fa_id, &fa); + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } + + assert(fa->fa_size % 4 == 0); + + erased_val = flash_area_erased_val(fa); + erased_val_32 = ERASED_VAL_32(erased_val); + + end = fa->fa_size; + for (addr = 0; addr < end; addr += sizeof(data)) { + if (end - addr < sizeof(data)) { + bytes_to_read = end - addr; + } else { + bytes_to_read = sizeof(data); + } + + rc = flash_area_read(fa, addr, data, bytes_to_read); + if (rc != 0) { + flash_area_close(fa); + return MGMT_ERR_EUNKNOWN; + } + + for (i = 0; i < bytes_to_read / 4; i++) { + if (data[i] != erased_val_32) { + *out_empty = false; + flash_area_close(fa); + return 0; + } + } + } + + *out_empty = true; + flash_area_close(fa); + return 0; +} + +/** + * Get flash_area ID for a image number; actually the slots are images + * for Zephyr, as slot 0 of image 0 is image_0, slot 0 of image 1 is + * image_2 and so on. The function treats slot numbers as absolute + * slot number starting at 0. + */ +static int +zephyr_img_mgmt_flash_area_id(int slot) +{ + uint8_t fa_id; + + switch (slot) { + case 0: + fa_id = FLASH_AREA_ID(image_0); + break; + + case 1: + fa_id = FLASH_AREA_ID(image_1); + break; + +#if FLASH_AREA_LABEL_EXISTS(image_2) + case 2: + fa_id = FLASH_AREA_ID(image_2); + break; +#endif + +#if FLASH_AREA_LABEL_EXISTS(image_3) + case 3: + fa_id = FLASH_AREA_ID(image_3); + break; +#endif + + default: + fa_id = -1; + break; + } + + return fa_id; +} + +#if IMG_MGMT_UPDATABLE_IMAGE_NUMBER == 1 +/** + * In normal operation this function will select between first two slot + * (in reality it just checks whether second slot can be used), ignoring the + * slot parameter. + * When CONFIG_IMG_MGMT_DIRECT_IMAGE_UPLOAD is defined it will check if given + * slot is available, and allowed, for DFU; providing 0 as a parameter means + * find any unused and non-active available (auto-select); any other positive + * value is direct (slot + 1) to be used; if checks are positive, then area + * ID is returned, -1 is returned otherwise. + * Note that auto-selection is performed only between two two first slots. + */ +static int +img_mgmt_get_unused_slot_area_id(int slot) +{ +#if defined(CONFIG_IMG_MGMT_DIRECT_IMAGE_UPLOAD) + slot--; + if (slot < -1) { + return -1; + } else if (slot == -1) { +#endif + /* + * Auto select slot; note that this is performed only between two first + * slots, at this point, which will require fix when Direct-XIP, which + * may support more slots, gets support within Zephyr. + */ + for (slot = 0; slot < 2; slot++) { + if (img_mgmt_slot_in_use(slot) == 0) { + int area_id = zephyr_img_mgmt_flash_area_id(slot); + + if (area_id >= 0) { + return area_id; + } + } + } + return -1; +#if defined(CONFIG_IMG_MGMT_DIRECT_IMAGE_UPLOAD) + } + /* + * Direct selection; the first two slots are checked for being available + * and unused; the all other slots are just checked for availability. + */ + if (slot < 2) { + slot = img_mgmt_slot_in_use(slot) == 0 ? slot : -1; + } + + /* Return area ID for the slot or -1 */ + return slot != -1 ? zephyr_img_mgmt_flash_area_id(slot) : -1; +#endif +} + +#elif IMG_MGMT_UPDATABLE_IMAGE_NUMBER == 2 +static int +img_mgmt_get_unused_slot_area_id(int image) +{ + int area_id = -1; + + if (image == 0 || image == -1) { + if (img_mgmt_slot_in_use(1) == 0) { + area_id = zephyr_img_mgmt_flash_area_id(1); + } + } else if (image == 1) { + area_id = zephyr_img_mgmt_flash_area_id(3); + } + + return area_id; +} +#else +#error "Unsupported number of images" +#endif + +/** + * Compares two image version numbers in a semver-compatible way. + * + * @param a The first version to compare. + * @param b The second version to compare. + * + * @return -1 if a < b + * @return 0 if a = b + * @return 1 if a > b + */ +static int +img_mgmt_vercmp(const struct image_version *a, const struct image_version *b) +{ + if (a->iv_major < b->iv_major) { + return -1; + } else if (a->iv_major > b->iv_major) { + return 1; + } + + if (a->iv_minor < b->iv_minor) { + return -1; + } else if (a->iv_minor > b->iv_minor) { + return 1; + } + + if (a->iv_revision < b->iv_revision) { + return -1; + } else if (a->iv_revision > b->iv_revision) { + return 1; + } + + /* Note: For semver compatibility, don't compare the 32-bit build num. */ + + return 0; +} + +int +img_mgmt_impl_erase_slot(void) +{ + bool empty; + int rc, best_id; + + /* Select any non-active, unused slot */ + best_id = img_mgmt_get_unused_slot_area_id(-1); + if (best_id < 0) { + return MGMT_ERR_ENOENT; + } + rc = zephyr_img_mgmt_flash_check_empty(best_id, &empty); + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } + + if (!empty) { + rc = boot_erase_img_bank(best_id); + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } + } + + return 0; +} + +int +img_mgmt_impl_write_pending(int slot, bool permanent) +{ + int rc; + + if (slot != 1 && !(IMG_MGMT_UPDATABLE_IMAGE_NUMBER == 2 && slot == 3)) { + return MGMT_ERR_EINVAL; + } + + rc = boot_request_upgrade_multi(zephyr_img_mgmt_slot_to_image(slot), permanent); + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } + + return 0; +} + +int +img_mgmt_impl_write_confirmed(void) +{ + int rc; + + rc = boot_write_img_confirmed(); + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } + + return 0; +} + +int +img_mgmt_impl_read(int slot, unsigned int offset, void *dst, + unsigned int num_bytes) +{ + const struct flash_area *fa; + int rc; + int area_id = zephyr_img_mgmt_flash_area_id(slot); + + if (area_id < 0) { + return MGMT_ERR_EUNKNOWN; + } + + rc = flash_area_open(area_id, &fa); + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } + + rc = flash_area_read(fa, offset, dst, num_bytes); + flash_area_close(fa); + + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } + + return 0; +} + +/* + * The alloc_ctx and free_ctx are specifically provided for + * the img_mgmt_impl_write_image_data to allocate/free single flash_img_context + * type buffer. + * When heap is enabled these functions will operate on heap; when heap is not + * allocated the alloc_ctx just returns pointer to static, global life-time + * variable, and free_ctx does nothing. + * CONFIG_HEAP_MEM_POOL_SIZE is C preprocessor literal. + */ +static inline struct flash_img_context *alloc_ctx(void) +{ + struct flash_img_context *ctx = NULL; + + if (CONFIG_HEAP_MEM_POOL_SIZE > 0) { + ctx = k_malloc(sizeof(*ctx)); + } else { + static struct flash_img_context stcx; + + ctx = &stcx; + } + return ctx; +} + +static inline void free_ctx(struct flash_img_context *ctx) +{ + if (CONFIG_HEAP_MEM_POOL_SIZE > 0) { + k_free(ctx); + } +} + +int +img_mgmt_impl_write_image_data(unsigned int offset, const void *data, unsigned int num_bytes, + bool last) +{ + int rc = 0; + static struct flash_img_context *ctx; + + if (CONFIG_HEAP_MEM_POOL_SIZE > 0 && offset != 0 && ctx == NULL) { + return MGMT_ERR_EUNKNOWN; + } + + if (offset == 0) { + if (ctx == NULL) { + ctx = alloc_ctx(); + + if (ctx == NULL) { + rc = MGMT_ERR_ENOMEM; + goto out; + } + } + + rc = flash_img_init_id(ctx, g_img_mgmt_state.area_id); + + if (rc != 0) { + rc = MGMT_ERR_EUNKNOWN; + goto out; + } + } + + if (offset != ctx->stream.bytes_written + ctx->stream.buf_bytes) { + rc = MGMT_ERR_EUNKNOWN; + goto out; + } + + /* Cast away const. */ + rc = flash_img_buffered_write(ctx, (void *)data, num_bytes, last); + if (rc != 0) { + rc = MGMT_ERR_EUNKNOWN; + goto out; + } + +out: + if (CONFIG_HEAP_MEM_POOL_SIZE > 0 && (last || rc != 0)) { + k_free(ctx); + ctx = NULL; + } + + return rc; +} + +int +img_mgmt_impl_erase_image_data(unsigned int off, unsigned int num_bytes) +{ + const struct flash_area *fa; + int rc; + + if (off != 0) { + rc = MGMT_ERR_EINVAL; + goto end; + } + + rc = flash_area_open(g_img_mgmt_state.area_id, &fa); + if (rc != 0) { + LOG_ERR("Can't bind to the flash area (err %d)", rc); + rc = MGMT_ERR_EUNKNOWN; + goto end; + } + + /* align requested erase size to the erase-block-size */ + const struct device *dev = flash_area_get_device(fa); + struct flash_pages_info page; + off_t page_offset = fa->fa_off + num_bytes - 1; + + rc = flash_get_page_info_by_offs(dev, page_offset, &page); + if (rc != 0) { + LOG_ERR("bad offset (0x%lx)", (long)page_offset); + rc = MGMT_ERR_EUNKNOWN; + goto end_fa; + } + + size_t erase_size = page.start_offset + page.size - fa->fa_off; + + rc = flash_area_erase(fa, 0, erase_size); + + if (rc != 0) { + LOG_ERR("image slot erase of 0x%zx bytes failed (err %d)", erase_size, + rc); + rc = MGMT_ERR_EUNKNOWN; + goto end_fa; + } + + LOG_INF("Erased 0x%zx bytes of image slot", erase_size); + + /* erase the image trailer area if it was not erased */ + off = BOOT_TRAILER_IMG_STATUS_OFFS(fa); + if (off >= erase_size) { + rc = flash_get_page_info_by_offs(dev, fa->fa_off + off, &page); + + off = page.start_offset - fa->fa_off; + erase_size = fa->fa_size - off; + + rc = flash_area_erase(fa, off, erase_size); + if (rc != 0) { + LOG_ERR("image slot trailer erase of 0x%zx bytes failed (err %d)", + erase_size, rc); + rc = MGMT_ERR_EUNKNOWN; + goto end_fa; + } + + LOG_INF("Erased 0x%zx bytes of image slot trailer", erase_size); + } + + rc = 0; + +end_fa: + flash_area_close(fa); +end: + return rc; +} + +#if IMG_MGMT_LAZY_ERASE +int img_mgmt_impl_erase_if_needed(uint32_t off, uint32_t len) +{ + /* This is done internally to the flash_img API. */ + return 0; +} +#endif + +int +img_mgmt_impl_swap_type(int slot) +{ + int image = zephyr_img_mgmt_slot_to_image(slot); + + switch (mcuboot_swap_type_multi(image)) { + case BOOT_SWAP_TYPE_NONE: + return IMG_MGMT_SWAP_TYPE_NONE; + case BOOT_SWAP_TYPE_TEST: + return IMG_MGMT_SWAP_TYPE_TEST; + case BOOT_SWAP_TYPE_PERM: + return IMG_MGMT_SWAP_TYPE_PERM; + case BOOT_SWAP_TYPE_REVERT: + return IMG_MGMT_SWAP_TYPE_REVERT; + default: + assert(0); + return IMG_MGMT_SWAP_TYPE_NONE; + } +} + +/** + * Verifies an upload request and indicates the actions that should be taken + * during processing of the request. This is a "read only" function in the + * sense that it doesn't write anything to flash and doesn't modify any global + * variables. + * + * @param req The upload request to inspect. + * @param action On success, gets populated with information about how to process + * the request. + * + * @return 0 if processing should occur; A MGMT_ERR code if an error response should be sent + * instead. + */ +int +img_mgmt_impl_upload_inspect(const struct img_mgmt_upload_req *req, + struct img_mgmt_upload_action *action, const char **errstr) +{ + const struct image_header *hdr; + const struct flash_area *fa; + struct image_version cur_ver; + uint8_t rem_bytes; + bool empty; + int rc; + + memset(action, 0, sizeof(*action)); + + if (req->off == -1) { + /* Request did not include an `off` field. */ + *errstr = img_mgmt_err_str_hdr_malformed; + return MGMT_ERR_EINVAL; + } + + if (req->off == 0) { + /* First upload chunk. */ + if (req->data_len < sizeof(struct image_header)) { + /* Image header is the first thing in the image */ + *errstr = img_mgmt_err_str_hdr_malformed; + return MGMT_ERR_EINVAL; + } + + if (req->size == -1) { + /* Request did not include a `len` field. */ + *errstr = img_mgmt_err_str_hdr_malformed; + return MGMT_ERR_EINVAL; + } + action->size = req->size; + + hdr = (struct image_header *)req->img_data; + if (hdr->ih_magic != IMAGE_MAGIC) { + *errstr = img_mgmt_err_str_magic_mismatch; + return MGMT_ERR_EINVAL; + } + + if (req->data_sha_len > IMG_MGMT_DATA_SHA_LEN) { + return MGMT_ERR_EINVAL; + } + + /* + * If request includes proper data hash we can check whether there is + * upload in progress (interrupted due to e.g. link disconnection) with + * the same data hash so we can just resume it by simply including + * current upload offset in response. + */ + if ((req->data_sha_len > 0) && (g_img_mgmt_state.area_id != -1)) { + if ((g_img_mgmt_state.data_sha_len == req->data_sha_len) && + !memcmp(g_img_mgmt_state.data_sha, req->data_sha, req->data_sha_len)) { + return 0; + } + } + + action->area_id = img_mgmt_get_unused_slot_area_id(req->image); + if (action->area_id < 0) { + /* No slot where to upload! */ + *errstr = img_mgmt_err_str_no_slot; + return MGMT_ERR_ENOENT; + } + + /* + * If request includes proper data hash we can check whether there is + * upload in progress (interrupted due to e.g. link disconnection) with + * the same data hash so we can just resume it by simply including + * current upload offset in response. + */ + if ((req->data_sha_len > 0) && (g_img_mgmt_state.area_id != -1)) { + if ((g_img_mgmt_state.data_sha_len == req->data_sha_len) && + !memcmp(g_img_mgmt_state.data_sha, req->data_sha, req->data_sha_len)) { + return 0; + } + } + + action->area_id = img_mgmt_get_unused_slot_area_id(req->image - 1); + if (action->area_id < 0) { + /* No slot where to upload! */ + *errstr = img_mgmt_err_str_no_slot; + return MGMT_ERR_ENOMEM; + } + +#if defined(CONFIG_IMG_MGMT_REJECT_DIRECT_XIP_MISMATCHED_SLOT) + if (hdr->ih_flags & IMAGE_F_ROM_FIXED_ADDR) { + rc = flash_area_open(action->area_id, &fa); + if (rc) { + *errstr = img_mgmt_err_str_flash_open_failed; + return MGMT_ERR_EUNKNOWN; + } + + if (fa->fa_off != hdr->ih_load_addr) { + *errstr = img_mgmt_err_str_image_bad_flash_addr; + flash_area_close(fa); + return MGMT_ERR_EINVAL; + } + + flash_area_close(fa); + } +#endif + + + if (req->upgrade) { + /* User specified upgrade-only. Make sure new image version is + * greater than that of the currently running image. + */ + rc = img_mgmt_my_version(&cur_ver); + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } + + if (img_mgmt_vercmp(&cur_ver, &hdr->ih_ver) >= 0) { + *errstr = img_mgmt_err_str_downgrade; + return MGMT_ERR_EBADSTATE; + } + } + +#if IMG_MGMT_LAZY_ERASE + (void) empty; +#else + rc = zephyr_img_mgmt_flash_check_empty(action->area_id, &empty); + if (rc) { + return MGMT_ERR_EUNKNOWN; + } + + action->erase = !empty; +#endif + } else { + /* Continuation of upload. */ + action->area_id = g_img_mgmt_state.area_id; + action->size = g_img_mgmt_state.size; + + if (req->off != g_img_mgmt_state.off) { + /* + * Invalid offset. Drop the data, and respond with the offset we're + * expecting data for. + */ + return 0; + } + } + + /* Calculate size of flash write. */ + action->write_bytes = req->data_len; + if (req->off + req->data_len < action->size) { + /* + * Respect flash write alignment if not in the last block + */ + rc = flash_area_open(action->area_id, &fa); + if (rc) { + *errstr = img_mgmt_err_str_flash_open_failed; + return MGMT_ERR_EUNKNOWN; + } + + rem_bytes = req->data_len % flash_area_align(fa); + flash_area_close(fa); + + if (rem_bytes) { + action->write_bytes -= rem_bytes; + } + } + + action->proceed = true; + return 0; +} + +int +img_mgmt_impl_erased_val(int slot, uint8_t *erased_val) +{ + const struct flash_area *fa; + int rc; + int area_id = zephyr_img_mgmt_flash_area_id(slot); + + if (area_id < 0) { + return MGMT_ERR_EUNKNOWN; + } + + rc = flash_area_open(area_id, &fa); + if (rc != 0) { + return MGMT_ERR_EUNKNOWN; + } + + *erased_val = flash_area_erased_val(fa); + flash_area_close(fa); + + return 0; +} diff --git a/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/zephyr_img_mgmt_log.c b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/zephyr_img_mgmt_log.c new file mode 100644 index 000000000000..c4afd089a49f --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/img_mgmt/src/zephyr_img_mgmt_log.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +/** + * Log event types (all events are CBOR-encoded): + * + * upstart: + * When: upon receiving an upload request with an offset of 0. + * Structure: + * { + * "ev": "upstart", + * "rc": + * } + * + * updone: + * When: upon receiving an upload request containing the final chunk of an + * image OR a failed upload request with a non-zero offset. + * Structure: + * { + * "ev": "updone", + * "rc": + * "hs": (only present on success) + * } + * + * pend: + * When: upon receiving a non-permanent `set-pending` request. + * Structure: + * { + * "ev": "pend", + * "rc": , + * "hs": + * } + * + * conf: + * When: upon receiving a `confirm` request OR a permanent `set-pending` + * request. + * Structure: + * { + * "ev": "conf", + * "rc": , + * "hs": (only present for `set-pending`) + * } + */ + +#define IMG_MGMT_LOG_EV_UPSTART "upstart" +#define IMG_MGMT_LOG_EV_UPDONE "updone" +#define IMG_MGMT_LOG_EV_PEND "pend" +#define IMG_MGMT_LOG_EV_CONF "conf" + +int +img_mgmt_impl_log_upload_start(int status) +{ + return 0; +} + +int +img_mgmt_impl_log_upload_done(int status, const uint8_t *hash) +{ + return 0; +} + +int +img_mgmt_impl_log_pending(int status, const uint8_t *hash) +{ + return 0; +} + +int +img_mgmt_impl_log_confirm(int status, const uint8_t *hash) +{ + return 0; +} diff --git a/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/CMakeLists.txt b/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/CMakeLists.txt new file mode 100644 index 000000000000..21e41de0e49f --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/CMakeLists.txt @@ -0,0 +1,14 @@ +# +# Copyright (c) 2018-2021 mcumgr authors +# +# SPDX-License-Identifier: Apache-2.0 +# + +target_include_directories(MCUMGR INTERFACE + include +) + +zephyr_library_sources( + src/zephyr_os_mgmt.c + src/os_mgmt.c +) diff --git a/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/include/os_mgmt/os_mgmt.h b/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/include/os_mgmt/os_mgmt.h new file mode 100644 index 000000000000..b30a80c1855e --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/include/os_mgmt/os_mgmt.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef H_OS_MGMT_ +#define H_OS_MGMT_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Command IDs for OS management group. + */ +#define OS_MGMT_ID_ECHO 0 +#define OS_MGMT_ID_CONS_ECHO_CTRL 1 +#define OS_MGMT_ID_TASKSTAT 2 +#define OS_MGMT_ID_MPSTAT 3 +#define OS_MGMT_ID_DATETIME_STR 4 +#define OS_MGMT_ID_RESET 5 + +#define OS_MGMT_TASK_NAME_LEN 32 + +struct os_mgmt_task_info { + uint8_t oti_prio; + uint8_t oti_taskid; + uint8_t oti_state; + uint16_t oti_stkusage; + uint16_t oti_stksize; + uint32_t oti_cswcnt; + uint32_t oti_runtime; + uint32_t oti_last_checkin; + uint32_t oti_next_checkin; + + char oti_name[OS_MGMT_TASK_NAME_LEN]; +}; + +/** + * @brief Registers the OS management command handler group. + */ +void os_mgmt_register_group(void); + +#ifdef __cplusplus +} +#endif + +#endif /* H_OS_MGMT_ */ diff --git a/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/include/os_mgmt/os_mgmt_config.h b/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/include/os_mgmt/os_mgmt_config.h new file mode 100644 index 000000000000..ce4e0e8aa09e --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/include/os_mgmt/os_mgmt_config.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef H_OS_MGMT_CONFIG_ +#define H_OS_MGMT_CONFIG_ + +#define OS_MGMT_RESET_MS CONFIG_OS_MGMT_RESET_MS +#define OS_MGMT_TASKSTAT CONFIG_OS_MGMT_TASKSTAT +#define OS_MGMT_ECHO CONFIG_OS_MGMT_ECHO + +#endif diff --git a/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/include/os_mgmt/os_mgmt_impl.h b/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/include/os_mgmt/os_mgmt_impl.h new file mode 100644 index 000000000000..de6f10a34345 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/include/os_mgmt/os_mgmt_impl.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Declares implementation-specific functions required by OS management. + * The default stubs can be overridden with functions that are compatible with the host OS. + */ + +#ifndef H_OS_MGMT_IMPL_ +#define H_OS_MGMT_IMPL_ + +#ifdef __cplusplus +extern "C" { +#endif + +struct os_mgmt_task_info; + +/** + * @brief Retrieves information about the specified task. + * + * @param idx The index of the task to query. + * @param out_info On success, the requested information gets written here. + * + * @return 0 on success; + * MGMT_ERR_ENOENT if no such task exists; + * Other MGMT_ERR_[...] code on failure. + */ +int os_mgmt_impl_task_info(int idx, struct os_mgmt_task_info *out_info); + +/** + * @brief Schedules a near-immediate system reset. There must be a slight + * delay before the reset occurs to allow time for the mgmt response to be + * delivered. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +int os_mgmt_impl_reset(unsigned int delay_ms); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/src/os_mgmt.c b/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/src/os_mgmt.c new file mode 100644 index 000000000000..b9f4c6ebb636 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/src/os_mgmt.c @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "tinycbor/cbor.h" +#include "cborattr/cborattr.h" +#include "mgmt/mgmt.h" +#include "os_mgmt/os_mgmt.h" +#include "os_mgmt/os_mgmt_impl.h" +#include "os_mgmt/os_mgmt_config.h" + +/** + * Command handler: os echo + */ +#if OS_MGMT_ECHO +static int +os_mgmt_echo(struct mgmt_ctxt *ctxt) +{ + char echo_buf[128]; + CborError err; + + const struct cbor_attr_t attrs[2] = { + [0] = { + .attribute = "d", + .type = CborAttrTextStringType, + .addr.string = echo_buf, + .nodefault = 1, + .len = sizeof(echo_buf), + }, + [1] = { + .attribute = NULL + } + }; + + echo_buf[0] = '\0'; + + err = cbor_read_object(&ctxt->it, attrs); + if (err != 0) { + return MGMT_ERR_EINVAL; + } + + err |= cbor_encode_text_stringz(&ctxt->encoder, "r"); + err |= cbor_encode_text_string(&ctxt->encoder, echo_buf, strlen(echo_buf)); + + if (err != 0) { + return MGMT_ERR_ENOMEM; + } + + return 0; +} +#endif + +#if OS_MGMT_TASKSTAT +/** + * Encodes a single taskstat entry. + */ +static int +os_mgmt_taskstat_encode_one(struct CborEncoder *encoder, const struct os_mgmt_task_info *task_info) +{ + CborEncoder task_map; + CborError err; + + err = 0; + err |= cbor_encode_text_stringz(encoder, task_info->oti_name); + err |= cbor_encoder_create_map(encoder, &task_map, CborIndefiniteLength); + err |= cbor_encode_text_stringz(&task_map, "prio"); + err |= cbor_encode_uint(&task_map, task_info->oti_prio); + err |= cbor_encode_text_stringz(&task_map, "tid"); + err |= cbor_encode_uint(&task_map, task_info->oti_taskid); + err |= cbor_encode_text_stringz(&task_map, "state"); + err |= cbor_encode_uint(&task_map, task_info->oti_state); + err |= cbor_encode_text_stringz(&task_map, "stkuse"); + err |= cbor_encode_uint(&task_map, task_info->oti_stkusage); + err |= cbor_encode_text_stringz(&task_map, "stksiz"); + err |= cbor_encode_uint(&task_map, task_info->oti_stksize); + err |= cbor_encode_text_stringz(&task_map, "cswcnt"); + err |= cbor_encode_uint(&task_map, task_info->oti_cswcnt); + err |= cbor_encode_text_stringz(&task_map, "runtime"); + err |= cbor_encode_uint(&task_map, task_info->oti_runtime); + err |= cbor_encode_text_stringz(&task_map, "last_checkin"); + err |= cbor_encode_uint(&task_map, task_info->oti_last_checkin); + err |= cbor_encode_text_stringz(&task_map, "next_checkin"); + err |= cbor_encode_uint(&task_map, task_info->oti_next_checkin); + err |= cbor_encoder_close_container(encoder, &task_map); + + if (err != 0) { + return MGMT_ERR_ENOMEM; + } + + return 0; +} + +/** + * Command handler: os taskstat + */ +static int +os_mgmt_taskstat_read(struct mgmt_ctxt *ctxt) +{ + struct os_mgmt_task_info task_info; + struct CborEncoder tasks_map; + CborError err; + int task_idx; + int rc; + + err = 0; + err |= cbor_encode_text_stringz(&ctxt->encoder, "tasks"); + err |= cbor_encoder_create_map(&ctxt->encoder, &tasks_map, CborIndefiniteLength); + if (err != 0) { + return MGMT_ERR_ENOMEM; + } + + /* Iterate the list of tasks, encoding each. */ + for (task_idx = 0; ; task_idx++) { + rc = os_mgmt_impl_task_info(task_idx, &task_info); + if (rc == MGMT_ERR_ENOENT) { + /* No more tasks to encode. */ + break; + } else if (rc != 0) { + return rc; + } + + rc = os_mgmt_taskstat_encode_one(&tasks_map, &task_info); + if (rc != 0) { + cbor_encoder_close_container(&ctxt->encoder, &tasks_map); + return rc; + } + } + + err = cbor_encoder_close_container(&ctxt->encoder, &tasks_map); + if (err != 0) { + return MGMT_ERR_ENOMEM; + } + + return 0; +} +#endif + +/** + * Command handler: os reset + */ +static int +os_mgmt_reset(struct mgmt_ctxt *ctxt) +{ + return os_mgmt_impl_reset(OS_MGMT_RESET_MS); +} + +static const struct mgmt_handler os_mgmt_group_handlers[] = { +#if OS_MGMT_ECHO + [OS_MGMT_ID_ECHO] = { + os_mgmt_echo, os_mgmt_echo + }, +#endif +#if OS_MGMT_TASKSTAT + [OS_MGMT_ID_TASKSTAT] = { + os_mgmt_taskstat_read, NULL + }, +#endif + [OS_MGMT_ID_RESET] = { + NULL, os_mgmt_reset + }, +}; + +#define OS_MGMT_GROUP_SZ ARRAY_SIZE(os_mgmt_group_handlers) + +static struct mgmt_group os_mgmt_group = { + .mg_handlers = os_mgmt_group_handlers, + .mg_handlers_count = OS_MGMT_GROUP_SZ, + .mg_group_id = MGMT_GROUP_ID_OS, +}; + + +void +os_mgmt_register_group(void) +{ + mgmt_register_group(&os_mgmt_group); +} + +void +os_mgmt_module_init(void) +{ + os_mgmt_register_group(); +} diff --git a/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/src/zephyr_os_mgmt.c b/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/src/zephyr_os_mgmt.c new file mode 100644 index 000000000000..d97113b8b1f9 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/os_mgmt/src/zephyr_os_mgmt.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static void zephyr_os_mgmt_reset_cb(struct k_timer *timer); +static void zephyr_os_mgmt_reset_work_handler(struct k_work *work); + +static K_TIMER_DEFINE(zephyr_os_mgmt_reset_timer, zephyr_os_mgmt_reset_cb, NULL); + +K_WORK_DEFINE(zephyr_os_mgmt_reset_work, zephyr_os_mgmt_reset_work_handler); + +#ifdef CONFIG_THREAD_MONITOR +static const struct k_thread * +zephyr_os_mgmt_task_at(int idx) +{ + const struct k_thread *thread; + int i; + + thread = SYS_THREAD_MONITOR_HEAD; + for (i = 0; i < idx; i++) { + if (thread == NULL) { + break; + } + thread = SYS_THREAD_MONITOR_NEXT(thread); + } + + return thread; +} + +int +os_mgmt_impl_task_info(int idx, struct os_mgmt_task_info *out_info) +{ + const struct k_thread *thread; +#if defined(CONFIG_INIT_STACKS) && defined(CONFIG_THREAD_STACK_INFO) + size_t unused; +#endif + + thread = zephyr_os_mgmt_task_at(idx); + if (thread == NULL) { + return MGMT_ERR_ENOENT; + } + + *out_info = (struct os_mgmt_task_info){ 0 }; + +#ifdef CONFIG_THREAD_NAME + strncpy(out_info->oti_name, thread->name, OS_MGMT_TASK_NAME_LEN-1); + out_info->oti_name[OS_MGMT_TASK_NAME_LEN - 1] = '\0'; +#else + ll_to_s(thread->base.prio, sizeof(out_info->oti_name), out_info->oti_name); +#endif + + out_info->oti_prio = thread->base.prio; + out_info->oti_taskid = idx; + out_info->oti_state = thread->base.thread_state; +#ifdef CONFIG_THREAD_STACK_INFO + out_info->oti_stksize = thread->stack_info.size / 4; +#ifdef CONFIG_INIT_STACKS + if (k_thread_stack_space_get(thread, &unused) == 0) { + out_info->oti_stkusage = (thread->stack_info.size - unused) / 4; + } else { + out_info->oti_stkusage = 0; + } +#endif +#endif + + return 0; +} +#endif /* CONFIG_THREAD_MONITOR */ + +static void +zephyr_os_mgmt_reset_work_handler(struct k_work *work) +{ + sys_reboot(SYS_REBOOT_WARM); +} + +static void +zephyr_os_mgmt_reset_cb(struct k_timer *timer) +{ + /* Reboot the system from the system workqueue thread. */ + k_work_submit(&zephyr_os_mgmt_reset_work); +} + +int +os_mgmt_impl_reset(unsigned int delay_ms) +{ + k_timer_start(&zephyr_os_mgmt_reset_timer, K_MSEC(delay_ms), K_NO_WAIT); + return 0; +} diff --git a/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/CMakeLists.txt b/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/CMakeLists.txt new file mode 100644 index 000000000000..172d52d21967 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/CMakeLists.txt @@ -0,0 +1,14 @@ +# +# Copyright (c) 2018-2021 mcumgr authors +# +# SPDX-License-Identifier: Apache-2.0 +# + +target_include_directories(MCUMGR INTERFACE + include +) + +zephyr_library_sources( + src/zephyr_shell_mgmt.c + src/shell_mgmt.c +) diff --git a/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/include/shell_mgmt/shell_mgmt.h b/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/include/shell_mgmt/shell_mgmt.h new file mode 100644 index 000000000000..0d626f0b04d9 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/include/shell_mgmt/shell_mgmt.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef H_SHELL_MGMT_ +#define H_SHELL_MGMT_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Command IDs for shell management group. + */ +#define SHELL_MGMT_ID_EXEC 0 + +/** + * @brief Registers the shell management command handler group. + */ +void +shell_mgmt_register_group(void); + +#ifdef __cplusplus +} +#endif + +#endif /* H_SHELL_MGMT_ */ diff --git a/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/include/shell_mgmt/shell_mgmt_config.h b/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/include/shell_mgmt/shell_mgmt_config.h new file mode 100644 index 000000000000..4e3df0038c2e --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/include/shell_mgmt/shell_mgmt_config.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef H_SHELL_MGMT_CONFIG_ +#define H_SHELL_MGMT_CONFIG_ + +#define SHELL_MGMT_MAX_LINE_LEN CONFIG_SHELL_CMD_BUFF_SIZE +#define SHELL_MGMT_MAX_ARGC CONFIG_SHELL_ARGC_MAX + +#endif diff --git a/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/include/shell_mgmt/shell_mgmt_impl.h b/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/include/shell_mgmt/shell_mgmt_impl.h new file mode 100644 index 000000000000..1d545e242805 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/include/shell_mgmt/shell_mgmt_impl.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Declares implementation-specific functions required by shell + * management. The default stubs can be overridden with functions that + * are compatible with the host OS. + */ + +#ifndef H_SHELL_MGMT_IMPL_ +#define H_SHELL_MGMT_IMPL_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Execute `line` as a shell command + * + * @param line : shell command to be executed + * @return int : 0 on success, -errno otherwire + */ +int +shell_mgmt_impl_exec(const char *line); + +/** + * @brief Capture the output of the shell + * + * @return const char* : shell output. This is not the return code, it is + * the string output of the shell command if it exists. If the shell provided no + * output, this will be an empty string + */ +const char * +shell_mgmt_impl_get_output(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/src/shell_mgmt.c b/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/src/shell_mgmt.c new file mode 100644 index 000000000000..377b15a9b9eb --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/src/shell_mgmt.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "mgmt/mgmt.h" +#include "cborattr/cborattr.h" +#include "shell_mgmt/shell_mgmt.h" +#include "shell_mgmt/shell_mgmt_impl.h" +#include "shell_mgmt/shell_mgmt_config.h" + +/** + * Command handler: shell exec + */ +static int +shell_mgmt_exec(struct mgmt_ctxt *cb) +{ + static char line[SHELL_MGMT_MAX_LINE_LEN + 1] = {0}; + CborEncoder str_encoder; + CborError err; + int rc; + char *argv[SHELL_MGMT_MAX_ARGC]; + int argc; + + const struct cbor_attr_t attrs[] = { + { + .attribute = "argv", + .type = CborAttrArrayType, + .addr.array = { + .element_type = CborAttrTextStringType, + .arr.strings.ptrs = argv, + .arr.strings.store = line, + .arr.strings.storelen = sizeof(line), + .count = &argc, + .maxlen = ARRAY_SIZE(argv), + }, + }, + { 0 }, + }; + + err = cbor_read_object(&cb->it, attrs); + if (err != 0) { + return MGMT_ERR_EINVAL; + } + + /* Key="o"; value= */ + err |= cbor_encode_text_stringz(&cb->encoder, "o"); + err |= cbor_encoder_create_indef_text_string(&cb->encoder, &str_encoder); + + rc = shell_mgmt_impl_exec(line); + + err |= cbor_encode_text_stringz(&str_encoder, + shell_mgmt_impl_get_output()); + + err |= cbor_encoder_close_container(&cb->encoder, &str_encoder); + + /* Key="rc"; value= */ + err |= cbor_encode_text_stringz(&cb->encoder, "rc"); + err |= cbor_encode_int(&cb->encoder, rc); + + if (err != 0) { + return MGMT_ERR_ENOMEM; + } + + return 0; +} + +static struct mgmt_handler shell_mgmt_handlers[] = { + [SHELL_MGMT_ID_EXEC] = { NULL, shell_mgmt_exec }, +}; + +#define SHELL_MGMT_HANDLER_CNT ARRAY_SIZE(shell_mgmt_handlers) + +static struct mgmt_group shell_mgmt_group = { + .mg_handlers = shell_mgmt_handlers, + .mg_handlers_count = SHELL_MGMT_HANDLER_CNT, + .mg_group_id = MGMT_GROUP_ID_SHELL, +}; + + +void +shell_mgmt_register_group(void) +{ + mgmt_register_group(&shell_mgmt_group); +} diff --git a/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/src/zephyr_shell_mgmt.c b/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/src/zephyr_shell_mgmt.c new file mode 100644 index 000000000000..019d4db6c462 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/shell_mgmt/src/zephyr_shell_mgmt.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +int +shell_mgmt_impl_exec(const char *line) +{ + const struct shell *shell = shell_backend_dummy_get_ptr(); + + shell_backend_dummy_clear_output(shell); + return shell_execute_cmd(shell, line); +} + +const char * +shell_mgmt_impl_get_output() +{ + size_t len; + + return shell_backend_dummy_get_output( + shell_backend_dummy_get_ptr(), + &len + ); +} diff --git a/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/CMakeLists.txt b/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/CMakeLists.txt new file mode 100644 index 000000000000..b69a56c5b2e4 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/CMakeLists.txt @@ -0,0 +1,14 @@ +# +# Copyright (c) 2018-2021 mcumgr authors +# +# SPDX-License-Identifier: Apache-2.0 +# + +target_include_directories(MCUMGR INTERFACE + include +) + +zephyr_library_sources( + src/zephyr_stat_mgmt.c + src/stat_mgmt.c +) diff --git a/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/include/stat_mgmt/stat_mgmt.h b/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/include/stat_mgmt/stat_mgmt.h new file mode 100644 index 000000000000..d7e6dbe6dace --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/include/stat_mgmt/stat_mgmt.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef H_STAT_MGMT_ +#define H_STAT_MGMT_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Command IDs for statistics management group. + */ +#define STAT_MGMT_ID_SHOW 0 +#define STAT_MGMT_ID_LIST 1 + +/** + * @brief Represents a single value in a statistics group. + */ +struct stat_mgmt_entry { + const char *name; + uint64_t value; +}; + +/** + * @brief Registers the statistics management command handler group. + */ +void stat_mgmt_register_group(void); + +#ifdef __cplusplus +} +#endif + +#endif /* H_STAT_MGMT_ */ diff --git a/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/include/stat_mgmt/stat_mgmt_config.h b/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/include/stat_mgmt/stat_mgmt_config.h new file mode 100644 index 000000000000..5094f264c743 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/include/stat_mgmt/stat_mgmt_config.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef H_STAT_MGMT_CONFIG_ +#define H_STAT_MGMT_CONFIG_ + +#define STAT_MGMT_MAX_NAME_LEN CONFIG_STAT_MGMT_MAX_NAME_LEN + +#endif diff --git a/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/include/stat_mgmt/stat_mgmt_impl.h b/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/include/stat_mgmt/stat_mgmt_impl.h new file mode 100644 index 000000000000..9bddec8cd49b --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/include/stat_mgmt/stat_mgmt_impl.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Declares implementation-specific functions required by statistics + * management. The default stubs can be overridden with functions that + * are compatible with the host OS. + */ + +#ifndef H_STAT_MGMT_IMPL_ +#define H_STAT_MGMT_IMPL_ + +#ifdef __cplusplus +extern "C" { +#endif + +struct stat_mgmt_entry; + +typedef int stat_mgmt_foreach_entry_fn(struct stat_mgmt_entry *entry, void *arg); + +/** + * @brief Retrieves the name of the stat group at the specified index. + * + * @param idx The index of the stat group to retrieve. + * @param out_name On success, the name of the requested stat group gets written here. + * + * @return 0 on success; + * MGMT_ERR_ENOENT if no group with the specified index exists; + * Other MGMT_ERR_[...] code on failure. + */ +int stat_mgmt_impl_get_group(int idx, const char **out_name); + +/** + * @brief Applies a function to every entry in the specified stat group. + * + * @param group_name The name of the stat group to operate on. + * @param cb The callback to apply to each stat entry. + * @param arg An optional argument to pass to the callback. + * + * @return 0 on success; MGMT_ERR_[...] code on failure. + */ +int stat_mgmt_impl_foreach_entry(const char *group_name, stat_mgmt_foreach_entry_fn *cb, void *arg); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/src/stat_mgmt.c b/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/src/stat_mgmt.c new file mode 100644 index 000000000000..c9ad50515fc9 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/src/stat_mgmt.c @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "mgmt/mgmt.h" +#include "cborattr/cborattr.h" +#include "stat_mgmt/stat_mgmt.h" +#include "stat_mgmt/stat_mgmt_impl.h" +#include "stat_mgmt/stat_mgmt_config.h" + +static struct mgmt_handler stat_mgmt_handlers[]; + +static int +stat_mgmt_cb_encode(struct stat_mgmt_entry *entry, void *arg) +{ + CborEncoder *enc; + CborError err; + + enc = arg; + + err = 0; + err |= cbor_encode_text_stringz(enc, entry->name); + err |= cbor_encode_uint(enc, entry->value); + + if (err != 0) { + return MGMT_ERR_ENOMEM; + } + + return 0; +} + +/** + * Command handler: stat show + */ +static int +stat_mgmt_show(struct mgmt_ctxt *ctxt) +{ + char stat_name[STAT_MGMT_MAX_NAME_LEN]; + CborEncoder map_enc; + CborError err; + int rc; + + struct cbor_attr_t attrs[] = { + { + .attribute = "name", + .type = CborAttrTextStringType, + .addr.string = stat_name, + .len = sizeof(stat_name) + }, + { NULL }, + }; + + err = cbor_read_object(&ctxt->it, attrs); + if (err != 0) { + return MGMT_ERR_EINVAL; + } + + err |= cbor_encode_text_stringz(&ctxt->encoder, "rc"); + err |= cbor_encode_int(&ctxt->encoder, MGMT_ERR_EOK); + + err |= cbor_encode_text_stringz(&ctxt->encoder, "name"); + err |= cbor_encode_text_stringz(&ctxt->encoder, stat_name); + + err |= cbor_encode_text_stringz(&ctxt->encoder, "fields"); + err |= cbor_encoder_create_map(&ctxt->encoder, &map_enc, CborIndefiniteLength); + + rc = stat_mgmt_impl_foreach_entry(stat_name, stat_mgmt_cb_encode, + &map_enc); + + err |= cbor_encoder_close_container(&ctxt->encoder, &map_enc); + if (err != 0) { + rc = MGMT_ERR_ENOMEM; + } + + return rc; +} + +/** + * Command handler: stat list + */ +static int +stat_mgmt_list(struct mgmt_ctxt *ctxt) +{ + const char *group_name; + CborEncoder arr_enc; + CborError err; + int rc; + int i; + + err = CborNoError; + err |= cbor_encode_text_stringz(&ctxt->encoder, "rc"); + err |= cbor_encode_int(&ctxt->encoder, MGMT_ERR_EOK); + err |= cbor_encode_text_stringz(&ctxt->encoder, "stat_list"); + err |= cbor_encoder_create_array(&ctxt->encoder, &arr_enc, CborIndefiniteLength); + + /* Iterate the list of stat groups, encoding each group's name in the CBOR + * array. + */ + for (i = 0; ; i++) { + rc = stat_mgmt_impl_get_group(i, &group_name); + if (rc == MGMT_ERR_ENOENT) { + /* No more stat groups. */ + break; + } else if (rc != 0) { + /* Error. */ + cbor_encoder_close_container(&ctxt->encoder, &arr_enc); + return rc; + } + + err |= cbor_encode_text_stringz(&ctxt->encoder, group_name); + } + err |= cbor_encoder_close_container(&ctxt->encoder, &arr_enc); + + if (err != 0) { + return MGMT_ERR_ENOMEM; + } + return 0; +} + +static struct mgmt_handler stat_mgmt_handlers[] = { + [STAT_MGMT_ID_SHOW] = { stat_mgmt_show, NULL }, + [STAT_MGMT_ID_LIST] = { stat_mgmt_list, NULL }, +}; + +#define STAT_MGMT_HANDLER_CNT ARRAY_SIZE(stat_mgmt_handlers) + +static struct mgmt_group stat_mgmt_group = { + .mg_handlers = stat_mgmt_handlers, + .mg_handlers_count = STAT_MGMT_HANDLER_CNT, + .mg_group_id = MGMT_GROUP_ID_STAT, +}; + +void +stat_mgmt_register_group(void) +{ + mgmt_register_group(&stat_mgmt_group); +} diff --git a/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/src/zephyr_stat_mgmt.c b/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/src/zephyr_stat_mgmt.c new file mode 100644 index 000000000000..9560f72c3a9d --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/cmd/stat_mgmt/src/zephyr_stat_mgmt.c @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +struct zephyr_stat_mgmt_walk_arg { + stat_mgmt_foreach_entry_fn *cb; + void *arg; +}; + +int +stat_mgmt_impl_get_group(int idx, const char **out_name) +{ + const struct stats_hdr *cur; + int i; + + cur = NULL; + for (i = 0; i <= idx; i++) { + cur = stats_group_get_next(cur); + if (cur == NULL) { + return MGMT_ERR_ENOENT; + } + } + + *out_name = cur->s_name; + return 0; +} + +static int +zephyr_stat_mgmt_walk_cb(struct stats_hdr *hdr, void *arg, const char *name, uint16_t off) +{ + struct zephyr_stat_mgmt_walk_arg *walk_arg; + struct stat_mgmt_entry entry; + void *stat_val; + + walk_arg = arg; + + stat_val = (uint8_t *)hdr + off; + switch (hdr->s_size) { + case sizeof(uint16_t): + entry.value = *(uint16_t *) stat_val; + break; + case sizeof(uint32_t): + entry.value = *(uint32_t *) stat_val; + break; + case sizeof(uint64_t): + entry.value = *(uint64_t *) stat_val; + break; + default: + return MGMT_ERR_EUNKNOWN; + } + entry.name = name; + + return walk_arg->cb(&entry, walk_arg->arg); +} + +int +stat_mgmt_impl_foreach_entry(const char *group_name, stat_mgmt_foreach_entry_fn *cb, void *arg) +{ + struct zephyr_stat_mgmt_walk_arg walk_arg; + struct stats_hdr *hdr; + + hdr = stats_group_find(group_name); + if (hdr == NULL) { + return MGMT_ERR_ENOENT; + } + + walk_arg = (struct zephyr_stat_mgmt_walk_arg) { + .cb = cb, + .arg = arg, + }; + + return stats_walk(hdr, zephyr_stat_mgmt_walk_cb, &walk_arg); +} diff --git a/subsys/mgmt/mcumgr/lib/mgmt/CMakeLists.txt b/subsys/mgmt/mcumgr/lib/mgmt/CMakeLists.txt new file mode 100644 index 000000000000..c2e4cbac1365 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/mgmt/CMakeLists.txt @@ -0,0 +1,13 @@ +# +# Copyright (c) 2018-2021 mcumgr authors +# +# SPDX-License-Identifier: Apache-2.0 +# + +target_include_directories(MCUMGR INTERFACE + include +) + +zephyr_library_sources( + src/mgmt.c +) diff --git a/subsys/mgmt/mcumgr/lib/mgmt/include/mgmt/endian.h b/subsys/mgmt/mcumgr/lib/mgmt/include/mgmt/endian.h new file mode 100644 index 000000000000..c7cf5c8e5cd6 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/mgmt/include/mgmt/endian.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef H_MGMT_ENDIAN_ +#define H_MGMT_ENDIAN_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + +#ifndef ntohs +#define ntohs(x) (x) +#endif + +#ifndef htons +#define htons(x) (x) +#endif + +#else +/* Little endian. */ + +#ifndef ntohs +#define ntohs(x) ((uint16_t) ((((x) & 0xff00) >> 8) | (((x) & 0x00ff) << 8))) +#endif + +#ifndef htons +#define htons(x) (ntohs(x)) +#endif + +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/subsys/mgmt/mcumgr/lib/mgmt/include/mgmt/mgmt.h b/subsys/mgmt/mcumgr/lib/mgmt/include/mgmt/mgmt.h new file mode 100644 index 000000000000..10cdda5ba3d4 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/mgmt/include/mgmt/mgmt.h @@ -0,0 +1,429 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef H_MGMT_MGMT_ +#define H_MGMT_MGMT_ + +#include +#include "tinycbor/cbor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* MTU for newtmgr responses */ +#define MGMT_MAX_MTU 1024 + +/** Opcodes; encoded in first byte of header. */ +#define MGMT_OP_READ 0 +#define MGMT_OP_READ_RSP 1 +#define MGMT_OP_WRITE 2 +#define MGMT_OP_WRITE_RSP 3 + +/** + * The first 64 groups are reserved for system level mcumgr commands. + * Per-user commands are then defined after group 64. + */ +#define MGMT_GROUP_ID_OS 0 +#define MGMT_GROUP_ID_IMAGE 1 +#define MGMT_GROUP_ID_STAT 2 +#define MGMT_GROUP_ID_CONFIG 3 +#define MGMT_GROUP_ID_LOG 4 +#define MGMT_GROUP_ID_CRASH 5 +#define MGMT_GROUP_ID_SPLIT 6 +#define MGMT_GROUP_ID_RUN 7 +#define MGMT_GROUP_ID_FS 8 +#define MGMT_GROUP_ID_SHELL 9 +#define MGMT_GROUP_ID_PERUSER 64 + +/** + * mcumgr error codes. + */ +#define MGMT_ERR_EOK 0 +#define MGMT_ERR_EUNKNOWN 1 +#define MGMT_ERR_ENOMEM 2 +#define MGMT_ERR_EINVAL 3 +#define MGMT_ERR_ETIMEOUT 4 +#define MGMT_ERR_ENOENT 5 +#define MGMT_ERR_EBADSTATE 6 /* Current state disallows command. */ +#define MGMT_ERR_EMSGSIZE 7 /* Response too large. */ +#define MGMT_ERR_ENOTSUP 8 /* Command not supported. */ +#define MGMT_ERR_ECORRUPT 9 /* Corrupt */ +#define MGMT_ERR_EPERUSER 256 + +#define MGMT_HDR_SIZE 8 + +/* + * MGMT event opcodes. + */ +#define MGMT_EVT_OP_CMD_RECV 0x01 +#define MGMT_EVT_OP_CMD_STATUS 0x02 +#define MGMT_EVT_OP_CMD_DONE 0x03 + +struct mgmt_hdr { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + uint8_t nh_op:3; /* MGMT_OP_[...] */ + uint8_t _res1:5; +#endif +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + uint8_t _res1:5; + uint8_t nh_op:3; /* MGMT_OP_[...] */ +#endif + uint8_t nh_flags; /* Reserved for future flags */ + uint16_t nh_len; /* Length of the payload */ + uint16_t nh_group; /* MGMT_GROUP_ID_[...] */ + uint8_t nh_seq; /* Sequence number */ + uint8_t nh_id; /* Message ID within group */ +}; + +#define nmgr_hdr mgmt_hdr + +/* + * MGMT_EVT_OP_CMD_STATUS argument + */ +struct mgmt_evt_op_cmd_status_arg { + int status; +}; + +/* + * MGMT_EVT_OP_CMD_DONE argument + */ +struct mgmt_evt_op_cmd_done_arg { + int err; /* MGMT_ERR_[...] */ +}; + +/** @typedef mgmt_on_evt_cb + * @brief Function to be called on MGMT event. + * + * This callback function is used to notify application about mgmt event. + * + * @param opcode MGMT_EVT_OP_[...]. + * @param group MGMT_GROUP_ID_[...]. + * @param id Message ID within group. + * @param arg Optional event argument. + */ +typedef void (*mgmt_on_evt_cb)(uint8_t opcode, uint16_t group, uint8_t id, void *arg); + +/** @typedef mgmt_alloc_rsp_fn + * @brief Allocates a buffer suitable for holding a response. + * + * If a source buf is provided, its user data is copied into the new buffer. + * + * @param src_buf An optional source buffer to copy user data from. + * @param arg Optional streamer argument. + * + * @return Newly-allocated buffer on success NULL on failure. + */ +typedef void *(*mgmt_alloc_rsp_fn)(const void *src_buf, void *arg); + +/** @typedef mgmt_trim_front_fn + * @brief Trims data from the front of a buffer. + * + * If the amount to trim exceeds the size of the buffer, the buffer is + * truncated to a length of 0. + * + * @param buf The buffer to trim. + * @param len The number of bytes to remove. + * @param arg Optional streamer argument. + */ +typedef void (*mgmt_trim_front_fn)(void *buf, size_t len, void *arg); + +/** @typedef mgmt_reset_buf_fn + * @brief Resets a buffer to a length of 0. + * + * The buffer's user data remains, but its payload is cleared. + * + * @param buf The buffer to reset. + * @param arg Optional streamer argument. + */ +typedef void (*mgmt_reset_buf_fn)(void *buf, void *arg); + +/** @typedef mgmt_write_at_fn + * @brief Writes data to a CBOR encoder. + * + * Any existing data at the specified offset is overwritten by the new data. + * Any new data that extends past the buffer's current length is appended. + * + * @param writer The encoder to write to. + * @param offset The byte offset to write to, + * @param data The data to write. + * @param len The number of bytes to write. + * @param arg Optional streamer argument. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +typedef int (*mgmt_write_at_fn)(struct cbor_encoder_writer *writer, size_t offset, + const void *data, size_t len, void *arg); + +/** @typedef mgmt_init_reader_fn + * @brief Initializes a CBOR reader with the specified buffer. + * + * @param reader The reader to initialize. + * @param buf The buffer to configure the reader with. + * @param arg Optional streamer argument. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +typedef int (*mgmt_init_reader_fn)(struct cbor_decoder_reader *reader, void *buf, void *arg); + +/** @typedef mgmt_init_writer_fn + * @brief Initializes a CBOR writer with the specified buffer. + * + * @param writer The writer to initialize. + * @param buf The buffer to configure the writer with. + * @param arg Optional streamer argument. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +typedef int (*mgmt_init_writer_fn)(struct cbor_encoder_writer *writer, void *buf, void *arg); + +/** @typedef mgmt_init_writer_fn + * @brief Frees the specified buffer. + * + * @param buf The buffer to free. + * @param arg Optional streamer argument. + */ +typedef void (*mgmt_free_buf_fn)(void *buf, void *arg); + +/** + * @brief Configuration for constructing a mgmt_streamer object. + */ +struct mgmt_streamer_cfg { + mgmt_alloc_rsp_fn alloc_rsp; + mgmt_trim_front_fn trim_front; + mgmt_reset_buf_fn reset_buf; + mgmt_write_at_fn write_at; + mgmt_init_reader_fn init_reader; + mgmt_init_writer_fn init_writer; + mgmt_free_buf_fn free_buf; +}; + +/** + * @brief Decodes requests and encodes responses for any mcumgr protocol. + */ +struct mgmt_streamer { + const struct mgmt_streamer_cfg *cfg; + void *cb_arg; + struct cbor_decoder_reader *reader; + struct cbor_encoder_writer *writer; +}; + +/** + * @brief Context required by command handlers for parsing requests and writing + * responses. + */ +struct mgmt_ctxt { + struct CborEncoder encoder; + struct CborParser parser; + struct CborValue it; +}; + +/** @typedef mgmt_handler_fn + * @brief Processes a request and writes the corresponding response. + * + * A separate handler is required for each supported op-ID pair. + * + * @param ctxt The mcumgr context to use. + * + * @return 0 if a response was successfully encoded, MGMT_ERR_[...] code on failure. + */ +typedef int (*mgmt_handler_fn)(struct mgmt_ctxt *ctxt); + +/** + * @brief Read handler and write handler for a single command ID. + */ +struct mgmt_handler { + mgmt_handler_fn mh_read; + mgmt_handler_fn mh_write; +}; + +/** + * @brief A collection of handlers for an entire command group. + */ +struct mgmt_group { + /** Points to the next group in the list. */ + struct mgmt_group *mg_next; + + /** Array of handlers; one entry per command ID. */ + const struct mgmt_handler *mg_handlers; + uint16_t mg_handlers_count; + + /* The numeric ID of this group. */ + uint16_t mg_group_id; +}; + +/** + * @brief Uses the specified streamer to allocates a response buffer. + * + * If a source buf is provided, its user data is copied into the new buffer. + * + * @param streamer The streamer providing the callback. + * @param src_buf An optional source buffer to copy user data from. + * + * @return Newly-allocated buffer on success + * NULL on failure. + */ +void *mgmt_streamer_alloc_rsp(struct mgmt_streamer *streamer, const void *src_buf); + +/** + * @brief Uses the specified streamer to trim data from the front of a buffer. + * + * If the amount to trim exceeds the size of the buffer, the buffer is + * truncated to a length of 0. + * + * @param streamer The streamer providing the callback. + * @param buf The buffer to trim. + * @param len The number of bytes to remove. + */ +void mgmt_streamer_trim_front(struct mgmt_streamer *streamer, void *buf, size_t len); + +/** + * @brief Uses the specified streamer to reset a buffer to a length of 0. + * + * The buffer's user data remains, but its payload is cleared. + * + * @param streamer The streamer providing the callback. + * @param buf The buffer to reset. + */ +void mgmt_streamer_reset_buf(struct mgmt_streamer *streamer, void *buf); + +/** + * @brief Uses the specified streamer to write data to a CBOR encoder. + * + * Any existing data at the specified offset is overwritten by the new data. + * Any new data that extends past the buffer's current length is appended. + * + * @param streamer The streamer providing the callback. + * @param writer The encoder to write to. + * @param offset The byte offset to write to, + * @param data The data to write. + * @param len The number of bytes to write. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +int mgmt_streamer_write_at(struct mgmt_streamer *streamer, size_t offset, const void *data, + int len); + +/** + * @brief Uses the specified streamer to initialize a CBOR reader. + * + * @param streamer The streamer providing the callback. + * @param reader The reader to initialize. + * @param buf The buffer to configure the reader with. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +int mgmt_streamer_init_reader(struct mgmt_streamer *streamer, void *buf); + +/** + * @brief Uses the specified streamer to initializes a CBOR writer. + * + * @param streamer The streamer providing the callback. + * @param writer The writer to initialize. + * @param buf The buffer to configure the writer with. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +int mgmt_streamer_init_writer(struct mgmt_streamer *streamer, void *buf); + +/** + * @brief Uses the specified streamer to free a buffer. + * + * @param streamer The streamer providing the callback. + * @param buf The buffer to free. + */ +void mgmt_streamer_free_buf(struct mgmt_streamer *streamer, void *buf); + +/** + * @brief Registers a full command group. + * + * @param group The group to register. + */ +void mgmt_register_group(struct mgmt_group *group); + +/** + * @brief Unregisters a full command group. + * + * @param group The group to register. + */ +void mgmt_unregister_group(struct mgmt_group *group); + +/** + * @brief Finds a registered command handler. + * + * @param group_id The group of the command to find. + * @param command_id The ID of the command to find. + * + * @return The requested command handler on success; + * NULL on failure. + */ +const struct mgmt_handler *mgmt_find_handler(uint16_t group_id, uint16_t command_id); + +/** + * @brief Encodes a response status into the specified management context. + * + * @param ctxt The management context to encode into. + * @param status The response status to write. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +int mgmt_write_rsp_status(struct mgmt_ctxt *ctxt, int status); + +/** + * @brief Initializes a management context object with the specified streamer. + * + * @param ctxt The context object to initialize. + * @param streamer The streamer that will be used with the context. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +int mgmt_ctxt_init(struct mgmt_ctxt *ctxt, struct mgmt_streamer *streamer); + +/** + * @brief Converts a CBOR status code to a MGMT_ERR_[...] code. + * + * @param cbor_status The CBOR status code to convert. + * + * @return The corresponding MGMT_ERR_[,,,] code. + */ +int mgmt_err_from_cbor(int cbor_status); + +/** + * @brief Byte-swaps an mcumgr header from network to host byte order. + * + * @param hdr The mcumgr header to byte-swap. + */ +void mgmt_ntoh_hdr(struct mgmt_hdr *hdr); + +/** + * @brief Byte-swaps an mcumgr header from host to network byte order. + * + * @param hdr The mcumgr header to byte-swap. + */ +void mgmt_hton_hdr(struct mgmt_hdr *hdr); + +/** + * @brief Register event callback function. + * + * @param cb Callback function. + */ +void mgmt_register_evt_cb(mgmt_on_evt_cb cb); + +/** + * @brief This function is called to notify about mgmt event. + * + * @param opcode MGMT_EVT_OP_[...]. + * @param group MGMT_GROUP_ID_[...]. + * @param id Message ID within group. + * @param arg Optional event argument. + */ +void mgmt_evt(uint8_t opcode, uint16_t group, uint8_t id, void *arg); + +#ifdef __cplusplus +} +#endif + +#endif /* MGMT_MGMT_H_ */ diff --git a/subsys/mgmt/mcumgr/lib/mgmt/src/mgmt.c b/subsys/mgmt/mcumgr/lib/mgmt/src/mgmt.c new file mode 100644 index 000000000000..b39e4a1b7b31 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/mgmt/src/mgmt.c @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "tinycbor/cbor.h" +#include "mgmt/endian.h" +#include "mgmt/mgmt.h" + +static mgmt_on_evt_cb evt_cb; +static struct mgmt_group *mgmt_group_list; +static struct mgmt_group *mgmt_group_list_end; + +void * +mgmt_streamer_alloc_rsp(struct mgmt_streamer *streamer, const void *req) +{ + return streamer->cfg->alloc_rsp(req, streamer->cb_arg); +} + +void +mgmt_streamer_trim_front(struct mgmt_streamer *streamer, void *buf, size_t len) +{ + streamer->cfg->trim_front(buf, len, streamer->cb_arg); +} + +void +mgmt_streamer_reset_buf(struct mgmt_streamer *streamer, void *buf) +{ + streamer->cfg->reset_buf(buf, streamer->cb_arg); +} + +int +mgmt_streamer_write_at(struct mgmt_streamer *streamer, size_t offset, + const void *data, int len) +{ + return streamer->cfg->write_at(streamer->writer, offset, data, len, streamer->cb_arg); +} + +int +mgmt_streamer_init_reader(struct mgmt_streamer *streamer, void *buf) +{ + return streamer->cfg->init_reader(streamer->reader, buf, streamer->cb_arg); +} + +int +mgmt_streamer_init_writer(struct mgmt_streamer *streamer, void *buf) +{ + return streamer->cfg->init_writer(streamer->writer, buf, streamer->cb_arg); +} + +void +mgmt_streamer_free_buf(struct mgmt_streamer *streamer, void *buf) +{ + streamer->cfg->free_buf(buf, streamer->cb_arg); +} + +void +mgmt_unregister_group(struct mgmt_group *group) +{ + struct mgmt_group *curr = mgmt_group_list, *prev = NULL; + + if (!group) { + return; + } + + if (curr == group) { + mgmt_group_list = curr->mg_next; + return; + } + + while (curr && curr != group) { + prev = curr; + curr = curr->mg_next; + } + + if (!prev || !curr) { + return; + } + + prev->mg_next = curr->mg_next; + if (curr->mg_next == NULL) { + mgmt_group_list_end = curr; + } +} + +static struct mgmt_group * +mgmt_find_group(uint16_t group_id, uint16_t command_id) +{ + struct mgmt_group *group; + + /* + * Find the group with the specified group id, if one exists + * check the handler for the command id and make sure + * that is not NULL. If that is not set, look for the group + * with a command id that is set + */ + for (group = mgmt_group_list; group != NULL; group = group->mg_next) { + if (group->mg_group_id == group_id) { + if (command_id >= group->mg_handlers_count) { + return NULL; + } + + if (!group->mg_handlers[command_id].mh_read && + !group->mg_handlers[command_id].mh_write) { + continue; + } + + break; + } + } + + return group; +} + +void +mgmt_register_group(struct mgmt_group *group) +{ + if (mgmt_group_list_end == NULL) { + mgmt_group_list = group; + } else { + mgmt_group_list_end->mg_next = group; + } + mgmt_group_list_end = group; +} + +const struct mgmt_handler * +mgmt_find_handler(uint16_t group_id, uint16_t command_id) +{ + const struct mgmt_group *group; + + group = mgmt_find_group(group_id, command_id); + if (!group) { + return NULL; + } + + return &group->mg_handlers[command_id]; +} + +int +mgmt_write_rsp_status(struct mgmt_ctxt *ctxt, int errcode) +{ + int rc; + + rc = cbor_encode_text_stringz(&ctxt->encoder, "rc"); + if (rc != 0) { + return rc; + } + + rc = cbor_encode_int(&ctxt->encoder, errcode); + if (rc != 0) { + return rc; + } + + return 0; +} + +int +mgmt_err_from_cbor(int cbor_status) +{ + switch (cbor_status) { + case CborNoError: + return MGMT_ERR_EOK; + case CborErrorOutOfMemory: + return MGMT_ERR_ENOMEM; + } + return MGMT_ERR_EUNKNOWN; +} + +int +mgmt_ctxt_init(struct mgmt_ctxt *ctxt, struct mgmt_streamer *streamer) +{ + int rc; + + rc = cbor_parser_init(streamer->reader, 0, &ctxt->parser, &ctxt->it); + if (rc != CborNoError) { + return mgmt_err_from_cbor(rc); + } + + cbor_encoder_init(&ctxt->encoder, streamer->writer, 0); + + return 0; +} + +void +mgmt_ntoh_hdr(struct mgmt_hdr *hdr) +{ + hdr->nh_len = ntohs(hdr->nh_len); + hdr->nh_group = ntohs(hdr->nh_group); +} + +void +mgmt_hton_hdr(struct mgmt_hdr *hdr) +{ + hdr->nh_len = htons(hdr->nh_len); + hdr->nh_group = htons(hdr->nh_group); +} + +void +mgmt_register_evt_cb(mgmt_on_evt_cb cb) +{ + evt_cb = cb; +} + +void +mgmt_evt(uint8_t opcode, uint16_t group, uint8_t id, void *arg) +{ + if (evt_cb) { + evt_cb(opcode, group, id, arg); + } +} diff --git a/subsys/mgmt/mcumgr/lib/protocol.md b/subsys/mgmt/mcumgr/lib/protocol.md new file mode 100644 index 000000000000..a1c296e610d5 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/protocol.md @@ -0,0 +1,256 @@ +# SMP (Simple Management Protocol) Protocol Notes + +The `mcumgr` SMP server and `mcumgr-cli` SMP client tool ([source](https://github.com/apache/mynewt-mcumgr-cli)) +use a custom protocol to send commands and responses between the server and client using a +variety of transports (currently TTY serial or BLE). + +The protocol isn't fully documented but the following information has been inferred +from the source code available on Github and using the `-l DEBUG` flag when +executing commands. + +## Source Code + +**SMP** is based on the earlier **NMP**, which is part of [Apache Mynewt](https://mynewt.apache.org/). + +The golang source for the original **newtmgr** is [available here](https://github.com/apache/mynewt-newtmgr), +and can be used to provide some insight into how data is exchanged between the +utility and the device under test. + +This repository (`mynewt-mcumgr`) implements an SMP server in **C**, +and a new command-line SMP client called **mcumgr** was created at +[apache/mynewt-mcumgr-cli](https://github.com/apache/mynewt-mcumgr-cli). + +## SMP Frame Format + +### Endianness + +Frames are normally serialized as **Big Endian** when dealing with values > 8 bits. This is +mandatory in NMP, but the SMP implementation does add support for **Little Endian** as an +option at the struct level, as shown below. + +### Frame Header + +Frames in SMP have the following header format: + +``` +struct mgmt_hdr { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + uint8_t nh_op:3; /* MGMT_OP_[...] */ + uint8_t _res1:5; +#endif +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + uint8_t _res1:5; + uint8_t nh_op:3; /* MGMT_OP_[...] */ +#endif + uint8_t nh_flags; /* Reserved for future flags */ + uint16_t nh_len; /* Length of the payload */ + uint16_t nh_group; /* MGMT_GROUP_ID_[...] */ + uint8_t nh_seq; /* Sequence number */ + uint8_t nh_id; /* Message ID within group */ +}; +``` + +The NMP/newtmgr [go source](https://github.com/apache/mynewt-newtmgr/blob/master/nmxact/nmp/nmp.go) is as follows, +without the option to select endianness: + +``` +type NmpHdr struct { + Op uint8 /* 3 bits of opcode */ + Flags uint8 + Len uint16 + Group uint16 + Seq uint8 + Id uint8 +} +``` + +`nh_op` (or `Op` in newtmgr) can be one of the following values: + +``` +/** Opcodes; encoded in first byte of header. */ +#define MGMT_OP_READ 0 +#define MGMT_OP_READ_RSP 1 +#define MGMT_OP_WRITE 2 +#define MGMT_OP_WRITE_RSP 3 +``` + +- **`op`**: The operation code +- **`Flags`**: TBD +- **`Len`**: The payload len when `Data` is present +- **`Group`**: Commands are organized into groups. Groups are defined + [here](https://github.com/apache/mynewt-mcumgr/blob/master/mgmt/include/mgmt/mgmt.h). +- **`Seq`**: TBD +- **`Id`**: The command ID to send. Commands in the default `Group` are defined + [here](https://github.com/apache/mynewt-mcumgr/blob/master/mgmt/include/mgmt/mgmt.h). + +SMP header (Little Endian) + +``` + 0 1 2 3 + 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 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| OP | Res. | Flags | Length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Group ID | Sequence | Command ID | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +``` + +#### `Data` Payload + +If `nh_len` (`Len` in nmp) is non-zero, the `nh_len` byte payload (referred to as **`Data`** in this document) immediately follows the frame header. + +### Example Packets + +The following example commands show how the different fields work: + +#### Simple Read Request: `taskstats` + +The following example corresponds to the `taskstats` command ([source](https://github.com/apache/mynewt-mcumgr/blob/master/cmd/os_mgmt/include/os_mgmt/os_mgmt.h)), and +can be seen by running `mcumgr -l DEBUG -c serial taskstats`: + +``` +Op: 0 # NMGR_OP_READ +Flags: 0 +Len: 0 # No payload present +Group: 0 # 0x00 = NMGR_GROUP_ID_DEFAULT +Seq: 0 +Id: 2 # 0x02 in group 0x00 = OS_MGMT_ID_TASKSTAT +Data: [] # No payload (len = 0 above) +``` + +When serialized this will be sent as `0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02`. + +If this was sent using the serial port, you would get the following request and response: + +``` +$ mcumgr -l DEBUG -c serial taskstats +2016/11/11 12:45:44 [DEBUG] Writing newtmgr request &{Op:0 Flags:0 Len:0 Group:0 Seq:0 Id:2 Data:[]} +2016/11/11 12:45:44 [DEBUG] Serializing request &{Op:0 Flags:0 Len:0 Group:0 Seq:0 Id:2 Data:[]} into buffer [0 0 0 0 0 0 0 2] +2016/11/11 12:45:44 [DEBUG] Tx packet dump: +00000000 00 00 00 00 00 00 00 02 |........| + +2016/11/11 12:45:44 [DEBUG] Writing [6 9] to data channel +2016/11/11 12:45:44 [DEBUG] Writing [65 65 111 65 65 65 65 65 65 65 65 65 65 105 66 67] to data channel +2016/11/11 12:45:44 [DEBUG] Writing [10] to data channel +2016/11/11 12:45:44 [DEBUG] Reading [6 9 65 65 111 65 65 65 65 65 65 65 65 65 65 105 66 67] from data channel +2016/11/11 12:45:44 [DEBUG] Rx packet dump: +00000000 00 00 00 00 00 00 00 02 |........| + +2016/11/11 12:45:44 [DEBUG] Deserialized response &{Op:0 Flags:0 Len:0 Group:0 Seq:0 Id:2 Data:[]} +2016/11/11 12:45:44 [DEBUG] Reading [13] from data channel +2016/11/11 12:45:44 [DEBUG] Reading [6 9 65 90 119 66 65 81 71 83 65 65 65 65 65 114 57 105 99 109 77 65 90 88 82 104 99 50 116 122 118 50 82 112 90 71 120 108 118 50 82 119 99 109 108 118 71 80 57 106 100 71 108 107 65 71 86 122 100 71 70 48 90 81 70 109 99 51 82 114 100 88 78 108 71 66 108 109 99 51 82 114 99 50 108 54 71 69 66 109 89 51 78 51 89 50 53 48 71 103 65 85 102 109 112 110 99 110 86 117 100 71 108 116 90 82 111 65 69 53 120 80 98 71 120 104 99 51 82 102] from data channel +2016/11/11 12:45:44 [DEBUG] Reading [4 20 89 50 104 108 89 50 116 112 98 103 66 115 98 109 86 52 100 70 57 106 97 71 86 106 97 50 108 117 65 80 57 109 89 109 120 108 88 50 120 115 118 50 82 119 99 109 108 118 65 71 78 48 97 87 81 66 90 88 78 48 89 88 82 108 65 109 90 122 100 71 116 49 99 50 85 89 79 109 90 122 100 71 116 122 97 88 111 89 85 71 90 106 99 51 100 106 98 110 81 90 54 112 120 110 99 110 86 117 100 71 108 116 90 82 107 74 82 87 120 115 89 88 78 48 88 50 78 111] from data channel +2016/11/11 12:45:44 [DEBUG] Reading [4 20 90 87 78 114 97 87 52 65 98 71 53 108 101 72 82 102 89 50 104 108 89 50 116 112 98 103 68 47 98 109 74 115 90 88 86 104 99 110 82 102 89 110 74 112 90 71 100 108 118 50 82 119 99 109 108 118 66 87 78 48 97 87 81 67 90 88 78 48 89 88 82 108 65 87 90 122 100 71 116 49 99 50 85 89 72 50 90 122 100 71 116 122 97 88 111 90 65 81 66 109 89 51 78 51 89 50 53 48 71 103 65 84 113 89 78 110 99 110 86 117 100 71 108 116 90 81 66 115] from data channel +2016/11/11 12:45:44 [DEBUG] Reading [4 20 98 71 70 122 100 70 57 106 97 71 86 106 97 50 108 117 65 71 120 117 90 88 104 48 88 50 78 111 90 87 78 114 97 87 52 65 47 50 100 105 98 71 86 119 99 110 66 111 118 50 82 119 99 109 108 118 65 87 78 48 97 87 81 68 90 88 78 48 89 88 82 108 65 87 90 122 100 71 116 49 99 50 85 89 48 50 90 122 100 71 116 122 97 88 111 90 65 86 66 109 89 51 78 51 89 50 53 48 71 81 113 68 90 51 74 49 98 110 82 112 98 87 85 69 98 71 120 104] from data channel +2016/11/11 12:45:44 [DEBUG] Reading [4 20 99 51 82 102 89 50 104 108 89 50 116 112 98 103 66 115 98 109 86 52 100 70 57 106 97 71 86 106 97 50 108 117 65 80 47 47 47 56 85 88] from data channel +2016/11/11 12:45:44 [DEBUG] Rx packet dump: +00000000 01 01 01 92 00 00 00 02 bf 62 72 63 00 65 74 61 |.........brc.eta| +00000010 73 6b 73 bf 64 69 64 6c 65 bf 64 70 72 69 6f 18 |sks.didle.dprio.| +00000020 ff 63 74 69 64 00 65 73 74 61 74 65 01 66 73 74 |.ctid.estate.fst| +00000030 6b 75 73 65 18 19 66 73 74 6b 73 69 7a 18 40 66 |kuse..fstksiz.@f| +00000040 63 73 77 63 6e 74 1a 00 14 7e 6a 67 72 75 6e 74 |cswcnt...~jgrunt| +00000050 69 6d 65 1a 00 13 9c 4f 6c 6c 61 73 74 5f 63 68 |ime....Ollast_ch| +00000060 65 63 6b 69 6e 00 6c 6e 65 78 74 5f 63 68 65 63 |eckin.lnext_chec| +00000070 6b 69 6e 00 ff 66 62 6c 65 5f 6c 6c bf 64 70 72 |kin..fble_ll.dpr| +00000080 69 6f 00 63 74 69 64 01 65 73 74 61 74 65 02 66 |io.ctid.estate.f| +00000090 73 74 6b 75 73 65 18 3a 66 73 74 6b 73 69 7a 18 |stkuse.:fstksiz.| +000000a0 50 66 63 73 77 63 6e 74 19 ea 9c 67 72 75 6e 74 |Pfcswcnt...grunt| +000000b0 69 6d 65 19 09 45 6c 6c 61 73 74 5f 63 68 65 63 |ime..Ellast_chec| +000000c0 6b 69 6e 00 6c 6e 65 78 74 5f 63 68 65 63 6b 69 |kin.lnext_checki| +000000d0 6e 00 ff 6e 62 6c 65 75 61 72 74 5f 62 72 69 64 |n..nbleuart_brid| +000000e0 67 65 bf 64 70 72 69 6f 05 63 74 69 64 02 65 73 |ge.dprio.ctid.es| +000000f0 74 61 74 65 01 66 73 74 6b 75 73 65 18 1f 66 73 |tate.fstkuse..fs| +00000100 74 6b 73 69 7a 19 01 00 66 63 73 77 63 6e 74 1a |tksiz...fcswcnt.| +00000110 00 13 a9 83 67 72 75 6e 74 69 6d 65 00 6c 6c 61 |....gruntime.lla| +00000120 73 74 5f 63 68 65 63 6b 69 6e 00 6c 6e 65 78 74 |st_checkin.lnext| +00000130 5f 63 68 65 63 6b 69 6e 00 ff 67 62 6c 65 70 72 |_checkin..gblepr| +00000140 70 68 bf 64 70 72 69 6f 01 63 74 69 64 03 65 73 |ph.dprio.ctid.es| +00000150 74 61 74 65 01 66 73 74 6b 75 73 65 18 d3 66 73 |tate.fstkuse..fs| +00000160 74 6b 73 69 7a 19 01 50 66 63 73 77 63 6e 74 19 |tksiz..Pfcswcnt.| +00000170 0a 83 67 72 75 6e 74 69 6d 65 04 6c 6c 61 73 74 |..gruntime.llast| +00000180 5f 63 68 65 63 6b 69 6e 00 6c 6e 65 78 74 5f 63 |_checkin.lnext_c| +00000190 68 65 63 6b 69 6e 00 ff ff ff |heckin....| + +2016/11/11 12:45:44 [DEBUG] Deserialized response &{Op:1 Flags:1 Len:402 Group:0 Seq:0 Id:2 Data:[191 98 114 99 0 101 116 97 115 107 115 191 100 105 100 108 101 191 100 112 114 105 111 24 255 99 116 105 100 0 101 115 116 97 116 101 1 102 115 116 107 117 115 101 24 25 102 115 116 107 115 105 122 24 64 102 99 115 119 99 110 116 26 0 20 126 106 103 114 117 110 116 105 109 101 26 0 19 156 79 108 108 97 115 116 95 99 104 101 99 107 105 110 0 108 110 101 120 116 95 99 104 101 99 107 105 110 0 255 102 98 108 101 95 108 108 191 100 112 114 105 111 0 99 116 105 100 1 101 115 116 97 116 101 2 102 115 116 107 117 115 101 24 58 102 115 116 107 115 105 122 24 80 102 99 115 119 99 110 116 25 234 156 103 114 117 110 116 105 109 101 25 9 69 108 108 97 115 116 95 99 104 101 99 107 105 110 0 108 110 101 120 116 95 99 104 101 99 107 105 110 0 255 110 98 108 101 117 97 114 116 95 98 114 105 100 103 101 191 100 112 114 105 111 5 99 116 105 100 2 101 115 116 97 116 101 1 102 115 116 107 117 115 101 24 31 102 115 116 107 115 105 122 25 1 0 102 99 115 119 99 110 116 26 0 19 169 131 103 114 117 110 116 105 109 101 0 108 108 97 115 116 95 99 104 101 99 107 105 110 0 108 110 101 120 116 95 99 104 101 99 107 105 110 0 255 103 98 108 101 112 114 112 104 191 100 112 114 105 111 1 99 116 105 100 3 101 115 116 97 116 101 1 102 115 116 107 117 115 101 24 211 102 115 116 107 115 105 122 25 1 80 102 99 115 119 99 110 116 25 10 131 103 114 117 110 116 105 109 101 4 108 108 97 115 116 95 99 104 101 99 107 105 110 0 108 110 101 120 116 95 99 104 101 99 107 105 110 0 255 255 255]} +Return Code = 0 + task pri tid runtime csw stksz stkuse last_checkin next_checkin + bleuart_bridge 5 2 0 1288579 256 31 0 0 + bleprph 1 3 4 2691 336 211 0 0 + idle 255 0 1285199 1343082 64 25 0 0 + ble_ll 0 1 2373 60060 80 58 0 0 +``` + +#### Group Read Request: `image list` + +The following command lists images on the device and uses commands from `Group` +0x01 (`MGMT_GROUP_ID_IMAGE`), and was generated with `$ mcumgr -l DEBUG -c serial image list`: + +> See [img_mgmt](https://github.com/apache/mynewt-mcumgr/tree/master/cmd/img_mgmt) +for a full list of commands in the IMAGE `Group`. + +``` +$ mcumgr -l DEBUG -c serial image list +2016/11/11 12:25:51 [DEBUG] Writing newtmgr request &{Op:0 Flags:0 Len:0 Group:1 Seq:0 Id:0 Data:[]} +2016/11/11 12:25:51 [DEBUG] Serializing request &{Op:0 Flags:0 Len:0 Group:1 Seq:0 Id:0 Data:[]} into buffer [0 0 0 0 0 1 0 0] +2016/11/11 12:25:51 [DEBUG] Tx packet dump: +00000000 00 00 00 00 00 01 00 00 |........| + +2016/11/11 12:25:51 [DEBUG] Writing [6 9] to data channel +2016/11/11 12:25:51 [DEBUG] Writing [65 65 111 65 65 65 65 65 65 65 69 65 65 68 99 119] to data channel +2016/11/11 12:25:51 [DEBUG] Writing [10] to data channel +2016/11/11 12:25:51 [DEBUG] Reading [6 9 65 65 111 65 65 65 65 65 65 65 69 65 65 68 99 119] from data channel +2016/11/11 12:25:51 [DEBUG] Rx packet dump: +00000000 00 00 00 00 00 01 00 00 |........| + +2016/11/11 12:25:51 [DEBUG] Deserialized response &{Op:0 Flags:0 Len:0 Group:1 Seq:0 Id:0 Data:[]} +2016/11/11 12:25:51 [DEBUG] Reading [13] from data channel +2016/11/11 12:25:51 [DEBUG] Reading [6 9 65 73 85 66 65 81 66 55 65 65 69 65 65 76 57 109 97 87 49 104 90 50 86 122 110 55 57 107 99 50 120 118 100 65 66 110 100 109 86 121 99 50 108 118 98 109 85 119 76 106 77 117 77 71 82 111 89 88 78 111 87 67 68 83 84 76 77 70 69 49 81 88 75 55 85 81 110 53 121 48 114 110 104 104 50 87 49 113 47 102 120 71 50 48 103 115 54 121 48 48 113 75 101 79 48 71 104 105 98 50 57 48 89 87 74 115 90 102 86 110 99 71 86 117 90 71 108 117] from data channel +2016/11/11 12:25:51 [DEBUG] Reading [4 20 90 47 82 112 89 50 57 117 90 109 108 121 98 87 86 107 57 87 90 104 89 51 82 112 100 109 88 49 47 47 57 114 99 51 66 115 97 88 82 84 100 71 70 48 100 88 77 65 47 49 78 116] from data channel +2016/11/11 12:25:51 [DEBUG] Rx packet dump: +00000000 01 01 00 7b 00 01 00 00 bf 66 69 6d 61 67 65 73 |...{.....fimages| +00000010 9f bf 64 73 6c 6f 74 00 67 76 65 72 73 69 6f 6e |..dslot.gversion| +00000020 65 30 2e 33 2e 30 64 68 61 73 68 58 20 d2 4c b3 |e0.3.0dhashX .L.| +00000030 05 13 54 17 2b b5 10 9f 9c b4 ae 78 61 d9 6d 6a |..T.+......xa.mj| +00000040 fd fc 46 db 48 2c eb 2d 34 a8 a7 8e d0 68 62 6f |..F.H,.-4....hbo| +00000050 6f 74 61 62 6c 65 f5 67 70 65 6e 64 69 6e 67 f4 |otable.gpending.| +00000060 69 63 6f 6e 66 69 72 6d 65 64 f5 66 61 63 74 69 |iconfirmed.facti| +00000070 76 65 f5 ff ff 6b 73 70 6c 69 74 53 74 61 74 75 |ve...ksplitStatu| +00000080 73 00 ff |s..| + +2016/11/11 12:25:51 [DEBUG] Deserialized response &{Op:1 Flags:1 Len:123 Group:1 Seq:0 Id:0 Data:[191 102 105 109 97 103 101 115 159 191 100 115 108 111 116 0 103 118 101 114 115 105 111 110 101 48 46 51 46 48 100 104 97 115 104 88 32 210 76 179 5 19 84 23 43 181 16 159 156 180 174 120 97 217 109 106 253 252 70 219 72 44 235 45 52 168 167 142 208 104 98 111 111 116 97 98 108 101 245 103 112 101 110 100 105 110 103 244 105 99 111 110 102 105 114 109 101 100 245 102 97 99 116 105 118 101 245 255 255 107 115 112 108 105 116 83 116 97 116 117 115 0 255]} +Images: + slot=0 + version: 0.3.0 + bootable: true + flags: active confirmed + hash: d24cb3051354172bb5109f9cb4ae7861d96d6afdfc46db482ceb2d34a8a78ed0 +Split status: N/A +``` + +When serialised this will be sent as `0x00 0x00 0x00 0x00 0x00 0x01 0x00 0x00`. + +## Transports + +### Mcumgr/Newtmgr SMP Client Over Serial + +`mcumgr` or `newtmgr` can be used over TTY serial with the following parameters +to connect to a SMP server running on the target device: + +- Baud Rate: 115200 +- HW Flow Control: None + +### Mcumgr/Newtmgr SMP Client Over BLE + +`mcumgr` or `newtmgr` can be used over BLE with the following GATT service and +characteristic UUIDs to connect to a SMP server running on the target device: + +- **Service UUID**: `8D53DC1D-1DB7-4CD3-868B-8A527460AA84` +- **Characteristic UUID**: `DA2E7828-FBCE-4E01-AE9E-261174997C48` + +The "SMP" GATT service consists of one **write no-rsp characteristic** +for SMP requests: a single-byte characteristic that +can only accepts write-without-response commands. The contents of +each write command contains an SMP request. + +SMP responses are sent back in the form of unsolicited notifications +from the same characteristic. diff --git a/subsys/mgmt/mcumgr/lib/smp/CMakeLists.txt b/subsys/mgmt/mcumgr/lib/smp/CMakeLists.txt new file mode 100644 index 000000000000..b319bcf6c82e --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/smp/CMakeLists.txt @@ -0,0 +1,13 @@ +# +# Copyright (c) 2018-2021 mcumgr authors +# +# SPDX-License-Identifier: Apache-2.0 +# + +target_include_directories(MCUMGR INTERFACE + include +) + +zephyr_library_sources( + src/smp.c +) diff --git a/subsys/mgmt/mcumgr/lib/smp/include/smp/smp.h b/subsys/mgmt/mcumgr/lib/smp/include/smp/smp.h new file mode 100644 index 000000000000..9794d883c251 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/smp/include/smp/smp.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief SMP - Simple Management Protocol. + * + * SMP is a basic protocol that sits on top of the mgmt layer. SMP requests + * and responses have the following format: + * + * [Offset 0]: Mgmt header + * [Offset 8]: CBOR map of command-specific key-value pairs. + * + * SMP request packets may contain multiple concatenated requests. Each + * request must start at an offset that is a multiple of 4, so padding should + * be inserted between requests as necessary. Requests are processed + * sequentially from the start of the packet to the end. Each response is sent + * individually in its own packet. If a request elicits an error response, + * processing of the packet is aborted. + */ + +#ifndef H_SMP_ +#define H_SMP_ + +#include "mgmt/mgmt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct smp_streamer; +struct mgmt_hdr; + +/** @typedef smp_tx_rsp_fn + * @brief Transmits an SMP response packet. + * + * @param ss The streamer to transmit via. + * @param buf Buffer containing the response packet. + * @param arg Optional streamer argument. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +typedef int smp_tx_rsp_fn(struct smp_streamer *ss, void *buf, void *arg); + +/** + * @brief Decodes, encodes, and transmits SMP packets. + */ +struct smp_streamer { + struct mgmt_streamer mgmt_stmr; + smp_tx_rsp_fn *tx_rsp_cb; +}; + +/** + * @brief Processes a single SMP request packet and sends all corresponding responses. + * + * Processes all SMP requests in an incoming packet. Requests are processed + * sequentially from the start of the packet to the end. Each response is sent + * individually in its own packet. If a request elicits an error response, + * processing of the packet is aborted. This function consumes the supplied + * request buffer regardless of the outcome. + * + * @param streamer The streamer providing the required SMP callbacks. + * @param req The request packet to process. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +int smp_process_request_packet(struct smp_streamer *streamer, void *req); + +#ifdef __cplusplus +} +#endif + +#endif /* H_SMP_ */ diff --git a/subsys/mgmt/mcumgr/lib/smp/src/smp.c b/subsys/mgmt/mcumgr/lib/smp/src/smp.c new file mode 100644 index 000000000000..20d52924265e --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/smp/src/smp.c @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** SMP - Simple Management Protocol. */ + +#include +#include + +#include "tinycbor/cbor.h" +#include "mgmt/endian.h" +#include "mgmt/mgmt.h" +#include "smp/smp.h" + +static int +smp_align4(int x) +{ + int rem; + + rem = x % 4; + if (rem == 0) { + return x; + } else { + return x - rem + 4; + } +} + +/** + * Converts a request opcode to its corresponding response opcode. + */ +static uint8_t +smp_rsp_op(uint8_t req_op) +{ + if (req_op == MGMT_OP_READ) { + return MGMT_OP_READ_RSP; + } else { + return MGMT_OP_WRITE_RSP; + } +} + +static void +smp_init_rsp_hdr(const struct mgmt_hdr *req_hdr, struct mgmt_hdr *rsp_hdr) +{ + *rsp_hdr = (struct mgmt_hdr) { + .nh_len = 0, + .nh_flags = 0, + .nh_op = smp_rsp_op(req_hdr->nh_op), + .nh_group = req_hdr->nh_group, + .nh_seq = req_hdr->nh_seq, + .nh_id = req_hdr->nh_id, + }; +} + +static int +smp_read_hdr(struct smp_streamer *streamer, struct mgmt_hdr *dst_hdr) +{ + struct cbor_decoder_reader *reader; + + reader = streamer->mgmt_stmr.reader; + + if (reader->message_size < sizeof(*dst_hdr)) { + return MGMT_ERR_EINVAL; + } + + reader->cpy(reader, (char *)dst_hdr, 0, sizeof(*dst_hdr)); + return 0; +} + +static int +smp_write_hdr(struct smp_streamer *streamer, const struct mgmt_hdr *src_hdr) +{ + int rc; + + rc = mgmt_streamer_write_at(&streamer->mgmt_stmr, 0, src_hdr, sizeof(*src_hdr)); + return mgmt_err_from_cbor(rc); +} + +static int +smp_build_err_rsp(struct smp_streamer *streamer, + const struct mgmt_hdr *req_hdr, + int status) +{ + struct CborEncoder map; + struct mgmt_ctxt cbuf; + struct mgmt_hdr rsp_hdr; + int rc; + + rc = mgmt_ctxt_init(&cbuf, &streamer->mgmt_stmr); + if (rc != 0) { + return rc; + } + + smp_init_rsp_hdr(req_hdr, &rsp_hdr); + rc = smp_write_hdr(streamer, &rsp_hdr); + if (rc != 0) { + return rc; + } + + rc = cbor_encoder_create_map(&cbuf.encoder, &map, CborIndefiniteLength); + if (rc != 0) { + return rc; + } + + rc = mgmt_write_rsp_status(&cbuf, status); + if (rc != 0) { + return rc; + } + + rc = cbor_encoder_close_container(&cbuf.encoder, &map); + if (rc != 0) { + return rc; + } + + rsp_hdr.nh_len = cbor_encode_bytes_written(&cbuf.encoder) - MGMT_HDR_SIZE; + mgmt_hton_hdr(&rsp_hdr); + rc = smp_write_hdr(streamer, &rsp_hdr); + if (rc != 0) { + return rc; + } + + return 0; +} + +/** + * Processes a single SMP request and generates a response payload (i.e., + * everything after the management header). On success, the response payload + * is written to the supplied cbuf but not transmitted. On failure, no error + * response gets written; the caller is expected to build an error response + * from the return code. + * + * @param cbuf A cbuf containing the request and response buffer. + * @param req_hdr The management header belonging to the incoming request (host-byte order). + * + * @return A MGMT_ERR_[...] error code. + */ +static int +smp_handle_single_payload(struct mgmt_ctxt *cbuf, const struct mgmt_hdr *req_hdr, + bool *handler_found) +{ + const struct mgmt_handler *handler; + mgmt_handler_fn handler_fn; + struct CborEncoder payload_encoder; + int rc; + + handler = mgmt_find_handler(req_hdr->nh_group, req_hdr->nh_id); + if (handler == NULL) { + return MGMT_ERR_ENOTSUP; + } + + /* Begin response payload. Response fields are inserted into the root + * map as key value pairs. + */ + rc = cbor_encoder_create_map(&cbuf->encoder, &payload_encoder, CborIndefiniteLength); + rc = mgmt_err_from_cbor(rc); + if (rc != 0) { + return rc; + } + + switch (req_hdr->nh_op) { + case MGMT_OP_READ: + handler_fn = handler->mh_read; + break; + + case MGMT_OP_WRITE: + handler_fn = handler->mh_write; + break; + + default: + return MGMT_ERR_EINVAL; + } + + if (handler_fn) { + *handler_found = true; + mgmt_evt(MGMT_EVT_OP_CMD_RECV, req_hdr->nh_group, req_hdr->nh_id, NULL); + + rc = handler_fn(cbuf); + } else { + rc = MGMT_ERR_ENOTSUP; + } + + if (rc != 0) { + return rc; + } + + /* End response payload. */ + rc = cbor_encoder_close_container(&cbuf->encoder, &payload_encoder); + return mgmt_err_from_cbor(rc); +} + +/** + * Processes a single SMP request and generates a complete response (i.e., + * header and payload). On success, the response is written using the supplied + * streamer but not transmitted. On failure, no error response gets written; + * the caller is expected to build an error response from the return code. + * + * @param streamer The SMP streamer to use for reading the request and writing the response. + * @param req_hdr The management header belonging to the incoming request (host-byte order). + * + * @return A MGMT_ERR_[...] error code. + */ +static int +smp_handle_single_req(struct smp_streamer *streamer, const struct mgmt_hdr *req_hdr, + bool *handler_found) +{ + struct mgmt_ctxt cbuf; + struct mgmt_hdr rsp_hdr; + int rc; + + rc = mgmt_ctxt_init(&cbuf, &streamer->mgmt_stmr); + if (rc != 0) { + return rc; + } + + /* Write a dummy header to the beginning of the response buffer. Some + * fields will need to be fixed up later. + */ + smp_init_rsp_hdr(req_hdr, &rsp_hdr); + rc = smp_write_hdr(streamer, &rsp_hdr); + if (rc != 0) { + return rc; + } + + /* Process the request and write the response payload. */ + rc = smp_handle_single_payload(&cbuf, req_hdr, handler_found); + if (rc != 0) { + return rc; + } + + /* Fix up the response header with the correct length. */ + rsp_hdr.nh_len = cbor_encode_bytes_written(&cbuf.encoder) - MGMT_HDR_SIZE; + mgmt_hton_hdr(&rsp_hdr); + rc = smp_write_hdr(streamer, &rsp_hdr); + if (rc != 0) { + return rc; + } + + return 0; +} + +/** + * Attempts to transmit an SMP error response. This function consumes both + * supplied buffers. + * + * @param streamer The SMP streamer for building and transmitting the response. + * @param req_hdr The header of the request which elicited the error. + * @param req The buffer holding the request. + * @param rsp The buffer holding the response, or NULL if none was allocated. + * @param status The status to indicate in the error response. + */ +static void +smp_on_err(struct smp_streamer *streamer, const struct mgmt_hdr *req_hdr, + void *req, void *rsp, int status) +{ + int rc; + + /* Prefer the response buffer for holding the error response. If no + * response buffer was allocated, use the request buffer instead. + */ + if (rsp == NULL) { + rsp = req; + req = NULL; + } + + /* Clear the partial response from the buffer, if any. */ + mgmt_streamer_reset_buf(&streamer->mgmt_stmr, rsp); + mgmt_streamer_init_writer(&streamer->mgmt_stmr, rsp); + + /* Build and transmit the error response. */ + rc = smp_build_err_rsp(streamer, req_hdr, status); + if (rc == 0) { + streamer->tx_rsp_cb(streamer, rsp, streamer->mgmt_stmr.cb_arg); + rsp = NULL; + } + + /* Free any extra buffers. */ + mgmt_streamer_free_buf(&streamer->mgmt_stmr, req); + mgmt_streamer_free_buf(&streamer->mgmt_stmr, rsp); +} + +/** + * Processes all SMP requests in an incoming packet. Requests are processed + * sequentially from the start of the packet to the end. Each response is sent + * individually in its own packet. If a request elicits an error response, + * processing of the packet is aborted. This function consumes the supplied + * request buffer regardless of the outcome. + * + * @param streamer The streamer to use for reading, writing, and transmitting. + * @param req A buffer containing the request packet. + * + * @return 0 on success, MGMT_ERR_[...] code on failure. + */ +int +smp_process_request_packet(struct smp_streamer *streamer, void *req) +{ + struct mgmt_hdr req_hdr; + struct mgmt_evt_op_cmd_done_arg cmd_done_arg; + void *rsp; + bool valid_hdr, handler_found; + int rc; + + rsp = NULL; + valid_hdr = true; + + while (1) { + handler_found = false; + + rc = mgmt_streamer_init_reader(&streamer->mgmt_stmr, req); + if (rc != 0) { + valid_hdr = false; + break; + } + + /* Read the management header and strip it from the request. */ + rc = smp_read_hdr(streamer, &req_hdr); + if (rc != 0) { + valid_hdr = false; + break; + } + mgmt_ntoh_hdr(&req_hdr); + mgmt_streamer_trim_front(&streamer->mgmt_stmr, req, MGMT_HDR_SIZE); + + rsp = mgmt_streamer_alloc_rsp(&streamer->mgmt_stmr, req); + if (rsp == NULL) { + rc = MGMT_ERR_ENOMEM; + break; + } + + rc = mgmt_streamer_init_writer(&streamer->mgmt_stmr, rsp); + if (rc != 0) { + break; + } + + /* Process the request payload and build the response. */ + rc = smp_handle_single_req(streamer, &req_hdr, &handler_found); + if (rc != 0) { + break; + } + + /* Send the response. */ + rc = streamer->tx_rsp_cb(streamer, rsp, streamer->mgmt_stmr.cb_arg); + rsp = NULL; + if (rc != 0) { + break; + } + + /* Trim processed request to free up space for subsequent responses. */ + mgmt_streamer_trim_front(&streamer->mgmt_stmr, req, smp_align4(req_hdr.nh_len)); + + cmd_done_arg.err = MGMT_ERR_EOK; + mgmt_evt(MGMT_EVT_OP_CMD_DONE, req_hdr.nh_group, req_hdr.nh_id, + &cmd_done_arg); + } + + if (rc != 0 && valid_hdr) { + smp_on_err(streamer, &req_hdr, req, rsp, rc); + + if (handler_found) { + cmd_done_arg.err = rc; + mgmt_evt(MGMT_EVT_OP_CMD_DONE, req_hdr.nh_group, req_hdr.nh_id, + &cmd_done_arg); + } + + return rc; + } + + mgmt_streamer_free_buf(&streamer->mgmt_stmr, req); + mgmt_streamer_free_buf(&streamer->mgmt_stmr, rsp); + return 0; +} diff --git a/subsys/mgmt/mcumgr/lib/transport/smp-bluetooth.md b/subsys/mgmt/mcumgr/lib/transport/smp-bluetooth.md new file mode 100644 index 000000000000..029d995f0483 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/transport/smp-bluetooth.md @@ -0,0 +1,50 @@ +# SMP over Bluetooth + +This document specifies how the mcumgr Simple Management Procotol (SMP) is +transmitted over Bluetooth. + +## Overview + +All SMP communication utilizes a single GATT characteristic. An SMP request is +sent in the form of either 1) a GATT Write Command, or 2) a GATT Write Without +Response command. An SMP response is sent in the form of a GATT Notification +specifying the same characteristic that was written. + +If an SMP request or response is too large to fit in a single GATT command, the +sender fragments it across several commands. No additional framing is +introduced when a request or response is fragmented; the payload is simply +split among several commands. Since Bluetooth guarantees ordered delivery of +packets, the SMP header in the first fragment contains sufficient information +for reassembly. + +## Services + +### SMP service + +UUID: `8D53DC1D-1DB7-4CD3-868B-8A527460AA84` + +### Characteristics + +#### SMP Characteristic + +| Field | Value | +| ----- | ----------------------------------------------------------------- | +| Name | SMP | +| Description | Used for both SMP requests and responses. | +| Read | Excluded | +| Write | Mandatory | +| WriteWithoutResponse | Mandatory | +| SignedWrite | Excluded | +| Notify | Mandatory | +| Indicate | Excluded | +| WritableAuxiliaries | Excluded | +| Broadcast | Excluded | +| ExtendedProperties | | + +As indicated, SMP requests can be sent in the form of either a Write or a Write +Without Response. The Write Without Response form is generally preferred, as +an application-layer response is always sent in the form of a Notification. +The regular Write form is accepted in case the client requires a GATT response +to initiate pairing. + +Security for this characteristic is optional. diff --git a/subsys/mgmt/mcumgr/lib/transport/smp-console.md b/subsys/mgmt/mcumgr/lib/transport/smp-console.md new file mode 100644 index 000000000000..0b12f3c8d56e --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/transport/smp-console.md @@ -0,0 +1,57 @@ +# SMP over console + +This document specifies how the mcumgr Simple Management Procotol (SMP) is +transmitted over text consoles. + +## Overview + +Mcumgr packets sent over serial are fragmented into frames of 127 bytes or +fewer. This 127-byte maximum applies to the entire frame, including header, +CRC, and terminating newline. + +The initial frame in a packet has the following format: + +``` + offset 0: 0x06 0x09 + === Begin base64 encoding === + offset 2: <16-bit packet-length> + offset ?: + offset ?: (if final frame) + === End base64 encoding === + offset ?: 0x0a (newline) +``` + +All subsequent frames have the following format: + +``` + offset 0: 0x04 0x14 + === Begin base64 encoding === + offset 2: + offset ?: (if final frame) + === End base64 encoding === + offset ?: 0x0a (newline) +``` + +All integers are represented in big-endian. The packet fields are described +below: + +| Field | Description | +| ----- | ----------- | +| 0x06 0x09 | Byte pair indicating the start of a packet. | +| 0x04 0x14 | Byte pair indicating the start of a continuation frame. | +| Packet length | The combined total length of the *unencoded* body plus the final CRC (2 bytes). Length is in Big-Endian format. | +| Body | The actual SMP data (i.e., 8-byte header and CBOR key-value map). | +| CRC16 | A CRC16 of the *unencoded* body of the entire packet. This field is only present in the final frame of a packet. | +| Newline | A 0x0a byte; terminates a frame. | + +The packet is fully received when bytes of body has been +received. + +## CRC details + +The CRC16 should be calculated with the following parameters: + +| Field | Value | +| ------------- | ------------- | +| Polynomial | 0x1021 | +| Initial Value | 0 | diff --git a/subsys/mgmt/mcumgr/lib/util/CMakeLists.txt b/subsys/mgmt/mcumgr/lib/util/CMakeLists.txt new file mode 100644 index 000000000000..4ebec069d380 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/util/CMakeLists.txt @@ -0,0 +1,13 @@ +# +# Copyright (c) 2018-2021 mcumgr authors +# +# SPDX-License-Identifier: Apache-2.0 +# + +target_include_directories(MCUMGR INTERFACE + include +) + +zephyr_library_sources( + src/mcumgr_util.c +) diff --git a/subsys/mgmt/mcumgr/lib/util/include/util/mcumgr_util.h b/subsys/mgmt/mcumgr/lib/util/include/util/mcumgr_util.h new file mode 100644 index 000000000000..c9cff6601b01 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/util/include/util/mcumgr_util.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef H_MCUMGR_UTIL_ +#define H_MCUMGR_UTIL_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Converts an unsigned long long to a null-terminated string. + * + * @param val The source number to convert. + * @param dst_max_len The size, in bytes, of the destination buffer. + * @param dst The destination buffer. + * + * @return The length of the resulting string on success; + * -1 if the buffer is too small. + */ +int ull_to_s(unsigned long long val, int dst_max_len, char *dst); + +/** + * @brief Converts a long long to a null-terminated string. + * + * @param val The source number to convert. + * @param dst_max_len The size, in bytes, of the destination buffer. + * @param dst The destination buffer. + * + * @return The length of the resulting string on success; + * -1 if the buffer is too small. + */ +int ll_to_s(long long val, int dst_max_len, char *dst); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/subsys/mgmt/mcumgr/lib/util/src/mcumgr_util.c b/subsys/mgmt/mcumgr/lib/util/src/mcumgr_util.c new file mode 100644 index 000000000000..d1a6dc4cbb83 --- /dev/null +++ b/subsys/mgmt/mcumgr/lib/util/src/mcumgr_util.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "util/mcumgr_util.h" + +int +ull_to_s(unsigned long long val, int dst_max_len, char *dst) +{ + unsigned long copy; + int digit; + int off; + int len; + + /* First, calculate the length of the resulting string. */ + copy = val; + for (len = 0; copy != 0; len++) { + copy /= 10; + } + + /* A value of 0 still requires one character ("0"). */ + if (len == 0) { + len = 1; + } + + /* Ensure the buffer can accommodate the string and terminator. */ + if (len >= dst_max_len - 1) { + return -1; + } + + /* Encode the string from right to left. */ + off = len; + dst[off--] = '\0'; + do { + digit = val % 10; + dst[off--] = '0' + digit; + + val /= 10; + } while (val > 0); + + return len; +} + +int +ll_to_s(long long val, int dst_max_len, char *dst) +{ + unsigned long long ull; + + if (val < 0) { + if (dst_max_len < 1) { + return -1; + } + + dst[0] = '-'; + dst_max_len--; + dst++; + + ull = -val; + } else { + ull = val; + } + + return ull_to_s(ull, dst_max_len, dst); +} diff --git a/west.yml b/west.yml index b66a1f93dde2..fe197227fbd1 100644 --- a/west.yml +++ b/west.yml @@ -170,9 +170,6 @@ manifest: - name: mcuboot revision: c61538748ead773ea75a551a7beee299228bdcaf path: bootloader/mcuboot - - name: mcumgr - revision: c854c85ec75d624c47058bc4ac0ab9b12e2dd0e7 - path: modules/lib/mcumgr - name: mipi-sys-t path: modules/debug/mipi-sys-t groups: