/*
    See license.txt in the root of this project.
*/

# include "luametatex.h"

# include <utilcrypt.h>

// AES_HAS_IV AES_INLINE_IV AES_CONTINUE AES_NULL_PADDING

static const uint8_t nulliv[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

typedef size_t aes_coder (
    const void *input,
    size_t      length,
    void       *output,
    const void *key,
    size_t      keylength,
    const void *iv,
    int         flags
);

/* data key [block] [inline] [padding] */ /* key : 16 24 32 */

/* random_bytes is taken from pplib */

static int aeslib_aux_code(lua_State *L, aes_coder code) {
    size_t inputlength = 0;
    const char *input = lua_tolstring(L, 1, &inputlength);
    if (inputlength) {
        size_t keylength = 0;
        const char *key = lua_tolstring(L, 2, &keylength);
        if (keylength == 16 || keylength == 24 || keylength == 32) {
            luaL_Buffer buffer;
            /* always */
            int flags = 0;
            /* the same length as input plus optional 16 from iv */
            char *output = NULL;
            size_t outputlength = 0;
            /* this is optional, iv get copied in aes */
            const uint8_t *iv = NULL;
            uint8_t randiv[16];
            switch (lua_type(L, 3)) {
                case LUA_TSTRING:
                    {
                        size_t ivlength = 0;
                        iv = (const uint8_t *) lua_tolstring(L, 3, &ivlength);
                        if (ivlength != 16) {
                            iv = nulliv;
                        }
                        break;
                    }
                case LUA_TBOOLEAN:
                    if (lua_toboolean(L, 3)) {
                        random_bytes(randiv, 16);
                        iv = (const uint8_t *) randiv;
                        break;
                    }
                    // fall through
                default:
                    iv = nulliv;
            }
            if (lua_toboolean(L, 4)) {
               flags |= AES_INLINE_IV;
            }
            if (! lua_toboolean(L, 5)) {
               flags |= AES_NULL_PADDING;
            }
            /* always multiples of 16 and we might have the iv too */
            output = luaL_buffinitsize(L, &buffer, inputlength + 32);
            outputlength = code(input, inputlength, output, key, keylength, iv, flags);
            if (outputlength) {
                luaL_pushresultsize(&buffer, outputlength);
                return 1;
            }
        } else {
            luaL_error(L, "aeslib: key of length 16, 24 or 32 expected");
        }
    }
    lua_pushnil(L);
    return 1;
}

static int aeslib_encode(lua_State *L) {
    return aeslib_aux_code(L, &aes_encode_data);
}

static int aeslib_decode(lua_State *L) {
    return aeslib_aux_code(L, &aes_decode_data);
}

static int aeslib_random(lua_State *L) {
    uint8_t iv[32];
    int n = lmt_optinteger(L, 1, 16);
    if (n > 32) {
        n = 32;
    }
    random_bytes(iv, n);
    lua_pushlstring(L, (const char *) iv, n);
    return 1;
}

static struct luaL_Reg aeslib_function_list[] = {
    /*tex We started out with this: */
    { "encode", aeslib_encode },
    { "decode", aeslib_decode },
    { "random", aeslib_random },
    { NULL,     NULL          },
};

int luaopen_aes(lua_State *L) {
    lua_newtable(L);
    luaL_setfuncs(L, aeslib_function_list, 0);
    return 1;
}
