/*-
 * Scram-rs - a SCRAM authentification authorization library
 * 
 * Copyright (C) 2021  Aleksandr Morozov
 * Copyright (C) 2025 Aleksandr Morozov
 * 
 * The syslog-rs crate can be redistributed and/or modified
 * under the terms of either of the following licenses:
 *
 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
 *
 *   2. The MIT License (MIT)
 *                     
 *   3. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
 */

#ifndef _SCRAMRS_H_
#define _SCRAMRS_H_

#include "scramrs_defs.h"

#ifndef CAPI_SCRAM_NOCAPI

/**
  * DO NOT DEALLOCATE MEMORY USING MALLOC.H FREE() method!
  */

#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>

/**
 * A mock salt default len
 */
#define MOCK_AUTH_NONCE_LEN 16

#define SCRAM_MAX_ITERS 999999999

/**
* A minimum recomended iters for sha. 
*/
#define SCRAM_SHA_ITERMS_MIN 4096

/** 
* A minumum recomended iters for sha3.
*/
#define SCRAM_SHA3_ITERS_MIN 10000

/**
* A default raw (non base64) nonce length
*/
#define SCRAM_RAW_NONCE_LEN 32



// --- error.rs bindings

/**
* Error code
*/
enum ScramErrorCode 
{
  /**
    * Error happened inside lib
    */
  INTERNAL_ERROR,
  /**
    * Error during verification of proof or other value
    */
  VERIFICATION_ERROR,
  /**
    * Error which occurs outside of the lib
    */
  EXTERNAL_ERROR,
  /**
    * Error due malformed SCRAM message
    */
  MALFORMED_SCRAM_MSG,
  /**
    * Error which occure when unsupported options are included in received msg
    */
  FEATURE_NOT_SUPPORTED,
  /**
    * Error due to protocol violation
    */
  PROTOCOL_VIOLATION,
  /**
    * Wrong call on extract result
    */
  AUTH_SEQ_COMPLETED,
  /**
    * Client side received error
    */
  CLIENT_SIDE,
  /**
    * SCRAM channel binding 'type' not supported or not implemented
    */
  CHAN_BIND_NOT_IMPLEMENTED,
};

/**
  * An instance which keeps error description. 
  * Should be deallocated manually using `capi_scram_error_free`.
  */
typedef struct CApiScramRuntimeError CApiScramRuntimeError;

/**
  * Deallocates the `struct CApiScramRuntimeError`.
  */
void capi_scram_error_free(CApiScramRuntimeError *error);

/**
  * Returns the error code `enum ScramErrorCode`.
  */
enum ScramErrorCode capi_scram_error_get_code(CApiScramRuntimeError *error);

/**
  * Returns the error description which can be printed to stderr.
  */
const char * capi_scram_error_get_descr(CApiScramRuntimeError *error);

// Result

/**
  * `ScramResult` is a crate's native implementation of the scram error.
  *  It is needed to avoid convertion to CApi where it is not desired and 
  *   useless.
  */
typedef struct ScramResult ScramResult;


// ---- nonce.rs bindings

/**
  * An instance which converts Rusts ScramNonce into C compatiable.
  * Should be deallocated with: `capi_scram_nonce_free`.
  *   Should be initialized using:
  *   - capi_scram_nonce_none
  *   - capi_scram_nonce_plain
  *   - capi_scram_nonce_base64
  * 
  */
typedef struct CApiNonce CApiNonce;


/**
  * Deallocated the `CApiNonce`.
  */
void capi_scram_nonce_free(CApiNonce *nonce);

/**
  * Initalizes the `nonce` as deafult. If error occures and `err_msg` is not NULL,
  *   the error desription will be set. 
  *    The `err_msg` is pointer to pointer i.e a pointer to heap allocated object will be 
  *     stored.
  *   The `CApiScramRuntimeError` must be deallocated
  *   using `capi_scram_error_free`.
  *
  * Returns:
  *   -1 - if any of the arguments are invalid.
  *    0 - successfull
  *    1 - if error happened.
  */
int32_t capi_scram_nonce_none(CApiNonce **nonce, struct CApiScramRuntimeError ** err_msg);

/**
  * Initalizes the `nonce` with plain nonce data. The `plain_nonce` should be an array of the
  *   uint8_t. The size of the array is passed via `plain_nonce_len`.
  *    If error occures and `err_msg` is not NULL, the error desription will be set. 
  *    The `err_msg` is pointer to pointer i.e a pointer to heap allocated object will be 
  *     stored.
  *    The `CApiScramRuntimeError` must be deallocated using `capi_scram_error_free`.
  *
  * Returns:
  *   -1 - if any of the arguments are invalid.
  *    0 - successfull
  *    1 - if error happened.
  */
int32_t capi_scram_nonce_plain(CApiNonce **nonce, const uint8_t * plain_nonce, size_t plain_nonce_len, 
  struct CApiScramRuntimeError ** error_out);

/**
  * Initalizes the `nonce` with base64 nonce data. The `base64_nonce` should be a  UTF-8 
  *   compatiable char array, null terminated.
  *    If error occures and `err_msg` is not NULL, the error desription will be set. 
  *    The `err_msg` is pointer to pointer i.e a pointer to heap allocated object will be 
  *     stored.
  *    The `CApiScramRuntimeError` must be deallocated using `capi_scram_error_free`.
  *
  * Returns:
  *   -1 - if any of the arguments are invalid.
  *    0 - successfull
  *    1 - if error happened.
  */
int32_t capi_scram_nonce_base64(CApiNonce **nonce, const char * base64_nonce, 
  CApiScramRuntimeError ** err_msg);

// ---- client.rs bindings


/**
  * Hasher (provider).
  */
typedef enum CApiScramHasherProvider 
{
  // PBKDF + HMAC
  RUST_NATIVE = 0,

  // RING CRATE
  RUST_RING = 1,
} CApiScramHasherProvider;

/**
  * Hash type
  */
typedef enum CApiScramTypeAlias 
{
  // If crate compiled with exclude_sha1, causes runtime error feature not sup.
  SHA1 = 0,
  SHA256 = 1,
  SHA512 = 2,
  SHA3_512 = 3,
} CApiScramTypeAlias;


// ---- scram_auth.rs

/**
  * A scram key (scram_auth.rs). Rust internal struct. Must never be
  *  modified outside of the crate!
  * Should be deallocated with capi_scram_key_free(struct ScramKey * key);
  */
typedef struct ScramKey ScramKey;

/**
  * Initializes the scram_keys. 
  * NULL ptr - for defaults.
  * The client_key and server_key keys are independent from 
  * each other. So, client key can be default, the server key can be custom.
  *
  * The key instance is returned via `key_out` to local pointer in case of success.
  * If `error_out` is set to NULL, the error message will not be returned in case
  * of `1` - error condition.
  * # Returns
  *
  * A int32_t is returned which indicates the result overall:
  * 
  * `<0` - invalid argument number (no descr is returned)
  * `0` - success
  * `1` - error (use `error_out` to obtain a description) 
  */
int32_t capi_scram_key(const uint8_t * client_key,
  size_t client_key_len, const uint8_t *server_key, size_t server_key_len,
  ScramKey **key_out, CApiScramRuntimeError **error_out);

/**
  * Deallocates the ScramKey.
  */
void capi_scram_key_free(ScramKey *key);

  /**
    * A ScramPassword (scram_auth.rs).
    * Should be deallocated with capi_scram_password_free(struct ScramPassword * pass);
    */
typedef struct ScramPassword ScramPassword;

/**
  * Deallocates the ScramPassword.
  */
void capi_scram_password_free(struct ScramPassword * pass);

/**
  * A program which utilizes this crate should call this function if user was not
  * found in DB. The execution should not be interrupted.
  * 
  * Returns
  * 
  * The returned value should be passed in the callback back to Rust Scram crate.
  * It is not required and should not be deallocated.
  */
ScramResult * capi_scram_password_not_found(CApiScramHasherProvider hash_prov, 
  CApiScramTypeAlias hash_type);

/**
  * A program which uses this crate should call this function if user was found
  * but password is encoded as plain text. This function requires that the correct
  * [ScramHashing] which was previously used to initialize the server, should be used.
  *
  * Returns
  * 
  * The returned value should be passed in the callback back to Rust Scram crate.
  * It is not required and should not be deallocated.
  */
ScramResult * capi_scram_password_found_plaintext(CApiScramHasherProvider hash_prov,
  CApiScramTypeAlias hash_type, const uint8_t * password, size_t pass_len, ScramKey * scram_keys_opt);


/**
  * A function which can be used for salted password generation from
  * provided parameters.
  *
  * # Arguments
  *
  * `hash_prov` - a CApiScramHasherProvider which is provided in the callback function.
  * `hash_type` - a CApiScramTypeAlias which is provided in the callback function.
  * `plain_password` and `plain_password_len` - a plaintext password and length in bytes.
  *     Should not be NULL-terminated.
  * `salt_plain_opt` and `salt_plain_len` - a plain salt and length in bytes. If NULL, then
  *   will 'mock' the salt.
  * `iterations` - a pointer to the value with amount of operations. If NULL, then the default
  *   will be set.
  * `scram_keys_opt` - a ScramKey. If NULL - sets the default. The callback function, from where
  *   this function is called, should manage the keys manually.
  *
  * Returns
  * 
  * The returned value should be passed in the callback back to Rust Scram crate.
  * It is not required and should not be deallocated.
  */ 
ScramResult * capi_scram_password_with_params(
  CApiScramHasherProvider hash_prov,
  CApiScramTypeAlias hash_type,
  const uint8_t * plain_password,
  size_t plain_password_len, 
  const uint8_t * salt_plain_opt,
  size_t salt_plain_len,
  uint32_t * iterations, 
  struct ScramKey * scram_keys_opt
);


struct ScramResult * capi_scram_password_found_secret_base64_password(
  const char * salted_hashed_password_base64, const char * salt_base64, uint32_t iterations,
  ScramKey * scram_keys_opt);

// ---- scram_sync_client.rs
/** 
  * A client instance. Should be destructed with `capi_scram_client_free`
  *   function.
  */
typedef struct CApiScramClient CApiScramClient;

/**
  * Initialzes the SCRAM client instance.
  *
  * Arguments
  *
  * - `hashing` a hashing algorithm.
  * - `plain_username` a null-terminated string with user name.
  * - `plain_authzid` a null-terminated string with authzid, if not availabe then NULL.
  * - `plain_password` a null-terminated string with password.
  * - `client_key` an array with the client key. Can be NULL.
  * - `client_key_len` a length of the `client_key`. If `client_key`
  *     is NULL, then size should be zero.
  * - `server_key` a pointer to the initialized [ScramKey] or NULL for default.
  * - `client_nonce` an initial client Nonce. If set to NULL will be
  *     converted to None.
  * - `ignore_exts` when set to true ignores the extensions which may appear after the
  *       protocol data. When set to false, an error will be returned in case if any
  *       unknown data presents.
  * - `client` a returned client instance on success.
  * - `error` an error description. If NULL, no error description returend.
  *
  * Returns
  *
  * A int32_t is returned which indicates the result overall:
  * 
  * `<0` - invalid argument number
  * `0` - success
  * `1` - error (use `error` to obtain a description)
  */
int32_t capi_scram_client_init(CApiScramHasherProvider hash_provider, const char *scram_type,
  const char *plain_username, const char *plain_authzid, const char *plain_password, ScramKey *scram_key,
  CApiNonce * client_nonce, bool ignore_exts, CApiScramClient **client, CApiScramRuntimeError **error);


/**
  * Deallocated the client instance.
  */
void capi_scram_client_free(CApiScramClient *res);


/**
  * Contains the result received from the scram algorithm.
  * The instance is allocated on the heap and must be destructed manually
  * using `capi_scram_client_result_free`.
  */
typedef struct CApiScramResultClient CApiScramResultClient;

/**
  * Deallocates the CApiScramResultClient instance.
  */
void capi_scram_client_result_free(CApiScramResultClient *msg_res);

// Create init message, -1 means NULL argument
int32_t capi_scram_client_init_msg(CApiScramClient *client, CApiScramResultClient **init_msg);

/**
  * Parses response from server. The `resp_plain` arg is plain string SCRAM meesage NULL-terminated!
  *
  * The parsing result which could be sent back to client is returned via `res_out`.
  * If `error_out` is set to NULL, the error message will not be returned in case
  * of `1` - error condition.
  * # Returns
  *
  * A int32_t is returned which indicates the result overall:
  * 
  * `<0` - invalid argument number (no descr is returned)
  * `0` - success
  * `1` - error (use `error_out` to obtain a description) 
  */
int32_t capi_scram_client_parse_resp_plain(CApiScramClient *client, const char *resp_plain,
  CApiScramResultClient **res_out, CApiScramRuntimeError **error_out);

/**
  * Parses response from server. The `resp_b64` arg is a base64 encoded message. 
  *  `resp_len` arg is a length of the `resp_b64` array.
  *
  * The parsing result which could be sent back to client is returned via `res_out`.
  * If `error_out` is set to NULL, the error message will not be returned in case
  * of `1` - error condition.
  * # Returns
  *
  * A int32_t is returned which indicates the result overall:
  * 
  * `<0` - invalid argument number (no descr is returned)
  * `0` - success
  * `1` - error (use `error_out` to obtain a description) 
  */
int32_t capi_scram_client_parse_resp_base64(CApiScramClient *client, const uint8_t *resp_b64, 
    size_t resp_len, CApiScramResultClient **res_out, CApiScramRuntimeError **error_out);

/**
  * Returns `true` if the secuence completed and the client instance can be destructed.
  * Otherwise if a message for server is available, a `false` will be returned.
  */
bool capi_scram_client_result_is_final(CApiScramResultClient * msg_res);

/**
  * Returns the poitner to the response message encoded as null terminated raw. The `plain_msg` 
  *   must not be modified! Use strdup()!.
  */
int32_t capi_scram_client_result_plain(CApiScramResultClient * msg_res, const char **plain_msg);

/**
  * Returns the poitner to the response message encoded as null terminated base64. The `base64_msg` 
  *   must not be modified! Use strdup()!.
  */
int32_t capi_scram_client_result_base64(CApiScramResultClient * msg_res, const char **base64_msg);

/**
  * Deallocates the CApiScramResultClient instance
  */
void capi_scram_client_result_free(CApiScramResultClient * msg_res);


// ------- SERVER -------

/**
  * An enumerator which reflects the state of the CApiScramResultServer.
  */
typedef enum CApiScramResultType
{
  // A data for client to be sent.
  REPLY_DATA = 0,
  // Error, the error should be reported to client and session closed.
  REPLY_ERROR = 1,
  // A final message which should be sent to client and server instance
  // could be closed after.
  REPLY_FINAL = 2,
} CApiScramResultType;

// scram_type aliases

#define SCRAM_SHA_1 "SCRAM-SHA-1"
#define SCRAM_SHA_256 "SCRAM-SHA-256"
#define SCRAM_SHA_256_PLUS "SCRAM-SHA-256-PLUS"
#define SCRAM_SHA_512 "SCRAM-SHA-512"
#define SCRAM_SHA_512_PLUS "SCRAM-SHA-512-PLUS"
#define SCRAM_SHA3_512 "SCRAM-SHA3-512"
#define SCRAM_SHA3_512_PLUS "SCRAM-SHA3-512-PLUS"

/**
  * A server instance. 
  * Can be constructed using: capi_scram_client_init()
  * Should be deallocated using: capi_scram_server_free()
  */
typedef struct CApiScramServer CApiScramServer;

/**
 * A `auzthzid` is NULL when no (a=) was provided by the client.
 */
typedef ScramResult* (*func_password_for_user_ptr)(void *auth_db_inst, 
  CApiScramHasherProvider hash_prov, CApiScramTypeAlias hash_type, 
  const char * username, const char * auzthzid);

/**
  * Initializes the server instance.
  *
  * The parsing result which could be sent back to client is returned via `res_out`.
  * If `error_out` is set to NULL, the error message will not be returned in case
  * of `1` - error condition.

  * # Arguments
  * 
  * `id` - instance identification, must be > 0.
  * `hashing` - CApiScramHasherProvider a hash provider
  * `server_nonce` - a CApiNonce server nonce.
  * `auth_db_inst` - a user pointer which should point to master-struct which should 
  *   contain all necesarry access to auth DB. It is passed to `func_password_for_user_ptr`.
  * `func_password_for_user` - a pointer to callback function which is called after client
  *   provides the user name to provide the scram crate with password data.
  * `scram_type` see `SCRAM_TYPES` at scram_common.rs.
  * `ignore_exts` - when set to true will ignore all unknown items in the message
  *     otherwise the error will be thrown.
  * SCRAM-SHA-1, SCRAM-SHA-256, SCRAM-SHA-256-PLUS, SCRAM-SHA-512, SCRAM-SHA-512-PLUS
  *
  * # Returns
  *
  * A int32_t is returned which indicates the result overall:
  * 
  * `<0` - invalid argument number (no descr is returned)
  * `0` - success
  * `1` - error (use `error_out` to obtain a description) 
  */
int32_t capi_scram_server_init(size_t id, CApiScramHasherProvider hashing,
  CApiNonce * server_nonce, void *auth_db_inst, func_password_for_user_ptr func_password_for_user,
  const char * scram_type, bool ignore_exts, CApiScramServer **serv_out, CApiScramRuntimeError **error_out);

/**
  * Destructs the scram server instance.
  */
void capi_scram_server_free(CApiScramServer * server);

/**
  * Returns username to provided pointer. Does NEED to be deallocated using
  *   capi_scram_server_get_username_free().
  * The username is available only when the initial message will be received
  * from client.
  *
  * # Returns
  *
  * `<0` - if any of the arguments are invalid.
  * `0` - success
  * `1` - no username availabe yet.
  */
int32_t capi_scram_server_get_username(CApiScramServer * server, const char ** username);

/**
  * Dealocates the username.
  */
void capi_scram_server_get_username_free(const char * username);

/**
  * A definition of the structure which holds a parsing result.
  * Should be deallocated with capi_scram_server_result_free()..
  */
typedef union CApiScramResultServer CApiScramResultServer;

/**
  * Parses the response from client which is base64 encoded.
  */
int32_t capi_scram_server_parse_resp_base64(CApiScramServer * server, const uint8_t * resp_b64, 
  size_t resp_len, CApiScramResultServer **serv_res, CApiScramRuntimeError **error_out);

/**
  * Parses the response from client which is plain encoded. The plain is usually a UTF-8
  * valid message with all printable characters.
  */
int32_t capi_scram_server_parse_resp_plain(CApiScramServer * server, const char * resp_plain,
  CApiScramResultServer **serv_res, CApiScramRuntimeError **error_out);

/**
  * Deallocates the CApiScramResultServer instance.
  */
void capi_scram_server_result_free(CApiScramResultServer *resp);

/**
  * Returns the CApiScramResultType type of the current parsing result.
  *
  * # Returns 
  * '<0` - if argument is invalid.
  * `0` - on success.
  */
int32_t capi_scram_server_result_type(CApiScramResultServer * resp, CApiScramResultType *resp_type);

/**
  * Returns the result which can be sent back to client. This function returns the
  *   message in base64 format i.e a NULL terminated string. Or the err is returned
  *   via `err_out`. If `err_out` is NULL, the error description will not be returend.
  *   Unlike the rest similar functions, the `msg_raw_out` is returned anyway even
  *   if error happens, because the error is reported to client.
  * 
  * `msg_res` does not need to be deallocated (part of the CApiScramResultServer).
  * `err_out` does not need to be deallocated (part of the CApiScramResultServer). 
  *   Instead the `msg_res` should be deallocated using capi_scram_server_result_free().
  */
int32_t capi_scram_server_result_base64(CApiScramResultServer * msg_res, const char **msg_raw_out,
  CApiScramRuntimeError ** err_out);

/**
* Returns the result which can be sent back to client. This function returns the
*   message in plain format i.e a NULL terminated string. Or the err is returned
*   via `err_out`. If `err_out` is NULL, the error description will not be returend.
*   Unlike the rest similar functions, the `msg_raw_out` is returned anyway even
*   if error happens, because the error is reported to client.
* 
* `msg_res` does not need to be deallocated (part of the CApiScramResultServer).
* `err_out` does not need to be deallocated (part of the CApiScramResultServer). 
*   Instead the `msg_res` should be deallocated using capi_scram_server_result_free().
*/
int32_t capi_scram_server_result_plain(CApiScramResultServer * msg_res, const char **msg_raw_out,
  CApiScramRuntimeError ** err_out);


// --- NOSTD global alloc init

#ifdef CAPI_SCRAM_NOSTD
/**
  * A type definition for memory allocation.
  */
typedef void * (*func_globall_malloc)(size_t sz, size_t align);

/**
  * A type definition for memory deallocation.
  */
typedef void (*func_globall_dealloc)(void * ptr);

/**
  * A type definition for memory reallocation.
  */
typedef void * (*func_globall_realloc)(void * ptr, size_t new_size);

/**
  * If crate compiled with no_std, the crate can allocate heap memory. For this reason, a
  * heap managment functions can be passed to crate.
  *
  * For example:
  * void * globall_malloc_f(size_t sz, size_t align)
  * {
  *   return malloc((unsigned long)sz);
  * }
  *
  * # Arguemnts
  *
  * `fn_alloc` a pointer to function `func_globall_malloc` to allocate the memory in
  *   heap. Either return pointer or NULL.
  * `fn_alloc_zr` a pointer to function `func_globall_malloc` to allocate NULLED memory in
  *   heap. Either return pointer or NULL.
  * `fn_dealloc` a poitner to function `func_globall_dealloc` to deallocate heap memory.
  * `fn_realloc` a pointer to function `fn_realloc` to reallocate memory. Same as 
  *   stdlib realloc.
  *
  * # Returns
  * 
  * `<0` - error, incorrect argument number.
  * '0' - success.
  */
int32_t set_allocator(func_globall_malloc fn_alloc, func_globall_malloc fn_alloc_zr, func_globall_dealloc fn_dealloc, func_globall_realloc fn_realloc);

#endif //CAPI_SCRAM_NOSTD

// --- EXPORTED FUNCTIONS

typedef struct CApiExpBase64
{
  char * base64;
} CApiExpBase64;

int32_t capi_exp_base64_encode(const uint8_t * input, size_t len, CApiExpBase64 * out);

void capi_exp_base64_free(CApiExpBase64 * out);

#endif // !CAPI_SCRAM_NOCAPI

#endif
