/*
 * 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 <crc/crc8.h>
#include <crc/crc16.h>

#include "fcb/fcb2.h"
#include "fcb_priv.h"

/*
 * Given offset in flash area, compute crc16 over the data.
 */
int
fcb2_elem_crc16(struct fcb2_entry *loc, uint16_t *c16p)
{
    uint8_t tmp_str[FCB2_TMP_BUF_SZ];
    int blk_sz;
    uint16_t crc16;
    uint32_t off;
    uint32_t end;
    int rc;

    crc16 = 0xFFFF;

    off = loc->fe_data_off;
    end = loc->fe_data_off + loc->fe_data_len;
    for (; off < end; off += blk_sz) {
        blk_sz = end - off;
        if (blk_sz > sizeof(tmp_str)) {
            blk_sz = sizeof(tmp_str);
        }

        rc = fcb2_read_from_sector(loc, off, tmp_str, blk_sz);
        if (rc) {
            return FCB2_ERR_FLASH;
        }
        crc16 = crc16_ccitt(crc16, tmp_str, blk_sz);
    }
    *c16p = crc16;

    return 0;
}

static int
fcb2_read_entry(struct fcb2_entry *loc)
{
    uint8_t buf[FCB2_ENTRY_SIZE];
    uint8_t entry_crc;
    uint32_t entry_offset;
    uint32_t offset;
    uint16_t len;
    int rc;

    assert(loc != NULL);
    entry_offset = fcb2_entry_location_in_range(loc);
    rc = flash_area_read_is_empty(&loc->fe_range->fsr_flash_area,
        entry_offset, buf, sizeof(buf));
    if (rc < 0) {
        /* Error reading from flash */
        return FCB2_ERR_FLASH;
    } else if (rc == 1) {
        /* Entry not filled on flash */
        return FCB2_ERR_NOVAR;
    }
    /* Check entry CRC first */
    entry_crc = crc8_calc(crc8_init(), buf, FCB2_ENTRY_SIZE - 1);
    if (entry_crc != buf[FCB2_ENTRY_SIZE - 1]) {
        return FCB2_ERR_CRC;
    }
    offset = (buf[0] << 16) | (buf[1] << 8) | (buf[2] << 0);
    len = (buf[3] << 8) | (buf[4] << 0);
    /* Sanity check for entry */
    if (offset < fcb2_len_in_flash(loc->fe_range,
                                   sizeof(struct fcb2_disk_area)) ||
        len > FCB2_MAX_LEN ||
        offset + len > entry_offset) {
        /* Entry was found but data stored does not make any sense
         * report as CRC error so it can be skipped */
        return FCB2_ERR_CRC;
    }

    /* Entry looks decent, pass to the caller */
    loc->fe_data_off = offset;
    loc->fe_data_len = len;

    return 0;
}

int
fcb2_elem_info(struct fcb2_entry *loc)
{
    int rc;
    uint16_t crc16;
    uint8_t fl_crc16[2];

    /* Read entry from the end of the sector */
    rc = fcb2_read_entry(loc);
    if (rc != 0) {
        return rc;
    }

    /* Read actual data and calculate CRC */
    rc = fcb2_elem_crc16(loc, &crc16);
    if (rc) {
        return rc;
    }

    /* Read CRC from flash */
    rc = fcb2_read_from_sector(loc,
        loc->fe_data_off + fcb2_len_in_flash(loc->fe_range, loc->fe_data_len),
        &fl_crc16, 2);
    if (rc || get_be16(fl_crc16) != crc16) {
        return FCB2_ERR_CRC;
    }

    return 0;
}
