#ifndef __PAIR_AP_H__
#define __PAIR_AP_H__

#include <stdint.h>

#define PAIR_AP_VERSION_MAJOR 0
#define PAIR_AP_VERSION_MINOR 14

#define PAIR_AP_DEVICE_ID_LEN_MAX 64

#define PAIR_AP_POST_PIN_START "POST /pair-pin-start"
#define PAIR_AP_POST_SETUP "POST /pair-setup"
#define PAIR_AP_POST_VERIFY "POST /pair-verify"
#define PAIR_AP_POST_ADD "POST /pair-add"
#define PAIR_AP_POST_LIST "POST /pair-list"
#define PAIR_AP_POST_REMOVE "POST /pair-remove"

enum pair_type
{
  // This is the pairing type required for Apple TV device verification, which
  // became mandatory with tvOS 10.2.
  PAIR_CLIENT_FRUIT,
  // This is the Homekit type required for AirPlay 2 with both PIN setup and
  // verification
  PAIR_CLIENT_HOMEKIT_NORMAL,
  // Same as normal except PIN is fixed to 3939 and stops after setup step 2,
  // when session key is established
  PAIR_CLIENT_HOMEKIT_TRANSIENT,
  // Server side implementation supporting both transient and normal mode,
  // letting client choose mode. If a PIN is with pair_setup_new() then only
  // normal mode will be possible.
  PAIR_SERVER_HOMEKIT,
};

/* This struct stores the various forms of pairing results. The shared secret
 * is used to initialise an encrypted session via pair_cipher_new(). For
 * non-transient client pair setup, you also get a key string (client_setup_keys) from
 * pair_setup_result() that you can store and use to later initialise
 * pair_verify_new(). For non-transient server pair setup, you can either:
 *  - Register an "add pairing" callback (add_cb) with pair_setup_new(), and
 *    then save the client id and key in the callback (see server-example.c for
 *    this approach).
 *  - Check pairing result with pair_setup_result() and if successful read and
 *    store the client id and key from the result struct.
 *  - Decide not to authenticate clients during pair-verify (set get_cb to NULL)
 *    in which case you don't need to save client ids and keys from pair-setup.
 *
 * Table showing returned data (everything else will be zeroed):
 *
 *                                  | pair-setup                    | pair-verify
 *  --------------------------------|-------------------------------|--------------
 *  PAIR_CLIENT_FRUIT               | client keys                   | shared secret
 *  PAIR_CLIENT_HOMEKIT_NORMAL      | client keys, server public    | shared secret
                                    | key, server id                | shared secret
 *  PAIR_CLIENT_HOMEKIT_TRANSIENT   | shared secret                 | n/a
 *  PAIR_SERVER_HOMEKIT (normal)    | client public key, client id  | shared secret
 *  PAIR_SERVER_HOMEKIT (transient) | shared secret                 | n/a
 */
struct pair_result
{
  char device_id[PAIR_AP_DEVICE_ID_LEN_MAX]; // ID of the peer
  uint8_t client_private_key[64];
  uint8_t client_public_key[32];
  uint8_t server_public_key[32];
  uint8_t shared_secret[64];
  size_t shared_secret_len; // Will be 32 (normal) or 64 (transient)
};

struct pair_setup_context;
struct pair_verify_context;
struct pair_cipher_context;

typedef int (*pair_cb)(uint8_t public_key[32], const char *device_id, void *cb_arg);
typedef void (*pair_list_cb)(pair_cb list_cb, void *list_cb_arg, void *cb_arg);


/* ------------------------------- pair setup ------------------------------- */

/* Client
 * When you have the pin-code (must be 4 chars), create a new context with this
 * function and then call pair_setup() or pair_setup_request1(). device_id is
 * only required for Homekit pairing. If the client previously paired
 * (non-transient) and has saved credentials, it should instead skip setup and
 * only do verification. The callback is only for Homekit, and you can leave it
 * at NULL if you don't care about saving ID and key of the server for later
 * verification (then you also set get_cb to NULL in pair_verify_new), or if you
 * will read the id and key via pair_setup_result.
 *
 * Server
 * The client will make a connection and then at some point make a /pair-setup
 * or a /pair-verify. The server should:
 *   - new /pair-setup: create a setup context with a pin-code (or NULL to allow
 *     transient pairing), and then call pair_setup() to process request and
 *     construct reply (also for subsequent /pair-setup requests)
 *   - new /pair_verify: create a verify context and then call pair_verify()
 *     to process request and construct reply (also for subsequent /pair-verify
 *     requests)
 */
struct pair_setup_context *
pair_setup_new(enum pair_type type, const char *pin, pair_cb add_cb, void *cb_arg, const char *device_id);
void
pair_setup_free(struct pair_setup_context *sctx);

/* Returns last error message
 */
const char *
pair_setup_errmsg(struct pair_setup_context *sctx);

/* Will create a request (if client) or response (if server) based on the setup
 * context and last message from the peer. If this is the first client request
 * then set *in to NULL. Returns negative on error.
 */
int
pair_setup(uint8_t **out, size_t *out_len, struct pair_setup_context *sctx, const uint8_t *in, size_t in_len);

/* Returns the result of a pairing, or negative if pairing is not completed. See
 * 'struct pair_result' for info about pairing results. The string is a
 * representation of the result that is easy to persist and can be used to feed
 * back into pair_verify_new. The result and string becomes invalid when you
 * free sctx.
 */
int
pair_setup_result(const char **client_setup_keys, struct pair_result **result, struct pair_setup_context *sctx);

/* These are for constructing specific message types and reading specific
 * message types. Not needed for Homekit pairing if you use pair_setup().
 */
uint8_t *
pair_setup_request1(size_t *len, struct pair_setup_context *sctx);
uint8_t *
pair_setup_request2(size_t *len, struct pair_setup_context *sctx);
uint8_t *
pair_setup_request3(size_t *len, struct pair_setup_context *sctx);

int
pair_setup_response1(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len);
int
pair_setup_response2(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len);
int
pair_setup_response3(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len);


/* ------------------------------ pair verify ------------------------------- */

/* Client
 * When you have completed pair setup you get a string containing some keys
 * from pair_setup_result(). Give the string as input to this function to create
 * a verification context. Set the callback to NULL. Then call pair_verify().
 * The device_id is required for Homekit pairing.
 *
 * Server
 * When you get a pair verify request from a new peer, create a new context with
 * client_setup_keys set to NULL, with a callback set and the server's device ID
 * (same as for setup). Then call pair_verify(). The callback is used to get
 * the persisted client public key (saved after pair setup), so the client can
 * be verified. You can set the callback to NULL if you don't care about that.
 * If set, the callback is made as part of pair_verify_response2. The job of the
 * callback is to fill out the public_key with the public key from the setup
 * stage (see 'struct pair_result'). If the client device id is not known (i.e.
 * it has not completed pair-setup), return -1.
 */
struct pair_verify_context *
pair_verify_new(enum pair_type type, const char *client_setup_keys, pair_cb get_cb, void *cb_arg, const char *device_id);
void
pair_verify_free(struct pair_verify_context *vctx);

/* Returns last error message
 */
const char *
pair_verify_errmsg(struct pair_verify_context *vctx);

/* Will create a request (if client) or response (if server) based on the verify
 * context and last message from the peer. If this is the first client request
 * then set *in to NULL. Returns negative on error.
 */
int
pair_verify(uint8_t **out, size_t *out_len, struct pair_verify_context *sctx, const uint8_t *in, size_t in_len);

/* Returns a pointer to the result of the pairing. Only the shared secret will
 * be filled out. Note that the result become invalid when you free vctx.
 */
int
pair_verify_result(struct pair_result **result, struct pair_verify_context *vctx);

/* These are for constructing specific message types and reading specific
 * message types. Not needed for Homekit pairing where you can use pair_verify().
 */
uint8_t *
pair_verify_request1(size_t *len, struct pair_verify_context *vctx);
uint8_t *
pair_verify_request2(size_t *len, struct pair_verify_context *vctx);

int
pair_verify_response1(struct pair_verify_context *vctx, const uint8_t *in, size_t in_len);
int
pair_verify_response2(struct pair_verify_context *vctx, const uint8_t *in, size_t in_len);


/* ------------------------------- ciphering -------------------------------- */

/* When you have completed the verification you can extract a shared secret with
 * pair_verify_result() - or, in case of transient pairing, from
 * pair_setup_result(). Give the shared secret as input to this function to
 * create a ciphering context.
 */
struct pair_cipher_context *
pair_cipher_new(enum pair_type type, int channel, const uint8_t *shared_secret, size_t shared_secret_len);
void
pair_cipher_free(struct pair_cipher_context *cctx);

/* Returns last error message
 */
const char *
pair_cipher_errmsg(struct pair_cipher_context *cctx);

/* The return value equals length of plaintext that was encrypted, so if the
 * return value == plaintext_len then everything was encrypted. On error -1 is
 * returned.
 */
ssize_t
pair_encrypt(uint8_t **ciphertext, size_t *ciphertext_len, const uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx);

/* The return value equals length of ciphertext that was decrypted, so if the
 * return value == ciphertext_len then everything was decrypted. On error -1 is
 * returned.
 */
ssize_t
pair_decrypt(uint8_t **plaintext, size_t *plaintext_len, const uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx);

/* Rolls back the nonce
 */
void
pair_encrypt_rollback(struct pair_cipher_context *cctx);
void
pair_decrypt_rollback(struct pair_cipher_context *cctx);


/* --------------------------------- other ---------------------------------- */

/* These are for Homekit pairing where they are called by the controller, e.g.
 * the Home app
 *
 * TODO this part is currenly not working
 */
int
pair_add(enum pair_type type, uint8_t **out, size_t *out_len, pair_cb add_cb, void *cb_arg, const uint8_t *in, size_t in_len);

int
pair_remove(enum pair_type type, uint8_t **out, size_t *out_len, pair_cb remove_cb, void *cb_arg, const uint8_t *in, size_t in_len);

int
pair_list(enum pair_type type, uint8_t **out, size_t *out_len, pair_list_cb list_cb, void *cb_arg, const uint8_t *in, size_t in_len);

/* For parsing an incoming message to see what type ("state") it is. Mostly
 * useful for servers. Returns 1-6 for pair-setup and 1-4 for pair-verify.
 */
int
pair_state_get(enum pair_type type, const char **errmsg, const uint8_t *in, size_t in_len);

/* For servers, pair_ap calculates the public key using device_id as a seed.
 * This function returns that public key.
 */
void
pair_public_key_get(enum pair_type type, uint8_t server_public_key[32], const char *device_id);

#endif  /* !__PAIR_AP_H__ */
