Include pair_ap as a simple directory.

This commit is contained in:
Mike Brady
2021-07-07 14:11:32 +00:00
parent fdb919a788
commit 94e47fdf9e
15 changed files with 9402 additions and 0 deletions
+72
View File
@@ -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)
+578
View File
@@ -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;
}
+381
View File
@@ -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
+218
View File
@@ -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
+77
View File
@@ -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__ */
+771
View File
@@ -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);
}
+266
View File
@@ -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__ */
+10
View File
@@ -0,0 +1,10 @@
*.o
*.lo
*.a
*.la
.dirstamp
.deps/
.libs/
client-example
server-example
+176
View File
@@ -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_ */
+51
View File
@@ -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
@@ -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 */
File diff suppressed because it is too large Load Diff
+1063
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+685
View File
@@ -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;
}