/**********
 * Copyright (c) 2003-2004 Greg Parker.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY GREG PARKER ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 **********/

// fixme additional entropy sources for non-CPM:
// microphone input
// hardware timers / counters


#include "includes.h"
#include "rand.h"
#include "fortuna/fortuna.h"
#include "openssl/bn/bn.h"
#include "rsrc/rsrc.h"
#include <CPMLib.h>


static uint32_t lastTime = 0;
static int useARMTime = -1;

static uint32_t RAND_time(void)
{
    rand_log(__PRETTY_FUNCTION__);

    if (useARMTime == -1) {
        complain("useARMTime not initialized!");
    }
    if (useARMTime) {
        lastTime = BN_time(lastTime);
    } else {
        lastTime = TimGetTicks();
    }
    return lastTime;
}



static int UseCPM = 0;

// Fortuna version
static struct fortuna_t *rng = NULL;
#define SAVED_SEED_SIZE 32

// CPM version
static UInt16 CPMLibRefnum = 0;
static int LoadedCPMLib = 0;


static Boolean CPMLibAvailable(void) 
{
    Err err;
    UInt16 providerCount;

    // fixme dunno if CPMLib is any good, 
    // or whether CPMLibGenerateRandomBytes is documented and usable, 
    // or whether CPMLibAddRandomSeed is necessary to make it work.

    rand_log(__PRETTY_FUNCTION__);

    return false;

    err = SysLibFind("CPMLib", &CPMLibRefnum);
    if (!err) {
        // library already loaded
        return true;
    } else {
        // not already loaded - try to load it ourselves
        err = SysLibLoad(sysFileTLibrary, cpmCreator, &CPMLibRefnum);
        if (!err) {
            // loaded successfully - balance with SysLibRemove() later
            LoadedCPMLib = 1;
            return true;
        } else {
            // not loaded
            return false;
        }
    }
}


void RAND_init(void)
{
    Err err;

    rand_log(__PRETTY_FUNCTION__);

    if (CPMLibAvailable()) {
        UInt16 providerCount;
        err = CPMLibOpen(CPMLibRefnum, &providerCount);
        if (!err  ||  err == cpmErrAlreadyOpen) {
            UseCPM = 1;
        }
    }

    if (!UseCPM) {
        // init Fortuna instead of CPM
        uint8_t *ftrBuf;
        uint32_t value32;
        uint8_t value8;
        uint32_t warned;

        if (!rng) rng = fortuna_create();

        if (useARMTime == -1) {
            // use ARM time code on Tungsten C only
            uint32_t companyID;
            uint32_t deviceID;
            FtrGet(sysFtrCreator, sysFtrNumOEMCompanyID, &companyID);
            FtrGet(sysFtrCreator, sysFtrNumOEMDeviceID, &deviceID);
            useARMTime = (companyID == 'palm'  &&  deviceID == 'MT64'); // T|C
            
            if (!useARMTime) {
                // Entropy sources suck - complain
                err = FtrGet(PSSH_CREATOR, ftrEntropyWarning, &warned);
                if (err) warned = 0;
                if (!warned) {
                    FrmCustomAlert(AlertFormID, "WARNING: Low-quality random number generator in use. pssh is currently EVEN MORE INSECURE than on devices with a high-quality random number generator.", " ", " ");
                    warned = 1;
                    FtrSet(PSSH_CREATOR, ftrEntropyWarning, warned);
                }
            }
        }

        // Seed PRNG with initial values. 
        // All values are be placed in pool 0; 
        // the generator will be reseeded from pool 0 when the first 
        // random bytes are extracted.
        
        // saved seed file
        err = FtrGet(PSSH_CREATOR, ftrEntropy, (UInt32 *)&ftrBuf);
        if (!err  &&  ftrBuf  &&  MemPtrSize(ftrBuf) == SAVED_SEED_SIZE) {
            fortuna_put_bytes(rng, RAND_source_init, 0, ftrBuf, SAVED_SEED_SIZE);
            DmSet(ftrBuf, 0, SAVED_SEED_SIZE, 0);
        } else {
            // complain("no seed file");
            // fixme add a few seconds of high-resolution timer queries 
            // and other additional sources if seed file is gone
            
            // record count and byte size of all databases
            UInt16 dbIndex;
            UInt16 dbCount = DmNumDatabases(0);
            for (dbIndex = 0; dbIndex < dbCount; dbIndex++) {
                UInt32 recordCount;
                UInt32 dbBytes;
                LocalID dbid = DmGetDatabase(0, dbIndex);
                if (dbid == 0) continue;
                
                err = DmDatabaseSize(0, dbid, &recordCount, &dbBytes, NULL);
                if (err) continue;
                
                fortuna_put_bytes(rng, RAND_source_init, 0, (uint8_t *)&recordCount, sizeof(recordCount));
                fortuna_put_bytes(rng, RAND_source_init, 0, (uint8_t *)&dbBytes, sizeof(dbBytes));
            }
        }
        FtrPtrFree(PSSH_CREATOR, ftrEntropy);
        
        value32 = RAND_time();
        fortuna_put_bytes(rng, RAND_source_init, 0, (uint8_t *)&value32, sizeof(value32));
        
        // current date and clock time
        value32 = TimGetSeconds();
        fortuna_put_bytes(rng, RAND_source_init, 0, (uint8_t *)&value32, sizeof(value32));
        
        value32 = RAND_time();
        fortuna_put_bytes(rng, RAND_source_init, 0, (uint8_t *)&value32, sizeof(value32));
        
        // battery level
        SysBatteryInfo(false, NULL, NULL, NULL, NULL, NULL, &value8);
        fortuna_put_bytes(rng, RAND_source_init, 0, &value8, sizeof(value8));
        
        value32 = RAND_time();
        fortuna_put_bytes(rng, RAND_source_init, 0, (uint8_t *)&value32, sizeof(value32));
    }
}


void RAND_stop(void)
{
    rand_log(__PRETTY_FUNCTION__);

    if (UseCPM) {
        if (CPMLibRefnum) {
            CPMLibClose(CPMLibRefnum);
            if (LoadedCPMLib) {
                SysLibRemove(CPMLibRefnum);
            }
        }
    }
    else {
        uint8_t *ftrBuf = NULL;
        Err err;
        
        if (!rng) return;
        
        // write new seed file
        err = FtrPtrNew(PSSH_CREATOR, ftrEntropy, SAVED_SEED_SIZE, (void **)&ftrBuf);
        if (!err  &&  ftrBuf  &&  MemPtrSize(ftrBuf) == SAVED_SEED_SIZE) {
            uint8_t *bytesBuf = arena_malloc(SAVED_SEED_SIZE);
            fortuna_get_bytes(rng, bytesBuf, SAVED_SEED_SIZE);
            DmWrite(ftrBuf, 0, bytesBuf, SAVED_SEED_SIZE);
            arena_free(bytesBuf);
        }
    }
}


void RAND_add(uint8_t sourceID, uint8_t poolID, uint8_t *inBytes, unsigned int byteCount)
{
    rand_log(__PRETTY_FUNCTION__);

    if (UseCPM) {
        // do nothing
        // fixme make sure CPM RNG doesn't need manual seeding
    }
    else {
        if (!rng) rng = fortuna_create();
        fortuna_put_bytes(rng, sourceID, poolID, inBytes, byteCount);
    }
}


int32_t RAND_pseudo_bytes(char *buf, uint32_t len) 
{
    rand_log(__PRETTY_FUNCTION__);

    return RAND_bytes(buf, len);
}


static uint8_t generatorPool = 0;
int32_t RAND_bytes(char *buf, uint32_t len)
{
    rand_log(__PRETTY_FUNCTION__);

    if (UseCPM) {
        Err err = CPMLibGenerateRandomBytes(CPMLibRefnum, buf, &len);
        if (err) fatal("CPMLibGenerateRandomBytes failed (%d)", err);
    }
    else {
        uint32_t time;
        time = RAND_time();
        if (!rng) rng = fortuna_create();
        
        RAND_add(RAND_source_generator, generatorPool++, 
                 (uint8_t *)&time, sizeof(time));
        fortuna_get_bytes(rng, buf, len);
    }
    return 1;
}


static uint8_t eventPool = 0;
void RAND_add_event_entropy(EventType *e)
{
    rand_log(__PRETTY_FUNCTION__);

    if (UseCPM) {
        // do nothing
    } 
    else {
        UInt16 v[4];
        uint32_t time = RAND_time();
        v[0] = time & 0xffff;
        v[1] = time >> 16;
        v[2] = e->eType;
        switch (e->eType) {
        case keyDownEvent: 
            v[3] = e->data.keyDown.chr; 
            break;
        case penDownEvent:
        case penUpEvent:
        case penMoveEvent:
        default:
            v[3] = (e->screenX << 8) | e->screenY;
            break;
        }
        
        RAND_add(RAND_source_event, eventPool++, (uint8_t *)v, sizeof(v));
    }
}


static uint8_t netPool = 0;
void RAND_add_net_entropy(void)
{
    rand_log(__PRETTY_FUNCTION__);

    if (UseCPM) {
        // do nothing
    }
    else {
        uint32_t time = RAND_time();
        RAND_add(RAND_source_net, netPool++, (uint8_t *)&time, sizeof(time));
    }
}
