#include "mxkN2.h"

#include <Windows.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>

#include "../../dll/devices/smb_n2.h"
#include "../ami/ami.h"
#include "mxkCrypt.h"
#include "mxkSmbus.h"

typedef struct {
    unsigned char m_Key[16];
    unsigned char m_IV[16];
} AESKey_t;

#pragma pack(push, 1)
typedef struct {
    unsigned short m_Tag;
    unsigned short m_ParamSize;
    unsigned short m_Command;
    unsigned char m_Body[N2_DATA_MAX];
} N2Packet_t;
typedef struct {
    union {
        struct {
            Sha1Sum_t m_ShaSum;
        } rqu;
        struct {
            N2Nonce_t m_Nonce;
            HmacSum_t m_Hmac;
        } sign;
    };
} N2PacketFooter_t;
#pragma pack(pop)

BOOL MXK_HAS_INIT = FALSE;
BOOL MXK_N2_INIT = FALSE;
#define CheckN2Init                               \
    do {                                          \
        if (!MXK_N2_INIT) {                       \
            amiDebugLog("Error: Uninitialized."); \
            return -3;                            \
        }                                         \
    } while (0);
typedef struct {
    unsigned char m_Addr;
    HmacKey_t m_HmacKey;
    AESKey_t m_AESKey;
    N2AuthKey_t m_AuthKey;
    AESKey_t m_EncKey;
    HmacSum_t m_HmacSalt;
} N2DeviceInfo_t;
N2DeviceInfo_t N2DeviceInfo;

/**
 * Device keys for:
 *
 * RingEdge:
 * Key: 76ec42b6ae0cb048105171ad8cb2fb07
 * IV: 8e300ba42e154baf651532f570041f5b
 * Auth: 96ed31b2287105a5a330540f25bed851a5c83621
 *
 * ELEFUN:
 * Key: 519aaa25b5ed20b53d28bb56731d7c8d
 * IV: 40a1bfc5341340d3dfdbd1a12b855700
 * Auth: 998e952b6a4899ba42d1d0e8a4fdc87775e19c26
 *
 * Nu:
 * Key: c21c966cbd8b00b9cf4c51bab2c3dfa5
 * IV: 0b137aab20acc7eea0bbec594957dc6d
 * Auth: 74935ef7e0181c0661f7bb7118c5512a130a5d19
 */

// Auth = 96ed31b2287105a5a330540f25bed851a5c83621
unsigned char N2_KEY_HMAC[2][20] = {
    { 0x2c, 0xf8, 0x3e, 0x5e, 0x51, 0xf0, 0x60, 0x1b, 0xf6, 0xb1,
      0x49, 0x11, 0x3a, 0xaf, 0x36, 0xe1, 0x51, 0x1c, 0x16, 0x05 },
    { 0xba, 0x15, 0x0f, 0xec, 0x79, 0x81, 0x65, 0xbe, 0x55, 0x81,
      0x1d, 0x1e, 0x1f, 0x11, 0xee, 0xb0, 0xf4, 0xd4, 0x20, 0x24 }
};

// Key = 76ec42b6ae0cb048105171ad8cb2fb07
// IV = 8e300ba42e154baf651532f570041f5b
AESKey_t N2_KEY_AES[2] = {
    { { 0xd7, 0xf6, 0x86, 0xfb, 0x63, 0x44, 0xff, 0xa5, 0x60, 0x9d, 0x4e, 0xf0, 0x18, 0xe9, 0x45,
        0xb4 },
      { 0x12, 0x6c, 0xb1, 0xdb, 0x9e, 0x00, 0x8c, 0xc6, 0xae, 0xa1, 0x73, 0xec, 0xe7, 0x2f, 0x5a,
        0xc4 } },
    { { 0xa1, 0x1a, 0xc4, 0x4d, 0xcd, 0x48, 0x4f, 0xed, 0x70, 0xcc, 0x3f, 0x5d, 0x94, 0x5b, 0xbe,
        0xb3 },
      { 0x9c, 0x5c, 0xba, 0x7f, 0xb0, 0x15, 0xc7, 0x69, 0xcb, 0xb4, 0x41, 0x19, 0x97, 0x2b, 0x45,
        0x9f } },
};

int mxkN2CmdInit(void) {
    if (MXK_N2_INIT) {
        amiDebugLog("Error: Already initialized.");
        return -4;
    }
    ZeroMemory(&N2DeviceInfo, sizeof N2DeviceInfo);
    MXK_N2_INIT = TRUE;
    return 0;
}

int mxkN2CmdSetDeviceInfo(void) {
    if (MXK_HAS_INIT) return -4;

    if (mxkN2CmdInit() != 0) return -5;

    HmacKey_t hmacKey;
    AESKey_t aesKey;
    for (int i = 0; i < sizeof hmacKey; i++) hmacKey[i] = N2_KEY_HMAC[0][i] ^ N2_KEY_HMAC[1][i];
    for (int i = 0; i < sizeof aesKey.m_Key; i++)
        aesKey.m_Key[i] = N2_KEY_AES[0].m_Key[i] ^ N2_KEY_AES[1].m_Key[i];
    for (int i = 0; i < sizeof aesKey.m_IV; i++)
        aesKey.m_IV[i] = N2_KEY_AES[0].m_IV[i] ^ N2_KEY_AES[1].m_IV[i];

    N2DeviceInfo_t deviceInfo = { 0 };
    deviceInfo.m_Addr = N2_ADDR;

    memcpy(&deviceInfo.m_HmacKey, hmacKey, sizeof hmacKey);
    memcpy(&deviceInfo.m_AESKey, &aesKey, sizeof aesKey);
    if (!MXK_N2_INIT)
        amiDebugLog("Error: Uninitialized.");
    else
        memcpy(&N2DeviceInfo, &deviceInfo, sizeof deviceInfo);

    MXK_HAS_INIT = TRUE;
    return 0;
}

// TODO: Better name
void _randomBytes(void *buf, int nbytes) {
    FILETIME fileTime;
    amtime_t amTime;

    for (int i = 0; i < nbytes; i++) {
        amiTimerGet(&amTime);
        GetSystemTimeAsFileTime(&fileTime);
        ((unsigned char *)buf)[i] =
            (amTime.microseconds ^ fileTime.dwLowDateTime ^ fileTime.dwHighDateTime) & 0xff;
    }
}
void mxkN2GetPacketNonce(PN2Nonce_t nonce) { _randomBytes(nonce, 20); }

int mxkN2UtilVCatenateData(unsigned char *out, size_t *count, unsigned int numStreams,
                           va_list args) {
    if (out == NULL || count == NULL) {
        amiDebugLog("Error: Invalid param.");
        return -2;
    }

    size_t offset = 0;
    size_t max = *count;
    for (size_t i = 0; i < numStreams && i < max; i++) {
        unsigned char *b = va_arg(args, unsigned char *);
        size_t n = va_arg(args, size_t);
        if (offset + n > max) n = max - offset;
        memcpy(out + offset, b, n);
        offset += n;
    }

    *count = offset;
    return 0;
}
void mxkN2UtilCatenateData(unsigned char *concatinated, size_t *count, unsigned int numStreams,
                           ...) {
    va_list args;
    va_start(args, numStreams);

    mxkN2UtilVCatenateData(concatinated, count, numStreams, args);

    va_end(args);
}

void mxkN2UtilSha1Many(PSha1Sum_t sum, int numStreams, ...) {
    va_list args;
    va_start(args, numStreams);

    Sha1Sum_t sumout;
    unsigned char concatinated[0x200];
    size_t size = sizeof concatinated;

    mxkN2UtilVCatenateData(concatinated, &size, numStreams, args);
    mxkCryptCalcHashWithSha1(concatinated, size, &sumout);
    memcpy_s(sum, sizeof *sum, sumout, sizeof sumout);

    va_end(args);
}

int mxkN2UtilHmacPacket(PSha1Sum_t packetSha, PHmacSum_t hmacSalt, PN2Nonce_t nonce, PHmacKey_t key,
                        PHmacSum_t computedHash) {
    HmacSum_t hash;
    unsigned char data_in[sizeof *packetSha + sizeof *hmacSalt + sizeof *nonce];

    size_t count = sizeof *packetSha + sizeof *hmacSalt + sizeof *nonce;
    unsigned int nout = HMAC_SUM_SIZE;
    mxkN2UtilCatenateData(data_in, &count, 3, packetSha, sizeof *packetSha, hmacSalt,
                          sizeof *hmacSalt, nonce, sizeof *nonce);

    int err = mxkCryptCalcHashWithHmacSha1(key, &hash, &nout, data_in, count);
    if (err == -2 || nout != HMAC_SUM_SIZE) return -2;

    memcpy_s(computedHash, sizeof *computedHash, hash, HMAC_SUM_SIZE);
    return 0;
}

int mxkN2CmdWriteData(unsigned char addr, void *data, unsigned short nbytes,
                      unsigned short *nbytesOut) {
    unsigned int ptr_;
    unsigned char nstep;
    int status;
    unsigned short position;

    if (mxkSmbusRequestMutex() != 0) {
        amiDebugLog("Error: mxkSmbusRequestMutex");
        return -6;
    }

    status = 0;
    position = 0;
    if (nbytes != 0) {
        do {
            ptr_ = position;
            if ((int)(nbytes - ptr_) < 5) {
                nstep = (byte)(nbytes - ptr_);
                if (nstep != 1) goto LAB_0040ae51;
                status = mxkSmbusWriteByte(addr, 0xcc, ((unsigned char *)data)[ptr_]);
            } else {
                nstep = 4;
            LAB_0040ae51:
                status = mxkSmbusI2CWriteBlock(addr, 0xcc, &((unsigned char *)data)[ptr_], nstep);
            }
            if (status != 0) {
                amiDebugLog("Error: Data write failed. Position %d. ErrorCode %d.", position,
                            status);

                break;
            }
            position += nstep;
            Sleep(0);
        } while (position < nbytes);
    }
    *nbytesOut = position;

    if (mxkSmbusReleaseMutex() != 0) {
        amiDebugLog("Error: mxkSmbusReleaseMutex");
        return -6;
    }
    return 0;
}

int mxkN2CmdWriteCommand(PN2Nonce_t requestNonce, PHmacSum_t hmacSalt, N2Tag_t tag,
                         N2Command_t command, PHmacKey_t hmacKey, unsigned char addr,
                         N2Byte_t *data, unsigned short nbytes) {
    Sha1Sum_t sha1sum;
    N2Packet_t packet;
    int ret;

    if (data == NULL && nbytes != 0) return -2;

    N2PacketFooter_t *footer = (N2PacketFooter_t *)(packet.m_Body + nbytes);

    ZeroMemory(&packet, sizeof packet);
    unsigned short paramsize =
        N2_HEADER_SIZE + nbytes +
        (tag == N2_TAG_RQU_COMMAND ? N2_FOOTER_RQU_SIZE : N2_FOOTER_SIGN_SIZE);

    N2Tag_t realTag = tag == N2_TAG_RQU_COMMAND ? N2_TAG_RQU_COMMAND : N2_TAG_RQU_AUTH_COMMAND;
    packet.m_Tag = _byteswap_ushort(realTag);
    packet.m_ParamSize = _byteswap_ushort(paramsize);
    packet.m_Command = _byteswap_ushort(command);

    if (data != NULL) memcpy_s(packet.m_Body, sizeof packet.m_Body, data, nbytes);

    mxkN2UtilSha1Many(&sha1sum, 1, &packet, nbytes + N2_HEADER_SIZE);

    if (tag == N2_TAG_RQU_COMMAND) {
        memcpy_s(footer->rqu.m_ShaSum, sizeof footer->rqu.m_ShaSum, sha1sum, sizeof sha1sum);
    } else {
        if (requestNonce == NULL || hmacKey == NULL) return -2;

        mxkN2GetPacketNonce(requestNonce);
        memcpy_s(footer->sign.m_Nonce, sizeof footer->sign.m_Nonce, requestNonce,
                 sizeof *requestNonce);

        mxkN2UtilHmacPacket(&sha1sum, hmacSalt, &footer->sign.m_Nonce, hmacKey,
                            &footer->sign.m_Hmac);
    }

    unsigned short nBytesOut;
    ret = mxkN2CmdWriteData(addr, &packet, paramsize, &nBytesOut);
    if (ret != 0) {
        amiDebugLog("Error: Data write failed. ErrorCode %d.", ret);
        return -8;
    }
    return 0;
}

int mxkN2CmdReadData(unsigned char addr, N2Packet_t *lpPacket, unsigned short nReadSize,
                     unsigned short *lpBytesRead) {
    int err;
    *lpBytesRead = 0;

    if (nReadSize < 4 || lpPacket == NULL) {
        amiDebugLog("Error: Invalid param.");
        return -2;
    }
    if (mxkSmbusRequestMutex() != 0) return -6;

    unsigned char *buffer = (unsigned char *)lpPacket;

    // Read until m_ParamSize
    err = mxkSmbusI2CReadBlock(addr, 0xc3, buffer, 4);

    unsigned short readPosition = 4;
    if (err == 0) {
        unsigned short paramSize = _byteswap_ushort(lpPacket->m_ParamSize);
        if (nReadSize < paramSize - 4) {
            amiDebugLog("mxkSmbusI2CReadBlock data : %04X %04X", lpPacket->m_Tag,
                        lpPacket->m_ParamSize);
            amiDebugLog("Error: Data size is too small.(readSize:%04X, m_paramSize:%04X)",
                        nReadSize, (lpPacket->m_ParamSize << 8 | lpPacket->m_ParamSize >> 8) - 4);
            return -11;
        }

        if (4 < paramSize) {
            while (readPosition < paramSize) {
                unsigned short blockSize = paramSize - readPosition;
                if (blockSize > 4) blockSize = 4;

                if (blockSize == 1)
                    err = mxkSmbusReadByte(addr, buffer + readPosition, 0xc3);
                else
                    err = mxkSmbusI2CReadBlock(addr, 0xc3, buffer + readPosition, blockSize & 0xff);

                if (err != 0) {
                    amiDebugLog("Error: Data read failed. Position %d. ErrorCode %d.", readPosition,
                                err);
                    break;
                }

                readPosition += blockSize;
            }
        }
    }

    *lpBytesRead = readPosition;
    if (mxkSmbusReleaseMutex() != 0) {
        amiDebugLog("Error: ReleaseMutex(). ErrorCode %d.", GetLastError());
        return -6;
    }
    if (err) return -1;
    return 0;
}

bool mxkN2CmdIsValidOutParam(N2Packet_t *packet, unsigned tag, unsigned short paramSize) {
    unsigned short tagPacket = _byteswap_ushort(packet->m_Tag);
    unsigned short paramSizePacket = _byteswap_ushort(packet->m_ParamSize);
    unsigned short returnCode = _byteswap_ushort(packet->m_Command);
    if (tagPacket == tag && paramSizePacket == paramSize && returnCode == 0) return true;

    amiDebugLog("tag: %04x, paramSize: %d, returnCode: %04x", tagPacket, paramSizePacket,
                returnCode);
    return false;
}

int mxkN2CmdReadResponce(PN2Nonce_t requestNonce, PHmacSum_t hmacSalt, N2Tag_t tag,
                         N2Command_t command, PHmacKey_t hmacKey, unsigned char addr, void *bufOut,
                         unsigned short nOut) {
    int ret;
    unsigned short nBytesRead;
    Sha1Sum_t sha1sum;
    N2Packet_t packet;

    ZeroMemory(&packet, sizeof packet);
    unsigned short footerSize =
        tag == N2_TAG_RQU_COMMAND ? N2_FOOTER_RQU_SIZE : N2_FOOTER_SIGN_SIZE;
    ret = mxkN2CmdReadData(addr, &packet, nOut + N2_HEADER_SIZE + footerSize, &nBytesRead);
    if (ret != 0) {
        amiDebugLog("Error: Data read failed. ErrorCode %d.", ret);
        return -7;
    }

    if (!mxkN2CmdIsValidOutParam(
            &packet, tag == N2_TAG_RQU_COMMAND ? N2_TAG_RSP_COMMAND : N2_TAG_RSP_AUTH_COMMAND,
            nBytesRead)) {
        amiDebugLog("Error: Invalid output parameter.");
        return -9;
    }

    unsigned short fixedCommand = _byteswap_ushort(command);

    mxkN2UtilSha1Many(&sha1sum, 3, &packet, N2_HEADER_SIZE, &fixedCommand, sizeof fixedCommand,
                      packet.m_Body, nOut);

    N2PacketFooter_t *footer = (N2PacketFooter_t *)(packet.m_Body + nOut);

    if (tag == N2_TAG_RQU_COMMAND) {
        if (memcmp(footer->rqu.m_ShaSum, sha1sum, sizeof sha1sum) != 0) {
            amiDebugLog("Error: Invalid hash value.");
            return -10;
        }
    } else {
        if (requestNonce == NULL || hmacKey == NULL) return -2;

        HmacSum_t computedHash;
        mxkN2UtilHmacPacket(&sha1sum, &footer->sign.m_Nonce, requestNonce, hmacKey, &computedHash);
        if (memcmp(footer->sign.m_Hmac, computedHash, sizeof computedHash) != 0) {
            amiDebugLog("Error: Invalid hash value.");
            return -10;
        }
        memcpy_s(requestNonce, sizeof *requestNonce, packet.m_Body + nOut, 20);

        if (hmacSalt != NULL)
            memcpy_s(hmacSalt, sizeof *hmacSalt, &footer->sign.m_Nonce,
                     sizeof footer->sign.m_Nonce);
    }

    if (bufOut != NULL && nOut != 0) {
        memcpy_s(bufOut, nOut, packet.m_Body,
                 ((nBytesRead - footerSize) - N2_HEADER_SIZE) & 0xffff);
    }
    return 0;
}

int mxkN2CmdEnableSession(void) {
    N2Nonce_t nonce;

    CheckN2Init;

    int err;
    err = mxkN2CmdWriteCommand(&nonce, NULL, N2_TAG_RQU_COMMAND, N2_ORD_ENABLE_SESSION, NULL,
                               N2DeviceInfo.m_Addr, NULL, 0);
    if (err != 0) return err;

    Sleep(32);
    err = mxkN2CmdReadResponce(&nonce, NULL, N2_TAG_RQU_COMMAND, N2_ORD_ENABLE_SESSION, NULL,
                               N2DeviceInfo.m_Addr, &N2DeviceInfo.m_HmacSalt, 20);
    if (err != 0) return err;
    Sleep(16);
    return 0;
}

int mxkN2CmdGetErrorCode(unsigned short *errorCode) {
    N2Nonce_t nonce;

    CheckN2Init;

    int err;
    err = mxkN2CmdWriteCommand(&nonce, NULL, N2_TAG_RQU_COMMAND, N2_ORD_GET_ERROR_CODE, NULL,
                               N2DeviceInfo.m_Addr, NULL, 0);
    if (err != 0) return err;

    Sleep(32);
    unsigned short rawErrorCode;
    err = mxkN2CmdReadResponce(&nonce, NULL, N2_TAG_RQU_COMMAND, N2_ORD_GET_ERROR_CODE, NULL,
                               N2DeviceInfo.m_Addr, &rawErrorCode, 2);
    if (err != 0) return err;

    Sleep(16);
    *errorCode = _byteswap_ushort(rawErrorCode);
    return 0;
}

int mxkN2CmdSetAuthKey(PN2AuthKey_t authKey) {
    CheckN2Init;

    unsigned char authKeyPt[32];
    ZeroMemory(authKeyPt, sizeof authKeyPt);
    unsigned char authKeyCt[32];
    memcpy_s(authKeyPt, sizeof authKeyPt, authKey, sizeof *authKey);
    mxkCryptEncryptAes128CBC(N2DeviceInfo.m_AESKey.m_Key, N2DeviceInfo.m_AESKey.m_IV, authKeyCt,
                             authKeyPt, sizeof authKeyPt);

    N2Nonce_t nonce;
    int err;
    err = mxkN2CmdWriteCommand(&nonce, &N2DeviceInfo.m_HmacSalt, N2_TAG_RQU_AUTH_COMMAND,
                               N2_ORD_SET_AUTH_KEY, &N2DeviceInfo.m_HmacKey, N2DeviceInfo.m_Addr,
                               authKeyCt, sizeof authKeyCt);
    if (err != 0) return err;

    Sleep(96);
    err = mxkN2CmdReadResponce(&nonce, &N2DeviceInfo.m_HmacSalt, N2_TAG_RQU_AUTH_COMMAND,
                               N2_ORD_SET_AUTH_KEY, &N2DeviceInfo.m_HmacKey, N2DeviceInfo.m_Addr,
                               NULL, 0);
    if (err != 0) return err;
    Sleep(16);
    memcpy_s(N2DeviceInfo.m_AuthKey, sizeof N2DeviceInfo.m_AuthKey, authKey, sizeof *authKey);
    return 0;
}

int mxkN2CmdSetEncKey(AESKey_t *encKey) {
    CheckN2Init;

    unsigned char encKeyCt[sizeof *encKey];
    mxkCryptEncryptAes128CBC(N2DeviceInfo.m_AESKey.m_Key, N2DeviceInfo.m_AESKey.m_IV, encKeyCt,
                             encKey, sizeof encKeyCt);

    N2Nonce_t requestNonce;
    int err;
    err = mxkN2CmdWriteCommand(&requestNonce, &N2DeviceInfo.m_HmacSalt, N2_TAG_RQU_AUTH_COMMAND,
                               N2_ORD_SET_ENC_KEY, &N2DeviceInfo.m_AuthKey, N2DeviceInfo.m_Addr,
                               encKeyCt, sizeof encKeyCt);
    if (err != 0) return err;

    Sleep(96);
    err = mxkN2CmdReadResponce(&requestNonce, &N2DeviceInfo.m_HmacSalt, N2_TAG_RQU_AUTH_COMMAND,
                               N2_ORD_SET_ENC_KEY, &N2DeviceInfo.m_AuthKey, N2DeviceInfo.m_Addr,
                               NULL, 0);
    if (err != 0) return err;

    Sleep(16);
    memcpy_s(&N2DeviceInfo.m_EncKey, sizeof N2DeviceInfo.m_EncKey, encKey, sizeof *encKey);
    return 0;
}

int mxkN2CmdGetAuthLevel(unsigned char *authLevel) {
    CheckN2Init;

    unsigned char rawAuthlevel;
    N2Nonce_t nonce;

    int err;
    err = mxkN2CmdWriteCommand(&nonce, NULL, N2_TAG_RQU_COMMAND, N2_ORD_GET_AUTH_LEVEL, NULL,
                               N2DeviceInfo.m_Addr, NULL, 0);
    if (err != 0) return err;
    Sleep(32);
    err = mxkN2CmdReadResponce(&nonce, NULL, N2_TAG_RQU_COMMAND, N2_ORD_GET_AUTH_LEVEL, NULL,
                               N2DeviceInfo.m_Addr, &rawAuthlevel, 1);
    if (err != 0) return err;
    Sleep(16);
    err = 0;
    *authLevel = rawAuthlevel;
    return 0;
}

int mxkN2GetErrorCode(void) {
    unsigned short errorCode;

    int err = mxkN2CmdGetErrorCode(&errorCode);
    if (err != 0) {
        amiDebugLog("Error: mxkN2CmdGetErrorCode(). ErrorCode %d", err);
        return -5;
    }

    amiDebugLog("N2 ErrorCode 0x%04X", errorCode);
    return 0;
}
int mxkN2CmdReadKeychipID(N2KeychipId_t *lpKeychipId) {
    CheckN2Init;

    N2Nonce_t nonce;

    unsigned char readData[sizeof *lpKeychipId + 2];

    N2Byte_t request[4];
    request[0] = 0;
    request[1] = 0;
    request[2] = 0;
    request[3] = sizeof *lpKeychipId;

    int err;
    err = mxkN2CmdWriteCommand(&nonce, &N2DeviceInfo.m_HmacSalt, N2_TAG_RQU_AUTH_COMMAND,
                               N2_ORD_READ_KEYCHIP_ID, &N2DeviceInfo.m_AuthKey, N2DeviceInfo.m_Addr,
                               request, sizeof request);
    if (err != 0) return err;
    Sleep(128);
    err = mxkN2CmdReadResponce(&nonce, &N2DeviceInfo.m_HmacSalt, N2_TAG_RQU_AUTH_COMMAND,
                               N2_ORD_READ_KEYCHIP_ID, &N2DeviceInfo.m_AuthKey, N2DeviceInfo.m_Addr,
                               readData, sizeof readData);

    if (err != 0) return err;
    Sleep(16);

    mxkCryptDecryptAes128CBC(N2DeviceInfo.m_EncKey.m_Key, N2DeviceInfo.m_EncKey.m_IV, readData + 2,
                             (unsigned char *)lpKeychipId, sizeof *lpKeychipId);
    return 0;
}

int mxkN2Authentication(void) {
    N2AuthKey_t authKey;
    AESKey_t enc_key;

    if (!MXK_HAS_INIT) {
        amiDebugLog("No Init Error!!");
        return -3;
    }

    int err;
    err = mxkN2CmdEnableSession();
    if (err != 0) {
        mxkN2GetErrorCode();
        amiDebugLog("Error: mxkN2CmdEnableSession(). ErrorCode %d", err);
        return -5;
    }

    unsigned char authLevel;
    mxkN2CmdGetAuthLevel(&authLevel);
    printf("Achieved auth level: %d\n", authLevel);

    _randomBytes(authKey, sizeof authKey);
    err = mxkN2CmdSetAuthKey(&authKey);
    if (err != 0) {
        mxkN2GetErrorCode();
        amiDebugLog("Error: mxkN2CmdSetAuthKey(). ErrorCode %d", err);
        return -5;
    }

    mxkN2CmdGetAuthLevel(&authLevel);
    printf("Achieved auth level: %d\n", authLevel);

    _randomBytes(&enc_key, sizeof enc_key);
    err = mxkN2CmdSetEncKey(&enc_key);
    if (err != 0) {
        mxkN2GetErrorCode();
        amiDebugLog("Error: mxkN2CmdSetEncKey().ErrorCode %d", err);
        return -5;
    }

    mxkN2CmdGetAuthLevel(&authLevel);
    printf("Achieved auth level: %d\n", authLevel);

    // unsigned char authLevel;
    err = mxkN2CmdGetAuthLevel(&authLevel);
    if (err == 0 && authLevel == 3) return 0;

    mxkN2GetErrorCode();
    amiDebugLog("Error: mxkN2CmdGetAuthLevel().ErrorCode %d", err);
    return -5;
}

void mxkN2Exit(void) { MXK_N2_INIT = FALSE; }

int mxkN2Init(void) {
    int err;
    err = mxkN2CmdSetDeviceInfo();
    if (err != 0) return err;

    err = mxkN2Authentication();
    if (err != 0) {
        unsigned short errorCode;
        err = mxkN2CmdGetErrorCode(&errorCode);
        if (err != 0) {
            amiDebugLog("Error: mxkN2CmdGetErrorCode(). ErrorCode %d", err);
        } else {
            amiDebugLog("N2 ErrorCode 0x%04X", errorCode);
        }
        return -1;
    }
    return 0;
}
