/*************************************************************************
* Rutoken                                                                *
* Copyright (c) 2003-2017, CJSC Aktiv-Soft. All rights reserved.         *
* Подробная информация:  http://www.rutoken.ru                           *
*************************************************************************/



package ru.rutoken.pkcs11smsample;

/**
 * @author Aktiv Co. <hotline@rutoken.ru>
 */

import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;

import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.ptr.NativeLongByReference;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import ru.rutoken.Pkcs11.CK_MECHANISM;
import ru.rutoken.Pkcs11.RtPkcs11;
import ru.rutoken.Pkcs11.CK_TOKEN_INFO;
import ru.rutoken.Pkcs11.Pkcs11Constants;
import ru.rutoken.Pkcs11.RtPkcs11Constants;

public class Pkcs11SampleActivity extends Activity {
    public enum SampleType {
        getTokenInfo,
        hashData,
        startSm
    }

    /*************************************************************************
     * Загрузить библиотеку при загрузке приложения                          *
     *************************************************************************/
    static final RtPkcs11 mPkcs11 = (RtPkcs11) Native.loadLibrary("rtpkcs11ecp", RtPkcs11.class);

    TextView mLoggerView;
    boolean mBackPressAllowed = false;

    Handler mLogHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            logInMainThread(msg.getData().getString("line"));
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.log);

        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);

        mLoggerView = (TextView) findViewById(R.id.loggerView);

        final Intent intent = getIntent();

        new AsyncTask<Void, Void, Boolean>() {
            @Override
            protected Boolean doInBackground(Void... voids) {
                SampleType sampleType = (SampleType) intent.getSerializableExtra("sampleType");
                switch(sampleType) {
                    case getTokenInfo:
                        getTokenInfo();
                        break;
                    case hashData:
                        hashData();
                        break;
                    case startSm:
                        startSm((byte[])intent.getSerializableExtra("password"));
                        break;
                }
                return Boolean.TRUE;
            }

            @Override
            protected void onPostExecute(Boolean result) {
                Pkcs11SampleActivity.this.mBackPressAllowed = true;
            }
        }.execute();
    }

    @Override
    public void onBackPressed() {
        if(mBackPressAllowed)
            super.onBackPressed();
    }

    abstract class CleanupOperation {
        abstract protected void doOperation();
        public void run() {
            try {
                doOperation();
            }  catch(Exception e) {}
        }
    }

    protected void getTokenInfo() {
        List<CleanupOperation> cleanupOperations = new ArrayList<CleanupOperation>();

        try {
            /*************************************************************************
             * Инициализировать библиотеку                                           *
             *************************************************************************/
            NativeLong rv = mPkcs11.C_Initialize(null);
            checkResult("C_Initialize", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));

            /*************************************************************************
             * Деинициализировать библиотеку при выходе из текущего блока            *
             *************************************************************************/
            cleanupOperations.add(0, new CleanupOperation() {
                @Override
                protected void doOperation() {
                    NativeLong rv = mPkcs11.C_Finalize(null);
                    checkResult("C_Finalize", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));
                }
            });

            /*************************************************************************
             * Получить количество слотов c подключенными токенами                   *
             *************************************************************************/
            NativeLongByReference slotCount = new NativeLongByReference();
            rv = mPkcs11.C_GetSlotList(true, null, slotCount);
            checkResult("C_GetSlotList", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));

            checkResult("Checking number of tokens connected", slotCount.getValue().intValue() > 0, "No tokens available");

            /*************************************************************************
             * Получить список слотов c подключенными токенами                       *
             *************************************************************************/
            NativeLong[] slotList = new NativeLong[slotCount.getValue().intValue()];
            rv = mPkcs11.C_GetSlotList(true, slotList, slotCount);
            checkResult("C_GetSlotList", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));

            logLine(String.format("№ of tokens connected: %1$d", slotCount.getValue().intValue()));

            List<CK_TOKEN_INFO> tokenInfos = new ArrayList<CK_TOKEN_INFO>();
            for (int i = 0; i < slotCount.getValue().intValue(); ++i)
            {
                /*************************************************************************
                 * Получить информацию о токене                                          *
                 *************************************************************************/
                CK_TOKEN_INFO tokenInfo = new CK_TOKEN_INFO();
                rv = mPkcs11.C_GetTokenInfo(slotList[i], tokenInfo);
                checkResult("C_GetTokenInfo", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));

                tokenInfos.add(tokenInfo);
            }

            /*************************************************************************
             * Распечатать информацию о подключенных токенах                         *
             *************************************************************************/
            logLine("Connected tokens info:");
            for(CK_TOKEN_INFO tokenInfo: tokenInfos) {
                logLine(" Token " + ByteArrayConverter.byteArrayToString(tokenInfo.serialNumber).trim() + " info");
                logLine("  Label: " + ByteArrayConverter.byteArrayToString(tokenInfo.label).trim());
                logLine("  Manufacturer: " + ByteArrayConverter.byteArrayToString(tokenInfo.manufacturerID).trim());
                logLine("  Model: " + ByteArrayConverter.byteArrayToString(tokenInfo.model).trim());
                logLine("  Total memory: " +
                        String.valueOf(tokenInfo.ulTotalPublicMemory.intValue() + tokenInfo.ulTotalPrivateMemory.intValue()));
                logLine("  Free memory: " +
                        String.valueOf(tokenInfo.ulFreePublicMemory.intValue() + tokenInfo.ulFreePrivateMemory.intValue()));
            }
        } catch(Exception e) { }
        finally {
            /*************************************************************************
             * Выполнить отложенные до выхода из предыдущего блока операции          *
             *************************************************************************/
            for(CleanupOperation operation: cleanupOperations) {
                operation.run();
            }
        }
    }

    protected void hashData() {
        List<CleanupOperation> cleanupOperations = new ArrayList<CleanupOperation>();

        try {
            /*************************************************************************
             * Инициализировать библиотеку                                           *
             *************************************************************************/
            NativeLong rv = mPkcs11.C_Initialize(null);
            checkResult("C_Initialize", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));

            /*************************************************************************
             * Деинициализировать библиотеку при выходе из текущего блока            *
             *************************************************************************/
            cleanupOperations.add(0, new CleanupOperation() {
                @Override
                protected void doOperation() {
                    NativeLong rv = mPkcs11.C_Finalize(null);
                    checkResult("C_Finalize", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));
                }
            });

            /*************************************************************************
             * Получить количество слотов c подключенными токенами                   *
             *************************************************************************/
            NativeLongByReference slotCount = new NativeLongByReference();
            rv = mPkcs11.C_GetSlotList(true, null, slotCount);
            checkResult("C_GetSlotList", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));

            checkResult("Checking number of tokens connected", slotCount.getValue().intValue() > 0, "No tokens available");

            /*************************************************************************
             * Получить список слотов c подключенными токенами                       *
             *************************************************************************/
            NativeLong[] slotList = new NativeLong[slotCount.getValue().intValue()];
            rv = mPkcs11.C_GetSlotList(true, slotList, slotCount);
            checkResult("C_GetSlotList", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));

            logLine("Using token in first slot");
            final NativeLong slot = slotList[0];

            /*************************************************************************
             * Получить список поддерживаемых токеном механизмов                     *
             *************************************************************************/
            NativeLongByReference mechanismCountByReference = new NativeLongByReference();
            rv = mPkcs11.C_GetMechanismList(slot, null, mechanismCountByReference);
            checkResult("C_GetMechanismList", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));

            int mechanismCount = mechanismCountByReference.getValue().intValue();
            checkResult("Checking mechanisms available", mechanismCount > 0, "No mechanisms available");

            NativeLong[] mechanisms = new NativeLong[mechanismCount];
            rv = mPkcs11.C_GetMechanismList(slot, mechanisms, mechanismCountByReference);
            checkResult("C_GetMechanismList", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));

            /*************************************************************************
             * Определение поддерживаемых токеном механизмов                          *
             *************************************************************************/
            CK_MECHANISM mechanism =
                    new CK_MECHANISM(RtPkcs11Constants.CKM_GOSTR3411, null, null);
            boolean isGostR3411Supported = Arrays.asList(mechanisms).contains(mechanism.mechanism);
            checkResult("Checking CKM_GOSTR3411 support", isGostR3411Supported, "CKM_GOSTR3411 isn`t supported!");

            /*************************************************************************
             * Открыть RW сессию в первом доступном слоте                            *
             *************************************************************************/
            NativeLongByReference sessionByReference = new NativeLongByReference();
            rv = mPkcs11.C_OpenSession(slot,
                    new NativeLong(Pkcs11Constants.CKF_SERIAL_SESSION.longValue() | Pkcs11Constants.CKF_RW_SESSION.longValue()),
                    null, null, sessionByReference);
            checkResult("C_OpenSession", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));

            /*************************************************************************
             * Закрыть сессию при выходе из текущего блока                           *
             *************************************************************************/
            final NativeLong session = sessionByReference.getValue();
            cleanupOperations.add(0, new CleanupOperation() {
                @Override
                protected void doOperation() {
                    NativeLong rv = mPkcs11.C_CloseSession(session);
                    checkResult("C_CloseSession", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));
                }
            });

            final byte data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};

            /*************************************************************************
             * Инициализировать хэш-функцию                                          *
             *************************************************************************/
            rv = mPkcs11.C_DigestInit(session, mechanism);
            checkResult("C_DigestInit", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));

            /*************************************************************************
             * Определить размер хэш-кода                                            *
             *************************************************************************/
            NativeLongByReference count = new NativeLongByReference(new NativeLong());
            rv = mPkcs11.C_Digest(session, data, new NativeLong(data.length), null, count);
            checkResult("C_Digest", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));

            /*************************************************************************
             * Вычислить хэш-код данных                                              *
             *************************************************************************/
            byte hash[] = new byte[count.getValue().intValue()];
            rv = mPkcs11.C_Digest(session, data, new NativeLong(data.length), hash, count);
            checkResult("C_Digest", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));

            /*************************************************************************
             * Распечатать буфер, содержащий хэш-код                                 *
             *************************************************************************/
            logLine("Digest result: ");
            logHex(hash);
        } catch(Exception e) { }
        finally {
            /*************************************************************************
             * Выполнить отложенные до выхода из предыдущего блока операции          *
             *************************************************************************/
            for(CleanupOperation operation: cleanupOperations) {
                operation.run();
            }
        }
    }

    protected void startSm(byte[] password) {
        List<CleanupOperation> cleanupOperations = new ArrayList<CleanupOperation>();

        try {
            /*************************************************************************
             * Инициализировать библиотеку                                           *
             *************************************************************************/
            NativeLong rv = mPkcs11.C_Initialize(null);
            checkResult("C_Initialize", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));

            /*************************************************************************
             * Деинициализировать библиотеку при выходе из текущего блока            *
             *************************************************************************/
            cleanupOperations.add(0, new CleanupOperation() {
                @Override
                protected void doOperation() {
                    NativeLong rv = mPkcs11.C_Finalize(null);
                    checkResult("C_Finalize", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));
                }
            });

            /*************************************************************************
             * Получить количество слотов c подключенными токенами                   *
             *************************************************************************/
            NativeLongByReference slotCount = new NativeLongByReference();
            rv = mPkcs11.C_GetSlotList(true, null, slotCount);
            checkResult("C_GetSlotList", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));

            checkResult("Checking number of tokens connected", slotCount.getValue().intValue() > 0, "No tokens available");

            /*************************************************************************
             * Получить список слотов c подключенными токенами                       *
             *************************************************************************/
            NativeLong[] slotList = new NativeLong[slotCount.getValue().intValue()];
            rv = mPkcs11.C_GetSlotList(true, slotList, slotCount);
            checkResult("C_GetSlotList", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));

            /*************************************************************************
             * Проверить, что подключен только один токен                            *
             *************************************************************************/
            checkResult("Checking number of tokens connected", slotCount.getValue().intValue() == 1,
                    "Several tokens connected. Please ensure only one token is connected.");

            final NativeLong slot = slotList[0];

            /*************************************************************************
             * Проверить, что требуется активация шифрования канала                  *
             *************************************************************************/
            NativeLongByReference sessionByReference = new NativeLongByReference();
            rv = mPkcs11.C_OpenSession(slot,
                    new NativeLong(Pkcs11Constants.CKF_SERIAL_SESSION.longValue() | Pkcs11Constants.CKF_RW_SESSION.longValue()),
                    null, null, sessionByReference);

            if(rv.equals(Pkcs11Constants.CKR_OK)) {
                /*************************************************************************
                 * Закрыть сессию при выходе из блока catch                              *
                 *************************************************************************/
                final NativeLong session = sessionByReference.getValue();
                cleanupOperations.add(0, new CleanupOperation() {
                    @Override
                    protected void doOperation() {
                        NativeLong rv = mPkcs11.C_CloseSession(session);
                        checkResult("C_CloseSession", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));
                    }
                });
            }

            checkResult("Checking secure messaging activation is required", rv.equals(Pkcs11Constants.CKR_FUNCTION_NOT_SUPPORTED),
                    "Secure messaging activation is not required");

            /*************************************************************************
             * Передать пароль активации для установки защищенного канала            *
             *************************************************************************/
            rv = mPkcs11.C_EX_SetActivationPassword(slot, password);;
            checkResult("C_EX_SetActivationPassword", rv.equals(Pkcs11Constants.CKR_OK), rvToString(rv));
        } catch(Exception e) { }
        finally {
            /*************************************************************************
             * Выполнить отложенные до выхода из предыдущего блока операции          *
             *************************************************************************/
            for(CleanupOperation operation: cleanupOperations) {
                operation.run();
            }
        }
    }

    protected void checkResult(String msg, boolean condition, String errMsg) {
        log(msg);
        if(!condition) {
            logLine(" -> Failed");
            if(errMsg != null) {
                logLine(errMsg);
            }

            throw new RuntimeException();
        } else {
            logLine(" -> OK");
        }
    }

    protected void logInMainThread(String line) {
        mLoggerView.setText(mLoggerView.getText()+line);
        int scrollY = mLoggerView.getLineCount() * mLoggerView.getLineHeight() - mLoggerView.getHeight();
        if(scrollY > 0)
            mLoggerView.scrollTo(0, scrollY);
    }

    protected void log(String line) {
        Bundle bundle = new Bundle();
        bundle.putString("line", line);
        Message message = new Message();
        message.setData(bundle);
        mLogHandler.sendMessage(message);
    }

    protected void logHex(byte[] data) {
        String hex = ByteArrayConverter.byteArrayToHexString(data);
        hex = hex.replaceAll("(..)", "$1 " );
        hex = hex.replaceAll("((.. ){8})", "$1\n" );
        log(hex);
    }

    protected void logLine(String line) {
        log(line + "\n");
    }

    static String rvToString(NativeLong rv)
    {
        if (rv.equals(Pkcs11Constants.CKR_OK)) return "CKR_OK";
        else if (rv.equals(Pkcs11Constants.CKR_CANCEL)) return "CKR_CANCEL";
        else if (rv.equals(Pkcs11Constants.CKR_HOST_MEMORY)) return "CKR_HOST_MEMORY";
        else if (rv.equals(Pkcs11Constants.CKR_SLOT_ID_INVALID)) return "CKR_SLOT_ID_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_GENERAL_ERROR)) return "CKR_GENERAL_ERROR";
        else if (rv.equals(Pkcs11Constants.CKR_FUNCTION_FAILED)) return "CKR_FUNCTION_FAILED";
        else if (rv.equals(Pkcs11Constants.CKR_ARGUMENTS_BAD)) return "CKR_ARGUMENTS_BAD";
        else if (rv.equals(Pkcs11Constants.CKR_NO_EVENT)) return "CKR_NO_EVENT";
        else if (rv.equals(Pkcs11Constants.CKR_NEED_TO_CREATE_THREADS)) return "CKR_NEED_TO_CREATE_THREADS";
        else if (rv.equals(Pkcs11Constants.CKR_CANT_LOCK)) return "CKR_CANT_LOCK";
        else if (rv.equals(Pkcs11Constants.CKR_ATTRIBUTE_READ_ONLY)) return "CKR_ATTRIBUTE_READ_ONLY";
        else if (rv.equals(Pkcs11Constants.CKR_ATTRIBUTE_SENSITIVE)) return "CKR_ATTRIBUTE_SENSITIVE";
        else if (rv.equals(Pkcs11Constants.CKR_ATTRIBUTE_TYPE_INVALID)) return "CKR_ATTRIBUTE_TYPE_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_ATTRIBUTE_VALUE_INVALID)) return "CKR_ATTRIBUTE_VALUE_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_DATA_INVALID)) return "CKR_DATA_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_DATA_LEN_RANGE)) return "CKR_DATA_LEN_RANGE";
        else if (rv.equals(Pkcs11Constants.CKR_DEVICE_ERROR)) return "CKR_DEVICE_ERROR";
        else if (rv.equals(Pkcs11Constants.CKR_DEVICE_MEMORY)) return "CKR_DEVICE_MEMORY";
        else if (rv.equals(Pkcs11Constants.CKR_DEVICE_REMOVED)) return "CKR_DEVICE_REMOVED";
        else if (rv.equals(Pkcs11Constants.CKR_ENCRYPTED_DATA_INVALID)) return "CKR_ENCRYPTED_DATA_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_ENCRYPTED_DATA_LEN_RANGE)) return "CKR_ENCRYPTED_DATA_LEN_RANGE";
        else if (rv.equals(Pkcs11Constants.CKR_FUNCTION_CANCELED)) return "CKR_FUNCTION_CANCELED";
        else if (rv.equals(Pkcs11Constants.CKR_FUNCTION_NOT_PARALLEL)) return "CKR_FUNCTION_NOT_PARALLEL";
        else if (rv.equals(Pkcs11Constants.CKR_FUNCTION_NOT_SUPPORTED)) return "CKR_FUNCTION_NOT_SUPPORTED";
        else if (rv.equals(Pkcs11Constants.CKR_KEY_HANDLE_INVALID)) return "CKR_KEY_HANDLE_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_KEY_SIZE_RANGE)) return "CKR_KEY_SIZE_RANGE";
        else if (rv.equals(Pkcs11Constants.CKR_KEY_TYPE_INCONSISTENT)) return "CKR_KEY_TYPE_INCONSISTENT";
        else if (rv.equals(Pkcs11Constants.CKR_KEY_NOT_NEEDED)) return "CKR_KEY_NOT_NEEDED";
        else if (rv.equals(Pkcs11Constants.CKR_KEY_CHANGED)) return "CKR_KEY_CHANGED";
        else if (rv.equals(Pkcs11Constants.CKR_KEY_NEEDED)) return "CKR_KEY_NEEDED";
        else if (rv.equals(Pkcs11Constants.CKR_KEY_INDIGESTIBLE)) return "CKR_KEY_INDIGESTIBLE";
        else if (rv.equals(Pkcs11Constants.CKR_KEY_FUNCTION_NOT_PERMITTED)) return "CKR_KEY_FUNCTION_NOT_PERMITTED";
        else if (rv.equals(Pkcs11Constants.CKR_KEY_NOT_WRAPPABLE)) return "CKR_KEY_NOT_WRAPPABLE";
        else if (rv.equals(Pkcs11Constants.CKR_KEY_UNEXTRACTABLE)) return "CKR_KEY_UNEXTRACTABLE";
        else if (rv.equals(Pkcs11Constants.CKR_MECHANISM_INVALID)) return "CKR_MECHANISM_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_MECHANISM_PARAM_INVALID)) return "CKR_MECHANISM_PARAM_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_OBJECT_HANDLE_INVALID)) return "CKR_OBJECT_HANDLE_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_OPERATION_ACTIVE)) return "CKR_OPERATION_ACTIVE";
        else if (rv.equals(Pkcs11Constants.CKR_OPERATION_NOT_INITIALIZED)) return "CKR_OPERATION_NOT_INITIALIZED";
        else if (rv.equals(Pkcs11Constants.CKR_PIN_INCORRECT)) return "CKR_PIN_INCORRECT";
        else if (rv.equals(Pkcs11Constants.CKR_PIN_INVALID)) return "CKR_PIN_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_PIN_LEN_RANGE)) return "CKR_PIN_LEN_RANGE";
        else if (rv.equals(Pkcs11Constants.CKR_PIN_EXPIRED)) return "CKR_PIN_EXPIRED";
        else if (rv.equals(Pkcs11Constants.CKR_PIN_LOCKED)) return "CKR_PIN_LOCKED";
        else if (rv.equals(Pkcs11Constants.CKR_SESSION_CLOSED)) return "CKR_SESSION_CLOSED";
        else if (rv.equals(Pkcs11Constants.CKR_SESSION_COUNT)) return "CKR_SESSION_COUNT";
        else if (rv.equals(Pkcs11Constants.CKR_SESSION_HANDLE_INVALID)) return "CKR_SESSION_HANDLE_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_SESSION_PARALLEL_NOT_SUPPORTED)) return "CKR_SESSION_PARALLEL_NOT_SUPPORTED";
        else if (rv.equals(Pkcs11Constants.CKR_SESSION_READ_ONLY)) return "CKR_SESSION_READ_ONLY";
        else if (rv.equals(Pkcs11Constants.CKR_SESSION_EXISTS)) return "CKR_SESSION_EXISTS";
        else if (rv.equals(Pkcs11Constants.CKR_SESSION_READ_ONLY_EXISTS)) return "CKR_SESSION_READ_ONLY_EXISTS";
        else if (rv.equals(Pkcs11Constants.CKR_SESSION_READ_WRITE_SO_EXISTS)) return "CKR_SESSION_READ_WRITE_SO_EXISTS";
        else if (rv.equals(Pkcs11Constants.CKR_SIGNATURE_INVALID)) return "CKR_SIGNATURE_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_SIGNATURE_LEN_RANGE)) return "CKR_SIGNATURE_LEN_RANGE";
        else if (rv.equals(Pkcs11Constants.CKR_TEMPLATE_INCOMPLETE)) return "CKR_TEMPLATE_INCOMPLETE";
        else if (rv.equals(Pkcs11Constants.CKR_TEMPLATE_INCONSISTENT)) return "CKR_TEMPLATE_INCONSISTENT";
        else if (rv.equals(Pkcs11Constants.CKR_TOKEN_NOT_PRESENT)) return "CKR_TOKEN_NOT_PRESENT";
        else if (rv.equals(Pkcs11Constants.CKR_TOKEN_NOT_RECOGNIZED)) return "CKR_TOKEN_NOT_RECOGNIZED";
        else if (rv.equals(Pkcs11Constants.CKR_TOKEN_WRITE_PROTECTED)) return "CKR_TOKEN_WRITE_PROTECTED";
        else if (rv.equals(Pkcs11Constants.CKR_UNWRAPPING_KEY_HANDLE_INVALID)) return "CKR_UNWRAPPING_KEY_HANDLE_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_UNWRAPPING_KEY_SIZE_RANGE)) return "CKR_UNWRAPPING_KEY_SIZE_RANGE";
        else if (rv.equals(Pkcs11Constants.CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT)) return "CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT";
        else if (rv.equals(Pkcs11Constants.CKR_USER_ALREADY_LOGGED_IN)) return "CKR_USER_ALREADY_LOGGED_IN";
        else if (rv.equals(Pkcs11Constants.CKR_USER_NOT_LOGGED_IN)) return "CKR_USER_NOT_LOGGED_IN";
        else if (rv.equals(Pkcs11Constants.CKR_USER_PIN_NOT_INITIALIZED)) return "CKR_USER_PIN_NOT_INITIALIZED";
        else if (rv.equals(Pkcs11Constants.CKR_USER_TYPE_INVALID)) return "CKR_USER_TYPE_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_USER_ANOTHER_ALREADY_LOGGED_IN)) return "CKR_USER_ANOTHER_ALREADY_LOGGED_IN";
        else if (rv.equals(Pkcs11Constants.CKR_USER_TOO_MANY_TYPES)) return "CKR_USER_TOO_MANY_TYPES";
        else if (rv.equals(Pkcs11Constants.CKR_WRAPPED_KEY_INVALID)) return "CKR_WRAPPED_KEY_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_WRAPPED_KEY_LEN_RANGE)) return "CKR_WRAPPED_KEY_LEN_RANGE";
        else if (rv.equals(Pkcs11Constants.CKR_WRAPPING_KEY_HANDLE_INVALID)) return "CKR_WRAPPING_KEY_HANDLE_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_WRAPPING_KEY_SIZE_RANGE)) return "CKR_WRAPPING_KEY_SIZE_RANGE";
        else if (rv.equals(Pkcs11Constants.CKR_WRAPPING_KEY_TYPE_INCONSISTENT)) return "CKR_WRAPPING_KEY_TYPE_INCONSISTENT";
        else if (rv.equals(Pkcs11Constants.CKR_RANDOM_SEED_NOT_SUPPORTED)) return "CKR_RANDOM_SEED_NOT_SUPPORTED";
        else if (rv.equals(Pkcs11Constants.CKR_RANDOM_NO_RNG)) return "CKR_RANDOM_NO_RNG";
        else if (rv.equals(Pkcs11Constants.CKR_DOMAIN_PARAMS_INVALID)) return "CKR_DOMAIN_PARAMS_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_BUFFER_TOO_SMALL)) return "CKR_BUFFER_TOO_SMALL";
        else if (rv.equals(Pkcs11Constants.CKR_SAVED_STATE_INVALID)) return "CKR_SAVED_STATE_INVALID";
        else if (rv.equals(Pkcs11Constants.CKR_INFORMATION_SENSITIVE)) return "CKR_INFORMATION_SENSITIVE";
        else if (rv.equals(Pkcs11Constants.CKR_STATE_UNSAVEABLE)) return "CKR_STATE_UNSAVEABLE";
        else if (rv.equals(Pkcs11Constants.CKR_CRYPTOKI_NOT_INITIALIZED)) return "CKR_CRYPTOKI_NOT_INITIALIZED";
        else if (rv.equals(Pkcs11Constants.CKR_CRYPTOKI_ALREADY_INITIALIZED)) return "CKR_CRYPTOKI_ALREADY_INITIALIZED";
        else if (rv.equals(Pkcs11Constants.CKR_MUTEX_BAD)) return "CKR_MUTEX_BAD";
        else if (rv.equals(Pkcs11Constants.CKR_MUTEX_NOT_LOCKED)) return "CKR_MUTEX_NOT_LOCKED";
        else if (rv.equals(Pkcs11Constants.CKR_NEW_PIN_MODE)) return "CKR_NEW_PIN_MODE";
        else if (rv.equals(Pkcs11Constants.CKR_NEXT_OTP)) return "CKR_NEXT_OTP";
        else if (rv.equals(Pkcs11Constants.CKR_FUNCTION_REJECTED)) return "CKR_FUNCTION_REJECTED";
        else if (rv.equals(RtPkcs11Constants.CKR_CORRUPTED_MAPFILE)) return "CKR_CORRUPTED_MAPFILE";
        else if (rv.equals(RtPkcs11Constants.CKR_WRONG_VERSION_FIELD)) return "CKR_WRONG_VERSION_FIELD";
        else if (rv.equals(RtPkcs11Constants.CKR_WRONG_PKCS1_ENCODING)) return "CKR_WRONG_PKCS1_ENCODING";
        else if (rv.equals(RtPkcs11Constants.CKR_RTPKCS11_DATA_CORRUPTED)) return "CKR_RTPKCS11_DATA_CORRUPTED";
        else if (rv.equals(RtPkcs11Constants.CKR_RTPKCS11_RSF_DATA_CORRUPTED)) return "CKR_RTPKCS11_RSF_DATA_CORRUPTED";
        else if (rv.equals(RtPkcs11Constants.CKR_SM_PASSWORD_INVALID)) return "CKR_SM_PASSWORD_INVALID";
        else if (rv.equals(RtPkcs11Constants.CKR_LICENSE_READ_ONLY)) return "CKR_LICENSE_READ_ONLY";
        else return "Unknown error";
    }
}

class ByteArrayConverter {
    public static String byteArrayToString(byte[] array) {
        String s = new String();
        for (int i = 0; i < array.length; ++i)
            s+=(char)array[i];
        return s;
    }

    public static String byteArrayToHexString(byte[] array) {
        StringBuilder builder = new StringBuilder(array.length * 2);
        for(byte b: array)
            builder.append(String.format("%02x", b & 0xff));
        return builder.toString();
    }
}
