/*
 * testbn.c: standalone test program for the bignum code.
 */

/*
 * Accepts input on standard input, in the form generated by
 * testdata/bignum.py.
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#include "ssh.h"
#include "sshbn.h"

void modalfatalbox(const char *p, ...)
{
    va_list ap;
    fprintf(stderr, "FATAL ERROR: ");
    va_start(ap, p);
    vfprintf(stderr, p, ap);
    va_end(ap);
    fputc('\n', stderr);
    exit(1);
}

int random_byte(void)
{
    modalfatalbox("random_byte called in testbn");
    return 0;
}

void queue_idempotent_callback(IdempotentCallback *ic) { assert(0); }

#define fromxdigit(c) ( (c)>'9' ? ((c)&0xDF) - 'A' + 10 : (c) - '0' )

/* For Unix in particular, but harmless if this main() is reused elsewhere */
const bool buildinfo_gtk_relevant = false;

int main(int argc, char **argv)
{
    char *buf;
    int line = 0;
    int passes = 0, fails = 0;

    printf("BIGNUM_INT_BITS = %d\n", (int)BIGNUM_INT_BITS);

    while ((buf = fgetline(stdin)) != NULL) {
        int maxlen = strlen(buf);
        unsigned char *data = snewn(maxlen, unsigned char);
        unsigned char *ptrs[5], *q;
        int ptrnum;
        char *bufp = buf;

        line++;

        q = data;
        ptrnum = 0;

        while (*bufp && !isspace((unsigned char)*bufp))
            bufp++;
        if (*bufp)
            *bufp++ = '\0';

        while (*bufp) {
            char *start, *end;
            int i;

            while (*bufp && !isxdigit((unsigned char)*bufp))
                bufp++;
            start = bufp;

            if (!*bufp)
                break;

            while (*bufp && isxdigit((unsigned char)*bufp))
                bufp++;
            end = bufp;

            if (ptrnum >= lenof(ptrs))
                break;
            ptrs[ptrnum++] = q;
            
            for (i = -((end - start) & 1); i < end-start; i += 2) {
                unsigned char val = (i < 0 ? 0 : fromxdigit(start[i]));
                val = val * 16 + fromxdigit(start[i+1]);
                *q++ = val;
            }
        }

        if (ptrnum < lenof(ptrs))
            ptrs[ptrnum] = q;

        if (!strcmp(buf, "mul")) {
            Bignum a, b, c, p;

            if (ptrnum != 3) {
                printf("%d: mul with %d parameters, expected 3\n", line, ptrnum);
                exit(1);
            }
            a = bignum_from_bytes(ptrs[0], ptrs[1]-ptrs[0]);
            b = bignum_from_bytes(ptrs[1], ptrs[2]-ptrs[1]);
            c = bignum_from_bytes(ptrs[2], ptrs[3]-ptrs[2]);
            p = bigmul(a, b);

            if (bignum_cmp(c, p) == 0) {
                passes++;
            } else {
                char *as = bignum_decimal(a);
                char *bs = bignum_decimal(b);
                char *cs = bignum_decimal(c);
                char *ps = bignum_decimal(p);
                
                printf("%d: fail: %s * %s gave %s expected %s\n",
                       line, as, bs, ps, cs);
                fails++;

                sfree(as);
                sfree(bs);
                sfree(cs);
                sfree(ps);
            }
            freebn(a);
            freebn(b);
            freebn(c);
            freebn(p);
        } else if (!strcmp(buf, "modmul")) {
            Bignum a, b, m, c, p;

            if (ptrnum != 4) {
                printf("%d: modmul with %d parameters, expected 4\n",
                       line, ptrnum);
                exit(1);
            }
            a = bignum_from_bytes(ptrs[0], ptrs[1]-ptrs[0]);
            b = bignum_from_bytes(ptrs[1], ptrs[2]-ptrs[1]);
            m = bignum_from_bytes(ptrs[2], ptrs[3]-ptrs[2]);
            c = bignum_from_bytes(ptrs[3], ptrs[4]-ptrs[3]);
            p = modmul(a, b, m);

            if (bignum_cmp(c, p) == 0) {
                passes++;
            } else {
                char *as = bignum_decimal(a);
                char *bs = bignum_decimal(b);
                char *ms = bignum_decimal(m);
                char *cs = bignum_decimal(c);
                char *ps = bignum_decimal(p);
                
                printf("%d: fail: %s * %s mod %s gave %s expected %s\n",
                       line, as, bs, ms, ps, cs);
                fails++;

                sfree(as);
                sfree(bs);
                sfree(ms);
                sfree(cs);
                sfree(ps);
            }
            freebn(a);
            freebn(b);
            freebn(m);
            freebn(c);
            freebn(p);
        } else if (!strcmp(buf, "pow")) {
            Bignum base, expt, modulus, expected, answer;

            if (ptrnum != 4) {
                printf("%d: pow with %d parameters, expected 4\n", line, ptrnum);
                exit(1);
            }

            base = bignum_from_bytes(ptrs[0], ptrs[1]-ptrs[0]);
            expt = bignum_from_bytes(ptrs[1], ptrs[2]-ptrs[1]);
            modulus = bignum_from_bytes(ptrs[2], ptrs[3]-ptrs[2]);
            expected = bignum_from_bytes(ptrs[3], ptrs[4]-ptrs[3]);
            answer = modpow(base, expt, modulus);

            if (bignum_cmp(expected, answer) == 0) {
                passes++;
            } else {
                char *as = bignum_decimal(base);
                char *bs = bignum_decimal(expt);
                char *cs = bignum_decimal(modulus);
                char *ds = bignum_decimal(answer);
                char *ps = bignum_decimal(expected);
                
                printf("%d: fail: %s ^ %s mod %s gave %s expected %s\n",
                       line, as, bs, cs, ds, ps);
                fails++;

                sfree(as);
                sfree(bs);
                sfree(cs);
                sfree(ds);
                sfree(ps);
            }
            freebn(base);
            freebn(expt);
            freebn(modulus);
            freebn(expected);
            freebn(answer);
        } else if (!strcmp(buf, "divmod")) {
            Bignum n, d, expect_q, expect_r, answer_q, answer_r;
            bool fail;

            if (ptrnum != 4) {
                printf("%d: divmod with %d parameters, expected 4\n", line, ptrnum);
                exit(1);
            }

            n = bignum_from_bytes(ptrs[0], ptrs[1]-ptrs[0]);
            d = bignum_from_bytes(ptrs[1], ptrs[2]-ptrs[1]);
            expect_q = bignum_from_bytes(ptrs[2], ptrs[3]-ptrs[2]);
            expect_r = bignum_from_bytes(ptrs[3], ptrs[4]-ptrs[3]);
            answer_q = bigdiv(n, d);
            answer_r = bigmod(n, d);

            fail = false;
            if (bignum_cmp(expect_q, answer_q) != 0) {
                char *as = bignum_decimal(n);
                char *bs = bignum_decimal(d);
                char *cs = bignum_decimal(answer_q);
                char *ds = bignum_decimal(expect_q);

                printf("%d: fail: %s / %s gave %s expected %s\n",
                       line, as, bs, cs, ds);
                fail = true;

                sfree(as);
                sfree(bs);
                sfree(cs);
                sfree(ds);
            }
            if (bignum_cmp(expect_r, answer_r) != 0) {
                char *as = bignum_decimal(n);
                char *bs = bignum_decimal(d);
                char *cs = bignum_decimal(answer_r);
                char *ds = bignum_decimal(expect_r);

                printf("%d: fail: %s mod %s gave %s expected %s\n",
                       line, as, bs, cs, ds);
                fail = true;

                sfree(as);
                sfree(bs);
                sfree(cs);
                sfree(ds);
            }

            freebn(n);
            freebn(d);
            freebn(expect_q);
            freebn(expect_r);
            freebn(answer_q);
            freebn(answer_r);

            if (fail)
                fails++;
            else
                passes++;
        } else {
            printf("%d: unrecognised test keyword: '%s'\n", line, buf);
            exit(1);
        }

        sfree(buf);
        sfree(data);
    }

    printf("passed %d failed %d total %d\n", passes, fails, passes+fails);
    return fails != 0;
}
