]> git.ipfire.org Git - thirdparty/shairport-sync.git/commitdiff
Include pair_ap as a simple directory.
authorMike Brady <4265913+mikebrady@users.noreply.github.com>
Wed, 7 Jul 2021 14:11:32 +0000 (14:11 +0000)
committerMike Brady <4265913+mikebrady@users.noreply.github.com>
Wed, 7 Jul 2021 14:11:32 +0000 (14:11 +0000)
15 files changed:
pair_ap/README.md [new file with mode: 0644]
pair_ap/client-example.c [new file with mode: 0644]
pair_ap/pair-internal.h [new file with mode: 0644]
pair_ap/pair-tlv.c [new file with mode: 0644]
pair_ap/pair-tlv.h [new file with mode: 0644]
pair_ap/pair.c [new file with mode: 0644]
pair_ap/pair.h [new file with mode: 0644]
pair_ap/pair_ap-master/.gitignore [new file with mode: 0644]
pair_ap/pair_ap-master/evrtsp/evrtsp.h [new file with mode: 0644]
pair_ap/pair_ap-master/evrtsp/log.h [new file with mode: 0644]
pair_ap/pair_ap-master/evrtsp/rtsp-internal.h [new file with mode: 0644]
pair_ap/pair_ap-master/evrtsp/rtsp.c [new file with mode: 0644]
pair_ap/pair_fruit.c [new file with mode: 0644]
pair_ap/pair_homekit.c [new file with mode: 0644]
pair_ap/server-example.c [new file with mode: 0644]

diff --git a/pair_ap/README.md b/pair_ap/README.md
new file mode 100644 (file)
index 0000000..1c85544
--- /dev/null
@@ -0,0 +1,72 @@
+# pair_ap
+C client implementation of pairing for:
+* Apple TV device verification, which became mandatory with tvOS 10.2 (this is
+  called fruit mode in pair_ap)
+* Homekit pairing (for AirPlay 2, not working for Home app)
+
+Credit goes to @funtax and @ViktoriiaKh for doing some of the heavy lifting.
+## Requirements
+- libsodium
+- libgcrypt or libopenssl
+- libplist (only for Apple TV device verification)
+
+To build the example client and server you also need libevent2. If the
+dependencies are met you can build simply by running 'make'.
+
+## Homekit pairing
+Since I haven't been able to find much information on the internet on how
+Homekit pairing is designed, here is a write-up of my current understanding. If
+you know better, please help improve this.
+
+With Homekit pairing, there may be a controller (e.g. the Home app), and a
+number of devices/accessories (e.g. speakers). The controller acts as a client
+and can make requests for pairing. After it is paired it can also add other
+"third-party" pairings to the device, it can remove pairings and it can ask for
+a list of pairings.
+
+Other parties, e.g. the Music app or just iOS as an Airplay sender, can also
+pair with devices/accesssories in a similar manner, but they are not full-
+fledged Homekit controllers and thus don't make requests for adding, removing
+or listing pairings.
+
+The controller uses `/pair-add` to make sure that all devices on a network get
+the ID and public key of all the other devices, so that the user only needs to
+pair a device once.
+
+### Normal pairing
+For a normal first-time pairing, the client needs a one-time code (the device
+announces via mDNS whether a code is required). The client calls
+`/pair-pin-start` and the device displays the code. There is also QR-based
+pairing, which is (probably?) an encoded code.
+
+After obtaining the code, the client initiates a three step `/pair-setup`
+sequence, which results in both peers registering each other's ID and public
+key. Henceforth, a pairing is verified with the two step `/pair-verify`, where
+the parties check each-others identify. Saving the peer's ID + public key isn't
+strictly necessary if client or server doesn't care about verifying the peer,
+i.e. that `/pair-setup` has actually been completed.
+
+The result of `/pair-verify` is a shared secret that is used for symmetric
+encryption of the following communinacation between the parties.
+
+### Transient pairing
+Some devices don't require a code from the user for pairing (e.g. an Airport
+Express 2). If so, the client just needs to go through a two-step `/pair-setup`
+sequence which results in a shared secret, which is then used for encrypted
+communication. A fixed code of 3939 is used.
+
+Such devices don't appear to be fully Homekit compatible - they will not, for
+instance - appear in the Home app.
+
+## "fruit" pairing
+Like normal Homekit pairing, this consists of first requesting a code with
+`/pair-pin-start`, then a three-step `/pair-setup` and finally a two-step
+`/pair-verify`. After that the communication is encrypted with the resulting
+shared secret.
+
+
+## Acknowledgments
+- [AirPlayAuth](https://github.com/funtax/AirPlayAuth)
+- [AirPlayAuth-ObjC](https://github.com/ViktoriiaKh/AirPlayAuth-ObjC)
+- [ap2-sender](https://github.com/ViktoriiaKh/ap2-sender)
+- [csrp](https://github.com/cocagne/csrp)
diff --git a/pair_ap/client-example.c b/pair_ap/client-example.c
new file mode 100644 (file)
index 0000000..35e424a
--- /dev/null
@@ -0,0 +1,578 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <event2/event.h>
+#include <event2/buffer.h>
+
+#include "evrtsp/evrtsp.h"
+#include "pair.h"
+
+#define DEVICE_ID "AABBCCDD11223344"
+#define ACTIVE_REMOTE "3515324763"
+#define DACP_ID "FF1DB45949E6CBD3"
+#define USER_AGENT "AirPlay/381.13"
+
+#define ENDPOINT_SETUP_FRUIT "/pair-setup-pin"
+#define CONTENTTYPE_SETUP_FRUIT "application/x-apple-binary-plist"
+
+#define ENDPOINT_SETUP_HOMEKIT "/pair-setup"
+#define CONTENTTYPE_SETUP_HOMEKIT "application/octet-stream"
+
+
+typedef void (*request_cb)(struct evrtsp_request *, void *);
+
+static struct event_base *evbase;
+static struct evrtsp_connection *evcon;
+static int cseq;
+
+static const char *endpoint_setup;
+static const char *content_type_setup;
+
+static enum pair_type pair_type;
+
+static struct pair_cipher_context *cipher_ctx;
+static struct pair_verify_context *verify_ctx;
+static struct pair_setup_context *setup_ctx;
+
+
+static char *
+prompt_pin(void)
+{
+  char *pin = NULL;
+  size_t len;
+
+  printf ("Enter pin: ");
+  fflush (stdout);
+
+  len = getline(&pin, &len, stdin);
+  if (len != 5) // Includes EOL
+    {
+      printf ("Bad pin length %zu\n", len);
+      return NULL;
+    }
+
+  return pin;
+}
+
+static int
+response_process(uint8_t **response, struct evrtsp_request *req)
+{
+  if (!req)
+    {
+      printf("failed, could not read response\n");
+      return -1;
+    }
+  else if (req->response_code != 200)
+    {
+      printf("failed with error code %d: %s (body is %zu bytes)\n\n", req->response_code, req->response_code_line, evbuffer_get_length(req->input_buffer));
+      return -1;
+    }
+
+  printf("success\n\n");
+
+  *response = evbuffer_pullup(req->input_buffer, -1);
+
+  return evbuffer_get_length(req->input_buffer);
+}
+
+static int
+make_request(const char *url, const void *data, size_t len, const char *content_type, request_cb cb)
+{
+  struct evrtsp_request *req;
+  char buffer[1024];
+
+  req = evrtsp_request_new(cb, NULL);
+
+  if (data)
+    evbuffer_add(req->output_buffer, data, len);
+
+  if (content_type)
+    evrtsp_add_header(req->output_headers, "Content-Type", content_type);
+
+  cseq++;
+  snprintf(buffer, sizeof(buffer), "%d", cseq);
+  evrtsp_add_header(req->output_headers, "CSeq", buffer);
+
+  evrtsp_add_header(req->output_headers, "User-Agent", USER_AGENT);
+//  evrtsp_add_header(req->output_headers, "DACP-ID", DACP_ID);
+//  evrtsp_add_header(req->output_headers, "Active-Remote", ACTIVE_REMOTE);
+
+  if (pair_type == PAIR_CLIENT_HOMEKIT_NORMAL)
+    evrtsp_add_header(req->output_headers, "X-Apple-HKP", "3");
+  else if (pair_type == PAIR_CLIENT_HOMEKIT_TRANSIENT)
+    evrtsp_add_header(req->output_headers, "X-Apple-HKP", "4");
+
+  printf("Making request %d to '%s'... ", cseq, url);
+
+  return evrtsp_make_request(evcon, req, EVRTSP_REQ_POST, url);
+}
+
+static int
+make_request_options(const char *url, const void *data, size_t len, const char *content_type, request_cb cb)
+{
+  struct evrtsp_request *req;
+  char buffer[1024];
+
+  req = evrtsp_request_new(cb, NULL);
+
+  if (data)
+    evbuffer_add(req->output_buffer, data, len);
+
+  if (content_type)
+    evrtsp_add_header(req->output_headers, "Content-Type", content_type);
+
+  cseq++;
+  snprintf(buffer, sizeof(buffer), "%d", cseq);
+  evrtsp_add_header(req->output_headers, "CSeq", buffer);
+
+  evrtsp_add_header(req->output_headers, "User-Agent", USER_AGENT);
+//  evrtsp_add_header(req->output_headers, "DACP-ID", DACP_ID);
+//  evrtsp_add_header(req->output_headers, "Active-Remote", ACTIVE_REMOTE);
+  evrtsp_add_header(req->output_headers, "X-Apple-HKP", "3");
+
+  printf("Making request %d to '%s'... ", cseq, url);
+
+  return evrtsp_make_request(evcon, req, EVRTSP_REQ_OPTIONS, url);
+}
+
+
+static void
+options_response(struct evrtsp_request *req, void *arg)
+{
+  uint8_t *response;
+  int ret;
+
+  ret = response_process(&response, req);
+  if (ret >= 0)
+    printf("OPTIONS complete\n");
+
+  printf("Done\n");
+
+  event_base_loopbreak(evbase);
+}
+
+static int
+options_request(void)
+{
+  return make_request_options("*", NULL, 0, NULL, options_response);
+}
+
+static void
+rtsp_cipher(struct evbuffer *evbuf, void *arg, int encrypt)
+{
+  uint8_t *out = NULL;
+  size_t out_len = 0;
+  ssize_t processed;
+
+  uint8_t *in = evbuffer_pullup(evbuf, -1);
+  size_t in_len = evbuffer_get_length(evbuf);
+
+  if (encrypt)
+    processed = pair_encrypt(&out, &out_len, in, in_len, cipher_ctx);
+  else
+    processed = pair_decrypt(&out, &out_len, in, in_len, cipher_ctx);
+
+  evbuffer_drain(evbuf, in_len);
+
+  if (processed < 0)
+    {
+      printf("Error while ciphering: %s\n", pair_cipher_errmsg(cipher_ctx));
+      return;
+    }
+  else if (processed != in_len)
+    {
+      printf("Partial ciphering, only %zd of %zu input bytes were processed\n", processed, in_len);
+    }
+
+  evbuffer_add(evbuf, out, out_len);
+}
+
+static void
+verify_step2_response(struct evrtsp_request *req, void *arg)
+{
+  uint8_t *response;
+  struct pair_result *result;
+  int ret;
+
+  ret = response_process(&response, req);
+  if (ret < 0)
+    goto error;
+
+  ret = pair_verify_response2(verify_ctx, response, ret);
+  if (ret < 0)
+    goto error;
+
+  printf("Verify complete!\n\n");
+
+  ret = pair_verify_result(&result, verify_ctx);
+  if (ret < 0)
+    goto error;
+
+  cipher_ctx = pair_cipher_new(pair_type, 0, result->shared_secret, result->shared_secret_len);
+  if (!cipher_ctx)
+    goto error;
+
+  evrtsp_connection_set_ciphercb(evcon, rtsp_cipher, NULL);
+
+  ret = options_request();
+  if (ret < 0)
+    goto error;
+
+  pair_verify_free(verify_ctx);
+
+  return;
+
+ error:
+  printf("Error: %s\n", pair_verify_errmsg(verify_ctx));
+  pair_verify_free(verify_ctx);
+  pair_cipher_free(cipher_ctx);
+  event_base_loopbreak(evbase);
+}
+
+static int
+verify_step2_request(void)
+{
+  uint8_t *request;
+  size_t len;
+  int ret;
+
+  request = pair_verify_request2(&len, verify_ctx);
+  if (!request)
+    return -1;
+
+  ret = make_request("/pair-verify", request, len, "application/octet-stream", verify_step2_response);
+
+  free(request);
+
+  return ret;
+}
+
+static void
+verify_step1_response(struct evrtsp_request *req, void *arg)
+{
+  uint8_t *response;
+  int ret;
+
+  ret = response_process(&response, req);
+  if (ret <= 0)
+    goto error;
+
+  ret = pair_verify_response1(verify_ctx, response, ret);
+  if (ret < 0)
+    goto error;
+
+  ret = verify_step2_request();
+  if (ret < 0)
+    goto error;
+
+  return;
+
+ error:
+  printf("Error: %s\n", pair_verify_errmsg(verify_ctx));
+  pair_verify_free(verify_ctx);
+  event_base_loopbreak(evbase);
+}
+
+static int
+verify_step1_request(const char *authorisation_key)
+{
+  uint8_t *request = NULL;
+  size_t len;
+  int ret;
+
+  verify_ctx = pair_verify_new(pair_type, authorisation_key, NULL, NULL, DEVICE_ID);
+  if (!verify_ctx)
+    return -1;
+
+  request = pair_verify_request1(&len, verify_ctx);
+  if (!request)
+    goto error;
+
+  ret = make_request("/pair-verify", request, len, "application/octet-stream", verify_step1_response);
+  if (ret < 0)
+    goto error;
+
+  free(request);
+  return ret;
+
+ error:
+  printf("Error: %s\n", pair_verify_errmsg(verify_ctx));
+  pair_verify_free(verify_ctx);
+  free(request);
+  return -1;
+}
+
+static void
+setup_step3_response(struct evrtsp_request *req, void *arg)
+{
+  const char *key;
+  uint8_t *response;
+  struct pair_result *result;
+  int ret;
+
+  ret = response_process(&response, req);
+  if (ret <= 0)
+    goto error;
+
+  ret = pair_setup_response3(setup_ctx, response, ret);
+  if (ret < 0)
+    goto error;
+
+  ret = pair_setup_result(&key, &result, setup_ctx);
+  if (ret < 0)
+    goto error;
+
+  printf("Setup of device ID %s complete, got key: %s\n", result->device_id, key);
+
+  ret = verify_step1_request(key);
+  if (ret < 0)
+    goto error;
+
+  pair_setup_free(setup_ctx);
+
+  return;
+
+ error:
+  printf("Error: %s\n", pair_setup_errmsg(setup_ctx));
+  pair_setup_free(setup_ctx);
+  event_base_loopbreak(evbase);
+}
+
+static int
+setup_step3_request(void)
+{
+  uint8_t *request;
+  size_t len;
+  int ret;
+
+  request = pair_setup_request3(&len, setup_ctx);
+  if (!request)
+    return -1;
+
+  ret = make_request(endpoint_setup, request, len, content_type_setup, setup_step3_response);
+
+  free(request);
+
+  return ret;
+}
+
+static void
+setup_step2_response(struct evrtsp_request *req, void *arg)
+{
+  uint8_t *response;
+  struct pair_result *result;
+  int ret;
+
+  ret = response_process(&response, req);
+  if (ret <= 0)
+    goto error;
+
+  ret = pair_setup_response2(setup_ctx, response, ret);
+  if (ret < 0)
+    goto error;
+
+  printf("Setup SRP stage complete\n");
+
+  if (pair_type == PAIR_CLIENT_HOMEKIT_TRANSIENT)
+    {
+      ret = pair_setup_result(NULL, &result, setup_ctx);
+      if (ret < 0)
+       goto error;
+
+      cipher_ctx = pair_cipher_new(pair_type, 0, result->shared_secret, result->shared_secret_len);
+      if (!cipher_ctx)
+       goto error;
+
+      evrtsp_connection_set_ciphercb(evcon, rtsp_cipher, NULL);
+
+      ret = options_request();
+      if (ret < 0)
+       goto error;
+
+      pair_setup_free(setup_ctx);
+
+      return;
+    }
+
+  ret = setup_step3_request();
+  if (ret < 0)
+    goto error;
+
+  return;
+
+ error:
+  printf("Error: %s\n", pair_setup_errmsg(setup_ctx));
+  pair_setup_free(setup_ctx);
+  event_base_loopbreak(evbase);
+}
+
+static int
+setup_step2_request(void)
+{
+  uint8_t *request;
+  size_t len;
+  int ret;
+
+  request = pair_setup_request2(&len, setup_ctx);
+  if (!request)
+    return -1;
+
+  ret = make_request(endpoint_setup, request, len, content_type_setup, setup_step2_response);
+
+  free(request);
+
+  return ret;
+}
+
+static void
+setup_step1_response(struct evrtsp_request *req, void *arg)
+{
+  uint8_t *response;
+  int ret;
+
+  ret = response_process(&response, req);
+  if (ret <= 0)
+    goto error;
+
+  ret = pair_setup_response1(setup_ctx, response, ret);
+  if (ret < 0)
+    goto error;
+
+  ret = setup_step2_request();
+  if (ret < 0)
+    goto error;
+
+  return;
+
+ error:
+  printf("Error: %s\n", pair_setup_errmsg(setup_ctx));
+  pair_setup_free(setup_ctx);
+  event_base_loopbreak(evbase);
+}
+
+static int
+setup_step1_request(void)
+{
+  uint8_t *request;
+  size_t len;
+  int ret;
+
+  request = pair_setup_request1(&len, setup_ctx);
+  if (!request)
+    return -1;
+
+  ret = make_request(endpoint_setup, request, len, content_type_setup, setup_step1_response);
+
+  free(request);
+  return ret;
+}
+
+static void
+setup_start_response(struct evrtsp_request *req, void *arg)
+{
+  uint8_t *response;
+  char *pin = NULL;
+  int ret;
+
+  if (req)
+    {
+      ret = response_process(&response, req);
+      if (ret < 0)
+       goto error;
+    }
+
+  if (pair_type != PAIR_CLIENT_HOMEKIT_TRANSIENT)
+    {
+      pin = prompt_pin();
+      if (!pin)
+       goto error;
+    }
+
+  setup_ctx = pair_setup_new(pair_type, pin, NULL, NULL, DEVICE_ID);
+  if (!setup_ctx)
+    goto error;
+
+  ret = setup_step1_request();
+  if (ret < 0)
+    goto error;
+
+  free(pin);
+  return;
+
+ error:
+  if (setup_ctx)
+    printf("Error: %s\n", pair_setup_errmsg(setup_ctx));
+  free(pin);
+  pair_setup_free(setup_ctx);
+  event_base_loopbreak(evbase);
+}
+
+static int
+setup_start_request(void)
+{
+  return make_request("/pair-pin-start", NULL, 0, "application/x-apple-binary-plist", setup_start_response);
+}
+
+
+int
+main( int argc, char * argv[] )
+{
+  int ret;
+
+  if (argc < 4 || argc > 5)
+    {
+      printf("%s ip_address port homekit|fruit|transient [skip_pin]\n", argv[0]);
+      return -1;
+    }
+
+  const char *address = argv[1];
+  const char *port = argv[2];
+  int skip_pin = (argc == 5);
+
+  if (strcmp(argv[3], "fruit") == 0)
+    {
+      printf("Pair type is fruit\n");
+      pair_type = PAIR_CLIENT_FRUIT;
+      endpoint_setup = ENDPOINT_SETUP_FRUIT;
+      content_type_setup = CONTENTTYPE_SETUP_FRUIT;
+    }
+  else if (strcmp(argv[3], "homekit") == 0)
+    {
+      printf("Pair type is homekit (normal)\n");
+      pair_type = PAIR_CLIENT_HOMEKIT_NORMAL;
+      endpoint_setup = ENDPOINT_SETUP_HOMEKIT;
+      content_type_setup = CONTENTTYPE_SETUP_HOMEKIT;
+    }
+  else if (strcmp(argv[3], "transient") == 0)
+    {
+      printf("Pair type is homekit (transient)\n");
+      pair_type = PAIR_CLIENT_HOMEKIT_TRANSIENT;
+      endpoint_setup = ENDPOINT_SETUP_HOMEKIT;
+      content_type_setup = CONTENTTYPE_SETUP_HOMEKIT;
+    }
+
+  evbase = event_base_new();
+  evcon = evrtsp_connection_new(address, atoi(port));
+  evrtsp_connection_set_base(evcon, evbase);
+
+  if (pair_type == PAIR_CLIENT_HOMEKIT_TRANSIENT || skip_pin)
+    {
+      setup_start_response(NULL, NULL);
+    }
+  else
+    {
+      ret = setup_start_request();
+      if (ret < 0)
+       goto the_end;
+    }
+
+  event_base_dispatch(evbase);
+
+ the_end:
+  evrtsp_connection_free(evcon);
+  event_base_free(evbase);
+
+  return 0;
+}
diff --git a/pair_ap/pair-internal.h b/pair_ap/pair-internal.h
new file mode 100644 (file)
index 0000000..e7cd85e
--- /dev/null
@@ -0,0 +1,381 @@
+#include <stdint.h>
+#include <stdbool.h>
+#include <sodium.h>
+
+#include "pair.h"
+
+#define RETURN_ERROR(s, m) \
+  do { handle->status = (s); handle->errmsg = (m); goto error; } while(0)
+
+
+struct SRPUser;
+struct SRPVerifier;
+
+struct pair_client_setup_context
+{
+  struct SRPUser *user;
+
+  uint8_t pin[4];
+  char device_id[PAIR_AP_DEVICE_ID_LEN_MAX];
+
+  pair_cb add_cb;
+  void *add_cb_arg;
+
+  uint8_t public_key[crypto_sign_PUBLICKEYBYTES];
+  uint8_t private_key[crypto_sign_SECRETKEYBYTES];
+
+  const uint8_t *pkA;
+  int pkA_len;
+
+  uint8_t *pkB;
+  uint64_t pkB_len;
+
+  const uint8_t *M1;
+  int M1_len;
+
+  uint8_t *M2;
+  uint64_t M2_len;
+
+  uint8_t *salt;
+  uint64_t salt_len;
+
+  // We don't actually use the server's epk and authtag for anything
+  uint8_t *epk;
+  uint64_t epk_len;
+  uint8_t *authtag;
+  uint64_t authtag_len;
+};
+
+struct pair_server_setup_context
+{
+  struct SRPVerifier *verifier;
+
+  uint8_t pin[4];
+  char device_id[PAIR_AP_DEVICE_ID_LEN_MAX];
+
+  pair_cb add_cb;
+  void *add_cb_arg;
+
+  uint8_t public_key[crypto_sign_PUBLICKEYBYTES];
+  uint8_t private_key[crypto_sign_SECRETKEYBYTES];
+
+  bool is_transient;
+
+  uint8_t *pkA;
+  uint64_t pkA_len;
+
+  uint8_t *pkB;
+  int pkB_len;
+
+  uint8_t *b;
+  int b_len;
+
+  uint8_t *M1;
+  uint64_t M1_len;
+
+  const uint8_t *M2;
+  int M2_len;
+
+  uint8_t *v;
+  int v_len;
+
+  uint8_t *salt;
+  int salt_len;
+};
+
+enum pair_status
+{
+  PAIR_STATUS_IN_PROGRESS,
+  PAIR_STATUS_COMPLETED,
+  PAIR_STATUS_AUTH_FAILED,
+  PAIR_STATUS_INVALID,
+};
+
+struct pair_setup_context
+{
+  struct pair_definition *type;
+
+  enum pair_status status;
+  const char *errmsg;
+
+  struct pair_result result;
+  char result_str[256]; // Holds the hex string version of the keys that pair_verify_new() needs
+
+  // Hex-formatet concatenation of public + private, 0-terminated
+  char auth_key[2 * (crypto_sign_PUBLICKEYBYTES + crypto_sign_SECRETKEYBYTES) + 1];
+
+  union pair_setup_union
+  {
+    struct pair_client_setup_context client;
+    struct pair_server_setup_context server;
+  } sctx;
+};
+
+struct pair_client_verify_context
+{
+  char device_id[PAIR_AP_DEVICE_ID_LEN_MAX];
+
+  // These are the keys that were registered with the server in pair-setup
+  uint8_t client_public_key[crypto_sign_PUBLICKEYBYTES]; // 32
+  uint8_t client_private_key[crypto_sign_SECRETKEYBYTES]; // 64
+
+  bool verify_server_signature;
+  uint8_t server_fruit_public_key[64]; // Not sure why it has this length in fruit mode
+  uint8_t server_public_key[crypto_sign_PUBLICKEYBYTES]; // 32
+
+  // For establishing the shared secret for encrypted communication
+  uint8_t client_eph_public_key[crypto_box_PUBLICKEYBYTES]; // 32
+  uint8_t client_eph_private_key[crypto_box_SECRETKEYBYTES]; // 32
+
+  uint8_t server_eph_public_key[crypto_box_PUBLICKEYBYTES]; // 32
+
+  uint8_t shared_secret[crypto_scalarmult_BYTES]; // 32
+};
+
+struct pair_server_verify_context
+{
+  char device_id[PAIR_AP_DEVICE_ID_LEN_MAX];
+
+  // Same keys as used for pair-setup, derived from device_id
+  uint8_t server_public_key[crypto_sign_PUBLICKEYBYTES]; // 32
+  uint8_t server_private_key[crypto_sign_SECRETKEYBYTES]; // 64
+
+  bool verify_client_signature;
+  pair_cb get_cb;
+  void *get_cb_arg;
+
+  // For establishing the shared secret for encrypted communication
+  uint8_t server_eph_public_key[crypto_box_PUBLICKEYBYTES]; // 32
+  uint8_t server_eph_private_key[crypto_box_SECRETKEYBYTES]; // 32
+
+  uint8_t client_eph_public_key[crypto_box_PUBLICKEYBYTES]; // 32
+
+  uint8_t shared_secret[crypto_scalarmult_BYTES]; // 32
+};
+
+struct pair_verify_context
+{
+  struct pair_definition *type;
+
+  enum pair_status status;
+  const char *errmsg;
+
+  struct pair_result result;
+
+  union pair_verify_union
+  {
+    struct pair_client_verify_context client;
+    struct pair_server_verify_context server;
+  } vctx;
+};
+
+struct pair_cipher_context
+{
+  struct pair_definition *type;
+
+  uint8_t encryption_key[32];
+  uint8_t decryption_key[32];
+
+  uint64_t encryption_counter;
+  uint64_t decryption_counter;
+
+  // For rollback
+  uint64_t encryption_counter_prev;
+  uint64_t decryption_counter_prev;
+
+  const char *errmsg;
+};
+
+struct pair_definition
+{
+  int (*pair_setup_new)(struct pair_setup_context *sctx, const char *pin, pair_cb add_cb, void *cb_arg, const char *device_id);
+  void (*pair_setup_free)(struct pair_setup_context *sctx);
+  int (*pair_setup_result)(struct pair_setup_context *sctx);
+
+  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);
+
+  int (*pair_verify_new)(struct pair_verify_context *vctx, const char *client_setup_keys, pair_cb cb, void *cb_arg, const char *device_id);
+  void (*pair_verify_free)(struct pair_verify_context *vctx);
+  int (*pair_verify_result)(struct pair_verify_context *vctx);
+
+  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);
+
+  int (*pair_add)(uint8_t **out, size_t *out_len, pair_cb cb, void *cb_arg, const uint8_t *in, size_t in_len);
+  int (*pair_remove)(uint8_t **out, size_t *out_len, pair_cb cb, void *cb_arg, const uint8_t *in, size_t in_len);
+  int (*pair_list)(uint8_t **out, size_t *out_len, pair_list_cb cb, void *cb_arg, const uint8_t *in, size_t in_len);
+
+  struct pair_cipher_context *(*pair_cipher_new)(struct pair_definition *type, int channel, const uint8_t *shared_secret, size_t shared_secret_len);
+  void (*pair_cipher_free)(struct pair_cipher_context *cctx);
+
+  ssize_t (*pair_encrypt)(uint8_t **ciphertext, size_t *ciphertext_len, const uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx);
+  ssize_t (*pair_decrypt)(uint8_t **plaintext, size_t *plaintext_len, const uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx);
+
+  int (*pair_state_get)(const char **errmsg, const uint8_t *in, size_t in_len);
+  void (*pair_public_key_get)(uint8_t server_public_key[32], const char *device_id);
+};
+
+
+/* -------------------- GCRYPT AND OPENSSL COMPABILITY --------------------- */
+/*                   partly borrowed from ffmpeg (rtmpdh.c)                  */
+
+#if CONFIG_GCRYPT
+#include <gcrypt.h>
+#define SHA512_DIGEST_LENGTH 64
+#define bnum_new(bn)                                            \
+    do {                                                        \
+        if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { \
+            if (!gcry_check_version("1.5.4"))                   \
+                abort();                                        \
+            gcry_control(GCRYCTL_DISABLE_SECMEM, 0);            \
+            gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);   \
+        }                                                       \
+        bn = gcry_mpi_new(1);                                   \
+    } while (0)
+#define bnum_free(bn)                 gcry_mpi_release(bn)
+#define bnum_num_bytes(bn)            (gcry_mpi_get_nbits(bn) + 7) / 8
+#define bnum_is_zero(bn)              (gcry_mpi_cmp_ui(bn, (unsigned long)0) == 0)
+#define bnum_bn2bin(bn, buf, len)     gcry_mpi_print(GCRYMPI_FMT_USG, buf, len, NULL, bn)
+#define bnum_bin2bn(bn, buf, len)     gcry_mpi_scan(&bn, GCRYMPI_FMT_USG, buf, len, NULL)
+#define bnum_hex2bn(bn, buf)          gcry_mpi_scan(&bn, GCRYMPI_FMT_HEX, buf, 0, 0)
+#define bnum_random(bn, num_bits)     gcry_mpi_randomize(bn, num_bits, GCRY_WEAK_RANDOM)
+#define bnum_add(bn, a, b)            gcry_mpi_add(bn, a, b)
+#define bnum_sub(bn, a, b)            gcry_mpi_sub(bn, a, b)
+#define bnum_mul(bn, a, b)            gcry_mpi_mul(bn, a, b)
+#define bnum_mod(bn, a, b)            gcry_mpi_mod(bn, a, b)
+typedef gcry_mpi_t bnum;
+__attribute__((unused)) static void bnum_modexp(bnum bn, bnum y, bnum q, bnum p)
+{
+  gcry_mpi_powm(bn, y, q, p);
+}
+__attribute__((unused)) static void bnum_modadd(bnum bn, bnum a, bnum b, bnum m)
+{
+  gcry_mpi_addm(bn, a, b, m);
+}
+#elif CONFIG_OPENSSL
+#include <openssl/crypto.h>
+#include <openssl/bn.h>
+#include <openssl/rand.h>
+#include <openssl/sha.h>
+#include <openssl/evp.h>
+#define bnum_new(bn)                  bn = BN_new()
+#define bnum_free(bn)                 BN_free(bn)
+#define bnum_num_bytes(bn)            BN_num_bytes(bn)
+#define bnum_is_zero(bn)              BN_is_zero(bn)
+#define bnum_bn2bin(bn, buf, len)     BN_bn2bin(bn, buf)
+#define bnum_bin2bn(bn, buf, len)     bn = BN_bin2bn(buf, len, 0)
+#define bnum_hex2bn(bn, buf)          BN_hex2bn(&bn, buf)
+#define bnum_random(bn, num_bits)     BN_rand(bn, num_bits, 0, 0)
+#define bnum_add(bn, a, b)            BN_add(bn, a, b)
+#define bnum_sub(bn, a, b)            BN_sub(bn, a, b)
+typedef BIGNUM* bnum;
+__attribute__((unused)) static void bnum_mul(bnum bn, bnum a, bnum b)
+{
+  // No error handling
+  BN_CTX *ctx = BN_CTX_new();
+  BN_mul(bn, a, b, ctx);
+  BN_CTX_free(ctx);
+}
+__attribute__((unused)) static void bnum_mod(bnum bn, bnum a, bnum b)
+{
+  // No error handling
+  BN_CTX *ctx = BN_CTX_new();
+  BN_mod(bn, a, b, ctx);
+  BN_CTX_free(ctx);
+}
+__attribute__((unused)) static void bnum_modexp(bnum bn, bnum y, bnum q, bnum p)
+{
+  // No error handling
+  BN_CTX *ctx = BN_CTX_new();
+  BN_mod_exp(bn, y, q, p, ctx);
+  BN_CTX_free(ctx);
+}
+__attribute__((unused)) static void bnum_modadd(bnum bn, bnum a, bnum b, bnum m)
+{
+  // No error handling
+  BN_CTX *ctx = BN_CTX_new();
+  BN_mod_add(bn, a, b, m, ctx);
+  BN_CTX_free(ctx);
+}
+#endif
+
+
+/* -------------------------- SHARED HASHING HELPERS ------------------------ */
+
+#ifdef CONFIG_OPENSSL
+enum hash_alg
+{
+  HASH_SHA1,
+  HASH_SHA224,
+  HASH_SHA256,
+  HASH_SHA384,
+  HASH_SHA512,
+};
+#elif CONFIG_GCRYPT
+enum hash_alg
+{
+  HASH_SHA1 = GCRY_MD_SHA1,
+  HASH_SHA224 = GCRY_MD_SHA224,
+  HASH_SHA256 = GCRY_MD_SHA256,
+  HASH_SHA384 = GCRY_MD_SHA384,
+  HASH_SHA512 = GCRY_MD_SHA512,
+};
+#endif
+
+#if CONFIG_OPENSSL
+typedef union
+{
+  SHA_CTX    sha;
+  SHA256_CTX sha256;
+  SHA512_CTX sha512;
+} HashCTX;
+#elif CONFIG_GCRYPT
+typedef gcry_md_hd_t HashCTX;
+#endif
+
+int
+hash_init(enum hash_alg alg, HashCTX *c);
+
+int
+hash_update(enum hash_alg alg, HashCTX *c, const void *data, size_t len);
+
+int
+hash_final(enum hash_alg alg, HashCTX *c, unsigned char *md);
+
+unsigned char *
+hash(enum hash_alg alg, const unsigned char *d, size_t n, unsigned char *md);
+
+int
+hash_length(enum hash_alg alg);
+
+int
+hash_ab(enum hash_alg alg, unsigned char *md, const unsigned char *m1, int m1_len, const unsigned char *m2, int m2_len);
+
+bnum
+H_nn_pad(enum hash_alg alg, const bnum n1, const bnum n2);
+
+bnum
+H_ns(enum hash_alg alg, const bnum n, const unsigned char *bytes, int len_bytes);
+
+void
+update_hash_n(enum hash_alg alg, HashCTX *ctx, const bnum n);
+
+void
+hash_num(enum hash_alg alg, const bnum n, unsigned char *dest);
+
+
+/* ----------------------------- OTHER HELPERS -------------------------------*/
+
+#ifdef DEBUG_PAIR
+void
+hexdump(const char *msg, uint8_t *mem, size_t len);
+#endif
diff --git a/pair_ap/pair-tlv.c b/pair_ap/pair-tlv.c
new file mode 100644 (file)
index 0000000..ea1a761
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * TLV helpers are adapted from ESP homekit:
+ *    <https://github.com/maximkulkin/esp-homekit>
+ *
+ * The MIT License (MIT)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "pair-tlv.h"
+#include "pair-internal.h"
+
+
+static int
+tlv_add_value_(pair_tlv_values_t *values, uint8_t type, uint8_t *value, size_t size) {
+    pair_tlv_t *tlv = malloc(sizeof(pair_tlv_t));
+    if (!tlv) {
+        return PAIR_TLV_ERROR_MEMORY;
+    }
+    tlv->type = type;
+    tlv->size = size;
+    tlv->value = value;
+    tlv->next = NULL;
+
+    if (!values->head) {
+        values->head = tlv;
+    } else {
+        pair_tlv_t *t = values->head;
+        while (t->next) {
+            t = t->next;
+        }
+        t->next = tlv;
+    }
+
+    return 0;
+}
+
+
+pair_tlv_values_t *
+pair_tlv_new() {
+    pair_tlv_values_t *values = malloc(sizeof(pair_tlv_values_t));
+    if (!values)
+        return NULL;
+
+    values->head = NULL;
+    return values;
+}
+
+void
+pair_tlv_free(pair_tlv_values_t *values) {
+    pair_tlv_t *t = values->head;
+    while (t) {
+        pair_tlv_t *t2 = t;
+        t = t->next;
+        if (t2->value)
+            free(t2->value);
+        free(t2);
+    }
+    free(values);
+}
+
+int
+pair_tlv_add_value(pair_tlv_values_t *values, uint8_t type, const uint8_t *value, size_t size) {
+    uint8_t *data = NULL;
+    int ret;
+    if (size) {
+        data = malloc(size);
+        if (!data) {
+            return PAIR_TLV_ERROR_MEMORY;
+        }
+        memcpy(data, value, size);
+    }
+    ret = tlv_add_value_(values, type, data, size);
+    if (ret < 0)
+        free(data);
+    return ret;
+}
+
+pair_tlv_t *
+pair_tlv_get_value(const pair_tlv_values_t *values, uint8_t type) {
+    pair_tlv_t *t = values->head;
+    while (t) {
+        if (t->type == type)
+            return t;
+        t = t->next;
+    }
+    return NULL;
+}
+
+int
+pair_tlv_format(const pair_tlv_values_t *values, uint8_t *buffer, size_t *size) {
+    size_t required_size = 0;
+    pair_tlv_t *t = values->head;
+    while (t) {
+        required_size += t->size + 2 * ((t->size + 254) / 255);
+        t = t->next;
+    }
+
+    if (*size < required_size) {
+        *size = required_size;
+        return PAIR_TLV_ERROR_INSUFFICIENT_SIZE;
+    }
+
+    *size = required_size;
+
+    t = values->head;
+    while (t) {
+        uint8_t *data = t->value;
+        if (!t->size) {
+            buffer[0] = t->type;
+            buffer[1] = 0;
+            buffer += 2;
+            t = t->next;
+            continue;
+        }
+
+        size_t remaining = t->size;
+
+        while (remaining) {
+            buffer[0] = t->type;
+            size_t chunk_size = (remaining > 255) ? 255 : remaining;
+            buffer[1] = chunk_size;
+            memcpy(&buffer[2], data, chunk_size);
+            remaining -= chunk_size;
+            buffer += chunk_size + 2;
+            data += chunk_size;
+        }
+
+        t = t->next;
+    }
+
+    return 0;
+}
+
+int
+pair_tlv_parse(const uint8_t *buffer, size_t length, pair_tlv_values_t *values) {
+    size_t i = 0;
+    int ret;
+    while (i < length) {
+        uint8_t type = buffer[i];
+        size_t size = 0;
+        uint8_t *data = NULL;
+
+        // scan TLVs to accumulate total size of subsequent TLVs with same type (chunked data)
+        size_t j = i;
+        while (j < length && buffer[j] == type && buffer[j+1] == 255) {
+            size_t chunk_size = buffer[j+1];
+            size += chunk_size;
+            j += chunk_size + 2;
+        }
+        if (j < length && buffer[j] == type) {
+            size_t chunk_size = buffer[j+1];
+            size += chunk_size;
+        }
+
+        // allocate memory to hold all pieces of chunked data and copy data there
+        if (size != 0) {
+            data = malloc(size);
+            if (!data)
+                return PAIR_TLV_ERROR_MEMORY;
+
+            uint8_t *p = data;
+
+            size_t remaining = size;
+            while (remaining) {
+                size_t chunk_size = buffer[i+1];
+                memcpy(p, &buffer[i+2], chunk_size);
+                p += chunk_size;
+                i += chunk_size + 2;
+                remaining -= chunk_size;
+            }
+        }
+
+        ret = tlv_add_value_(values, type, data, size);
+        if (ret < 0) {
+            free(data);
+            return ret;
+        }
+    }
+
+    return 0;
+}
+
+#ifdef DEBUG_PAIR
+void
+pair_tlv_debug(const pair_tlv_values_t *values)
+{
+  printf("Received TLV values\n");
+  for (pair_tlv_t *t=values->head; t; t=t->next)
+    {
+      printf("Type %d value (%zu bytes): \n", t->type, t->size);
+      hexdump("", t->value, t->size);
+    }
+}
+#endif
+
diff --git a/pair_ap/pair-tlv.h b/pair_ap/pair-tlv.h
new file mode 100644 (file)
index 0000000..6ef8db2
--- /dev/null
@@ -0,0 +1,77 @@
+#ifndef __PAIR_AP_TLV_H__
+#define __PAIR_AP_TLV_H__
+
+#include <stdint.h>
+
+#define PAIR_TLV_ERROR_MEMORY -1
+#define PAIR_TLV_ERROR_INSUFFICIENT_SIZE -2
+
+typedef enum {
+  TLVType_Method = 0,        // (integer) Method to use for pairing. See PairMethod
+  TLVType_Identifier = 1,    // (UTF-8) Identifier for authentication
+  TLVType_Salt = 2,          // (bytes) 16+ bytes of random salt
+  TLVType_PublicKey = 3,     // (bytes) Curve25519, SRP public key or signed Ed25519 key
+  TLVType_Proof = 4,         // (bytes) Ed25519 or SRP proof
+  TLVType_EncryptedData = 5, // (bytes) Encrypted data with auth tag at end
+  TLVType_State = 6,         // (integer) State of the pairing process. 1=M1, 2=M2, etc.
+  TLVType_Error = 7,         // (integer) Error code. Must only be present if error code is
+                             // not 0. See TLVError
+  TLVType_RetryDelay = 8,    // (integer) Seconds to delay until retrying a setup code
+  TLVType_Certificate = 9,   // (bytes) X.509 Certificate
+  TLVType_Signature = 10,    // (bytes) Ed25519
+  TLVType_Permissions = 11,  // (integer) Bit value describing permissions of the controller
+                             // being added.
+                             // None (0x00): Regular user
+                             // Bit 1 (0x01): Admin that is able to add and remove
+                             // pairings against the accessory
+  TLVType_FragmentData = 13, // (bytes) Non-last fragment of data. If length is 0,
+                             // it's an ACK.
+  TLVType_FragmentLast = 14, // (bytes) Last fragment of data
+  TLVType_Flags = 19,        // Added from airplay2_receiver
+  TLVType_Separator = 0xff,
+} TLVType;
+
+
+typedef enum {
+  TLVError_Unknown = 1,         // Generic error to handle unexpected errors
+  TLVError_Authentication = 2,  // Setup code or signature verification failed
+  TLVError_Backoff = 3,         // Client must look at the retry delay TLV item and
+                                // wait that many seconds before retrying
+  TLVError_MaxPeers = 4,        // Server cannot accept any more pairings
+  TLVError_MaxTries = 5,        // Server reached its maximum number of
+                                // authentication attempts
+  TLVError_Unavailable = 6,     // Server pairing method is unavailable
+  TLVError_Busy = 7,            // Server is busy and cannot accept a pairing
+                                // request at this time
+} TLVError;
+
+typedef struct _tlv {
+    struct _tlv *next;
+    uint8_t type;
+    uint8_t *value;
+    size_t size;
+} pair_tlv_t;
+
+
+typedef struct {
+    pair_tlv_t *head;
+} pair_tlv_values_t;
+
+
+pair_tlv_values_t *pair_tlv_new();
+
+void pair_tlv_free(pair_tlv_values_t *values);
+
+int pair_tlv_add_value(pair_tlv_values_t *values, uint8_t type, const uint8_t *value, size_t size);
+
+pair_tlv_t *pair_tlv_get_value(const pair_tlv_values_t *values, uint8_t type);
+
+int pair_tlv_format(const pair_tlv_values_t *values, uint8_t *buffer, size_t *size);
+
+int pair_tlv_parse(const uint8_t *buffer, size_t length, pair_tlv_values_t *values);
+
+#ifdef DEBUG_PAIR
+void pair_tlv_debug(const pair_tlv_values_t *values);
+#endif
+
+#endif  /* !__PAIR_AP_TLV_H__ */
diff --git a/pair_ap/pair.c b/pair_ap/pair.c
new file mode 100644 (file)
index 0000000..6908e8b
--- /dev/null
@@ -0,0 +1,771 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h> // for isprint()
+
+#include <sodium.h>
+
+#include "pair.h"
+#include "pair-internal.h"
+
+extern struct pair_definition pair_client_fruit;
+extern struct pair_definition pair_client_homekit_normal;
+extern struct pair_definition pair_client_homekit_transient;
+extern struct pair_definition pair_server_homekit;
+
+// Must be in sync with enum pair_type
+static struct pair_definition *pair[] = {
+    &pair_client_fruit,
+    &pair_client_homekit_normal,
+    &pair_client_homekit_transient,
+    &pair_server_homekit,
+};
+
+/* -------------------------- SHARED HASHING HELPERS ------------------------ */
+
+int
+hash_init(enum hash_alg alg, HashCTX *c)
+{
+#if CONFIG_OPENSSL
+  switch (alg)
+    {
+      case HASH_SHA1  : return SHA1_Init(&c->sha);
+      case HASH_SHA224: return SHA224_Init(&c->sha256);
+      case HASH_SHA256: return SHA256_Init(&c->sha256);
+      case HASH_SHA384: return SHA384_Init(&c->sha512);
+      case HASH_SHA512: return SHA512_Init(&c->sha512);
+      default:
+        return -1;
+    };
+#elif CONFIG_GCRYPT
+  gcry_error_t err;
+
+  err = gcry_md_open(c, alg, 0);
+
+  if (err)
+    return -1;
+
+  return 0;
+#endif
+}
+
+int
+hash_update(enum hash_alg alg, HashCTX *c, const void *data, size_t len)
+{
+#if CONFIG_OPENSSL
+  switch (alg)
+    {
+      case HASH_SHA1  : return SHA1_Update(&c->sha, data, len);
+      case HASH_SHA224: return SHA224_Update(&c->sha256, data, len);
+      case HASH_SHA256: return SHA256_Update(&c->sha256, data, len);
+      case HASH_SHA384: return SHA384_Update(&c->sha512, data, len);
+      case HASH_SHA512: return SHA512_Update(&c->sha512, data, len);
+      default:
+        return -1;
+    };
+#elif CONFIG_GCRYPT
+  gcry_md_write(*c, data, len);
+  return 0;
+#endif
+}
+
+int
+hash_final(enum hash_alg alg, HashCTX *c, unsigned char *md)
+{
+#if CONFIG_OPENSSL
+  switch (alg)
+    {
+      case HASH_SHA1  : return SHA1_Final(md, &c->sha);
+      case HASH_SHA224: return SHA224_Final(md, &c->sha256);
+      case HASH_SHA256: return SHA256_Final(md, &c->sha256);
+      case HASH_SHA384: return SHA384_Final(md, &c->sha512);
+      case HASH_SHA512: return SHA512_Final(md, &c->sha512);
+      default:
+        return -1;
+    };
+#elif CONFIG_GCRYPT
+  unsigned char *buf = gcry_md_read(*c, alg);
+  if (!buf)
+    return -1;
+
+  memcpy(md, buf, gcry_md_get_algo_dlen(alg));
+  gcry_md_close(*c);
+  return 0;
+#endif
+}
+
+unsigned char *
+hash(enum hash_alg alg, const unsigned char *d, size_t n, unsigned char *md)
+{
+#if CONFIG_OPENSSL
+  switch (alg)
+    {
+      case HASH_SHA1  : return SHA1(d, n, md);
+      case HASH_SHA224: return SHA224(d, n, md);
+      case HASH_SHA256: return SHA256(d, n, md);
+      case HASH_SHA384: return SHA384(d, n, md);
+      case HASH_SHA512: return SHA512(d, n, md);
+      default:
+        return NULL;
+    };
+#elif CONFIG_GCRYPT
+  gcry_md_hash_buffer(alg, md, d, n);
+  return md;
+#endif
+}
+
+int
+hash_length(enum hash_alg alg)
+{
+#if CONFIG_OPENSSL
+  switch (alg)
+    {
+      case HASH_SHA1  : return SHA_DIGEST_LENGTH;
+      case HASH_SHA224: return SHA224_DIGEST_LENGTH;
+      case HASH_SHA256: return SHA256_DIGEST_LENGTH;
+      case HASH_SHA384: return SHA384_DIGEST_LENGTH;
+      case HASH_SHA512: return SHA512_DIGEST_LENGTH;
+      default:
+        return -1;
+    };
+#elif CONFIG_GCRYPT
+  return gcry_md_get_algo_dlen(alg);
+#endif
+}
+
+int
+hash_ab(enum hash_alg alg, unsigned char *md, const unsigned char *m1, int m1_len, const unsigned char *m2, int m2_len)
+{
+  HashCTX ctx;
+
+  hash_init(alg, &ctx);
+  hash_update(alg, &ctx, m1, m1_len);
+  hash_update(alg, &ctx, m2, m2_len);
+  return hash_final(alg, &ctx, md);
+}
+
+bnum
+H_nn_pad(enum hash_alg alg, const bnum n1, const bnum n2)
+{
+  bnum          bn;
+  unsigned char *bin;
+  unsigned char buff[SHA512_DIGEST_LENGTH];
+  int           len_n1 = bnum_num_bytes(n1);
+  int           len_n2 = bnum_num_bytes(n2);
+  int           nbytes = 2 * len_n1;
+
+  if ((len_n2 < 1) || (len_n2 > len_n1))
+    return 0;
+
+  bin = calloc( 1, nbytes );
+
+  bnum_bn2bin(n1, bin, len_n1);
+  bnum_bn2bin(n2, bin + nbytes - len_n2, len_n2);
+  hash( alg, bin, nbytes, buff );
+  free(bin);
+  bnum_bin2bn(bn, buff, hash_length(alg));
+  return bn;
+}
+
+bnum
+H_ns(enum hash_alg alg, const bnum n, const unsigned char *bytes, int len_bytes)
+{
+  bnum          bn;
+  unsigned char buff[SHA512_DIGEST_LENGTH];
+  int           len_n  = bnum_num_bytes(n);
+  int           nbytes = len_n + len_bytes;
+  unsigned char *bin   = malloc(nbytes);
+
+  bnum_bn2bin(n, bin, len_n);
+  memcpy( bin + len_n, bytes, len_bytes );
+  hash( alg, bin, nbytes, buff );
+  free(bin);
+  bnum_bin2bn(bn, buff, hash_length(alg));
+  return bn;
+}
+
+void
+update_hash_n(enum hash_alg alg, HashCTX *ctx, const bnum n)
+{
+  unsigned long len = bnum_num_bytes(n);
+  unsigned char *n_bytes = malloc(len);
+
+  bnum_bn2bin(n, n_bytes, len);
+  hash_update(alg, ctx, n_bytes, len);
+  free(n_bytes);
+}
+
+void
+hash_num(enum hash_alg alg, const bnum n, unsigned char *dest)
+{
+  int           nbytes = bnum_num_bytes(n);
+  unsigned char *bin   = malloc(nbytes);
+
+  bnum_bn2bin(n, bin, nbytes);
+  hash( alg, bin, nbytes, dest );
+  free(bin);
+}
+
+
+/* ----------------------------- OTHER HELPERS -------------------------------*/
+
+#ifdef DEBUG_PAIR
+void
+hexdump(const char *msg, uint8_t *mem, size_t len)
+{
+  int i, j;
+  int hexdump_cols = 16;
+
+  if (msg)
+    printf("%s", msg);
+
+  for (i = 0; i < len + ((len % hexdump_cols) ? (hexdump_cols - len % hexdump_cols) : 0); i++)
+    {
+      if(i % hexdump_cols == 0)
+       printf("0x%06x: ", i);
+
+      if (i < len)
+       printf("%02x ", 0xFF & ((char*)mem)[i]);
+      else
+       printf("   ");
+
+      if (i % hexdump_cols == (hexdump_cols - 1))
+       {
+         for (j = i - (hexdump_cols - 1); j <= i; j++)
+           {
+             if (j >= len)
+               putchar(' ');
+             else if (isprint(((char*)mem)[j]))
+               putchar(0xFF & ((char*)mem)[j]);
+             else
+               putchar('.');
+           }
+
+         putchar('\n');
+       }
+    }
+}
+#endif
+
+
+/* ----------------------------------- API -----------------------------------*/
+
+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)
+{
+  struct pair_setup_context *sctx;
+
+  if (!pair[type]->pair_setup_new)
+    return NULL;
+
+  sctx = calloc(1, sizeof(struct pair_setup_context));
+  if (!sctx)
+    return NULL;
+
+  sctx->type = pair[type];
+
+  if (pair[type]->pair_setup_new(sctx, pin, add_cb, cb_arg, device_id) < 0)
+    {
+      free(sctx);
+      return NULL;
+    }
+
+  return sctx;
+}
+
+void
+pair_setup_free(struct pair_setup_context *sctx)
+{
+  if (!sctx)
+    return;
+
+  if (sctx->type->pair_setup_free)
+    sctx->type->pair_setup_free(sctx);
+
+  free(sctx);
+}
+
+const char *
+pair_setup_errmsg(struct pair_setup_context *sctx)
+{
+  return sctx->errmsg;
+}
+
+int
+pair_setup(uint8_t **out, size_t *out_len, struct pair_setup_context *sctx, const uint8_t *in, size_t in_len)
+{
+  int state;
+  int ret = -1;
+
+  if (!sctx->type->pair_state_get)
+    {
+      sctx->errmsg = "Getting pair state unsupported";
+      return -1;
+    }
+
+  *out = NULL;
+  *out_len = 0;
+
+  state = sctx->type->pair_state_get(&sctx->errmsg, in, in_len);
+  if (state < 0)
+    return -1;
+
+  switch (state)
+    {
+      case 0:
+        *out = pair_setup_request1(out_len, sctx);
+        break;
+      case 1:
+        ret = pair_setup_response1(sctx, in, in_len);
+        if (ret < 0)
+          break;
+        *out = pair_setup_request1(out_len, sctx);
+        break;
+      case 2:
+        ret = pair_setup_response1(sctx, in, in_len);
+        if (ret < 0)
+          break;
+        *out = pair_setup_request2(out_len, sctx);
+        break;
+      case 3:
+        ret = pair_setup_response2(sctx, in, in_len);
+        if (ret < 0)
+          break;
+        *out = pair_setup_request2(out_len, sctx);
+        break;
+      case 4:
+        ret = pair_setup_response2(sctx, in, in_len);
+        if (ret < 0)
+          break;
+        *out = pair_setup_request3(out_len, sctx);
+        break;
+      case 5:
+        ret = pair_setup_response3(sctx, in, in_len);
+        if (ret < 0)
+          break;
+        *out = pair_setup_request3(out_len, sctx);
+        break;
+      case 6:
+        ret = pair_setup_response3(sctx, in, in_len);
+        if (ret < 0)
+          break;
+        break;
+      default:
+        sctx->errmsg = "Setup: Unsupported state";
+        ret = -1;
+    }
+
+  if (ret < 0 || !(*out))
+    return -1;
+
+  return 0;
+}
+
+uint8_t *
+pair_setup_request1(size_t *len, struct pair_setup_context *sctx)
+{
+  if (!sctx->type->pair_setup_request1)
+    {
+      sctx->errmsg = "Setup request 1: Unsupported";
+      return NULL;
+    }
+
+  return sctx->type->pair_setup_request1(len, sctx);
+}
+
+uint8_t *
+pair_setup_request2(size_t *len, struct pair_setup_context *sctx)
+{
+  if (!sctx->type->pair_setup_request2)
+    {
+      sctx->errmsg = "Setup request 2: Unsupported";
+      return NULL;
+    }
+
+  return sctx->type->pair_setup_request2(len, sctx);
+}
+
+uint8_t *
+pair_setup_request3(size_t *len, struct pair_setup_context *sctx)
+{
+  if (!sctx->type->pair_setup_request3)
+    {
+      sctx->errmsg = "Setup request 3: Unsupported";
+      return NULL;
+    }
+
+  return sctx->type->pair_setup_request3(len, sctx);
+}
+
+int
+pair_setup_response1(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len)
+{
+  if (!sctx->type->pair_setup_response1)
+    {
+      sctx->errmsg = "Setup response 1: Unsupported";
+      return -1;
+    }
+
+  return sctx->type->pair_setup_response1(sctx, in, in_len);
+}
+
+int
+pair_setup_response2(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len)
+{
+  if (!sctx->type->pair_setup_response2)
+    {
+      sctx->errmsg = "Setup response 2: Unsupported";
+      return -1;
+    }
+
+  return sctx->type->pair_setup_response2(sctx, in, in_len);
+}
+
+int
+pair_setup_response3(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len)
+{
+  if (!sctx->type->pair_setup_response3)
+    {
+      sctx->errmsg = "Setup response 3: Unsupported";
+      return -1;
+    }
+
+  if (sctx->type->pair_setup_response3(sctx, in, in_len) != 0)
+    return -1;
+
+  return 0;
+}
+
+int
+pair_setup_result(const char **client_setup_keys, struct pair_result **result, struct pair_setup_context *sctx)
+{
+  if (sctx->status != PAIR_STATUS_COMPLETED)
+    {
+      sctx->errmsg = "Setup result: Pair setup has not been completed";
+      return -1;
+    }
+
+  if (sctx->type->pair_setup_result)
+    {
+      if (sctx->type->pair_setup_result(sctx) != 0)
+       return -1;
+    }
+
+  if (client_setup_keys)
+    *client_setup_keys = sctx->result_str;
+  if (result)
+    *result = &sctx->result;
+  return 0;
+}
+
+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)
+{
+  struct pair_verify_context *vctx;
+
+  if (!pair[type]->pair_verify_new)
+    return NULL;
+
+  vctx = calloc(1, sizeof(struct pair_verify_context));
+  if (!vctx)
+    return NULL;
+
+  vctx->type = pair[type];
+
+  if (pair[type]->pair_verify_new(vctx, client_setup_keys, get_cb, cb_arg, device_id) < 0)
+    {
+      free(vctx);
+      return NULL;
+    }
+
+  return vctx;
+}
+
+void
+pair_verify_free(struct pair_verify_context *vctx)
+{
+  if (!vctx)
+    return;
+
+  if (vctx->type->pair_verify_free)
+    vctx->type->pair_verify_free(vctx);
+
+  free(vctx);
+}
+
+const char *
+pair_verify_errmsg(struct pair_verify_context *vctx)
+{
+  return vctx->errmsg;
+}
+
+int
+pair_verify(uint8_t **out, size_t *out_len, struct pair_verify_context *vctx, const uint8_t *in, size_t in_len)
+{
+  int state;
+  int ret = -1;
+
+  if (!vctx->type->pair_state_get)
+    {
+      vctx->errmsg = "Getting pair state unsupported";
+      return -1;
+    }
+
+  *out = NULL;
+  *out_len = 0;
+
+  state = vctx->type->pair_state_get(&vctx->errmsg, in, in_len);
+  if (state < 0)
+    return -1;
+
+  switch (state)
+    {
+      case 0:
+        *out = pair_verify_request1(out_len, vctx);
+        break;
+      case 1:
+        ret = pair_verify_response1(vctx, in, in_len);
+        if (ret < 0)
+          break;
+        *out = pair_verify_request1(out_len, vctx);
+        break;
+      case 2:
+        ret = pair_verify_response1(vctx, in, in_len);
+        if (ret < 0)
+          break;
+        *out = pair_verify_request2(out_len, vctx);
+        break;
+      case 3:
+        ret = pair_verify_response2(vctx, in, in_len);
+        if (ret < 0)
+          break;
+        *out = pair_verify_request2(out_len, vctx);
+        break;
+      case 4:
+        ret = pair_verify_response2(vctx, in, in_len);
+        if (ret < 0)
+          break;
+        break;
+      default:
+        vctx->errmsg = "Verify: Unsupported state";
+        ret = -1;
+    }
+
+  if (ret < 0 || !(*out))
+    return -1;
+
+  return 0;
+}
+
+uint8_t *
+pair_verify_request1(size_t *len, struct pair_verify_context *vctx)
+{
+  if (!vctx->type->pair_verify_request1)
+    {
+      vctx->errmsg = "Verify request 1: Unsupported";
+      return NULL;
+    }
+
+  return vctx->type->pair_verify_request1(len, vctx);
+}
+
+uint8_t *
+pair_verify_request2(size_t *len, struct pair_verify_context *vctx)
+{
+  if (!vctx->type->pair_verify_request2)
+    {
+      vctx->errmsg = "Verify request 2: Unsupported";
+      return NULL;
+    }
+
+  return vctx->type->pair_verify_request2(len, vctx);
+}
+
+int
+pair_verify_response1(struct pair_verify_context *vctx, const uint8_t *in, size_t in_len)
+{
+  if (!vctx->type->pair_verify_response1)
+    {
+      vctx->errmsg = "Verify response 1: Unsupported";
+      return -1;
+    }
+
+  return vctx->type->pair_verify_response1(vctx, in, in_len);
+}
+
+int
+pair_verify_response2(struct pair_verify_context *vctx, const uint8_t *in, size_t in_len)
+{
+  if (!vctx->type->pair_verify_response2)
+    {
+      vctx->errmsg = "Verify response 2: Unsupported";
+      return -1;
+    }
+
+  if (vctx->type->pair_verify_response2(vctx, in, in_len) != 0)
+    return -1;
+
+  return 0;
+}
+
+int
+pair_verify_result(struct pair_result **result, struct pair_verify_context *vctx)
+{
+  if (vctx->status != PAIR_STATUS_COMPLETED)
+    {
+      vctx->errmsg = "Verify result: The pairing verification did not complete";
+      return -1;
+    }
+
+  if (vctx->type->pair_verify_result)
+    {
+      if (vctx->type->pair_verify_result(vctx) != 0)
+       return -1;
+    }
+
+  if (result)
+    *result = &vctx->result;
+  return 0;
+}
+
+struct pair_cipher_context *
+pair_cipher_new(enum pair_type type, int channel, const uint8_t *shared_secret, size_t shared_secret_len)
+{
+  if (!pair[type]->pair_cipher_new)
+    return NULL;
+
+  return pair[type]->pair_cipher_new(pair[type], channel, shared_secret, shared_secret_len);
+}
+
+void
+pair_cipher_free(struct pair_cipher_context *cctx)
+{
+  if (!cctx)
+    return;
+
+  if (!cctx->type->pair_cipher_free)
+    return;
+
+  return cctx->type->pair_cipher_free(cctx);
+}
+
+const char *
+pair_cipher_errmsg(struct pair_cipher_context *cctx)
+{
+  return cctx->errmsg;
+}
+
+ssize_t
+pair_encrypt(uint8_t **ciphertext, size_t *ciphertext_len, const uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx)
+{
+  if (!cctx->type->pair_encrypt)
+    {
+      cctx->errmsg = "Encryption unsupported";
+      return -1;
+    }
+
+  return cctx->type->pair_encrypt(ciphertext, ciphertext_len, plaintext, plaintext_len, cctx);
+}
+
+ssize_t
+pair_decrypt(uint8_t **plaintext, size_t *plaintext_len, const uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx)
+{
+  if (!cctx->type->pair_decrypt)
+    {
+      cctx->errmsg = "Decryption unsupported";
+      return -1;
+    }
+
+  return cctx->type->pair_decrypt(plaintext, plaintext_len, ciphertext, ciphertext_len, cctx);
+}
+
+void
+pair_encrypt_rollback(struct pair_cipher_context *cctx)
+{
+  cctx->encryption_counter = cctx->encryption_counter_prev;
+}
+
+void
+pair_decrypt_rollback(struct pair_cipher_context *cctx)
+{
+  cctx->decryption_counter = cctx->decryption_counter_prev;
+}
+
+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)
+{
+  if (!pair[type]->pair_add)
+    {
+      return -1;
+    }
+
+  return pair[type]->pair_add(out, out_len, add_cb, cb_arg, in, 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)
+{
+  if (!pair[type]->pair_remove)
+    {
+      return -1;
+    }
+
+  return pair[type]->pair_remove(out, out_len, remove_cb, cb_arg, in, 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)
+{
+  if (!pair[type]->pair_list)
+    {
+      return -1;
+    }
+
+  return pair[type]->pair_list(out, out_len, list_cb, cb_arg, in, in_len);
+}
+
+int
+pair_state_get(enum pair_type type, const char **errmsg, const uint8_t *in, size_t in_len)
+{
+  if (!pair[type]->pair_state_get)
+    {
+      *errmsg = "Getting pair state unsupported";
+      return -1;
+    }
+
+  return pair[type]->pair_state_get(errmsg, in, in_len);
+}
+
+void
+pair_public_key_get(enum pair_type type, uint8_t server_public_key[32], const char *device_id)
+{
+  if (!pair[type]->pair_public_key_get)
+    {
+      return;
+    }
+
+  pair[type]->pair_public_key_get(server_public_key, device_id);
+}
diff --git a/pair_ap/pair.h b/pair_ap/pair.h
new file mode 100644 (file)
index 0000000..6324bc1
--- /dev/null
@@ -0,0 +1,266 @@
+#ifndef __PAIR_AP_H__
+#define __PAIR_AP_H__
+
+#include <stdint.h>
+
+#define PAIR_AP_VERSION_MAJOR 0
+#define PAIR_AP_VERSION_MINOR 5
+
+#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__ */
diff --git a/pair_ap/pair_ap-master/.gitignore b/pair_ap/pair_ap-master/.gitignore
new file mode 100644 (file)
index 0000000..a7eeb67
--- /dev/null
@@ -0,0 +1,10 @@
+*.o
+*.lo
+*.a
+*.la
+.dirstamp
+.deps/
+.libs/
+
+client-example
+server-example
diff --git a/pair_ap/pair_ap-master/evrtsp/evrtsp.h b/pair_ap/pair_ap-master/evrtsp/evrtsp.h
new file mode 100644 (file)
index 0000000..5a03cd6
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2010 Julien BLACHE <jb@jblache.org>
+ * Based on evhttp from libevent 1.4.x
+ *
+ * Copyright (c) 2000-2004 Niels Provos <provos@citi.umich.edu>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _EVRTSP_H_
+#define _EVRTSP_H_
+
+#include <event2/event.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <winsock2.h>
+#include <windows.h>
+#undef WIN32_LEAN_AND_MEAN
+#endif
+
+/* Response codes */
+#define RTSP_OK                        200
+#define RTSP_UNAUTHORIZED       401
+#define RTSP_FORBIDDEN          403
+
+struct evrtsp_connection;
+
+/*
+ * Interfaces for making requests
+ */
+enum evrtsp_cmd_type {
+  EVRTSP_REQ_ANNOUNCE,
+  EVRTSP_REQ_OPTIONS,
+  EVRTSP_REQ_SETUP,
+  EVRTSP_REQ_RECORD,
+  EVRTSP_REQ_PAUSE,
+  EVRTSP_REQ_GET_PARAMETER, // Careful using this, some devices do not support it or will hang up eg RAOP_DEV_APEX1_80211G
+  EVRTSP_REQ_SET_PARAMETER,
+  EVRTSP_REQ_FLUSH,
+  EVRTSP_REQ_TEARDOWN,
+  EVRTSP_REQ_POST,
+};
+
+enum evrtsp_request_kind { EVRTSP_REQUEST, EVRTSP_RESPONSE };
+
+struct evrtsp_request {
+#if defined(TAILQ_ENTRY)
+       TAILQ_ENTRY(evrtsp_request) next;
+#else
+struct {
+       struct evrtsp_request *tqe_next;
+       struct evrtsp_request **tqe_prev;
+}       next;
+#endif
+
+       /* the connection object that this request belongs to */
+       struct evrtsp_connection *evcon;
+       int flags;
+#define EVRTSP_REQ_OWN_CONNECTION      0x0001
+
+       struct evkeyvalq *input_headers;
+       struct evkeyvalq *output_headers;
+
+       enum evrtsp_request_kind kind;
+       enum evrtsp_cmd_type type;
+
+       char *uri;                      /* uri after RTSP request was parsed */
+
+       char major;                     /* RTSP Major number */
+       char minor;                     /* RTSP Minor number */
+
+       int response_code;              /* RTSP Response code */
+       char *response_code_line;       /* Readable response */
+
+       struct evbuffer *input_buffer;  /* read data */
+       ev_int64_t ntoread;
+
+       struct evbuffer *output_buffer; /* outgoing post or data */
+
+       /* Callback */
+       void (*cb)(struct evrtsp_request *, void *);
+       void *cb_arg;
+};
+
+/**
+ * Creates a new request object that needs to be filled in with the request
+ * parameters.  The callback is executed when the request completed or an
+ * error occurred.
+ */
+struct evrtsp_request *evrtsp_request_new(
+       void (*cb)(struct evrtsp_request *, void *), void *arg);
+
+/** Frees the request object and removes associated events. */
+void evrtsp_request_free(struct evrtsp_request *req);
+
+/**
+ * A connection object that can be used to for making RTSP requests.  The
+ * connection object tries to establish the connection when it is given an
+ * rtsp request object.
+ */
+struct evrtsp_connection *evrtsp_connection_new(
+       const char *address, unsigned short port);
+
+/** Frees an rtsp connection */
+void evrtsp_connection_free(struct evrtsp_connection *evcon);
+
+/** Set a callback for connection close. */
+void evrtsp_connection_set_closecb(struct evrtsp_connection *evcon,
+    void (*)(struct evrtsp_connection *, void *), void *);
+
+/** Set a callback for encryption/decryption. */
+void evrtsp_connection_set_ciphercb(struct evrtsp_connection *evcon,
+    void (*)(struct evbuffer *, void *, int encrypt), void *);
+
+/**
+ * Associates an event base with the connection - can only be called
+ * on a freshly created connection object that has not been used yet.
+ */
+void evrtsp_connection_set_base(struct evrtsp_connection *evcon,
+    struct event_base *base);
+
+/** Get the remote address and port associated with this connection. */
+void evrtsp_connection_get_peer(struct evrtsp_connection *evcon,
+    char **address, u_short *port);
+
+/** Get the local address, port and family associated with this connection. */
+void
+evrtsp_connection_get_local_address(struct evrtsp_connection *evcon,
+    char **address, u_short *port, int *family);
+
+/** The connection gets ownership of the request */
+int evrtsp_make_request(struct evrtsp_connection *evcon,
+    struct evrtsp_request *req,
+    enum evrtsp_cmd_type type, const char *uri);
+
+const char *evrtsp_request_uri(struct evrtsp_request *req);
+
+/* Interfaces for dealing with headers */
+
+const char *evrtsp_find_header(const struct evkeyvalq *, const char *);
+int evrtsp_remove_header(struct evkeyvalq *, const char *);
+int evrtsp_add_header(struct evkeyvalq *, const char *, const char *);
+void evrtsp_clear_headers(struct evkeyvalq *);
+
+const char *evrtsp_method(enum evrtsp_cmd_type type);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_EVRTSP_H_ */
diff --git a/pair_ap/pair_ap-master/evrtsp/log.h b/pair_ap/pair_ap-master/evrtsp/log.h
new file mode 100644 (file)
index 0000000..7bc6632
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2000-2004 Niels Provos <provos@citi.umich.edu>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _LOG_H_
+#define _LOG_H_
+
+#ifdef __GNUC__
+#define EV_CHECK_FMT(a,b) __attribute__((format(printf, a, b)))
+#else
+#define EV_CHECK_FMT(a,b)
+#endif
+
+void event_err(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3);
+void event_warn(const char *fmt, ...) EV_CHECK_FMT(1,2);
+void event_errx(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3);
+void event_warnx(const char *fmt, ...) EV_CHECK_FMT(1,2);
+void event_msgx(const char *fmt, ...) EV_CHECK_FMT(1,2);
+void _event_debugx(const char *fmt, ...) EV_CHECK_FMT(1,2);
+
+#ifdef USE_DEBUG
+#define event_debug(x) _event_debugx x
+#else
+#define event_debug(x) do {;} while (0)
+#endif
+
+#undef EV_CHECK_FMT
+
+#endif
diff --git a/pair_ap/pair_ap-master/evrtsp/rtsp-internal.h b/pair_ap/pair_ap-master/evrtsp/rtsp-internal.h
new file mode 100644 (file)
index 0000000..dcab48a
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2010 Julien BLACHE <jb@jblache.org>
+ * Based on evhttp from libevent 1.4.x
+ *
+ * Copyright 2001 Niels Provos <provos@citi.umich.edu>
+ * All rights reserved.
+ *
+ * This header file contains definitions for dealing with RTSP requests
+ * that are internal to libevent.  As user of the library, you should not
+ * need to know about these.
+ */
+
+#ifndef _RTSP_H_
+#define _RTSP_H_
+
+#include <event2/buffer.h>
+#include <event2/event_struct.h>
+
+#define RTSP_CONNECT_TIMEOUT   5
+#define RTSP_WRITE_TIMEOUT     30
+#define RTSP_READ_TIMEOUT      30
+
+#define RTSP_PREFIX            "rtsp://"
+
+enum message_read_status {
+       ALL_DATA_READ = 1,
+       MORE_DATA_EXPECTED = 0,
+       DATA_CORRUPTED = -1,
+       REQUEST_CANCELED = -2
+};
+
+enum evrtsp_connection_error {
+       EVCON_RTSP_TIMEOUT,
+       EVCON_RTSP_EOF,
+       EVCON_RTSP_INVALID_HEADER
+};
+
+struct evbuffer;
+struct addrinfo;
+struct evrtsp_request;
+
+/* A stupid connection object - maybe make this a bufferevent later */
+
+enum evrtsp_connection_state {
+       EVCON_DISCONNECTED,     /**< not currently connected not trying either*/
+       EVCON_CONNECTING,       /**< tries to currently connect */
+       EVCON_IDLE,             /**< connection is established */
+       EVCON_READING_FIRSTLINE,/**< reading Request-Line (incoming conn) or
+                                **< Status-Line (outgoing conn) */
+       EVCON_READING_HEADERS,  /**< reading request/response headers */
+       EVCON_READING_BODY,     /**< reading request/response body */
+       EVCON_READING_TRAILER,  /**< reading request/response chunked trailer */
+       EVCON_WRITING           /**< writing request/response headers/body */
+};
+
+struct event_base;
+
+struct evrtsp_connection {
+       int fd;
+       struct event ev;
+       struct event close_ev;
+       struct evbuffer *input_buffer;
+       struct evbuffer *output_buffer;
+       
+       char *bind_address;             /* address to use for binding the src */
+       u_short bind_port;              /* local port for binding the src */
+
+       char *address;                  /* address to connect to */
+       int family;
+       u_short port;
+
+       int flags;
+#define EVRTSP_CON_CLOSEDETECT  0x0004  /* detecting if persistent close */
+
+       int timeout;                    /* timeout in seconds for events */
+       
+       enum evrtsp_connection_state state;
+       int cseq;
+
+       TAILQ_HEAD(evcon_requestq, evrtsp_request) requests;
+       
+       void (*cb)(struct evrtsp_connection *, void *);
+       void *cb_arg;
+       
+       void (*closecb)(struct evrtsp_connection *, void *);
+       void *closecb_arg;
+
+       void (*ciphercb)(struct evbuffer *evbuf, void *, int encrypt);
+       void *ciphercb_arg;
+
+       struct event_base *base;
+};
+
+/* resets the connection; can be reused for more requests */
+void evrtsp_connection_reset(struct evrtsp_connection *);
+
+/* connects if necessary */
+int evrtsp_connection_connect(struct evrtsp_connection *);
+
+/* notifies the current request that it failed; resets connection */
+void evrtsp_connection_fail(struct evrtsp_connection *,
+    enum evrtsp_connection_error error);
+
+int evrtsp_hostportfile(char *, char **, u_short *, char **);
+
+int evrtsp_parse_firstline(struct evrtsp_request *, struct evbuffer*);
+int evrtsp_parse_headers(struct evrtsp_request *, struct evbuffer*);
+
+void evrtsp_start_read(struct evrtsp_connection *);
+void evrtsp_make_header(struct evrtsp_connection *, struct evrtsp_request *);
+
+void evrtsp_write_buffer(struct evrtsp_connection *,
+    void (*)(struct evrtsp_connection *, void *), void *);
+
+#endif /* _RTSP_H */
diff --git a/pair_ap/pair_ap-master/evrtsp/rtsp.c b/pair_ap/pair_ap-master/evrtsp/rtsp.c
new file mode 100644 (file)
index 0000000..82efc54
--- /dev/null
@@ -0,0 +1,1816 @@
+/*
+ * Copyright (C) 2010 Julien BLACHE <jb@jblache.org>
+ * Based on evhttp from libevent 1.4.x
+ *
+ * Copyright (c) 2002-2006 Niels Provos <provos@citi.umich.edu>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/queue.h>
+
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <signal.h>
+#include <time.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <event2/event-config.h>
+
+#undef timeout_pending
+#undef timeout_initialized
+
+#include "evrtsp.h"
+/* #define USE_DEBUG */
+#include "log.h"
+#include "rtsp-internal.h"
+
+// For compability with libevent 2.0 (HAVE_LIBEVENT2_OLD)
+#if defined(_EVENT_HAVE_GETNAMEINFO)
+# define EVENT__HAVE_GETNAMEINFO 1
+#endif
+#if defined(_EVENT_HAVE_GETADDRINFO)
+# define EVENT__HAVE_GETADDRINFO 1
+#endif
+#if defined(_EVENT_HAVE_STRSEP)
+# define EVENT__HAVE_STRSEP 1
+#endif
+
+#ifndef EVENT__HAVE_GETNAMEINFO
+#define NI_MAXSERV 32
+#define NI_MAXHOST 1025
+
+#define NI_NUMERICHOST 1
+#define NI_NUMERICSERV 2
+
+static int
+fake_getnameinfo(const struct sockaddr *sa, size_t salen, char *host, 
+       size_t hostlen, char *serv, size_t servlen, int flags)
+{
+        struct sockaddr_in *sin = (struct sockaddr_in *)sa;
+        int ret;
+        
+        if (serv != NULL) {
+                               char tmpserv[16];
+                               evutil_snprintf(tmpserv, sizeof(tmpserv),
+                                       "%d", ntohs(sin->sin_port));
+                ret = evutil_snprintf(serv, servlen, "%s", tmpserv);
+                if ((ret < 0) || (ret >= servlen))
+                        return (-1);
+        }
+
+        if (host != NULL) {
+                if (flags & NI_NUMERICHOST) {
+                        ret = evutil_snprintf(host, hostlen, "%s", inet_ntoa(sin->sin_addr));
+                        if ((ret < 0) || (ret >= hostlen))
+                                return (-1);
+                        else
+                                return (0);
+                } else {
+                                               struct hostent *hp;
+                        hp = gethostbyaddr((char *)&sin->sin_addr, 
+                            sizeof(struct in_addr), AF_INET);
+                        if (hp == NULL)
+                                return (-2);
+                        
+                        ret = evutil_snprintf(host, hostlen, "%s", hp->h_name);
+                        if ((ret < 0) || (ret >= hostlen))
+                                return (-1);
+                        else
+                                return (0);
+                }
+        }
+        return (0);
+}
+
+#endif
+
+#ifndef EVENT__HAVE_GETADDRINFO
+struct addrinfo {
+       int ai_family;
+       int ai_socktype;
+       int ai_protocol;
+       size_t ai_addrlen;
+       struct sockaddr *ai_addr;
+       struct addrinfo *ai_next;
+};
+static int
+fake_getaddrinfo(const char *hostname, struct addrinfo *ai)
+{
+       struct hostent *he = NULL;
+       struct sockaddr_in *sa;
+       if (hostname) {
+               he = gethostbyname(hostname);
+               if (!he)
+                       return (-1);
+       }
+       ai->ai_family = he ? he->h_addrtype : AF_INET;
+       ai->ai_socktype = SOCK_STREAM;
+       ai->ai_protocol = 0;
+       ai->ai_addrlen = sizeof(struct sockaddr_in);
+       if (NULL == (ai->ai_addr = malloc(ai->ai_addrlen)))
+               return (-1);
+       sa = (struct sockaddr_in*)ai->ai_addr;
+       memset(sa, 0, ai->ai_addrlen);
+       if (he) {
+               sa->sin_family = he->h_addrtype;
+               memcpy(&sa->sin_addr, he->h_addr_list[0], he->h_length);
+       } else {
+               sa->sin_family = AF_INET;
+               sa->sin_addr.s_addr = INADDR_ANY;
+       }
+       ai->ai_next = NULL;
+       return (0);
+}
+static void
+fake_freeaddrinfo(struct addrinfo *ai)
+{
+       free(ai->ai_addr);
+}
+#endif
+
+#ifndef MIN
+#define MIN(a,b) (((a)<(b))?(a):(b))
+#endif
+
+/* wrapper for setting the base from the rtsp server */
+#define EVRTSP_BASE_SET(x, y) do { \
+       if ((x)->base != NULL) event_base_set((x)->base, y);    \
+} while (0) 
+
+extern int debug;
+
+static int socket_connect(int fd, const char *address, unsigned short port);
+static int bind_socket_ai(int family, struct addrinfo *, int reuse);
+static int bind_socket(int family, const char *, u_short, int reuse);
+static void name_from_addr(struct sockaddr *, socklen_t, char **, char **);
+static void evrtsp_connection_start_detectclose(
+       struct evrtsp_connection *evcon);
+static void evrtsp_connection_stop_detectclose(
+       struct evrtsp_connection *evcon);
+static void evrtsp_request_dispatch(struct evrtsp_connection* evcon);
+static void evrtsp_read_firstline(struct evrtsp_connection *evcon,
+                                 struct evrtsp_request *req);
+static void evrtsp_read_header(struct evrtsp_connection *evcon,
+    struct evrtsp_request *req);
+static int evrtsp_add_header_internal(struct evkeyvalq *headers,
+    const char *key, const char *value);
+
+void evrtsp_read(int, short, void *);
+void evrtsp_write(int, short, void *);
+
+#ifndef EVENT__HAVE_STRSEP
+/* strsep replacement for platforms that lack it.  Only works if
+ * del is one character long. */
+static char *
+strsep(char **s, const char *del)
+{
+       char *d, *tok;
+       assert(strlen(del) == 1);
+       if (!s || !*s)
+               return NULL;
+       tok = *s;
+       d = strstr(tok, del);
+       if (d) {
+               *d = '\0';
+               *s = d + 1;
+       } else
+               *s = NULL;
+       return tok;
+}
+#endif
+
+const char *
+evrtsp_method(enum evrtsp_cmd_type type)
+{
+       const char *method;
+
+       switch (type) {
+       case EVRTSP_REQ_ANNOUNCE:
+         method = "ANNOUNCE";
+         break;
+
+       case EVRTSP_REQ_OPTIONS:
+         method = "OPTIONS";
+         break;
+
+       case EVRTSP_REQ_SETUP:
+         method = "SETUP";
+         break;
+
+       case EVRTSP_REQ_RECORD:
+         method = "RECORD";
+         break;
+
+       case EVRTSP_REQ_PAUSE:
+         method = "PAUSE";
+         break;
+
+       case EVRTSP_REQ_GET_PARAMETER:
+         method = "GET_PARAMETER";
+         break;
+
+       case EVRTSP_REQ_SET_PARAMETER:
+         method = "SET_PARAMETER";
+         break;
+
+       case EVRTSP_REQ_FLUSH:
+         method = "FLUSH";
+         break;
+
+       case EVRTSP_REQ_TEARDOWN:
+         method = "TEARDOWN";
+         break;
+
+       case EVRTSP_REQ_POST:
+         method = "POST";
+         break;
+
+       default:
+         method = NULL;
+         break;
+       }
+
+       return (method);
+}
+
+static void
+evrtsp_add_event(struct event *ev, int timeout, int default_timeout)
+{
+       if (timeout != 0) {
+               struct timeval tv;
+               
+               evutil_timerclear(&tv);
+               tv.tv_sec = timeout != -1 ? timeout : default_timeout;
+               event_add(ev, &tv);
+       } else {
+               event_add(ev, NULL);
+       }
+}
+
+void
+evrtsp_write_buffer(struct evrtsp_connection *evcon,
+    void (*cb)(struct evrtsp_connection *, void *), void *arg)
+{
+       event_debug(("%s: preparing to write buffer", __func__));
+
+       /* Set call back */
+       evcon->cb = cb;
+       evcon->cb_arg = arg;
+
+       /* check if the event is already pending */
+       if (event_pending(&evcon->ev, EV_WRITE|EV_TIMEOUT, NULL))
+               event_del(&evcon->ev);
+
+       event_assign(&evcon->ev, evcon->base, evcon->fd, EV_WRITE, evrtsp_write, evcon);
+       evrtsp_add_event(&evcon->ev, evcon->timeout, RTSP_WRITE_TIMEOUT);
+}
+
+static int
+evrtsp_connected(struct evrtsp_connection *evcon)
+{
+       switch (evcon->state) {
+       case EVCON_DISCONNECTED:
+       case EVCON_CONNECTING:
+               return (0);
+       case EVCON_IDLE:
+       case EVCON_READING_FIRSTLINE:
+       case EVCON_READING_HEADERS:
+       case EVCON_READING_BODY:
+       case EVCON_READING_TRAILER:
+       case EVCON_WRITING:
+       default:
+               return (1);
+       }
+}
+
+/*
+ * Create the headers needed for an RTSP request
+ */
+static void
+evrtsp_make_header_request(struct evrtsp_connection *evcon,
+    struct evrtsp_request *req)
+{
+       const char *method;
+
+       /* Generate request line */
+       method = evrtsp_method(req->type);
+       evbuffer_add_printf(evcon->output_buffer, "%s %s RTSP/%d.%d\r\n",
+           method, req->uri, req->major, req->minor);
+
+       /* Content-Length is mandatory, absent means 0 */
+       if ((evbuffer_get_length(req->output_buffer) > 0)
+           && (evrtsp_find_header(req->output_headers, "Content-Length") == NULL))
+         {
+           char size[12];
+           evutil_snprintf(size, sizeof(size), "%ld",
+                           (long)evbuffer_get_length(req->output_buffer));
+           evrtsp_add_header(req->output_headers, "Content-Length", size);
+         }
+}
+
+void
+evrtsp_make_header(struct evrtsp_connection *evcon, struct evrtsp_request *req)
+{
+       struct evkeyval *header;
+
+       evrtsp_make_header_request(evcon, req);
+
+       TAILQ_FOREACH(header, req->output_headers, next) {
+               evbuffer_add_printf(evcon->output_buffer, "%s: %s\r\n",
+                   header->key, header->value);
+       }
+       evbuffer_add(evcon->output_buffer, "\r\n", 2);
+
+       if (evbuffer_get_length(req->output_buffer) > 0) {
+               evbuffer_add_buffer(evcon->output_buffer, req->output_buffer);
+       }
+}
+
+/* Separated host, port and file from URI */
+
+int /* FIXME: needed? */
+evrtsp_hostportfile(char *url, char **phost, u_short *pport, char **pfile)
+{
+       /* XXX not threadsafe. */
+       static char host[1024];
+       static char file[1024];
+       char *p;
+       const char *p2;
+       int len;
+       int ret;
+       u_short port;
+
+       len = strlen(RTSP_PREFIX);
+       if (strncasecmp(url, RTSP_PREFIX, len))
+               return (-1);
+
+       url += len;
+
+       /* We might overrun */
+       ret = evutil_snprintf(host, sizeof(host), "%s", url);
+       if ((ret < 0) || (ret >= sizeof(host)))
+               return (-1);
+
+       p = strchr(host, '/');
+       if (p != NULL) {
+               *p = '\0';
+               p2 = p + 1;
+       } else
+               p2 = NULL;
+
+       if (pfile != NULL) {
+               /* Generate request file */
+               if (p2 == NULL)
+                       p2 = "";
+               evutil_snprintf(file, sizeof(file), "/%s", p2);
+       }
+
+       p = strchr(host, ':');
+       if (p != NULL) {
+               *p = '\0';
+               port = atoi(p + 1);
+
+               if (port == 0)
+                       return (-1);
+       } else
+         return -1;
+
+       if (phost != NULL)
+               *phost = host;
+       if (pport != NULL)
+               *pport = port;
+       if (pfile != NULL)
+               *pfile = file;
+
+       return (0);
+}
+
+void
+evrtsp_connection_fail(struct evrtsp_connection *evcon,
+    enum evrtsp_connection_error error)
+{
+       struct evrtsp_request* req = TAILQ_FIRST(&evcon->requests);
+       void (*cb)(struct evrtsp_request *, void *);
+       void *cb_arg;
+       assert(req != NULL);
+
+       /* save the callback for later; the cb might free our object */
+       cb = req->cb;
+       cb_arg = req->cb_arg;
+
+       TAILQ_REMOVE(&evcon->requests, req, next);
+       evrtsp_request_free(req);
+
+       /* xxx: maybe we should fail all requests??? */
+
+       /* reset the connection */
+       evrtsp_connection_reset(evcon);
+       
+       /* We are trying the next request that was queued on us */
+       if (TAILQ_FIRST(&evcon->requests) != NULL)
+               evrtsp_connection_connect(evcon);
+
+       /* inform the user */
+       if (cb != NULL)
+               (*cb)(NULL, cb_arg);
+}
+
+void
+evrtsp_write(int fd, short what, void *arg)
+{
+       struct evrtsp_connection *evcon = arg;
+       int n;
+
+       if (what == EV_TIMEOUT) {
+               event_warn("%s: write timeout", __func__);
+               evrtsp_connection_fail(evcon, EVCON_RTSP_TIMEOUT);
+               return;
+       }
+
+       n = evbuffer_write(evcon->output_buffer, fd);
+       if (n == -1) {
+               event_warn("%s: evbuffer_write", __func__);
+               evrtsp_connection_fail(evcon, EVCON_RTSP_EOF);
+               return;
+       }
+
+       if (n == 0) {
+               event_warn("%s: write nothing", __func__);
+               evrtsp_connection_fail(evcon, EVCON_RTSP_EOF);
+               return;
+       }
+
+       if (evbuffer_get_length(evcon->output_buffer) != 0) {
+               evrtsp_add_event(&evcon->ev, 
+                   evcon->timeout, RTSP_WRITE_TIMEOUT);
+               return;
+       }
+
+       /* Activate our call back */
+       if (evcon->cb != NULL)
+               (*evcon->cb)(evcon, evcon->cb_arg);
+}
+
+/**
+ * Advance the connection state.
+ * - If this is an outgoing connection, we've just processed the response;
+ *   idle or close the connection.
+ */
+static void
+evrtsp_connection_done(struct evrtsp_connection *evcon)
+{
+       struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests);
+
+       /* idle or close the connection */
+       TAILQ_REMOVE(&evcon->requests, req, next);
+       req->evcon = NULL;
+
+       evcon->state = EVCON_IDLE;
+
+       if (TAILQ_FIRST(&evcon->requests) != NULL) {
+         /*
+          * We have more requests; reset the connection
+          * and deal with the next request.
+          */
+         if (!evrtsp_connected(evcon))
+           evrtsp_connection_connect(evcon);
+         else
+           evrtsp_request_dispatch(evcon);
+       } else {
+         /*
+          * The connection is going to be persistent, but we
+          * need to detect if the other side closes it.
+          */
+         evrtsp_connection_start_detectclose(evcon);
+       }
+
+       /* notify the user of the request */
+       (*req->cb)(req, req->cb_arg);
+
+       evrtsp_request_free(req);
+}
+
+static void /* FIXME: needed? */
+evrtsp_read_trailer(struct evrtsp_connection *evcon, struct evrtsp_request *req)
+{
+       struct evbuffer *buf = evcon->input_buffer;
+
+       switch (evrtsp_parse_headers(req, buf)) {
+       case DATA_CORRUPTED:
+               event_warn("%s: invalid header", __func__);
+               evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER);
+               break;
+       case ALL_DATA_READ:
+               event_del(&evcon->ev);
+               evrtsp_connection_done(evcon);
+               break;
+       case MORE_DATA_EXPECTED:
+       default:
+               evrtsp_add_event(&evcon->ev, evcon->timeout,
+                   RTSP_READ_TIMEOUT);
+               break;
+       }
+}
+
+static void
+evrtsp_read_body(struct evrtsp_connection *evcon, struct evrtsp_request *req)
+{
+       struct evbuffer *buf = evcon->input_buffer;
+
+       if (req->ntoread < 0) {
+               /* Read until connection close. */
+               evbuffer_add_buffer(req->input_buffer, buf);
+       } else if (evbuffer_get_length(buf) >= req->ntoread) {
+               /* Completed content length */
+               evbuffer_add(req->input_buffer, evbuffer_pullup(buf,-1),
+                   (size_t)req->ntoread);
+               evbuffer_drain(buf, (size_t)req->ntoread);
+               req->ntoread = 0;
+               evrtsp_connection_done(evcon);
+               return;
+       }
+       /* Read more! */
+       event_assign(&evcon->ev, evcon->base, evcon->fd, EV_READ, evrtsp_read, evcon);
+       evrtsp_add_event(&evcon->ev, evcon->timeout, RTSP_READ_TIMEOUT);
+}
+
+/*
+ * Reads data into a buffer structure until no more data
+ * can be read on the file descriptor or we have read all
+ * the data that we wanted to read.
+ * Execute callback when done.
+ */
+
+void
+evrtsp_read(int fd, short what, void *arg)
+{
+       struct evrtsp_connection *evcon = arg;
+       struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests);
+       struct evbuffer *buf = evcon->input_buffer;
+       int n;
+
+       if (what == EV_TIMEOUT) {
+               event_warn("%s: read timeout", __func__);
+               evrtsp_connection_fail(evcon, EVCON_RTSP_TIMEOUT);
+               return;
+       }
+       n = evbuffer_read(buf, fd, -1);
+       event_debug(("%s: got %d on %d", __func__, n, fd));
+       
+       if (n == -1) {
+               if (errno != EINTR && errno != EAGAIN) {
+                       event_warn("%s: evbuffer_read", __func__);
+                       evrtsp_connection_fail(evcon, EVCON_RTSP_EOF);
+               } else {
+                       evrtsp_add_event(&evcon->ev, evcon->timeout,
+                           RTSP_READ_TIMEOUT);        
+               }
+               return;
+       } else if (n == 0) {
+               /* Connection closed */
+               evcon->state = EVCON_DISCONNECTED;
+               evrtsp_connection_done(evcon);
+               return;
+       }
+
+       if (evcon->ciphercb)
+               evcon->ciphercb(evcon->input_buffer, evcon->ciphercb_arg, 0);
+
+       switch (evcon->state) {
+       case EVCON_READING_FIRSTLINE:
+               evrtsp_read_firstline(evcon, req);
+               break;
+       case EVCON_READING_HEADERS:
+               evrtsp_read_header(evcon, req);
+               break;
+       case EVCON_READING_BODY:
+               evrtsp_read_body(evcon, req);
+               break;
+       case EVCON_READING_TRAILER:
+               evrtsp_read_trailer(evcon, req);
+               break;
+       case EVCON_DISCONNECTED:
+       case EVCON_CONNECTING:
+       case EVCON_IDLE:
+       case EVCON_WRITING:
+       default:
+               event_errx(1, "%s: illegal connection state %d",
+                          __func__, evcon->state);
+       }
+}
+
+static void
+evrtsp_write_connectioncb(struct evrtsp_connection *evcon, void *arg)
+{
+       /* This is after writing the request to the server */
+       struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests);
+       assert(req != NULL);
+
+       assert(evcon->state == EVCON_WRITING);
+
+       /* We are done writing our header and are now expecting the response */
+       req->kind = EVRTSP_RESPONSE;
+
+       evrtsp_start_read(evcon);
+}
+
+
+
+/*
+ * Clean up a connection object
+ */
+
+void
+evrtsp_connection_free(struct evrtsp_connection *evcon)
+{
+       struct evrtsp_request *req;
+
+       /* notify interested parties that this connection is going down */
+       if (evcon->fd != -1) {
+               if (evrtsp_connected(evcon) && evcon->closecb != NULL)
+                       (*evcon->closecb)(evcon, evcon->closecb_arg);
+       }
+
+       /* remove all requests that might be queued on this connection */
+       while ((req = TAILQ_FIRST(&evcon->requests)) != NULL) {
+               TAILQ_REMOVE(&evcon->requests, req, next);
+               evrtsp_request_free(req);
+       }
+
+       if (event_initialized(&evcon->close_ev))
+               event_del(&evcon->close_ev);
+
+       if (event_initialized(&evcon->ev))
+               event_del(&evcon->ev);
+       
+       if (evcon->fd != -1)
+               EVUTIL_CLOSESOCKET(evcon->fd);
+
+       if (evcon->bind_address != NULL)
+               free(evcon->bind_address);
+
+       if (evcon->address != NULL)
+               free(evcon->address);
+
+       if (evcon->input_buffer != NULL)
+               evbuffer_free(evcon->input_buffer);
+
+       if (evcon->output_buffer != NULL)
+               evbuffer_free(evcon->output_buffer);
+
+       free(evcon);
+}
+
+static void
+evrtsp_request_dispatch(struct evrtsp_connection* evcon)
+{
+       struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests);
+
+       /* this should not usually happy but it's possible */
+       if (req == NULL)
+               return;
+
+       /* delete possible close detection events */
+       evrtsp_connection_stop_detectclose(evcon);
+       
+       /* we assume that the connection is connected already */
+       assert(evcon->state == EVCON_IDLE);
+
+       evcon->state = EVCON_WRITING;
+
+       /* Create the header from the store arguments */
+       evrtsp_make_header(evcon, req);
+
+       /* forked-daapd customisation for encryption */
+       if (evcon->ciphercb)
+               evcon->ciphercb(evcon->output_buffer, evcon->ciphercb_arg, 1);
+
+       evrtsp_write_buffer(evcon, evrtsp_write_connectioncb, NULL);
+}
+
+/* Reset our connection state */
+void
+evrtsp_connection_reset(struct evrtsp_connection *evcon)
+{
+       if (event_initialized(&evcon->ev))
+               event_del(&evcon->ev);
+
+       if (evcon->fd != -1) {
+               /* inform interested parties about connection close */
+               if (evrtsp_connected(evcon) && evcon->closecb != NULL)
+                       (*evcon->closecb)(evcon, evcon->closecb_arg);
+
+               EVUTIL_CLOSESOCKET(evcon->fd);
+               evcon->fd = -1;
+       }
+       evcon->state = EVCON_DISCONNECTED;
+
+       evbuffer_drain(evcon->input_buffer,
+           evbuffer_get_length(evcon->input_buffer));
+       evbuffer_drain(evcon->output_buffer,
+           evbuffer_get_length(evcon->output_buffer));
+}
+
+static void
+evrtsp_detect_close_cb(int fd, short what, void *arg)
+{
+       struct evrtsp_connection *evcon = arg;
+
+       evrtsp_connection_reset(evcon);
+}
+
+static void
+evrtsp_connection_start_detectclose(struct evrtsp_connection *evcon)
+{
+       evcon->flags |= EVRTSP_CON_CLOSEDETECT;
+
+       if (event_initialized(&evcon->close_ev))
+               event_del(&evcon->close_ev);
+       event_assign(&evcon->close_ev, evcon->base, evcon->fd, EV_READ,
+           evrtsp_detect_close_cb, evcon);
+       event_add(&evcon->close_ev, NULL);
+}
+
+static void
+evrtsp_connection_stop_detectclose(struct evrtsp_connection *evcon)
+{
+       evcon->flags &= ~EVRTSP_CON_CLOSEDETECT;
+
+       if (event_initialized(&evcon->close_ev))
+               event_del(&evcon->close_ev);
+}
+
+/*
+ * Call back for asynchronous connection attempt.
+ */
+
+static void
+evrtsp_connectioncb(int fd, short what, void *arg)
+{
+  struct evrtsp_connection *evcon = arg;
+  int error;
+  socklen_t errsz = sizeof(error);
+
+  if (what == EV_TIMEOUT) {
+    event_warnx("%s: connection timeout for \"%s:%d\" on %d",
+                __func__, evcon->address, evcon->port, evcon->fd);
+    goto cleanup;
+  }
+
+  /* Check if the connection completed */
+  if (getsockopt(evcon->fd, SOL_SOCKET, SO_ERROR, (void*)&error,
+                &errsz) == -1) {
+    event_warnx("%s: getsockopt for \"%s:%d\" on %d",
+                __func__, evcon->address, evcon->port, evcon->fd);
+    goto cleanup;
+  }
+
+  if (error) {
+    event_warnx("%s: connect failed for \"%s:%d\" on %d: %s",
+                __func__, evcon->address, evcon->port, evcon->fd,
+                strerror(error));
+    goto cleanup;
+  }
+
+  /* We are connected to the server now */
+  event_debug(("%s: connected to \"%s:%d\" on %d",
+              __func__, evcon->address, evcon->port, evcon->fd));
+
+  evcon->state = EVCON_IDLE;
+
+  /* try to start requests that have queued up on this connection */
+  evrtsp_request_dispatch(evcon);
+  return;
+
+ cleanup:
+  evrtsp_connection_reset(evcon);
+
+  /* for now, we just signal all requests by executing their callbacks */
+  while (TAILQ_FIRST(&evcon->requests) != NULL) {
+    struct evrtsp_request *request = TAILQ_FIRST(&evcon->requests);
+    TAILQ_REMOVE(&evcon->requests, request, next);
+    request->evcon = NULL;
+
+    /* we might want to set an error here */
+    request->cb(request, request->cb_arg);
+    evrtsp_request_free(request);
+  }
+}
+
+/*
+ * Check if we got a valid response code.
+ */
+
+static int
+evrtsp_valid_response_code(int code)
+{
+       if (code == 0)
+               return (0);
+
+       return (1);
+}
+
+/* Parses the status line of an RTSP server */
+
+static int
+evrtsp_parse_response_line(struct evrtsp_request *req, char *line)
+{
+       char *protocol;
+       char *number;
+       const char *readable = "";
+
+       protocol = strsep(&line, " ");
+       if (line == NULL)
+               return (-1);
+       number = strsep(&line, " ");
+       if (line != NULL)
+               readable = line;
+
+       if (strcmp(protocol, "RTSP/1.0") == 0) {
+               req->major = 1;
+               req->minor = 0;
+       } else if (strcmp(protocol, "RTSP/1.1") == 0) {
+               req->major = 1;
+               req->minor = 1;
+       } else {
+               event_warnx("%s: bad protocol \"%s\"",
+                       __func__, protocol);
+               return (-1);
+       }
+
+       req->response_code = atoi(number);
+       if (!evrtsp_valid_response_code(req->response_code)) {
+               event_warnx("%s: bad response code \"%s\"",
+                       __func__, number);
+               return (-1);
+       }
+
+       if ((req->response_code_line = strdup(readable)) == NULL)
+               event_err(1, "%s: strdup", __func__);
+
+       return (0);
+}
+
+const char *
+evrtsp_find_header(const struct evkeyvalq *headers, const char *key)
+{
+       struct evkeyval *header;
+
+       TAILQ_FOREACH(header, headers, next) {
+               if (strcasecmp(header->key, key) == 0)
+                       return (header->value);
+       }
+
+       return (NULL);
+}
+
+void
+evrtsp_clear_headers(struct evkeyvalq *headers)
+{
+       struct evkeyval *header;
+
+       for (header = TAILQ_FIRST(headers);
+           header != NULL;
+           header = TAILQ_FIRST(headers)) {
+               TAILQ_REMOVE(headers, header, next);
+               free(header->key);
+               free(header->value);
+               free(header);
+       }
+}
+
+/*
+ * Returns 0,  if the header was successfully removed.
+ * Returns -1, if the header could not be found.
+ */
+
+int
+evrtsp_remove_header(struct evkeyvalq *headers, const char *key)
+{
+       struct evkeyval *header;
+
+       TAILQ_FOREACH(header, headers, next) {
+               if (strcasecmp(header->key, key) == 0)
+                       break;
+       }
+
+       if (header == NULL)
+               return (-1);
+
+       /* Free and remove the header that we found */
+       TAILQ_REMOVE(headers, header, next);
+       free(header->key);
+       free(header->value);
+       free(header);
+
+       return (0);
+}
+
+static int
+evrtsp_header_is_valid_value(const char *value)
+{
+       const char *p = value;
+
+       while ((p = strpbrk(p, "\r\n")) != NULL) {
+               /* we really expect only one new line */
+               p += strspn(p, "\r\n");
+               /* we expect a space or tab for continuation */
+               if (*p != ' ' && *p != '\t')
+                       return (0);
+       }
+       return (1);
+}
+
+int
+evrtsp_add_header(struct evkeyvalq *headers,
+    const char *key, const char *value)
+{
+       event_debug(("%s: key: %s val: %s", __func__, key, value));
+
+       if (strchr(key, '\r') != NULL || strchr(key, '\n') != NULL) {
+               /* drop illegal headers */
+               event_warn("%s: dropping illegal header key", __func__);
+               return (-1);
+       }
+       
+       if (!evrtsp_header_is_valid_value(value)) {
+               event_warn("%s: dropping illegal header value", __func__);
+               return (-1);
+       }
+
+       return (evrtsp_add_header_internal(headers, key, value));
+}
+
+static int
+evrtsp_add_header_internal(struct evkeyvalq *headers,
+    const char *key, const char *value)
+{
+       struct evkeyval *header = calloc(1, sizeof(struct evkeyval));
+
+       if (header == NULL) {
+               event_warn("%s: calloc", __func__);
+               return (-1);
+       }
+       if ((header->key = strdup(key)) == NULL) {
+               free(header);
+               event_warn("%s: strdup", __func__);
+               return (-1);
+       }
+       if ((header->value = strdup(value)) == NULL) {
+               free(header->key);
+               free(header);
+               event_warn("%s: strdup", __func__);
+               return (-1);
+       }
+
+       TAILQ_INSERT_TAIL(headers, header, next);
+
+       return (0);
+}
+
+/*
+ * Parses header lines from a request or a response into the specified
+ * request object given an event buffer.
+ *
+ * Returns
+ *   DATA_CORRUPTED      on error
+ *   MORE_DATA_EXPECTED  when we need to read more headers
+ *   ALL_DATA_READ       when all headers have been read.
+ */
+
+enum message_read_status
+evrtsp_parse_firstline(struct evrtsp_request *req, struct evbuffer *buffer)
+{
+       char *line;
+       enum message_read_status status = ALL_DATA_READ;
+
+       line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_ANY);
+       if (line == NULL)
+               return (MORE_DATA_EXPECTED);
+
+       switch (req->kind) {
+       case EVRTSP_RESPONSE:
+               if (evrtsp_parse_response_line(req, line) == -1)
+                       status = DATA_CORRUPTED;
+               break;
+       default:
+               status = DATA_CORRUPTED;
+       }
+
+       free(line);
+       return (status);
+}
+
+static int
+evrtsp_append_to_last_header(struct evkeyvalq *headers, const char *line)
+{
+       struct evkeyval *header = TAILQ_LAST(headers, evkeyvalq);
+       char *newval;
+       size_t old_len, line_len;
+
+       if (header == NULL)
+               return (-1);
+
+       old_len = strlen(header->value);
+       line_len = strlen(line);
+
+       newval = realloc(header->value, old_len + line_len + 1);
+       if (newval == NULL)
+               return (-1);
+
+       memcpy(newval + old_len, line, line_len + 1);
+       header->value = newval;
+
+       return (0);
+}
+
+enum message_read_status
+evrtsp_parse_headers(struct evrtsp_request *req, struct evbuffer *buffer)
+{
+       char *line;
+       enum message_read_status status = MORE_DATA_EXPECTED;
+
+       struct evkeyvalq *headers = req->input_headers;
+       while ((line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_CRLF))
+              != NULL) {
+               char *skey, *svalue;
+
+               if (*line == '\0') { /* Last header - Done */
+                       status = ALL_DATA_READ;
+                       free(line);
+                       break;
+               }
+
+               /* Check if this is a continuation line */
+               if (*line == ' ' || *line == '\t') {
+                       if (evrtsp_append_to_last_header(headers, line) == -1)
+                               goto error;
+                       free(line);
+                       continue;
+               }
+
+               /* Processing of header lines */
+               svalue = line;
+               skey = strsep(&svalue, ":");
+               if (svalue == NULL)
+                       goto error;
+
+               svalue += strspn(svalue, " ");
+
+               if (evrtsp_add_header(headers, skey, svalue) == -1)
+                       goto error;
+
+               free(line);
+       }
+
+       return (status);
+
+ error:
+       free(line);
+       return (DATA_CORRUPTED);
+}
+
+static int
+evrtsp_get_body_length(struct evrtsp_request *req)
+{
+       struct evkeyvalq *headers = req->input_headers;
+       const char *content_length;
+
+       content_length = evrtsp_find_header(headers, "Content-Length");
+
+       if (content_length == NULL) {
+         /* If there is no Content-Length: header, a value of 0 is assumed, per spec. */
+               req->ntoread = 0;
+       } else {
+               ev_int64_t ntoread = evutil_strtoll(content_length, NULL, 10);
+               if (*content_length == '\0' || ntoread < 0) {
+                       event_warnx("%s: illegal content length: %s",
+                               __func__, content_length);
+                       return (-1);
+               }
+               req->ntoread = ntoread;
+       }
+               
+       event_debug(("%s: bytes to read: %lld (in buffer %zu)",
+               __func__, req->ntoread,
+               evbuffer_get_length(req->evcon->input_buffer)));
+
+       return (0);
+}
+
+static void
+evrtsp_get_body(struct evrtsp_connection *evcon, struct evrtsp_request *req)
+{
+       evcon->state = EVCON_READING_BODY;
+       if (evrtsp_get_body_length(req) == -1) {
+               event_warn("%s: invalid body", __func__);
+               evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER);
+               return;
+       }
+
+       evrtsp_read_body(evcon, req);
+}
+
+static void
+evrtsp_read_firstline(struct evrtsp_connection *evcon,
+                     struct evrtsp_request *req)
+{
+       enum message_read_status res;
+
+       res = evrtsp_parse_firstline(req, evcon->input_buffer);
+       if (res == DATA_CORRUPTED) {
+               /* Error while reading, terminate */
+               event_warnx("%s: bad header lines on %d",
+                       __func__, evcon->fd);
+               evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER);
+               return;
+       } else if (res == MORE_DATA_EXPECTED) {
+               /* Need more header lines */
+               evrtsp_add_event(&evcon->ev, 
+                    evcon->timeout, RTSP_READ_TIMEOUT);
+               return;
+       }
+
+       evcon->state = EVCON_READING_HEADERS;
+       evrtsp_read_header(evcon, req);
+}
+
+static void
+evrtsp_read_header(struct evrtsp_connection *evcon, struct evrtsp_request *req)
+{
+       enum message_read_status res;
+       int fd = evcon->fd;
+
+       res = evrtsp_parse_headers(req, evcon->input_buffer);
+       if (res == DATA_CORRUPTED) {
+               /* Error while reading, terminate */
+               event_warnx("%s: bad header lines on %d", __func__, fd);
+               evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER);
+               return;
+       } else if (res == MORE_DATA_EXPECTED) {
+               /* Need more header lines */
+               evrtsp_add_event(&evcon->ev, 
+                   evcon->timeout, RTSP_READ_TIMEOUT);
+               return;
+       }
+
+       /* Done reading headers, do the real work */
+       switch (req->kind) {
+       case EVRTSP_RESPONSE:
+         event_debug(("%s: start of read body on %d",
+                      __func__, fd));
+         evrtsp_get_body(evcon, req);
+         break;
+
+       default:
+         event_warnx("%s: bad header on %d", __func__, fd);
+         evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER);
+         break;
+       }
+}
+
+/*
+ * Creates a TCP connection to the specified port and executes a callback
+ * when finished.  Failure or sucess is indicate by the passed connection
+ * object.
+ *
+ * Although this interface accepts a hostname, it is intended to take
+ * only numeric hostnames so that non-blocking DNS resolution can
+ * happen elsewhere.
+ */
+
+struct evrtsp_connection *
+evrtsp_connection_new(const char *address, unsigned short port)
+{
+       struct evrtsp_connection *evcon = NULL;
+       char *intf;
+       char *addr;
+       unsigned char scratch[16];
+       int family;
+
+       if ((addr = strdup(address)) == NULL) {
+               event_warn("%s: strdup failed", __func__);
+               goto error;
+       }
+
+       intf = strchr(addr, '%');
+       if (intf)
+         *intf = '\0';
+
+       if (inet_pton(AF_INET6, addr, scratch) == 1)
+         family = AF_INET6;
+       else if (inet_pton(AF_INET, addr, scratch) == 1)
+         family = AF_INET;
+       else {
+         free(addr);
+         event_warn("%s: address is neither IPv6 nor IPv4", __func__);
+         return NULL;
+       }
+
+       if (intf)
+         *intf = '%';
+
+       event_debug(("Attempting connection to %s:%d", address, port));
+
+       if ((evcon = calloc(1, sizeof(struct evrtsp_connection))) == NULL) {
+               free(addr);
+               event_warn("%s: calloc failed", __func__);
+               goto error;
+       }
+
+       evcon->fd = -1;
+       evcon->port = port;
+
+       evcon->timeout = -1;
+
+       evcon->cseq = 1;
+
+       evcon->family = family;
+       evcon->address = addr;
+
+       if ((evcon->input_buffer = evbuffer_new()) == NULL) {
+               event_warn("%s: evbuffer_new failed", __func__);
+               goto error;
+       }
+
+       if ((evcon->output_buffer = evbuffer_new()) == NULL) {
+               event_warn("%s: evbuffer_new failed", __func__);
+               goto error;
+       }
+       
+       evcon->state = EVCON_DISCONNECTED;
+       TAILQ_INIT(&evcon->requests);
+
+       return (evcon);
+       
+ error:
+       if (evcon != NULL)
+               evrtsp_connection_free(evcon);
+       return (NULL);
+}
+
+void evrtsp_connection_set_base(struct evrtsp_connection *evcon,
+    struct event_base *base)
+{
+       assert(evcon->base == NULL);
+       assert(evcon->state == EVCON_DISCONNECTED);
+       evcon->base = base;
+}
+
+void
+evrtsp_connection_set_timeout(struct evrtsp_connection *evcon,
+    int timeout_in_secs)
+{
+       evcon->timeout = timeout_in_secs;
+}
+
+void
+evrtsp_connection_set_closecb(struct evrtsp_connection *evcon,
+    void (*cb)(struct evrtsp_connection *, void *), void *cbarg)
+{
+       evcon->closecb = cb;
+       evcon->closecb_arg = cbarg;
+}
+
+void
+evrtsp_connection_set_ciphercb(struct evrtsp_connection *evcon,
+    void (*cb)(struct evbuffer *, void *, int encrypt), void *cbarg)
+{
+       evcon->ciphercb = cb;
+       evcon->ciphercb_arg = cbarg;
+}
+
+void
+evrtsp_connection_get_local_address(struct evrtsp_connection *evcon,
+    char **address, u_short *port, int *family)
+{
+  union {
+    struct sockaddr_storage ss;
+    struct sockaddr sa;
+    struct sockaddr_in sin;
+    struct sockaddr_in6 sin6;
+  } addr;
+  socklen_t slen;
+  int ret;
+
+  *address = NULL;
+  *port = 0;
+  memset(&addr, 0, sizeof(addr));
+
+  if (!evrtsp_connected(evcon))
+    return;
+
+  slen = sizeof(struct sockaddr_storage);
+  ret = getsockname(evcon->fd, &addr.sa, &slen);
+  if (ret < 0)
+    return;
+
+  name_from_addr(&addr.sa, slen, address, NULL);
+
+  if (!*address)
+    return;
+
+  *family = addr.sa.sa_family;
+
+  switch (*family)
+    {
+      case AF_INET:
+       *port = ntohs(addr.sin.sin_port);
+       break;
+
+#ifdef AF_INET6
+      case AF_INET6:
+       *port = ntohs(addr.sin6.sin6_port);
+       break;
+#endif
+
+      default:
+       free(*address);
+       *address = NULL;
+
+       event_err(1, "%s: unhandled address family\n", __func__);
+       return;
+    }
+}
+
+void
+evrtsp_connection_get_peer(struct evrtsp_connection *evcon,
+    char **address, u_short *port)
+{
+       *address = evcon->address;
+       *port = evcon->port;
+}
+
+int
+evrtsp_connection_connect(struct evrtsp_connection *evcon)
+{
+       if (evcon->state == EVCON_CONNECTING)
+               return (0);
+       
+       evrtsp_connection_reset(evcon);
+
+       evcon->fd = bind_socket(evcon->family,
+               evcon->bind_address, evcon->bind_port, 0 /*reuse*/);
+       if (evcon->fd == -1) {
+               event_warnx("%s: failed to bind to \"%s\"",
+                       __func__, evcon->bind_address);
+               return (-1);
+       }
+
+       if (socket_connect(evcon->fd, evcon->address, evcon->port) == -1) {
+               EVUTIL_CLOSESOCKET(evcon->fd); evcon->fd = -1;
+               return (-1);
+       }
+
+       /* Set up a callback for successful connection setup */
+       event_assign(&evcon->ev, evcon->base, evcon->fd, EV_WRITE, evrtsp_connectioncb, evcon);
+       evrtsp_add_event(&evcon->ev, evcon->timeout, RTSP_CONNECT_TIMEOUT);
+
+       evcon->state = EVCON_CONNECTING;
+       
+       return (0);
+}
+
+/*
+ * Starts an RTSP request on the provided evrtsp_connection object.
+ * If the connection object is not connected to the server already,
+ * this will start the connection.
+ */
+
+int
+evrtsp_make_request(struct evrtsp_connection *evcon,
+    struct evrtsp_request *req,
+    enum evrtsp_cmd_type type, const char *uri)
+{
+event_debug(("%s: TEST", __func__));
+
+       /* We are making a request */
+       req->kind = EVRTSP_REQUEST;
+       req->type = type;
+       if (req->uri != NULL)
+               free(req->uri);
+       if ((req->uri = strdup(uri)) == NULL)
+               event_err(1, "%s: strdup", __func__);
+
+       /* Set the protocol version if it is not supplied */
+       if (!req->major && !req->minor) {
+               req->major = 1;
+               req->minor = 0;
+       }
+       
+       assert(req->evcon == NULL);
+       req->evcon = evcon;
+       assert(!(req->flags & EVRTSP_REQ_OWN_CONNECTION));
+       
+       TAILQ_INSERT_TAIL(&evcon->requests, req, next);
+
+       /* If the connection object is not connected; make it so */
+       if (!evrtsp_connected(evcon))
+               return (evrtsp_connection_connect(evcon));
+
+       /*
+        * If it's connected already and we are the first in the queue,
+        * then we can dispatch this request immediately.  Otherwise, it
+        * will be dispatched once the pending requests are completed.
+        */
+       if (TAILQ_FIRST(&evcon->requests) == req)
+               evrtsp_request_dispatch(evcon);
+
+       return (0);
+}
+
+/*
+ * Reads data from file descriptor into request structure
+ * Request structure needs to be set up correctly.
+ */
+
+void
+evrtsp_start_read(struct evrtsp_connection *evcon)
+{
+       /* Set up an event to read the headers */
+       if (event_initialized(&evcon->ev))
+               event_del(&evcon->ev);
+       event_assign(&evcon->ev, evcon->base, evcon->fd, EV_READ, evrtsp_read, evcon);
+       evrtsp_add_event(&evcon->ev, evcon->timeout, RTSP_READ_TIMEOUT);
+       evcon->state = EVCON_READING_FIRSTLINE;
+}
+
+static void
+evrtsp_send_done(struct evrtsp_connection *evcon, void *arg)
+{
+       struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests);
+       TAILQ_REMOVE(&evcon->requests, req, next);
+
+       /* delete possible close detection events */
+       evrtsp_connection_stop_detectclose(evcon);
+       
+       assert(req->flags & EVRTSP_REQ_OWN_CONNECTION);
+       evrtsp_request_free(req);
+}
+
+/* Requires that headers and response code are already set up */
+
+static inline void
+evrtsp_send(struct evrtsp_request *req, struct evbuffer *databuf)
+{
+       struct evrtsp_connection *evcon = req->evcon;
+
+       if (evcon == NULL) {
+               evrtsp_request_free(req);
+               return;
+       }
+
+       assert(TAILQ_FIRST(&evcon->requests) == req);
+
+       /* xxx: not sure if we really should expose the data buffer this way */
+       if (databuf != NULL)
+               evbuffer_add_buffer(req->output_buffer, databuf);
+       
+       /* Adds headers to the response */
+       evrtsp_make_header(evcon, req);
+
+       evrtsp_write_buffer(evcon, evrtsp_send_done, NULL);
+}
+
+/*
+ * Request related functions
+ */
+
+struct evrtsp_request *
+evrtsp_request_new(void (*cb)(struct evrtsp_request *, void *), void *arg)
+{
+       struct evrtsp_request *req = NULL;
+
+       /* Allocate request structure */
+       if ((req = calloc(1, sizeof(struct evrtsp_request))) == NULL) {
+               event_warn("%s: calloc", __func__);
+               goto error;
+       }
+
+       req->kind = EVRTSP_RESPONSE;
+       req->input_headers = calloc(1, sizeof(struct evkeyvalq));
+       if (req->input_headers == NULL) {
+               event_warn("%s: calloc", __func__);
+               goto error;
+       }
+       TAILQ_INIT(req->input_headers);
+
+       req->output_headers = calloc(1, sizeof(struct evkeyvalq));
+       if (req->output_headers == NULL) {
+               event_warn("%s: calloc", __func__);
+               goto error;
+       }
+       TAILQ_INIT(req->output_headers);
+
+       if ((req->input_buffer = evbuffer_new()) == NULL) {
+               event_warn("%s: evbuffer_new", __func__);
+               goto error;
+       }
+
+       if ((req->output_buffer = evbuffer_new()) == NULL) {
+               event_warn("%s: evbuffer_new", __func__);
+               goto error;
+       }
+
+       req->cb = cb;
+       req->cb_arg = arg;
+
+       return (req);
+
+ error:
+       if (req != NULL)
+               evrtsp_request_free(req);
+       return (NULL);
+}
+
+void
+evrtsp_request_free(struct evrtsp_request *req)
+{
+       if (req->uri != NULL)
+               free(req->uri);
+       if (req->response_code_line != NULL)
+               free(req->response_code_line);
+
+       evrtsp_clear_headers(req->input_headers);
+       free(req->input_headers);
+
+       evrtsp_clear_headers(req->output_headers);
+       free(req->output_headers);
+
+       if (req->input_buffer != NULL)
+               evbuffer_free(req->input_buffer);
+
+       if (req->output_buffer != NULL)
+               evbuffer_free(req->output_buffer);
+
+       free(req);
+}
+
+/*
+ * Allows for inspection of the request URI
+ */
+
+const char *
+evrtsp_request_uri(struct evrtsp_request *req) {
+       if (req->uri == NULL)
+               event_warn("%s: request has no uri", __func__);
+       return (req->uri);
+}
+
+/*
+ * Network helper functions that we do not want to export to the rest of
+ * the world.
+ */
+#if 0 /* Unused */
+static struct addrinfo *
+addr_from_name(char *address)
+{
+#ifdef EVENT__HAVE_GETADDRINFO
+        struct addrinfo ai, *aitop;
+        int ai_result;
+
+        memset(&ai, 0, sizeof(ai));
+        ai.ai_family = AF_INET;
+        ai.ai_socktype = SOCK_RAW;
+        ai.ai_flags = 0;
+        if ((ai_result = getaddrinfo(address, NULL, &ai, &aitop)) != 0) {
+                if ( ai_result == EAI_SYSTEM )
+                        event_warn("getaddrinfo");
+                else
+                        event_warnx("getaddrinfo: %s", gai_strerror(ai_result));
+        }
+
+       return (aitop);
+#else
+       assert(0);
+       return NULL; /* XXXXX Use gethostbyname, if this function is ever used. */
+#endif
+}
+#endif
+
+static void
+name_from_addr(struct sockaddr *sa, socklen_t salen,
+    char **phost, char **pport)
+{
+       char ntop[NI_MAXHOST];
+       char strport[NI_MAXSERV];
+       int ni_result;
+
+#ifdef EVENT__HAVE_GETNAMEINFO
+       ni_result = getnameinfo(sa, salen,
+               ntop, sizeof(ntop), strport, sizeof(strport),
+               NI_NUMERICHOST|NI_NUMERICSERV);
+       
+       if (ni_result != 0) {
+               if (ni_result == EAI_SYSTEM)
+                       event_err(1, "getnameinfo failed");
+               else
+                       event_errx(1, "getnameinfo failed: %s", gai_strerror(ni_result));
+               return;
+       }
+#else
+       ni_result = fake_getnameinfo(sa, salen,
+               ntop, sizeof(ntop), strport, sizeof(strport),
+               NI_NUMERICHOST|NI_NUMERICSERV);
+       if (ni_result != 0)
+                       return;
+#endif
+       if (phost)
+         *phost = strdup(ntop);
+       if (pport)
+         *pport = strdup(strport);
+}
+
+/* Create a non-blocking socket and bind it */
+/* todo: rename this function */
+static int
+bind_socket_ai(int family, struct addrinfo *ai, int reuse)
+{
+        int fd, on = 1, r;
+       int serrno;
+
+       if (ai)
+         family = ai->ai_family;
+
+        /* Create listen socket */
+        fd = socket(family, SOCK_STREAM, 0);
+        if (fd == -1) {
+                event_warn("socket");
+                return (-1);
+        }
+
+        if (evutil_make_socket_nonblocking(fd) < 0)
+                goto out;
+
+#ifndef WIN32
+        if (fcntl(fd, F_SETFD, 1) == -1) {
+                event_warn("fcntl(F_SETFD)");
+                goto out;
+        }
+#endif
+
+       if (family == AF_INET6)
+         setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
+
+        setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on));
+       if (reuse) {
+               setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+                   (void *)&on, sizeof(on));
+       }
+
+       if (ai != NULL) {
+               r = bind(fd, ai->ai_addr, ai->ai_addrlen);
+               if (r == -1)
+                       goto out;
+       }
+
+       return (fd);
+
+ out:
+       serrno = EVUTIL_SOCKET_ERROR();
+       EVUTIL_CLOSESOCKET(fd);
+       EVUTIL_SET_SOCKET_ERROR(serrno);
+       return (-1);
+}
+
+static struct addrinfo *
+make_addrinfo(const char *address, u_short port)
+{
+        struct addrinfo *aitop = NULL;
+
+#ifdef EVENT__HAVE_GETADDRINFO
+        struct addrinfo ai;
+        char strport[NI_MAXSERV];
+        int ai_result;
+
+        memset(&ai, 0, sizeof(ai));
+        ai.ai_family = AF_UNSPEC;
+        ai.ai_socktype = SOCK_STREAM;
+        ai.ai_flags = AI_PASSIVE;  /* turn NULL host name into INADDR_ANY */
+        evutil_snprintf(strport, sizeof(strport), "%d", port);
+        if ((ai_result = getaddrinfo(address, strport, &ai, &aitop)) != 0) {
+                if ( ai_result == EAI_SYSTEM )
+                        event_warn("getaddrinfo");
+                else
+                        event_warnx("getaddrinfo: %s", gai_strerror(ai_result));
+               return (NULL);
+        }
+#else
+       static int cur;
+       static struct addrinfo ai[2]; /* We will be returning the address of some of this memory so it has to last even after this call. */
+       if (++cur == 2) cur = 0;   /* allow calling this function twice */
+
+       if (fake_getaddrinfo(address, &ai[cur]) < 0) {
+               event_warn("fake_getaddrinfo");
+               return (NULL);
+       }
+       aitop = &ai[cur];
+       ((struct sockaddr_in *) aitop->ai_addr)->sin_port = htons(port);
+#endif
+
+       return (aitop);
+}
+
+static int
+bind_socket(int family, const char *address, u_short port, int reuse)
+{
+       int fd;
+       struct addrinfo *aitop = NULL;
+
+       /* just create an unbound socket */
+       if (address == NULL && port == 0)
+               return bind_socket_ai(family, NULL, 0);
+               
+       aitop = make_addrinfo(address, port);
+
+       if (aitop == NULL)
+               return (-1);
+
+       fd = bind_socket_ai(family, aitop, reuse);
+
+#ifdef EVENT__HAVE_GETADDRINFO
+       freeaddrinfo(aitop);
+#else
+       fake_freeaddrinfo(aitop);
+#endif
+
+       return (fd);
+}
+
+static int
+socket_connect(int fd, const char *address, unsigned short port)
+{
+       struct addrinfo *ai = make_addrinfo(address, port);
+       int res = -1;
+
+       if (ai == NULL) {
+               event_warnx("%s: make_addrinfo: \"%s:%d\"",
+                       __func__, address, port);
+               return (-1);
+       }
+
+       if (connect(fd, ai->ai_addr, ai->ai_addrlen) == -1) {
+#ifdef WIN32
+               int tmp_error = WSAGetLastError();
+               if (tmp_error != WSAEWOULDBLOCK && tmp_error != WSAEINVAL &&
+                   tmp_error != WSAEINPROGRESS) {
+                       goto out;
+               }
+#else
+               if (errno != EINPROGRESS) {
+                       goto out;
+               }
+#endif
+       }
+
+       /* everything is fine */
+       res = 0;
+
+out:
+#ifdef EVENT__HAVE_GETADDRINFO
+       freeaddrinfo(ai);
+#else
+       fake_freeaddrinfo(ai);
+#endif
+
+       return (res);
+}
diff --git a/pair_ap/pair_fruit.c b/pair_ap/pair_fruit.c
new file mode 100644 (file)
index 0000000..42e0b06
--- /dev/null
@@ -0,0 +1,1063 @@
+/*
+ *
+ * The Secure Remote Password 6a implementation is adapted from:
+ *  - Tom Cocagne
+ *    <https://github.com/cocagne/csrp>
+ *
+ * The MIT License (MIT)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <plist/plist.h>
+#include <sodium.h>
+
+#include <assert.h>
+
+#include "pair-internal.h"
+
+/* ----------------------------- DEFINES ETC ------------------------------- */
+
+#define USERNAME "12:34:56:78:90:AB"
+#define EPK_LENGTH 32
+#define AUTHTAG_LENGTH 16
+#define AES_SETUP_KEY  "Pair-Setup-AES-Key"
+#define AES_SETUP_IV   "Pair-Setup-AES-IV"
+#define AES_VERIFY_KEY "Pair-Verify-AES-Key"
+#define AES_VERIFY_IV  "Pair-Verify-AES-IV"
+
+
+/* ---------------------------------- SRP ---------------------------------- */
+
+typedef enum
+{
+  SRP_NG_2048,
+  SRP_NG_CUSTOM
+} SRP_NGType;
+
+typedef struct
+{
+  bnum N;
+  bnum g;
+} NGConstant;
+
+struct SRPUser
+{
+  enum hash_alg     alg;
+  NGConstant        *ng;
+
+  bnum a;
+  bnum A;
+  bnum S;
+
+  const unsigned char *bytes_A;
+  int           authenticated;
+
+  char          *username;
+  unsigned char *password;
+  int           password_len;
+
+  unsigned char M           [SHA512_DIGEST_LENGTH];
+  unsigned char H_AMK       [SHA512_DIGEST_LENGTH];
+  unsigned char session_key [2 * SHA512_DIGEST_LENGTH]; // See hash_session_key()
+  int           session_key_len;
+};
+
+struct NGHex
+{
+  const char *n_hex;
+  const char *g_hex;
+};
+
+// We only need 2048 right now, but keep the array in case we want to add others later
+// All constants here were pulled from Appendix A of RFC 5054
+static struct NGHex global_Ng_constants[] =
+{
+  { /* 2048 */
+    "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4"
+    "A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60"
+    "95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF"
+    "747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907"
+    "8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861"
+    "60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB"
+    "FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73",
+    "2"
+  },
+  {0,0} /* null sentinel */
+};
+
+
+static NGConstant *
+new_ng(SRP_NGType ng_type, const char *n_hex, const char *g_hex)
+{
+  NGConstant *ng = calloc(1, sizeof(NGConstant));
+
+  if ( ng_type != SRP_NG_CUSTOM )
+    {
+      n_hex = global_Ng_constants[ ng_type ].n_hex;
+      g_hex = global_Ng_constants[ ng_type ].g_hex;
+    }
+
+  bnum_hex2bn(ng->N, n_hex);
+  bnum_hex2bn(ng->g, g_hex);
+
+  return ng;
+}
+
+static void
+free_ng(NGConstant * ng)
+{
+  if (!ng)
+    return;
+
+  bnum_free(ng->N);
+  bnum_free(ng->g);
+  free(ng);
+}
+
+static bnum
+calculate_x(enum hash_alg alg, const bnum salt, const char *username, const unsigned char *password, int password_len)
+{
+  unsigned char ucp_hash[SHA512_DIGEST_LENGTH];
+  HashCTX       ctx;
+
+  hash_init( alg, &ctx );
+  hash_update( alg, &ctx, username, strlen(username) );
+  hash_update( alg, &ctx, ":", 1 );
+  hash_update( alg, &ctx, password, password_len );
+  hash_final( alg, &ctx, ucp_hash );
+
+  return H_ns( alg, salt, ucp_hash, hash_length(alg) );
+}
+
+static int
+hash_session_key(enum hash_alg alg, const bnum n, unsigned char *dest)
+{
+  int           nbytes = bnum_num_bytes(n);
+  unsigned char *bin   = malloc(nbytes);
+  unsigned char fourbytes[4] = { 0 }; // Only God knows the reason for this, and perhaps some poor soul at Apple
+
+  bnum_bn2bin(n, bin, nbytes);
+
+  hash_ab(alg, dest, bin, nbytes, fourbytes, sizeof(fourbytes));
+
+  fourbytes[3] = 1; // Again, only ...
+
+  hash_ab(alg, dest + hash_length(alg), bin, nbytes, fourbytes, sizeof(fourbytes));
+
+  free(bin);
+
+  return (2 * hash_length(alg));
+}
+
+static void
+calculate_M(enum hash_alg alg, NGConstant *ng, unsigned char *dest, const char *I, const bnum s,
+            const bnum A, const bnum B, const unsigned char *K, int K_len)
+{
+  unsigned char H_N[ SHA512_DIGEST_LENGTH ];
+  unsigned char H_g[ SHA512_DIGEST_LENGTH ];
+  unsigned char H_I[ SHA512_DIGEST_LENGTH ];
+  unsigned char H_xor[ SHA512_DIGEST_LENGTH ];
+  HashCTX       ctx;
+  int           i = 0;
+  int           hash_len = hash_length(alg);
+
+  hash_num( alg, ng->N, H_N );
+  hash_num( alg, ng->g, H_g );
+
+  hash(alg, (const unsigned char *)I, strlen(I), H_I);
+
+  for (i=0; i < hash_len; i++ )
+    H_xor[i] = H_N[i] ^ H_g[i];
+    
+  hash_init( alg, &ctx );
+
+  hash_update( alg, &ctx, H_xor, hash_len );
+  hash_update( alg, &ctx, H_I,   hash_len );
+  update_hash_n( alg, &ctx, s );
+  update_hash_n( alg, &ctx, A );
+  update_hash_n( alg, &ctx, B );
+  hash_update( alg, &ctx, K, K_len );
+
+  hash_final( alg, &ctx, dest );
+}
+
+static void
+calculate_H_AMK(enum hash_alg alg, unsigned char *dest, const bnum A, const unsigned char * M, const unsigned char * K, int K_len)
+{
+  HashCTX ctx;
+
+  hash_init( alg, &ctx );
+
+  update_hash_n( alg, &ctx, A );
+  hash_update( alg, &ctx, M, hash_length(alg) );
+  hash_update( alg, &ctx, K, K_len );
+
+  hash_final( alg, &ctx, dest );
+}
+
+static struct SRPUser *
+srp_user_new(enum hash_alg alg, SRP_NGType ng_type, const char *username,
+             const unsigned char *bytes_password, int len_password,
+             const char *n_hex, const char *g_hex)
+{
+  struct SRPUser  *usr  = calloc(1, sizeof(struct SRPUser));
+  int              ulen = strlen(username) + 1;
+
+  if (!usr)
+    goto err_exit;
+
+  usr->alg = alg;
+  usr->ng  = new_ng( ng_type, n_hex, g_hex );
+
+  bnum_new(usr->a);
+  bnum_new(usr->A);
+  bnum_new(usr->S);
+
+  if (!usr->ng || !usr->a || !usr->A || !usr->S)
+    goto err_exit;
+
+  usr->username     = malloc(ulen);
+  usr->password     = malloc(len_password);
+  usr->password_len = len_password;
+
+  if (!usr->username || !usr->password)
+    goto err_exit;
+
+  memcpy(usr->username, username,       ulen);
+  memcpy(usr->password, bytes_password, len_password);
+
+  usr->authenticated = 0;
+  usr->bytes_A = 0;
+
+  return usr;
+
+ err_exit:
+  if (!usr)
+    return NULL;
+
+  bnum_free(usr->a);
+  bnum_free(usr->A);
+  bnum_free(usr->S);
+
+  free(usr->username);
+  if (usr->password)
+    {
+      memset(usr->password, 0, usr->password_len);
+      free(usr->password);
+    }
+  free(usr);
+
+  return NULL;
+}
+
+static void
+srp_user_free(struct SRPUser *usr)
+{
+  if(!usr)
+    return;
+
+  bnum_free(usr->a);
+  bnum_free(usr->A);
+  bnum_free(usr->S);
+
+  free_ng(usr->ng);
+
+  memset(usr->password, 0, usr->password_len);
+
+  free(usr->username);
+  free(usr->password);
+  free((char *)usr->bytes_A);
+
+  memset(usr, 0, sizeof(*usr));
+  free(usr);
+}
+
+static int
+srp_user_is_authenticated(struct SRPUser *usr)
+{
+  return usr->authenticated;
+}
+
+static const unsigned char *
+srp_user_get_session_key(struct SRPUser *usr, int *key_length)
+{
+  if (key_length)
+    *key_length = usr->session_key_len;
+  return usr->session_key;
+}
+
+/* Output: username, bytes_A, len_A */
+static void
+srp_user_start_authentication(struct SRPUser *usr, const char **username,
+                              const unsigned char **bytes_A, int *len_A)
+{
+  bnum_random(usr->a, 256);
+  bnum_modexp(usr->A, usr->ng->g, usr->a, usr->ng->N);
+
+  *len_A   = bnum_num_bytes(usr->A);
+  *bytes_A = malloc(*len_A);
+
+  if (!*bytes_A)
+    {
+      *len_A = 0;
+      *bytes_A = 0;
+      *username = 0;
+      return;
+    }
+
+  bnum_bn2bin(usr->A, (unsigned char *) *bytes_A, *len_A);
+
+  usr->bytes_A = *bytes_A;
+  *username = usr->username;
+}
+
+/* Output: bytes_M. Buffer length is SHA512_DIGEST_LENGTH */
+static void
+srp_user_process_challenge(struct SRPUser *usr, const unsigned char *bytes_s, int len_s,
+                           const unsigned char *bytes_B, int len_B,
+                           const unsigned char **bytes_M, int *len_M )
+{
+  bnum s, B, k, v;
+  bnum tmp1, tmp2, tmp3;
+  bnum u, x;
+
+  *len_M = 0;
+  *bytes_M = 0;
+
+  bnum_bin2bn(s, bytes_s, len_s);
+  bnum_bin2bn(B, bytes_B, len_B);
+  k    = H_nn_pad(usr->alg, usr->ng->N, usr->ng->g);
+  bnum_new(v);
+  bnum_new(tmp1);
+  bnum_new(tmp2);
+  bnum_new(tmp3);
+
+  if (!s || !B || !k || !v || !tmp1 || !tmp2 || !tmp3)
+    goto cleanup1;
+
+  u = H_nn_pad(usr->alg, usr->A, B);
+  x = calculate_x(usr->alg, s, usr->username, usr->password, usr->password_len);
+  if (!u || !x)
+    goto cleanup2;
+
+  // SRP-6a safety check
+  if (!bnum_is_zero(B) && !bnum_is_zero(u))
+    {
+      bnum_modexp(v, usr->ng->g, x, usr->ng->N);
+
+      // S = (B - k*(g^x)) ^ (a + ux)
+      bnum_mul(tmp1, u, x);
+      bnum_add(tmp2, usr->a, tmp1);        // tmp2 = (a + ux)
+      bnum_modexp(tmp1, usr->ng->g, x, usr->ng->N);
+      bnum_mul(tmp3, k, tmp1);             // tmp3 = k*(g^x)
+      bnum_sub(tmp1, B, tmp3);             // tmp1 = (B - K*(g^x))
+      bnum_modexp(usr->S, tmp1, tmp2, usr->ng->N);
+
+      usr->session_key_len = hash_session_key(usr->alg, usr->S, usr->session_key);
+
+      calculate_M(usr->alg, usr->ng, usr->M, usr->username, s, usr->A, B, usr->session_key, usr->session_key_len);
+      calculate_H_AMK(usr->alg, usr->H_AMK, usr->A, usr->M, usr->session_key, usr->session_key_len);
+
+      *bytes_M = usr->M;
+      if (len_M)
+        *len_M = hash_length(usr->alg);
+    }
+  else
+    {
+      *bytes_M = NULL;
+      if (len_M)
+        *len_M   = 0;
+    }
+
+ cleanup2:
+  bnum_free(x);
+  bnum_free(u);
+ cleanup1:
+  bnum_free(tmp3);
+  bnum_free(tmp2);
+  bnum_free(tmp1);
+  bnum_free(v);
+  bnum_free(k);
+  bnum_free(B);
+  bnum_free(s);
+}
+
+static void
+srp_user_verify_session(struct SRPUser *usr, const unsigned char *bytes_HAMK)
+{
+  if (memcmp(usr->H_AMK, bytes_HAMK, hash_length(usr->alg)) == 0)
+    usr->authenticated = 1;
+}
+
+
+/* -------------------------------- HELPERS -------------------------------- */
+
+static int
+encrypt_gcm(unsigned char *ciphertext, int ciphertext_len, unsigned char *tag, unsigned char *plaintext, int plaintext_len, unsigned char *key, unsigned char *iv, const char **errmsg)
+{
+#ifdef CONFIG_OPENSSL
+  EVP_CIPHER_CTX *ctx;
+  int len;
+
+  *errmsg = NULL;
+
+  if ( !(ctx = EVP_CIPHER_CTX_new()) ||
+       (EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL) != 1) ||
+       (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL) != 1) ||
+       (EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) != 1) )
+    {
+      *errmsg = "Error initialising AES 128 GCM encryption";
+      goto error;
+    }
+
+  if (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len) != 1)
+    {
+      *errmsg = "Error GCM encrypting";
+      goto error;
+    }
+
+  if (len > ciphertext_len)
+    {
+      *errmsg = "Bug! Buffer overflow";
+      goto error;
+    }
+
+  if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1)
+    {
+      *errmsg = "Error finalising GCM encryption";
+      goto error;
+    }
+
+  if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, AUTHTAG_LENGTH, tag) != 1)
+    {
+      *errmsg = "Error getting authtag";
+      goto error;
+    }
+
+  EVP_CIPHER_CTX_free(ctx);
+  return 0;
+
+ error:
+  EVP_CIPHER_CTX_free(ctx);
+  return -1;
+#elif CONFIG_GCRYPT
+  gcry_cipher_hd_t hd;
+  gcry_error_t err;
+
+  err = gcry_cipher_open(&hd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, 0);
+  if (err)
+    {
+      *errmsg = "Error initialising AES 128 GCM encryption";
+      return -1;
+    }
+
+  err = gcry_cipher_setkey(hd, key, gcry_cipher_get_algo_keylen(GCRY_CIPHER_AES128));
+  if (err)
+    {
+      *errmsg = "Could not set key for AES 128 GCM";
+      goto error;
+    }
+
+  err = gcry_cipher_setiv(hd, iv, gcry_cipher_get_algo_blklen(GCRY_CIPHER_AES128));
+  if (err)
+    {
+      *errmsg = "Could not set iv for AES 128 GCM";
+      goto error;
+    }
+
+  err = gcry_cipher_encrypt(hd, ciphertext, ciphertext_len, plaintext, plaintext_len);
+  if (err)
+    {
+      *errmsg = "Error GCM encrypting";
+      goto error;
+    }
+
+  err = gcry_cipher_gettag(hd, tag, AUTHTAG_LENGTH);
+  if (err)
+    {
+      *errmsg = "Error getting authtag";
+      goto error;
+    }
+
+  gcry_cipher_close(hd);
+  return 0;
+
+ error:
+  gcry_cipher_close(hd);
+  return -1;
+#endif
+}
+
+static int
+encrypt_ctr(unsigned char *ciphertext, int ciphertext_len,
+            unsigned char *plaintext1, int plaintext1_len, unsigned char *plaintext2, int plaintext2_len,
+            unsigned char *key, unsigned char *iv, const char **errmsg)
+{
+#ifdef CONFIG_OPENSSL
+  EVP_CIPHER_CTX *ctx;
+  int len;
+
+  *errmsg = NULL;
+
+  if ( !(ctx = EVP_CIPHER_CTX_new()) || (EVP_EncryptInit_ex(ctx, EVP_aes_128_ctr(), NULL, key, iv) != 1) )
+    {
+      *errmsg = "Error initialising AES 128 CTR encryption";
+      goto error;
+    }
+
+  if ( (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext1, plaintext1_len) != 1) ||
+       (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext2, plaintext2_len) != 1) )
+    {
+      *errmsg = "Error CTR encrypting";
+      goto error;
+    }
+
+  if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1)
+    {
+      *errmsg = "Error finalising encryption";
+      goto error;
+    }
+
+  EVP_CIPHER_CTX_free(ctx);
+  return 0;
+
+ error:
+  EVP_CIPHER_CTX_free(ctx);
+  return -1;
+#elif CONFIG_GCRYPT
+  gcry_cipher_hd_t hd;
+  gcry_error_t err;
+
+  err = gcry_cipher_open(&hd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR, 0);
+  if (err)
+    {
+      *errmsg = "Error initialising AES 128 CTR encryption";
+      return -1;
+    }
+  err = gcry_cipher_setkey(hd, key, gcry_cipher_get_algo_keylen(GCRY_CIPHER_AES128));
+  if (err)
+    {
+      *errmsg = "Could not set key for AES 128 CTR";
+      goto error;
+    }
+
+  err = gcry_cipher_setctr(hd, iv, gcry_cipher_get_algo_blklen(GCRY_CIPHER_AES128));
+  if (err)
+    {
+      *errmsg = "Could not set iv for AES 128 CTR";
+      goto error;
+    }
+
+  err = gcry_cipher_encrypt(hd, ciphertext, ciphertext_len, plaintext1, plaintext1_len);
+  if (err)
+    {
+      *errmsg = "Error CTR encrypting plaintext 1";
+      goto error;
+    }
+
+  err = gcry_cipher_encrypt(hd, ciphertext, ciphertext_len, plaintext2, plaintext2_len);
+  if (err)
+    {
+      *errmsg = "Error CTR encrypting plaintext 2";
+      goto error;
+    }
+
+  gcry_cipher_close(hd);
+  return 0;
+
+ error:
+  gcry_cipher_close(hd);
+  return -1;
+#endif
+}
+
+
+/* -------------------------- IMPLEMENTATION -------------------------------- */
+
+static int
+client_setup_new(struct pair_setup_context *handle, const char *pin, pair_cb add_cb, void *cb_arg, const char *device_id)
+{
+  struct pair_client_setup_context *sctx = &handle->sctx.client;
+
+  if (sodium_init() == -1)
+    return -1;
+
+  if (!pin || strlen(pin) < 4)
+    return -1;
+
+  memcpy(sctx->pin, pin, sizeof(sctx->pin));
+
+  return 0;
+}
+
+static void
+client_setup_free(struct pair_setup_context *handle)
+{
+  struct pair_client_setup_context *sctx = &handle->sctx.client;
+
+  srp_user_free(sctx->user);
+
+  free(sctx->pkB);
+  free(sctx->M2);
+  free(sctx->salt);
+  free(sctx->epk);
+  free(sctx->authtag);
+}
+
+static uint8_t *
+client_setup_request1(size_t *len, struct pair_setup_context *handle)
+{
+  struct pair_client_setup_context *sctx = &handle->sctx.client;
+  plist_t dict;
+  plist_t method;
+  plist_t user;
+  uint32_t uint32;
+  char *data = NULL; // Necessary to initialize because plist_to_bin() uses value
+
+  sctx->user = srp_user_new(HASH_SHA1, SRP_NG_2048, USERNAME, (unsigned char *)sctx->pin, sizeof(sctx->pin), 0, 0);
+
+  dict = plist_new_dict();
+
+  method = plist_new_string("pin");
+  user = plist_new_string(USERNAME);
+
+  plist_dict_set_item(dict, "method", method);
+  plist_dict_set_item(dict, "user", user);
+  plist_to_bin(dict, &data, &uint32);
+  plist_free(dict);
+
+  *len = (size_t)uint32;
+  return (uint8_t *)data;
+}
+
+static uint8_t *
+client_setup_request2(size_t *len, struct pair_setup_context *handle)
+{
+  struct pair_client_setup_context *sctx = &handle->sctx.client;
+  plist_t dict;
+  plist_t pk;
+  plist_t proof;
+  const char *auth_username = NULL;
+  uint32_t uint32;
+  char *data = NULL;
+
+  // Calculate A
+  srp_user_start_authentication(sctx->user, &auth_username, &sctx->pkA, &sctx->pkA_len);
+
+  // Calculate M1 (client proof)
+  srp_user_process_challenge(sctx->user, (const unsigned char *)sctx->salt, sctx->salt_len, (const unsigned char *)sctx->pkB, sctx->pkB_len, &sctx->M1, &sctx->M1_len);
+
+  pk = plist_new_data((char *)sctx->pkA, sctx->pkA_len);
+  proof = plist_new_data((char *)sctx->M1, sctx->M1_len);
+
+  dict = plist_new_dict();
+  plist_dict_set_item(dict, "pk", pk);
+  plist_dict_set_item(dict, "proof", proof);
+  plist_to_bin(dict, &data, &uint32);
+  plist_free(dict);
+
+  *len = (size_t)uint32;
+  return (uint8_t *)data;
+}
+
+static uint8_t *
+client_setup_request3(size_t *len, struct pair_setup_context *handle)
+{
+  struct pair_client_setup_context *sctx = &handle->sctx.client;
+  plist_t dict;
+  plist_t epk;
+  plist_t authtag;
+  uint32_t uint32;
+  char *data = NULL;
+  const unsigned char *session_key;
+  int session_key_len;
+  unsigned char key[SHA512_DIGEST_LENGTH];
+  unsigned char iv[SHA512_DIGEST_LENGTH];
+  unsigned char encrypted[128]; // Alloc a bit extra - should only need 2*16
+  unsigned char tag[16];
+  const char *errmsg;
+  int ret;
+
+  session_key = srp_user_get_session_key(sctx->user, &session_key_len);
+  if (!session_key)
+    {
+      handle->errmsg = "Setup request 3: No valid session key";
+      return NULL;
+    }
+
+  ret = hash_ab(HASH_SHA512, key, (unsigned char *)AES_SETUP_KEY, strlen(AES_SETUP_KEY), session_key, session_key_len);
+  if (ret < 0)
+    {
+      handle->errmsg = "Setup request 3: Hashing of key string and shared secret failed";
+      return NULL;
+    }
+
+  ret = hash_ab(HASH_SHA512, iv, (unsigned char *)AES_SETUP_IV, strlen(AES_SETUP_IV), session_key, session_key_len);
+  if (ret < 0)
+    {
+      handle->errmsg = "Setup request 3: Hashing of iv string and shared secret failed";
+      return NULL;
+    }
+
+  iv[15]++; // Nonce?
+/*
+  if (iv[15] == 0x00 || iv[15] == 0xff)
+    printf("- note that value of last byte is %d!\n", iv[15]);
+*/
+  crypto_sign_keypair(sctx->public_key, sctx->private_key);
+
+  ret = encrypt_gcm(encrypted, sizeof(encrypted), tag, sctx->public_key, sizeof(sctx->public_key), key, iv, &errmsg);
+  if (ret < 0)
+    {
+      handle->errmsg = errmsg;
+      return NULL;
+    }
+
+  epk = plist_new_data((char *)encrypted, EPK_LENGTH);
+  authtag = plist_new_data((char *)tag, AUTHTAG_LENGTH);
+
+  dict = plist_new_dict();
+  plist_dict_set_item(dict, "epk", epk);
+  plist_dict_set_item(dict, "authTag", authtag);
+  plist_to_bin(dict, &data, &uint32);
+  plist_free(dict);
+
+  *len = (size_t)uint32;
+  return (uint8_t *)data;
+}
+
+static int
+client_setup_response1(struct pair_setup_context *handle, const uint8_t *data, size_t data_len)
+{
+  struct pair_client_setup_context *sctx = &handle->sctx.client;
+  plist_t dict;
+  plist_t pk;
+  plist_t salt;
+
+  plist_from_bin((const char *)data, data_len, &dict);
+
+  pk = plist_dict_get_item(dict, "pk");
+  salt = plist_dict_get_item(dict, "salt");
+  if (!pk || !salt)
+    {
+      handle->errmsg = "Setup response 1: Missing pk or salt";
+      plist_free(dict);
+      return -1;
+    }
+
+  plist_get_data_val(pk, (char **)&sctx->pkB, &sctx->pkB_len); // B
+  plist_get_data_val(salt, (char **)&sctx->salt, &sctx->salt_len);
+
+  plist_free(dict);
+
+  return 0;
+}
+
+static int
+client_setup_response2(struct pair_setup_context *handle, const uint8_t *data, size_t data_len)
+{
+  struct pair_client_setup_context *sctx = &handle->sctx.client;
+  plist_t dict;
+  plist_t proof;
+
+  plist_from_bin((const char *)data, data_len, &dict);
+
+  proof = plist_dict_get_item(dict, "proof");
+  if (!proof)
+    {
+      handle->errmsg = "Setup response 2: Missing proof";
+      plist_free(dict);
+      return -1;
+    }
+
+  plist_get_data_val(proof, (char **)&sctx->M2, &sctx->M2_len); // M2
+
+  plist_free(dict);
+
+  // Check M2
+  srp_user_verify_session(sctx->user, (const unsigned char *)sctx->M2);
+  if (!srp_user_is_authenticated(sctx->user))
+    {
+      handle->errmsg = "Setup response 2: Server authentication failed";
+      return -1;
+    }
+
+  return 0;
+}
+
+static int
+client_setup_response3(struct pair_setup_context *handle, const uint8_t *data, size_t data_len)
+{
+  struct pair_client_setup_context *sctx = &handle->sctx.client;
+  plist_t dict;
+  plist_t epk;
+  plist_t authtag;
+
+  plist_from_bin((const char *)data, data_len, &dict);
+
+  epk = plist_dict_get_item(dict, "epk");
+  if (!epk)
+    {
+      handle->errmsg = "Setup response 3: Missing epk";
+      plist_free(dict);
+      return -1;
+    }
+
+  plist_get_data_val(epk, (char **)&sctx->epk, &sctx->epk_len);
+
+  authtag = plist_dict_get_item(dict, "authTag");
+  if (!authtag)
+    {
+      handle->errmsg = "Setup response 3: Missing authTag";
+      plist_free(dict);
+      return -1;
+    }
+
+  plist_get_data_val(authtag, (char **)&sctx->authtag, &sctx->authtag_len);
+
+  plist_free(dict);
+
+  assert(sizeof(handle->result.client_private_key) == sizeof(sctx->private_key));
+  assert(sizeof(handle->result.client_public_key) == sizeof(sctx->public_key));
+
+  memcpy(handle->result.client_private_key, sctx->private_key, sizeof(sctx->private_key));
+  memcpy(handle->result.client_public_key, sctx->public_key, sizeof(sctx->public_key));
+
+  handle->status = PAIR_STATUS_COMPLETED;
+  return 0;
+}
+
+static int
+client_setup_result(struct pair_setup_context *handle)
+{
+  struct pair_client_setup_context *sctx = &handle->sctx.client;
+  char *ptr;
+  int i;
+
+  // Last 32 bytes of the private key is the public key, so we don't need to
+  // explicitly export that
+  ptr = handle->result_str;
+  for (i = 0; i < sizeof(sctx->private_key); i++)
+    ptr += sprintf(ptr, "%02x", sctx->private_key[i]); // 2 x 64 bytes
+  *ptr = '\0';
+
+  return 0;
+}
+
+
+static int
+client_verify_new(struct pair_verify_context *handle, const char *client_setup_keys, pair_cb cb, void *cb_arg, const char *device_id)
+{
+  struct pair_client_verify_context *vctx = &handle->vctx.client;
+  char hex[] = { 0, 0, 0 };
+  size_t hexkey_len;
+  const char *ptr;
+  int i;
+
+  if (sodium_init() == -1)
+    return -1;
+
+  if (!client_setup_keys)
+    return -1;
+
+  hexkey_len = strlen(client_setup_keys);
+
+  if (hexkey_len != 2 * sizeof(vctx->client_private_key))
+    return -1;
+
+  if (device_id && strlen(device_id) != 16)
+    return -1;
+
+  if (device_id)
+    memcpy(vctx->device_id, device_id, strlen(device_id));
+
+  ptr = client_setup_keys;
+  for (i = 0; i < sizeof(vctx->client_private_key); i++, ptr+=2)
+    {
+      hex[0] = ptr[0];
+      hex[1] = ptr[1];
+      vctx->client_private_key[i] = strtol(hex, NULL, 16);
+    }
+
+  crypto_sign_ed25519_sk_to_pk(vctx->client_public_key, vctx->client_private_key);
+
+  return 0;
+}
+
+static uint8_t *
+client_verify_request1(size_t *len, struct pair_verify_context *handle)
+{
+  struct pair_client_verify_context *vctx = &handle->vctx.client;
+  const uint8_t basepoint[32] = {9};
+  uint8_t *data;
+  int ret;
+
+  ret = crypto_scalarmult(vctx->client_eph_public_key, vctx->client_eph_private_key, basepoint);
+  if (ret < 0)
+    {
+      handle->errmsg = "Verify request 1: Curve 25519 returned an error";
+      return NULL;
+    }
+
+  *len = 4 + sizeof(vctx->client_eph_public_key) + sizeof(vctx->client_public_key);
+  data = calloc(1, *len);
+  if (!data)
+    {
+      handle->errmsg = "Verify request 1: Out of memory";
+      return NULL;
+    }
+
+  data[0] = 1; // Magic
+  memcpy(data + 4, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key));
+  memcpy(data + 4 + sizeof(vctx->client_eph_public_key), vctx->client_public_key, sizeof(vctx->client_public_key));
+
+  return data;
+}
+
+static uint8_t *
+client_verify_request2(size_t *len, struct pair_verify_context *handle)
+{
+  struct pair_client_verify_context *vctx = &handle->vctx.client;
+  uint8_t shared_secret[crypto_scalarmult_BYTES];
+  uint8_t key[SHA512_DIGEST_LENGTH];
+  uint8_t iv[SHA512_DIGEST_LENGTH];
+  uint8_t encrypted[128]; // Alloc a bit extra, should only really need size of public key len
+  uint8_t signature[crypto_sign_BYTES];
+  uint8_t *data;
+  int ret;
+  const char *errmsg;
+
+  *len = sizeof(vctx->client_eph_public_key) + sizeof(vctx->server_eph_public_key);
+  data = calloc(1, *len);
+  if (!data)
+    {
+      handle->errmsg = "Verify request 2: Out of memory";
+      return NULL;
+    }
+
+  memcpy(data, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key));
+  memcpy(data + sizeof(vctx->client_eph_public_key), vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key));
+
+  crypto_sign_detached(signature, NULL, data, *len, vctx->client_private_key);
+
+  free(data);
+
+  ret = crypto_scalarmult(shared_secret, vctx->client_eph_private_key, vctx->server_eph_public_key);
+  if (ret < 0)
+    {
+      handle->errmsg = "Verify request 2: Curve 25519 returned an error";
+      return NULL;
+    }
+
+  ret = hash_ab(HASH_SHA512, key, (unsigned char *)AES_VERIFY_KEY, strlen(AES_VERIFY_KEY), shared_secret, sizeof(shared_secret));
+  if (ret < 0)
+    {
+      handle->errmsg = "Verify request 2: Hashing of key string and shared secret failed";
+      return NULL;
+    }
+
+  ret = hash_ab(HASH_SHA512, iv, (unsigned char *)AES_VERIFY_IV, strlen(AES_VERIFY_IV), shared_secret, sizeof(shared_secret));
+  if (ret < 0)
+    {
+      handle->errmsg = "Verify request 2: Hashing of iv string and shared secret failed";
+      return NULL;
+    }
+
+  ret = encrypt_ctr(encrypted, sizeof(encrypted), vctx->server_fruit_public_key, sizeof(vctx->server_fruit_public_key), signature, sizeof(signature), key, iv, &errmsg);
+  if (ret < 0)
+    {
+      handle->errmsg = errmsg;
+      return NULL;
+    }
+
+  *len = 4 + sizeof(vctx->server_fruit_public_key);
+  data = calloc(1, *len);
+  if (!data)
+    {
+      handle->errmsg = "Verify request 2: Out of memory";
+      return NULL;
+    }
+
+  memcpy(data + 4, encrypted, sizeof(vctx->server_fruit_public_key));
+
+  return data;
+}
+
+static int
+client_verify_response1(struct pair_verify_context *handle, const uint8_t *data, size_t data_len)
+{
+  struct pair_client_verify_context *vctx = &handle->vctx.client;
+  size_t wanted;
+
+  wanted = sizeof(vctx->server_eph_public_key) + sizeof(vctx->server_fruit_public_key);
+  if (data_len < wanted)
+    {
+      handle->errmsg = "Verify response 2: Unexpected response (too short)";
+      return -1;
+    }
+
+  memcpy(vctx->server_eph_public_key, data, sizeof(vctx->server_eph_public_key));
+  memcpy(vctx->server_fruit_public_key, data + sizeof(vctx->server_eph_public_key), sizeof(vctx->server_fruit_public_key));
+
+  return 0;
+}
+
+static int
+client_verify_response2(struct pair_verify_context *handle, const uint8_t *data, size_t data_len)
+{
+  struct pair_client_verify_context *vctx = &handle->vctx.client;
+  // TODO actually check response
+
+  memcpy(handle->result.shared_secret, vctx->shared_secret, sizeof(vctx->shared_secret));
+  handle->result.shared_secret_len = sizeof(vctx->shared_secret);
+
+  handle->status = PAIR_STATUS_COMPLETED;
+
+  return 0;
+}
+
+
+struct pair_definition pair_client_fruit =
+{
+  .pair_setup_new = client_setup_new,
+  .pair_setup_free = client_setup_free,
+  .pair_setup_result = client_setup_result,
+
+  .pair_setup_request1 = client_setup_request1,
+  .pair_setup_request2 = client_setup_request2,
+  .pair_setup_request3 = client_setup_request3,
+
+  .pair_setup_response1 = client_setup_response1,
+  .pair_setup_response2 = client_setup_response2,
+  .pair_setup_response3 = client_setup_response3,
+
+  .pair_verify_new = client_verify_new,
+
+  .pair_verify_request1 = client_verify_request1,
+  .pair_verify_request2 = client_verify_request2,
+
+  .pair_verify_response1 = client_verify_response1,
+  .pair_verify_response2 = client_verify_response2,
+};
diff --git a/pair_ap/pair_homekit.c b/pair_ap/pair_homekit.c
new file mode 100644 (file)
index 0000000..076ea14
--- /dev/null
@@ -0,0 +1,3123 @@
+/*
+ * Adaption of ap2-sender by ViktoriiaKh:
+ *    <https://github.com/ViktoriiaKh/ap2-sender>
+ *
+ * To test, it is useful to try with this receiver:
+ *    <https://github.com/ckdo/airplay2-receiver>
+ *
+ * The Secure Remote Password 6a implementation is adapted from:
+ *  - Tom Cocagne
+ *    <https://github.com/cocagne/csrp>
+ *
+ * TLV helpers are adapted from ESP homekit:
+ *    <https://github.com/maximkulkin/esp-homekit>
+ *
+ * The MIT License (MIT)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include <assert.h>
+
+#include <sodium.h>
+
+#include "pair-internal.h"
+#include "pair-tlv.h"
+
+
+/* ----------------------------- DEFINES ETC ------------------------------- */
+
+#define USERNAME "Pair-Setup"
+#define AUTHTAG_LENGTH 16
+#define NONCE_LENGTH 12 // 96 bits according to chacha poly1305
+#define REQUEST_BUFSIZE 4096
+#define ENCRYPTED_LEN_MAX 0x400
+
+enum pair_keys
+{
+  PAIR_SETUP_MSG01 = 0,
+  PAIR_SETUP_MSG02,
+  PAIR_SETUP_MSG03,
+  PAIR_SETUP_MSG04,
+  PAIR_SETUP_MSG05,
+  PAIR_SETUP_MSG06,
+  PAIR_SETUP_CONTROLLER_SIGN,
+  PAIR_SETUP_ACCESSORY_SIGN,
+  PAIR_VERIFY_MSG01,
+  PAIR_VERIFY_MSG02,
+  PAIR_VERIFY_MSG03,
+  PAIR_VERIFY_MSG04,
+  PAIR_CONTROL_WRITE,
+  PAIR_CONTROL_READ,
+  PAIR_EVENTS_WRITE,
+  PAIR_EVENTS_READ,
+};
+
+struct pair_keys_map
+{
+  uint8_t state;
+  const char *salt;
+  const char *info;
+  const char nonce[8];
+};
+
+static struct pair_keys_map pair_keys_map[] =
+{
+  // Used for /pair-setup
+  { 0x01, NULL, NULL, "" },
+  { 0x02, NULL, NULL, "" },
+  { 0x03, NULL, NULL, "" },
+  { 0x04, NULL, NULL, "" },
+  { 0x05, "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info", "PS-Msg05" },
+  { 0x06, "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info", "PS-Msg06" },
+  { 0, "Pair-Setup-Controller-Sign-Salt", "Pair-Setup-Controller-Sign-Info", "" },
+  { 0, "Pair-Setup-Accessory-Sign-Salt", "Pair-Setup-Accessory-Sign-Info", "" },
+
+  // Used for /pair-verify
+  { 0x01, NULL, NULL, "" },
+  { 0x02, "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info", "PV-Msg02" },
+  { 0x03, "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info", "PV-Msg03" },
+  { 0x04, NULL, NULL, "" },
+
+  // Encryption/decryption of control channel
+  { 0, "Control-Salt", "Control-Write-Encryption-Key", "" },
+  { 0, "Control-Salt", "Control-Read-Encryption-Key", "" },
+
+  // Encryption/decryption of event channel
+  { 0, "Events-Salt", "Events-Write-Encryption-Key", "" },
+  { 0, "Events-Salt", "Events-Read-Encryption-Key", "" },
+};
+
+enum pair_method {
+  PairingMethodPairSetup          = 0x00,
+  PairingMethodPairSetupWithAuth  = 0x01,
+  PairingMethodPairVerify         = 0x02,
+  PairingMethodAddPairing         = 0x03,
+  PairingMethodRemovePairing      = 0x04,
+  PairingMethodListPairings       = 0x05
+};
+
+enum pair_flags {
+  PairingFlagsTransient           = 0x10,
+};
+
+// Forwards
+const struct pair_definition pair_client_homekit_normal;
+const struct pair_definition pair_client_homekit_transient;
+const struct pair_definition pair_server_homekit;
+
+
+/* ---------------------------------- SRP ----------------------------------- */
+
+typedef enum
+{
+  SRP_NG_2048,
+  SRP_NG_3072,
+  SRP_NG_CUSTOM
+} SRP_NGType;
+
+typedef struct
+{
+  bnum N;
+  bnum g;
+} NGConstant;
+
+struct SRPUser
+{
+  enum hash_alg     alg;
+  NGConstant        *ng;
+
+  bnum a;
+  bnum A;
+  bnum S;
+
+  const unsigned char *bytes_A;
+  int           authenticated;
+
+  char          *username;
+  unsigned char *password;
+  int           password_len;
+
+  unsigned char M           [SHA512_DIGEST_LENGTH];
+  unsigned char H_AMK       [SHA512_DIGEST_LENGTH];
+  unsigned char session_key [SHA512_DIGEST_LENGTH];
+  int           session_key_len;
+};
+
+struct SRPVerifier
+{
+  enum hash_alg     alg;
+  NGConstant        *ng;
+
+  const unsigned char *bytes_B;
+  int           authenticated;
+
+  char          *username;
+
+  unsigned char M           [SHA512_DIGEST_LENGTH];
+  unsigned char H_AMK       [SHA512_DIGEST_LENGTH];
+  unsigned char session_key [SHA512_DIGEST_LENGTH];
+  int           session_key_len;
+};
+
+struct NGHex
+{
+  const char *n_hex;
+  const char *g_hex;
+};
+
+// These constants here were pulled from Appendix A of RFC 5054
+static struct NGHex global_Ng_constants[] =
+{
+  { /* 2048 */
+    "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4"
+    "A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60"
+    "95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF"
+    "747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907"
+    "8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861"
+    "60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB"
+    "FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73",
+    "2"
+  },
+  { /* 3072 */
+    "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B"
+    "139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485"
+    "B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1F"
+    "E649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23"
+    "DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32"
+    "905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF69558"
+    "17183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521"
+    "ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D7"
+    "1E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B1817"
+    "7B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82"
+    "D120A93AD2CAFFFFFFFFFFFFFFFF",
+    "5"
+  },
+  {0,0} /* null sentinel */
+};
+
+
+static NGConstant *
+new_ng(SRP_NGType ng_type, const char *n_hex, const char *g_hex)
+{
+  NGConstant *ng = calloc(1, sizeof(NGConstant));
+
+  if ( ng_type != SRP_NG_CUSTOM )
+    {
+      n_hex = global_Ng_constants[ ng_type ].n_hex;
+      g_hex = global_Ng_constants[ ng_type ].g_hex;
+    }
+
+  bnum_hex2bn(ng->N, n_hex);
+  bnum_hex2bn(ng->g, g_hex);
+
+  return ng;
+}
+
+static void
+free_ng(NGConstant * ng)
+{
+  if (!ng)
+    return;
+
+  bnum_free(ng->N);
+  bnum_free(ng->g);
+  free(ng);
+}
+
+static bnum
+calculate_x(enum hash_alg alg, const bnum salt, const char *username, const unsigned char *password, int password_len)
+{
+  unsigned char ucp_hash[SHA512_DIGEST_LENGTH];
+  HashCTX       ctx;
+
+  hash_init( alg, &ctx );
+  hash_update( alg, &ctx, username, strlen(username) );
+  hash_update( alg, &ctx, ":", 1 );
+  hash_update( alg, &ctx, password, password_len );
+  hash_final( alg, &ctx, ucp_hash );
+
+  return H_ns( alg, salt, ucp_hash, hash_length(alg) );
+}
+
+static void
+calculate_M(enum hash_alg alg, NGConstant *ng, unsigned char *dest, const char *I, const bnum s,
+            const bnum A, const bnum B, const unsigned char *K, int K_len)
+{
+  unsigned char H_N[ SHA512_DIGEST_LENGTH ];
+  unsigned char H_g[ SHA512_DIGEST_LENGTH ];
+  unsigned char H_I[ SHA512_DIGEST_LENGTH ];
+  unsigned char H_xor[ SHA512_DIGEST_LENGTH ];
+  HashCTX       ctx;
+  int           i = 0;
+  int           hash_len = hash_length(alg);
+
+  hash_num( alg, ng->N, H_N );
+  hash_num( alg, ng->g, H_g );
+
+  hash(alg, (const unsigned char *)I, strlen(I), H_I);
+
+  for (i=0; i < hash_len; i++ )
+    H_xor[i] = H_N[i] ^ H_g[i];
+    
+  hash_init( alg, &ctx );
+
+  hash_update( alg, &ctx, H_xor, hash_len );
+  hash_update( alg, &ctx, H_I,   hash_len );
+  update_hash_n( alg, &ctx, s );
+  update_hash_n( alg, &ctx, A );
+  update_hash_n( alg, &ctx, B );
+  hash_update( alg, &ctx, K, K_len );
+
+  hash_final( alg, &ctx, dest );
+}
+
+static void
+calculate_H_AMK(enum hash_alg alg, unsigned char *dest, const bnum A, const unsigned char * M, const unsigned char * K, int K_len)
+{
+  HashCTX ctx;
+
+  hash_init( alg, &ctx );
+
+  update_hash_n( alg, &ctx, A );
+  hash_update( alg, &ctx, M, hash_length(alg) );
+  hash_update( alg, &ctx, K, K_len );
+
+  hash_final( alg, &ctx, dest );
+}
+
+/* ----------------------- SRP for the client side -------------------------- */
+
+static struct SRPUser *
+srp_user_new(enum hash_alg alg, SRP_NGType ng_type, const char *username,
+             const unsigned char *bytes_password, int len_password,
+             const char *n_hex, const char *g_hex)
+{
+  struct SRPUser  *usr  = calloc(1, sizeof(struct SRPUser));
+  int              ulen = strlen(username) + 1;
+
+  if (!usr)
+    goto err_exit;
+
+  usr->alg = alg;
+  usr->ng  = new_ng( ng_type, n_hex, g_hex );
+
+  bnum_new(usr->a);
+  bnum_new(usr->A);
+  bnum_new(usr->S);
+
+  if (!usr->ng || !usr->a || !usr->A || !usr->S)
+    goto err_exit;
+
+  usr->username     = malloc(ulen);
+  usr->password     = malloc(len_password);
+  usr->password_len = len_password;
+
+  if (!usr->username || !usr->password)
+    goto err_exit;
+
+  memcpy(usr->username, username,       ulen);
+  memcpy(usr->password, bytes_password, len_password);
+
+  usr->authenticated = 0;
+  usr->bytes_A = 0;
+
+  return usr;
+
+ err_exit:
+  if (!usr)
+    return NULL;
+
+  bnum_free(usr->a);
+  bnum_free(usr->A);
+  bnum_free(usr->S);
+
+  free(usr->username);
+  if (usr->password)
+    {
+      memset(usr->password, 0, usr->password_len);
+      free(usr->password);
+    }
+  free(usr);
+
+  return NULL;
+}
+
+static void
+srp_user_free(struct SRPUser *usr)
+{
+  if(!usr)
+    return;
+
+  bnum_free(usr->a);
+  bnum_free(usr->A);
+  bnum_free(usr->S);
+
+  free_ng(usr->ng);
+
+  memset(usr->password, 0, usr->password_len);
+
+  free(usr->username);
+  free(usr->password);
+  free((char *)usr->bytes_A);
+
+  memset(usr, 0, sizeof(*usr));
+  free(usr);
+}
+
+static int
+srp_user_is_authenticated(struct SRPUser *usr)
+{
+  return usr->authenticated;
+}
+
+static const unsigned char *
+srp_user_get_session_key(struct SRPUser *usr, int *key_length)
+{
+  if (key_length)
+    *key_length = usr->session_key_len;
+  return usr->session_key;
+}
+
+/* Output: username, bytes_A, len_A */
+static void
+srp_user_start_authentication(struct SRPUser *usr, const char **username,
+                              const unsigned char **bytes_A, int *len_A)
+{
+  bnum_random(usr->a, 256);
+//  BN_hex2bn(&(usr->a), "D929DFB605687233C9E9030C2280156D03BDB9FDCF3CCE3BC27D9CCFCB5FF6A1");
+
+  bnum_modexp(usr->A, usr->ng->g, usr->a, usr->ng->N);
+    
+  *len_A   = bnum_num_bytes(usr->A);
+  *bytes_A = malloc(*len_A);
+
+  if (!*bytes_A)
+    {
+      *len_A = 0;
+      *bytes_A = 0;
+      *username = 0;
+      return;
+    }
+
+  bnum_bn2bin(usr->A, (unsigned char *) *bytes_A, *len_A);
+
+  usr->bytes_A = *bytes_A;
+  *username = usr->username;
+}
+
+/* Output: bytes_M. Buffer length is SHA512_DIGEST_LENGTH */
+static void
+srp_user_process_challenge(struct SRPUser *usr, const unsigned char *bytes_s, int len_s,
+                           const unsigned char *bytes_B, int len_B,
+                           const unsigned char **bytes_M, int *len_M )
+{
+  bnum s, B, k, v;
+  bnum tmp1, tmp2, tmp3;
+  bnum u, x;
+
+  *len_M = 0;
+  *bytes_M = 0;
+
+  bnum_bin2bn(s, bytes_s, len_s);
+  bnum_bin2bn(B, bytes_B, len_B);
+  k    = H_nn_pad(usr->alg, usr->ng->N, usr->ng->g);
+
+  bnum_new(v);
+  bnum_new(tmp1);
+  bnum_new(tmp2);
+  bnum_new(tmp3);
+
+  if (!s || !B || !k || !v || !tmp1 || !tmp2 || !tmp3)
+    goto cleanup1;
+
+  u = H_nn_pad(usr->alg, usr->A, B);
+  x = calculate_x(usr->alg, s, usr->username, usr->password, usr->password_len);
+  if (!u || !x)
+    goto cleanup2;
+
+  // SRP-6a safety check
+  if (!bnum_is_zero(B) && !bnum_is_zero(u))
+    {
+      bnum_modexp(v, usr->ng->g, x, usr->ng->N);
+
+      // S = (B - k*(g^x)) ^ (a + ux)
+      bnum_mul(tmp1, u, x);
+      bnum_add(tmp2, usr->a, tmp1);        // tmp2 = (a + ux)
+      bnum_modexp(tmp1, usr->ng->g, x, usr->ng->N);
+      bnum_mul(tmp3, k, tmp1);             // tmp3 = k*(g^x)
+      bnum_sub(tmp1, B, tmp3);             // tmp1 = (B - K*(g^x))
+      bnum_modexp(usr->S, tmp1, tmp2, usr->ng->N);
+
+      hash_num(usr->alg, usr->S, usr->session_key);
+      usr->session_key_len = hash_length(usr->alg);
+
+      calculate_M(usr->alg, usr->ng, usr->M, usr->username, s, usr->A, B, usr->session_key, usr->session_key_len);
+      calculate_H_AMK(usr->alg, usr->H_AMK, usr->A, usr->M, usr->session_key, usr->session_key_len);
+
+      *bytes_M = usr->M;
+      if (len_M)
+        *len_M = hash_length(usr->alg);
+    }
+  else
+    {
+      *bytes_M = NULL;
+      if (len_M)
+        *len_M   = 0;
+    }
+
+ cleanup2:
+  bnum_free(x);
+  bnum_free(u);
+ cleanup1:
+  bnum_free(tmp3);
+  bnum_free(tmp2);
+  bnum_free(tmp1);
+  bnum_free(v);
+  bnum_free(k);
+  bnum_free(B);
+  bnum_free(s);
+}
+
+static void
+srp_user_verify_session(struct SRPUser *usr, const unsigned char *bytes_HAMK)
+{
+  if (memcmp(usr->H_AMK, bytes_HAMK, hash_length(usr->alg)) == 0)
+    usr->authenticated = 1;
+}
+
+
+/* ----------------------- SRP for the server side -------------------------- */
+
+static int
+srp_create_salted_verification_key(enum hash_alg alg,
+                                   SRP_NGType ng_type, const char *username,
+                                   const unsigned char *password, int len_password,
+                                   unsigned char **bytes_s, int *len_s, 
+                                   unsigned char **bytes_v, int *len_v,
+                                   const char *n_hex, const char *g_hex )
+{
+  bnum s, v, x;
+  NGConstant *ng;
+
+  bnum_new(s);
+  bnum_new(v);
+  x = NULL;
+
+  ng = new_ng(ng_type, n_hex, g_hex);
+
+  *bytes_s = NULL;
+  *bytes_v = NULL;
+
+  if (!s || !v || !ng)
+    goto error;
+
+  bnum_random(s, 128); // MODIFIED from csrp's BN_rand(s, 32, -1, 0)
+
+  x = calculate_x(alg, s, username, password, len_password);
+  if (!x)
+    goto error;
+
+  bnum_modexp(v, ng->g, x, ng->N);
+
+  *len_s = bnum_num_bytes(s);
+  *len_v = bnum_num_bytes(v);
+
+  *bytes_s = malloc(*len_s);
+  *bytes_v = malloc(*len_v);
+  if (!(*bytes_s) || !(*bytes_v))
+    goto error;
+
+  bnum_bn2bin(s, (unsigned char *) *bytes_s, *len_s);
+  bnum_bn2bin(v, (unsigned char *) *bytes_v, *len_v);
+
+  free_ng(ng);
+  bnum_free(s);
+  bnum_free(v);
+  bnum_free(x);
+  return 0;
+
+ error:
+  free(*bytes_s);
+  free(*bytes_v);
+  free_ng(ng);
+  bnum_free(s);
+  bnum_free(v);
+  bnum_free(x);
+  return -1;
+}
+
+static int
+srp_verifier_start_authentication(enum hash_alg alg, SRP_NGType ng_type,
+                                  const unsigned char *bytes_v, int len_v,
+                                  unsigned char **bytes_b, int *len_b,
+                                  unsigned char **bytes_B, int *len_B,
+                                  const char *n_hex, const char *g_hex)
+{
+  bnum v, k, b, B, tmp1, tmp2;
+  NGConstant *ng;
+
+  v = NULL;
+  k = NULL;
+  bnum_new(b);
+  bnum_new(B);
+  bnum_new(tmp1);
+  bnum_new(tmp2);
+
+  *len_b   = 0;
+  *bytes_b = NULL;
+  *len_B   = 0;
+  *bytes_B = NULL;
+
+  ng = new_ng(ng_type, n_hex, g_hex);
+
+  if (!b || !B || !tmp1 || !tmp2 || !ng)
+    goto error;
+
+  bnum_bin2bn(v, bytes_v, len_v);
+
+  bnum_random(b, 256); // MODIFIED from BN_rand(b, 256, -1, 0)
+
+  k = H_nn_pad(alg, ng->N, ng->g); // MODIFIED from H_nn(alg, ng->N, ng->g)
+  if (!k)
+    goto error;
+
+  // B = kv + g^b
+  bnum_mul(tmp1, k, v);
+  bnum_modexp(tmp2, ng->g, b, ng->N);
+  bnum_modadd(B, tmp1, tmp2, ng->N);
+
+  *len_B = bnum_num_bytes(B);
+  *len_b = bnum_num_bytes(b);
+
+  *bytes_B = malloc(*len_B);
+  *bytes_b = malloc(*len_b);
+  if (!(*bytes_B) || !(*bytes_b))
+    goto error;
+
+  bnum_bn2bin(B, (unsigned char *) *bytes_B, *len_B);
+  bnum_bn2bin(b, (unsigned char *) *bytes_b, *len_b);
+
+  bnum_free(b);
+  bnum_free(B);
+  bnum_free(v);
+  bnum_free(k);
+  bnum_free(tmp1);
+  bnum_free(tmp2);
+  free_ng(ng);
+  return 0;
+
+ error:
+  free(*bytes_B);
+  free(*bytes_b);
+  bnum_free(b);
+  bnum_free(B);
+  bnum_free(v);
+  bnum_free(k);
+  bnum_free(tmp1);
+  bnum_free(tmp2);
+  free_ng(ng);
+  return -1;
+}
+
+static void
+srp_verifier_free(struct SRPVerifier *ver)
+{
+  if (!ver)
+    return;
+
+  free_ng(ver->ng);
+
+  free(ver->username);
+
+  memset(ver, 0, sizeof(struct SRPVerifier));
+  free(ver);
+}
+
+static struct SRPVerifier *
+srp_verifier_new(enum hash_alg alg, SRP_NGType ng_type, const char *username,
+                 const unsigned char *bytes_s, int len_s,
+                 const unsigned char *bytes_v, int len_v,
+                 const unsigned char *bytes_A, int len_A,
+                 const unsigned char *bytes_b, int len_b,
+                 const unsigned char *bytes_B, int len_B,
+                 const char *n_hex, const char *g_hex )
+{
+  struct SRPVerifier *ver = NULL;
+  bnum s, v, A, b, B, S, tmp1, tmp2, u, k;
+  NGConstant *ng;
+  size_t ulen;
+
+  bnum_bin2bn(s, bytes_s, len_s);
+  bnum_bin2bn(v, bytes_v, len_v);
+  bnum_bin2bn(A, bytes_A, len_A);
+  bnum_bin2bn(b, bytes_b, len_b);
+  bnum_bin2bn(B, bytes_B, len_B);
+  bnum_new(S);
+  bnum_new(tmp1);
+  bnum_new(tmp2);
+  u = NULL;
+  k = NULL;
+
+  ng = new_ng(ng_type, n_hex, g_hex);
+
+  if (!s || !v || !A || !B || !S || !b || !tmp1 || !tmp2 || !ng)
+    goto error;
+
+  ver = calloc(1, sizeof(struct SRPVerifier));
+  if (!ver)
+    goto error;
+
+  ulen = strlen(username) + 1;
+
+  ver->alg = alg;
+  ver->ng  = ng;
+
+  ver->username = malloc(ulen);
+  if (!ver->username)
+    goto error;
+
+  memcpy(ver->username, username, ulen);
+
+  ver->authenticated = 0;
+
+  // SRP-6a safety check
+  bnum_mod(tmp1, A, ng->N);
+  if (bnum_is_zero(tmp1))
+    goto error;
+
+  k = H_nn_pad(alg, ng->N, ng->g); // MODIFIED from H_nn(alg, ng->N, ng->g)
+  u = H_nn_pad(alg, A, B); // MODIFIED from H_nn(alg, A, B)
+
+  // S = (A *(v^u)) ^ b
+  bnum_modexp(tmp1, v, u, ng->N);
+  bnum_mul(tmp2, A, tmp1);
+  bnum_modexp(S, tmp2, b, ng->N);
+
+  hash_num(alg, S, ver->session_key);
+  ver->session_key_len = hash_length(ver->alg);
+
+  calculate_M(alg, ng, ver->M, username, s, A, B, ver->session_key, ver->session_key_len);
+  calculate_H_AMK(alg, ver->H_AMK, A, ver->M, ver->session_key, ver->session_key_len);
+
+  ver->bytes_B = bytes_B;
+
+  bnum_free(s);
+  bnum_free(v);
+  bnum_free(A);
+  bnum_free(u);
+  bnum_free(k);
+  bnum_free(B);
+  bnum_free(S);
+  bnum_free(b);
+  bnum_free(tmp1);
+  bnum_free(tmp2);
+  return ver;
+
+ error:
+  srp_verifier_free(ver);
+  bnum_free(s);
+  bnum_free(v);
+  bnum_free(A);
+  bnum_free(u);
+  bnum_free(k);
+  bnum_free(B);
+  bnum_free(S);
+  bnum_free(b);
+  bnum_free(tmp1);
+  bnum_free(tmp2);
+  return NULL;
+}
+
+// user_M must be exactly SHA512_DIGEST_LENGTH bytes in size
+static void
+srp_verifier_verify_session(struct SRPVerifier *ver, const unsigned char *user_M, const unsigned char **bytes_HAMK)
+{
+  if (memcmp(ver->M, user_M, hash_length(ver->alg)) == 0)
+    {
+      ver->authenticated = 1;
+      *bytes_HAMK = ver->H_AMK;
+    }
+  else
+    *bytes_HAMK = NULL;
+}
+
+static const unsigned char *
+srp_verifier_get_session_key(struct SRPVerifier *ver, int *key_length)
+{
+  if (key_length)
+    *key_length = hash_length(ver->alg);
+
+  return ver->session_key;
+}
+
+/* -------------------------------- HELPERS --------------------------------- */
+
+static void
+hexread(uint8_t *out, size_t out_len, const char *in)
+{
+  char hex[] = { 0, 0, 0 };
+  int i;
+
+  for (i = 0; i < out_len; i++, in+=2)
+    {
+      hex[0] = in[0];
+      hex[1] = in[1];
+      out[i] = strtol(hex, NULL, 16);
+    }
+}
+
+static pair_tlv_values_t *
+message_process(const uint8_t *data, size_t data_len, const char **errmsg)
+{
+  pair_tlv_values_t *response;
+  pair_tlv_t *error;
+  int ret;
+
+  response = pair_tlv_new();
+  if (!response)
+    {
+      *errmsg = "Out of memory\n";
+      return NULL;
+    }
+
+  ret = pair_tlv_parse(data, data_len, response);
+  if (ret < 0)
+    {
+      *errmsg = "Could not parse TLV\n";
+      goto error;
+    }
+
+#ifdef DEBUG_PAIR
+  pair_tlv_debug(response);
+#endif
+
+  error = pair_tlv_get_value(response, TLVType_Error);
+  if (error)
+    {
+      if (error->value[0] == TLVError_Authentication)
+       *errmsg = "Device returned an authentication failure";
+      else if (error->value[0] == TLVError_Backoff)
+       *errmsg = "Device told us to back off pairing attempts\n";
+      else if (error->value[0] == TLVError_MaxPeers)
+       *errmsg = "Max peers trying to connect to device\n";
+      else if (error->value[0] == TLVError_MaxTries)
+       *errmsg = "Max pairing attemps reached\n";
+      else if (error->value[0] == TLVError_Unavailable)
+       *errmsg = "Device is unuavailble at this time\n";
+      else
+       *errmsg = "Device is busy/returned unknown error\n";
+
+      goto error;
+    }
+
+  return response;
+
+ error:
+  pair_tlv_free(response);
+  return NULL;
+}
+
+/* Executes SHA512 RFC 5869 extract + expand, writing a derived key to okm
+
+   hkdfExtract(SHA512, salt, salt_len, ikm, ikm_len, prk);
+   hkdfExpand(SHA512, prk, SHA512_LEN, info, info_len, okm, okm_len);
+*/
+static int
+hkdf_extract_expand(uint8_t *okm, size_t okm_len, const uint8_t *ikm, size_t ikm_len, enum pair_keys pair_key)
+{
+#ifdef CONFIG_OPENSSL
+#include <openssl/kdf.h>
+  EVP_PKEY_CTX *pctx;
+
+  if (okm_len > SHA512_DIGEST_LENGTH)
+    return -1;
+  if (! (pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL)))
+    return -1;
+  if (EVP_PKEY_derive_init(pctx) <= 0)
+    goto error;
+  if (EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha512()) <= 0)
+    goto error;
+  if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, (const unsigned char *)pair_keys_map[pair_key].salt, strlen(pair_keys_map[pair_key].salt)) <= 0)
+    goto error;
+  if (EVP_PKEY_CTX_set1_hkdf_key(pctx, ikm, ikm_len) <= 0)
+    goto error;
+  if (EVP_PKEY_CTX_add1_hkdf_info(pctx, (const unsigned char *)pair_keys_map[pair_key].info, strlen(pair_keys_map[pair_key].info)) <= 0)
+    goto error;
+  if (EVP_PKEY_derive(pctx, okm, &okm_len) <= 0)
+    goto error;
+
+  EVP_PKEY_CTX_free(pctx);
+  return 0;
+
+ error:
+  EVP_PKEY_CTX_free(pctx);
+  return -1;
+#elif CONFIG_GCRYPT
+  uint8_t prk[SHA512_DIGEST_LENGTH];
+  gcry_md_hd_t hmac_handle;
+
+  if (okm_len > SHA512_DIGEST_LENGTH)
+    return -1; // Below calculation not valid if output is larger than hash size
+  if (gcry_md_open(&hmac_handle, GCRY_MD_SHA512, GCRY_MD_FLAG_HMAC) != GPG_ERR_NO_ERROR)
+    return -1;
+  if (gcry_md_setkey(hmac_handle, (const unsigned char *)pair_keys_map[pair_key].salt, strlen(pair_keys_map[pair_key].salt)) != GPG_ERR_NO_ERROR)
+    goto error;
+  gcry_md_write(hmac_handle, ikm, ikm_len);
+  memcpy(prk, gcry_md_read(hmac_handle, 0), sizeof(prk));
+
+  gcry_md_reset(hmac_handle);
+
+  if (gcry_md_setkey(hmac_handle, prk, sizeof(prk)) != GPG_ERR_NO_ERROR)
+    goto error;
+  gcry_md_write(hmac_handle, (const unsigned char *)pair_keys_map[pair_key].info, strlen(pair_keys_map[pair_key].info));
+  gcry_md_putc(hmac_handle, 1);
+
+  memcpy(okm, gcry_md_read(hmac_handle, 0), okm_len);
+
+  gcry_md_close(hmac_handle);
+  return 0;
+
+ error:
+  gcry_md_close(hmac_handle);
+  return -1;
+#else
+  return -1;
+#endif
+}
+
+static int
+encrypt_chacha(uint8_t *cipher, const uint8_t *plain, size_t plain_len, const uint8_t *key, size_t key_len, const void *ad, size_t ad_len, uint8_t *tag, size_t tag_len, const uint8_t nonce[NONCE_LENGTH])
+{
+#ifdef CONFIG_OPENSSL
+  EVP_CIPHER_CTX *ctx;
+  int len;
+
+  if (! (ctx = EVP_CIPHER_CTX_new()))
+    return -1;
+
+  if (EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, key, nonce) != 1)
+    goto error;
+
+  if (EVP_CIPHER_CTX_set_padding(ctx, 0) != 1) // Maybe not necessary
+    goto error;
+
+  if (ad_len > 0 && EVP_EncryptUpdate(ctx, NULL, &len, ad, ad_len) != 1)
+    goto error;
+
+  if (EVP_EncryptUpdate(ctx, cipher, &len, plain, plain_len) != 1)
+    goto error;
+
+  assert(len == plain_len);
+
+  if (EVP_EncryptFinal_ex(ctx, NULL, &len) != 1)
+    goto error;
+
+  if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, tag_len, tag) != 1)
+    goto error;
+
+  EVP_CIPHER_CTX_free(ctx);
+  return 0;
+
+ error:
+  EVP_CIPHER_CTX_free(ctx);
+  return -1;
+#elif CONFIG_GCRYPT
+  gcry_cipher_hd_t hd;
+
+  if (gcry_cipher_open(&hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_POLY1305, 0) != GPG_ERR_NO_ERROR)
+    return -1;
+
+  if (gcry_cipher_setkey(hd, key, key_len) != GPG_ERR_NO_ERROR)
+    goto error;
+
+  if (gcry_cipher_setiv(hd, nonce, NONCE_LENGTH) != GPG_ERR_NO_ERROR)
+    goto error;
+
+  if (ad_len > 0 && gcry_cipher_authenticate(hd, ad, ad_len) != GPG_ERR_NO_ERROR)
+    goto error;
+
+  if (gcry_cipher_encrypt(hd, cipher, plain_len, plain, plain_len) != GPG_ERR_NO_ERROR)
+    goto error;
+
+  if (gcry_cipher_gettag(hd, tag, tag_len) != GPG_ERR_NO_ERROR)
+    goto error;
+
+  gcry_cipher_close(hd);
+  return 0;
+
+ error:
+  gcry_cipher_close(hd);
+  return -1;
+#else
+  return -1;
+#endif
+}
+
+static int
+decrypt_chacha(uint8_t *plain, const uint8_t *cipher, size_t cipher_len, const uint8_t *key, size_t key_len, const void *ad, size_t ad_len, uint8_t *tag, size_t tag_len, const uint8_t nonce[NONCE_LENGTH])
+{
+#ifdef CONFIG_OPENSSL
+  EVP_CIPHER_CTX *ctx;
+  int len;
+
+  if (! (ctx = EVP_CIPHER_CTX_new()))
+    return -1;
+
+  if (EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, key, nonce) != 1)
+    goto error;
+
+  if (EVP_CIPHER_CTX_set_padding(ctx, 0) != 1) // Maybe not necessary
+    goto error;
+
+  if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, tag) != 1)
+    goto error;
+
+  if (ad_len > 0 && EVP_DecryptUpdate(ctx, NULL, &len, ad, ad_len) != 1)
+    goto error;
+
+  if (EVP_DecryptUpdate(ctx, plain, &len, cipher, cipher_len) != 1)
+    goto error;
+
+  if (EVP_DecryptFinal_ex(ctx, NULL, &len) != 1)
+    goto error;
+
+  EVP_CIPHER_CTX_free(ctx);
+  return 0;
+
+ error:
+  EVP_CIPHER_CTX_free(ctx);
+  return -1;
+#elif CONFIG_GCRYPT
+  gcry_cipher_hd_t hd;
+
+  if (gcry_cipher_open(&hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_POLY1305, 0) != GPG_ERR_NO_ERROR)
+    return -1;
+
+  if (gcry_cipher_setkey(hd, key, key_len) != GPG_ERR_NO_ERROR)
+    goto error;
+
+  if (gcry_cipher_setiv(hd, nonce, NONCE_LENGTH) != GPG_ERR_NO_ERROR)
+    goto error;
+
+  if (ad_len > 0 && gcry_cipher_authenticate(hd, ad, ad_len) != GPG_ERR_NO_ERROR)
+    goto error;
+
+  if (gcry_cipher_decrypt(hd, plain, cipher_len, cipher, cipher_len) != GPG_ERR_NO_ERROR)
+    goto error;
+
+  if (gcry_cipher_checktag(hd, tag, tag_len) != GPG_ERR_NO_ERROR)
+    goto error;
+
+  gcry_cipher_close(hd);
+  return 0;
+
+ error:
+  gcry_cipher_close(hd);
+  return -1;
+#else
+  return -1;
+#endif
+}
+
+static int
+create_info(uint8_t *info, size_t *info_len, uint8_t *a, size_t a_len, uint8_t *b, size_t b_len, uint8_t *c, size_t c_len)
+{
+  if (a_len + b_len + c_len > *info_len)
+    return -1;
+
+  *info_len = a_len + b_len + c_len;
+  memcpy(info, a, a_len);
+  memcpy(info + a_len, b, b_len);
+  memcpy(info + a_len + b_len, c, c_len);
+
+  return 0;
+}
+
+static int
+create_and_sign_device_info(uint8_t *msg, size_t *msg_len, const char *device_id, uint8_t *device_x, size_t device_x_len, uint8_t *pk, size_t pk_len, uint8_t *sk)
+{
+  pair_tlv_values_t *tlv;
+  uint8_t device_info[256];
+  size_t device_info_len;
+  size_t device_id_len;
+  uint8_t signature[crypto_sign_BYTES];
+  int ret;
+
+  device_id_len = strlen(device_id);
+  device_info_len = sizeof(device_info);
+
+  ret = create_info(device_info, &device_info_len, device_x, device_x_len, (uint8_t *)device_id, device_id_len, pk, pk_len);
+  if (ret < 0)
+    return -1;
+
+  crypto_sign_detached(signature, NULL, device_info, device_info_len, sk);
+
+  tlv = pair_tlv_new();
+  pair_tlv_add_value(tlv, TLVType_Identifier, (unsigned char *)device_id, device_id_len);
+  pair_tlv_add_value(tlv, TLVType_Signature, signature, sizeof(signature));
+
+  ret = pair_tlv_format(tlv, msg, msg_len);
+
+  pair_tlv_free(tlv);
+  return ret;
+}
+
+static int
+create_and_sign_accessory_info(uint8_t *msg, size_t *msg_len, uint8_t *server_pk, size_t server_pk_len, const char *accessory_id, uint8_t *client_pk, size_t client_pk_len, uint8_t *sk)
+{
+  pair_tlv_values_t *tlv;
+  uint8_t accessory_info[256];
+  size_t accessory_info_len;
+  size_t accessory_id_len;
+  uint8_t signature[crypto_sign_BYTES];
+  int ret;
+
+  accessory_id_len = strlen(accessory_id);
+  accessory_info_len = sizeof(accessory_info);
+
+  ret = create_info(accessory_info, &accessory_info_len, server_pk, server_pk_len, (uint8_t *)accessory_id, accessory_id_len, client_pk, client_pk_len);
+  if (ret < 0)
+    return -1;
+
+  crypto_sign_detached(signature, NULL, accessory_info, accessory_info_len, sk);
+
+  tlv = pair_tlv_new();
+  pair_tlv_add_value(tlv, TLVType_Identifier, (unsigned char *)accessory_id, accessory_id_len);
+  pair_tlv_add_value(tlv, TLVType_Signature, signature, sizeof(signature));
+
+  ret = pair_tlv_format(tlv, msg, msg_len);
+
+  pair_tlv_free(tlv);
+  return ret;
+}
+
+static int
+verify_info(uint8_t *signature, uint8_t *pk, uint8_t *a, size_t a_len, uint8_t *b, size_t b_len, uint8_t *c, size_t c_len)
+{
+  uint8_t info[256];
+  size_t info_len;
+  int ret;
+
+  info_len = sizeof(info);
+  ret = create_info(info, &info_len, a, a_len, b, b_len, c, c_len);
+  if (ret < 0)
+    return -1;
+
+  return crypto_sign_verify_detached(signature, info, info_len, pk);
+}
+
+
+/* ------------------------- CLIENT IMPLEMENTATION -------------------------- */
+
+static int
+client_setup_new(struct pair_setup_context *handle, const char *pin, pair_cb add_cb, void *cb_arg, const char *device_id)
+{
+  struct pair_client_setup_context *sctx = &handle->sctx.client;
+
+  if (sodium_init() == -1)
+    return -1;
+
+  if (handle->type == &pair_client_homekit_normal)
+    {
+      if (!pin || strlen(pin) < 4)
+       return -1;
+    }
+  else if (handle->type == &pair_client_homekit_transient && !pin)
+    {
+      pin = "3939";
+    }
+
+  if (device_id && strlen(device_id) >= PAIR_AP_DEVICE_ID_LEN_MAX)
+    return -1;
+
+  memcpy(sctx->pin, pin, sizeof(sctx->pin));
+
+  sctx->add_cb = add_cb;
+  sctx->add_cb_arg = cb_arg;
+
+  if (device_id)
+    memcpy(sctx->device_id, device_id, strlen(device_id));
+
+  crypto_sign_keypair(sctx->public_key, sctx->private_key);
+
+  return 0;
+}
+
+static void
+client_setup_free(struct pair_setup_context *handle)
+{
+  struct pair_client_setup_context *sctx = &handle->sctx.client;
+
+  srp_user_free(sctx->user);
+
+  free(sctx->pkB);
+  free(sctx->M2);
+  free(sctx->salt);
+  free(sctx->epk);
+  free(sctx->authtag);
+}
+
+static uint8_t *
+client_setup_request1(size_t *len, struct pair_setup_context *handle)
+{
+  struct pair_client_setup_context *sctx = &handle->sctx.client;
+  pair_tlv_values_t *request;
+  uint8_t *data;
+  size_t data_len;
+  uint8_t method;
+  uint8_t flags;
+  int endian_test = 1;
+  int ret;
+
+  data_len = REQUEST_BUFSIZE;
+  data = malloc(data_len);
+  request = pair_tlv_new();
+
+  // Test here instead of setup_new() so we can give an error message
+  if(*(char *)&endian_test != 1)
+    {
+      handle->errmsg = "Setup request 1: No support for big endian architechture";
+      goto error;
+    }
+
+  sctx->user = srp_user_new(HASH_SHA512, SRP_NG_3072, USERNAME, (unsigned char *)sctx->pin, sizeof(sctx->pin), 0, 0);
+  if (!sctx->user)
+    {
+      handle->errmsg = "Setup request 1: Create SRP user failed";
+      goto error;
+    }
+
+  method = PairingMethodPairSetup;
+  pair_tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_SETUP_MSG01].state, sizeof(pair_keys_map[PAIR_SETUP_MSG01].state));
+  pair_tlv_add_value(request, TLVType_Method, &method, sizeof(method));
+
+  if (handle->type == &pair_client_homekit_transient)
+    {
+      flags = PairingFlagsTransient;
+      pair_tlv_add_value(request, TLVType_Flags, &flags, sizeof(flags));
+    }
+
+  ret = pair_tlv_format(request, data, &data_len);
+  if (ret < 0)
+    {
+      handle->errmsg = "Setup request 1: pair_tlv_format returned an error";
+      goto error;
+    }
+
+  *len = data_len;
+
+  pair_tlv_free(request);
+  return data;
+
+ error:
+  pair_tlv_free(request);
+  free(data);
+  return NULL;
+}
+
+static uint8_t *
+client_setup_request2(size_t *len, struct pair_setup_context *handle)
+{
+  struct pair_client_setup_context *sctx = &handle->sctx.client;
+  pair_tlv_values_t *request;
+  uint8_t *data;
+  size_t data_len;
+  const char *auth_username = NULL;
+  int ret;
+
+  data_len = REQUEST_BUFSIZE;
+  data = malloc(data_len);
+  request = pair_tlv_new();
+
+  // Calculate A
+  srp_user_start_authentication(sctx->user, &auth_username, &sctx->pkA, &sctx->pkA_len);
+
+  // Calculate M1 (client proof)
+  srp_user_process_challenge(sctx->user, (const unsigned char *)sctx->salt, sctx->salt_len, (const unsigned char *)sctx->pkB, sctx->pkB_len, &sctx->M1, &sctx->M1_len);
+
+  pair_tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_SETUP_MSG03].state, sizeof(pair_keys_map[PAIR_SETUP_MSG03].state));
+  pair_tlv_add_value(request, TLVType_PublicKey, sctx->pkA, sctx->pkA_len);
+  pair_tlv_add_value(request, TLVType_Proof, sctx->M1, sctx->M1_len);
+
+  ret = pair_tlv_format(request, data, &data_len);
+  if (ret < 0)
+    {
+      handle->errmsg = "Setup request 2: pair_tlv_format returned an error";
+      goto error;
+    }
+
+  *len = data_len;
+
+  pair_tlv_free(request);
+  return data;
+
+ error:
+  pair_tlv_free(request);
+  free(data);
+  return NULL;
+}
+
+static uint8_t *
+client_setup_request3(size_t *len, struct pair_setup_context *handle)
+{
+  struct pair_client_setup_context *sctx = &handle->sctx.client;
+  pair_tlv_values_t *request;
+  uint8_t *data;
+  size_t data_len;
+  const unsigned char *session_key;
+  int session_key_len;
+  uint8_t device_x[32];
+  uint8_t nonce[NONCE_LENGTH] = { 0 };
+  uint8_t tag[AUTHTAG_LENGTH];
+  uint8_t derived_key[32];
+  pair_tlv_values_t *append;
+  size_t append_len;
+  uint8_t *encrypted_data = NULL;
+  size_t encrypted_data_len;
+  int ret;
+
+  data_len = REQUEST_BUFSIZE;
+  data = malloc(data_len);
+  request = pair_tlv_new();
+
+  session_key = srp_user_get_session_key(sctx->user, &session_key_len);
+  if (!session_key)
+    {
+      handle->errmsg = "Setup request 3: No valid session key";
+      goto error;
+    }
+
+  ret = hkdf_extract_expand(device_x, sizeof(device_x), session_key, session_key_len, PAIR_SETUP_CONTROLLER_SIGN);
+  if (ret < 0)
+    {
+      handle->errmsg = "Setup request 3: hkdf error getting device_x";
+      goto error;
+    }
+
+  ret = create_and_sign_device_info(data, &data_len, sctx->device_id, device_x, sizeof(device_x), sctx->public_key, sizeof(sctx->public_key), sctx->private_key);
+  if (ret < 0)
+    {
+      handle->errmsg = "Setup request 3: error creating signed device info";
+      goto error;
+    }
+
+  ret = hkdf_extract_expand(derived_key, sizeof(derived_key), session_key, session_key_len, PAIR_SETUP_MSG05);
+  if (ret < 0)
+    {
+      handle->errmsg = "Setup request 3: hkdf error getting derived_key";
+      goto error;
+    }
+
+  // Append TLV-encoded public key to *data, which already has identifier and signature
+  append = pair_tlv_new();
+  append_len = REQUEST_BUFSIZE - data_len;
+  pair_tlv_add_value(append, TLVType_PublicKey, sctx->public_key, sizeof(sctx->public_key));
+  ret = pair_tlv_format(append, data + data_len, &append_len);
+  pair_tlv_free(append);
+  if (ret < 0)
+    {
+      handle->errmsg = "Setup request 3: error appending public key to TLV";
+      goto error;
+    }
+  data_len += append_len;
+
+  memcpy(nonce + 4, pair_keys_map[PAIR_SETUP_MSG05].nonce, NONCE_LENGTH - 4);
+
+  encrypted_data_len = data_len + sizeof(tag); // Space for ciphered payload and authtag
+  encrypted_data = malloc(encrypted_data_len);
+
+  ret = encrypt_chacha(encrypted_data, data, data_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce);
+  if (ret < 0)
+    {
+      handle->errmsg = "Setup request 3: Could not encrypt";
+      goto error;
+    }
+
+  memcpy(encrypted_data + data_len, tag, sizeof(tag));
+
+  pair_tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_SETUP_MSG05].state, sizeof(pair_keys_map[PAIR_SETUP_MSG05].state));
+  pair_tlv_add_value(request, TLVType_EncryptedData, encrypted_data, encrypted_data_len);
+
+  data_len = REQUEST_BUFSIZE; // Re-using *data, so pass original length to pair_tlv_format
+  ret = pair_tlv_format(request, data, &data_len);
+  if (ret < 0)
+    {
+      handle->errmsg = "Setup request 3: error appending public key to TLV";
+      goto error;
+    }
+
+  *len = data_len;
+
+  free(encrypted_data);
+  pair_tlv_free(request);
+  return data;
+
+ error:
+  free(encrypted_data);
+  pair_tlv_free(request);
+  free(data);
+  return NULL;
+}
+
+static int
+client_setup_response1(struct pair_setup_context *handle, const uint8_t *data, size_t data_len)
+{
+  struct pair_client_setup_context *sctx = &handle->sctx.client;
+  pair_tlv_values_t *response;
+  pair_tlv_t *pk;
+  pair_tlv_t *salt;
+
+  response = message_process(data, data_len, &handle->errmsg);
+  if (!response)
+    {
+      return -1;
+    }
+
+  pk = pair_tlv_get_value(response, TLVType_PublicKey);
+  salt = pair_tlv_get_value(response, TLVType_Salt);
+  if (!pk || !salt)
+    {
+      handle->errmsg = "Setup response 1: Missing or invalid pk/salt";
+      goto error;
+    }
+
+  sctx->pkB_len = pk->size; // 384
+  sctx->pkB = malloc(sctx->pkB_len);
+  memcpy(sctx->pkB, pk->value, sctx->pkB_len);
+
+  sctx->salt_len = salt->size; // 16
+  sctx->salt = malloc(sctx->salt_len);
+  memcpy(sctx->salt, salt->value, sctx->salt_len);
+
+  pair_tlv_free(response);
+  return 0;
+
+ error:
+  pair_tlv_free(response);
+  return -1;
+}
+
+static int
+client_setup_response2(struct pair_setup_context *handle, const uint8_t *data, size_t data_len)
+{
+  struct pair_client_setup_context *sctx = &handle->sctx.client;
+  pair_tlv_values_t *response;
+  pair_tlv_t *proof;
+  const uint8_t *session_key;
+  int session_key_len;
+
+  response = message_process(data, data_len, &handle->errmsg);
+  if (!response)
+    {
+      return -1;
+    }
+
+  proof = pair_tlv_get_value(response, TLVType_Proof);
+  if (!proof)
+    {
+      handle->errmsg = "Setup response 2: Missing proof";
+      goto error;
+    }
+
+  sctx->M2_len = proof->size; // 64
+  sctx->M2 = malloc(sctx->M2_len);
+  memcpy(sctx->M2, proof->value, sctx->M2_len);
+
+  // Check M2
+  srp_user_verify_session(sctx->user, (const unsigned char *)sctx->M2);
+  if (!srp_user_is_authenticated(sctx->user))
+    {
+      handle->errmsg = "Setup response 2: Server authentication failed";
+      goto error;
+    }
+
+  if (handle->type == &pair_client_homekit_transient)
+    {
+      session_key = srp_user_get_session_key(sctx->user, &session_key_len);
+      if (!session_key)
+        {
+         handle->errmsg = "Setup response 2: Could not compute session key";
+         goto error;
+       }
+
+      assert(sizeof(handle->result.shared_secret) >= session_key_len);
+
+      memcpy(handle->result.shared_secret, session_key, session_key_len);
+      handle->result.shared_secret_len = session_key_len;
+
+      handle->status = PAIR_STATUS_COMPLETED;
+    }
+
+  pair_tlv_free(response);
+  return 0;
+
+ error:
+  pair_tlv_free(response);
+  return -1;
+}
+
+static int
+client_setup_response3(struct pair_setup_context *handle, const uint8_t *data, size_t data_len)
+{
+  struct pair_client_setup_context *sctx = &handle->sctx.client;
+  pair_tlv_values_t *response;
+  pair_tlv_t *encrypted_data;
+  pair_tlv_t *device_id;
+  pair_tlv_t *pk;
+  pair_tlv_t *signature;
+  uint8_t nonce[NONCE_LENGTH] = { 0 };
+  uint8_t tag[AUTHTAG_LENGTH];
+  uint8_t derived_key[32];
+  size_t encrypted_len;
+  uint8_t *decrypted_data = NULL;
+  const uint8_t *session_key;
+  int session_key_len;
+  uint8_t device_x[32];
+  int ret;
+
+  response = message_process(data, data_len, &handle->errmsg);
+  if (!response)
+    {
+      return -1;
+    }
+
+  encrypted_data = pair_tlv_get_value(response, TLVType_EncryptedData);
+  if (!encrypted_data)
+    {
+      handle->errmsg = "Setup response 3: Missing encrypted_data";
+      goto error;
+    }
+
+  session_key = srp_user_get_session_key(sctx->user, &session_key_len);
+  if (!session_key)
+    {
+      handle->errmsg = "Setup response 3: No valid session key";
+      goto error;
+    }
+
+  ret = hkdf_extract_expand(derived_key, sizeof(derived_key), session_key, session_key_len, PAIR_SETUP_MSG06);
+  if (ret < 0)
+    {
+      handle->errmsg = "Setup response 3: hkdf error getting derived_key";
+      goto error;
+    }
+
+  // encrypted_data->value consists of the encrypted payload + the auth tag
+  if (encrypted_data->size < AUTHTAG_LENGTH)
+    {
+      handle->errmsg = "Setup response 3: Invalid encrypted data";
+      goto error;
+    }
+
+  encrypted_len = encrypted_data->size - AUTHTAG_LENGTH;
+  memcpy(tag, encrypted_data->value + encrypted_len, AUTHTAG_LENGTH);
+  memcpy(nonce + 4, pair_keys_map[PAIR_SETUP_MSG06].nonce, NONCE_LENGTH - 4);
+
+  decrypted_data = malloc(encrypted_len);
+
+  ret = decrypt_chacha(decrypted_data, encrypted_data->value, encrypted_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce);
+  if (ret < 0)
+    {
+      handle->errmsg = "Setup response 3: Decryption error";
+      goto error;
+    }
+
+  pair_tlv_free(response);
+  response = message_process(decrypted_data, encrypted_len, &handle->errmsg);
+  if (!response)
+    {
+      goto error;
+    }
+
+  ret = hkdf_extract_expand(device_x, sizeof(device_x), session_key, session_key_len, PAIR_SETUP_ACCESSORY_SIGN);
+  if (ret < 0)
+    {
+      handle->errmsg = "Setup response 3: hkdf error getting device_x";
+      goto error;
+    }
+
+  device_id = pair_tlv_get_value(response, TLVType_Identifier);
+  pk = pair_tlv_get_value(response, TLVType_PublicKey);
+  signature = pair_tlv_get_value(response, TLVType_Signature);
+  if (!device_id || device_id->size >= sizeof(handle->result.device_id) || !pk || pk->size != crypto_sign_PUBLICKEYBYTES || !signature || signature->size != crypto_sign_BYTES)
+    {
+      handle->errmsg = "Setup response 3: Missing/invalid device ID, public key or signature";
+      goto error;
+    }
+
+  ret = verify_info(signature->value, pk->value, device_x, sizeof(device_x), device_id->value, device_id->size, pk->value, pk->size);
+  if (ret < 0)
+    {
+      handle->errmsg = "Setup request 3: Invalid signature";
+      goto error;
+    }
+
+  assert(sizeof(handle->result.client_private_key) == sizeof(sctx->private_key));
+  assert(sizeof(handle->result.client_public_key) == sizeof(sctx->public_key));
+  assert(sizeof(handle->result.server_public_key) == pk->size);
+
+  memcpy(handle->result.client_private_key, sctx->private_key, sizeof(sctx->private_key));
+  memcpy(handle->result.client_public_key, sctx->public_key, sizeof(sctx->public_key));
+  memcpy(handle->result.server_public_key, pk->value, pk->size);
+  memcpy(handle->result.device_id, device_id->value, device_id->size);
+
+  if (sctx->add_cb)
+    sctx->add_cb(handle->result.server_public_key, handle->result.device_id, sctx->add_cb_arg);
+
+  handle->status = PAIR_STATUS_COMPLETED;
+
+  free(decrypted_data);
+  pair_tlv_free(response);
+  return 0;
+
+ error:
+  free(decrypted_data);
+  pair_tlv_free(response);
+  return -1;
+}
+
+static int
+client_setup_result(struct pair_setup_context *handle)
+{
+  char *ptr;
+  int i;
+
+  assert(sizeof(handle->result_str) >= 2 * sizeof(handle->result.client_private_key) + 2 * sizeof(handle->result.server_public_key) + 1);
+
+  // It is enough to export the private key, since the public key can be
+  // extracted from that with crypto_sign_ed25519_sk_to_pk (it is the last 32
+  // bytes)
+  ptr = handle->result_str;
+  for (i = 0; i < sizeof(handle->result.client_private_key); i++)
+    ptr += sprintf(ptr, "%02x", handle->result.client_private_key[i]); // 2 x 64 bytes
+  for (i = 0; i < sizeof(handle->result.server_public_key); i++)
+    ptr += sprintf(ptr, "%02x", handle->result.server_public_key[i]); // 2 x 32 bytes
+  *ptr = '\0';
+
+  return 0;
+}
+
+static int
+client_verify_new(struct pair_verify_context *handle, const char *client_setup_keys, pair_cb cb, void *cb_arg, const char *device_id)
+{
+  struct pair_client_verify_context *vctx = &handle->vctx.client;
+  size_t hexkey_len;
+
+  if (sodium_init() == -1)
+    return -1;
+
+  if (!device_id || strlen(device_id) >= PAIR_AP_DEVICE_ID_LEN_MAX)
+    return -1;
+
+  if (!client_setup_keys)
+    return -1;
+
+  hexkey_len = strlen(client_setup_keys);
+  if (hexkey_len == 2 * sizeof(vctx->client_private_key) + 2 * sizeof(vctx->server_public_key))
+    {
+      hexread(vctx->client_private_key, sizeof(vctx->client_private_key), client_setup_keys);
+      hexread(vctx->server_public_key, sizeof(vctx->server_public_key), client_setup_keys + 2 * sizeof(vctx->client_private_key));
+      vctx->verify_server_signature = true;
+    }
+  else if (hexkey_len == 2 * sizeof(vctx->client_private_key)) // No server public key known, so signature validation will be skipped
+    {
+      hexread(vctx->client_private_key, sizeof(vctx->client_private_key), client_setup_keys);
+    }
+  else
+    return -1;
+
+  crypto_sign_ed25519_sk_to_pk(vctx->client_public_key, vctx->client_private_key);
+
+  snprintf(vctx->device_id, sizeof(vctx->device_id), "%s", device_id);
+
+  return 0;
+}
+
+static uint8_t *
+client_verify_request1(size_t *len, struct pair_verify_context *handle)
+{
+  struct pair_client_verify_context *vctx = &handle->vctx.client;
+//  const uint8_t basepoint[crypto_scalarmult_BYTES] = {9}; // 32 bytes
+  pair_tlv_values_t *request;
+  uint8_t *data;
+  size_t data_len;
+  int ret;
+
+  data_len = REQUEST_BUFSIZE;
+  data = malloc(data_len);
+  request = pair_tlv_new();
+
+  crypto_box_keypair(vctx->client_eph_public_key, vctx->client_eph_private_key);
+
+/*
+  // TODO keep around in case box_keypair doesn't work
+  ret = crypto_scalarmult(vctx->client_eph_public_key, vctx->client_eph_private_key, basepoint);
+  if (ret < 0)
+    {
+      handle->errmsg = "Verify request 1: Curve 25519 returned an error";
+      goto error;
+    }
+*/
+
+  pair_tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_VERIFY_MSG01].state, sizeof(pair_keys_map[PAIR_VERIFY_MSG01].state));
+  pair_tlv_add_value(request, TLVType_PublicKey, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key));
+
+  ret = pair_tlv_format(request, data, &data_len);
+  if (ret < 0)
+    {
+      handle->errmsg = "Verify request 1: pair_tlv_format returned an error";
+      goto error;
+    }
+
+  *len = data_len;
+
+  pair_tlv_free(request);
+  return data;
+
+ error:
+  pair_tlv_free(request);
+  free(data);
+  return NULL;
+}
+
+static uint8_t *
+client_verify_request2(size_t *len, struct pair_verify_context *handle)
+{
+  struct pair_client_verify_context *vctx = &handle->vctx.client;
+  pair_tlv_values_t *request;
+  uint8_t *data;
+  size_t data_len;
+  uint8_t nonce[NONCE_LENGTH] = { 0 };
+  uint8_t tag[AUTHTAG_LENGTH];
+  uint8_t derived_key[32];
+  uint8_t *encrypted_data = NULL;
+  size_t encrypted_data_len;
+  int ret;
+
+  data_len = REQUEST_BUFSIZE;
+  data = malloc(data_len);
+  request = pair_tlv_new();
+
+  ret = create_and_sign_device_info(data, &data_len, vctx->device_id, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key),
+                                    vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key), vctx->client_private_key);
+  if (ret < 0)
+    {
+      handle->errmsg = "Verify request 2: error creating signed device info";
+      goto error;
+    }
+
+  ret = hkdf_extract_expand(derived_key, sizeof(derived_key), vctx->shared_secret, sizeof(vctx->shared_secret), PAIR_VERIFY_MSG03);
+  if (ret < 0)
+    {
+      handle->errmsg = "Verify request 2: hkdf error getting derived_key";
+      goto error;
+    }
+
+  memcpy(nonce + 4, pair_keys_map[PAIR_VERIFY_MSG03].nonce, NONCE_LENGTH - 4);
+
+  encrypted_data_len = data_len + sizeof(tag); // Space for ciphered payload and authtag
+  encrypted_data = malloc(encrypted_data_len);
+
+  ret = encrypt_chacha(encrypted_data, data, data_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce);
+  if (ret < 0)
+    {
+      handle->errmsg = "Verify request 2: Could not encrypt";
+      goto error;
+    }
+
+  memcpy(encrypted_data + data_len, tag, sizeof(tag));
+
+  pair_tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_VERIFY_MSG03].state, sizeof(pair_keys_map[PAIR_VERIFY_MSG03].state));
+  pair_tlv_add_value(request, TLVType_EncryptedData, encrypted_data, encrypted_data_len);
+
+  data_len = REQUEST_BUFSIZE; // Re-using *data, so pass original length to pair_tlv_format
+  ret = pair_tlv_format(request, data, &data_len);
+  if (ret < 0)
+    {
+      handle->errmsg = "Verify request 2: pair_tlv_format returned an error";
+      goto error;
+    }
+
+  *len = data_len;
+
+  free(encrypted_data);
+  pair_tlv_free(request);
+  return data;
+
+ error:
+  free(encrypted_data);
+  pair_tlv_free(request);
+  free(data);
+  return NULL;
+}
+
+static int
+client_verify_response1(struct pair_verify_context *handle, const uint8_t *data, size_t data_len)
+{
+  struct pair_client_verify_context *vctx = &handle->vctx.client;
+  pair_tlv_values_t *response;
+  pair_tlv_t *encrypted_data;
+  pair_tlv_t *public_key;
+  pair_tlv_t *device_id;
+  pair_tlv_t *signature;
+  uint8_t nonce[NONCE_LENGTH] = { 0 };
+  uint8_t tag[AUTHTAG_LENGTH];
+  uint8_t derived_key[32];
+  size_t encrypted_len;
+  uint8_t *decrypted_data = NULL;
+  int ret;
+
+  response = message_process(data, data_len, &handle->errmsg);
+  if (!response)
+    {
+      return -1;
+    }
+
+  encrypted_data = pair_tlv_get_value(response, TLVType_EncryptedData);
+  if (!encrypted_data)
+    {
+      handle->errmsg = "Verify response 1: Missing encrypted_data";
+      goto error;
+    }
+
+  public_key = pair_tlv_get_value(response, TLVType_PublicKey);
+  if (!public_key || public_key->size != sizeof(vctx->server_eph_public_key))
+    {
+      handle->errmsg = "Verify response 1: Missing or invalid public_key";
+      goto error;
+    }
+
+  memcpy(vctx->server_eph_public_key, public_key->value, sizeof(vctx->server_eph_public_key));
+  ret = crypto_scalarmult(vctx->shared_secret, vctx->client_eph_private_key, vctx->server_eph_public_key);
+  if (ret < 0)
+    {
+      handle->errmsg = "Verify response 1: Curve 25519 returned an error";
+      goto error;
+    }
+
+  ret = hkdf_extract_expand(derived_key, sizeof(derived_key), vctx->shared_secret, sizeof(vctx->shared_secret), PAIR_VERIFY_MSG02);
+  if (ret < 0)
+    {
+      handle->errmsg = "Verify response 1: hkdf error getting derived_key";
+      goto error;
+    }
+
+  // encrypted_data->value consists of the encrypted payload + the auth tag
+  if (encrypted_data->size < AUTHTAG_LENGTH)
+    {
+      handle->errmsg = "Verify response 1: Invalid encrypted data";
+      goto error;
+    }
+
+  encrypted_len = encrypted_data->size - AUTHTAG_LENGTH;
+  memcpy(tag, encrypted_data->value + encrypted_len, AUTHTAG_LENGTH);
+  memcpy(nonce + 4, pair_keys_map[PAIR_VERIFY_MSG02].nonce, NONCE_LENGTH - 4);
+
+  decrypted_data = malloc(encrypted_len);
+
+  ret = decrypt_chacha(decrypted_data, encrypted_data->value, encrypted_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce);
+  if (ret < 0)
+    {
+      handle->errmsg = "Verify response 1: Decryption error";
+      goto error;
+    }
+
+  pair_tlv_free(response);
+  response = message_process(decrypted_data, encrypted_len, &handle->errmsg);
+  if (!response)
+    {
+      goto error;
+    }
+
+  device_id = pair_tlv_get_value(response, TLVType_Identifier);
+  signature = pair_tlv_get_value(response, TLVType_Signature);
+  if (!device_id || !signature || signature->size != crypto_sign_BYTES)
+    {
+      handle->errmsg = "Verify response 1: Missing device ID or signature";
+      goto error;
+    }
+
+  if (vctx->verify_server_signature)
+    {
+      ret = verify_info(signature->value, vctx->server_public_key, vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key),
+                        device_id->value, device_id->size, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key));
+      if (ret < 0)
+        {
+          handle->errmsg = "Verify response 1: Invalid signature";
+          goto error;
+        }
+    }
+
+  free(decrypted_data);
+  pair_tlv_free(response);
+  return 0;
+
+ error:
+  free(decrypted_data);
+  pair_tlv_free(response);
+  return -1;
+}
+
+static int
+client_verify_response2(struct pair_verify_context *handle, const uint8_t *data, size_t data_len)
+{
+  struct pair_client_verify_context *vctx = &handle->vctx.client;
+  pair_tlv_values_t *response;
+
+  response = message_process(data, data_len, &handle->errmsg);
+  if (!response)
+    {
+      return -1;
+    }
+
+  memcpy(handle->result.shared_secret, vctx->shared_secret, sizeof(vctx->shared_secret));
+  handle->result.shared_secret_len = sizeof(vctx->shared_secret);
+
+  handle->status = PAIR_STATUS_COMPLETED;
+
+  return 0;
+}
+
+
+/* ------------------------- SERVER IMPLEMENTATION -------------------------- */
+
+// Use (unsecure) keys seeded from device_id. We need the keys to always be the
+// same during pair setup and pair verify, since the client saves them after
+// pair-setup 3, so that the signature in pair-verify 1 can be checked.
+static void
+server_keypair(uint8_t *public_key, uint8_t *private_key, const char *device_id)
+{
+  uint8_t seed[crypto_sign_SEEDBYTES] = { 0 };
+  size_t len;
+
+  len = strlen(device_id);
+  if (len > sizeof(seed))
+    len = sizeof(seed);
+
+  memcpy(seed, device_id, len);
+
+  crypto_sign_seed_keypair(public_key, private_key, seed);
+}
+
+static uint8_t *
+server_auth_failed_response(size_t *len, enum pair_keys msg_state)
+{
+  pair_tlv_values_t *response;
+  uint8_t *data;
+  size_t data_len;
+  uint8_t error = TLVError_Authentication;
+
+  data_len = REQUEST_BUFSIZE;
+  data = malloc(data_len);
+  response = pair_tlv_new();
+
+  pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state));
+  pair_tlv_add_value(response, TLVType_Error, &error, sizeof(error));
+
+  pair_tlv_format(response, data, &data_len);
+  pair_tlv_free(response);
+
+  *len = data_len;
+
+  return data;
+}
+
+static int
+server_setup_new(struct pair_setup_context *handle, const char *pin, pair_cb add_cb, void *cb_arg, const char *device_id)
+{
+  struct pair_server_setup_context *sctx = &handle->sctx.server;
+
+  if (sodium_init() == -1)
+    return -1;
+
+  if (!pin)
+    pin = "3939";
+
+  if (!device_id || strlen(device_id) >= PAIR_AP_DEVICE_ID_LEN_MAX)
+    return -1;
+
+  memcpy(sctx->pin, pin, sizeof(sctx->pin));
+
+  sctx->add_cb = add_cb;
+  sctx->add_cb_arg = cb_arg;
+
+  snprintf(sctx->device_id, sizeof(sctx->device_id), "%s", device_id);
+
+  server_keypair(sctx->public_key, sctx->private_key, sctx->device_id);
+
+  return 0;
+}
+
+static void
+server_setup_free(struct pair_setup_context *handle)
+{
+  struct pair_server_setup_context *sctx = &handle->sctx.server;
+
+  srp_verifier_free(sctx->verifier);
+
+  free(sctx->pkA);
+  free(sctx->pkB);
+  free(sctx->b);
+  free(sctx->M1);
+  free(sctx->v);
+  free(sctx->salt);
+}
+
+static int
+server_setup_request1(struct pair_setup_context *handle, const uint8_t *data, size_t data_len)
+{
+  struct pair_server_setup_context *sctx = &handle->sctx.server;
+//  enum pair_keys msg_state = PAIR_SETUP_MSG01;
+  pair_tlv_values_t *request;
+  pair_tlv_t *method;
+  pair_tlv_t *type;
+  int ret;
+
+  request = message_process(data, data_len, &handle->errmsg);
+  if (!request)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg);
+    }
+
+  method = pair_tlv_get_value(request, TLVType_Method);
+  if (!method || method->size != 1 || method->value[0] != 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 1: Missing or unexpected pairing method in TLV");
+    }
+
+  type = pair_tlv_get_value(request, TLVType_Flags);
+  sctx->is_transient = (type && type->size == 1 && type->value[0] == PairingFlagsTransient);
+
+  // Note this is modified to return a 16 byte salt
+  ret = srp_create_salted_verification_key(HASH_SHA512, SRP_NG_3072, USERNAME, (unsigned char *)sctx->pin, sizeof(sctx->pin),
+    &sctx->salt, &sctx->salt_len, &sctx->v, &sctx->v_len, NULL, NULL);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 1: Could not create verification key");
+    }
+
+  ret = srp_verifier_start_authentication(HASH_SHA512, SRP_NG_3072, sctx->v, sctx->v_len,
+    &sctx->b, &sctx->b_len, &sctx->pkB, &sctx->pkB_len, NULL, NULL);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 1: Could not compute B");
+    }
+
+  pair_tlv_free(request);
+  return 0;
+
+ error:
+  pair_tlv_free(request);
+  return -1;
+}
+
+static int
+server_setup_request2(struct pair_setup_context *handle, const uint8_t *data, size_t data_len)
+{
+  struct pair_server_setup_context *sctx = &handle->sctx.server;
+//  enum pair_keys msg_state = PAIR_SETUP_MSG03;
+  pair_tlv_values_t *request;
+  pair_tlv_t *pk;
+  pair_tlv_t *proof;
+
+  request = message_process(data, data_len, &handle->errmsg);
+  if (!request)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg);
+    }
+
+  pk = pair_tlv_get_value(request, TLVType_PublicKey);
+  proof = pair_tlv_get_value(request, TLVType_Proof);
+  if (!pk || !proof)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 2: Missing pkA or proof");
+    }
+
+  sctx->pkA_len = pk->size; // 384
+  sctx->pkA = malloc(sctx->pkA_len);
+  memcpy(sctx->pkA, pk->value, sctx->pkA_len);
+
+  sctx->M1_len = proof->size; // 64
+  sctx->M1 = malloc(sctx->M1_len);
+  memcpy(sctx->M1, proof->value, sctx->M1_len);
+
+  sctx->verifier = srp_verifier_new(HASH_SHA512, SRP_NG_3072, USERNAME, sctx->salt, sctx->salt_len, sctx->v, sctx->v_len,
+    sctx->pkA, sctx->pkA_len, sctx->b, sctx->b_len, sctx->pkB, sctx->pkB_len, NULL, NULL);
+  if (!sctx->verifier)
+    {
+      handle->status = PAIR_STATUS_AUTH_FAILED;
+      goto out;
+    }
+
+  sctx->M2_len = 64; // 512 bit hash
+  srp_verifier_verify_session(sctx->verifier, sctx->M1, &sctx->M2);
+  if (!sctx->M2)
+    {
+      handle->status = PAIR_STATUS_AUTH_FAILED;
+      goto out; // Not an error, server should give proper TLV-formatet reply
+    }
+
+ out:
+  pair_tlv_free(request);
+  return 0;
+
+ error:
+  pair_tlv_free(request);
+  return -1;
+}
+
+static int
+server_setup_request3(struct pair_setup_context *handle, const uint8_t *data, size_t data_len)
+{
+  struct pair_server_setup_context *sctx = &handle->sctx.server;
+  enum pair_keys msg_state = PAIR_SETUP_MSG05;
+  pair_tlv_values_t *request;
+  pair_tlv_t *encrypted_data;
+  pair_tlv_t *device_id;
+  pair_tlv_t *pk;
+  pair_tlv_t *signature;
+  const uint8_t *session_key;
+  int session_key_len;
+  uint8_t nonce[NONCE_LENGTH] = { 0 };
+  uint8_t tag[AUTHTAG_LENGTH];
+  uint8_t derived_key[32];
+  size_t encrypted_len;
+  uint8_t *decrypted_data = NULL;
+  uint8_t device_x[32];
+  int ret;
+
+  request = message_process(data, data_len, &handle->errmsg);
+  if (!request)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg);
+    }
+
+  session_key = srp_verifier_get_session_key(sctx->verifier, &session_key_len);
+  if (!session_key)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: No valid session key");
+    }
+
+  ret = hkdf_extract_expand(derived_key, sizeof(derived_key), session_key, session_key_len, msg_state);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: hkdf error getting derived_key");
+    }
+
+  encrypted_data = pair_tlv_get_value(request, TLVType_EncryptedData);
+  if (!encrypted_data)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: Missing encrypted_data");
+    }
+
+  // encrypted_data->value consists of the encrypted payload + the auth tag
+  if (encrypted_data->size < AUTHTAG_LENGTH)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: Invalid encrypted data");
+    }
+
+  encrypted_len = encrypted_data->size - AUTHTAG_LENGTH;
+  memcpy(tag, encrypted_data->value + encrypted_len, AUTHTAG_LENGTH);
+  memcpy(nonce + 4, pair_keys_map[msg_state].nonce, NONCE_LENGTH - 4);
+
+  decrypted_data = malloc(encrypted_len);
+
+  ret = decrypt_chacha(decrypted_data, encrypted_data->value, encrypted_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: Decryption error");
+    }
+
+  pair_tlv_free(request);
+  request = message_process(decrypted_data, encrypted_len, &handle->errmsg);
+  if (!request)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg);
+    }
+
+  ret = hkdf_extract_expand(device_x, sizeof(device_x), session_key, session_key_len, PAIR_SETUP_CONTROLLER_SIGN);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: hkdf error getting device_x");
+    }
+
+  device_id = pair_tlv_get_value(request, TLVType_Identifier);
+  pk = pair_tlv_get_value(request, TLVType_PublicKey);
+  signature = pair_tlv_get_value(request, TLVType_Signature);
+  if (!device_id || device_id->size >= sizeof(handle->result.device_id) || !pk || pk->size != crypto_sign_PUBLICKEYBYTES || !signature || signature->size != crypto_sign_BYTES)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: Missing/invalid device ID, public key or signature");
+    }
+
+  ret = verify_info(signature->value, pk->value, device_x, sizeof(device_x), device_id->value, device_id->size, pk->value, pk->size);
+  if (ret < 0)
+    {
+      handle->status = PAIR_STATUS_AUTH_FAILED;
+      goto out;
+    }
+
+  memcpy(handle->result.device_id, device_id->value, device_id->size);
+  memcpy(handle->result.client_public_key, pk->value, pk->size);
+
+ out:
+  free(decrypted_data);
+  pair_tlv_free(request);
+  return 0;
+
+ error:
+  free(decrypted_data);
+  pair_tlv_free(request);
+  return -1;
+}
+
+static uint8_t *
+server_setup_response1(size_t *len, struct pair_setup_context *handle)
+{
+  struct pair_server_setup_context *sctx = &handle->sctx.server;
+  enum pair_keys msg_state = PAIR_SETUP_MSG02;
+  pair_tlv_values_t *response;
+  uint8_t *data;
+  size_t data_len;
+  int ret;
+
+  if (handle->status == PAIR_STATUS_AUTH_FAILED)
+    return server_auth_failed_response(len, msg_state);
+
+  data_len = REQUEST_BUFSIZE;
+  data = malloc(data_len);
+  response = pair_tlv_new();
+
+  pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state));
+  pair_tlv_add_value(response, TLVType_Salt, sctx->salt, sctx->salt_len); // 16
+  pair_tlv_add_value(response, TLVType_PublicKey, sctx->pkB, sctx->pkB_len); // 384
+
+  ret = pair_tlv_format(response, data, &data_len);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 1: pair_tlv_format returned an error");
+    }
+
+  *len = data_len;
+
+  pair_tlv_free(response);
+  return data;
+
+ error:
+  free(data);
+  pair_tlv_free(response);
+  return NULL;
+}
+
+static uint8_t *
+server_setup_response2(size_t *len, struct pair_setup_context *handle)
+{
+  struct pair_server_setup_context *sctx = &handle->sctx.server;
+  enum pair_keys msg_state = PAIR_SETUP_MSG04;
+  pair_tlv_values_t *response;
+  uint8_t *data;
+  size_t data_len;
+  const uint8_t *session_key;
+  int session_key_len;
+  int ret;
+
+  if (handle->status == PAIR_STATUS_AUTH_FAILED)
+    return server_auth_failed_response(len, msg_state);
+
+  data_len = REQUEST_BUFSIZE;
+  data = malloc(data_len);
+  response = pair_tlv_new();
+
+  pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state));
+  pair_tlv_add_value(response, TLVType_Proof, sctx->M2, sctx->M2_len); // 384
+
+  ret = pair_tlv_format(response, data, &data_len);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 2: pair_tlv_format returned an error");
+    }
+
+  if (sctx->is_transient)
+    {
+      session_key = srp_verifier_get_session_key(sctx->verifier, &session_key_len);
+      if (!session_key)
+        {
+         RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 2: Could not compute session key");
+       }
+
+      assert(sizeof(handle->result.shared_secret) >= session_key_len);
+
+      memcpy(handle->result.shared_secret, session_key, session_key_len);
+      handle->result.shared_secret_len = session_key_len;
+
+      handle->status = PAIR_STATUS_COMPLETED;
+    }
+
+  *len = data_len;
+
+  pair_tlv_free(response);
+  return data;
+
+ error:
+  free(data);
+  pair_tlv_free(response);
+  return NULL;
+}
+
+static uint8_t *
+server_setup_response3(size_t *len, struct pair_setup_context *handle)
+{
+  struct pair_server_setup_context *sctx = &handle->sctx.server;
+  enum pair_keys msg_state = PAIR_SETUP_MSG06;
+  const uint8_t *session_key;
+  int session_key_len;
+  pair_tlv_values_t *response;
+  uint8_t nonce[NONCE_LENGTH] = { 0 };
+  uint8_t tag[AUTHTAG_LENGTH];
+  uint8_t derived_key[32];
+  pair_tlv_values_t *append;
+  size_t append_len;
+  uint8_t *encrypted_data = NULL;
+  size_t encrypted_data_len;
+  uint8_t *data;
+  size_t data_len;
+  uint8_t device_x[32];
+  int ret;
+
+  if (handle->status == PAIR_STATUS_AUTH_FAILED)
+    return server_auth_failed_response(len, msg_state);
+
+  data_len = REQUEST_BUFSIZE;
+  data = malloc(data_len);
+  response = pair_tlv_new();
+
+  session_key = srp_verifier_get_session_key(sctx->verifier, &session_key_len);
+  if (!session_key)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: No valid session key");
+    }
+
+  ret = hkdf_extract_expand(device_x, sizeof(device_x), session_key, session_key_len, PAIR_SETUP_ACCESSORY_SIGN);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: hkdf error getting device_x");
+    }
+
+  ret = create_and_sign_device_info(data, &data_len, sctx->device_id, device_x, sizeof(device_x), sctx->public_key, sizeof(sctx->public_key), sctx->private_key);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: create device info returned an error");
+    }
+
+  // Append TLV-encoded public key to *data, which already has identifier and signature
+  append = pair_tlv_new();
+  append_len = REQUEST_BUFSIZE - data_len;
+  pair_tlv_add_value(append, TLVType_PublicKey, sctx->public_key, sizeof(sctx->public_key));
+  ret = pair_tlv_format(append, data + data_len, &append_len);
+  pair_tlv_free(append);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: error appending public key to TLV");
+    }
+  data_len += append_len;
+
+  ret = hkdf_extract_expand(derived_key, sizeof(derived_key), session_key, session_key_len, msg_state);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: hkdf error getting derived_key");
+    }
+
+  memcpy(nonce + 4, pair_keys_map[msg_state].nonce, NONCE_LENGTH - 4);
+
+  encrypted_data_len = data_len + sizeof(tag); // Space for ciphered payload and authtag
+  encrypted_data = malloc(encrypted_data_len);
+
+  ret = encrypt_chacha(encrypted_data, data, data_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: Could not encrypt");
+    }
+
+  memcpy(encrypted_data + data_len, tag, sizeof(tag));
+
+  pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state));
+  pair_tlv_add_value(response, TLVType_EncryptedData, encrypted_data, encrypted_data_len);
+
+  data_len = REQUEST_BUFSIZE; // Re-using *data, so pass original length to pair_tlv_format
+  ret = pair_tlv_format(response, data, &data_len);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: error appending public key to TLV");
+    }
+
+  if (sctx->add_cb)
+    sctx->add_cb(handle->result.client_public_key, handle->result.device_id, sctx->add_cb_arg);
+
+  handle->status = PAIR_STATUS_COMPLETED;
+
+  *len = data_len;
+
+  free(encrypted_data);
+  pair_tlv_free(response);
+  return data;
+
+ error:
+  free(encrypted_data);
+  free(data);
+  pair_tlv_free(response);
+  return NULL;
+}
+
+
+static int
+server_verify_new(struct pair_verify_context *handle, const char *client_setup_keys, pair_cb cb, void *cb_arg, const char *device_id)
+{
+  struct pair_server_verify_context *vctx = &handle->vctx.server;
+
+  if (sodium_init() == -1)
+    return -1;
+
+  if (client_setup_keys)
+    return -1;
+
+  if (!device_id || strlen(device_id) >= PAIR_AP_DEVICE_ID_LEN_MAX)
+    return -1;
+
+  snprintf(vctx->device_id, sizeof(vctx->device_id), "%s", device_id);
+
+  vctx->get_cb = cb;
+  vctx->get_cb_arg = cb_arg;
+  vctx->verify_client_signature = cb;
+
+  server_keypair(vctx->server_public_key, vctx->server_private_key, vctx->device_id);
+
+  return 0;
+}
+
+static int
+server_verify_request1(struct pair_verify_context *handle, const uint8_t *data, size_t data_len)
+{
+  struct pair_server_verify_context *vctx = &handle->vctx.server;
+//  enum pair_keys msg_state = PAIR_VERIFY_MSG01;
+  pair_tlv_values_t *request;
+  pair_tlv_t *pk;
+
+  request = message_process(data, data_len, &handle->errmsg);
+  if (!request)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg);
+    }
+
+  pk = pair_tlv_get_value(request, TLVType_PublicKey);
+  if (!pk || pk->size != sizeof(vctx->client_eph_public_key))
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 1: Missing or invalid public_key");
+    }
+
+  memcpy(vctx->client_eph_public_key, pk->value, sizeof(vctx->client_eph_public_key));
+
+  pair_tlv_free(request);
+  return 0;
+
+ error:
+  pair_tlv_free(request);
+  return -1;
+}
+
+static int
+server_verify_request2(struct pair_verify_context *handle, const uint8_t *data, size_t data_len)
+{
+  struct pair_server_verify_context *vctx = &handle->vctx.server;
+  enum pair_keys msg_state = PAIR_VERIFY_MSG03;
+  pair_tlv_values_t *request;
+  pair_tlv_t *encrypted_data;
+  pair_tlv_t *device_id;
+  pair_tlv_t *signature;
+  uint8_t nonce[NONCE_LENGTH] = { 0 };
+  uint8_t tag[AUTHTAG_LENGTH];
+  uint8_t derived_key[32];
+  size_t encrypted_len;
+  uint8_t *decrypted_data = NULL;
+  char id_str[PAIR_AP_DEVICE_ID_LEN_MAX] = { 0 };
+  uint8_t client_public_key[crypto_sign_PUBLICKEYBYTES];
+  int ret;
+
+  request = message_process(data, data_len, &handle->errmsg);
+  if (!request)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg);
+    }
+
+  ret = hkdf_extract_expand(derived_key, sizeof(derived_key), vctx->shared_secret, sizeof(vctx->shared_secret), msg_state);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: hkdf error getting derived_key");
+    }
+
+  encrypted_data = pair_tlv_get_value(request, TLVType_EncryptedData);
+  if (!encrypted_data)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: Missing encrypted_data");
+    }
+
+  // encrypted_data->value consists of the encrypted payload + the auth tag
+  if (encrypted_data->size < AUTHTAG_LENGTH)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: Invalid encrypted data");
+    }
+
+  encrypted_len = encrypted_data->size - AUTHTAG_LENGTH;
+  memcpy(tag, encrypted_data->value + encrypted_len, AUTHTAG_LENGTH);
+  memcpy(nonce + 4, pair_keys_map[msg_state].nonce, NONCE_LENGTH - 4);
+
+  decrypted_data = malloc(encrypted_len);
+
+  ret = decrypt_chacha(decrypted_data, encrypted_data->value, encrypted_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: Decryption error");
+    }
+
+  pair_tlv_free(request);
+  request = message_process(decrypted_data, encrypted_len, &handle->errmsg);
+  if (!request)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg);
+    }
+
+  device_id = pair_tlv_get_value(request, TLVType_Identifier);
+  signature = pair_tlv_get_value(request, TLVType_Signature);
+  if (!device_id || !signature || signature->size != crypto_sign_BYTES)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: Missing identifier or signature");
+    }
+
+  if (vctx->verify_client_signature)
+    {
+      if (device_id->size >= sizeof(id_str))
+        {
+          RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: Device ID from peer is too long");
+        }
+
+      memcpy(id_str, device_id->value, device_id->size);
+
+      ret = vctx->get_cb(client_public_key, id_str, vctx->get_cb_arg);
+      if (ret < 0)
+        {
+          handle->status = PAIR_STATUS_AUTH_FAILED;
+          goto out;
+        }
+
+      ret = verify_info(signature->value, client_public_key, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key),
+                        device_id->value, device_id->size, vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key));
+      if (ret < 0)
+        {
+          handle->status = PAIR_STATUS_AUTH_FAILED;
+          goto out;
+        }
+    }
+
+ out:
+  free(decrypted_data);
+  pair_tlv_free(request);
+  return 0;
+
+ error:
+  free(decrypted_data);
+  pair_tlv_free(request);
+  return -1;
+}
+
+static uint8_t *
+server_verify_response1(size_t *len, struct pair_verify_context *handle)
+{
+  struct pair_server_verify_context *vctx = &handle->vctx.server;
+  enum pair_keys msg_state = PAIR_VERIFY_MSG02;
+  pair_tlv_values_t *response;
+  uint8_t nonce[NONCE_LENGTH] = { 0 };
+  uint8_t tag[AUTHTAG_LENGTH];
+  uint8_t derived_key[32];
+  uint8_t *encrypted_data = NULL;
+  size_t encrypted_data_len;
+  uint8_t *data;
+  size_t data_len;
+  int ret;
+
+  if (handle->status == PAIR_STATUS_AUTH_FAILED)
+    return server_auth_failed_response(len, msg_state);
+
+  data_len = REQUEST_BUFSIZE;
+  data = malloc(data_len);
+  response = pair_tlv_new();
+
+  crypto_box_keypair(vctx->server_eph_public_key, vctx->server_eph_private_key);
+
+  ret = crypto_scalarmult(vctx->shared_secret, vctx->server_eph_private_key, vctx->client_eph_public_key);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 1: Error generating shared secret");
+    }
+
+  ret = create_and_sign_accessory_info(data, &data_len, vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key), vctx->device_id,
+                                       vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key), vctx->server_private_key);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 1: Error creating device info");
+    }
+
+  ret = hkdf_extract_expand(derived_key, sizeof(derived_key), vctx->shared_secret, sizeof(vctx->shared_secret), msg_state);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 1: hkdf error getting derived_key");
+    }
+
+  memcpy(nonce + 4, pair_keys_map[msg_state].nonce, NONCE_LENGTH - 4);
+
+  encrypted_data_len = data_len + sizeof(tag); // Space for ciphered payload and authtag
+  encrypted_data = malloc(encrypted_data_len);
+
+  ret = encrypt_chacha(encrypted_data, data, data_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 1: Could not encrypt");
+    }
+
+  memcpy(encrypted_data + data_len, tag, sizeof(tag));
+
+  pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state));
+  pair_tlv_add_value(response, TLVType_PublicKey, vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key));
+  pair_tlv_add_value(response, TLVType_EncryptedData, encrypted_data, encrypted_data_len);
+
+  data_len = REQUEST_BUFSIZE; // Re-using *data, so pass original length to pair_tlv_format
+  ret = pair_tlv_format(response, data, &data_len);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 1: pair_tlv_format returned an error");
+    }
+
+  *len = data_len;
+
+  free(encrypted_data);
+  pair_tlv_free(response);
+  return data;
+
+ error:
+  free(encrypted_data);
+  free(data);
+  pair_tlv_free(response);
+  return NULL;
+}
+
+static uint8_t *
+server_verify_response2(size_t *len, struct pair_verify_context *handle)
+{
+  struct pair_server_verify_context *vctx = &handle->vctx.server;
+  enum pair_keys msg_state = PAIR_VERIFY_MSG04;
+  pair_tlv_values_t *response;
+  uint8_t *data;
+  size_t data_len;
+  int ret;
+
+  if (handle->status == PAIR_STATUS_AUTH_FAILED)
+    return server_auth_failed_response(len, msg_state);
+
+  data_len = REQUEST_BUFSIZE;
+  data = malloc(data_len);
+  response = pair_tlv_new();
+
+  pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state));
+
+  ret = pair_tlv_format(response, data, &data_len);
+  if (ret < 0)
+    {
+      RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 2: pair_tlv_format returned an error");
+    }
+
+  *len = data_len;
+
+  memcpy(handle->result.shared_secret, vctx->shared_secret, sizeof(vctx->shared_secret));
+  handle->result.shared_secret_len = sizeof(vctx->shared_secret);
+
+  handle->status = PAIR_STATUS_COMPLETED;
+
+  pair_tlv_free(response);
+  return data;
+
+ error:
+  free(data);
+  pair_tlv_free(response);
+  return NULL;
+}
+
+static int
+server_add_remove_request(pair_cb cb, void *cb_arg, const uint8_t *in, size_t in_len)
+{
+  const char *errmsg;
+  pair_tlv_values_t *request;
+  pair_tlv_t *device_id;
+  pair_tlv_t *pk;
+  char id_str[PAIR_AP_DEVICE_ID_LEN_MAX] = { 0 };
+
+  request = message_process(in, in_len, &errmsg);
+  if (!request)
+    {
+      goto error;
+    }
+
+  device_id = pair_tlv_get_value(request, TLVType_Identifier);
+  pk = pair_tlv_get_value(request, TLVType_PublicKey);
+  if (!device_id || device_id->size >= sizeof(id_str) || !pk || pk->size != crypto_sign_PUBLICKEYBYTES)
+    {
+      goto error;
+    }
+
+  memcpy(id_str, device_id->value, device_id->size);
+
+  cb(pk->value, id_str, cb_arg);
+
+  pair_tlv_free(request);
+  return 0;
+
+ error:
+  pair_tlv_free(request);
+  return -1;
+}
+
+static uint8_t *
+server_add_remove_response(size_t *len)
+{
+  pair_tlv_values_t *response;
+  uint8_t *data;
+  uint8_t state = 2;
+  size_t data_len;
+  int ret;
+
+  data_len = REQUEST_BUFSIZE;
+  data = malloc(data_len);
+  response = pair_tlv_new();
+
+  pair_tlv_add_value(response, TLVType_State, &state, sizeof(state));
+
+  ret = pair_tlv_format(response, data, &data_len);
+  if (ret < 0)
+    {
+      goto error;
+    }
+
+  *len = data_len;
+
+  pair_tlv_free(response);
+  return data;
+
+ error:
+  free(data);
+  pair_tlv_free(response);
+  return NULL;
+}
+
+static int
+server_add_remove(uint8_t **out, size_t *out_len, pair_cb cb, void *cb_arg, const uint8_t *in, size_t in_len)
+{
+  int ret;
+
+  ret = server_add_remove_request(cb, cb_arg, in, in_len);
+  if (ret < 0)
+    return -1;
+
+  *out = server_add_remove_response(out_len);
+  if (!*out)
+    return -1;
+
+  return 0;
+}
+
+static int
+server_list_cb(uint8_t public_key[crypto_sign_PUBLICKEYBYTES], const char *device_id, void *cb_arg)
+{
+  pair_tlv_values_t *response = cb_arg;
+
+  pair_tlv_add_value(response, TLVType_Identifier, (unsigned char *)device_id, strlen(device_id));
+  pair_tlv_add_value(response, TLVType_PublicKey, public_key, crypto_sign_PUBLICKEYBYTES);
+  return 0;
+}
+
+static uint8_t *
+server_list_response(size_t *len, pair_list_cb cb, void *cb_arg)
+{
+  pair_tlv_values_t *response;
+  uint8_t *data;
+  uint8_t state = 2;
+  size_t data_len;
+  int ret;
+
+  data_len = REQUEST_BUFSIZE;
+  data = malloc(data_len);
+  response = pair_tlv_new();
+
+  pair_tlv_add_value(response, TLVType_State, &state, sizeof(state));
+
+  cb(server_list_cb, response, cb_arg);
+
+  ret = pair_tlv_format(response, data, &data_len);
+  if (ret < 0)
+    {
+      goto error;
+    }
+
+  *len = data_len;
+
+  pair_tlv_free(response);
+  return data;
+
+ error:
+  free(data);
+  pair_tlv_free(response);
+  return NULL;
+}
+
+static int
+server_list(uint8_t **out, size_t *out_len, pair_list_cb cb, void *cb_arg, const uint8_t *in, size_t in_len)
+{
+  // Skip reading the request, it just has state = 1 and pair method =
+  // PairingMethodListPairings
+
+  *out = server_list_response(out_len, cb, cb_arg);
+  if (!*out)
+    return -1;
+
+  return 0;
+}
+
+
+/* ----------------------- CIPHERING IMPLEMENTATION ------------------------- */
+
+static void
+cipher_free(struct pair_cipher_context *cctx)
+{
+  if (!cctx)
+    return;
+
+  free(cctx);
+}
+
+static struct pair_cipher_context *
+cipher_new(struct pair_definition *type, int channel, const uint8_t *shared_secret, size_t shared_secret_len)
+{
+  struct pair_cipher_context *cctx;
+  enum pair_keys write_key;
+  enum pair_keys read_key;
+  int ret;
+
+  // Note that events is opposite, probably because it is a reverse connection
+  switch (channel)
+    {
+      case 0:
+       write_key = PAIR_CONTROL_WRITE;
+       read_key = PAIR_CONTROL_READ;
+       break;
+      case 1:
+       write_key = PAIR_EVENTS_READ;
+       read_key = PAIR_EVENTS_WRITE;
+       break;
+      case 2:
+       write_key = PAIR_CONTROL_READ;
+       read_key = PAIR_CONTROL_WRITE;
+       break;
+      case 3:
+       write_key = PAIR_EVENTS_WRITE;
+       read_key = PAIR_EVENTS_READ;
+       break;
+      default:
+       return NULL;
+    }
+
+  cctx = calloc(1, sizeof(struct pair_cipher_context));
+  if (!cctx)
+    goto error;
+
+  cctx->type = type;
+
+  ret = hkdf_extract_expand(cctx->encryption_key, sizeof(cctx->encryption_key), shared_secret, shared_secret_len, write_key);
+  if (ret < 0)
+    goto error;
+
+  ret = hkdf_extract_expand(cctx->decryption_key, sizeof(cctx->decryption_key), shared_secret, shared_secret_len, read_key);
+  if (ret < 0)
+    goto error;
+
+  return cctx;
+
+ error:
+  pair_cipher_free(cctx);
+  return NULL;
+}
+
+static ssize_t
+encrypt(uint8_t **ciphertext, size_t *ciphertext_len, const uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx)
+{
+  uint8_t nonce[NONCE_LENGTH] = { 0 };
+  uint8_t tag[AUTHTAG_LENGTH];
+  const uint8_t *plain_block;
+  uint8_t *cipher_block;
+  uint16_t block_len;
+  int nblocks;
+  int ret;
+  int i;
+
+  if (plaintext_len == 0 || !plaintext)
+    return -1;
+
+  // Encryption is done in blocks, where each block consists of a short, the
+  // encrypted data and an auth tag. The short is the size of the encrypted
+  // data. The encrypted data in the block cannot exceed ENCRYPTED_LEN_MAX.
+  nblocks = 1 + ((plaintext_len - 1) / ENCRYPTED_LEN_MAX); // Ceiling of division
+
+  *ciphertext_len = nblocks * (sizeof(block_len) + AUTHTAG_LENGTH) + plaintext_len;
+  *ciphertext = malloc(*ciphertext_len);
+
+  cctx->encryption_counter_prev = cctx->encryption_counter;
+
+  for (i = 0, plain_block = plaintext, cipher_block = *ciphertext; i < nblocks; i++)
+    {
+      // If it is the last block we will encrypt only the remaining data
+      block_len = (i + 1 == nblocks) ? (plaintext + plaintext_len - plain_block) : ENCRYPTED_LEN_MAX;
+
+      memcpy(nonce + 4, &(cctx->encryption_counter), sizeof(cctx->encryption_counter));// TODO BE or LE?
+
+      // Write the ciphered block
+      memcpy(cipher_block, &block_len, sizeof(block_len)); // TODO BE or LE?
+      ret = encrypt_chacha(cipher_block + sizeof(block_len), plain_block, block_len, cctx->encryption_key, sizeof(cctx->encryption_key), &block_len, sizeof(block_len), tag, sizeof(tag), nonce);
+      if (ret < 0)
+       {
+         cctx->errmsg = "Encryption with chacha poly1305 failed";
+         cctx->encryption_counter = cctx->encryption_counter_prev;
+         free(*ciphertext);
+         return -1;
+       }
+      memcpy(cipher_block + sizeof(block_len) + block_len, tag, AUTHTAG_LENGTH);
+
+      plain_block += block_len;
+      cipher_block += block_len + sizeof(block_len) + AUTHTAG_LENGTH;
+      cctx->encryption_counter++;
+    }
+
+#ifdef DEBUG_PAIR
+  hexdump("Encrypted:\n", *ciphertext, *ciphertext_len);
+#endif
+
+  return plain_block - plaintext;
+}
+
+static ssize_t
+decrypt(uint8_t **plaintext, size_t *plaintext_len, const uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx)
+{
+  uint8_t nonce[NONCE_LENGTH] = { 0 };
+  uint8_t tag[AUTHTAG_LENGTH];
+  uint8_t *plain_block;
+  const uint8_t *cipher_block;
+  uint16_t block_len;
+  int ret;
+
+  if (ciphertext_len < sizeof(block_len) || !ciphertext)
+    return -1;
+
+  // This will allocate more than we need. Since we don't know the number of
+  // blocks in the ciphertext yet we can't calculate the exact required length.
+  *plaintext = malloc(ciphertext_len);
+
+  cctx->decryption_counter_prev = cctx->decryption_counter;
+
+  for (plain_block = *plaintext, cipher_block = ciphertext; cipher_block < ciphertext + ciphertext_len; )
+    {
+      memcpy(&block_len, cipher_block, sizeof(block_len)); // TODO BE or LE?
+      if (cipher_block + block_len + sizeof(block_len) + AUTHTAG_LENGTH > ciphertext + ciphertext_len)
+       {
+         // The remaining ciphertext doesn't contain an entire block, so stop
+         break;
+       }
+
+      memcpy(tag, cipher_block + sizeof(block_len) + block_len, sizeof(tag));
+      memcpy(nonce + 4, &(cctx->decryption_counter), sizeof(cctx->decryption_counter));// TODO BE or LE?
+
+      ret = decrypt_chacha(plain_block, cipher_block + sizeof(block_len), block_len, cctx->decryption_key, sizeof(cctx->decryption_key), &block_len, sizeof(block_len), tag, sizeof(tag), nonce);
+      if (ret < 0)
+       {
+         cctx->errmsg = "Decryption with chacha poly1305 failed";
+         cctx->decryption_counter = cctx->decryption_counter_prev;
+         free(*plaintext);
+         return -1;
+       }
+
+      plain_block += block_len;
+      cipher_block += block_len + sizeof(block_len) + AUTHTAG_LENGTH;
+      cctx->decryption_counter++;
+    }
+
+  *plaintext_len = plain_block - *plaintext;
+
+#ifdef DEBUG_PAIR
+  hexdump("Decrypted:\n", *plaintext, *plaintext_len);
+#endif
+
+  return cipher_block - ciphertext;
+}
+
+static int
+state_get(const char **errmsg, const uint8_t *data, size_t data_len)
+{
+  pair_tlv_values_t *message;
+  pair_tlv_t *state;
+  int ret;
+
+  if (!data || data_len == 0)
+    {
+      return 0; // state 0 = no incoming data yet -> first request
+    }
+
+  message = message_process(data, data_len, errmsg);
+  if (!message)
+    {
+      goto error;
+    }
+
+  state = pair_tlv_get_value(message, TLVType_State);
+  if (!state || state->size != 1)
+    {
+      *errmsg = "Could not get message state";
+      goto error;
+    }
+
+  ret = state->value[0];
+
+  pair_tlv_free(message);
+  return ret;
+
+ error:
+  pair_tlv_free(message);
+  return -1;
+}
+
+static void
+public_key_get(uint8_t server_public_key[crypto_sign_PUBLICKEYBYTES], const char *device_id)
+{
+  uint8_t private_key[crypto_sign_SECRETKEYBYTES];
+  server_keypair(server_public_key, private_key, device_id);
+}
+
+const struct pair_definition pair_client_homekit_normal =
+{
+  .pair_setup_new = client_setup_new,
+  .pair_setup_free = client_setup_free,
+  .pair_setup_result = client_setup_result,
+
+  .pair_setup_request1 = client_setup_request1,
+  .pair_setup_request2 = client_setup_request2,
+  .pair_setup_request3 = client_setup_request3,
+
+  .pair_setup_response1 = client_setup_response1,
+  .pair_setup_response2 = client_setup_response2,
+  .pair_setup_response3 = client_setup_response3,
+
+  .pair_verify_new = client_verify_new,
+
+  .pair_verify_request1 = client_verify_request1,
+  .pair_verify_request2 = client_verify_request2,
+
+  .pair_verify_response1 = client_verify_response1,
+  .pair_verify_response2 = client_verify_response2,
+
+  .pair_cipher_new = cipher_new,
+  .pair_cipher_free = cipher_free,
+
+  .pair_encrypt = encrypt,
+  .pair_decrypt = decrypt,
+
+  .pair_state_get = state_get,
+};
+
+const struct pair_definition pair_client_homekit_transient =
+{
+  .pair_setup_new = client_setup_new,
+  .pair_setup_free = client_setup_free,
+
+  .pair_setup_request1 = client_setup_request1,
+  .pair_setup_request2 = client_setup_request2,
+  .pair_setup_request3 = client_setup_request3,
+
+  .pair_setup_response1 = client_setup_response1,
+  .pair_setup_response2 = client_setup_response2,
+  .pair_setup_response3 = client_setup_response3,
+
+  .pair_verify_new = client_verify_new,
+
+  .pair_verify_request1 = client_verify_request1,
+  .pair_verify_request2 = client_verify_request2,
+
+  .pair_verify_response1 = client_verify_response1,
+  .pair_verify_response2 = client_verify_response2,
+
+  .pair_cipher_new = cipher_new,
+  .pair_cipher_free = cipher_free,
+
+  .pair_encrypt = encrypt,
+  .pair_decrypt = decrypt,
+
+  .pair_state_get = state_get,
+};
+
+const struct pair_definition pair_server_homekit =
+{
+  .pair_setup_new = server_setup_new,
+  .pair_setup_free = server_setup_free,
+
+  .pair_setup_request1 = server_setup_response1,
+  .pair_setup_request2 = server_setup_response2,
+  .pair_setup_request3 = server_setup_response3,
+
+  .pair_setup_response1 = server_setup_request1,
+  .pair_setup_response2 = server_setup_request2,
+  .pair_setup_response3 = server_setup_request3,
+
+  .pair_verify_new = server_verify_new,
+
+  .pair_verify_request1 = server_verify_response1,
+  .pair_verify_request2 = server_verify_response2,
+
+  .pair_verify_response1 = server_verify_request1,
+  .pair_verify_response2 = server_verify_request2,
+
+  .pair_add = server_add_remove,
+  .pair_remove = server_add_remove,
+  .pair_list = server_list,
+
+  .pair_cipher_new = cipher_new,
+  .pair_cipher_free = cipher_free,
+
+  .pair_encrypt = encrypt,
+  .pair_decrypt = decrypt,
+
+  .pair_state_get = state_get,
+  .pair_public_key_get = public_key_get,
+};
diff --git a/pair_ap/server-example.c b/pair_ap/server-example.c
new file mode 100644 (file)
index 0000000..73fe4bd
--- /dev/null
@@ -0,0 +1,685 @@
+/*
+ *
+ * The MIT License (MIT)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <assert.h>
+
+#include <event2/event.h>
+#include <event2/buffer.h>
+#include <event2/bufferevent.h>
+#include <event2/listener.h>
+
+#include "pair.h"
+
+#define DEVICE_ID "FFEEDDCCBBAA9988"
+#define LISTEN_PORT 7000
+#define CONTENT_TYPE_OCTET "application/octet-stream"
+#define RTSP_VERSION "RTSP/1.0"
+#define OPTIONS "OPTIONS *"
+
+struct connection_ctx
+{
+  struct evbuffer *pending;
+  struct pair_setup_context *setup_ctx;
+  struct pair_verify_context *verify_ctx;
+  struct pair_cipher_context *cipher_ctx;
+
+  int pair_completed;
+};
+
+struct rtsp_msg
+{
+  int content_length;
+  char *content_type;
+  char *first_line;
+  int cseq;
+
+  const uint8_t *body;
+  size_t bodylen;
+
+  const uint8_t *data;
+  size_t datalen;
+};
+
+struct pairings
+{
+  char device_id[PAIR_AP_DEVICE_ID_LEN_MAX];
+  uint8_t public_key[32];
+
+  struct pairings *next;
+} *pairings;
+
+
+static void
+connection_free(struct connection_ctx *conn_ctx)
+{
+  if (!conn_ctx)
+    return;
+
+  evbuffer_free(conn_ctx->pending);
+  pair_setup_free(conn_ctx->setup_ctx);
+  pair_cipher_free(conn_ctx->cipher_ctx);
+
+  free(conn_ctx);
+}
+
+static void
+response_headers_add(struct evbuffer *response, int cseq, size_t content_length, const char *content_type)
+{
+  evbuffer_add_printf(response, "%s 200 OK\r\n", RTSP_VERSION);
+  evbuffer_add_printf(response, "Server: MyServer/1.0\r\n");
+  if (content_length)
+    evbuffer_add_printf(response, "Content-Length: %zu\r\n", content_length);
+  if (content_type)
+    evbuffer_add_printf(response, "Content-Type: %s\r\n", content_type);
+  evbuffer_add_printf(response, "CSeq: %d\r\n", cseq);
+  evbuffer_add_printf(response, "\r\n");
+}
+
+static void
+response_create_from_raw(struct evbuffer *response, uint8_t *body, size_t body_len, int cseq, const char *content_type)
+{
+  response_headers_add(response, cseq, body_len, content_type);
+
+  if (body)
+    evbuffer_add(response, body, body_len);
+}
+
+static int
+encryption_enable(struct connection_ctx *conn_ctx, const uint8_t *shared_secret, size_t shared_secret_len)
+{
+  conn_ctx->cipher_ctx = pair_cipher_new(PAIR_SERVER_HOMEKIT, 2, shared_secret, shared_secret_len);
+  if (!conn_ctx->cipher_ctx)
+    {
+      printf("Error setting up ciphering\n");
+      return -1;
+    }
+
+  return 0;
+}
+
+static int
+buffer_encrypt(struct evbuffer *output, uint8_t *in, size_t in_len, struct connection_ctx *conn_ctx)
+{
+  uint8_t *out;
+  size_t out_len;
+  int ret;
+
+  ret = pair_encrypt(&out, &out_len, in, in_len, conn_ctx->cipher_ctx);
+  if (ret < 0)
+    {
+      printf("Error encrypting: %s\n", pair_cipher_errmsg(conn_ctx->cipher_ctx));
+      return -1;
+    }
+
+  evbuffer_add(output, out, out_len);
+  free(out);
+  return 0;
+}
+
+static int
+buffer_decrypt(struct evbuffer *output, struct evbuffer *input, struct connection_ctx *conn_ctx)
+{
+  uint8_t *in;
+  size_t in_len;
+  ssize_t bytes_decrypted;
+  uint8_t *plain;
+  size_t plain_len;
+
+  in = evbuffer_pullup(input, -1);
+  in_len = evbuffer_get_length(input);
+
+  // Note that bytes_decrypted is not necessarily equal to plain_len
+  bytes_decrypted = pair_decrypt(&plain, &plain_len, in, in_len, conn_ctx->cipher_ctx);
+  if (bytes_decrypted < 0)
+    {
+      printf("Error decrypting: %s\n", pair_cipher_errmsg(conn_ctx->cipher_ctx));
+      return -1;
+    }
+
+  evbuffer_add(output, plain, plain_len);
+  evbuffer_drain(input, bytes_decrypted);
+  free(plain);
+  return 0;
+}
+
+
+/* ---------------------------- Pairing callbacks --------------------------- */
+/*   Note that none of these callbacks are required if you don't care about   */
+/*  securely verifying the client + don't require support for the pair-add,   */
+/*                   pair-remove and pair-list methods.                       */
+
+static struct pairings *
+pairing_find(const char *device_id)
+{
+  struct pairings *pairing;
+
+  for (pairing = pairings; pairing; pairing = pairing->next)
+    {
+      if (strcmp(device_id, pairing->device_id) == 0)
+        break;
+    }
+
+  return pairing;
+}
+
+static int
+pairing_add_cb(uint8_t public_key[32], const char *device_id, void *cb_arg)
+{
+  struct pairings *pairing;
+
+  printf("Adding paired device %s\n", device_id);
+
+  pairing = pairing_find(device_id);
+  if (pairing)
+    {
+      memcpy(pairing->public_key, public_key, sizeof(pairing->public_key));
+      return 0;
+    }
+
+  pairing = calloc(1, sizeof(struct pairings));
+  snprintf(pairing->device_id, sizeof(pairing->device_id), "%s", device_id);
+  memcpy(pairing->public_key, public_key, sizeof(pairing->public_key));
+
+  pairing->next = pairings;
+  pairings = pairing;
+
+  return 0;
+}
+
+static int
+pairing_remove_cb(uint8_t public_key[32], const char *device_id, void *cb_arg)
+{
+  struct pairings *pairing;
+  struct pairings *iter;
+
+  printf("Removing paired device %s\n", device_id);
+
+  pairing = pairing_find(device_id);
+  if (!pairing)
+    {
+      printf("Remove callback for unknown device\n");
+      return -1;
+    }
+
+  if (pairing == pairings)
+    pairings = pairing->next;
+  else
+    {
+      for (iter = pairings; iter && (iter->next != pairing); iter = iter->next)
+       ; /* EMPTY */
+
+      if (iter)
+       iter->next = pairing->next;
+    }
+
+  free(pairing);
+  return 0;
+}
+
+static void
+pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg, void *cb_arg)
+{
+  struct pairings *pairing;
+
+  printf("Listing paired devices\n");
+
+  for (pairing = pairings; pairing; pairing = pairing->next)
+    {
+      enum_cb(pairing->public_key, pairing->device_id, enum_cb_arg);
+    }
+}
+
+static int
+pairing_get_cb(uint8_t public_key[32], const char *device_id, void *cb_arg)
+{
+  struct pairings *pairing;
+
+  printf("Returning public key for paired device %s\n", device_id);
+
+  pairing = pairing_find(device_id);
+  if (!pairing)
+    return -1;
+
+  memcpy(public_key, pairing->public_key, sizeof(pairing->public_key));
+
+  return 0;
+}
+
+
+/* -------------------------- Pair request handlers ------------------------- */
+
+static int
+handle_pin_start(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg)
+{
+  printf("Please pair with code 3939\n");
+
+  response_create_from_raw(output, NULL, 0, msg->cseq, NULL);
+
+  return 0;
+}
+
+static int
+handle_pair_setup(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg)
+{
+  uint8_t *out;
+  size_t out_len;
+  struct pair_result *result;
+  int ret;
+
+  if (!conn_ctx->setup_ctx)
+    {
+      conn_ctx->setup_ctx = pair_setup_new(PAIR_SERVER_HOMEKIT, NULL, pairing_add_cb, NULL, DEVICE_ID);
+      if (!conn_ctx->setup_ctx)
+        {
+          printf("Error creating setup context\n");
+          return -1;
+        }
+    }
+
+  ret = pair_setup(&out, &out_len, conn_ctx->setup_ctx, msg->body, msg->bodylen);
+  if (ret < 0)
+    {
+      printf("Pair setup error: %s\n", pair_setup_errmsg(conn_ctx->setup_ctx));
+      return -1;
+    }
+
+  ret = pair_setup_result(NULL, &result, conn_ctx->setup_ctx);
+  if (ret == 0 && result->shared_secret_len > 0) // Transient pairing completed (step 2)
+    {
+      encryption_enable(conn_ctx, result->shared_secret, result->shared_secret_len);
+      conn_ctx->pair_completed = 1;
+    }
+
+  response_create_from_raw(output, out, out_len, msg->cseq, CONTENT_TYPE_OCTET);
+  free(out);
+
+  return 0;
+}
+
+static int
+handle_pair_verify(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg)
+{
+  uint8_t *out;
+  size_t out_len;
+  struct pair_result *result;
+  int ret;
+
+  if (!conn_ctx->verify_ctx)
+    {
+      conn_ctx->verify_ctx = pair_verify_new(PAIR_SERVER_HOMEKIT, NULL, pairing_get_cb, NULL, DEVICE_ID);
+      if (!conn_ctx->verify_ctx)
+        {
+          printf("Error creating verify context\n");
+          return -1;
+        }
+    }
+
+  ret = pair_verify(&out, &out_len, conn_ctx->verify_ctx, msg->body, msg->bodylen);
+  if (ret < 0)
+    {
+      printf("Pair verify error: %s\n", pair_verify_errmsg(conn_ctx->verify_ctx));
+      return -1;
+    }
+
+  ret = pair_verify_result(&result, conn_ctx->verify_ctx);
+  if (ret == 0)
+    {
+      encryption_enable(conn_ctx, result->shared_secret, result->shared_secret_len);
+      conn_ctx->pair_completed = 1;
+    }
+
+  response_create_from_raw(output, out, out_len, msg->cseq, CONTENT_TYPE_OCTET);
+  free(out);
+
+  return 0;
+}
+
+static int
+handle_pair_add(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg)
+{
+  uint8_t *out;
+  size_t out_len;
+  int ret;
+
+  ret = pair_add(PAIR_SERVER_HOMEKIT, &out, &out_len, pairing_add_cb, NULL, msg->body, msg->bodylen);
+  if (ret < 0)
+    {
+      printf("Error adding device to list\n");
+      return -1;
+    }
+
+  response_create_from_raw(output, out, out_len, msg->cseq, CONTENT_TYPE_OCTET);
+  free(out);
+
+  return 0;
+}
+
+static int
+handle_pair_remove(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg)
+{
+  uint8_t *out;
+  size_t out_len;
+  int ret;
+
+  ret = pair_remove(PAIR_SERVER_HOMEKIT, &out, &out_len, pairing_remove_cb, NULL, msg->body, msg->bodylen);
+  if (ret < 0)
+    {
+      printf("Error removing device from list\n");
+      return -1;
+    }
+
+  response_create_from_raw(output, out, out_len, msg->cseq, CONTENT_TYPE_OCTET);
+  free(out);
+
+  return 0;
+}
+
+static int
+handle_pair_list(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg)
+{
+  uint8_t *out;
+  size_t out_len;
+  int ret;
+
+  ret = pair_list(PAIR_SERVER_HOMEKIT, &out, &out_len, pairing_list_cb, NULL, msg->body, msg->bodylen);
+  if (ret < 0)
+    {
+      printf("Error creating list of paired devices\n");
+      return -1;
+    }
+
+  response_create_from_raw(output, out, out_len, msg->cseq, CONTENT_TYPE_OCTET);
+  free(out);
+
+  return 0;
+}
+
+static int
+handle_options(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg)
+{
+  struct evbuffer *response;
+  uint8_t *plain;
+  size_t plain_len;
+  int ret;
+
+  response = evbuffer_new();
+
+  response_create_from_raw(response, NULL, 0, msg->cseq, NULL);
+
+  if (!conn_ctx->cipher_ctx)
+    {
+      evbuffer_add_buffer(output, response);
+      evbuffer_free(response);
+      return 0;
+    }
+
+  plain = evbuffer_pullup(response, -1);
+  plain_len = evbuffer_get_length(response);
+
+  ret = buffer_encrypt(output, plain, plain_len, conn_ctx);
+
+  evbuffer_free(response);
+  return ret;
+}
+
+static int
+response_send(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg)
+{
+  if (!msg->first_line)
+    return -1;
+
+  if (strncmp(msg->first_line, PAIR_AP_POST_PIN_START, strlen(PAIR_AP_POST_PIN_START)) == 0)
+    return handle_pin_start(output, conn_ctx, msg);
+  else if (strncmp(msg->first_line, PAIR_AP_POST_SETUP, strlen(PAIR_AP_POST_SETUP)) == 0)
+    return handle_pair_setup(output, conn_ctx, msg);
+  else if (strncmp(msg->first_line, PAIR_AP_POST_VERIFY, strlen(PAIR_AP_POST_VERIFY)) == 0)
+    return handle_pair_verify(output, conn_ctx, msg);
+  else if (strncmp(msg->first_line, PAIR_AP_POST_ADD, strlen(PAIR_AP_POST_ADD)) == 0)
+    return handle_pair_add(output, conn_ctx, msg);
+  else if (strncmp(msg->first_line, PAIR_AP_POST_LIST, strlen(PAIR_AP_POST_LIST)) == 0)
+    return handle_pair_list(output, conn_ctx, msg);
+  else if (strncmp(msg->first_line, PAIR_AP_POST_REMOVE, strlen(PAIR_AP_POST_REMOVE)) == 0)
+    return handle_pair_remove(output, conn_ctx, msg);
+  else if (strncmp(msg->first_line, OPTIONS, strlen(OPTIONS)) == 0)
+    return handle_options(output, conn_ctx, msg);
+
+  printf("Unknown method: %s\n", msg->first_line);
+  return -1;
+}
+
+
+/* --------------------- A basic RTSP server implementation ----------------- */
+
+static void
+rtsp_clear(struct rtsp_msg *msg)
+{
+  free(msg->first_line);
+  free(msg->content_type);
+}
+
+// Very primitive RTSP message parser, hope you have a better one
+static int
+rtsp_parse(struct rtsp_msg *msg, uint8_t *in, size_t in_len)
+{
+  char *line;
+  int i;
+
+  line = (char *)in;
+  for (i = 0; i < in_len; i++)
+    {
+      if (in[i] != '\n' && in[i - 1] != '\r')
+       continue;
+
+      if (in[i - 2] == '\n' && in[i - 3] == '\r')
+       {
+         msg->bodylen = in_len - (i + 1);
+         if (msg->bodylen != msg->content_length)
+           {
+             printf("Incomplete read (have %zu, content-length %d), waiting for more data\n\n", msg->bodylen, msg->content_length);
+             rtsp_clear(msg);
+             return 1;
+           }
+         else if (msg->bodylen > 0)
+           msg->body = in + i + 1;
+
+         break;
+       }
+
+      in[i - 1] = '\0';
+
+      if (!msg->first_line)
+       msg->first_line = strdup(line);
+
+      if (strncmp(line, "CSeq: ", strlen("CSeq: ")) == 0)
+       msg->cseq = atoi(line + strlen("CSeq: "));
+
+      if (strncmp(line, "Content-Length: ", strlen("Content-Length: ")) == 0)
+       msg->content_length = atoi(line + strlen("Content-Length: "));
+
+      if (strncmp(line, "Content-Type: ", strlen("Content-Type: ")) == 0 && !msg->content_type)
+       msg->content_type = strdup(line + strlen("Content-Type: "));
+
+      in[i - 1] = '\r';
+
+      line = (char *)in + i + 1;
+    }
+
+  msg->data = in;
+  msg->datalen = in_len;
+
+  return 0;
+}
+
+static void
+in_read_cb(struct bufferevent *bev, void *arg)
+{
+  struct connection_ctx *conn_ctx = arg;
+  struct evbuffer *input;
+  struct evbuffer *output;
+  uint8_t *plain;
+  size_t plain_len;
+  struct rtsp_msg msg = { 0 };
+  int ret;
+
+  input = bufferevent_get_input(bev);
+  output = bufferevent_get_output(bev);
+
+  printf("\n--------------------------------------------------------------------------\n");
+
+  if (conn_ctx->pair_completed)
+    {
+      buffer_decrypt(conn_ctx->pending, input, conn_ctx);
+    }
+  else
+    {
+      evbuffer_add_buffer(conn_ctx->pending, input);
+    }
+
+  // Pending holds all the message we have received so far, incl. what parts we
+  // might have received in previous callbacks
+  plain = evbuffer_pullup(conn_ctx->pending, -1);
+  plain_len = evbuffer_get_length(conn_ctx->pending);
+
+  ret = rtsp_parse(&msg, plain, plain_len);
+  if (ret < 0)
+    {
+      printf("Could not parse RTSP message\n");
+      goto error;
+    }
+  else if (ret == 1)
+    return; // Message incomplete, wait for more data
+
+  ret = response_send(output, conn_ctx, &msg);
+  if (ret < 0)
+    {
+      goto error;
+    }
+
+ error:
+  rtsp_clear(&msg);
+  evbuffer_drain(conn_ctx->pending, evbuffer_get_length(conn_ctx->pending));
+  return;
+}
+
+static void
+in_event_cb(struct bufferevent *bev, short events, void *arg)
+{
+  struct connection_ctx *conn_ctx = arg;
+
+  if (events & BEV_EVENT_ERROR)
+    printf("Error from bufferevent: %s\n", evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
+
+  if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR))
+    bufferevent_free(bev);
+
+  connection_free(conn_ctx);
+}
+
+
+/*-------------------------  General server stuff ----------------------------*/
+
+static void
+in_accept_cb(struct evconnlistener *listener, evutil_socket_t sock, struct sockaddr *address, int socklen, void *ctx)
+{
+  struct event_base *base = evconnlistener_get_base(listener);
+  struct bufferevent *bev = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE);
+  struct connection_ctx *conn_ctx;
+
+  conn_ctx = calloc(1, sizeof(struct connection_ctx));
+  conn_ctx->pending = evbuffer_new();
+
+  bufferevent_setcb(bev, in_read_cb, NULL, in_event_cb, conn_ctx);
+  bufferevent_enable(bev, EV_READ | EV_WRITE);
+
+  printf("New connection accepted\n");
+}
+
+static void
+in_error_cb(struct evconnlistener *listener, void *ctx)
+{
+  int err = EVUTIL_SOCKET_ERROR();
+  printf("Error occured %d (%s) on the listener\n", err, evutil_socket_error_to_string(err));
+}
+
+static struct evconnlistener *
+listen_add(struct event_base *evbase, evconnlistener_cb req_cb, evconnlistener_errorcb err_cb, unsigned short port)
+{
+  struct evconnlistener *listener;
+  struct addrinfo hints = { 0 };
+  struct addrinfo *servinfo;
+  char strport[8];
+  int ret;
+
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_flags = AI_PASSIVE;
+
+  snprintf(strport, sizeof(strport), "%hu", port);
+  ret = getaddrinfo(NULL, strport, &hints, &servinfo);
+  if (ret < 0)
+    {
+      printf("getaddrinf() failed: %s\n", gai_strerror(ret));
+      return NULL;
+    }
+
+  listener = evconnlistener_new_bind(evbase, req_cb, NULL, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1, servinfo->ai_addr, servinfo->ai_addrlen);
+  freeaddrinfo(servinfo);
+  if (!listener)
+    {
+      printf("Could not create listener for port %hu\n", port);
+      return NULL;
+    }
+
+  evconnlistener_set_error_cb(listener, err_cb);
+
+  return listener;
+}
+
+int
+main(int argc, char * argv[])
+{
+  struct event_base *evbase;
+  struct evconnlistener *listener;
+
+  evbase = event_base_new();
+
+  listener = listen_add(evbase, in_accept_cb, in_error_cb, LISTEN_PORT);
+  if (!listener)
+    return -1;
+
+  printf("Listening for pairing requests on port %d\n", LISTEN_PORT);
+
+  event_base_dispatch(evbase);
+
+  evconnlistener_free(listener);
+  event_base_free(evbase);
+
+  return 0;
+}