You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by cc...@apache.org on 2018/01/18 01:36:44 UTC

[mynewt-mcumgr] 06/11: OS-agnostic command handlers: fs, img, os.

This is an automated email from the ASF dual-hosted git repository.

ccollins pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-mcumgr.git

commit 83a748308658d5adbfe28e0a34ff2a8f039b26ac
Author: Christopher Collins <cc...@apache.org>
AuthorDate: Wed Jan 17 17:12:52 2018 -0800

    OS-agnostic command handlers: fs, img, os.
---
 cmd/fs_mgmt/include/fs_mgmt/fs_mgmt.h         |  41 +++
 cmd/fs_mgmt/include/fs_mgmt/fs_mgmt_impl.h    |  77 +++++
 cmd/fs_mgmt/src/fs_mgmt.c                     | 250 ++++++++++++++++
 cmd/fs_mgmt/src/fs_mgmt_config.h              |  26 ++
 cmd/fs_mgmt/src/stubs.c                       |  46 +++
 cmd/img_mgmt/include/img_mgmt/image.h         |  82 ++++++
 cmd/img_mgmt/include/img_mgmt/img_mgmt.h      |  49 ++++
 cmd/img_mgmt/include/img_mgmt/img_mgmt_impl.h | 107 +++++++
 cmd/img_mgmt/src/img_mgmt.c                   | 400 ++++++++++++++++++++++++++
 cmd/img_mgmt/src/img_mgmt_config.h            |  22 ++
 cmd/img_mgmt/src/img_mgmt_priv.h              |  96 +++++++
 cmd/img_mgmt/src/img_mgmt_state.c             | 315 ++++++++++++++++++++
 cmd/img_mgmt/src/img_mgmt_util.c              |  39 +++
 cmd/img_mgmt/src/stubs.c                      |  64 +++++
 cmd/os_mgmt/include/os_mgmt/os_mgmt.h         |  64 +++++
 cmd/os_mgmt/include/os_mgmt/os_mgmt_impl.h    |  62 ++++
 cmd/os_mgmt/src/os_mgmt.c                     | 190 ++++++++++++
 cmd/os_mgmt/src/os_mgmt_config.h              |  22 ++
 cmd/os_mgmt/src/stubs.c                       |  38 +++
 19 files changed, 1990 insertions(+)

diff --git a/cmd/fs_mgmt/include/fs_mgmt/fs_mgmt.h b/cmd/fs_mgmt/include/fs_mgmt/fs_mgmt.h
new file mode 100644
index 0000000..38173fd
--- /dev/null
+++ b/cmd/fs_mgmt/include/fs_mgmt/fs_mgmt.h
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#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/cmd/fs_mgmt/include/fs_mgmt/fs_mgmt_impl.h b/cmd/fs_mgmt/include/fs_mgmt/fs_mgmt_impl.h
new file mode 100644
index 0000000..3cb30b8
--- /dev/null
+++ b/cmd/fs_mgmt/include/fs_mgmt/fs_mgmt_impl.h
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * @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/cmd/fs_mgmt/src/fs_mgmt.c b/cmd/fs_mgmt/src/fs_mgmt.c
new file mode 100644
index 0000000..2ce7fc2
--- /dev/null
+++ b/cmd/fs_mgmt/src/fs_mgmt.c
@@ -0,0 +1,250 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <limits.h>
+#include <string.h>
+#include "cborattr/cborattr.h"
+#include "mgmt/mgmt.h"
+#include "fs_mgmt/fs_mgmt.h"
+#include "fs_mgmt/fs_mgmt_impl.h"
+#include "fs_mgmt_config.h"
+
+static mgmt_handler_fn fs_mgmt_file_download;
+static mgmt_handler_fn fs_mgmt_file_upload;
+
+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[] = {
+    [FS_MGMT_ID_FILE] = {
+        .mh_read = fs_mgmt_file_download,
+        .mh_write = fs_mgmt_file_upload,
+    },
+};
+
+#define FS_MGMT_HANDLER_CNT \
+    sizeof fs_mgmt_handlers / sizeof fs_mgmt_handlers[0]
+
+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,
+};
+
+/**
+ * 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);
+}
+
+void
+fs_mgmt_register_group(void)
+{
+    mgmt_register_group(&fs_mgmt_group);
+}
diff --git a/cmd/fs_mgmt/src/fs_mgmt_config.h b/cmd/fs_mgmt/src/fs_mgmt_config.h
new file mode 100644
index 0000000..81fb7dd
--- /dev/null
+++ b/cmd/fs_mgmt/src/fs_mgmt_config.h
@@ -0,0 +1,26 @@
+#ifndef H_FS_MGMT_CONFIG_
+#define H_FS_MGMT_CONFIG_
+
+#if defined MYNEWT
+
+#include "syscfg/syscfg.h"
+
+#define FS_MGMT_DL_CHUNK_SIZE   MYNEWT_VAL(FS_MGMT_DL_CHUNK_SIZE)
+#define FS_MGMT_PATH_SIZE       MYNEWT_VAL(FS_MGMT_PATH_SIZE)
+#define FS_MGMT_UL_CHUNK_SIZE   MYNEWT_VAL(FS_MGMT_UL_CHUNK_SIZE)
+
+#elif defined __ZEPHYR__
+
+#define FS_MGMT_DL_CHUNK_SIZE   CONFIG_FS_MGMT_DL_CHUNK_SIZE
+#define FS_MGMT_PATH_SIZE       CONFIG_FS_MGMT_PATH_SIZE
+#define FS_MGMT_UL_CHUNK_SIZE   CONFIG_FS_MGMT_UL_CHUNK_SIZE
+
+#else
+
+/* No direct support for this OS.  The application needs to define the above
+ * settings itself.
+ */
+
+#endif
+
+#endif
diff --git a/cmd/fs_mgmt/src/stubs.c b/cmd/fs_mgmt/src/stubs.c
new file mode 100644
index 0000000..8b6ab83
--- /dev/null
+++ b/cmd/fs_mgmt/src/stubs.c
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * These stubs get linked in when there is no equivalent OS-specific
+ * implementation.
+ */
+
+#include "mgmt/mgmt.h"
+#include "fs_mgmt/fs_mgmt_impl.h"
+
+int  __attribute__((weak))
+fs_mgmt_impl_filelen(const char *path, size_t *out_len)
+{
+    return MGMT_ERR_ENOTSUP;
+}
+
+int __attribute__((weak))
+fs_mgmt_impl_read(const char *path, size_t offset, size_t len,
+                  void *out_data, size_t *out_len)
+{
+    return MGMT_ERR_ENOTSUP;
+}
+
+int __attribute__((weak))
+fs_mgmt_impl_write(const char *path, size_t offset, const void *data,
+                   size_t len)
+{
+    return MGMT_ERR_ENOTSUP;
+}
diff --git a/cmd/img_mgmt/include/img_mgmt/image.h b/cmd/img_mgmt/include/img_mgmt/image.h
new file mode 100644
index 0000000..d390783
--- /dev/null
+++ b/cmd/img_mgmt/include/img_mgmt/image.h
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef H_IMAGE_
+#define H_IMAGE_
+
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define IMAGE_MAGIC                 0x96f3b83d
+#define IMAGE_TLV_INFO_MAGIC        0x6907
+
+#define IMAGE_HEADER_SIZE           32
+
+/** Image header flags. */
+#define IMAGE_F_NON_BOOTABLE        0x00000010 /* Split image app. */
+
+/** 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/cmd/img_mgmt/include/img_mgmt/img_mgmt.h b/cmd/img_mgmt/include/img_mgmt/img_mgmt.h
new file mode 100644
index 0000000..865964d
--- /dev/null
+++ b/cmd/img_mgmt/include/img_mgmt/img_mgmt.h
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef H_IMG_MGMT_
+#define H_IMG_MGMT_
+
+#include <inttypes.h>
+struct image_version;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * 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
+
+/**
+ * @brief Registers the image management command handler group.
+ */ 
+void img_mgmt_register_group(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_IMG_MGMT_ */
diff --git a/cmd/img_mgmt/include/img_mgmt/img_mgmt_impl.h b/cmd/img_mgmt/include/img_mgmt/img_mgmt_impl.h
new file mode 100644
index 0000000..d0898ef
--- /dev/null
+++ b/cmd/img_mgmt/include/img_mgmt/img_mgmt_impl.h
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * @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_
+
+#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.
+ *
+ * @return                      An IMG_MGMT_SWAP_TYPE_[...] code.
+ */
+int img_mgmt_impl_swap_type(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/cmd/img_mgmt/src/img_mgmt.c b/cmd/img_mgmt/src/img_mgmt.c
new file mode 100644
index 0000000..fe91397
--- /dev/null
+++ b/cmd/img_mgmt/src/img_mgmt.c
@@ -0,0 +1,400 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <limits.h>
+#include <assert.h>
+#include <string.h>
+
+#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_config.h"
+
+static mgmt_handler_fn img_mgmt_upload;
+static mgmt_handler_fn img_mgmt_erase;
+
+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
+    },
+};
+
+#define IMG_MGMT_HANDLER_CNT \
+    sizeof(img_mgmt_handlers) / sizeof(img_mgmt_handlers[0])
+
+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,
+};
+
+static struct {
+    /* Whether an upload is currently in progress. */
+    bool uploading;
+
+    /** Expected offset of next upload request. */
+    size_t off;
+
+    /** Total length of image currently being uploaded. */
+    size_t len;
+} img_mgmt_ctxt;
+
+/**
+ * Finds the TLVs in the specified image slot, if any.
+ */
+static int
+img_mgmt_find_tlvs(const struct image_header *hdr,
+                   int slot, size_t *start_off, size_t *end_off)
+{
+    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 != IMAGE_TLV_INFO_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)
+{
+    struct image_header hdr;
+    struct image_tlv tlv;
+    size_t data_off;
+    size_t data_end;
+    bool hash_found;
+    int rc;
+
+    rc = img_mgmt_impl_read(image_slot, 0, &hdr, sizeof hdr);
+    if (rc != 0) {
+        return MGMT_ERR_EUNKNOWN;
+    }
+
+    if (ver != NULL) {
+        memset(ver, 0xff, sizeof(*ver));
+    }
+    if (hdr.ih_magic == IMAGE_MAGIC) {
+        if (ver != NULL) {
+            memcpy(ver, &hdr.ih_ver, sizeof(*ver));
+        }
+    } else if (hdr.ih_magic == 0xffffffff) {
+        return MGMT_ERR_ENOENT;
+    } else {
+        return MGMT_ERR_EUNKNOWN;
+    }
+
+    if (flags != NULL) {
+        *flags = hdr.ih_flags;
+    }
+
+    /* Read the image's TLVs.  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(&hdr, image_slot, &data_off, &data_end);
+    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; 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; i++) {
+        if (img_mgmt_read_info(i, ver, hash, NULL) != 0) {
+            continue;
+        }
+        if (!memcmp(hash, find, IMAGE_HASH_LEN)) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+/**
+ * Command handler: image erase
+ */
+static int
+img_mgmt_erase(struct mgmt_ctxt *ctxt)
+{
+    CborError err;
+    int rc;
+
+    rc = img_mgmt_impl_erase_slot();
+
+    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;
+}
+
+/**
+ * Encodes an image upload response.
+ */
+static int
+img_mgmt_encode_upload_rsp(struct mgmt_ctxt *ctxt, int status)
+{
+    CborError err;
+
+    err = 0;
+    err |= cbor_encode_text_stringz(&ctxt->encoder, "rc");
+    err |= cbor_encode_int(&ctxt->encoder, status);
+    err |= cbor_encode_text_stringz(&ctxt->encoder, "off");
+    err |= cbor_encode_int(&ctxt->encoder, img_mgmt_ctxt.off);
+
+    if (err != 0) {
+        return MGMT_ERR_ENOMEM;
+    }
+    return 0;
+}
+
+/**
+ * Processes an upload request specifying an offset of 0 (i.e., the first image
+ * chunk).  The caller is responsible for encoding the response.
+ */
+static int
+img_mgmt_upload_first_chunk(struct mgmt_ctxt *ctxt, const uint8_t *req_data,
+                            size_t len)
+{
+    struct image_header hdr;
+    int rc;
+
+    if (len < sizeof hdr) {
+        return MGMT_ERR_EINVAL;
+    }
+
+    memcpy(&hdr, req_data, sizeof hdr);
+    if (hdr.ih_magic != IMAGE_MAGIC) {
+        return MGMT_ERR_EINVAL;
+    }
+
+    if (img_mgmt_slot_in_use(1)) {
+        /* No free slot. */
+        return MGMT_ERR_ENOMEM;
+    }
+
+    rc = img_mgmt_impl_erase_slot();
+    if (rc != 0) {
+        return rc;
+    }
+
+    img_mgmt_ctxt.uploading = true;
+    img_mgmt_ctxt.off = 0;
+    img_mgmt_ctxt.len = 0;
+
+    return 0;
+}
+
+/**
+ * Command handler: image upload
+ */
+static int
+img_mgmt_upload(struct mgmt_ctxt *ctxt)
+{
+    uint8_t img_mgmt_data[IMG_MGMT_UL_CHUNK_SIZE];
+    unsigned long long len;
+    unsigned long long off;
+    size_t data_len;
+    size_t new_off;
+    bool last;
+    int rc;
+
+    const struct cbor_attr_t off_attr[4] = {
+        [0] = {
+            .attribute = "data",
+            .type = CborAttrByteStringType,
+            .addr.bytestring.data = img_mgmt_data,
+            .addr.bytestring.len = &data_len,
+            .len = sizeof(img_mgmt_data)
+        },
+        [1] = {
+            .attribute = "len",
+            .type = CborAttrUnsignedIntegerType,
+            .addr.uinteger = &len,
+            .nodefault = true
+        },
+        [2] = {
+            .attribute = "off",
+            .type = CborAttrUnsignedIntegerType,
+            .addr.uinteger = &off,
+            .nodefault = true
+        },
+        [3] = { 0 },
+    };
+
+    len = ULLONG_MAX;
+    off = ULLONG_MAX;
+    data_len = 0;
+    rc = cbor_read_object(&ctxt->it, off_attr);
+    if (rc || off == ULLONG_MAX) {
+        return MGMT_ERR_EINVAL;
+    }
+
+    if (off == 0) {
+        /* Total image length is a required field in the first request. */
+        if (len == ULLONG_MAX) {
+            return MGMT_ERR_EINVAL;
+        }
+
+        rc = img_mgmt_upload_first_chunk(ctxt, img_mgmt_data, data_len);
+        if (rc != 0) {
+            return rc;
+        }
+        img_mgmt_ctxt.len = len;
+    } else {
+        if (!img_mgmt_ctxt.uploading) {
+            return MGMT_ERR_EINVAL;
+        }
+
+        if (off != img_mgmt_ctxt.off) {
+            /* Invalid offset.  Drop the data and send the expected offset. */
+            return img_mgmt_encode_upload_rsp(ctxt, 0);
+        }
+    }
+
+    new_off = img_mgmt_ctxt.off + data_len;
+    if (new_off > img_mgmt_ctxt.len) {
+        /* Data exceeds image length. */
+        return MGMT_ERR_EINVAL;
+    }
+    last = new_off == img_mgmt_ctxt.len;
+
+    if (data_len > 0) {
+        rc = img_mgmt_impl_write_image_data(off, img_mgmt_data, data_len,
+                                            last);
+        if (rc != 0) {
+            return rc;
+        }
+    }
+
+    img_mgmt_ctxt.off = new_off;
+    if (last) {
+        /* Upload complete. */
+        img_mgmt_ctxt.uploading = false;
+    }
+
+    return img_mgmt_encode_upload_rsp(ctxt, 0);
+}
+
+void
+img_mgmt_register_group(void)
+{
+    mgmt_register_group(&img_mgmt_group);
+}
diff --git a/cmd/img_mgmt/src/img_mgmt_config.h b/cmd/img_mgmt/src/img_mgmt_config.h
new file mode 100644
index 0000000..4ec8295
--- /dev/null
+++ b/cmd/img_mgmt/src/img_mgmt_config.h
@@ -0,0 +1,22 @@
+#ifndef H_IMG_MGMT_CONFIG_
+#define H_IMG_MGMT_CONFIG_
+
+#if defined MYNEWT
+
+#include "syscfg/syscfg.h"
+
+#define IMG_MGMT_UL_CHUNK_SIZE  MYNEWT_VAL(IMG_MGMT_UL_CHUNK_SIZE)
+
+#elif defined __ZEPHYR__
+
+#define IMG_MGMT_UL_CHUNK_SIZE  CONFIG_IMG_MGMT_UL_CHUNK_SIZE
+
+#else
+
+/* No direct support for this OS.  The application needs to define the above
+ * settings itself.
+ */
+
+#endif
+
+#endif
diff --git a/cmd/img_mgmt/src/img_mgmt_priv.h b/cmd/img_mgmt/src/img_mgmt_priv.h
new file mode 100644
index 0000000..a8a3a28
--- /dev/null
+++ b/cmd/img_mgmt/src/img_mgmt_priv.h
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef H_IMG_PRIV_
+#define H_IMG_PRIV_
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#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
+
+/*
+ * Response to list:
+ * {
+ *      "images":[ <version1>, <version2>]
+ * }
+ *
+ *
+ * Request to boot to version:
+ * {
+ *      "test":<version>
+ * }
+ *
+ *
+ * Response to boot read:
+ * {
+ *	"test":<version>,
+ *	"main":<version>,
+ *      "active":<version>
+ * }
+ *
+ *
+ * Request to image upload:
+ * {
+ *      "off":<offset>,
+ *      "len":<img_size>		inspected when off = 0
+ *      "data":<base64encoded binary>
+ * }
+ *
+ *
+ * Response to upload:
+ * {
+ *      "off":<offset>
+ * }
+ *
+ *
+ * Request to image upload:
+ * {
+ *      "off":<offset>
+ *	    "name":<filename>		inspected when off = 0
+ *      "len":<file_size>		inspected when off = 0
+ *      "data":<base64encoded binary>
+ * }
+ */
+
+struct mgmt_ctxt;
+
+int img_mgmt_core_erase(struct mgmt_ctxt *);
+int img_mgmt_core_list(struct mgmt_ctxt *);
+int img_mgmt_core_load(struct mgmt_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_read_info(int image_slot, struct image_version *ver,
+                       uint8_t *hash, uint32_t *flags);
+int img_mgmt_slot_in_use(int slot);
+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/cmd/img_mgmt/src/img_mgmt_state.c b/cmd/img_mgmt/src/img_mgmt_state.c
new file mode 100644
index 0000000..bca3aed
--- /dev/null
+++ b/cmd/img_mgmt/src/img_mgmt_state.c
@@ -0,0 +1,315 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <assert.h>
+#include "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"
+
+#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 */
+
+/**
+ * Collects information about the specified image slot.
+ */
+static uint8_t
+img_mgmt_state_flags(int query_slot)
+{
+    uint8_t flags;
+    int swap_type;
+
+    assert(query_slot == 0 || query_slot == 1);
+
+    flags = 0;
+
+    /* Determine if this is is pending or confirmed (only applicable for
+     * unified images and loaders.
+     */
+    swap_type = img_mgmt_impl_swap_type();
+    switch (swap_type) {
+    case IMG_MGMT_SWAP_TYPE_NONE:
+        if (query_slot == 0) {
+            flags |= IMG_MGMT_STATE_F_CONFIRMED;
+            flags |= IMG_MGMT_STATE_F_ACTIVE;
+        }
+        break;
+
+    case IMG_MGMT_SWAP_TYPE_TEST:
+        if (query_slot == 0) {
+            flags |= IMG_MGMT_STATE_F_CONFIRMED;
+        } else if (query_slot == 1) {
+            flags |= IMG_MGMT_STATE_F_PENDING;
+        }
+        break;
+
+    case IMG_MGMT_SWAP_TYPE_PERM:
+        if (query_slot == 0) {
+            flags |= IMG_MGMT_STATE_F_CONFIRMED;
+        } else if (query_slot == 1) {
+            flags |= IMG_MGMT_STATE_F_PENDING | IMG_MGMT_STATE_F_PERMANENT;
+        }
+        break;
+
+    case IMG_MGMT_SWAP_TYPE_REVERT:
+        if (query_slot == 0) {
+            flags |= IMG_MGMT_STATE_F_ACTIVE;
+        } else if (query_slot == 1) {
+            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 == 0) {
+        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.
+ */
+static 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 state_flags;
+    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) {
+        return MGMT_ERR_EBADSTATE;
+    }
+
+    rc = img_mgmt_impl_write_pending(slot, permanent);
+    if (rc != 0) {
+        return MGMT_ERR_EUNKNOWN;
+    }
+
+    return 0;
+}
+
+/**
+ * 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()) {
+        return MGMT_ERR_EBADSTATE;
+    }
+
+    rc = img_mgmt_impl_write_confirmed();
+    if (rc != 0) {
+        return MGMT_ERR_EUNKNOWN;
+    }
+
+    return 0;
+}
+
+/**
+ * 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; 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);
+        err |= cbor_encode_text_stringz(&image, "slot");
+        err |= cbor_encode_int(&image, i);
+
+        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);
+
+        err |= cbor_encode_text_stringz(&image, "bootable");
+        err |= cbor_encode_boolean(&image, !(flags & IMAGE_F_NON_BOOTABLE));
+
+        err |= cbor_encode_text_stringz(&image, "pending");
+        err |= cbor_encode_boolean(&image,
+                                     state_flags & IMG_MGMT_STATE_F_PENDING);
+
+        err |= cbor_encode_text_stringz(&image, "confirmed");
+        err |= cbor_encode_boolean(&image,
+                                     state_flags & IMG_MGMT_STATE_F_CONFIRMED);
+
+        err |= cbor_encode_text_stringz(&image, "active");
+        err |= cbor_encode_boolean(&image,
+                                     state_flags & IMG_MGMT_STATE_F_ACTIVE);
+
+        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);
+
+    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)
+{
+    uint8_t hash[IMAGE_HASH_LEN];
+    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 = 0;
+        } 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 == 0 && 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/cmd/img_mgmt/src/img_mgmt_util.c b/cmd/img_mgmt/src/img_mgmt_util.c
new file mode 100644
index 0000000..5889f61
--- /dev/null
+++ b/cmd/img_mgmt/src/img_mgmt_util.c
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "img_mgmt/image.h"
+#include "img_mgmt/img_mgmt.h"
+
+int
+img_mgmt_ver_str(const struct image_version *ver, char *dst)
+{
+    if (ver->iv_build_num != 0) {
+        return sprintf(dst, "%u.%u.%u.%lu",
+          ver->iv_major, ver->iv_minor, ver->iv_revision,
+          (unsigned long)ver->iv_build_num);
+    } else {
+        return sprintf(dst, "%u.%u.%u",
+          ver->iv_major, ver->iv_minor, ver->iv_revision);
+    }
+}
diff --git a/cmd/img_mgmt/src/stubs.c b/cmd/img_mgmt/src/stubs.c
new file mode 100644
index 0000000..fa440dc
--- /dev/null
+++ b/cmd/img_mgmt/src/stubs.c
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * These stubs get linked in when there is no equivalent OS-specific
+ * implementation.
+ */
+
+#include "mgmt/mgmt.h"
+#include "img_mgmt/img_mgmt_impl.h"
+
+int __attribute__((weak))
+img_mgmt_impl_erase_slot(void)
+{
+    return MGMT_ERR_ENOTSUP;
+}
+
+int __attribute__((weak))
+img_mgmt_impl_write_pending(int slot, bool permanent)
+{
+    return MGMT_ERR_ENOTSUP;
+}
+
+int __attribute__((weak))
+img_mgmt_impl_write_confirmed(void)
+{
+    return MGMT_ERR_ENOTSUP;
+}
+
+int __attribute__((weak))
+img_mgmt_impl_read(int slot, unsigned int offset, void *dst,
+                   unsigned int num_bytes)
+{
+    return MGMT_ERR_ENOTSUP;
+}
+
+int __attribute__((weak))
+img_mgmt_impl_write_image_data(unsigned int offset, const void *data,
+                               unsigned int num_bytes, bool last)
+{
+    return MGMT_ERR_ENOTSUP;
+}
+
+int __attribute__((weak))
+img_mgmt_impl_swap_type(void)
+{
+    return MGMT_ERR_ENOTSUP;
+}
diff --git a/cmd/os_mgmt/include/os_mgmt/os_mgmt.h b/cmd/os_mgmt/include/os_mgmt/os_mgmt.h
new file mode 100644
index 0000000..493d227
--- /dev/null
+++ b/cmd/os_mgmt/include/os_mgmt/os_mgmt.h
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef H_OS_MGMT_
+#define H_OS_MGMT_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct mgmt_ctxt;
+
+/**
+ * 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/cmd/os_mgmt/include/os_mgmt/os_mgmt_impl.h b/cmd/os_mgmt/include/os_mgmt/os_mgmt_impl.h
new file mode 100644
index 0000000..252ba75
--- /dev/null
+++ b/cmd/os_mgmt/include/os_mgmt/os_mgmt_impl.h
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * @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/cmd/os_mgmt/src/os_mgmt.c b/cmd/os_mgmt/src/os_mgmt.c
new file mode 100644
index 0000000..ed359d8
--- /dev/null
+++ b/cmd/os_mgmt/src/os_mgmt.c
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include "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_config.h"
+
+static mgmt_handler_fn os_mgmt_echo;
+static mgmt_handler_fn os_mgmt_reset;
+static mgmt_handler_fn os_mgmt_taskstat_read;
+
+static const struct mgmt_handler os_mgmt_group_handlers[] = {
+    [OS_MGMT_ID_ECHO] = {
+        os_mgmt_echo, os_mgmt_echo
+    },
+    [OS_MGMT_ID_TASKSTAT] = {
+        os_mgmt_taskstat_read, NULL
+    },
+    [OS_MGMT_ID_RESET] = {
+        NULL, os_mgmt_reset
+    },
+};
+
+#define OS_MGMT_GROUP_SZ    \
+    (sizeof os_mgmt_group_handlers / sizeof os_mgmt_group_handlers[0])
+
+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,
+};
+
+/**
+ * Command handler: os 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;
+}
+
+/**
+ * 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) {
+            return rc;
+        }
+    }
+
+    err = cbor_encoder_close_container(&ctxt->encoder, &tasks_map);
+    if (err != 0) {
+        return MGMT_ERR_ENOMEM;
+    }
+
+    return 0;
+}
+
+/**
+ * Command handler: os reset
+ */
+static int
+os_mgmt_reset(struct mgmt_ctxt *ctxt)
+{
+    return os_mgmt_impl_reset(OS_MGMT_RESET_MS);
+}
+
+void
+os_mgmt_register_group(void)
+{
+    mgmt_register_group(&os_mgmt_group);
+}
diff --git a/cmd/os_mgmt/src/os_mgmt_config.h b/cmd/os_mgmt/src/os_mgmt_config.h
new file mode 100644
index 0000000..d388dec
--- /dev/null
+++ b/cmd/os_mgmt/src/os_mgmt_config.h
@@ -0,0 +1,22 @@
+#ifndef H_OS_MGMT_CONFIG_
+#define H_OS_MGMT_CONFIG_
+
+#if defined MYNEWT
+
+#include "syscfg/syscfg.h"
+
+#define OS_MGMT_RESET_MS    MYNEWT_VAL(OS_MGMT_RESET_MS)
+
+#elif defined __ZEPHYR__
+
+#define OS_MGMT_RESET_MS    CONFIG_OS_MGMT_RESET_MS
+
+#else
+
+/* No direct support for this OS.  The application needs to define the above
+ * settings itself.
+ */
+
+#endif
+
+#endif
diff --git a/cmd/os_mgmt/src/stubs.c b/cmd/os_mgmt/src/stubs.c
new file mode 100644
index 0000000..c17532b
--- /dev/null
+++ b/cmd/os_mgmt/src/stubs.c
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * These stubs get linked in when there is no equivalent OS-specific
+ * implementation.
+ */
+
+#include "mgmt/mgmt.h"
+#include "os_mgmt/os_mgmt_impl.h"
+
+int __attribute__((weak))
+os_mgmt_impl_task_info(int idx, struct os_mgmt_task_info *out_info)
+{
+    return MGMT_ERR_ENOTSUP;
+}
+
+int __attribute__((weak))
+os_mgmt_impl_reset(unsigned int delay_ms)
+{
+    return MGMT_ERR_ENOTSUP;
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@mynewt.apache.org" <co...@mynewt.apache.org>.