/*************************************************************************
* Copyright (C) 2002 Intel Corporation
*
* 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.
*************************************************************************/

/*
//               Intel(R) Cryptography Primitives Library
//
//  Contents:
//     ippsGcd_BN()
//
*/

#include "owndefs.h"
#include "owncp.h"
#include "pcpbn.h"
#include "pcptool.h"


/*F*
//    Name: ippsGcd_BN
//
// Purpose: compute GCD value.
//
// Returns:                Reason:
//    ippStsNullPtrErr        pA  == NULL
//                            pB  == NULL
//                            pGCD  == NULL
//    ippStsContextMatchErr   !BN_VALID_ID(pA)
//                            !BN_VALID_ID(pB)
//                            !BN_VALID_ID(pGCD)
//    ippStsBadArgErr         A==B==0
//    ippStsOutOfRangeErr     pGCD can not hold result
//    ippStsNoErr             no errors
//
// Parameters:
//    pA    source BigNum
//    pB    source BigNum
//    pGCD    GCD value
//
*F*/
IPPFUN(IppStatus, ippsGcd_BN, (IppsBigNumState * pA, IppsBigNumState* pB, IppsBigNumState* pGCD))
{
    IPP_BAD_PTR3_RET(pA, pB, pGCD);

    IPP_BADARG_RET(!BN_VALID_ID(pA), ippStsContextMatchErr);
    IPP_BADARG_RET(!BN_VALID_ID(pB), ippStsContextMatchErr);
    IPP_BADARG_RET(!BN_VALID_ID(pGCD), ippStsContextMatchErr);

    IPP_BADARG_RET(BN_ROOM(pGCD) < IPP_MIN(BN_SIZE(pA), BN_SIZE(pB)), ippStsOutOfRangeErr);

    {
        IppsBigNumState* x = pA;
        IppsBigNumState* y = pB;
        IppsBigNumState* g = pGCD;

        int aIsZero = BN_SIZE(pA) == 1 && BN_NUMBER(pA)[0] == 0;
        int bIsZero = BN_SIZE(pB) == 1 && BN_NUMBER(pB)[0] == 0;

        if (aIsZero && bIsZero)
            return ippStsBadArgErr;
        if (aIsZero && !bIsZero) {
            COPY_BNU(BN_NUMBER(g), BN_NUMBER(pB), BN_SIZE(pB));
            BN_SIZE(g) = BN_SIZE(pB);
            BN_SIGN(g) = ippBigNumPOS;
            return ippStsNoErr;
        }
        if (bIsZero && !aIsZero) {
            COPY_BNU(BN_NUMBER(g), BN_NUMBER(pA), BN_SIZE(pA));
            BN_SIZE(g) = BN_SIZE(pA);
            BN_SIGN(g) = ippBigNumPOS;
            return ippStsNoErr;
        }

        /*
      // Lehmer's algorithm requires that first number must be greater than second
      // x is the first, y is the second
      */
        {
            int cmpRes = cpCmp_BNU(BN_NUMBER(x), BN_SIZE(x), BN_NUMBER(y), BN_SIZE(y));
            if (0 > cmpRes)
                SWAP_PTR(IppsBigNumState, x, y);
            if (0 == cmpRes) {
                COPY_BNU(BN_NUMBER(g), BN_NUMBER(x), BN_SIZE(x));
                BN_SIGN(g) = ippBigNumPOS;
                BN_SIZE(g) = BN_SIZE(x);
                return ippStsNoErr;
            }
            if (BN_SIZE(x) == 1) {
                BNU_CHUNK_T gcd = cpGcd_BNU(BN_NUMBER(x)[0], BN_NUMBER(y)[0]);
                BN_NUMBER(g)[0] = gcd;
                BN_SIZE(g)      = 1;
                return ippStsNoErr;
            }
        }

        {
            Ipp32u* xBuffer = (Ipp32u*)BN_BUFFER(x);
            Ipp32u* yBuffer = (Ipp32u*)BN_BUFFER(y);
            Ipp32u* gBuffer = (Ipp32u*)BN_BUFFER(g);
            Ipp32u* xData   = (Ipp32u*)BN_NUMBER(x);
            Ipp32u* yData   = (Ipp32u*)BN_NUMBER(y);
            Ipp32u* gData   = (Ipp32u*)BN_NUMBER(g);
            cpSize nsXmax   = BN_ROOM(x) * ((Ipp32s)(sizeof(BNU_CHUNK_T) / sizeof(Ipp32u)));
            cpSize nsYmax   = BN_ROOM(y) * ((Ipp32s)(sizeof(BNU_CHUNK_T) / sizeof(Ipp32u)));
            cpSize nsGmax   = BN_ROOM(g) * ((Ipp32s)(sizeof(BNU_CHUNK_T) / sizeof(Ipp32u)));
            cpSize nsX      = BN_SIZE(x) * ((Ipp32s)(sizeof(BNU_CHUNK_T) / sizeof(Ipp32u)));
            cpSize nsY      = BN_SIZE(y) * ((Ipp32s)(sizeof(BNU_CHUNK_T) / sizeof(Ipp32u)));

            Ipp32u* T;
            Ipp32u* u;

            FIX_BNU32(xData, nsX);
            FIX_BNU32(yData, nsY);

            /* init buffers */
            ZEXPAND_COPY_BNU(xBuffer, nsXmax, xData, nsX);
            ZEXPAND_COPY_BNU(yBuffer, nsYmax, yData, nsY);

            T = gBuffer;
            u = gData;
            ZEXPAND_BNU(T, 0, nsGmax);
            ZEXPAND_BNU(u, 0, nsGmax);

            while (nsX > (cpSize)(sizeof(BNU_CHUNK_T) / sizeof(Ipp32u))) {
                /* xx and yy is the high-order digits of x and y (yy could be 0) */

                Ipp64u xx = (Ipp64u)(xBuffer[nsX - 1]);
                Ipp64u yy = (nsY < nsX) ? 0 : (Ipp64u)(yBuffer[nsY - 1]);

                Ipp64s AA = 1;
                Ipp64s BB = 0;
                Ipp64s CC = 0;
                Ipp64s DD = 1;
                Ipp64s t;

                while ((yy + (Ipp64u)CC) != 0 && (yy + (Ipp64u)DD) != 0) {
                    Ipp64u q  = (xx + (Ipp64u)AA) / (yy + (Ipp64u)CC);
                    Ipp64u q1 = (xx + (Ipp64u)BB) / (yy + (Ipp64u)DD);
                    if (q != q1)
                        break;
                    t  = AA - (Ipp64s)q * CC;
                    AA = CC;
                    CC = t;
                    t  = BB - (Ipp64s)q * DD;
                    BB = DD;
                    DD = t;
                    t  = (Ipp64s)(xx - q * yy);
                    xx = yy;
                    yy = (Ipp64u)t;
                }

                if (BB == 0) {
                    /* T = x mod y */
                    cpSize nsT = cpMod_BNU32(xBuffer, nsX, yBuffer, nsY);
                    ZEXPAND_BNU(T, 0, nsGmax);
                    COPY_BNU(T, xBuffer, nsT);
                    /* a = b; b = T; */
                    ZEXPAND_BNU(xBuffer, 0, nsXmax);
                    COPY_BNU(xBuffer, yBuffer, nsY);
                    ZEXPAND_BNU(yBuffer, 0, nsYmax);
                    COPY_BNU(yBuffer, T, nsY);
                }

                else {
                    Ipp32u carry;
                    /*
               // T = AA*x + BB*y;
               // u = CC*x + DD*y;
               // b = u; a = T;
               */
                    if ((AA <= 0) && (BB >= 0)) {
                        Ipp32u a1 = (Ipp32u)(-AA);
                        carry     = cpMulDgt_BNU32(T, yBuffer, nsY, (Ipp32u)BB);
                        carry     = cpMulDgt_BNU32(u, xBuffer, nsY, a1);
                        /* T = BB*y - AA*x; */
                        carry = cpSub_BNU32(T, T, u, nsY);
                    } else {
                        if ((AA >= 0) && (BB <= 0)) {
                            Ipp32u b1 = (Ipp32u)(-BB);
                            carry     = cpMulDgt_BNU32(T, xBuffer, nsY, (Ipp32u)AA);
                            carry     = cpMulDgt_BNU32(u, yBuffer, nsY, b1);
                            /* T = AA*x - BB*y; */
                            carry = cpSub_BNU32(T, T, u, nsY);
                        } else {
                            /*AA*BB>=0 */
                            carry = cpMulDgt_BNU32(T, xBuffer, nsY, (Ipp32u)AA);
                            carry = cpMulDgt_BNU32(u, yBuffer, nsY, (Ipp32u)BB);
                            /* T = AA*x + BB*y; */
                            carry = cpAdd_BNU32(T, T, u, nsY);
                        }
                    }

                    /* Now T is reserved. We use only u for intermediate results. */
                    if ((CC <= 0) && (DD >= 0)) {
                        Ipp32u c1 = (Ipp32u)(-CC);
                        /* u = x*CC; x = u; */
                        carry = cpMulDgt_BNU32(u, xBuffer, nsY, c1);
                        COPY_BNU(xBuffer, u, nsY);
                        /* u = y*DD; */
                        carry = cpMulDgt_BNU32(u, yBuffer, nsY, (Ipp32u)DD);
                        /* u = DD*y - CC*x; */
                        carry = cpSub_BNU32(u, u, xBuffer, nsY);
                    } else {
                        if ((CC >= 0) && (DD <= 0)) {
                            Ipp32u d1 = (Ipp32u)(-DD);
                            /* u = y*DD; y = u */
                            carry = cpMulDgt_BNU32(u, yBuffer, nsY, d1);
                            COPY_BNU(yBuffer, u, nsY);
                            /* u = CC*x; */
                            carry = cpMulDgt_BNU32(u, xBuffer, nsY, (Ipp32u)CC);
                            /* u = CC*x - DD*y; */
                            carry = cpSub_BNU32(u, u, yBuffer, nsY);
                        } else {
                            /*CC*DD>=0 */
                            /* y = y*DD */
                            carry = cpMulDgt_BNU32(u, yBuffer, nsY, (Ipp32u)DD);
                            COPY_BNU(yBuffer, u, nsY);
                            /* u = x*CC */
                            carry = cpMulDgt_BNU32(u, xBuffer, nsY, (Ipp32u)CC);
                            /* u = x*CC + y*DD */
                            carry = cpAdd_BNU32(u, u, yBuffer, nsY);
                        }
                    }

                    IPP_UNREFERENCED_PARAMETER(carry);

                    /* y = u; x = T; */
                    COPY_BNU(yBuffer, u, nsY);
                    COPY_BNU(xBuffer, T, nsY);
                }

                FIX_BNU32(xBuffer, nsX);
                FIX_BNU32(yBuffer, nsY);

                if (nsY > nsX) {
                    SWAP_PTR(IppsBigNumState, x, y);
                    SWAP(nsX, nsY);
                }

                if (nsY == 1 && yBuffer[nsY - 1] == 0) {
                    /* End evaluation */
                    ZEXPAND_BNU(gData, 0, nsGmax);
                    COPY_BNU(gData, xBuffer, nsX);
                    BN_SIZE(g) = INTERNAL_BNU_LENGTH(nsX);
                    BN_SIGN(g) = ippBigNumPOS;
                    return ippStsNoErr;
                }
            }

            BN_NUMBER(g)[0] = cpGcd_BNU(((BNU_CHUNK_T*)xBuffer)[0], ((BNU_CHUNK_T*)yBuffer)[0]);
            BN_SIZE(g)      = 1;
            BN_SIGN(g)      = ippBigNumPOS;
            return ippStsNoErr;
        }
    }
}
