#include <string.h>

#include "security/id.h"

#include "util/hex.h"
#include "util/log.h"
#include "util/mem.h"

const struct security_id security_id_default = {
    .header = SECURITY_ID_HEADER,
    .id = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
    .checksum = 0x6F,
};

static uint8_t
security_id_checksum_calc(uint8_t initseed, const uint8_t *inbuf, size_t length)
{
    const uint8_t *v3; // edi@1
    int v4; // ebp@2
    uint8_t result; // al@2
    unsigned int v6; // esi@3
    signed int v7; // ebx@3
    int v8; // edx@4
    uint8_t v9; // al@4
    int v10; // edx@6
    uint8_t v11; // al@6
    int v12; // edx@8
    uint8_t v13; // al@8
    int v14; // edx@10

    v3 = inbuf;

    if (length <= 0) {
        result = initseed;
    } else {
        v4 = length;
        result = initseed;

        do {
            v6 = *v3;
            v7 = 0;

            do {
                v8 = (result ^ (uint8_t) (v6 >> v7)) & 1;
                v9 = result >> 1;

                if (v8) {
                    v9 ^= 0x8Cu;
                }

                v10 = (v9 ^ (uint8_t) (v6 >> (v7 + 1))) & 1;
                v11 = v9 >> 1;

                if (v10) {
                    v11 ^= 0x8Cu;
                }

                v12 = (v11 ^ (uint8_t) (v6 >> (v7 + 2))) & 1;
                v13 = v11 >> 1;

                if (v12) {
                    v13 ^= 0x8Cu;
                }

                v14 = (v13 ^ (uint8_t) (v6 >> (v7 + 3))) & 1;
                result = v13 >> 1;

                if (v14) {
                    result ^= 0x8Cu;
                }

                v7 += 4;
            } while (v7 < 8);

            v3 = (v3 + 1);
            --v4;
        } while (v4);
    }

    return result;
}

static uint8_t security_id_checksum_buffer(const uint8_t *inbuf)
{
    uint8_t bufcheck[7];

    bufcheck[0] = inbuf[1];
    bufcheck[1] = inbuf[7];
    bufcheck[2] = inbuf[6];
    bufcheck[3] = inbuf[5];
    bufcheck[4] = inbuf[4];
    bufcheck[5] = inbuf[3];
    bufcheck[6] = inbuf[2];

    return security_id_checksum_calc(0, bufcheck, sizeof(bufcheck));
}

bool security_id_parse(const char *str, struct security_id *id)
{
    log_assert(str);
    log_assert(id);

    return hex_decode(id, sizeof(struct security_id), str, strlen(str));
}

char *security_id_to_str(const struct security_id *id, bool id_only)
{
    char *str;
    size_t len;

    log_assert(id);

    if (id_only) {
        len = sizeof(id->id) * 2 + 1;
        str = xmalloc(len);
        hex_encode_uc(id->id, sizeof(id->id), str, len);
    } else {
        len = sizeof(struct security_id) * 2 + 1;
        str = xmalloc(len);
        hex_encode_uc(id, sizeof(*id), str, len);
    }

    str[len - 1] = '\0';

    return str;
}

void security_id_prepare(struct security_id *id)
{
    log_assert(id);

    id->header = SECURITY_ID_HEADER;
    id->checksum = security_id_checksum_buffer(id->id);
}

bool security_id_verify(const struct security_id *id)
{
    log_assert(id);

    if (id->header != SECURITY_ID_HEADER) {
        log_warning(
            "PCBID header needs to be %02X but was %02X",
            SECURITY_ID_HEADER,
            id->header);
        return false;
    }

    uint8_t check = id->checksum;
    uint8_t need = security_id_checksum_buffer(id->id);

    if (check != need) {
        log_warning("PCBID checksum should be %02X got %02X", need, check);
        return false;
    }

    return true;
}
