
--BLAKE2 ffi binding.
--Writen by Cosmin Apreutesei. Public Domain.

local ffi = require'ffi'
local C = ffi.load'blake2'
local M = {C = C}

ffi.cdef[[
enum {
	BLAKE2S_BLOCKBYTES = 64,
	BLAKE2S_OUTBYTES   = 32,
	BLAKE2S_KEYBYTES   = 32,
	BLAKE2S_SALTBYTES  = 8,
	BLAKE2S_PERSONALBYTES = 8,
	BLAKE2B_BLOCKBYTES = 128,
	BLAKE2B_OUTBYTES   = 64,
	BLAKE2B_KEYBYTES   = 64,
	BLAKE2B_SALTBYTES  = 16,
	BLAKE2B_PERSONALBYTES = 16
};

#pragma pack(push, 1)
typedef struct __blake2s_param
{
	uint8_t  digest_length; // 1
	uint8_t  key_length;    // 2
	uint8_t  fanout;        // 3
	uint8_t  depth;         // 4
	uint32_t leaf_length;   // 8
	uint8_t  node_offset[6];// 14
	uint8_t  node_depth;    // 15
	uint8_t  inner_length;  // 16
	// uint8_t  reserved[0];
	uint8_t  salt[BLAKE2S_SALTBYTES]; // 24
	uint8_t  personal[BLAKE2S_PERSONALBYTES];  // 32
} blake2s_param;

typedef struct __blake2s_state
{
	uint32_t h[8];
	uint32_t t[2];
	uint32_t f[2];
	uint8_t  buf[2 * BLAKE2S_BLOCKBYTES];
	uint32_t buflen;
	uint8_t  outlen;
	uint8_t  last_node;
} blake2s_state;

typedef struct __blake2b_param
{
	uint8_t  digest_length; // 1
	uint8_t  key_length;    // 2
	uint8_t  fanout;        // 3
	uint8_t  depth;         // 4
	uint32_t leaf_length;   // 8
	uint64_t node_offset;   // 16
	uint8_t  node_depth;    // 17
	uint8_t  inner_length;  // 18
	uint8_t  reserved[14];  // 32
	uint8_t  salt[BLAKE2B_SALTBYTES]; // 48
	uint8_t  personal[BLAKE2B_PERSONALBYTES];  // 64
} blake2b_param;

typedef struct __blake2b_state
{
	uint64_t h[8];
	uint64_t t[2];
	uint64_t f[2];
	uint8_t  buf[2 * BLAKE2B_BLOCKBYTES];
	uint32_t buflen;
	uint8_t  outlen;
	uint8_t  last_node;
} blake2b_state;

typedef struct __blake2sp_state
{
	blake2s_state S[8][1];
	blake2s_state R[1];
	uint8_t  buf[8 * BLAKE2S_BLOCKBYTES];
	uint32_t buflen;
	uint8_t  outlen;
} blake2sp_state;

typedef struct __blake2bp_state
{
	blake2b_state S[4][1];
	blake2b_state R[1];
	uint8_t  buf[4 * BLAKE2B_BLOCKBYTES];
	uint32_t buflen;
	uint8_t  outlen;
} blake2bp_state;
#pragma pack(pop)

// Streaming API

int blake2s_init       ( blake2s_state *S, size_t outlen );
int blake2s_init_key   ( blake2s_state *S, size_t outlen, const void *key, size_t keylen );
int blake2s_init_param ( blake2s_state *S, const blake2s_param *P );
int blake2s_update     ( blake2s_state *S, const uint8_t *in, size_t inlen );
int blake2s_final      ( blake2s_state *S, uint8_t *out, size_t outlen );

int blake2b_init       ( blake2b_state *S, size_t outlen );
int blake2b_init_key   ( blake2b_state *S, size_t outlen, const void *key, size_t keylen );
int blake2b_init_param ( blake2b_state *S, const blake2b_param *P );
int blake2b_update     ( blake2b_state *S, const uint8_t *in, size_t inlen );
int blake2b_final      ( blake2b_state *S, uint8_t *out, size_t outlen );

int blake2sp_init      ( blake2sp_state *S, size_t outlen );
int blake2sp_init_key  ( blake2sp_state *S, size_t outlen, const void *key, size_t keylen );
int blake2sp_update    ( blake2sp_state *S, const uint8_t *in, size_t inlen );
int blake2sp_final     ( blake2sp_state *S, uint8_t *out, size_t outlen );

int blake2bp_init      ( blake2bp_state *S, size_t outlen );
int blake2bp_init_key  ( blake2bp_state *S, size_t outlen, const void *key, size_t keylen );
int blake2bp_update    ( blake2bp_state *S, const uint8_t *in, size_t inlen );
int blake2bp_final     ( blake2bp_state *S, uint8_t *out, size_t outlen );

// Simple API

int blake2s  ( uint8_t *out, const void *in, const void *key, size_t outlen, size_t inlen, size_t keylen );
int blake2b  ( uint8_t *out, const void *in, const void *key, size_t outlen, size_t inlen, size_t keylen );
int blake2sp ( uint8_t *out, const void *in, const void *key, size_t outlen, size_t inlen, size_t keylen );
int blake2bp ( uint8_t *out, const void *in, const void *key, size_t outlen, size_t inlen, size_t keylen );
]]

local _ = string.format

local function check(ret)
	if ret == 0 then return end
	error('blake2 error '..ret)
end

local outlens = {
	s  = C.BLAKE2S_OUTBYTES,
	b  = C.BLAKE2B_OUTBYTES,
	sp = C.BLAKE2S_OUTBYTES,
	bp = C.BLAKE2B_OUTBYTES,
}
local blocklens = {
	s  = C.BLAKE2S_BLOCKBYTES,
	b  = C.BLAKE2B_BLOCKBYTES,
	sp = C.BLAKE2S_BLOCKBYTES,
	bp = C.BLAKE2B_BLOCKBYTES,
}
local parambufs = {
	s = ffi.new'blake2s_param',
	b = ffi.new'blake2b_param',
}
parambufs.sp = parambufs.s
parambufs.bp = parambufs.b

local outbuf = ffi.new('uint8_t[?]', C.BLAKE2B_BLOCKBYTES)

local function copystr(buf, s, maxsz)
	ffi.fill(buf, maxsz)
	if s and #s > 0 then
		ffi.copy(buf, s, math.min(#s, maxsz))
	end
end

local function mkdigest(V)

	local P          = not V:find'p'
	local state_ct   = ffi.typeof(_('blake2%s_state', V))
	local init       = C[_('blake2%s_init', V)]
	local init_key   = C[_('blake2%s_init_key', V)]
	local init_param = P and C[_('blake2%s_init_param', V)]
	local update     = C[_('blake2%s_update', V)]
	local final      = C[_('blake2%s_final', V)]
	local param      = P and parambufs[V]
	local max_outlen = outlens[V]
	local blocklen   = blocklens[V]

	local state = {}
	state.__index = state

	function state.reset(S, key, outlen)
		if type(key) == 'table' then
			if not P then
				error(_('options table not supported with blake2%s', V))
			end
			local t = key
			param.digest_length = t.hash_length or max_outlen
			param.key_length = t.key and #t.key or 0
			param.fanout = t.fanout or 1
			param.depth = t.depth or 1
			param.leaf_length = t.leaf_length or 0
			if ffi.istype('uint64_t', param.node_offset) then
				param.node_offset = t.node_offset or 0
			else --48-bit uint
				ffi.cast('uint64_t*', outbuf)[0] = t.node_offset or 0
				ffi.copy(param.node_offset, outbuf, 6) --assuming little endian
			end
			param.node_depth = t.node_depth or 0
			param.inner_length = t.inner_length or 0
			copystr(param.salt, t.salt, ffi.sizeof(param.salt))
			copystr(param.personal, t.personal, ffi.sizeof(param.personal))
			check(init_param(S, param))
			if t.key then
				copystr(outbuf, t.key, blocklen)
				update(S, outbuf, blocklen)
				copystr(outbuf, nil, blocklen)
			end
		elseif key then
			check(init_key(S, outlen or max_outlen, key, #key))
		else
			check(init(S, outlen or max_outlen))
		end
	end

	function state.update(S, data, size)
		check(update(S, data, size or #data))
	end

	function state.length(S)
		return S.outlen
	end

	function state.final_to_buffer(S, outbuf)
		check(final(S, outbuf, S.outlen))
	end

	function state.final(S)
		check(final(S, outbuf, S.outlen))
		return ffi.string(outbuf, S.outlen)
	end

	function state:__call(data, size)
		if data then
			self:update(data, size)
		else
			return self:final()
		end
	end

	ffi.metatype(state_ct, state)

	return function(key)
		local S = state_ct()
		S:reset(key)
		return S
	end
end

local function mksum(V)
	local sum = C[_('blake2%s', V)]
	local max_outlen = outlens[V]
	return function(data, size, key, outlen)
		outlen = outlen or max_outlen
		check(sum(outbuf, data, key, outlen, size or #data, key and #key or 0))
		return ffi.string(outbuf, outlen)
	end
end

for i,V in ipairs{'s', 'b', 'sp', 'bp'} do
	M[_('blake2%s_digest', V)] = mkdigest(V)
	M[_('blake2%s', V)] = mksum(V)
end


if not ... then

	local blake2 = M
	local glue = require'glue'

	for _,V in ipairs{'s', 'b', 'sp', 'bp'} do
		local d, k, h
		local n = 0
		for s in io.lines(string.format('media/blake2/blake2%s-test.txt', V)) do
			d = s:match'^in:%s*(.*)' or d
			k = s:match'^key:%s*(.*)' or k
			h = s:match'^hash:%s*(.*)' or h
			if s:match'^hash:' then
				d = glue.fromhex(d)
				k = glue.fromhex(k)
				h = glue.fromhex(h:gsub('ok', ''))

				--test simple API
				assert(blake2['blake2'..V](d, nil, k) == h)

				--test streaming API / blake2x_init_key() branch
				local digest = blake2['blake2'..V..'_digest'](k); digest(d)
				assert(digest() == h)

				--test streaming API / blake2x_init_param() branch
				if not V:find'p' then
					local digest = blake2['blake2'..V..'_digest']({key = k})
					digest(d)
					assert(digest() == h)
				end

				n = n + 1
			end
		end
		print(V, n..' tests passed')
	end

end


return M
