/*
`PBKDF2` password-based key derivation function.

See: [[ https://www.rfc-editor.org/rfc/rfc2898 ]]
*/
package pbkdf2

import "core:crypto"
import "core:crypto/hash"
import "core:crypto/hmac"
import "core:encoding/endian"

// derive invokes PBKDF2-HMAC with the specified hash algorithm, password,
// salt, iteration count, and outputs the derived key to dst.
derive :: proc(
	hmac_hash: hash.Algorithm,
	password: []byte,
	salt: []byte,
	iterations: u32,
	dst: []byte,
) {
	h_len := hash.DIGEST_SIZES[hmac_hash]

	// 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long"
	// and stop.

	dk_len := len(dst)
	switch {
	case dk_len == 0:
		return
	case u64(dk_len) > u64(max(u32)) * u64(h_len):
		// This is so beyond anything that is practical or reasonable,
		// so just panic instead of returning an error.
		panic("crypto/pbkdf2: derived key too long")
	case:
	}

	// 2. Let l be the number of hLen-octet blocks in the derived key,
	// rounding up, and let r be the number of octets in the last block.

	l := dk_len / h_len // Don't need to round up.
	r := dk_len % h_len

	// 3. For each block of the derived key apply the function F defined
	// below to the password P, the salt S, the iteration count c, and
	// the block index to compute the block.
	//
	// 4. Concatenate the blocks and extract the first dkLen octets to
	// produce a derived key DK.
	//
	// 5. Output the derived key DK.

	// Each iteration of F is always `PRF (P, ...)`, so instantiate the
	// PRF, and clone since memcpy is faster than having to re-initialize
	// HMAC repeatedly.

	base: hmac.Context
	defer hmac.reset(&base)

	hmac.init(&base, hmac_hash, password)

	// Process all of the blocks that will be written directly to dst.
	dst_blk := dst
	for i in 1 ..= l { 	// F expects i starting at 1.
		_F(&base, salt, iterations, u32(i), dst_blk[:h_len])
		dst_blk = dst_blk[h_len:]
	}

	// Instead of rounding l up, just proceass the one extra block if and only if (⟺)
	// r != 0.
	if r > 0 {
		tmp: [hash.MAX_DIGEST_SIZE]byte
		blk := tmp[:h_len]
		defer crypto.zero_explicit(raw_data(blk), h_len)

		_F(&base, salt, iterations, u32(l + 1), blk)
		copy(dst_blk, blk)
	}
}

@(private)
_F :: proc(base: ^hmac.Context, salt: []byte, c: u32, i: u32, dst_blk: []byte) {
	h_len := len(dst_blk)

	tmp: [hash.MAX_DIGEST_SIZE]byte
	u := tmp[:h_len]
	defer crypto.zero_explicit(raw_data(u), h_len)

	// F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
	//
	// where
	//
	// U_1 = PRF (P, S || INT (i)) ,
	// U_2 = PRF (P, U_1) ,
	// ...
	// U_c = PRF (P, U_{c-1}) .
	//
	// Here, INT (i) is a four-octet encoding of the integer i, most
	// significant octet first.

	prf: hmac.Context

	// U_1: PRF (P, S || INT (i))
	hmac.clone(&prf, base)
	hmac.update(&prf, salt)
	endian.unchecked_put_u32be(u, i) // Use u as scratch space.
	hmac.update(&prf, u[:4])
	hmac.final(&prf, u)
	copy(dst_blk, u)

	// U_2 ... U_c: U_n = PRF (P, U_(n-1))
	for _ in 1 ..< c {
		hmac.clone(&prf, base)
		hmac.update(&prf, u)
		hmac.final(&prf, u)

		// XOR dst_blk and u.
		for v, i in u {
			dst_blk[i] ~= v
		}
	}
}
