# frozen_string_literal: true

# (c) 2017 Ribose Inc.

require 'ffi'

require 'botan/defaults'
require 'botan/utils'

module Botan
  # Key Derivation Functions
  #
  # == Examples
  # === examples/kdf.rb
  # {include:file:examples/kdf.rb}
  module KDF
    # Derives a key using the given KDF algorithm.
    #
    # @param secret [String] the secret input
    # @param key_length [Integer] the desired length of the key to produce
    # @param label [String] purpose for the derived keying material
    # @param algo [String] the KDF algorithm name
    # @param salt [String] the randomly chosen salt
    # @return [String] the derived key
    def self.kdf(secret:, key_length:,
                 label: '',
                 algo: DEFAULT_KDF_ALGO,
                 salt: RNG.get(DEFAULT_KDF_SALT_LENGTH))
      out_buf = FFI::MemoryPointer.new(:uint8, key_length)

      secret_buf = FFI::MemoryPointer.from_data(secret)
      salt_buf = FFI::MemoryPointer.from_data(salt)
      label_buf = FFI::MemoryPointer.from_data(label)
      Botan.call_ffi(:botan_kdf,
                     algo, out_buf, out_buf.size,
                     secret_buf, secret_buf.size,
                     salt_buf, salt_buf.size,
                     label_buf, label_buf.size)
      out_buf.read_bytes(key_length)
    end

    # Derives a key using the given PBKDF algorithm.
    #
    # @param password [String] the password to derive the key from
    # @param key_length [Integer] the desired length of the key to produce
    # @param algo [String] the PBKDF algorithm name
    # @param iterations [Integer] the number of iterations to use
    # @param salt [String] the randomly chosen salt
    # @return [String] the derived key
    def self.pbkdf(password:, key_length:,
                   algo: DEFAULT_PBKDF_ALGO,
                   iterations: DEFAULT_KDF_ITERATIONS,
                   salt: RNG.get(DEFAULT_KDF_SALT_LENGTH))
      out_buf = FFI::MemoryPointer.new(:uint8, key_length)
      salt_buf = FFI::MemoryPointer.from_data(salt)
      Botan.call_ffi(:botan_pbkdf,
                     algo, out_buf, key_length,
                     password, salt_buf, salt_buf.size, iterations)
      out_buf.read_bytes(key_length)
    end

    # Derives a key using the given PBKDF algorithm.
    #
    # @param password [String] the password to derive the key from
    # @param key_length [Integer] teh desired length of the key to rpoduce
    # @param milliseconds [Integer] the number of milliseconds to run
    # @param algo [String] the PBKDF algorithm name
    # @param salt [String] the randomly chosen salt
    # @return [Hash<Symbol>]
    #   * :iterations [Integer] the iteration count used
    #   * :key [String] the derived key
    def self.pbkdf_timed(password:, key_length:, milliseconds:,
                         algo: DEFAULT_PBKDF_ALGO,
                         salt: RNG.get(DEFAULT_KDF_SALT_LENGTH))
      out_buf = FFI::MemoryPointer.new(:uint8, key_length)
      salt_buf = FFI::MemoryPointer.from_data(salt)
      iterations_ptr = FFI::MemoryPointer.new(:size_t)
      Botan.call_ffi(:botan_pbkdf_timed,
                     algo, out_buf, key_length,
                     password, salt_buf, salt_buf.size,
                     milliseconds, iterations_ptr)
      { iterations: iterations_ptr.read(:size_t),
        key: out_buf.read_bytes(key_length) }
    end
  end # module
end # module