
package uk.co.wingpath.registration;

import java.math.BigInteger;

class Crypt
{
    static final char CHAR_VERSION_A = 'a';
    static final char CHAR_VERSION_B = 'b';
    static final char CHAR_VERSION_C = 'c';
    static final char CHAR_VERSION_D = 'd';
    static final char CHAR_VERSION_E = 'e';
    static final char CHAR_VERSION_F = 'f';
    static final char CHAR_VERSION_CURRENT = 'f';

    static final char CHAR_KEY = 'a';
    static final char CHAR_PRODUCTID = 'b';
    static final char CHAR_LICENCE = 'c';
    static final char CHAR_TRANSFER = 'd';

    // The following constants (MAX_REGVERSION..checkMod) are used in
    // constructing registration keys and product IDs. If you change any of
    // these values you must make corresponding changes to the web-site program
    // /www/website/web/src/nregkey.c, and any such change will probably
    // make the web-site inconsistent with programs that have already been
    // downloaded - so don't do it without an awful lot of thought!

    static final int MAX_REGVERSION = 2;
    static final BigInteger maxRegVersion = BigInteger.valueOf (MAX_REGVERSION);
    static final int MAX_OLDKEY = 2;
    static final BigInteger maxOldKey = BigInteger.valueOf (MAX_OLDKEY);
    static final int MAX_VENDOR = 10;
    static final BigInteger maxVendor = BigInteger.valueOf (MAX_VENDOR);
    static final int MAX_OS = 20;
    static final BigInteger maxOS = BigInteger.valueOf (MAX_OS);
    static final int MAX_JAVAVERSION = 10;
    static final BigInteger maxJavaVersion =
        BigInteger.valueOf (MAX_JAVAVERSION);
    static final int OLD_MAX_LOGID = 1000;
    static final BigInteger oldMaxLogId = BigInteger.valueOf (OLD_MAX_LOGID);
    static final int MAX_LOGID = 100000;
    static final BigInteger maxLogId = BigInteger.valueOf (MAX_LOGID);
    static final int MAX_HASH = 0x10000;
    static final BigInteger maxHash = BigInteger.valueOf (MAX_HASH);
    static final int MAX_DATE = 20000;
    static final int MAX_EXPIRY = 100;
    static final BigInteger maxDate = BigInteger.valueOf (MAX_DATE);
    static final int DATE_BASE = 19000;
    static final int MAX_EVAL_PERIOD = 50;
    static final BigInteger maxEvalPeriod =
        BigInteger.valueOf (MAX_EVAL_PERIOD);
    static final int MAX_PRODUCT = 50;
    static final BigInteger maxProduct = BigInteger.valueOf (MAX_PRODUCT);
    static final int MAX_MAJOR_VERSION = 10;
    static final BigInteger maxMajorVersion =
        BigInteger.valueOf (MAX_MAJOR_VERSION);
    static final int MAX_MINOR_VERSION = 100;
    static final BigInteger maxMinorVersion =
        BigInteger.valueOf (MAX_MINOR_VERSION);
    static final int MAX_MANUAL = 2;
    static final BigInteger maxManual = BigInteger.valueOf (MAX_MANUAL);
    static final int MAX_UPGRADE = 2;
    static final BigInteger maxUpgrade = BigInteger.valueOf (MAX_UPGRADE);
    static final int MAX_MODTIME = 67;
    static final BigInteger maxModTime = BigInteger.valueOf (MAX_MODTIME);
    static final int MAX_RAND = 101;
    static final BigInteger maxRand = BigInteger.valueOf (MAX_RAND);
    static final int MAX_STATUS = 10;
    static final BigInteger maxStatus = BigInteger.valueOf (MAX_STATUS);
    static final int MAX_64BIT = 3;
    static final BigInteger max64Bit = BigInteger.valueOf (MAX_64BIT);
    static final int MAX_RANDOM = 100;
    static final BigInteger maxRandom = BigInteger.valueOf (MAX_RANDOM);
    static final int MAX_HASSERIAL = 3;
    static final BigInteger maxHasSerial = BigInteger.valueOf (MAX_HASSERIAL);
    static final int MAX_LICENCE_LEVEL = 100;
    static final BigInteger maxLicenceLevel =
        BigInteger.valueOf (MAX_LICENCE_LEVEL);

    static final BigInteger oldCryptMult = new BigInteger ("13891176665706064842");
    static final int OLD_CRYPT_MOD_N = 64;
    static final int OLD_CRYPT_MOD_K = 59;
    static final BigInteger oldCryptMod =
        makePrime (OLD_CRYPT_MOD_N, OLD_CRYPT_MOD_K);
    static final BigInteger oldCryptMultInv =
        oldCryptMult.modInverse (oldCryptMod);

    static final BigInteger cryptMultA =
        new BigInteger ("73268029776996600788346432152");
    static final int CRYPT_MOD_N_A = 96;
    static final int CRYPT_MOD_K_A = 17;
    static final BigInteger cryptModA =
        makePrime (CRYPT_MOD_N_A, CRYPT_MOD_K_A);
    static final BigInteger cryptMultInvA = cryptMultA.modInverse (cryptModA);

    static final BigInteger cryptMultC =
        new BigInteger ("243267374564284687042667403923350539132");
    static final int CRYPT_MOD_N_C = 128;
    static final int CRYPT_MOD_K_C = 159;
    static final BigInteger cryptModC =
        makePrime (CRYPT_MOD_N_C, CRYPT_MOD_K_C);
    static final BigInteger cryptMultInvC = cryptMultC.modInverse (cryptModC);

    static final BigInteger checkMod = BigInteger.valueOf (991);

    // "Magic" numbers - these are simply small constant numbers inserted in
    // the keys as an extra recognition check.
    static final int MAX_MAGIC = 10;
    static final int MAGIC_KEY = 3;
    static final int MAGIC_PRODUCTID = 4;
    static final int MAGIC_LICENCE = 5;
    static final int MAGIC_TRANSFER = 6;

    // FULL_LICENCE is used in the expiry field to indicate full registration.
    // OLD_LICENCE is used in the expiry field to indicate that a full
    // registration has been de-registered.
    // NEW_LICENCE is used in the expiry field to indicate that no registration
    // key has yet been entered.
    // EXPIRED_LICENCE is used in the expiry field to indicate that an
    // evaluation licence has expired. A special value is used, instead of
    // simply an expiry date in the past, to ensure that the user can't get
    // the program to run by putting back the system date.
    // EXPIREDF_LICENCE indicates that an evaluation has expired according
    // to the "funny file" timing, rather than the expiry date in the key.
    // CORRUPT_LICENCE indicates that the registration files have been
    // corrupted.
    // Any other value in the expiry field is the expiry date of the
    // evaluation period in days since 1 Jan 1970. In key versions 'f' and
    // later, 19000 is subtracted from the expiry date to avoid overflow.
    // Warnings as above about modification also apply to these values.

    public static final int NEW_LICENCE = 26;
    public static final int FULL_LICENCE = 31;
    public static final int CORRUPT_LICENCE = 35;
    public static final int OLD_LICENCE = 37;
    public static final int EXPIRED_LICENCE = 39;
    public static final int EXPIREDF_LICENCE = 40;

    private static BigInteger makePrime (int n, int k)
    {
        BigInteger bk = BigInteger.valueOf (k);
        BigInteger b2 = BigInteger.valueOf (2);
        return b2.pow (n).subtract (bk);
    }

    static BigInteger oldDecrypt (String str)
    {
        try
        {
            BigInteger n = new BigInteger (str);
            n = n.multiply (oldCryptMultInv);
            n = n.mod (oldCryptMod);
            n = n.divide (maxRand);
            return n;
        }
        catch (NumberFormatException e)
        {
            return BigInteger.ZERO;
        }
    }

    static BigInteger addCheck (BigInteger n)
    {
        BigInteger c = n.mod (checkMod);
        n = n.multiply (checkMod);
        n = n.add (c);
        return n;
    }

    static BigInteger removeCheck (BigInteger n)
    {
        BigInteger c1 = n.mod (checkMod);
        n = n.divide (checkMod);
        BigInteger c2 = n.mod (checkMod);
        if (!c1.equals (c2))
            return null;
        return n;
    }

    static String encrypt (BigInteger n, char version)
    {
        if (version >= CHAR_VERSION_C)
        {
            n = n.multiply (cryptMultC);
            n = n.mod (cryptModC);
        }
        else
        {
            n = n.multiply (cryptMultA);
            n = n.mod (cryptModA);
        }
        n = addCheck (n);
        return n.toString (16);
    }

    /**
    * Decrypts a number encoded as a hex string.
    * @param str the string to be decoded.
    * @return the decrypted number, or {@code null} if the string is invalid.
    */
    static BigInteger decrypt (String str, char version)
    {
        if (version < 'a' || version > CHAR_VERSION_CURRENT)
            return null;
        try
        {
            BigInteger n = new BigInteger (str, 16);
            n = removeCheck (n);
            if (n == null)
                return null;
            if (version >= CHAR_VERSION_C)
            {
                n = n.multiply (cryptMultInvC);
                n = n.mod (cryptModC);
            }
            else
            {
                n = n.multiply (cryptMultInvA);
                n = n.mod (cryptModA);
            }
            return n;
        }
        catch (NumberFormatException e)
        {
            return null;
        }
    }

    static int hash (String str)
    {
        int code = 0;
        str = str.toLowerCase ();

        for (int i = 0 ; i < str.length () ; i++)
        {
            int c = str.charAt (i);
            if (c >= 'a' && c <= 'z')
            {
                code ^= c;

                for (int j = 0 ; j < 8 ; j++)
                {
                    if ((code & 1) != 0)
                    {
                        code >>= 1;
                        code ^= 0xa001;
                    }
                    else
                    {
                        code >>= 1;
                    }
                }
            }
        }

        return code % MAX_HASH;
    }
}

