/*******************************************************************************
 * Copyright (c) 2016 Matthijs Kooijman
 *
 * LICENSE
 *
 * Permission is hereby granted, free of charge, to anyone
 * obtaining a copy of this document and accompanying files,
 * to do whatever they want with them without any restriction,
 * including, but not limited to, copying, modification and
 * redistribution.
 *
 * NO WARRANTY OF ANY KIND IS PROVIDED.
 *******************************************************************************/

/*
 * The original LMIC AES implementation integrates raw AES encryption
 * with CMAC and AES-CTR in a single piece of code. Most other AES
 * implementations (only) offer raw single block AES encryption, so this
 * file contains an implementation of CMAC and AES-CTR, and offers the
 * same API through the os_aes() function as the original AES
 * implementation. This file assumes that there is an encryption
 * function available with this signature:
 *
 *      extern "C" void lmic_aes_encrypt(u1_t *data, u1_t *key);
 *
 *  That takes a single 16-byte buffer and encrypts it wit the given
 *  16-byte key.
 */

#include "../lmic/oslmic.h"

#if !defined(USE_ORIGINAL_AES)

// This should be defined elsewhere
void lmic_aes_encrypt(u1_t *data, u1_t *key);

// global area for passing parameters (aux, key)
u4_t AESAUX[16/sizeof(u4_t)];
u4_t AESKEY[16/sizeof(u4_t)];

// Shift the given buffer left one bit
static void shift_left(xref2u1_t buf, u1_t len) {
    while (len--) {
        u1_t next = len ? buf[1] : 0;

        u1_t val = (*buf << 1);
        if (next & 0x80)
            val |= 1;
        *buf++ = val;
    }
}

// Apply RFC4493 CMAC, using AESKEY as the key. If prepend_aux is true,
// AESAUX is prepended to the message. AESAUX is used as working memory
// in any case. The CMAC result is returned in AESAUX as well.
static void os_aes_cmac(xref2u1_t buf, u2_t len, u1_t prepend_aux) {
    if (prepend_aux)
        lmic_aes_encrypt(AESaux, AESkey);
    else
        memset (AESaux, 0, 16);

    while (len > 0) {
        u1_t need_padding = 0;
        for (u1_t i = 0; i < 16; ++i, ++buf, --len) {
            if (len == 0) {
                // The message is padded with 0x80 and then zeroes.
                // Since zeroes are no-op for xor, we can just skip them
                // and leave AESAUX unchanged for them.
                AESaux[i] ^= 0x80;
                need_padding = 1;
                break;
            }
            AESaux[i] ^= *buf;
        }

        if (len == 0) {
            // Final block, xor with K1 or K2. K1 and K2 are calculated
            // by encrypting the all-zeroes block and then applying some
            // shifts and xor on that.
            u1_t final_key[16];
            memset(final_key, 0, sizeof(final_key));
            lmic_aes_encrypt(final_key, AESkey);

            // Calculate K1
            u1_t msb = final_key[0] & 0x80;
            shift_left(final_key, sizeof(final_key));
            if (msb)
                final_key[sizeof(final_key)-1] ^= 0x87;

            // If the final block was not complete, calculate K2 from K1
            if (need_padding) {
                msb = final_key[0] & 0x80;
                shift_left(final_key, sizeof(final_key));
                if (msb)
                    final_key[sizeof(final_key)-1] ^= 0x87;
            }

            // Xor with K1 or K2
            for (u1_t i = 0; i < sizeof(final_key); ++i)
                AESaux[i] ^= final_key[i];
        }

        lmic_aes_encrypt(AESaux, AESkey);
    }
}

// Run AES-CTR using the key in AESKEY and using AESAUX as the
// counter block. The last byte of the counter block will be incremented
// for every block. The given buffer will be encrypted in place.
static void os_aes_ctr (xref2u1_t buf, u2_t len) {
    u1_t ctr[16];
    while (len) {
        // Encrypt the counter block with the selected key
        memcpy(ctr, AESaux, sizeof(ctr));
        lmic_aes_encrypt(ctr, AESkey);

        // Xor the payload with the resulting ciphertext
        for (u1_t i = 0; i < 16 && len > 0; i++, len--, buf++)
            *buf ^= ctr[i];

        // Increment the block index byte
        AESaux[15]++;
    }
}

u4_t os_aes (u1_t mode, xref2u1_t buf, u2_t len) {
    switch (mode & ~AES_MICNOAUX) {
        case AES_MIC:
            os_aes_cmac(buf, len, /* prepend_aux */ !(mode & AES_MICNOAUX));
            return os_rmsbf4(AESaux);

        case AES_ENC:
            // TODO: Check / handle when len is not a multiple of 16
            for (u1_t i = 0; i < len; i += 16)
                lmic_aes_encrypt(buf+i, AESkey);
            break;

        case AES_CTR:
            os_aes_ctr(buf, len);
            break;
    }
    return 0;
}

#endif // !defined(USE_ORIGINAL_AES)
