// Example use of the Dinkey Pro/FD API in Java.
// Copyright 2018 Microcosm Ltd.

import uk.microcosm.dinkeydongle.DinkeyPro;
import java.util.Arrays;                        // For Arrays.toString()

class DPSample
{
    static final String MY_PRODCODE = "DEMO";   // !!!! Replace "DEMO" with the product code that you specify when adding protection with DinkeyAdd.
    static final int MY_SDSN = 10101;           // !!!! "10101" is the SDSN used by the demo SDK and demo dongles.
                                                // If not using a demo, replace "10101" with your own SDSN here.

    static final int DRIS_ENCRYPTION_PARAMETER_1 = 123; // !!!! If you enable DRIS encryption when adding protection with DinkeyAdd,
    static final int DRIS_ENCRYPTION_PARAMETER_2 = 212; // replace these values with the encryption parameters that you specify.
    static final int DRIS_ENCRYPTION_PARAMETER_3 = 97;

    public static void main(String[] args)
    {
        // Each of the methods below demonstrates different API functionality.
        // Uncomment the appropriate methods to execute the examples.
        // See the comments in each method for more details about the features that they demonstrate.

        protCheck();
        // protCheckWithAlg();
        // writeDataArea();
        // readDataArea();
        // encryptUserData();
        // protCheckEnc();
        // writeDataAreaEnc();
        // readDataAreaEnc();
        // encryptUserDataEnc();
        // displayNetUsers();
    }

    // An example of a basic protection check
    static void protCheck()
    {
        int retCode;

        // Create a DinkeyPro instance that uses the default library name, DPJava.
        // All DRIS fields are initialised to random values.
        DinkeyPro ddpro = new DinkeyPro();

        // You can use an alternative library name like this:
        // DinkeyPro ddpro = new DinkeyPro("MyName");
        // The manner in which the library name is mapped to the actual system library is system dependent,
        // but common behaviour is that Java will attempt to locate and load MyName.dll on Windows, libMyName.dylib on macOS,
        // or libMyName.so on Linux.

        // You can also specify the full path to the library, if it is not located in one of the paths specified by java.library.path,
        // or uses a non-standard filename:
        // Dinkey ddpro = new DinkeyPro("~/Unusual Folder For Libraries/nonstandardname.jnilib");

        // Initialise the DRIS
        ddpro.function = DinkeyPro.PROTECTION_CHECK;
        ddpro.flags = DinkeyPro.DEC_ONE_EXEC | DinkeyPro.START_NET_USER;

        // Call the API
        retCode = ddpro.checkProtection();

        // Check the return value
        if (retCode != 0)
        {
            displayError(retCode, ddpro.ext_err);
            return;
        }

        // ...

        // You can check other values from the DRIS in other parts of your program to improve security.
        // For example:

        // Check the SDSN
        if (ddpro.sdsn != MY_SDSN)
        {
            System.out.println("Incorrect SDSN! Have you replaced '10101' with your SDSN in the sample code?");
            return;
        }

        // Check the product code
        if (!(ddpro.getProdCode().equals(MY_PRODCODE)))
        {
            System.out.println("Incorrect Product Code! Please modify your source code so that MY_PRODCODE is set to be the Product Code in the dongle.");
            return;
        }

        System.out.println("Protection check successful.");
        return;
    }

    // An example of a protection check that also executes an algorithm stored in the dongle.
    // The best way to use this feature is to identify lines of code in your program
    // that can be represented by algorithms stored in the dongle,
    // and replace these lines with calls to the API.
    // Lite dongle users cannot change the algorithm stored in the dongles,
    // so should calculate the algorithm in their own code and compare the result with the one returned by the API,
    // as in this example.
    static void protCheckWithAlg()
    {
        int retCode;
        DinkeyPro ddpro = new DinkeyPro();

        // Initialise the DRIS
        ddpro.function = DinkeyPro.EXECUTE_ALGORITHM;
        ddpro.flags = 0;
        ddpro.alg_number = 1;   // The algorithm to execute. Lite dongles ignore this field, as they contain only 1 algorithm
        ddpro.var_a = 1;        // The input variables to use in the algorithm
        ddpro.var_b = 2;
        ddpro.var_c = 3;
        ddpro.var_d = 4;
        ddpro.var_e = 5;
        ddpro.var_f = 6;
        ddpro.var_g = 7;
        ddpro.var_h = 8;

        // Call the API
        retCode = ddpro.checkProtection();

        // Check the return value
        if (retCode != 0)
        {
            displayError(retCode, ddpro.ext_err);
            return;
        }

        // ...

        // Check the algorithm result in another part of your program to improve security.
        // !!!! Ensure the myAlgorithm() method matches the algorithm that was executed by the protection check!
        if (myAlgorithm(ddpro.var_a, ddpro.var_b, ddpro.var_c, ddpro.var_d, ddpro.var_e, ddpro.var_f, ddpro.var_g, ddpro.var_h) != ddpro.alg_answer)
        {
            System.out.println("Error! The algorithm result was not as expected.");
            return;
        }

        System.out.println("Protection check with algorithm successful.");
        return;
    }

    // An example of a protection check that also writes data to the dongle's secure data area.
    // This feature is not supported by Lite dongles.
    // Make sure you specify a large enough data area size when adding protection with DinkeyAdd!
    static void writeDataArea()
    {
        int retCode;
        DinkeyPro ddpro = new DinkeyPro();
        byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};   // Data to write

        // Initialise the DRIS
        ddpro.function = DinkeyPro.WRITE_DATA_AREA;
        ddpro.flags = DinkeyPro.USE_FUNCTION_ARGUMENT;  // Java uses DDProtCheck()'s data parameter, so this flag must be set
        ddpro.rw_offset = 0;                            // Index to start writing at. Indices start at zero
        ddpro.rw_length = data.length;                  // The number of bytes to be written

        // Call the API
        retCode = ddpro.checkProtection(data);

        // Check the return value
        if (retCode != 0)
        {
            displayError(retCode, ddpro.ext_err);
            return;
        }

        // You can inspect the dongle with DinkeyLook to see that the data has been written to the dongle

        System.out.println("Writing data successful.");
        return;
    }

    // An example of a protection check that also reads data from the dongle's secure data area.
    // This feature is not supported by Lite dongles.
    // Make sure you specify a large enough data area size when adding protection with DinkeyAdd!
    static void readDataArea()
    {
        int retCode;
        DinkeyPro ddpro = new DinkeyPro();
        byte[] data = new byte[10];

        // Initialise the DRIS
        ddpro.function = DinkeyPro.READ_DATA_AREA;
        ddpro.flags = DinkeyPro.USE_FUNCTION_ARGUMENT;  // Java uses DDProtCheck()'s data parameter, so this flag must be set
        ddpro.rw_offset = 0;                            // Index to start reading from. Indices start at zero
        ddpro.rw_length = data.length;                  // The number of bytes to read

        // Call the API
        retCode = ddpro.checkProtection(data);

        // Check the return value
        if (retCode != 0)
        {
            displayError(retCode, ddpro.ext_err);
            return;
        }

        System.out.println("Reading data successful: " + Arrays.toString(data));
        return;
    }

    // An example of protection checks that encrypt/decrypt your data.
    // This feature is not supported by Lite dongles.
    static void encryptUserData()
    {
        int retCode;
        DinkeyPro ddpro = new DinkeyPro();
        byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};   // The data to be encrypted
        byte[] originalData = data.clone();             // A copy of the data for displaying later
        byte[] encryptedData;

        // Initialise the DRIS
        ddpro.function = DinkeyPro.ENCRYPT_USER_DATA;
        ddpro.flags = DinkeyPro.USE_FUNCTION_ARGUMENT;  // Java uses DDProtCheck()'s data parameter, so this flag must be set
        ddpro.rw_length = data.length;                  // The number of bytes to be encrypted
        ddpro.data_crypt_key_num = 1;                   // The encryption key to use

        // Call the API
        retCode = ddpro.checkProtection(data);

        // Check the return value
        if (retCode != 0)
        {
            displayError(retCode, ddpro.ext_err);
            return;
        }

        encryptedData = data.clone();                   // A copy of the encrypted data for displaying later

        // Initialise the DRIS
        ddpro.reset();                                  // Reset the DRIS fields with random values
        ddpro.function = DinkeyPro.DECRYPT_USER_DATA;
        ddpro.flags = DinkeyPro.USE_FUNCTION_ARGUMENT;  // Java uses DDProtCheck()'s data parameter, so this flag must be set
        ddpro.rw_length = data.length;
        ddpro.data_crypt_key_num = 1;                   // This must be the same key used to encrypt the data

        // Call the API
        retCode = ddpro.checkProtection(data);

        // Check the return value
        if (retCode != 0)
        {
            displayError(retCode, ddpro.ext_err);
            return;
        }

        System.out.println("Data encryption successful.");
        System.out.println("Original data: " + Arrays.toString(originalData));
        System.out.println("Encrypted data: " + Arrays.toString(encryptedData));
        System.out.println("Decrypted data: " + Arrays.toString(data));
        return;
    }

    // An example of a protection check that uses DRIS encryption for extra security.
    static void protCheckEnc()
    {
        int retCode;
        DinkeyPro ddpro = new DinkeyPro();

        // Initialise the DRIS
        ddpro.function = DinkeyPro.PROTECTION_CHECK;
        ddpro.flags = 0;

        // Encrypt the DRIS
        ddpro.cryptDris(DRIS_ENCRYPTION_PARAMETER_1, DRIS_ENCRYPTION_PARAMETER_2, DRIS_ENCRYPTION_PARAMETER_3);

        // Call the API
        retCode = ddpro.checkProtection();

        // Decrypt the DRIS
        ddpro.cryptDris(DRIS_ENCRYPTION_PARAMETER_1, DRIS_ENCRYPTION_PARAMETER_2, DRIS_ENCRYPTION_PARAMETER_3);

        // Check the return value
        if (retCode != 0)
        {
            displayError(retCode, ddpro.ext_err);
            return;
        }

        // ...

        // You can check other values from the DRIS in other parts of your program to improve security.
        // For example:

        // Check the SDSN
        if (ddpro.sdsn != MY_SDSN)
        {
            System.out.println("Incorrect SDSN! Have you replaced '10101' with your SDSN in the sample code?");
            return;
        }

        // Check the product code
        if (!(ddpro.getProdCode().equals(MY_PRODCODE)))
        {
            System.out.println("Incorrect Product Code! Please modify your source code so that MY_PRODCODE is set to be the Product Code in the dongle.");
            return;
        }

        System.out.println("Protection check successful.");
        return;
    }

    // An example of a protection check that also writes data to the dongle's secure data area.
    // This feature is not supported by Lite dongles.
    // Make sure you specify a large enough data area size when adding protection with DinkeyAdd!
    // This method also uses DRIS encryption and data encryption for extra security.
    // You must select these options on the Extra Security tab of DinkeyAdd for this method to work correctly!
    static void writeDataAreaEnc()
    {
        int retCode, algAnswer;
        DinkeyPro ddpro = new DinkeyPro();
        byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};   // The data to be written

        // Initialise the DRIS
        ddpro.function = DinkeyPro.WRITE_DATA_AREA;
        ddpro.flags = DinkeyPro.USE_FUNCTION_ARGUMENT;  // Java uses DDProtCheck()'s data parameter, so this flag must be set
        ddpro.rw_offset = 0;                            // Index to start writing at. Indices start at zero
        ddpro.rw_length = data.length;                  // The number of bytes to write

        // If taking data encryption parameters from the R/W algorithm:
        // Calculate the R/W algorithm result
        // The input variables were initialised to random values when ddpro was created
        algAnswer = myRwAlgorithm(ddpro.var_a, ddpro.var_b, ddpro.var_c, ddpro.var_d, ddpro.var_e, ddpro.var_f, ddpro.var_g, ddpro.var_h);

        // Use the algorithm result to encrypt the data to be written to the dongle
        ddpro.cryptApiData(data, algAnswer);

        // If using constant data encryption parameters:
        // ddpro.cryptApiData(data, DATA_ENCRYPTION_PARAMETER_1, DATA_ENCRYPTION_PARAMETER_2, DATA_ENCRYPTION_PARAMETER_3);

        // Encrypt the DRIS
        ddpro.cryptDris(DRIS_ENCRYPTION_PARAMETER_1, DRIS_ENCRYPTION_PARAMETER_2, DRIS_ENCRYPTION_PARAMETER_3);

        // Call the API
        retCode = ddpro.checkProtection(data);

        // Decrypt the DRIS
        ddpro.cryptDris(DRIS_ENCRYPTION_PARAMETER_1, DRIS_ENCRYPTION_PARAMETER_2, DRIS_ENCRYPTION_PARAMETER_3);

        // Check the return value
        if (retCode != 0)
        {
            displayError(retCode, ddpro.ext_err);
            return;
        }

        System.out.println("Writing data successful.");
        return;
    }

    // An example of a protection check that also reads data from the dongle's secure data area.
    // This feature is not supported by Lite dongles.
    // Make sure you specify a large enough data area size when adding protection with DinkeyAdd!
    // This method also uses DRIS encryption and data encryption for extra security.
    // You must select these options on the Extra Security tab of DinkeyAdd for this method to work correctly!
    static void readDataAreaEnc()
    {
        int retCode, algAnswer;
        DinkeyPro ddpro = new DinkeyPro();
        byte[] data = new byte[10];

        // Initialise the DRIS
        ddpro.function = DinkeyPro.READ_DATA_AREA;
        ddpro.flags = DinkeyPro.USE_FUNCTION_ARGUMENT;  // Java uses DDProtCheck()'s data parameter, so this flag must be set
        ddpro.rw_offset = 0;                            // Index to start reading from. Indices start at zero
        ddpro.rw_length = data.length;                  // The number of bytes to read

        // Encrypt the DRIS
        ddpro.cryptDris(DRIS_ENCRYPTION_PARAMETER_1, DRIS_ENCRYPTION_PARAMETER_2, DRIS_ENCRYPTION_PARAMETER_3);

        // Call the API
        retCode = ddpro.checkProtection(data);

        // Decrypt the DRIS
        ddpro.cryptDris(DRIS_ENCRYPTION_PARAMETER_1, DRIS_ENCRYPTION_PARAMETER_2, DRIS_ENCRYPTION_PARAMETER_3);

        // Check the return value
        if (retCode != 0)
        {
            displayError(retCode, ddpro.ext_err);
            return;
        }

        // If taking data encryption parameters from the R/W algorithm:
        // Calculate the R/W algorithm result
        // The input variables were initialised to random values when ddpro was created
        algAnswer = myRwAlgorithm(ddpro.var_a, ddpro.var_b, ddpro.var_c, ddpro.var_d, ddpro.var_e, ddpro.var_f, ddpro.var_g, ddpro.var_h);

        // Use the algorithm result to decrypt the data read from the dongle
        ddpro.cryptApiData(data, algAnswer);

        // If using constant data encryption parameters:
        // ddpro.cryptApiData(data, DATA_ENCRYPTION_PARAMETER_1, DATA_ENCRYPTION_PARAMETER_2, DATA_ENCRYPTION_PARAMETER_3);

        System.out.println("Reading data successful: " + Arrays.toString(data));
        return;
    }

    // An example of protection checks that encrypt/decrypt your data.
    // This feature is not supported by Lite dongles.
    // This method also uses DRIS encryption and data encryption for extra security.
    // You must select these options on the Extra Security tab of DinkeyAdd for this method to work correctly!
    static void encryptUserDataEnc()
    {
        int retCode, algAnswer;
        DinkeyPro ddpro = new DinkeyPro();
        byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};   // The data to be encrypted
        byte[] originalData = data.clone();             // A copy of the data for displaying later
        byte[] encryptedData;

        // Initialise the DRIS
        ddpro.function = DinkeyPro.ENCRYPT_USER_DATA;
        ddpro.flags = DinkeyPro.USE_FUNCTION_ARGUMENT;  // Java uses DDProtCheck()'s data parameter, so this flag must be set
        ddpro.rw_length = data.length;                  // The number of bytes to be encrypted
        ddpro.data_crypt_key_num = 1;                   // The encryption key to use

        // If taking data encryption parameters from the R/W algorithm:
        // Calculate the R/W algorithm result
        // The input variables were initialised to random values when ddpro was created
        algAnswer = myRwAlgorithm(ddpro.var_a, ddpro.var_b, ddpro.var_c, ddpro.var_d, ddpro.var_e, ddpro.var_f, ddpro.var_g, ddpro.var_h);

        // Use the algorithm result to encrypt the data passed to the API
        ddpro.cryptApiData(data, algAnswer);

        // If using constant data encryption parameters:
        // ddpro.cryptApiData(data, DATA_ENCRYPTION_PARAMETER_1, DATA_ENCRYPTION_PARAMETER_2, DATA_ENCRYPTION_PARAMETER_3);

        // Encrypt the DRIS
        ddpro.cryptDris(DRIS_ENCRYPTION_PARAMETER_1, DRIS_ENCRYPTION_PARAMETER_2, DRIS_ENCRYPTION_PARAMETER_3);

        // Call the API
        retCode = ddpro.checkProtection(data);

        // Decrypt the DRIS
        ddpro.cryptDris(DRIS_ENCRYPTION_PARAMETER_1, DRIS_ENCRYPTION_PARAMETER_2, DRIS_ENCRYPTION_PARAMETER_3);

        // Do not call cryptApiData() here to decrypt the data received from the API!
        // The data has been encrypted by the dongle hardware, so the API will not apply an unnecessary second layer of encryption

        // Check the return value
        if (retCode != 0)
        {
            displayError(retCode, ddpro.ext_err);
            return;
        }

        encryptedData = data.clone();                   // A copy of the encrypted data for displaying later

        // Initialise the DRIS
        ddpro.reset();                                  // Reset the DRIS fields with random values
        ddpro.function = DinkeyPro.DECRYPT_USER_DATA;
        ddpro.flags = DinkeyPro.USE_FUNCTION_ARGUMENT;  // Java uses DDProtCheck()'s data parameter, so this flag must be set
        ddpro.rw_length = data.length;
        ddpro.data_crypt_key_num = 1;                   // This must be the same key used to encrypt the data

        // Encrypt the DRIS
        ddpro.cryptDris(DRIS_ENCRYPTION_PARAMETER_1, DRIS_ENCRYPTION_PARAMETER_2, DRIS_ENCRYPTION_PARAMETER_3);

        // Do not call cryptApiData() here to encrypt the data passed to the API!
        // The data is already encrypted, so the API does not expect an unnecessary second layer of encryption

        // Call the API
        retCode = ddpro.checkProtection(data);

        // Decrypt the DRIS
        ddpro.cryptDris(DRIS_ENCRYPTION_PARAMETER_1, DRIS_ENCRYPTION_PARAMETER_2, DRIS_ENCRYPTION_PARAMETER_3);

        // Check the return value
        if (retCode != 0)
        {
            displayError(retCode, ddpro.ext_err);
            return;
        }

        // If taking data encryption parameters from the R/W algorithm:
        // Calculate the R/W algorithm result
        // The input variables were initialised to random values when ddpro was created
        algAnswer = myRwAlgorithm(ddpro.var_a, ddpro.var_b, ddpro.var_c, ddpro.var_d, ddpro.var_e, ddpro.var_f, ddpro.var_g, ddpro.var_h);

        // Use the algorithm result to decrypt the data received from the API
        ddpro.cryptApiData(data, algAnswer);

        // If using constant data encryption parameters:
        // ddpro.cryptApiData(data, DATA_ENCRYPTION_PARAMETER_1, DATA_ENCRYPTION_PARAMETER_2, DATA_ENCRYPTION_PARAMETER_3);

        System.out.println("Data encryption successful.");
        System.out.println("Original data: " + Arrays.toString(originalData));
        System.out.println("Encrypted data: " + Arrays.toString(encryptedData));
        System.out.println("Decrypted data: " + Arrays.toString(data));
        return;
    }

    // An example of a simple protection check and displaying information about the current users of a network dongle.
    // This feature is supported by Dinkey Pro Net and Dinkey FD Net dongles only.
    // queryNetUsers() will only work following a call to checkProtection() that established a connection to DinkeyServer.
    static void displayNetUsers()
    {
        int retCode, maxNetUsersToQuery;

        DinkeyPro ddpro = new DinkeyPro();  // Create a DinkeyPro instance. All DRIS fields are initialised to random values

        // Initialise the DRIS
        ddpro.function = DinkeyPro.PROTECTION_CHECK;
        ddpro.flags = DinkeyPro.START_NET_USER;

        // Call the API
        retCode = ddpro.checkProtection();

        // Check the return value
        if (retCode != 0)
        {
            displayError(retCode, ddpro.ext_err);
            return;
        }

        // If you lock your software to multiple dongle models,
        // you might want to check that a network dongle was found before trying to get network user information
        if ((ddpro.model == DinkeyPro.MODEL_LITE) || (ddpro.model == DinkeyPro.MODEL_PLUS))
        {
            System.out.println("Local dongle found. No network user information to display.");
            return;
        }

        // Set maxNetUsersToQuery to a sensible value!
        // This example requests information about 10 users if there is no limit to the number of users supported by the dongle
        if (ddpro.net_users == DinkeyPro.UNLIMITED_NET_USERS)
            maxNetUsersToQuery = 10;
        else
            maxNetUsersToQuery = ddpro.net_users;

        // Call the API
        retCode = ddpro.queryNetUsers(maxNetUsersToQuery);

        // Check the return value
        if (retCode != 0)
        {
            System.out.println("An error occurred getting network user information.");
            System.out.println("Error: " + retCode + "\nExtended Error: " + ddpro.queryNetUsersExtErr);
            return;
        }

        System.out.println(ddpro.numNetUsers + " current network users:\n");
        for (int i = 0; i < ddpro.num_net_users; i++)
        {
            System.out.println((i+1) + ") " + ddpro.userNames[i] + " @ " + ddpro.computerNames[i] + " (" + ddpro.ipAddresses[i] + ") using licence '" + ddpro.licenceNames[i] + "'");
        }
        return;
    }


    // !!!! This method must match one stored in the dongle for protCheckWithAlg() to work correctly.
    // For Lite dongles, use DinkeyLook to see the algorithm pre-programmed into the dongle.
    // For Plus and Net dongles, ensure this algorithm matches one you specified in DinkeyAdd when adding protection.
    static int myAlgorithm(int a, int b, int c, int d, int e, int f, int g, int h)
    {
        return a + b + c + d + e + f + g + h;
    }

    // !!!! This method must match the R/W algorithm stored in the dongle for
    // writeDataAreaEnc() and readDataAreaEnc() to work correctly.
    static int myRwAlgorithm(int a, int b, int c, int d, int e, int f, int g, int h)
    {
        return a ^ b ^ c ^ d ^ e ^ f;
    }


    // An example of error reporting. Displays descriptions for some common error codes.
    // IMPORTANT - THE MESSAGES DISPLAYED ARE TO HELP DEVELOPERS IMPLEMENT THE DINKEY PRO/FD API.
    // THEY ARE NOT SUITABLE FOR DISPLAYING TO USERS!
    static void displayError(int retCode, int extErr)
    {
        switch (retCode)
        {
            case 401:
                System.out.println("Error! No dongles detected!");
                break;
            case 403:
                System.out.println("Error! The dongle is a different type to the one specified in DinkeyAdd.");
                break;
            case 404:
                System.out.println("Error! The dongle is a different model to those specified in DinkeyAdd.");
                break;
            case 409:
                System.out.println("Error! The dongle has not been programmed by DinkeyAdd.");
                break;
            case 410:
                System.out.println("Error! The dongle has a different product code to the one specified in DinkeyAdd.");
                break;
            case 411:
                System.out.println("Error! This software's licence was not found in the dongle.");
                break;
            case 413:
                System.out.println("Error! This software has not been locked by DinkeyAdd. For guidance please read the DinkeyAdd chapter of the user manual.");
                break;
            case 417:
                System.out.println("Error! One or more of the parameters set in the DRIS is incorrect.\nCheck if you are encrypting the DRIS in your code but did not specify DRIS encryption in DinkeyAdd - or vice versa.");
                break;
            case 423:
                System.out.println("Error! The number of network users has been exceeded.");
                break;
            case 435:
                System.out.println("Error! DinkeyServer has not been detected on the network.");
                break;
            case 922:
                System.out.println("Error! The Software Key has expired.");
                break;
            default:
                System.out.println("An error occurred checking the dongle.\nError: " + retCode + "\nExtended Error: " + extErr);
                break;
        }
        return;
    }

}
