/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2016 Amaury Pouly
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include "upg.h"
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "md5.h"

struct nwz_model_t g_model_list[] =
{
    { "nwz-a10", true, "2572f4a7b8c1a08aeb5142ce9cb834d6" },
    { "nw-a20", true, "d91a61c7263bafc626e9a5b66f983c0b" },
    { "nwz-e450", true, "8a01b624bfbfde4a1662a1772220e3c5" },
    { "nwz-e460", true, "89d813f8f966efdebd9c9e0ea98156d2" },
    { "nwz-a860", true, "a7c4af6c28b8900a783f307c1ba538c5" },
    { "nwz-a850", true, "a2efb9168616c2e84d78291295c1aa5d" },
    { "nwz-a840", true, "78033fe79a67786fd79fbc138c865c68" },
    { "nwz-e470", true, "e4144baaa2707913f17b5634034262c4" },
    { "nwz-e580", true, "6e25f79812eca7ceed04819d833e80af" },
    { "nwz-s750", true, "6d4f4d9adec781baf197e6255cedd0f6" },
    { "nwz-x1000",true, "efafdee4c628fa7536de0b878cfe23af" },
    { "nw-zx100", true, "cdda8d5e5360fd4373154388743f84d2" },
    /* The following keys were obtained by brute forcing firmware upgrades,
     * someone with a device needs to confirm that they work */
    { "nw-a820", false, "0c9869c268e0eaa6d1ba62daab09cebc" },
    /* The following models use a different encryption, but we put the KAS here
     * to not forget them */
    { "nw-a30", false, "c40d91e7efff3e3aa5c8831dd85526fe4972086283419c8cd8fa3b7dcd39" },
    { "nw-wm1", false, "e8d171a5d92f35eed9658c03fb9f86a169591659851fd7c49525f587a70b" },
    { 0 }
};

static int digit_value(char c)
{
    if(c >= '0' && c <= '9') return c - '0';
    if(c >= 'a' && c <= 'f') return c - 'a' + 10;
    if(c >= 'A' && c <= 'F') return c - 'A' + 10;
    return -1;
}

static char hex_digit(unsigned v)
{
    return (v < 10) ? v + '0' : (v < 16) ? v - 10 + 'a' : 'x';
}

int decrypt_keysig(const char kas[NWZ_KAS_SIZE], char key[NWZ_KEY_SIZE],
    char sig[NWZ_SIG_SIZE])
{
    uint8_t src[NWZ_KAS_SIZE / 2];
    for(int index = 0; index < NWZ_KAS_SIZE / 2; index++)
    {
        int a = digit_value(kas[index * 2]);
        int b = digit_value(kas[index * 2 + 1]);
        if(a < 0 || b < 0)
            return -1;
        src[index] = a << 4 | b;
    }
    fwp_setkey("ed295076");
    fwp_crypt(src, sizeof(src), 1);
    memcpy(key, src, NWZ_KEY_SIZE);
    memcpy(sig, src + NWZ_KEY_SIZE, NWZ_SIG_SIZE);
    return 0;
}

void encrypt_keysig(char kas[NWZ_KEY_SIZE],
    const char key[NWZ_SIG_SIZE], const char sig[NWZ_KAS_SIZE])
{
    uint8_t src[NWZ_KAS_SIZE / 2];
    fwp_setkey("ed295076");
    memcpy(src, key, NWZ_KEY_SIZE);
    memcpy(src + NWZ_KEY_SIZE, sig, NWZ_SIG_SIZE);
    fwp_crypt(src, sizeof(src), 0);
    for(int i = 0; i < NWZ_KAS_SIZE / 2; i++)
    {
        kas[2 * i] = hex_digit((src[i] >> 4) & 0xf);
        kas[2 * i + 1] = hex_digit(src[i] & 0xf);
    }
}

struct upg_file_t *upg_read_memory(void *buf, size_t size, char key[NWZ_KEY_SIZE],
    char *sig, void *u, generic_printf_t printf)
{
#define cprintf(col, ...) printf(u, false, col, __VA_ARGS__)
#define cprintf_field(str1, ...) do{ cprintf(GREEN, str1); cprintf(YELLOW, __VA_ARGS__); }while(0)
#define err_printf(col, ...) printf(u, true, col, __VA_ARGS__)
    struct upg_md5_t *md5 = buf;
    cprintf(BLUE, "Preliminary\n");
    cprintf(GREEN, "  MD5: ");
    for(int i = 0; i < NWZ_MD5_SIZE; i++)
        cprintf(YELLOW, "%02x", md5->md5[i]);
    cprintf(OFF, " ");

    /* check MD5 */
    uint8_t actual_md5[NWZ_MD5_SIZE];
    MD5_CalculateDigest(actual_md5, (md5 + 1), size - sizeof(struct upg_header_t));
    if(memcmp(actual_md5, md5->md5, NWZ_MD5_SIZE) != 0)
    {
        cprintf(RED, "Mismatch\n");
        err_printf(GREY, "MD5 Mismatch\n");
        return NULL;
    }
    cprintf(RED, "Ok\n");

    struct upg_header_t *hdr = (void *)(md5 + 1);
    /* decrypt the whole file at once */
    fwp_read(hdr, size - sizeof(*md5), hdr, (void *)key);

    cprintf(BLUE, "Header\n");
    cprintf_field("  Signature:", " ");
    for(int i = 0; i < NWZ_SIG_SIZE; i++)
        cprintf(YELLOW, "%c", isprint(hdr->sig[i]) ? hdr->sig[i] : '.');
    if(sig)
    {
        if(memcmp(hdr->sig, sig, NWZ_SIG_SIZE) != 0)
        {
            cprintf(RED, "Mismatch\n");
            err_printf(GREY, "Signature Mismatch\n");
            return NULL;
        }
        cprintf(RED, " Ok\n");
    }
    else
        cprintf(RED, " Can't check\n");
    cprintf_field("  Files: ", "%d\n", hdr->nr_files);
    cprintf_field("  Pad: ", "0x%x\n", hdr->pad);


    /* Do a first pass to decrypt in-place */
    cprintf(BLUE, "Files\n");
    struct upg_entry_t *entry = (void *)(hdr + 1);
    for(unsigned i = 0; i < hdr->nr_files; i++, entry++)
    {
        cprintf(GREY, "  File");
        cprintf(RED, " %d\n", i);
        cprintf_field("    Offset: ", "0x%x\n", entry->offset);
        cprintf_field("    Size: ", "0x%x\n", entry->size);
    }
    /* Do a second pass to create the file structure */
    /* create file */
    struct upg_file_t *file = malloc(sizeof(struct upg_file_t));
    memset(file, 0, sizeof(struct upg_file_t));
    file->nr_files = hdr->nr_files;
    file->files = malloc(sizeof(struct upg_file_entry_t) * file->nr_files);

    entry = (void *)(hdr + 1);
    for(unsigned i = 0; i < hdr->nr_files; i++, entry++)
    {
        memset(&file->files[i], 0, sizeof(struct upg_file_entry_t));
        file->files[i].size = entry->size;
        file->files[i].data = malloc(file->files[i].size);
        memcpy(file->files[i].data, buf + entry->offset, entry->size);
    }

    return file;
}

void *upg_write_memory(struct upg_file_t *file, char key[NWZ_KEY_SIZE],
    char sig[NWZ_SIG_SIZE], size_t *out_size, void *u, generic_printf_t printf)
{
    if(file->nr_files == 0)
    {
        err_printf(GREY, "A UPG file must have at least one file\n");
        return NULL;
    }
    /* compute total size and create buffer */
    size_t tot_size = sizeof(struct upg_md5_t) + sizeof(struct upg_header_t)
        + file->nr_files * sizeof(struct upg_entry_t);
    for(int i = 0; i < file->nr_files; i++)
        tot_size += ROUND_UP(file->files[i].size, 8);
    /* allocate buffer */
    void *buf = malloc(tot_size);

    /* create md5 context, we push data to the context as we create it */
    struct upg_md5_t *md5 = buf;
    memset(md5, 0, sizeof(*md5));
    /* create the encrypted signature and header */
    struct upg_header_t *hdr = (void *)(md5 + 1);
    memcpy(hdr->sig, sig, NWZ_SIG_SIZE);
    hdr->nr_files = file->nr_files;
    hdr->pad = 0;

    /* create file headers */
    size_t offset = sizeof(*md5) + sizeof(*hdr) + file->nr_files * sizeof(struct upg_entry_t);
    struct upg_entry_t *entry = (void *)(hdr + 1);
    cprintf(BLUE, "Files\n");
    for(int i = 0; i < file->nr_files; i++)
    {
        entry[i].offset = offset;
        entry[i].size = file->files[i].size;
        offset += ROUND_UP(entry[i].size, 8); /* pad each file to a multiple of 8 for encryption */

        cprintf(GREY, "  File");
        cprintf(RED, " %d\n", i);
        cprintf_field("    Offset: ", "0x%lx\n", entry[i].offset);
        cprintf_field("    Size: ", "0x%lx\n", entry[i].size);
    }

    /* add file data */
    for(int i = 0; i < file->nr_files; i++)
    {
        /* copy data to buffer, and then encrypt in-place */
        size_t r_size = ROUND_UP(file->files[i].size, 8);
        void *data_ptr = (uint8_t *)buf + entry[i].offset;
        memset(data_ptr, 0, r_size); /* the padding will be zero 0 */
        memcpy(data_ptr, file->files[i].data, file->files[i].size);
    }
    /* encrypt everything and hash everything */
    fwp_write(hdr, tot_size - sizeof(*md5), hdr, (void *)key);
    /* write final MD5 */
    MD5_CalculateDigest(md5->md5, (void *)hdr, tot_size - sizeof(*md5));
    *out_size = tot_size;
    return buf;
}

struct upg_file_t *upg_new(void)
{
    struct upg_file_t *f = malloc(sizeof(struct upg_file_t));
    memset(f, 0, sizeof(struct upg_file_t));
    return f;
}

void upg_append(struct upg_file_t *file, void *data, size_t size)
{
    file->files = realloc(file->files, (file->nr_files + 1) * sizeof(struct upg_file_entry_t));
    file->files[file->nr_files].data = data;
    file->files[file->nr_files].size = size;
    file->nr_files++;
}

void upg_free(struct upg_file_t *file)
{
    if(file)
    {
        for(int i = 0; i < file->nr_files; i++)
            free(file->files[i].data);
        free(file->files);
    }
    free(file);
}
