/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed 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 <assert.h>
#include <fcntl.h>
#include <sys/types.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stddef.h>
#include <errno.h>

#include <nanohub/nanohub.h>
#include <nanohub/nanoapp.h>
#include <nanohub/appRelocFormat.h>

//This code assumes it is run on a LE CPU with unaligned access abilities. Sorry.

#define FLASH_BASE  0x10000000u
#define RAM_BASE    0x80000000u

#define FLASH_SIZE  0x10000000u  //256MB ought to be enough for everyone
#define RAM_SIZE    0x10000000u  //256MB ought to be enough for everyone

//caution: double evaluation
#define IS_IN_RANGE_E(_val, _rstart, _rend) (((_val) >= (_rstart)) && ((_val) < (_rend)))
#define IS_IN_RANGE(_val, _rstart, _rsz)    IS_IN_RANGE_E((_val), (_rstart), ((_rstart) + (_rsz)))
#define IS_IN_RAM(_val)              IS_IN_RANGE(_val, RAM_BASE, RAM_SIZE)
#define IS_IN_FLASH(_val)            IS_IN_RANGE(_val, FLASH_BASE, FLASH_SIZE)


#define NANO_RELOC_TYPE_RAM    0
#define NANO_RELOC_TYPE_FLASH  1
#define NANO_RELOC_LAST        2 //must be <= (RELOC_TYPE_MASK >> RELOC_TYPE_SHIFT)

struct RelocEntry {
    uint32_t where;
    uint32_t info;  //bottom 8 bits is type, top 24 is sym idx
};

#define RELOC_TYPE_ABS_S    2
#define RELOC_TYPE_ABS_D    21
#define RELOC_TYPE_SECT     23


struct SymtabEntry {
    uint32_t a;
    uint32_t addr;
    uint32_t b, c;
};

struct NanoRelocEntry {
    uint32_t ofstInRam;
    uint8_t type;
};

struct NanoAppInfo {
    union {
        struct BinHdr *bin;
        uint8_t *data;
    };
    size_t dataSizeUsed;
    size_t dataSizeAllocated;
    size_t codeAndDataSize;   // not including symbols, relocs and BinHdr
    size_t codeAndRoDataSize; // also not including GOT & RW data in flash
    struct SymtabEntry *symtab;
    size_t symtabSize; // number of symbols
    struct RelocEntry *reloc;
    size_t relocSize; // number of reloc entries
    struct NanoRelocEntry *nanoReloc;
    size_t nanoRelocSize; // number of nanoReloc entries <= relocSize
    uint8_t *packedNanoReloc;
    size_t packedNanoRelocSize;

    bool debug;
};

#ifndef ARRAY_SIZE
#define ARRAY_SIZE(ary) (sizeof(ary) / sizeof((ary)[0]))
#endif

static FILE *stdlog = NULL;

#define DBG(fmt, ...) fprintf(stdlog, fmt "\n", ##__VA_ARGS__)
#define ERR(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__)

static void fatalUsage(const char *name, const char *msg, const char *arg)
{
    if (msg && arg)
        ERR("Error: %s: %s\n", msg, arg);
    else if (msg)
        ERR("Error: %s\n", msg);

    ERR("USAGE: %s [-v] [-k <key id>] [-a <app id>] [-r] [-n <layout name>] [-i <layout id>] <input file> [<output file>]\n"
        "       -v               : be verbose\n"
        "       -n <layout name> : app, os, key\n"
        "       -i <layout id>   : 1 (app), 2 (key), 3 (os)\n"
        "       -f <layout flags>: 16-bit hex value, stored as layout-specific flags\n"
        "       -a <app ID>      : 64-bit hex number != 0\n"
        "       -e <app ver>     : 32-bit hex number\n"
        "       -k <key ID>      : 64-bit hex number != 0\n"
        "       -r               : bare (no AOSP header); used only for inner OS image generation\n"
        "       layout ID and layout name control the same parameter, so only one of them needs to be used\n"
        , name);
    exit(1);
}

bool packNanoRelocs(struct NanoAppInfo *app)
{
    size_t i, j, k;
    uint8_t *packedNanoRelocs;
    uint32_t packedNanoRelocSz;
    uint32_t lastOutType = 0, origin = 0;
    bool verbose = app->debug;

    //sort by type and then offset
    for (i = 0; i < app->nanoRelocSize; i++) {
        struct NanoRelocEntry t;

        for (k = i, j = k + 1; j < app->nanoRelocSize; j++) {
            if (app->nanoReloc[j].type > app->nanoReloc[k].type)
                continue;
            if ((app->nanoReloc[j].type < app->nanoReloc[k].type) || (app->nanoReloc[j].ofstInRam < app->nanoReloc[k].ofstInRam))
                k = j;
        }
        memcpy(&t, app->nanoReloc + i, sizeof(struct NanoRelocEntry));
        memcpy(app->nanoReloc + i, app->nanoReloc + k, sizeof(struct NanoRelocEntry));
        memcpy(app->nanoReloc + k, &t, sizeof(struct NanoRelocEntry));

        if (app->debug)
            DBG("SortedReloc[%3zu] = {0x%08" PRIX32 ",0x%02" PRIX8 "}", i, app->nanoReloc[i].ofstInRam, app->nanoReloc[i].type);
    }

    //produce output nanorelocs in packed format
    packedNanoRelocs = malloc(app->nanoRelocSize * 6); //definitely big enough
    packedNanoRelocSz = 0;

    if (!packedNanoRelocs) {
        ERR("Failed to allocate memory for packed relocs");
        return false;
    }

    for (i = 0; i < app->nanoRelocSize; i++) {
        uint32_t displacement;

        if (lastOutType != app->nanoReloc[i].type) {  //output type if ti changed
            if (app->nanoReloc[i].type - lastOutType == 1) {
                packedNanoRelocs[packedNanoRelocSz++] = TOKEN_RELOC_TYPE_NEXT;
                if (verbose)
                    DBG("Out: RelocTC [size 1] // to 0x%02" PRIX8, app->nanoReloc[i].type);
            } else {
                packedNanoRelocs[packedNanoRelocSz++] = TOKEN_RELOC_TYPE_CHG;
                packedNanoRelocs[packedNanoRelocSz++] = app->nanoReloc[i].type - lastOutType - 1;
                if (verbose)
                    DBG("Out: RelocTC [size 2] (0x%02" PRIX8 ")  // to 0x%02" PRIX8,
                        (uint8_t)(app->nanoReloc[i].type - lastOutType - 1), app->nanoReloc[i].type);
            }
            lastOutType = app->nanoReloc[i].type;
            origin = 0;
        }
        displacement = app->nanoReloc[i].ofstInRam - origin;
        origin = app->nanoReloc[i].ofstInRam + 4;
        if (displacement & 3) {
            ERR("Unaligned relocs are not possible!");
            return false;
        }
        displacement /= 4;

        //might be start of a run. look into that
        if (!displacement) {
            for (j = 1; (j + i) < app->nanoRelocSize && j < MAX_RUN_LEN &&
                        app->nanoReloc[j + i].type == lastOutType &&
                        (app->nanoReloc[j + i].ofstInRam - app->nanoReloc[j + i - 1].ofstInRam) == 4; j++);
            if (j >= MIN_RUN_LEN) {
                if (verbose)
                    DBG("Out: Reloc0 [size 2]; repeat=%zu", j);
                packedNanoRelocs[packedNanoRelocSz++] = TOKEN_CONSECUTIVE;
                packedNanoRelocs[packedNanoRelocSz++] = j - MIN_RUN_LEN;
                origin = app->nanoReloc[j + i - 1].ofstInRam + 4;  //reset origin to last one
                i += j - 1;  //loop will increment anyways, hence +1
                continue;
            }
        }

        //produce output
        if (displacement <= MAX_8_BIT_NUM) {
            if (verbose)
                DBG("Out: Reloc8 [size 1] 0x%02" PRIX32, displacement);
            packedNanoRelocs[packedNanoRelocSz++] = displacement;
        } else if (displacement <= MAX_16_BIT_NUM) {
            if (verbose)
                DBG("Out: Reloc16 [size 3] 0x%06" PRIX32, displacement);
                        displacement -= MAX_8_BIT_NUM;
            packedNanoRelocs[packedNanoRelocSz++] = TOKEN_16BIT_OFST;
            packedNanoRelocs[packedNanoRelocSz++] = displacement;
            packedNanoRelocs[packedNanoRelocSz++] = displacement >> 8;
        } else if (displacement <= MAX_24_BIT_NUM) {
            if (verbose)
                DBG("Out: Reloc24 [size 4] 0x%08" PRIX32, displacement);
                        displacement -= MAX_16_BIT_NUM;
            packedNanoRelocs[packedNanoRelocSz++] = TOKEN_24BIT_OFST;
            packedNanoRelocs[packedNanoRelocSz++] = displacement;
            packedNanoRelocs[packedNanoRelocSz++] = displacement >> 8;
            packedNanoRelocs[packedNanoRelocSz++] = displacement >> 16;
        } else {
            if (verbose)
                DBG("Out: Reloc32 [size 5] 0x%08" PRIX32, displacement);
            packedNanoRelocs[packedNanoRelocSz++] = TOKEN_32BIT_OFST;
            packedNanoRelocs[packedNanoRelocSz++] = displacement;
            packedNanoRelocs[packedNanoRelocSz++] = displacement >> 8;
            packedNanoRelocs[packedNanoRelocSz++] = displacement >> 16;
            packedNanoRelocs[packedNanoRelocSz++] = displacement >> 24;
        }
    }

    app->packedNanoReloc = packedNanoRelocs;
    app->packedNanoRelocSize = packedNanoRelocSz;

    return true;
}

static int finalizeAndWrite(struct NanoAppInfo *inf, FILE *out, uint32_t layoutFlags, uint64_t appId)
{
    bool good = true;
    struct AppInfo app;
    struct SectInfo *sect;
    struct BinHdr *bin = inf->bin;
    struct ImageHeader outHeader = {
        .aosp = (struct nano_app_binary_t) {
            .header_version = 1,
            .magic = NANOAPP_AOSP_MAGIC,
            .app_id = appId,
            .app_version = bin->hdr.appVer,
            .flags       = 0, // encrypted (1), signed (2) (will be set by other tools)
        },
        .layout = (struct ImageLayout) {
            .magic = GOOGLE_LAYOUT_MAGIC,
            .version = 1,
            .payload = LAYOUT_APP,
            .flags = layoutFlags,
        },
    };

    app.sect = bin->sect;
    app.vec  = bin->vec;
    sect = &app.sect;

    //if we have any bytes to output, show stats
    if (inf->codeAndRoDataSize) {
        size_t binarySize = 0;
        size_t gotSz = sect->got_end - sect->data_start;
        size_t bssSz = sect->bss_end - sect->bss_start;

        good = fwrite(&outHeader, sizeof(outHeader), 1, out) == 1 && good;
        binarySize += sizeof(outHeader);

        good = fwrite(&app, sizeof(app), 1, out) == 1 && good;
        binarySize += sizeof(app);

        good = fwrite(&bin[1], inf->codeAndDataSize, 1, out) == 1 && good;
        binarySize += inf->codeAndDataSize;

        if (inf->packedNanoReloc && inf->packedNanoRelocSize) {
            good = fwrite(inf->packedNanoReloc, inf->packedNanoRelocSize, 1, out) == 1 && good;
            binarySize += inf->packedNanoRelocSize;
        }

        if (!good) {
            ERR("Failed to write output file: %s\n", strerror(errno));
        } else {
            DBG("Final binary size %zu bytes", binarySize);
            DBG("");
            DBG("       FW header size (flash):      %6zu bytes", FLASH_RELOC_OFFSET);
            DBG("       Code + RO data (flash):      %6zu bytes", inf->codeAndRoDataSize);
            DBG("       Relocs (flash):              %6zu bytes", inf->packedNanoRelocSize);
            DBG("       GOT + RW data (flash & RAM): %6zu bytes", gotSz);
            DBG("       BSS (RAM):                   %6zu bytes", bssSz);
            DBG("");
            DBG("Runtime flash use: %zu bytes",
                (size_t)(inf->codeAndRoDataSize + inf->packedNanoRelocSize + gotSz + FLASH_RELOC_OFFSET));
            DBG("Runtime RAM use: %zu bytes", gotSz + bssSz);
        }
    }

    return good ? 0 : 2;
}

// Subtracts the fixed memory region offset from an absolute address and returns
// the associated NANO_RELOC_* value, or NANO_RELOC_LAST if the address is not
// in the expected range.
static uint8_t fixupAddress(uint32_t *addr, struct SymtabEntry *sym, bool debug)
{
    uint8_t type;
    uint32_t old = *addr;

    (*addr) += sym->addr;
    // TODO: this assumes that the host running this tool has the same
    // endianness as the image file/target processor
    if (IS_IN_RAM(*addr)) {
        *addr -= RAM_BASE;
        type = NANO_RELOC_TYPE_RAM;
        if (debug)
            DBG("Fixup addr 0x%08" PRIX32 " (RAM) --> 0x%08" PRIX32, old, *addr);
    } else if (IS_IN_FLASH(*addr)) {
        *addr -= FLASH_BASE + BINARY_RELOC_OFFSET;
        type = NANO_RELOC_TYPE_FLASH;
        if (debug)
            DBG("Fixup addr 0x%08" PRIX32 " (FLASH) --> 0x%08" PRIX32, old, *addr);
    } else {
        ERR("Error: invalid address 0x%08" PRIX32, *addr);
        type = NANO_RELOC_LAST;
    }

    return type;
}

static void relocDiag(const struct NanoAppInfo *app, const struct RelocEntry *reloc, const char *msg)
{
    size_t symIdx = reloc->info >> 8;
    uint8_t symType = reloc->info;

    ERR("Reloc %zu %s", reloc - app->reloc, msg);
    ERR("INFO:");
    ERR("        Where: 0x%08" PRIX32, reloc->where);
    ERR("        type: %" PRIu8, symType);
    ERR("        sym: %zu", symIdx);
    if (symIdx < app->symtabSize) {
        struct SymtabEntry *sym = &app->symtab[symIdx];
        ERR("        addr: %" PRIu32, sym->addr);
    } else {
        ERR("        addr: <invalid>");
    }
}

static uint8_t fixupReloc(struct NanoAppInfo *app, struct RelocEntry *reloc,
                          struct SymtabEntry *sym, struct NanoRelocEntry *nanoReloc)
{
    uint8_t type;
    uint32_t *addr;
    uint32_t relocOffset = reloc->where;
    uint32_t flashDataOffset = 0;

    if (IS_IN_FLASH(relocOffset)) {
        relocOffset -= FLASH_BASE;
        flashDataOffset = 0;
    } else if (IS_IN_RAM(reloc->where)) {
        relocOffset = reloc->where - RAM_BASE;
        flashDataOffset = app->bin->sect.data_data - FLASH_BASE;
    } else {
        relocDiag(app, reloc, "is neither in RAM nor in FLASH");
        return NANO_RELOC_LAST;
    }

    addr = (uint32_t*)(app->data + flashDataOffset + relocOffset);

    if (flashDataOffset + relocOffset >= app->dataSizeUsed - sizeof(*addr)) {
        relocDiag(app, reloc, "points outside valid data area");
        return NANO_RELOC_LAST;
    }

    switch (reloc->info & 0xFF) {
    case RELOC_TYPE_ABS_S:
    case RELOC_TYPE_ABS_D:
        type = fixupAddress(addr, sym, app->debug);
        break;

    case RELOC_TYPE_SECT:
        if (sym->addr) {
            relocDiag(app, reloc, "has section relocation with non-zero symbol address");
            return NANO_RELOC_LAST;
        }
        type = fixupAddress(addr, sym, app->debug);
        break;
    default:
        relocDiag(app, reloc, "has unknown type");
        type = NANO_RELOC_LAST;
    }

    if (nanoReloc && type != NANO_RELOC_LAST) {
        nanoReloc->ofstInRam = relocOffset;
        nanoReloc->type = type;
    }

    return type;
}

static int handleApp(uint8_t **pbuf, uint32_t bufUsed, FILE *out, uint32_t layoutFlags, uint64_t appId, uint32_t appVer, bool verbose)
{
    uint32_t i;
    struct BinHdr *bin;
    int ret = -1;
    struct SectInfo *sect;
    uint8_t *buf = *pbuf;
    uint32_t bufSz = bufUsed * 3 /2;
    struct NanoAppInfo app;

    //make buffer 50% bigger than bufUsed in case relocs grow out of hand
    buf = reallocOrDie(buf, bufSz);
    *pbuf = buf;

    //sanity checks
    bin = (struct BinHdr*)buf;
    if (bufUsed < sizeof(*bin)) {
        ERR("File size too small: %" PRIu32, bufUsed);
        goto out;
    }

    if (bin->hdr.magic != NANOAPP_FW_MAGIC) {
        ERR("Magic value is wrong: found %08" PRIX32"; expected %08" PRIX32, bin->hdr.magic, NANOAPP_FW_MAGIC);
        goto out;
    }

    sect = &bin->sect;
    bin->hdr.appVer = appVer;

    if (!IS_IN_FLASH(sect->rel_start) || !IS_IN_FLASH(sect->rel_end) || !IS_IN_FLASH(sect->data_data)) {
        ERR("relocation data or initialized data is not in FLASH");
        goto out;
    }
    if (!IS_IN_RAM(sect->data_start) || !IS_IN_RAM(sect->data_end) || !IS_IN_RAM(sect->bss_start) ||
        !IS_IN_RAM(sect->bss_end) || !IS_IN_RAM(sect->got_start) || !IS_IN_RAM(sect->got_end)) {
        ERR("data, bss, or got not in ram\n");
        goto out;
    }

    //do some math
    app.reloc = (struct RelocEntry*)(buf + sect->rel_start - FLASH_BASE);
    app.symtab = (struct SymtabEntry*)(buf + sect->rel_end - FLASH_BASE);
    app.relocSize = (sect->rel_end - sect->rel_start) / sizeof(struct RelocEntry);
    app.nanoRelocSize = 0;
    app.symtabSize = (struct SymtabEntry*)(buf + bufUsed) - app.symtab;
    app.data = buf;
    app.dataSizeAllocated = bufSz;
    app.dataSizeUsed = bufUsed;
    app.codeAndRoDataSize = sect->data_data - FLASH_BASE - sizeof(*bin);
    app.codeAndDataSize = sect->rel_start - FLASH_BASE - sizeof(*bin);
    app.debug = verbose;
    app.nanoReloc = NULL;
    app.packedNanoReloc = NULL;

    //sanity
    if (app.relocSize * sizeof(struct RelocEntry) + sect->rel_start != sect->rel_end) {
        ERR("Relocs of nonstandard size");
        goto out;
    }
    if (app.symtabSize * sizeof(struct SymtabEntry) + sect->rel_end != bufUsed + FLASH_BASE) {
        ERR("Syms of nonstandard size");
        goto out;
    }

    //show some info

    if (verbose)
        DBG("Found %zu relocs and a %zu-entry symbol table", app.relocSize, app.symtabSize);

    //handle relocs
    app.nanoReloc = malloc(sizeof(struct NanoRelocEntry[app.relocSize]));
    if (!app.nanoReloc) {
        ERR("Failed to allocate a nano-reloc table\n");
        goto out;
    }

    for (i = 0; i < app.relocSize; i++) {
        struct RelocEntry *reloc = &app.reloc[i];
        struct NanoRelocEntry *nanoReloc = &app.nanoReloc[app.nanoRelocSize];
        uint32_t relocType = reloc->info & 0xff;
        uint32_t whichSym = reloc->info >> 8;
        struct SymtabEntry *sym = &app.symtab[whichSym];

        if (whichSym >= app.symtabSize) {
            relocDiag(&app, reloc, "references a nonexistent symbol");
            goto out;
        }

        if (verbose) {
            const char *seg;

            if (IS_IN_RANGE_E(reloc->where, sect->bss_start, sect->bss_end))
                seg = ".bss";
            else if (IS_IN_RANGE_E(reloc->where, sect->data_start, sect->data_end))
                seg = ".data";
            else if (IS_IN_RANGE_E(reloc->where, sect->got_start, sect->got_end))
                seg = ".got";
            else if (IS_IN_RANGE_E(reloc->where, FLASH_BASE, FLASH_BASE + sizeof(struct BinHdr)))
                seg = "APPHDR";
            else
                seg = "???";

            DBG("Reloc[%3" PRIu32 "]:\n {@0x%08" PRIX32 ", type %3" PRIu32 ", -> sym[%3" PRIu32 "]: {@0x%08" PRIX32 "}, in   %s}",
                i, reloc->where, reloc->info & 0xff, whichSym, sym->addr, seg);
        }
        /* handle relocs inside the header */
        if (IS_IN_FLASH(reloc->where) && reloc->where - FLASH_BASE < sizeof(struct BinHdr) && relocType == RELOC_TYPE_SECT) {
            /* relocs in header are special - runtime corrects for them */
            // binary header generated by objcopy, .napp header and final FW header in flash are of different layout and size.
            // we subtract binary header offset here, so all the entry points are relative to beginning of "sect".
            // FW will use &sect as a base to call these vectors; no more problems with different header sizes;
            // Assumption: offsets between sect & vec, vec & code are the same in all images (or, in a simpler words, { sect, vec, code }
            // must go together). this is enforced by linker script, and maintained by all tools and FW download code in the OS.

            switch (fixupReloc(&app, reloc, sym, NULL)) {
            case NANO_RELOC_TYPE_RAM:
                relocDiag(&app, reloc, "is in APPHDR but relocated to RAM");
                goto out;
            case NANO_RELOC_TYPE_FLASH:
                break;
            default:
                // other error happened; it is already reported
                goto out;
            }

            if (verbose)
                DBG("  -> Nano reloc skipped for in-header reloc");

            continue; /* do not produce an output reloc */
        }

        // any other relocs may only happen in RAM
        if (!IS_IN_RAM(reloc->where)) {
            relocDiag(&app, reloc, "is not in RAM");
            goto out;
        }

        if (fixupReloc(&app, reloc, sym, nanoReloc) != NANO_RELOC_LAST) {
            app.nanoRelocSize++;
            if (verbose)
                DBG("  -> Nano reloc calculated as 0x%08" PRIX32 ",0x%02" PRIX8 "\n", nanoReloc->ofstInRam, nanoReloc->type);
        }
    }

    if (!packNanoRelocs(&app))
        goto out;

    // we're going to write packed relocs; set correct size
    sect->rel_end = sect->rel_start + app.packedNanoRelocSize;

    //adjust headers for easy access (RAM)
    sect->data_start -= RAM_BASE;
    sect->data_end -= RAM_BASE;
    sect->bss_start -= RAM_BASE;
    sect->bss_end -= RAM_BASE;
    sect->got_start -= RAM_BASE;
    sect->got_end -= RAM_BASE;

    //adjust headers for easy access (FLASH)
    sect->data_data -= FLASH_BASE + BINARY_RELOC_OFFSET;
    sect->rel_start -= FLASH_BASE + BINARY_RELOC_OFFSET;
    sect->rel_end -= FLASH_BASE + BINARY_RELOC_OFFSET;

    ret = finalizeAndWrite(&app, out, layoutFlags, appId);
out:
    free(app.nanoReloc);
    free(app.packedNanoReloc);
    return ret;
}

static int handleKey(uint8_t **pbuf, uint32_t bufUsed, FILE *out, uint32_t layoutFlags, uint64_t appId, uint64_t keyId)
{
    uint8_t *buf = *pbuf;
    struct KeyInfo ki = { .data = keyId };
    bool good = true;

    struct ImageHeader outHeader = {
        .aosp = (struct nano_app_binary_t) {
            .header_version = 1,
            .magic = NANOAPP_AOSP_MAGIC,
            .app_id = appId,
        },
        .layout = (struct ImageLayout) {
            .magic = GOOGLE_LAYOUT_MAGIC,
            .version = 1,
            .payload = LAYOUT_KEY,
            .flags = layoutFlags,
        },
    };

    good = good && fwrite(&outHeader, sizeof(outHeader), 1, out) == 1;
    good = good && fwrite(&ki, sizeof(ki), 1, out) ==  1;
    good = good && fwrite(buf, bufUsed, 1, out) == 1;

    return good ? 0 : 2;
}

static int handleOs(uint8_t **pbuf, uint32_t bufUsed, FILE *out, uint32_t layoutFlags, bool bare)
{
    uint8_t *buf = *pbuf;
    bool good;

    struct OsUpdateHdr os = {
        .magic = OS_UPDT_MAGIC,
        .marker = OS_UPDT_MARKER_INPROGRESS,
        .size = bufUsed
    };

    struct ImageHeader outHeader = {
        .aosp = (struct nano_app_binary_t) {
            .header_version = 1,
            .magic = NANOAPP_AOSP_MAGIC,
        },
        .layout = (struct ImageLayout) {
            .magic = GOOGLE_LAYOUT_MAGIC,
            .version = 1,
            .payload = LAYOUT_OS,
            .flags = layoutFlags,
        },
    };

    if (!bare)
        good = fwrite(&outHeader, sizeof(outHeader), 1, out) == 1;
    else
        good = fwrite(&os, sizeof(os), 1, out) == 1;
    good = good && fwrite(buf, bufUsed, 1, out) == 1;

    return good ? 0 : 2;
}

int main(int argc, char **argv)
{
    uint32_t bufUsed = 0;
    bool verbose = false;
    uint8_t *buf = NULL;
    uint64_t appId = 0;
    uint64_t keyId = 0;
    uint32_t appVer = 0;
    uint32_t layoutId = 0;
    uint32_t layoutFlags = 0;
    int ret = -1;
    uint32_t *u32Arg = NULL;
    uint64_t *u64Arg = NULL;
    const char **strArg = NULL;
    const char *appName = argv[0];
    int posArgCnt = 0;
    const char *posArg[2] = { NULL };
    FILE *out = NULL;
    const char *layoutName = "app";
    const char *prev = NULL;
    bool bareData = false;

    for (int i = 1; i < argc; i++) {
        char *end = NULL;
        if (argv[i][0] == '-') {
            prev = argv[i];
            if (!strcmp(argv[i], "-v"))
                verbose = true;
            else if (!strcmp(argv[i], "-r"))
                bareData = true;
            else if (!strcmp(argv[i], "-a"))
                u64Arg = &appId;
            else if (!strcmp(argv[i], "-e"))
                u32Arg = &appVer;
            else if (!strcmp(argv[i], "-k"))
                u64Arg = &keyId;
            else if (!strcmp(argv[i], "-n"))
                strArg = &layoutName;
            else if (!strcmp(argv[i], "-i"))
                u32Arg = &layoutId;
            else if (!strcmp(argv[i], "-f"))
                u32Arg = &layoutFlags;
            else
                fatalUsage(appName, "unknown argument", argv[i]);
        } else {
            if (u64Arg) {
                uint64_t tmp = strtoull(argv[i], &end, 16);
                if (*end == '\0')
                    *u64Arg = tmp;
                u64Arg = NULL;
            } else if (u32Arg) {
                uint32_t tmp = strtoul(argv[i], &end, 16);
                if (*end == '\0')
                    *u32Arg = tmp;
                u32Arg = NULL;
            } else if (strArg) {
                    *strArg = argv[i];
                strArg = NULL;
            } else {
                if (posArgCnt < 2)
                    posArg[posArgCnt++] = argv[i];
                else
                    fatalUsage(appName, "too many positional arguments", argv[i]);
            }
            prev = NULL;
        }
    }
    if (prev)
        fatalUsage(appName, "missing argument after", prev);

    if (!posArgCnt)
        fatalUsage(appName, "missing input file name", NULL);

    if (!layoutId) {
        if (strcmp(layoutName, "app") == 0)
            layoutId = LAYOUT_APP;
        else if (strcmp(layoutName, "os") == 0)
            layoutId = LAYOUT_OS;
        else if (strcmp(layoutName, "key") == 0)
            layoutId = LAYOUT_KEY;
        else
            fatalUsage(appName, "Invalid layout name", layoutName);
    }

    if (layoutId == LAYOUT_APP && !appId)
        fatalUsage(appName, "App layout requires app ID", NULL);
    if (layoutId == LAYOUT_KEY && !keyId)
        fatalUsage(appName, "Key layout requires key ID", NULL);
    if (layoutId == LAYOUT_OS && (keyId || appId))
        fatalUsage(appName, "OS layout does not need any ID", NULL);

    if (!posArg[1]) {
        out = stdout;
        stdlog = stderr;
    } else {
        out = fopen(posArg[1], "w");
        stdlog = stdout;
    }
    if (!out)
        fatalUsage(appName, "failed to create/open output file", posArg[1]);

    buf = loadFile(posArg[0], &bufUsed);
    DBG("Read %" PRIu32 " bytes from %s", bufUsed, posArg[0]);

    switch(layoutId) {
    case LAYOUT_APP:
        ret = handleApp(&buf, bufUsed, out, layoutFlags, appId, appVer, verbose);
        break;
    case LAYOUT_KEY:
        ret = handleKey(&buf, bufUsed, out, layoutFlags, appId, keyId);
        break;
    case LAYOUT_OS:
        ret = handleOs(&buf, bufUsed, out, layoutFlags, bareData);
        break;
    }

    free(buf);
    fclose(out);
    return ret;
}