blob: 01e535c646e0ec3992b5da9ad013f260d196ea79 [file] [log] [blame]
/*
* 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"
#define IMG_MGMT_DATA_SHA_LEN 32
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;
/** Hash of image data; used for resume of a partial upload. */
uint8_t data_sha_len;
uint8_t data_sha[IMG_MGMT_DATA_SHA_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;
if (img_mgmt_slot_in_use(1)) {
/* No free slot. */
return MGMT_ERR_EBADSTATE;
}
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;
}
/* reset uploading information on erase */
img_mgmt_ctxt.uploading = false;
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;
}
/* check if header for first packet is valid */
static int
img_mgmt_check_header(const uint8_t *req_data, size_t len)
{
struct image_header hdr;
if (len < sizeof(hdr)) {
return MGMT_ERR_EINVAL;
}
memcpy(&hdr, req_data, sizeof(hdr));
if (hdr.ih_magic != IMAGE_MAGIC) {
return MGMT_ERR_EINVAL;
}
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, const uint8_t *data_sha,
size_t data_sha_len)
{
int rc;
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;
/*
* 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.
*/
img_mgmt_ctxt.data_sha_len = data_sha_len;
memcpy(img_mgmt_ctxt.data_sha, data_sha, data_sha_len);
memset(&img_mgmt_ctxt.data_sha[data_sha_len], 0,
IMG_MGMT_DATA_SHA_LEN - data_sha_len);
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];
uint8_t data_sha[IMG_MGMT_DATA_SHA_LEN];
size_t data_sha_len = 0;
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[] = {
[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] = {
.attribute = "sha",
.type = CborAttrByteStringType,
.addr.bytestring.data = data_sha,
.addr.bytestring.len = &data_sha_len,
.len = sizeof(data_sha)
},
[4] = { 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_check_header(img_mgmt_data, data_len);
if (rc) {
return rc;
}
/* Reject if SHA len is to big */
if (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 ((data_sha_len > 0) && img_mgmt_ctxt.uploading) {
if ((img_mgmt_ctxt.data_sha_len == data_sha_len) &&
!memcmp(img_mgmt_ctxt.data_sha, data_sha, data_sha_len)) {
return img_mgmt_encode_upload_rsp(ctxt, 0);
}
}
rc = img_mgmt_upload_first_chunk(ctxt, img_mgmt_data, data_len,
data_sha, data_sha_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);
}