/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#define INCL_DOS
#define INCL_DOSERRORS
#include <os2.h>
#include "secrng.h"
#include "prerror.h"
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <sys/stat.h>

static BOOL
clockTickTime(unsigned long *phigh, unsigned long *plow)
{
    APIRET rc = NO_ERROR;
    QWORD qword = { 0, 0 };

    rc = DosTmrQueryTime(&qword);
    if (rc != NO_ERROR)
        return FALSE;

    *phigh = qword.ulHi;
    *plow = qword.ulLo;

    return TRUE;
}

size_t
RNG_GetNoise(void *buf, size_t maxbuf)
{
    unsigned long high = 0;
    unsigned long low = 0;
    clock_t val = 0;
    int n = 0;
    int nBytes = 0;
    time_t sTime;

    if (maxbuf <= 0)
        return 0;

    clockTickTime(&high, &low);

    /* get the maximally changing bits first */
    nBytes = sizeof(low) > maxbuf ? maxbuf : sizeof(low);
    memcpy(buf, &low, nBytes);
    n += nBytes;
    maxbuf -= nBytes;

    if (maxbuf <= 0)
        return n;

    nBytes = sizeof(high) > maxbuf ? maxbuf : sizeof(high);
    memcpy(((char *)buf) + n, &high, nBytes);
    n += nBytes;
    maxbuf -= nBytes;

    if (maxbuf <= 0)
        return n;

    /* get the number of milliseconds that have elapsed since application started */
    val = clock();

    nBytes = sizeof(val) > maxbuf ? maxbuf : sizeof(val);
    memcpy(((char *)buf) + n, &val, nBytes);
    n += nBytes;
    maxbuf -= nBytes;

    if (maxbuf <= 0)
        return n;

    /* get the time in seconds since midnight Jan 1, 1970 */
    time(&sTime);
    nBytes = sizeof(sTime) > maxbuf ? maxbuf : sizeof(sTime);
    memcpy(((char *)buf) + n, &sTime, nBytes);
    n += nBytes;

    return n;
}

static BOOL
EnumSystemFiles(void (*func)(const char *))
{
    APIRET rc;
    ULONG sysInfo = 0;
    char bootLetter[2];
    char sysDir[_MAX_PATH] = "";
    char filename[_MAX_PATH];
    HDIR hdir = HDIR_CREATE;
    ULONG numFiles = 1;
    FILEFINDBUF3 fileBuf = { 0 };
    ULONG buflen = sizeof(FILEFINDBUF3);

    if (DosQuerySysInfo(QSV_BOOT_DRIVE, QSV_BOOT_DRIVE, (PVOID)&sysInfo,
                        sizeof(ULONG)) == NO_ERROR) {
        bootLetter[0] = sysInfo + 'A' - 1;
        bootLetter[1] = '\0';
        strcpy(sysDir, bootLetter);
        strcpy(sysDir + 1, ":\\OS2\\");

        strcpy(filename, sysDir);
        strcat(filename, "*.*");
    }

    rc = DosFindFirst(filename, &hdir, FILE_NORMAL, &fileBuf, buflen,
                      &numFiles, FIL_STANDARD);
    if (rc == NO_ERROR) {
        do {
            // pass the full pathname to the callback
            sprintf(filename, "%s%s", sysDir, fileBuf.achName);
            (*func)(filename);

            numFiles = 1;
            rc = DosFindNext(hdir, &fileBuf, buflen, &numFiles);
            if (rc != NO_ERROR && rc != ERROR_NO_MORE_FILES)
                printf("DosFindNext errod code = %d\n", rc);
        } while (rc == NO_ERROR);

        rc = DosFindClose(hdir);
        if (rc != NO_ERROR)
            printf("DosFindClose error code = %d", rc);
    } else
        printf("DosFindFirst error code = %d", rc);

    return TRUE;
}

static int dwNumFiles, dwReadEvery, dwFileToRead = 0;

static void
CountFiles(const char *file)
{
    dwNumFiles++;
}

static void
ReadFiles(const char *file)
{
    if ((dwNumFiles % dwReadEvery) == 0)
        RNG_FileForRNG(file);

    dwNumFiles++;
}

static void
ReadSingleFile(const char *filename)
{
    unsigned char buffer[1024];
    FILE *file;

    file = fopen((char *)filename, "rb");
    if (file != NULL) {
        while (fread(buffer, 1, sizeof(buffer), file) > 0)
            ;
        fclose(file);
    }
}

static void
ReadOneFile(const char *file)
{
    if (dwNumFiles == dwFileToRead) {
        ReadSingleFile(file);
    }

    dwNumFiles++;
}

static void
ReadSystemFiles(void)
{
    // first count the number of files
    dwNumFiles = 0;
    if (!EnumSystemFiles(CountFiles))
        return;

    RNG_RandomUpdate(&dwNumFiles, sizeof(dwNumFiles));

    // now read 10 files
    if (dwNumFiles == 0)
        return;

    dwReadEvery = dwNumFiles / 10;
    if (dwReadEvery == 0)
        dwReadEvery = 1; // less than 10 files

    dwNumFiles = 0;
    EnumSystemFiles(ReadFiles);
}

void
RNG_SystemInfoForRNG(void)
{
    unsigned long *plong = 0;
    PTIB ptib;
    PPIB ppib;
    APIRET rc = NO_ERROR;
    DATETIME dt;
    COUNTRYCODE cc = { 0 };
    COUNTRYINFO ci = { 0 };
    unsigned long actual = 0;
    char path[_MAX_PATH] = "";
    char fullpath[_MAX_PATH] = "";
    unsigned long pathlength = sizeof(path);
    FSALLOCATE fsallocate;
    FILESTATUS3 fstatus;
    unsigned long defaultdrive = 0;
    unsigned long logicaldrives = 0;
    unsigned long sysInfo[QSV_MAX] = { 0 };
    char buffer[20];
    int nBytes = 0;

    nBytes = RNG_GetNoise(buffer, sizeof(buffer));
    RNG_RandomUpdate(buffer, nBytes);

    /* allocate memory and use address and memory */
    plong = (unsigned long *)malloc(sizeof(*plong));
    RNG_RandomUpdate(&plong, sizeof(plong));
    RNG_RandomUpdate(plong, sizeof(*plong));
    free(plong);

    /* process info */
    rc = DosGetInfoBlocks(&ptib, &ppib);
    if (rc == NO_ERROR) {
        RNG_RandomUpdate(ptib, sizeof(*ptib));
        RNG_RandomUpdate(ppib, sizeof(*ppib));
    }

    /* time */
    rc = DosGetDateTime(&dt);
    if (rc == NO_ERROR) {
        RNG_RandomUpdate(&dt, sizeof(dt));
    }

    /* country */
    rc = DosQueryCtryInfo(sizeof(ci), &cc, &ci, &actual);
    if (rc == NO_ERROR) {
        RNG_RandomUpdate(&cc, sizeof(cc));
        RNG_RandomUpdate(&ci, sizeof(ci));
        RNG_RandomUpdate(&actual, sizeof(actual));
    }

    /* current directory */
    rc = DosQueryCurrentDir(0, path, &pathlength);
    strcat(fullpath, "\\");
    strcat(fullpath, path);
    if (rc == NO_ERROR) {
        RNG_RandomUpdate(fullpath, strlen(fullpath));
        // path info
        rc = DosQueryPathInfo(fullpath, FIL_STANDARD, &fstatus, sizeof(fstatus));
        if (rc == NO_ERROR) {
            RNG_RandomUpdate(&fstatus, sizeof(fstatus));
        }
    }

    /* file system info */
    rc = DosQueryFSInfo(0, FSIL_ALLOC, &fsallocate, sizeof(fsallocate));
    if (rc == NO_ERROR) {
        RNG_RandomUpdate(&fsallocate, sizeof(fsallocate));
    }

    /* drive info */
    rc = DosQueryCurrentDisk(&defaultdrive, &logicaldrives);
    if (rc == NO_ERROR) {
        RNG_RandomUpdate(&defaultdrive, sizeof(defaultdrive));
        RNG_RandomUpdate(&logicaldrives, sizeof(logicaldrives));
    }

    /* system info */
    rc = DosQuerySysInfo(1L, QSV_MAX, (PVOID)&sysInfo, sizeof(ULONG) * QSV_MAX);
    if (rc == NO_ERROR) {
        RNG_RandomUpdate(&sysInfo, sizeof(sysInfo));
    }

    // now let's do some files
    ReadSystemFiles();

    /* more noise */
    nBytes = RNG_GetNoise(buffer, sizeof(buffer));
    RNG_RandomUpdate(buffer, nBytes);
}

void
RNG_FileForRNG(const char *filename)
{
    struct stat stat_buf;
    unsigned char buffer[1024];
    FILE *file = 0;
    int nBytes = 0;
    static int totalFileBytes = 0;

    if (stat((char *)filename, &stat_buf) < 0)
        return;

    RNG_RandomUpdate((unsigned char *)&stat_buf, sizeof(stat_buf));

    file = fopen((char *)filename, "r");
    if (file != NULL) {
        for (;;) {
            size_t bytes = fread(buffer, 1, sizeof(buffer), file);

            if (bytes == 0)
                break;

            RNG_RandomUpdate(buffer, bytes);
            totalFileBytes += bytes;
            if (totalFileBytes > 250000)
                break;
        }
        fclose(file);
    }

    nBytes = RNG_GetNoise(buffer, 20);
    RNG_RandomUpdate(buffer, nBytes);
}

static void
rng_systemJitter(void)
{
    dwNumFiles = 0;
    EnumSystemFiles(ReadOneFile);
    dwFileToRead++;
    if (dwFileToRead >= dwNumFiles) {
        dwFileToRead = 0;
    }
}

size_t
RNG_SystemRNG(void *dest, size_t maxLen)
{
    return rng_systemFromNoise(dest, maxLen);
}