/**********
 * 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.
 **********/

// 68K side of 68K->ARM calls into bignum library

#include "includes.h"
#include "bn.h"
#include "bn_armstubs.h"
#include "armstubs.h"

#include "rand.h"
#include "xmalloc.h"

extern void assert_failed(uint32_t which);


static const char bn_bits[256]={
    0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,
    5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
    6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
    6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
    7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
    7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
    7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
    7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
    8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
    8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
    8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
    8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
    8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
    8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
    8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
    8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
};


static uint32_t BN_dispatch_to_arm(uint32_t fn, uint32_t *param, int paramCount)
{
    static bn_globals g;
    static int inited = 0;

    if (!inited) {
        inited = 1;

        g.rand_bytes = (uint32_t)&RAND_bytes;
        g.assert_failed = (uint32_t)&assert_failed;
        g.arena_malloc = (uint32_t)&arena_malloc;
        g.arena_free = (uint32_t)&arena_free;
        g.bn_bits = &bn_bits[0];
    }

    return dispatch_to_arm(fn, (pace_globals *)&g, param, paramCount);
}

int BN_is_negative(BIGNUM *a) {
    uint32_t param;

    param = (uint32_t)a;
    return BN_dispatch_to_arm(BN_is_negative_id, &param, 1);
}

BN_CTX *BN_CTX_new(void) {
    return (BN_CTX *) BN_dispatch_to_arm(BN_CTX_new_id, NULL, 0);
}

void	BN_CTX_free(BN_CTX *c) {
    uint32_t param;

    param = (uint32_t)c;
    BN_dispatch_to_arm(BN_CTX_free_id, &param, 1);
}

void	BN_CTX_start(BN_CTX *ctx) {
    uint32_t param;
    
    param = (uint32_t)ctx;
    BN_dispatch_to_arm(BN_CTX_start_id, &param, 1);
}

BIGNUM *BN_CTX_get(BN_CTX *ctx) {
    uint32_t param;

    param = (uint32_t)ctx;
    return (BIGNUM *) BN_dispatch_to_arm(BN_CTX_get_id, &param, 1);
}

void	BN_CTX_end(BN_CTX *ctx) {
    uint32_t param;
    
    param = (uint32_t)ctx;
    BN_dispatch_to_arm(BN_CTX_end_id, &param, 1);
}

int     BN_rand(BIGNUM *rnd, int bits, int top,int bottom) {
    uint32_t param[4];
    
    param[0] = (uint32_t)rnd;
    param[1] = (uint32_t)bits;
    param[2] = (uint32_t)top;
    param[3] = (uint32_t)bottom;
    return (int) BN_dispatch_to_arm(BN_rand_id, param, 4);
}

int	BN_num_bits(const BIGNUM *a) {
    uint32_t param;

    param = (uint32_t)a;
    return (int) BN_dispatch_to_arm(BN_num_bits_id, &param, 1);
}

BIGNUM *BN_new(void) {
    return (BIGNUM *) BN_dispatch_to_arm(BN_new_id, NULL, 0);
}

void	BN_clear_free(BIGNUM *a) {
    uint32_t param;

    param = (uint32_t)a;
    BN_dispatch_to_arm(BN_clear_free_id, &param, 1);
}

BIGNUM *BN_bin2bn(const unsigned char *s,int len,BIGNUM *ret) {
    uint32_t param[3];
    
    param[0] = (uint32_t)s;
    param[1] = (uint32_t)len;
    param[2] = (uint32_t)ret;
    return (BIGNUM *) BN_dispatch_to_arm(BN_bin2bn_id, param, 3);
}

int	BN_bn2bin(const BIGNUM *a, unsigned char *to) {
    uint32_t param[2];
    
    param[0] = (uint32_t)a;
    param[1] = (uint32_t)to;
    return (int) BN_dispatch_to_arm(BN_bn2bin_id, param, 2);
}

int	BN_cmp(const BIGNUM *a, const BIGNUM *b) {
    uint32_t param[2];
    
    param[0] = (uint32_t)a;
    param[1] = (uint32_t)b;
    return (int) BN_dispatch_to_arm(BN_cmp_id, param, 2);
}

void	BN_free(BIGNUM *a) {
    uint32_t param;
    
    param = (uint32_t)a;
    BN_dispatch_to_arm(BN_free_id, &param, 1);
}

int	BN_is_bit_set(const BIGNUM *a, int n) {
    uint32_t param[2];
    
    param[0] = (uint32_t)a;
    param[1] = (uint32_t)n;
    return (int) BN_dispatch_to_arm(BN_is_bit_set_id, param, 2);
}

int	BN_mod_exp_mont(BIGNUM *r, const BIGNUM *a, const BIGNUM *p,
                        const BIGNUM *m, BN_CTX *ctx, BN_MONT_CTX *m_ctx) 
{
    uint32_t param[6];

    param[0] = (uint32_t)r;
    param[1] = (uint32_t)a;
    param[2] = (uint32_t)p;
    param[3] = (uint32_t)m;
    param[4] = (uint32_t)ctx;
    param[5] = (uint32_t)m_ctx;
    return (int) BN_dispatch_to_arm(BN_mod_exp_mont_id, param, 6);
}

int	BN_mod_exp_mont_word(BIGNUM *r, BN_ULONG a, const BIGNUM *p,
                             const BIGNUM *m, BN_CTX *ctx, BN_MONT_CTX *m_ctx) 
{
    uint32_t param[6];
    
    param[0] = (uint32_t)r;
    param[1] = (uint32_t)a;
    param[2] = (uint32_t)p;
    param[3] = (uint32_t)m;
    param[4] = (uint32_t)ctx;
    param[5] = (uint32_t)m_ctx;
    return (int) BN_dispatch_to_arm(BN_mod_exp_mont_word_id, param, 6);
}

char * 	BN_bn2hex(const BIGNUM *a) {
    uint32_t param;

    param = (uint32_t)a;
    return (char *) BN_dispatch_to_arm(BN_bn2hex_id, &param, 1);
}

uint32_t 	BN_hex2bn(BIGNUM **a, const char *str) {
    uint32_t param[2];

    param[0] = (uint32_t)a;
    param[1] = (uint32_t)str;
    return (uint32_t) BN_dispatch_to_arm(BN_hex2bn_id, param, 2);
}

BN_MONT_CTX *BN_MONT_CTX_new(void) {
    return (BN_MONT_CTX *) BN_dispatch_to_arm(BN_MONT_CTX_new_id, NULL, 0);
}

void BN_MONT_CTX_free(BN_MONT_CTX *mont) {
    uint32_t param;

    param = (uint32_t)mont;
    BN_dispatch_to_arm(BN_MONT_CTX_free_id, &param, 1);
}

int BN_MONT_CTX_set(BN_MONT_CTX *mont,const BIGNUM *mod,BN_CTX *ctx) {
    uint32_t param[3];
    
    param[0] = (uint32_t)mont;
    param[1] = (uint32_t)mod;
    param[2] = (uint32_t)ctx;
    return (int) BN_dispatch_to_arm(BN_MONT_CTX_set_id, param, 3);
}

int BN_is_zero(const BIGNUM *a) {
    uint32_t param;

    param = (uint32_t)a;
    return (int) BN_dispatch_to_arm(BN_is_zero_id, &param, 1);
}

int BN_mod_mul(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m, BN_CTX *ctx) {
    uint32_t param[5];

    param[0] = (uint32_t)r;
    param[1] = (uint32_t)a;
    param[2] = (uint32_t)b;
    param[3] = (uint32_t)m;
    param[4] = (uint32_t)ctx;
    return (int) BN_dispatch_to_arm(BN_mod_mul_id, param, 5);
}

int BN_ucmp(const BIGNUM *a, const BIGNUM *b) {
    uint32_t param[2];

    param[0] = (uint32_t)a;
    param[1] = (uint32_t)b;
    return (uint32_t) BN_dispatch_to_arm(BN_ucmp_id, param, 2);
}

BIGNUM *BN_mod_inverse(BIGNUM *ret, const BIGNUM *a, const BIGNUM *n,
                       BN_CTX *ctx)
{
    uint32_t param[4];

    param[0] = (uint32_t)ret;
    param[1] = (uint32_t)a;
    param[2] = (uint32_t)n;
    param[3] = (uint32_t)ctx;
    return (BIGNUM *) BN_dispatch_to_arm(BN_mod_inverse_id, param, 4);
}

int BN_div(BIGNUM *dv, BIGNUM *rem, const BIGNUM *m, const BIGNUM *d, BN_CTX *ctx) {
    uint32_t param[5];

    param[0] = (uint32_t)dv;
    param[1] = (uint32_t)rem;
    param[2] = (uint32_t)m;
    param[3] = (uint32_t)d;
    param[4] = (uint32_t)ctx;
    return (int) BN_dispatch_to_arm(BN_div_id, param, 5);
}

int BN_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b) {
    uint32_t param[3];

    param[0] = (uint32_t)r;
    param[1] = (uint32_t)a;
    param[2] = (uint32_t)b;
    return (int) BN_dispatch_to_arm(BN_add_id, param, 3);
}

int BN_sub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b) {
    uint32_t param[3];

    param[0] = (uint32_t)r;
    param[1] = (uint32_t)a;
    param[2] = (uint32_t)b;
    return (int) BN_dispatch_to_arm(BN_sub_id, param, 3);
}

int BN_rand_range(BIGNUM *r, BIGNUM *range) {
    uint32_t param[2];

    param[0] = (uint32_t)r;
    param[1] = (uint32_t)range;
    return (int) BN_dispatch_to_arm(BN_rand_range_id, param, 2);
}

#ifdef BNTEST

// fixme these are broken

int BN_bntest_rand(BIGNUM *rnd, int bits, int top,int bottom) {
    bn_args args;

    args.param[0] = (uint32_t)rnd;
    args.param[1] = (uint32_t)bits;
    args.param[2] = (uint32_t)top;
    args.param[3] = (uint32_t)bottom;
    return (int) BN_dispatch_to_arm(BN_bntest_rand_id, &args);
}

void BN_set_negative(BIGNUM *a, int neg) {
    bn_args args;

    args.param[0] = (uint32_t)a;
    args.param[1] = (uint32_t)neg;
    BN_dispatch_to_arm(BN_set_negative_id, &args);
}

BIGNUM * BN_copy(BIGNUM *a, const BIGNUM *b) {
    bn_args args;

    args.param[0] = (uint32_t)a;
    args.param[1] = (uint32_t)b;
    return (BIGNUM *) BN_dispatch_to_arm(BN_copy_id, &args);
}

int	BN_set_bit(BIGNUM *a, int n) {
    bn_args args;
    
    args.param[0] = (uint32_t)a;
    args.param[1] = (uint32_t)n;
    return (int) BN_dispatch_to_arm(BN_set_bit_id, &args);
}

int	BN_add_word(BIGNUM *a, BN_ULONG w) {
    bn_args args;
    
    args.param[0] = (uint32_t)a;
    args.param[1] = (uint32_t)w;
    return (int) BN_dispatch_to_arm(BN_add_word_id, &args);
}

int BN_lshift(BIGNUM *r, const BIGNUM *a, int n) {
    bn_args args;

    args.param[0] = (uint32_t)r;
    args.param[1] = (uint32_t)a;
    args.param[2] = (uint32_t)n;
    return (int) BN_dispatch_to_arm(BN_lshift_id, &args);
}

int BN_mul(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx) {
    bn_args args;

    args.param[0] = (uint32_t)r;
    args.param[1] = (uint32_t)a;
    args.param[2] = (uint32_t)b;
    args.param[3] = (uint32_t)ctx;
    return (int) BN_dispatch_to_arm(BN_mul_id, &args);
}

BN_RECP_CTX *BN_RECP_CTX_new(void) {
    bn_args args;

    return (BN_RECP_CTX *) BN_dispatch_to_arm(BN_RECP_CTX_new_id, &args);
}

int BN_sqr(BIGNUM *r, const BIGNUM *a, BN_CTX *ctx) {
    bn_args args;

    args.param[0] = (uint32_t)r;
    args.param[1] = (uint32_t)a;
    args.param[2] = (uint32_t)ctx;
    return (int) BN_dispatch_to_arm(BN_sqr_id, &args);
}

int BN_nnmod(BIGNUM *r, const BIGNUM *m, const BIGNUM *d, BN_CTX *ctx) {
    bn_args args;

    args.param[0] = (uint32_t)r;
    args.param[1] = (uint32_t)m;
    args.param[2] = (uint32_t)d;
    args.param[3] = (uint32_t)ctx;
    return (int) BN_dispatch_to_arm(BN_nnmod_id, &args);
}

int BN_to_montgomery(BIGNUM *r, const BIGNUM *a, BN_MONT_CTX *mont, BN_CTX *ctx) {
    bn_args args;

    args.param[0] = (uint32_t)r;
    args.param[1] = (uint32_t)a;
    args.param[2] = (uint32_t)mont;
    args.param[3] = (uint32_t)ctx;
    return (int) BN_dispatch_to_arm(BN_to_montgomery_id, &args);
}

int BN_mod_mul_montgomery(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_MONT_CTX *mont, BN_CTX *ctx) {
    bn_args args;

    args.param[0] = (uint32_t)r;
    args.param[1] = (uint32_t)a;
    args.param[2] = (uint32_t)b;
    args.param[3] = (uint32_t)mont;
    args.param[4] = (uint32_t)ctx;
    return (int) BN_dispatch_to_arm(BN_mod_mul_montgomery_id, &args);
}

int BN_from_montgomery(BIGNUM *r, const BIGNUM *a, BN_MONT_CTX *mont, BN_CTX *ctx) {
    bn_args args;

    args.param[0] = (uint32_t)r;
    args.param[1] = (uint32_t)a;
    args.param[2] = (uint32_t)mont;
    args.param[3] = (uint32_t)ctx;
    return (int) BN_dispatch_to_arm(BN_from_montgomery_id, &args);
}

int BN_mod_exp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, const BIGNUM *m, BN_CTX *ctx) {
    bn_args args;

    args.param[0] = (uint32_t)r;
    args.param[1] = (uint32_t)a;
    args.param[2] = (uint32_t)p;
    args.param[3] = (uint32_t)m;
    args.param[4] = (uint32_t)ctx;
    return (int) BN_dispatch_to_arm(BN_mod_exp_id, &args);
}


int BN_exp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx) {
    bn_args args;

    args.param[0] = (uint32_t)r;
    args.param[1] = (uint32_t)a;
    args.param[2] = (uint32_t)p;
    args.param[3] = (uint32_t)ctx;
    return (int) BN_dispatch_to_arm(BN_exp_id, &args);
}

int	BN_RECP_CTX_set(BN_RECP_CTX *recp,const BIGNUM *rdiv,BN_CTX *ctx) {
    bn_args args;
    
    args.param[0] = (uint32_t)recp;
    args.param[1] = (uint32_t)rdiv;
    args.param[2] = (uint32_t)ctx;
    return (int) BN_dispatch_to_arm(BN_RECP_CTX_set_id, &args);
}

int	BN_div_recp(BIGNUM *dv, BIGNUM *rem, const BIGNUM *m,
                    BN_RECP_CTX *recp, BN_CTX *ctx) {
    bn_args args;
    
    args.param[0] = (uint32_t)dv;
    args.param[1] = (uint32_t)rem;
    args.param[2] = (uint32_t)m;
    args.param[3] = (uint32_t)recp;
    args.param[4] = (uint32_t)ctx;
    return (int) BN_dispatch_to_arm(BN_div_recp_id, &args);
}

void	BN_RECP_CTX_free(BN_RECP_CTX *recp) {
    bn_args args;
    
    args.param[0] = (uint32_t)recp;
    BN_dispatch_to_arm(BN_RECP_CTX_free_id, &args);
}

int	BN_set_word(BIGNUM *a, BN_ULONG w) {
    bn_args args;

    args.param[0] = (uint32_t)a;
    args.param[1] = (uint32_t)w;
    return (int) BN_dispatch_to_arm(BN_set_word_id, &args);
}

#endif

uint32_t BN_time(uint32_t oldtime) {
    uint32_t param;

    param = (uint32_t)oldtime;
    return BN_dispatch_to_arm(BN_time_id, &param, 1);
}
