/*
 * 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 "os/mynewt.h"

#if MYNEWT_VAL(IMGMGR_COREDUMP)

#include <limits.h>

#include "flash_map/flash_map.h"
#include "mgmt/mgmt.h"
#include "coredump/coredump.h"
#include "cborattr/cborattr.h"

#include "imgmgr/imgmgr.h"
#include "imgmgr_priv.h"

int
imgr_core_list(struct mgmt_ctxt *ctxt)
{
    const struct flash_area *fa;
    struct coredump_header hdr;
    int rc;

    rc = flash_area_open(MYNEWT_VAL(COREDUMP_FLASH_AREA), &fa);
    if (rc != 0) {
        return MGMT_ERR_EUNKNOWN;
    }

    rc = flash_area_read(fa, 0, &hdr, sizeof(hdr));
    if (rc != 0) {
        return MGMT_ERR_EINVAL;
    }

    if (hdr.ch_magic != COREDUMP_MAGIC) {
        return MGMT_ERR_ENOENT;
    }

    rc = mgmt_write_rsp_status(ctxt, 0);
    if (rc != 0) {
        return rc;
    }

    return 0;
}

int
imgr_core_load(struct mgmt_ctxt *ctxt)
{
    unsigned long long off = UINT_MAX;
    const struct cbor_attr_t dload_attr[2] = {
        [0] = {
            .attribute = "off",
            .type = CborAttrUnsignedIntegerType,
            .addr.uinteger = &off
        },
        [1] = { 0 },
    };
    int rc;
    int sz;
    const struct flash_area *fa;
    uint8_t data[MYNEWT_VAL(IMGMGR_MAX_CHUNK_SIZE)];
    struct coredump_header *hdr;
    CborError g_err = CborNoError;

    hdr = (struct coredump_header *)data;

    rc = cbor_read_object(&ctxt->it, dload_attr);
    if (rc || off == UINT_MAX) {
        return MGMT_ERR_EINVAL;
    }

    rc = flash_area_open(MYNEWT_VAL(COREDUMP_FLASH_AREA), &fa);
    if (rc) {
        return MGMT_ERR_EINVAL;
    }

    rc = flash_area_read(fa, 0, hdr, sizeof(*hdr));
    if (rc) {
        rc = MGMT_ERR_EINVAL;
        goto err_close;
    }
    if (hdr->ch_magic != COREDUMP_MAGIC) {
        rc = MGMT_ERR_ENOENT;
        goto err_close;
    }
    if (off > hdr->ch_size) {
        off = hdr->ch_size;
    }
    sz = hdr->ch_size - off;
    if (sz > sizeof(data)) {
        sz = sizeof(data);
    }

    rc = flash_area_read(fa, off, data, sz);
    if (rc) {
        rc = MGMT_ERR_EINVAL;
        goto err_close;
    }

    g_err |= cbor_encode_text_stringz(&ctxt->encoder, "rc");
    g_err |= cbor_encode_int(&ctxt->encoder, MGMT_ERR_EOK);
    g_err |= cbor_encode_text_stringz(&ctxt->encoder, "off");
    g_err |= cbor_encode_int(&ctxt->encoder, off);
    g_err |= cbor_encode_text_stringz(&ctxt->encoder, "data");
    g_err |= cbor_encode_byte_string(&ctxt->encoder, data, sz);

    /* Only include length in first response. */
    if (off == 0) {
        g_err |= cbor_encode_text_stringz(&ctxt->encoder, "len");
        g_err |= cbor_encode_uint(&ctxt->encoder, hdr->ch_size);
    }

    flash_area_close(fa);
    if (g_err) {
        return MGMT_ERR_ENOMEM;
    }
    return 0;

err_close:
    flash_area_close(fa);
    return rc;
}

/*
 * Erase the area if it has a coredump, or the header is empty.
 */
int
imgr_core_erase(struct mgmt_ctxt *ctxt)
{
    struct coredump_header hdr;
    const struct flash_area *fa;
    int rc;

    rc = flash_area_open(MYNEWT_VAL(COREDUMP_FLASH_AREA), &fa);
    if (rc != 0) {
        return MGMT_ERR_EINVAL;
    }

    rc = flash_area_read(fa, 0, &hdr, sizeof(hdr));
    if (rc == 0 &&
      (hdr.ch_magic == COREDUMP_MAGIC || hdr.ch_magic == 0xffffffff)) {
        rc = flash_area_erase(fa, 0, fa->fa_size);
        if (rc != 0) {
            return MGMT_ERR_EINVAL;
        }
    }

    flash_area_close(fa);

    rc = mgmt_write_rsp_status(ctxt, rc);
    if (rc != 0) {
        return rc;
    }

    return 0;
}

#endif
