Files
2020-05-19 09:23:29 +08:00

3568 lines
123 KiB
C

#include "port_x.h"
#if !defined(ARDUINO8266_SERVER) && !defined(ARDUINO8266_SERVER_CPP)
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include <lwip/sockets.h>
#include <unistd.h>
#if defined(ESP_IDF)
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#elif defined(ESP_OPEN_RTOS)
#include <FreeRTOS.h>
#include <task.h>
#include <queue.h>
#elif defined(ARDUINO)
#include <Arduino.h>
#include "os_settings.h"
#else
#error "Unknown target platform"
#endif
#include "http_parser.h"
#include "cJSON.h"
#include <wolfssl/wolfcrypt/hash.h>
#include <wolfssl/wolfcrypt/coding.h>
#include "base64.h"
#include "crypto.h"
#include "pairing.h"
#include "storage.h"
#include "query_params.h"
#include "json.h"
#include "debug.h"
#include "port.h"
#include "homekit.h"
#include "characteristics.h"
#include "tlv.h""
#define PORT 5556
#ifndef HOMEKIT_MAX_CLIENTS
#define HOMEKIT_MAX_CLIENTS 16
#endif
struct _client_context_t;
typedef struct _client_context_t client_context_t;
#define HOMEKIT_NOTIFY_EVENT(server, event) \
if ((server)->config->on_event) \
(server)->config->on_event(event);
typedef enum {
HOMEKIT_ENDPOINT_UNKNOWN = 0,
HOMEKIT_ENDPOINT_PAIR_SETUP,
HOMEKIT_ENDPOINT_PAIR_VERIFY,
HOMEKIT_ENDPOINT_IDENTIFY,
HOMEKIT_ENDPOINT_GET_ACCESSORIES,
HOMEKIT_ENDPOINT_GET_CHARACTERISTICS,
HOMEKIT_ENDPOINT_UPDATE_CHARACTERISTICS,
HOMEKIT_ENDPOINT_PAIRINGS,
HOMEKIT_ENDPOINT_RESET,
HOMEKIT_ENDPOINT_RESOURCE,
} homekit_endpoint_t;
typedef struct {
Srp *srp;
byte *public_key;
size_t public_key_size;
client_context_t *client;
} pairing_context_t;
typedef struct {
byte *secret;
size_t secret_size;
byte *session_key;
size_t session_key_size;
byte *device_public_key;
size_t device_public_key_size;
byte *accessory_public_key;
size_t accessory_public_key_size;
} pair_verify_context_t;
typedef struct {
char *accessory_id;
ed25519_key *accessory_key;
homekit_server_config_t *config;
bool paired;
pairing_context_t *pairing_context;
int listen_fd;
fd_set fds;
int max_fd;
int nfds;
client_context_t *clients;
} homekit_server_t;
struct _client_context_t {
homekit_server_t *server;
int socket;
homekit_endpoint_t endpoint;
query_param_t *endpoint_params;
byte *data;
size_t data_size;
size_t data_available;
char *body;
size_t body_length;
http_parser *parser;
int pairing_id;
byte permissions;
bool disconnect;
homekit_characteristic_t *current_characteristic;
homekit_value_t *current_value;
bool encrypted;
byte *read_key;
byte *write_key;
int count_reads;
int count_writes;
QueueHandle_t event_queue;
pair_verify_context_t *verify_context;
struct _client_context_t *next;
};
//#if !defined(ARDUINO) && !defined(ESP8266)
typedef struct {
homekit_characteristic_t *characteristic;
homekit_value_t value;
} characteristic_event_t;
//#endif
void client_context_free(client_context_t *c);
void pairing_context_free(pairing_context_t *context);
homekit_server_t *server_new() {
homekit_server_t *server = malloc(sizeof(homekit_server_t));
FD_ZERO(&server->fds);
server->max_fd = 0;
server->nfds = 0;
server->accessory_id = NULL;
server->accessory_key = NULL;
server->config = NULL;
server->paired = false;
server->pairing_context = NULL;
server->clients = NULL;
return server;
}
void server_free(homekit_server_t *server) {
if (server->accessory_id)
free(server->accessory_id);
if (server->accessory_key)
crypto_ed25519_free(server->accessory_key);
if (server->pairing_context)
pairing_context_free(server->pairing_context);
if (server->clients) {
client_context_t *client = server->clients;
while (client) {
client_context_t *next = client->next;
client_context_free(client);
client = next;
}
}
free(server);
}
#ifdef HOMEKIT_DEBUG
#define TLV_DEBUG(values) tlv_debug(values)
#else
#define TLV_DEBUG(values)
#endif
#define CLIENT_DEBUG(client, message, ...) DEBUG("[Client %d] " message, client->socket, ##__VA_ARGS__)
#define CLIENT_INFO(client, message, ...) INFO("[Client %d] " message, client->socket, ##__VA_ARGS__)
#define CLIENT_ERROR(client, message, ...) ERROR("[Client %d] " message, client->socket, ##__VA_ARGS__)
void tlv_debug(const tlv_values_t *values) {
DEBUG("Got following TLV values:");
for (tlv_t *t=values->head; t; t=t->next) {
char *escaped_payload = binary_to_string(t->value, t->size);
DEBUG("Type %d value (%d bytes): %s", t->type, t->size, escaped_payload);
free(escaped_payload);
}
}
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_Separator = 0xff,
} TLVType;
typedef enum {
TLVMethod_PairSetup = 1,
TLVMethod_PairVerify = 2,
TLVMethod_AddPairing = 3,
TLVMethod_RemovePairing = 4,
TLVMethod_ListPairings = 5,
} TLVMethod;
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 enum {
// This specifies a success for the request
HAPStatus_Success = 0,
// Request denied due to insufficient privileges
HAPStatus_InsufficientPrivileges = -70401,
// Unable to communicate with requested services,
// e.g. the power to the accessory was turned off
HAPStatus_NoAccessoryConnection = -70402,
// Resource is busy, try again
HAPStatus_ResourceBusy = -70403,
// Connot write to read only characteristic
HAPStatus_ReadOnly = -70404,
// Cannot read from a write only characteristic
HAPStatus_WriteOnly = -70405,
// Notification is not supported for characteristic
HAPStatus_NotificationsUnsupported = -70406,
// Out of resources to process request
HAPStatus_OutOfResources = -70407,
// Operation timed out
HAPStatus_Timeout = -70408,
// Resource does not exist
HAPStatus_NoResource = -70409,
// Accessory received an invalid value in a write request
HAPStatus_InvalidValue = -70410,
// Insufficient Authorization
HAPStatus_InsufficientAuthorization = -70411,
} HAPStatus;
pair_verify_context_t *pair_verify_context_new() {
pair_verify_context_t *context = malloc(sizeof(pair_verify_context_t));
context->secret = NULL;
context->secret_size = 0;
context->session_key = NULL;
context->session_key_size = 0;
context->device_public_key = NULL;
context->device_public_key_size = 0;
context->accessory_public_key = NULL;
context->accessory_public_key_size = 0;
return context;
}
void pair_verify_context_free(pair_verify_context_t *context) {
if (context->secret)
free(context->secret);
if (context->session_key)
free(context->session_key);
if (context->device_public_key)
free(context->device_public_key);
if (context->accessory_public_key)
free(context->accessory_public_key);
free(context);
}
client_context_t *client_context_new() {
client_context_t *c = malloc(sizeof(client_context_t));
c->server = NULL;
c->endpoint_params = NULL;
c->data_size = 1024 + 18;
c->data_available = 0;
c->data = malloc(c->data_size);
c->body = NULL;
c->body_length = 0;
c->parser = malloc(sizeof(*c->parser));
http_parser_init(c->parser, HTTP_REQUEST);
c->parser->data = c;
c->pairing_id = -1;
c->encrypted = false;
c->read_key = NULL;
c->write_key = NULL;
c->count_reads = 0;
c->count_writes = 0;
c->disconnect = false;
c->event_queue = xQueueCreate(20, sizeof(characteristic_event_t*));
c->verify_context = NULL;
c->next = NULL;
return c;
}
void client_context_free(client_context_t *c) {
if (c->read_key)
free(c->read_key);
if (c->write_key)
free(c->write_key);
if (c->verify_context)
pair_verify_context_free(c->verify_context);
if (c->event_queue)
vQueueDelete(c->event_queue);
if (c->endpoint_params)
query_params_free(c->endpoint_params);
if (c->parser)
free(c->parser);
if (c->data)
free(c->data);
if (c->body)
free(c->body);
free(c);
}
pairing_context_t *pairing_context_new() {
//INFO("allocating pairing_context_t");
DEBUG_HEAP();
//INFO("pc 1");
int i = sizeof(pairing_context_t);
//INFO("pc 2");
//INFO("size 0x%d", i);
pairing_context_t *context = malloc(sizeof(pairing_context_t));
//INFO("allocated 0x%d", (int)context);
//INFO("starting crypto");
context->srp = crypto_srp_new();
INFO("started crypto");
context->client = NULL;
context->public_key = NULL;
context->public_key_size = 0;
return context;
}
void pairing_context_free(pairing_context_t *context) {
if (context->srp) {
crypto_srp_free(context->srp);
}
if (context->public_key) {
free(context->public_key);
}
free(context);
}
void client_notify_characteristic(homekit_characteristic_t *ch, homekit_value_t value, void *client);
typedef enum {
characteristic_format_type = (1 << 1),
characteristic_format_meta = (1 << 2),
characteristic_format_perms = (1 << 3),
characteristic_format_events = (1 << 4),
} characteristic_format_t;
void write_characteristic_json(json_stream *json, client_context_t *client, const homekit_characteristic_t *ch, characteristic_format_t format, const homekit_value_t *value) {
json_string(json, "aid"); json_integer(json, ch->service->accessory->id);
json_string(json, "iid"); json_integer(json, ch->id);
if (format & characteristic_format_type) {
json_string(json, "type"); json_string(json, ch->type);
}
if (format & characteristic_format_perms) {
json_string(json, "perms"); json_array_start(json);
if (ch->permissions & homekit_permissions_paired_read)
json_string(json, "pr");
if (ch->permissions & homekit_permissions_paired_write)
json_string(json, "pw");
if (ch->permissions & homekit_permissions_notify)
json_string(json, "ev");
if (ch->permissions & homekit_permissions_additional_authorization)
json_string(json, "aa");
if (ch->permissions & homekit_permissions_timed_write)
json_string(json, "tw");
if (ch->permissions & homekit_permissions_hidden)
json_string(json, "hd");
json_array_end(json);
}
if ((format & characteristic_format_events) && (ch->permissions & homekit_permissions_notify)) {
bool events = homekit_characteristic_has_notify_callback(ch, client_notify_characteristic, client);
json_string(json, "ev"); json_boolean(json, events);
}
if (format & characteristic_format_meta) {
if (ch->description) {
json_string(json, "description"); json_string(json, ch->description);
}
const char *format_str = NULL;
switch(ch->format) {
case homekit_format_bool: format_str = "bool"; break;
case homekit_format_uint8: format_str = "uint8"; break;
case homekit_format_uint16: format_str = "uint16"; break;
case homekit_format_uint32: format_str = "uint32"; break;
case homekit_format_uint64: format_str = "uint64"; break;
case homekit_format_int: format_str = "int"; break;
case homekit_format_float: format_str = "float"; break;
case homekit_format_string: format_str = "string"; break;
case homekit_format_tlv: format_str = "tlv8"; break;
case homekit_format_data: format_str = "data"; break;
}
if (format_str) {
json_string(json, "format"); json_string(json, format_str);
}
const char *unit_str = NULL;
switch(ch->unit) {
case homekit_unit_none: break;
case homekit_unit_celsius: unit_str = "celsius"; break;
case homekit_unit_percentage: unit_str = "percentage"; break;
case homekit_unit_arcdegrees: unit_str = "arcdegrees"; break;
case homekit_unit_lux: unit_str = "lux"; break;
case homekit_unit_seconds: unit_str = "seconds"; break;
}
if (unit_str) {
json_string(json, "unit"); json_string(json, unit_str);
}
if (ch->min_value) {
json_string(json, "minValue"); json_float(json, *ch->min_value);
}
if (ch->max_value) {
json_string(json, "maxValue"); json_float(json, *ch->max_value);
}
if (ch->min_step) {
json_string(json, "minStep"); json_float(json, *ch->min_step);
}
if (ch->max_len) {
json_string(json, "maxLen"); json_integer(json, *ch->max_len);
}
if (ch->max_data_len) {
json_string(json, "maxDataLen"); json_integer(json, *ch->max_data_len);
}
if (ch->valid_values.count) {
json_string(json, "valid-values"); json_array_start(json);
for (int i=0; i<ch->valid_values.count; i++) {
json_integer(json, ch->valid_values.values[i]);
}
json_array_end(json);
}
if (ch->valid_values_ranges.count) {
json_string(json, "valid-values-range"); json_array_start(json);
for (int i=0; i<ch->valid_values_ranges.count; i++) {
json_array_start(json);
json_integer(json, ch->valid_values_ranges.ranges[i].start);
json_integer(json, ch->valid_values_ranges.ranges[i].end);
json_array_end(json);
}
json_array_end(json);
}
}
if (ch->permissions & homekit_permissions_paired_read) {
homekit_value_t v = value ? *value : ch->getter_ex ? ch->getter_ex(ch) : ch->value;
if (v.is_null) {
// json_string(json, "value"); json_null(json);
} else if (v.format != ch->format) {
ERROR("Characteristic value format is different from characteristic format");
} else {
switch(v.format) {
case homekit_format_bool: {
json_string(json, "value"); json_boolean(json, v.bool_value);
break;
}
case homekit_format_uint8:
case homekit_format_uint16:
case homekit_format_uint32:
case homekit_format_uint64:
case homekit_format_int: {
json_string(json, "value"); json_integer(json, v.int_value);
break;
}
case homekit_format_float: {
json_string(json, "value"); json_float(json, v.float_value);
break;
}
case homekit_format_string: {
json_string(json, "value"); json_string(json, v.string_value);
break;
}
case homekit_format_tlv: {
json_string(json, "value");
if (!v.tlv_values) {
json_string(json, "");
} else {
size_t tlv_size = 0;
tlv_format(v.tlv_values, NULL, &tlv_size);
if (tlv_size == 0) {
json_string(json, "");
} else {
byte *tlv_data = malloc(tlv_size);
tlv_format(v.tlv_values, tlv_data, &tlv_size);
size_t encoded_tlv_size = base64_encoded_size(tlv_data, tlv_size);
byte *encoded_tlv_data = malloc(encoded_tlv_size + 1);
base64_encode(tlv_data, tlv_size, encoded_tlv_data);
encoded_tlv_data[encoded_tlv_size] = 0;
json_string(json, (char*) encoded_tlv_data);
free(encoded_tlv_data);
free(tlv_data);
}
}
break;
}
case homekit_format_data:
// TODO:
break;
}
}
if (!value && ch->getter_ex) {
// called getter to get value, need to free it
homekit_value_destruct(&v);
}
}
}
int client_send_encrypted(
client_context_t *context,
byte *payload, size_t size
) {
if (!context || !context->encrypted || !context->read_key)
return -1;
byte nonce[12];
memset(nonce, 0, sizeof(nonce));
byte encrypted[1024 + 18];
int payload_offset = 0;
while (payload_offset < size) {
size_t chunk_size = size - payload_offset;
if (chunk_size > 1024)
chunk_size = 1024;
byte aead[2] = {chunk_size % 256, chunk_size / 256};
memcpy(encrypted, aead, 2);
byte i = 4;
int x = context->count_reads++;
while (x) {
nonce[i++] = x % 256;
x /= 256;
}
size_t available = sizeof(encrypted) - 2;
int r = crypto_chacha20poly1305_encrypt(
context->read_key, nonce, aead, 2,
payload+payload_offset, chunk_size,
encrypted+2, &available
);
if (r) {
ERROR("Failed to chacha encrypt payload (code %d)", r);
return -1;
}
payload_offset += chunk_size;
write(context->socket, encrypted, available + 2);
}
return 0;
}
int client_decrypt(
client_context_t *context,
byte *payload, size_t payload_size,
byte *decrypted, size_t *decrypted_size
) {
if (!context || !context->encrypted || !context->write_key)
return -1;
const size_t block_size = 1024 + 16 + 2;
size_t required_decrypted_size =
payload_size / block_size * 1024 + payload_size % block_size - 16 - 2;
if (*decrypted_size < required_decrypted_size) {
*decrypted_size = required_decrypted_size;
return -2;
}
*decrypted_size = required_decrypted_size;
byte nonce[12];
memset(nonce, 0, sizeof(nonce));
int payload_offset = 0;
int decrypted_offset = 0;
while (payload_offset < payload_size) {
size_t chunk_size = payload[payload_offset] + payload[payload_offset+1]*256;
if (chunk_size+18 > payload_size-payload_offset) {
// Unfinished chunk
break;
}
byte i = 4;
int x = context->count_writes++;
while (x) {
nonce[i++] = x % 256;
x /= 256;
}
size_t decrypted_len = *decrypted_size - decrypted_offset;
int r = crypto_chacha20poly1305_decrypt(
context->write_key, nonce, payload+payload_offset, 2,
payload+payload_offset+2, chunk_size + 16,
decrypted, &decrypted_len
);
if (r) {
ERROR("Failed to chacha decrypt payload (code %d)", r);
return -1;
}
decrypted_offset += decrypted_len;
payload_offset += chunk_size + 0x12; // 0x10 is for some auth bytes
}
return payload_offset;
}
void homekit_setup_mdns(homekit_server_t *server);
void client_notify_characteristic(homekit_characteristic_t *ch, homekit_value_t value, void *context) {
client_context_t *client = context;
if (client->current_characteristic == ch && client->current_value && homekit_value_equal(client->current_value, &value))
// This value is set by this client, no need to send notification
return;
DEBUG("Got characteristic %d.%d change event", ch->service->accessory->id, ch->id);
if (!client->event_queue) {
ERROR("Client has no event queue. Skipping notification");
return;
}
characteristic_event_t *event = malloc(sizeof(characteristic_event_t));
event->characteristic = ch;
homekit_value_copy(&event->value, &value);
DEBUG("Sending event to client %d", client->socket);
xQueueSendToBack(client->event_queue, &event, 10);
}
void client_send(client_context_t *context, byte *data, size_t data_size) {
#ifdef HOMEKIT_DEBUG
if (data_size < 4096) {
char *payload = binary_to_string(data, data_size);
CLIENT_DEBUG(context, "Sending payload: %s", payload);
free(payload);
}
#endif
if (context->encrypted) {
int r = client_send_encrypted(context, data, data_size);
if (r) {
CLIENT_ERROR(context, "Failed to encrypt response (code %d)", r);
return;
}
} else {
write(context->socket, data, data_size);
}
}
void client_send_chunk(byte *data, size_t size, void *arg) {
client_context_t *context = arg;
size_t payload_size = size + 8;
byte *payload = malloc(payload_size);
int offset = snprintf((char *)payload, payload_size, "%x\r\n", size);
memcpy(payload + offset, data, size);
payload[offset + size] = '\r';
payload[offset + size + 1] = '\n';
client_send(context, payload, offset + size + 2);
free(payload);
}
void send_204_response(client_context_t *context) {
static char response[] = "HTTP/1.1 204 No Content\r\n\r\n";
client_send(context, (byte *)response, sizeof(response)-1);
}
void send_404_response(client_context_t *context) {
static char response[] = "HTTP/1.1 404 Not Found\r\n\r\n";
client_send(context, (byte *)response, sizeof(response)-1);
}
typedef struct _client_event {
const homekit_characteristic_t *characteristic;
homekit_value_t value;
struct _client_event *next;
} client_event_t;
void send_client_events(client_context_t *context, client_event_t *events) {
CLIENT_DEBUG(context, "Sending EVENT");
DEBUG_HEAP();
static byte http_headers[] =
"EVENT/1.0 200 OK\r\n"
"Content-Type: application/hap+json\r\n"
"Transfer-Encoding: chunked\r\n\r\n";
client_send(context, http_headers, sizeof(http_headers)-1);
// ~35 bytes per event JSON
// 256 should be enough for ~7 characteristic updates
json_stream *json = json_new(256, client_send_chunk, context);
json_object_start(json);
json_string(json, "characteristics"); json_array_start(json);
client_event_t *e = events;
while (e) {
json_object_start(json);
write_characteristic_json(json, context, e->characteristic, 0, &e->value);
json_object_end(json);
e = e->next;
}
json_array_end(json);
json_object_end(json);
json_flush(json);
json_free(json);
client_send_chunk(NULL, 0, context);
}
void send_tlv_response(client_context_t *context, tlv_values_t *values);
void send_tlv_error_response(client_context_t *context, int state, TLVError error) {
tlv_values_t *response = tlv_new();
tlv_add_integer_value(response, TLVType_State, 1, state);
tlv_add_integer_value(response, TLVType_Error, 1, error);
send_tlv_response(context, response);
}
void send_tlv_response(client_context_t *context, tlv_values_t *values) {
CLIENT_DEBUG(context, "Sending TLV response");
TLV_DEBUG(values);
size_t payload_size = 0;
tlv_format(values, NULL, &payload_size);
byte *payload = malloc(payload_size);
int r = tlv_format(values, payload, &payload_size);
if (r) {
CLIENT_ERROR(context, "Failed to format TLV payload (code %d)", r);
free(payload);
return;
}
tlv_free(values);
static char *http_headers =
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/pairing+tlv8\r\n"
"Content-Length: %d\r\n"
"Connection: keep-alive\r\n\r\n";
int response_size = strlen(http_headers) + payload_size + 32;
char *response = malloc(response_size);
int response_len = snprintf(response, response_size, http_headers, payload_size);
if (response_size - response_len < payload_size + 1) {
CLIENT_ERROR(context, "Incorrect response buffer size %d: headers took %d, payload size %d", response_size, response_len, payload_size);
free(response);
free(payload);
return;
}
memcpy(response+response_len, payload, payload_size);
response_len += payload_size;
free(payload);
client_send(context, (byte *)response, response_len);
free(response);
}
static byte json_200_response_headers[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/hap+json\r\n"
"Transfer-Encoding: chunked\r\n"
"Connection: keep-alive\r\n\r\n";
static byte json_207_response_headers[] =
"HTTP/1.1 207 Multi-Status\r\n"
"Content-Type: application/hap+json\r\n"
"Transfer-Encoding: chunked\r\n"
"Connection: keep-alive\r\n\r\n";
void send_json_response(client_context_t *context, int status_code, byte *payload, size_t payload_size) {
CLIENT_DEBUG(context, "Sending JSON response");
DEBUG_HEAP();
static char *http_headers =
"HTTP/1.1 %d %s\r\n"
"Content-Type: application/hap+json\r\n"
"Content-Length: %d\r\n"
"Connection: keep-alive\r\n\r\n";
CLIENT_DEBUG(context, "Payload: %s", payload);
const char *status_text = "OK";
switch (status_code) {
case 204: status_text = "No Content"; break;
case 207: status_text = "Multi-Status"; break;
case 400: status_text = "Bad Request"; break;
case 404: status_text = "Not Found"; break;
case 422: status_text = "Unprocessable Entity"; break;
case 500: status_text = "Internal Server Error"; break;
case 503: status_text = "Service Unavailable"; break;
}
int response_size = strlen(http_headers) + payload_size + strlen(status_text) + 32;
char *response = malloc(response_size);
if (!response) {
CLIENT_ERROR(context, "Failed to allocate response buffer of size %d", response_size);
return;
}
int response_len = snprintf(response, response_size, http_headers, status_code, status_text, payload_size);
if (response_size - response_len < payload_size + 1) {
CLIENT_ERROR(context, "Incorrect response buffer size %d: headers took %d, payload size %d", response_size, response_len, payload_size);
free(response);
return;
}
memcpy(response+response_len, payload, payload_size);
response_len += payload_size;
response[response_len] = 0; // required for debug output
CLIENT_DEBUG(context, "Sending HTTP response: %s", response);
client_send(context, (byte *)response, response_len);
free(response);
}
void send_json_error_response(client_context_t *context, int status_code, HAPStatus status) {
byte buffer[32];
int size = snprintf((char *)buffer, sizeof(buffer), "{\"status\": %d}", status);
send_json_response(context, status_code, buffer, size);
}
static client_context_t *current_client_context = NULL;
homekit_client_id_t homekit_get_client_id() {
return (homekit_client_id_t)current_client_context;
}
bool homekit_client_is_admin() {
if (!current_client_context)
return false;
return current_client_context->permissions & pairing_permissions_admin;
}
int homekit_client_send(unsigned char *data, size_t size) {
if (!current_client_context)
return -1;
client_send(current_client_context, data, size);
return 0;
}
void homekit_server_on_identify(client_context_t *context) {
CLIENT_INFO(context, "Identify");
DEBUG_HEAP();
if (context->server->paired) {
// Already paired
send_json_error_response(context, 400, HAPStatus_InsufficientPrivileges);
return;
}
send_204_response(context);
homekit_accessory_t *accessory =
homekit_accessory_by_id(context->server->config->accessories, 1);
if (!accessory) {
return;
}
homekit_service_t *accessory_info =
homekit_service_by_type(accessory, HOMEKIT_SERVICE_ACCESSORY_INFORMATION);
if (!accessory_info) {
return;
}
homekit_characteristic_t *ch_identify =
homekit_service_characteristic_by_type(accessory_info, HOMEKIT_CHARACTERISTIC_IDENTIFY);
if (!ch_identify) {
return;
}
if (ch_identify->setter_ex) {
ch_identify->setter_ex(ch_identify, HOMEKIT_BOOL(true));
}
}
void homekit_server_on_pair_setup(client_context_t *context, const byte *data, size_t size) {
CLIENT_INFO(context, "Pair Setup ");
DEBUG("Pair Setup");
DEBUG_HEAP();
#ifdef HOMEKIT_OVERCLOCK_PAIR_SETUP
homekit_overclock_start();
#endif
tlv_values_t *message = tlv_new();
tlv_parse(data, size, message);
TLV_DEBUG(message);
DEBUG_HEAP();
switch(tlv_get_integer_value(message, TLVType_State, -1)) {
case 1: {
CLIENT_INFO(context, "Pair Setup Step 1/3");
if (context->server->paired) {
CLIENT_INFO(context, "Refusing to pair: already paired");
send_tlv_error_response(context, 2, TLVError_Unavailable);
break;
}
if (context->server->pairing_context) {
if (context->server->pairing_context->client != context) {
CLIENT_INFO(context, "Refusing to pair: another pairing in progress");
send_tlv_error_response(context, 2, TLVError_Busy);
break;
}
} else {
CLIENT_INFO(context, "new pairing content");
context->server->pairing_context = pairing_context_new();
context->server->pairing_context->client = context;
}
CLIENT_DEBUG(context, "Initializing crypto");
DEBUG_HEAP();
char password[11];
if (context->server->config->password) {
strncpy(password, context->server->config->password, sizeof(password));
CLIENT_DEBUG(context, "Using user-specified password: %s", password);
} else {
for (int i=0; i<10; i++) {
password[i] = homekit_random() % 10 + '0';
}
password[3] = password[6] = '-';
password[10] = 0;
CLIENT_DEBUG(context, "Using random password: %s", password);
}
if (context->server->config->password_callback) {
context->server->config->password_callback(password);
}
crypto_srp_init(
context->server->pairing_context->srp,
"Pair-Setup", password
);
if (context->server->pairing_context->public_key) {
free(context->server->pairing_context->public_key);
context->server->pairing_context->public_key = NULL;
}
context->server->pairing_context->public_key_size = 0;
crypto_srp_get_public_key(context->server->pairing_context->srp, NULL, &context->server->pairing_context->public_key_size);
context->server->pairing_context->public_key = malloc(context->server->pairing_context->public_key_size);
int r = crypto_srp_get_public_key(context->server->pairing_context->srp, context->server->pairing_context->public_key, &context->server->pairing_context->public_key_size);
if (r) {
CLIENT_ERROR(context, "Failed to dump SPR public key (code %d)", r);
pairing_context_free(context->server->pairing_context);
context->server->pairing_context = NULL;
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
size_t salt_size = 0;
crypto_srp_get_salt(context->server->pairing_context->srp, NULL, &salt_size);
byte *salt = malloc(salt_size);
r = crypto_srp_get_salt(context->server->pairing_context->srp, salt, &salt_size);
if (r) {
CLIENT_ERROR(context, "Failed to get salt (code %d)", r);
free(salt);
pairing_context_free(context->server->pairing_context);
context->server->pairing_context = NULL;
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
tlv_values_t *response = tlv_new();
tlv_add_value(response, TLVType_PublicKey, context->server->pairing_context->public_key, context->server->pairing_context->public_key_size);
tlv_add_value(response, TLVType_Salt, salt, salt_size);
tlv_add_integer_value(response, TLVType_State, 1, 2);
free(salt);
CLIENT_INFO(context, "send_tlv_response");
send_tlv_response(context, response);
CLIENT_INFO(context, "send_tlv_response done");
break;
}
case 3: {
CLIENT_INFO(context, "Pair Setup Step 2/3");
DEBUG_HEAP();
tlv_t *device_public_key = tlv_get_value(message, TLVType_PublicKey);
if (!device_public_key) {
CLIENT_ERROR(context, "Invalid payload: no device public key");
send_tlv_error_response(context, 4, TLVError_Authentication);
break;
}
tlv_t *proof = tlv_get_value(message, TLVType_Proof);
if (!proof) {
CLIENT_ERROR(context, "Invalid payload: no device proof");
send_tlv_error_response(context, 4, TLVError_Authentication);
break;
}
CLIENT_DEBUG(context, "Computing SRP shared secret");
DEBUG_HEAP();
int r = crypto_srp_compute_key(
context->server->pairing_context->srp,
device_public_key->value, device_public_key->size,
context->server->pairing_context->public_key,
context->server->pairing_context->public_key_size
);
if (r) {
CLIENT_ERROR(context, "Failed to compute SRP shared secret (code %d)", r);
send_tlv_error_response(context, 4, TLVError_Authentication);
break;
}
free(context->server->pairing_context->public_key);
context->server->pairing_context->public_key = NULL;
context->server->pairing_context->public_key_size = 0;
CLIENT_DEBUG(context, "Verifying peer's proof");
DEBUG_HEAP();
r = crypto_srp_verify(context->server->pairing_context->srp, proof->value, proof->size);
if (r) {
CLIENT_ERROR(context, "Failed to verify peer's proof (code %d)", r);
send_tlv_error_response(context, 4, TLVError_Authentication);
break;
}
CLIENT_DEBUG(context, "Generating own proof");
size_t server_proof_size = 0;
crypto_srp_get_proof(context->server->pairing_context->srp, NULL, &server_proof_size);
byte *server_proof = malloc(server_proof_size);
r = crypto_srp_get_proof(context->server->pairing_context->srp, server_proof, &server_proof_size);
tlv_values_t *response = tlv_new();
tlv_add_integer_value(response, TLVType_State, 1, 4);
tlv_add_value(response, TLVType_Proof, server_proof, server_proof_size);
free(server_proof);
send_tlv_response(context, response);
break;
}
case 5: {
CLIENT_INFO(context, "Pair Setup Step 3/3");
DEBUG_HEAP();
int r;
byte shared_secret[HKDF_HASH_SIZE];
size_t shared_secret_size = sizeof(shared_secret);
CLIENT_DEBUG(context, "Calculating shared secret");
const char salt1[] = "Pair-Setup-Encrypt-Salt";
const char info1[] = "Pair-Setup-Encrypt-Info";
r = crypto_srp_hkdf(
context->server->pairing_context->srp,
(byte *)salt1, sizeof(salt1)-1,
(byte *)info1, sizeof(info1)-1,
shared_secret, &shared_secret_size
);
if (r) {
CLIENT_ERROR(context, "Failed to generate shared secret (code %d)", r);
send_tlv_error_response(context, 6, TLVError_Authentication);
break;
}
tlv_t *tlv_encrypted_data = tlv_get_value(message, TLVType_EncryptedData);
if (!tlv_encrypted_data) {
CLIENT_ERROR(context, "Invalid payload: no encrypted data");
send_tlv_error_response(context, 6, TLVError_Authentication);
break;
}
CLIENT_DEBUG(context, "Decrypting payload");
size_t decrypted_data_size = 0;
crypto_chacha20poly1305_decrypt(
shared_secret, (byte *)"\x0\x0\x0\x0PS-Msg05", NULL, 0,
tlv_encrypted_data->value, tlv_encrypted_data->size,
NULL, &decrypted_data_size
);
byte *decrypted_data = malloc(decrypted_data_size);
// TODO: check malloc result
r = crypto_chacha20poly1305_decrypt(
shared_secret, (byte *)"\x0\x0\x0\x0PS-Msg05", NULL, 0,
tlv_encrypted_data->value, tlv_encrypted_data->size,
decrypted_data, &decrypted_data_size
);
if (r) {
CLIENT_ERROR(context, "Failed to decrypt data (code %d)", r);
free(decrypted_data);
send_tlv_error_response(context, 6, TLVError_Authentication);
break;
}
tlv_values_t *decrypted_message = tlv_new();
r = tlv_parse(decrypted_data, decrypted_data_size, decrypted_message);
if (r) {
CLIENT_ERROR(context, "Failed to parse decrypted TLV (code %d)", r);
tlv_free(decrypted_message);
free(decrypted_data);
send_tlv_error_response(context, 6, TLVError_Authentication);
break;
}
free(decrypted_data);
tlv_t *tlv_device_id = tlv_get_value(decrypted_message, TLVType_Identifier);
if (!tlv_device_id) {
CLIENT_ERROR(context, "Invalid encrypted payload: no device identifier");
tlv_free(decrypted_message);
send_tlv_error_response(context, 6, TLVError_Authentication);
break;
}
tlv_t *tlv_device_public_key = tlv_get_value(decrypted_message, TLVType_PublicKey);
if (!tlv_device_public_key) {
CLIENT_ERROR(context, "Invalid encrypted payload: no device public key");
tlv_free(decrypted_message);
send_tlv_error_response(context, 6, TLVError_Authentication);
break;
}
tlv_t *tlv_device_signature = tlv_get_value(decrypted_message, TLVType_Signature);
if (!tlv_device_signature) {
CLIENT_ERROR(context, "Invalid encrypted payload: no device signature");
tlv_free(decrypted_message);
send_tlv_error_response(context, 6, TLVError_Authentication);
break;
}
CLIENT_DEBUG(context, "Importing device public key");
ed25519_key *device_key = crypto_ed25519_new();
r = crypto_ed25519_import_public_key(
device_key,
tlv_device_public_key->value, tlv_device_public_key->size
);
if (r) {
CLIENT_ERROR(context, "Failed to import device public Key (code %d)", r);
crypto_ed25519_free(device_key);
tlv_free(decrypted_message);
send_tlv_error_response(context, 6, TLVError_Authentication);
break;
}
byte device_x[HKDF_HASH_SIZE];
size_t device_x_size = sizeof(device_x);
CLIENT_DEBUG(context, "Calculating DeviceX");
const char salt2[] = "Pair-Setup-Controller-Sign-Salt";
const char info2[] = "Pair-Setup-Controller-Sign-Info";
r = crypto_srp_hkdf(
context->server->pairing_context->srp,
(byte *)salt2, sizeof(salt2)-1,
(byte *)info2, sizeof(info2)-1,
device_x, &device_x_size
);
if (r) {
CLIENT_ERROR(context, "Failed to generate DeviceX (code %d)", r);
crypto_ed25519_free(device_key);
tlv_free(decrypted_message);
send_tlv_error_response(context, 6, TLVError_Authentication);
break;
}
size_t device_info_size = device_x_size + tlv_device_id->size + tlv_device_public_key->size;
byte *device_info = malloc(device_info_size);
memcpy(device_info,
device_x,
device_x_size);
memcpy(device_info + device_x_size,
tlv_device_id->value,
tlv_device_id->size);
memcpy(device_info + device_x_size + tlv_device_id->size,
tlv_device_public_key->value,
tlv_device_public_key->size);
CLIENT_DEBUG(context, "Verifying device signature");
r = crypto_ed25519_verify(
device_key,
device_info, device_info_size,
tlv_device_signature->value, tlv_device_signature->size
);
if (r) {
CLIENT_ERROR(context, "Failed to generate DeviceX (code %d)", r);
free(device_info);
crypto_ed25519_free(device_key);
tlv_free(decrypted_message);
send_tlv_error_response(context, 6, TLVError_Authentication);
break;
}
free(device_info);
char *device_id = strndup((const char *)tlv_device_id->value, tlv_device_id->size);
r = homekit_storage_add_pairing(device_id, device_key, pairing_permissions_admin);
if (r) {
CLIENT_ERROR(context, "Failed to store pairing (code %d)", r);
free(device_id);
crypto_ed25519_free(device_key);
tlv_free(decrypted_message);
send_tlv_error_response(context, 6, TLVError_Unknown);
break;
}
INFO("Added pairing with %s", device_id);
HOMEKIT_NOTIFY_EVENT(context->server, HOMEKIT_EVENT_PAIRING_ADDED);
free(device_id);
crypto_ed25519_free(device_key);
tlv_free(decrypted_message);
CLIENT_DEBUG(context, "Exporting accessory public key");
size_t accessory_public_key_size = 0;
crypto_ed25519_export_public_key(context->server->accessory_key, NULL, &accessory_public_key_size);
byte *accessory_public_key = malloc(accessory_public_key_size);
r = crypto_ed25519_export_public_key(context->server->accessory_key, accessory_public_key, &accessory_public_key_size);
if (r) {
CLIENT_ERROR(context, "Failed to export accessory public key (code %d)", r);
free(accessory_public_key);
send_tlv_error_response(context, 6, TLVError_Authentication);
break;
}
size_t accessory_id_size = strlen(context->server->accessory_id);
size_t accessory_info_size = HKDF_HASH_SIZE + accessory_id_size + accessory_public_key_size;
byte *accessory_info = malloc(accessory_info_size);
CLIENT_DEBUG(context, "Calculating AccessoryX");
size_t accessory_x_size = accessory_info_size;
const char salt3[] = "Pair-Setup-Accessory-Sign-Salt";
const char info3[] = "Pair-Setup-Accessory-Sign-Info";
r = crypto_srp_hkdf(
context->server->pairing_context->srp,
(byte *)salt3, sizeof(salt3)-1,
(byte *)info3, sizeof(info3)-1,
accessory_info, &accessory_x_size
);
if (r) {
CLIENT_ERROR(context, "Failed to generate AccessoryX (code %d)", r);
free(accessory_info);
free(accessory_public_key);
send_tlv_error_response(context, 6, TLVError_Unknown);
break;
}
memcpy(accessory_info + accessory_x_size,
context->server->accessory_id, accessory_id_size);
memcpy(accessory_info + accessory_x_size + accessory_id_size,
accessory_public_key, accessory_public_key_size);
CLIENT_DEBUG(context, "Generating accessory signature");
DEBUG_HEAP();
size_t accessory_signature_size = 0;
crypto_ed25519_sign(
context->server->accessory_key,
accessory_info, accessory_info_size,
NULL, &accessory_signature_size
);
byte *accessory_signature = malloc(accessory_signature_size);
r = crypto_ed25519_sign(
context->server->accessory_key,
accessory_info, accessory_info_size,
accessory_signature, &accessory_signature_size
);
if (r) {
CLIENT_ERROR(context, "Failed to generate accessory signature (code %d)", r);
free(accessory_signature);
free(accessory_public_key);
free(accessory_info);
send_tlv_error_response(context, 6, TLVError_Unknown);
break;
}
free(accessory_info);
tlv_values_t *response_message = tlv_new();
tlv_add_value(response_message, TLVType_Identifier,
(byte *)context->server->accessory_id, accessory_id_size);
tlv_add_value(response_message, TLVType_PublicKey,
accessory_public_key, accessory_public_key_size);
tlv_add_value(response_message, TLVType_Signature,
accessory_signature, accessory_signature_size);
free(accessory_public_key);
free(accessory_signature);
size_t response_data_size = 0;
TLV_DEBUG(response_message);
tlv_format(response_message, NULL, &response_data_size);
byte *response_data = malloc(response_data_size);
r = tlv_format(response_message, response_data, &response_data_size);
if (r) {
CLIENT_ERROR(context, "Failed to format TLV response (code %d)", r);
free(response_data);
tlv_free(response_message);
send_tlv_error_response(context, 6, TLVError_Unknown);
break;
}
tlv_free(response_message);
CLIENT_DEBUG(context, "Encrypting response");
size_t encrypted_response_data_size = 0;
crypto_chacha20poly1305_encrypt(
shared_secret, (byte *)"\x0\x0\x0\x0PS-Msg06", NULL, 0,
response_data, response_data_size,
NULL, &encrypted_response_data_size
);
byte *encrypted_response_data = malloc(encrypted_response_data_size);
r = crypto_chacha20poly1305_encrypt(
shared_secret, (byte *)"\x0\x0\x0\x0PS-Msg06", NULL, 0,
response_data, response_data_size,
encrypted_response_data, &encrypted_response_data_size
);
free(response_data);
if (r) {
CLIENT_ERROR(context, "Failed to encrypt response data (code %d)", r);
free(encrypted_response_data);
send_tlv_error_response(context, 6, TLVError_Unknown);
break;
}
tlv_values_t *response = tlv_new();
tlv_add_integer_value(response, TLVType_State, 1, 6);
tlv_add_value(response, TLVType_EncryptedData,
encrypted_response_data, encrypted_response_data_size);
free(encrypted_response_data);
send_tlv_response(context, response);
pairing_context_free(context->server->pairing_context);
context->server->pairing_context = NULL;
context->server->paired = 1;
homekit_setup_mdns(context->server);
CLIENT_INFO(context, "Successfully paired");
break;
}
default: {
CLIENT_ERROR(context, "Unknown state: %d",
tlv_get_integer_value(message, TLVType_State, -1));
}
}
tlv_free(message);
#ifdef HOMEKIT_OVERCLOCK_PAIR_SETUP
homekit_overclock_end();
#endif
}
void homekit_server_on_pair_verify(client_context_t *context, const byte *data, size_t size) {
DEBUG("HomeKit Pair Verify");
DEBUG_HEAP();
#ifdef HOMEKIT_OVERCLOCK_PAIR_VERIFY
homekit_overclock_start();
#endif
tlv_values_t *message = tlv_new();
tlv_parse(data, size, message);
TLV_DEBUG(message);
int r;
switch(tlv_get_integer_value(message, TLVType_State, -1)) {
case 1: {
CLIENT_INFO(context, "Pair Verify Step 1/2");
CLIENT_DEBUG(context, "Importing device Curve25519 public key");
tlv_t *tlv_device_public_key = tlv_get_value(message, TLVType_PublicKey);
if (!tlv_device_public_key) {
CLIENT_ERROR(context, "Device Curve25519 public key not found");
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
curve25519_key *device_key = crypto_curve25519_new();
r = crypto_curve25519_import_public(
device_key,
tlv_device_public_key->value, tlv_device_public_key->size
);
if (r) {
CLIENT_ERROR(context, "Failed to import device Curve25519 public key (code %d)", r);
crypto_curve25519_free(device_key);
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
CLIENT_DEBUG(context, "Generating accessory Curve25519 key");
curve25519_key *my_key = crypto_curve25519_generate();
if (!my_key) {
CLIENT_ERROR(context, "Failed to generate accessory Curve25519 key");
crypto_curve25519_free(device_key);
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
CLIENT_DEBUG(context, "Exporting accessory Curve25519 public key");
size_t my_key_public_size = 0;
crypto_curve25519_export_public(my_key, NULL, &my_key_public_size);
byte *my_key_public = malloc(my_key_public_size);
r = crypto_curve25519_export_public(my_key, my_key_public, &my_key_public_size);
if (r) {
CLIENT_ERROR(context, "Failed to export accessory Curve25519 public key (code %d)", r);
free(my_key_public);
crypto_curve25519_free(my_key);
crypto_curve25519_free(device_key);
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
CLIENT_DEBUG(context, "Generating Curve25519 shared secret");
size_t shared_secret_size = 0;
crypto_curve25519_shared_secret(my_key, device_key, NULL, &shared_secret_size);
byte *shared_secret = malloc(shared_secret_size);
r = crypto_curve25519_shared_secret(my_key, device_key, shared_secret, &shared_secret_size);
crypto_curve25519_free(my_key);
crypto_curve25519_free(device_key);
if (r) {
CLIENT_ERROR(context, "Failed to generate Curve25519 shared secret (code %d)", r);
free(shared_secret);
free(my_key_public);
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
CLIENT_DEBUG(context, "Generating signature");
size_t accessory_id_size = strlen(context->server->accessory_id);
size_t accessory_info_size = my_key_public_size + accessory_id_size + tlv_device_public_key->size;
byte *accessory_info = malloc(accessory_info_size);
memcpy(accessory_info,
my_key_public, my_key_public_size);
memcpy(accessory_info + my_key_public_size,
context->server->accessory_id, accessory_id_size);
memcpy(accessory_info + my_key_public_size + accessory_id_size,
tlv_device_public_key->value, tlv_device_public_key->size);
size_t accessory_signature_size = 0;
crypto_ed25519_sign(
context->server->accessory_key,
accessory_info, accessory_info_size,
NULL, &accessory_signature_size
);
byte *accessory_signature = malloc(accessory_signature_size);
r = crypto_ed25519_sign(
context->server->accessory_key,
accessory_info, accessory_info_size,
accessory_signature, &accessory_signature_size
);
free(accessory_info);
if (r) {
CLIENT_ERROR(context, "Failed to generate signature (code %d)", r);
free(accessory_signature);
free(shared_secret);
free(my_key_public);
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
tlv_values_t *sub_response = tlv_new();
tlv_add_value(sub_response, TLVType_Identifier,
(const byte *)context->server->accessory_id, accessory_id_size);
tlv_add_value(sub_response, TLVType_Signature,
accessory_signature, accessory_signature_size);
free(accessory_signature);
size_t sub_response_data_size = 0;
tlv_format(sub_response, NULL, &sub_response_data_size);
byte *sub_response_data = malloc(sub_response_data_size);
r = tlv_format(sub_response, sub_response_data, &sub_response_data_size);
tlv_free(sub_response);
if (r) {
CLIENT_ERROR(context, "Failed to format sub-TLV message (code %d)", r);
free(sub_response_data);
free(shared_secret);
free(my_key_public);
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
CLIENT_DEBUG(context, "Generating proof");
size_t session_key_size = 0;
const byte salt[] = "Pair-Verify-Encrypt-Salt";
const byte info[] = "Pair-Verify-Encrypt-Info";
crypto_hkdf(
shared_secret, shared_secret_size,
salt, sizeof(salt)-1,
info, sizeof(info)-1,
NULL, &session_key_size
);
byte *session_key = malloc(session_key_size);
r = crypto_hkdf(
shared_secret, shared_secret_size,
salt, sizeof(salt)-1,
info, sizeof(info)-1,
session_key, &session_key_size
);
if (r) {
CLIENT_ERROR(context, "Failed to derive session key (code %d)", r);
free(session_key);
free(sub_response_data);
free(shared_secret);
free(my_key_public);
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
CLIENT_DEBUG(context, "Encrypting response");
size_t encrypted_response_data_size = 0;
crypto_chacha20poly1305_encrypt(
session_key, (byte *)"\x0\x0\x0\x0PV-Msg02", NULL, 0,
sub_response_data, sub_response_data_size,
NULL, &encrypted_response_data_size
);
byte *encrypted_response_data = malloc(encrypted_response_data_size);
r = crypto_chacha20poly1305_encrypt(
session_key, (byte *)"\x0\x0\x0\x0PV-Msg02", NULL, 0,
sub_response_data, sub_response_data_size,
encrypted_response_data, &encrypted_response_data_size
);
free(sub_response_data);
if (r) {
CLIENT_ERROR(context, "Failed to encrypt sub response data (code %d)", r);
free(encrypted_response_data);
free(session_key);
free(shared_secret);
free(my_key_public);
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
tlv_values_t *response = tlv_new();
tlv_add_integer_value(response, TLVType_State, 1, 2);
tlv_add_value(response, TLVType_PublicKey,
my_key_public, my_key_public_size);
tlv_add_value(response, TLVType_EncryptedData,
encrypted_response_data, encrypted_response_data_size);
free(encrypted_response_data);
send_tlv_response(context, response);
if (context->verify_context)
pair_verify_context_free(context->verify_context);
context->verify_context = pair_verify_context_new();
context->verify_context->secret = shared_secret;
context->verify_context->secret_size = shared_secret_size;
context->verify_context->session_key = session_key;
context->verify_context->session_key_size = session_key_size;
context->verify_context->accessory_public_key = my_key_public;
context->verify_context->accessory_public_key_size = my_key_public_size;
context->verify_context->device_public_key = malloc(tlv_device_public_key->size);
memcpy(context->verify_context->device_public_key,
tlv_device_public_key->value, tlv_device_public_key->size);
context->verify_context->device_public_key_size = tlv_device_public_key->size;
break;
}
case 3: {
CLIENT_INFO(context, "Pair Verify Step 2/2");
if (!context->verify_context) {
CLIENT_ERROR(context, "Failed to verify: no state 1 data");
send_tlv_error_response(context, 4, TLVError_Authentication);
break;
}
tlv_t *tlv_encrypted_data = tlv_get_value(message, TLVType_EncryptedData);
if (!tlv_encrypted_data) {
CLIENT_ERROR(context, "Failed to verify: no encrypted data");
pair_verify_context_free(context->verify_context);
context->verify_context = NULL;
send_tlv_error_response(context, 4, TLVError_Authentication);
break;
}
CLIENT_DEBUG(context, "Decrypting payload");
size_t decrypted_data_size = 0;
crypto_chacha20poly1305_decrypt(
context->verify_context->session_key, (byte *)"\x0\x0\x0\x0PV-Msg03", NULL, 0,
tlv_encrypted_data->value, tlv_encrypted_data->size,
NULL, &decrypted_data_size
);
byte *decrypted_data = malloc(decrypted_data_size);
r = crypto_chacha20poly1305_decrypt(
context->verify_context->session_key, (byte *)"\x0\x0\x0\x0PV-Msg03", NULL, 0,
tlv_encrypted_data->value, tlv_encrypted_data->size,
decrypted_data, &decrypted_data_size
);
if (r) {
CLIENT_ERROR(context, "Failed to decrypt data (code %d)", r);
free(decrypted_data);
pair_verify_context_free(context->verify_context);
context->verify_context = NULL;
send_tlv_error_response(context, 4, TLVError_Authentication);
break;
}
tlv_values_t *decrypted_message = tlv_new();
r = tlv_parse(decrypted_data, decrypted_data_size, decrypted_message);
free(decrypted_data);
if (r) {
CLIENT_ERROR(context, "Failed to parse decrypted TLV (code %d)", r);
tlv_free(decrypted_message);
pair_verify_context_free(context->verify_context);
context->verify_context = NULL;
send_tlv_error_response(context, 4, TLVError_Authentication);
break;
}
tlv_t *tlv_device_id = tlv_get_value(decrypted_message, TLVType_Identifier);
if (!tlv_device_id) {
CLIENT_ERROR(context, "Invalid encrypted payload: no device identifier");
tlv_free(decrypted_message);
pair_verify_context_free(context->verify_context);
context->verify_context = NULL;
send_tlv_error_response(context, 4, TLVError_Authentication);
break;
}
tlv_t *tlv_device_signature = tlv_get_value(decrypted_message, TLVType_Signature);
if (!tlv_device_signature) {
CLIENT_ERROR(context, "Invalid encrypted payload: no device identifier");
tlv_free(decrypted_message);
pair_verify_context_free(context->verify_context);
context->verify_context = NULL;
send_tlv_error_response(context, 4, TLVError_Authentication);
break;
}
char *device_id = strndup((const char *)tlv_device_id->value, tlv_device_id->size);
CLIENT_DEBUG(context, "Searching pairing with %s", device_id);
pairing_t *pairing = homekit_storage_find_pairing(device_id);
if (!pairing) {
CLIENT_ERROR(context, "No pairing for %s found", device_id);
free(device_id);
tlv_free(decrypted_message);
pair_verify_context_free(context->verify_context);
context->verify_context = NULL;
send_tlv_error_response(context, 4, TLVError_Authentication);
break;
}
CLIENT_INFO(context, "Found pairing with %s", device_id);
free(device_id);
byte permissions = pairing->permissions;
int pairing_id = pairing->id;
size_t device_info_size =
context->verify_context->device_public_key_size +
context->verify_context->accessory_public_key_size +
tlv_device_id->size;
byte *device_info = malloc(device_info_size);
memcpy(device_info,
context->verify_context->device_public_key, context->verify_context->device_public_key_size);
memcpy(device_info + context->verify_context->device_public_key_size,
tlv_device_id->value, tlv_device_id->size);
memcpy(device_info + context->verify_context->device_public_key_size + tlv_device_id->size,
context->verify_context->accessory_public_key, context->verify_context->accessory_public_key_size);
CLIENT_DEBUG(context, "Verifying device signature");
r = crypto_ed25519_verify(
pairing->device_key,
device_info, device_info_size,
tlv_device_signature->value, tlv_device_signature->size
);
free(device_info);
pairing_free(pairing);
tlv_free(decrypted_message);
if (r) {
CLIENT_ERROR(context, "Failed to verify device signature (code %d)", r);
pair_verify_context_free(context->verify_context);
context->verify_context = NULL;
send_tlv_error_response(context, 4, TLVError_Authentication);
break;
}
const byte salt[] = "Control-Salt";
size_t read_key_size = 32;
context->read_key = malloc(read_key_size);
const byte read_info[] = "Control-Read-Encryption-Key";
r = crypto_hkdf(
context->verify_context->secret, context->verify_context->secret_size,
salt, sizeof(salt)-1,
read_info, sizeof(read_info)-1,
context->read_key, &read_key_size
);
if (r) {
CLIENT_ERROR(context, "Failed to derive read encryption key (code %d)", r);
free(context->read_key);
context->read_key = NULL;
pair_verify_context_free(context->verify_context);
context->verify_context = NULL;
send_tlv_error_response(context, 4, TLVError_Unknown);
break;
}
size_t write_key_size = 32;
context->write_key = malloc(write_key_size);
const byte write_info[] = "Control-Write-Encryption-Key";
r = crypto_hkdf(
context->verify_context->secret, context->verify_context->secret_size,
salt, sizeof(salt)-1,
write_info, sizeof(write_info)-1,
context->write_key, &write_key_size
);
pair_verify_context_free(context->verify_context);
context->verify_context = NULL;
if (r) {
CLIENT_ERROR(context, "Failed to derive write encryption key (code %d)", r);
free(context->write_key);
context->write_key = NULL;
free(context->read_key);
context->read_key = NULL;
send_tlv_error_response(context, 4, TLVError_Unknown);
break;
}
tlv_values_t *response = tlv_new();
tlv_add_integer_value(response, TLVType_State, 1, 4);
send_tlv_response(context, response);
context->pairing_id = pairing_id;
context->permissions = permissions;
context->encrypted = true;
HOMEKIT_NOTIFY_EVENT(context->server, HOMEKIT_EVENT_CLIENT_VERIFIED);
CLIENT_INFO(context, "Verification successful, secure session established");
break;
}
default: {
CLIENT_ERROR(context, "Unknown state: %d",
tlv_get_integer_value(message, TLVType_State, -1));
}
}
tlv_free(message);
#ifdef HOMEKIT_OVERCLOCK_PAIR_VERIFY
homekit_overclock_end();
#endif
}
void homekit_server_on_get_accessories(client_context_t *context) {
CLIENT_INFO(context, "Get Accessories");
DEBUG_HEAP();
client_send(context, json_200_response_headers, sizeof(json_200_response_headers)-1);
json_stream *json = json_new(1024, client_send_chunk, context);
json_object_start(json);
json_string(json, "accessories"); json_array_start(json);
for (homekit_accessory_t **accessory_it = context->server->config->accessories; *accessory_it; accessory_it++) {
homekit_accessory_t *accessory = *accessory_it;
json_object_start(json);
json_string(json, "aid"); json_integer(json, accessory->id);
json_string(json, "services"); json_array_start(json);
for (homekit_service_t **service_it = accessory->services; *service_it; service_it++) {
homekit_service_t *service = *service_it;
json_object_start(json);
json_string(json, "iid"); json_integer(json, service->id);
json_string(json, "type"); json_string(json, service->type);
json_string(json, "hidden"); json_boolean(json, service->hidden);
json_string(json, "primary"); json_boolean(json, service->primary);
if (service->linked) {
json_string(json, "linked"); json_array_start(json);
for (homekit_service_t **linked=service->linked; *linked; linked++) {
json_integer(json, (*linked)->id);
}
json_array_end(json);
}
json_string(json, "characteristics"); json_array_start(json);
for (homekit_characteristic_t **ch_it = service->characteristics; *ch_it; ch_it++) {
homekit_characteristic_t *ch = *ch_it;
json_object_start(json);
write_characteristic_json(
json, context, ch,
characteristic_format_type
| characteristic_format_meta
| characteristic_format_perms
| characteristic_format_events,
NULL
);
json_object_end(json);
}
json_array_end(json);
json_object_end(json); // service
}
json_array_end(json);
json_object_end(json); // accessory
}
json_array_end(json);
json_object_end(json); // response
json_flush(json);
json_free(json);
client_send_chunk(NULL, 0, context);
}
void homekit_server_on_get_characteristics(client_context_t *context) {
CLIENT_INFO(context, "Get Characteristics");
DEBUG_HEAP();
query_param_t *qp = context->endpoint_params;
while (qp) {
CLIENT_DEBUG(context, "Query paramter %s = %s", qp->name, qp->value);
qp = qp->next;
}
query_param_t *id_param = query_params_find(context->endpoint_params, "id");
if (!id_param) {
CLIENT_ERROR(context, "Invalid get characteristics request: missing ID parameter");
send_json_error_response(context, 400, HAPStatus_InvalidValue);
return;
}
bool bool_endpoint_param(const char *name) {
query_param_t *param = query_params_find(context->endpoint_params, name);
return param && param->value && !strcmp(param->value, "1");
}
characteristic_format_t format = 0;
if (bool_endpoint_param("meta"))
format |= characteristic_format_meta;
if (bool_endpoint_param("perms"))
format |= characteristic_format_perms;
if (bool_endpoint_param("type"))
format |= characteristic_format_type;
if (bool_endpoint_param("ev"))
format |= characteristic_format_events;
bool success = true;
char *id = strdup(id_param->value);
char *ch_id;
char *_id = id;
while ((ch_id = strsep(&_id, ","))) {
char *dot = strstr(ch_id, ".");
if (!dot) {
send_json_error_response(context, 400, HAPStatus_InvalidValue);
free(id);
return;
}
*dot = 0;
int aid = atoi(ch_id);
int iid = atoi(dot+1);
CLIENT_DEBUG(context, "Requested characteristic info for %d.%d", aid, iid);
homekit_characteristic_t *ch = homekit_characteristic_by_aid_and_iid(context->server->config->accessories, aid, iid);
if (!ch) {
success = false;
continue;
}
if (!(ch->permissions & homekit_permissions_paired_read)) {
success = false;
continue;
}
}
free(id);
id = strdup(id_param->value);
if (success) {
client_send(context, json_200_response_headers, sizeof(json_200_response_headers)-1);
} else {
client_send(context, json_207_response_headers, sizeof(json_207_response_headers)-1);
}
json_stream *json = json_new(256, client_send_chunk, context);
json_object_start(json);
json_string(json, "characteristics"); json_array_start(json);
void write_characteristic_error(json_stream *json, int aid, int iid, int status) {
json_object_start(json);
json_string(json, "aid"); json_integer(json, aid);
json_string(json, "iid"); json_integer(json, iid);
json_string(json, "status"); json_integer(json, status);
json_object_end(json);
}
_id = id;
while ((ch_id = strsep(&_id, ","))) {
char *dot = strstr(ch_id, ".");
*dot = 0;
int aid = atoi(ch_id);
int iid = atoi(dot+1);
CLIENT_DEBUG(context, "Requested characteristic info for %d.%d", aid, iid);
homekit_characteristic_t *ch = homekit_characteristic_by_aid_and_iid(context->server->config->accessories, aid, iid);
if (!ch) {
write_characteristic_error(json, aid, iid, HAPStatus_NoResource);
continue;
}
if (!(ch->permissions & homekit_permissions_paired_read)) {
write_characteristic_error(json, aid, iid, HAPStatus_WriteOnly);
continue;
}
json_object_start(json);
write_characteristic_json(json, context, ch, format, NULL);
if (!success) {
json_string(json, "status"); json_integer(json, HAPStatus_Success);
}
json_object_end(json);
}
json_array_end(json);
json_object_end(json); // response
json_flush(json);
json_free(json);
client_send_chunk(NULL, 0, context);
free(id);
}
void homekit_server_on_update_characteristics(client_context_t *context, const byte *data, size_t size) {
CLIENT_INFO(context, "Update Characteristics");
DEBUG_HEAP();
char *data1 = strndup((char *)data, size);
cJSON *json = cJSON_Parse(data1);
free(data1);
if (!json) {
CLIENT_ERROR(context, "Failed to parse request JSON");
send_json_error_response(context, 400, HAPStatus_InvalidValue);
return;
}
cJSON *characteristics = cJSON_GetObjectItem(json, "characteristics");
if (!characteristics) {
CLIENT_ERROR(context, "Failed to parse request: no \"characteristics\" field");
cJSON_Delete(json);
send_json_error_response(context, 400, HAPStatus_InvalidValue);
return;
}
if (characteristics->type != cJSON_Array) {
CLIENT_ERROR(context, "Failed to parse request: \"characteristics\" field is not an list");
cJSON_Delete(json);
send_json_error_response(context, 400, HAPStatus_InvalidValue);
return;
}
HAPStatus process_characteristics_update(const cJSON *j_ch) {
cJSON *j_aid = cJSON_GetObjectItem(j_ch, "aid");
if (!j_aid) {
CLIENT_ERROR(context, "Failed to process request: no \"aid\" field");
return HAPStatus_NoResource;
}
if (j_aid->type != cJSON_Number) {
CLIENT_ERROR(context, "Failed to process request: \"aid\" field is not a number");
return HAPStatus_NoResource;
}
cJSON *j_iid = cJSON_GetObjectItem(j_ch, "iid");
if (!j_iid) {
CLIENT_ERROR(context, "Failed to process request: no \"iid\" field");
return HAPStatus_NoResource;
}
if (j_iid->type != cJSON_Number) {
CLIENT_ERROR(context, "Failed to process request: \"iid\" field is not a number");
return HAPStatus_NoResource;
}
int aid = j_aid->valueint;
int iid = j_iid->valueint;
homekit_characteristic_t *ch = homekit_characteristic_by_aid_and_iid(
context->server->config->accessories, aid, iid
);
if (!ch) {
CLIENT_ERROR(context, "Failed to process request to update %d.%d: "
"no such characteristic", aid, iid);
return HAPStatus_NoResource;
}
cJSON *j_value = cJSON_GetObjectItem(j_ch, "value");
//CLIENT_INFO(context, "Update Characteristics desc %s, id %d,format %d",ch->description,ch->id,(int)ch->format);
if (j_value) {
homekit_value_t h_value = HOMEKIT_NULL();
if (!(ch->permissions & homekit_permissions_paired_write)) {
CLIENT_ERROR(context, "Failed to update %d.%d: no write permission", aid, iid);
return HAPStatus_ReadOnly;
}
switch (ch->format) {
case homekit_format_bool: {
bool value = false;
if (j_value->type == cJSON_True) {
value = true;
} else if (j_value->type == cJSON_False) {
value = false;
} else if (j_value->type == cJSON_Number &&
(j_value->valueint == 0 || j_value->valueint == 1)) {
value = j_value->valueint == 1;
} else {
CLIENT_ERROR(context, "Failed to update %d.%d: value is not a boolean or 0/1", aid, iid);
return HAPStatus_InvalidValue;
}
// CLIENT_DEBUG(context, "Updating characteristic %d.%d with boolean %s", aid, iid, value ? "true" : "false");
//CLIENT_INFO(context, "Updating characteristic %d.%d with boolean %s", aid, iid, value ? "true" : "false");
h_value = HOMEKIT_BOOL(value);
if (ch->setter_ex) {
ch->setter_ex(ch, h_value);
} else {
ch->value = h_value;
}
break;
}
case homekit_format_uint8:
case homekit_format_uint16:
case homekit_format_uint32:
case homekit_format_uint64:
case homekit_format_int: {
// We accept boolean values here in order to fix a bug in HomeKit. HomeKit sometimes sends a boolean instead of an integer of value 0 or 1.
if (j_value->type != cJSON_Number && j_value->type != cJSON_False && j_value->type != cJSON_True) {
CLIENT_ERROR(context, "Failed to update %d.%d: value is not a number", aid, iid);
return HAPStatus_InvalidValue;
}
unsigned long long min_value = 0;
unsigned long long max_value = 0;
switch (ch->format) {
case homekit_format_uint8: {
min_value = 0;
max_value = 255;
break;
}
case homekit_format_uint16: {
min_value = 0;
max_value = 65535;
break;
}
case homekit_format_uint32: {
min_value = 0;
max_value = 4294967295;
break;
}
case homekit_format_uint64: {
min_value = 0;
max_value = 18446744073709551615ULL;
break;
}
case homekit_format_int: {
min_value = -2147483648;
max_value = 2147483647;
break;
}
default: {
// Impossible, keeping to make compiler happy
break;
}
}
if (ch->min_value)
min_value = (int)*ch->min_value;
if (ch->max_value)
max_value = (int)*ch->max_value;
int value = j_value->valueint;
if (value < min_value || value > max_value) {
CLIENT_ERROR(context, "Failed to update %d.%d: value is not in range", aid, iid);
return HAPStatus_InvalidValue;
}
if (ch->valid_values.count) {
bool matches = false;
for (int i=0; i<ch->valid_values.count; i++) {
if (value == ch->valid_values.values[i]) {
matches = true;
break;
}
}
if (!matches) {
CLIENT_ERROR(context, "Failed to update %d.%d: value is not one of valid values", aid, iid);
return HAPStatus_InvalidValue;
}
}
if (ch->valid_values_ranges.count) {
bool matches = false;
for (int i=0; i<ch->valid_values_ranges.count; i++) {
if (value >= ch->valid_values_ranges.ranges[i].start &&
value <= ch->valid_values_ranges.ranges[i].end) {
matches = true;
break;
}
}
if (!matches) {
CLIENT_ERROR(context, "Failed to update %d.%d: value is not in valid values range", aid, iid);
return HAPStatus_InvalidValue;
}
}
CLIENT_DEBUG(context, "Updating characteristic %d.%d with integer %d", aid, iid, value);
h_value = HOMEKIT_INT(value);
h_value.format = ch->format;
if (ch->setter_ex) {
ch->setter_ex(ch, h_value);
} else {
ch->value = h_value;
}
break;
}
case homekit_format_float: {
if (j_value->type != cJSON_Number) {
CLIENT_ERROR(context, "Failed to update %d.%d: value is not a number", aid, iid);
return HAPStatus_InvalidValue;
}
float value = j_value->valuedouble;
if ((ch->min_value && value < *ch->min_value) ||
(ch->max_value && value > *ch->max_value)) {
CLIENT_ERROR(context, "Failed to update %d.%d: value is not in range", aid, iid);
return HAPStatus_InvalidValue;
}
CLIENT_DEBUG(context, "Updating characteristic %d.%d with %g", aid, iid, value);
CLIENT_INFO(context, "Updating characteristic %d.%d with %g", aid, iid, value);
h_value = HOMEKIT_FLOAT(value);
if (ch->setter_ex) {
ch->setter_ex(ch, h_value);
} else {
ch->value = h_value;
}
break;
}
case homekit_format_string: {
if (j_value->type != cJSON_String) {
CLIENT_ERROR(context, "Failed to update %d.%d: value is not a string", aid, iid);
return HAPStatus_InvalidValue;
}
int max_len = (ch->max_len) ? *ch->max_len : 64;
char *value = j_value->valuestring;
if (strlen(value) > max_len) {
CLIENT_ERROR(context, "Failed to update %d.%d: value is too long", aid, iid);
return HAPStatus_InvalidValue;
}
CLIENT_DEBUG(context, "Updating characteristic %d.%d with \"%s\"", aid, iid, value);
h_value = HOMEKIT_STRING(value);
if (ch->setter_ex) {
ch->setter_ex(ch, h_value);
} else {
homekit_value_destruct(&ch->value);
homekit_value_copy(&ch->value, &h_value);
}
break;
}
case homekit_format_tlv: {
if (j_value->type != cJSON_String) {
CLIENT_ERROR(context, "Failed to update %d.%d: value is not a string", aid, iid);
return HAPStatus_InvalidValue;
}
int max_len = (ch->max_len) ? *ch->max_len : 256;
char *value = j_value->valuestring;
size_t value_len = strlen(value);
if (value_len > max_len) {
CLIENT_ERROR(context, "Failed to update %d.%d: value is too long", aid, iid);
return HAPStatus_InvalidValue;
}
size_t tlv_size = base64_decoded_size((unsigned char*)value, value_len);
byte *tlv_data = malloc(tlv_size);
if (base64_decode((byte*) value, value_len, tlv_data) < 0) {
free(tlv_data);
CLIENT_ERROR(context, "Failed to update %d.%d: error Base64 decoding", aid, iid);
return HAPStatus_InvalidValue;
}
tlv_values_t *tlv_values = tlv_new();
int r = tlv_parse(tlv_data, tlv_size, tlv_values);
free(tlv_data);
if (r) {
CLIENT_ERROR(context, "Failed to update %d.%d: error parsing TLV", aid, iid);
return HAPStatus_InvalidValue;
}
CLIENT_DEBUG(context, "Updating characteristic %d.%d with TLV:", aid, iid);
for (tlv_t *t=tlv_values->head; t; t=t->next) {
char *escaped_payload = binary_to_string(t->value, t->size);
CLIENT_DEBUG(context, " Type %d value (%d bytes): %s", t->type, t->size, escaped_payload);
free(escaped_payload);
}
h_value = HOMEKIT_TLV(tlv_values);
if (ch->setter_ex) {
ch->setter_ex(ch, h_value);
} else {
homekit_value_destruct(&ch->value);
homekit_value_copy(&ch->value, &h_value);
}
tlv_free(tlv_values);
break;
}
case homekit_format_data: {
// TODO:
break;
}
}
if (!h_value.is_null) {
context->current_characteristic = ch;
context->current_value = &h_value;
homekit_characteristic_notify(ch, h_value);
context->current_characteristic = NULL;
context->current_value = NULL;
}
}
cJSON *j_events = cJSON_GetObjectItem(j_ch, "ev");
if (j_events) {
if (!(ch->permissions && homekit_permissions_notify)) {
CLIENT_ERROR(context, "Failed to set notification state for %d.%d: "
"notifications are not supported", aid, iid);
return HAPStatus_NotificationsUnsupported;
}
if ((j_events->type != cJSON_True) && (j_events->type != cJSON_False)) {
CLIENT_ERROR(context, "Failed to set notification state for %d.%d: "
"invalid state value", aid, iid);
}
if (j_events->type == cJSON_True) {
homekit_characteristic_add_notify_callback(ch, client_notify_characteristic, context);
} else {
homekit_characteristic_remove_notify_callback(ch, client_notify_characteristic, context);
}
}
return HAPStatus_Success;
}
HAPStatus *statuses = malloc(sizeof(HAPStatus) * cJSON_GetArraySize(characteristics));
bool has_errors = false;
for (int i=0; i < cJSON_GetArraySize(characteristics); i++) {
cJSON *j_ch = cJSON_GetArrayItem(characteristics, i);
char *s = cJSON_Print(j_ch);
CLIENT_DEBUG(context, "Processing element %s", s);
free(s);
statuses[i] = process_characteristics_update(j_ch);
if (statuses[i] != HAPStatus_Success)
has_errors = true;
}
if (!has_errors) {
CLIENT_DEBUG(context, "There were no processing errors, sending No Content response");
send_204_response(context);
} else {
CLIENT_DEBUG(context, "There were processing errors, sending Multi-Status response");
client_send(context, json_207_response_headers, sizeof(json_207_response_headers)-1);
json_stream *json1 = json_new(1024, client_send_chunk, context);
json_object_start(json1);
json_string(json1, "characteristics"); json_array_start(json1);
for (int i=0; i < cJSON_GetArraySize(characteristics); i++) {
cJSON *j_ch = cJSON_GetArrayItem(characteristics, i);
json_object_start(json1);
json_string(json1, "aid"); json_integer(json1, cJSON_GetObjectItem(j_ch, "aid")->valueint);
json_string(json1, "iid"); json_integer(json1, cJSON_GetObjectItem(j_ch, "iid")->valueint);
json_string(json1, "status"); json_integer(json1, statuses[i]);
json_object_end(json1);
}
json_array_end(json1);
json_object_end(json1); // response
json_flush(json1);
json_free(json1);
client_send_chunk(NULL, 0, context);
}
free(statuses);
cJSON_Delete(json);
}
void homekit_server_on_pairings(client_context_t *context, const byte *data, size_t size) {
DEBUG("HomeKit Pairings");
DEBUG_HEAP();
tlv_values_t *message = tlv_new();
tlv_parse(data, size, message);
TLV_DEBUG(message);
int r;
if (tlv_get_integer_value(message, TLVType_State, -1) != 1) {
send_tlv_error_response(context, 2, TLVError_Unknown);
tlv_free(message);
return;
}
switch(tlv_get_integer_value(message, TLVType_Method, -1)) {
case TLVMethod_AddPairing: {
CLIENT_INFO(context, "Add Pairing");
if (!(context->permissions & pairing_permissions_admin)) {
CLIENT_ERROR(context, "Refusing to add pairing to non-admin controller");
send_tlv_error_response(context, 2, TLVError_Authentication);
break;
}
tlv_t *tlv_device_identifier = tlv_get_value(message, TLVType_Identifier);
if (!tlv_device_identifier) {
CLIENT_ERROR(context, "Invalid add pairing request: no device identifier");
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
tlv_t *tlv_device_public_key = tlv_get_value(message, TLVType_PublicKey);
if (!tlv_device_public_key) {
CLIENT_ERROR(context, "Invalid add pairing request: no device public key");
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
int device_permissions = tlv_get_integer_value(message, TLVType_Permissions, -1);
if (device_permissions == -1) {
CLIENT_ERROR(context, "Invalid add pairing request: no device permissions");
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
ed25519_key *device_key = crypto_ed25519_new();
r = crypto_ed25519_import_public_key(
device_key, tlv_device_public_key->value, tlv_device_public_key->size
);
if (r) {
CLIENT_ERROR(context, "Failed to import device public key");
crypto_ed25519_free(device_key);
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
char *device_identifier = strndup(
(const char *)tlv_device_identifier->value,
tlv_device_identifier->size
);
pairing_t *pairing = homekit_storage_find_pairing(device_identifier);
if (pairing) {
size_t pairing_public_key_size = 0;
crypto_ed25519_export_public_key(pairing->device_key, NULL, &pairing_public_key_size);
byte *pairing_public_key = malloc(pairing_public_key_size);
r = crypto_ed25519_export_public_key(pairing->device_key, pairing_public_key, &pairing_public_key_size);
if (r) {
CLIENT_ERROR(context, "Failed to add pairing: error exporting pairing public key (code %d)", r);
free(pairing_public_key);
pairing_free(pairing);
free(device_identifier);
crypto_ed25519_free(device_key);
send_tlv_error_response(context, 2, TLVError_Unknown);
}
pairing_free(pairing);
if (pairing_public_key_size != tlv_device_public_key->size ||
memcmp(tlv_device_public_key->value, pairing_public_key, pairing_public_key_size)) {
CLIENT_ERROR(context, "Failed to add pairing: pairing public key differs from given one");
free(pairing_public_key);
free(device_identifier);
crypto_ed25519_free(device_key);
send_tlv_error_response(context, 2, TLVError_Unknown);
}
free(pairing_public_key);
r = homekit_storage_update_pairing(device_identifier, device_permissions);
if (r) {
CLIENT_ERROR(context, "Failed to add pairing: storage error (code %d)", r);
free(device_identifier);
crypto_ed25519_free(device_key);
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
INFO("Updated pairing with %s", device_identifier);
} else {
if (!homekit_storage_can_add_pairing()) {
CLIENT_ERROR(context, "Failed to add pairing: max peers");
free(device_identifier);
crypto_ed25519_free(device_key);
send_tlv_error_response(context, 2, TLVError_MaxPeers);
break;
}
r = homekit_storage_add_pairing(
device_identifier, device_key, device_permissions
);
if (r) {
CLIENT_ERROR(context, "Failed to add pairing: storage error (code %d)", r);
free(device_identifier);
crypto_ed25519_free(device_key);
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
INFO("Added pairing with %s", device_identifier);
HOMEKIT_NOTIFY_EVENT(context->server, HOMEKIT_EVENT_PAIRING_ADDED);
}
free(device_identifier);
crypto_ed25519_free(device_key);
tlv_values_t *response = tlv_new();
tlv_add_integer_value(response, TLVType_State, 1, 2);
send_tlv_response(context, response);
break;
}
case TLVMethod_RemovePairing: {
CLIENT_INFO(context, "Remove Pairing");
if (!(context->permissions & pairing_permissions_admin)) {
CLIENT_ERROR(context, "Refusing to remove pairing to non-admin controller");
send_tlv_error_response(context, 2, TLVError_Authentication);
break;
}
tlv_t *tlv_device_identifier = tlv_get_value(message, TLVType_Identifier);
if (!tlv_device_identifier) {
CLIENT_ERROR(context, "Invalid remove pairing request: no device identifier");
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
char *device_identifier = strndup(
(const char *)tlv_device_identifier->value,
tlv_device_identifier->size
);
pairing_t *pairing = homekit_storage_find_pairing(device_identifier);
if (pairing) {
bool is_admin = pairing->permissions & pairing_permissions_admin;
pairing_free(pairing);
r = homekit_storage_remove_pairing(device_identifier);
if (r) {
CLIENT_ERROR(context, "Failed to remove pairing: storage error (code %d)", r);
free(device_identifier);
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
INFO("Removed pairing with %s", device_identifier);
HOMEKIT_NOTIFY_EVENT(context->server, HOMEKIT_EVENT_PAIRING_REMOVED);
client_context_t *c = context->server->clients;
while (c) {
if (c->pairing_id == pairing->id)
c->disconnect = true;
c = c->next;
}
if (is_admin) {
// Removed pairing was admin,
// check if there any other admins left.
// If no admins left, enable pairing again
pairing_iterator_t *pairing_it = homekit_storage_pairing_iterator();
pairing_t *pairing;
while ((pairing = homekit_storage_next_pairing(pairing_it))) {
if (pairing->permissions & pairing_permissions_admin) {
break;
}
pairing_free(pairing);
};
homekit_storage_pairing_iterator_free(pairing_it);
if (!pairing) {
// No admins left, enable pairing again
INFO("Last admin pairing was removed, enabling pair setup");
context->server->paired = false;
homekit_setup_mdns(context->server);
} else {
pairing_free(pairing);
}
}
}
free(device_identifier);
tlv_values_t *response = tlv_new();
tlv_add_integer_value(response, TLVType_State, 1, 2);
send_tlv_response(context, response);
break;
}
case TLVMethod_ListPairings: {
CLIENT_INFO(context, "List Pairings");
if (!(context->permissions & pairing_permissions_admin)) {
CLIENT_INFO(context, "Refusing to list pairings to non-admin controller");
send_tlv_error_response(context, 2, TLVError_Authentication);
break;
}
tlv_values_t *response = tlv_new();
tlv_add_integer_value(response, TLVType_State, 1, 2);
bool first = true;
pairing_iterator_t *it = homekit_storage_pairing_iterator();
pairing_t *pairing = NULL;
size_t public_key_size = 32;
byte *public_key = malloc(public_key_size);
while ((pairing = homekit_storage_next_pairing(it))) {
if (!first) {
tlv_add_value(response, TLVType_Separator, NULL, 0);
}
r = crypto_ed25519_export_public_key(pairing->device_key, public_key, &public_key_size);
tlv_add_string_value(response, TLVType_Identifier, pairing->device_id);
tlv_add_value(response, TLVType_PublicKey, public_key, public_key_size);
tlv_add_integer_value(response, TLVType_Permissions, 1, pairing->permissions);
first = false;
pairing_free(pairing);
}
homekit_storage_pairing_iterator_free(it);
free(public_key);
send_tlv_response(context, response);
break;
}
default: {
send_tlv_error_response(context, 2, TLVError_Unknown);
break;
}
}
tlv_free(message);
}
void homekit_server_on_reset(client_context_t *context) {
INFO("Reset");
homekit_server_reset();
send_204_response(context);
vTaskDelay(3000 / portTICK_PERIOD_MS);
homekit_system_restart();
}
void homekit_server_on_resource(client_context_t *context) {
CLIENT_INFO(context, "Resource");
DEBUG_HEAP();
if (!context->server->config->on_resource) {
send_404_response(context);
return;
}
context->server->config->on_resource(context->body, context->body_length);
}
int homekit_server_on_url(http_parser *parser, const char *data, size_t length) {
client_context_t *context = (client_context_t*) parser->data;
context->endpoint = HOMEKIT_ENDPOINT_UNKNOWN;
if (parser->method == HTTP_GET) {
if (!strncmp(data, "/accessories", length)) {
context->endpoint = HOMEKIT_ENDPOINT_GET_ACCESSORIES;
} else {
static const char url[] = "/characteristics";
size_t url_len = sizeof(url)-1;
if (length >= url_len && !strncmp(data, url, url_len) &&
(data[url_len] == 0 || data[url_len] == '?'))
{
context->endpoint = HOMEKIT_ENDPOINT_GET_CHARACTERISTICS;
if (data[url_len] == '?') {
char *query = strndup(data+url_len+1, length-url_len-1);
context->endpoint_params = query_params_parse(query);
free(query);
}
}
}
} else if (parser->method == HTTP_POST) {
if (!strncmp(data, "/identify", length)) {
context->endpoint = HOMEKIT_ENDPOINT_IDENTIFY;
} else if (!strncmp(data, "/pair-setup", length)) {
context->endpoint = HOMEKIT_ENDPOINT_PAIR_SETUP;
} else if (!strncmp(data, "/pair-verify", length)) {
context->endpoint = HOMEKIT_ENDPOINT_PAIR_VERIFY;
} else if (!strncmp(data, "/pairings", length)) {
context->endpoint = HOMEKIT_ENDPOINT_PAIRINGS;
} else if (!strncmp(data, "/reset", length)) {
context->endpoint = HOMEKIT_ENDPOINT_RESET;
} else if (!strncmp(data, "/resource", length)) {
context->endpoint = HOMEKIT_ENDPOINT_RESOURCE;
}
} else if (parser->method == HTTP_PUT) {
if (!strncmp(data, "/characteristics", length)) {
context->endpoint = HOMEKIT_ENDPOINT_UPDATE_CHARACTERISTICS;
}
}
if (context->endpoint == HOMEKIT_ENDPOINT_UNKNOWN) {
char *url = strndup(data, length);
ERROR("Unknown endpoint: %s %s", http_method_str(parser->method), url);
free(url);
}
return 0;
}
int homekit_server_on_body(http_parser *parser, const char *data, size_t length) {
client_context_t *context = parser->data;
context->body = realloc(context->body, context->body_length + length + 1);
memcpy(context->body + context->body_length, data, length);
context->body_length += length;
context->body[context->body_length] = 0;
return 0;
}
int homekit_server_on_message_complete(http_parser *parser) {
client_context_t *context = parser->data;
switch(context->endpoint) {
case HOMEKIT_ENDPOINT_PAIR_SETUP: {
homekit_server_on_pair_setup(context, (const byte *)context->body, context->body_length);
break;
}
case HOMEKIT_ENDPOINT_PAIR_VERIFY: {
homekit_server_on_pair_verify(context, (const byte *)context->body, context->body_length);
break;
}
case HOMEKIT_ENDPOINT_IDENTIFY: {
homekit_server_on_identify(context);
break;
}
case HOMEKIT_ENDPOINT_GET_ACCESSORIES: {
homekit_server_on_get_accessories(context);
break;
}
case HOMEKIT_ENDPOINT_GET_CHARACTERISTICS: {
homekit_server_on_get_characteristics(context);
break;
}
case HOMEKIT_ENDPOINT_UPDATE_CHARACTERISTICS: {
homekit_server_on_update_characteristics(context, (const byte *)context->body, context->body_length);
break;
}
case HOMEKIT_ENDPOINT_PAIRINGS: {
homekit_server_on_pairings(context, (const byte *)context->body, context->body_length);
break;
}
case HOMEKIT_ENDPOINT_RESET: {
homekit_server_on_reset(context);
break;
}
case HOMEKIT_ENDPOINT_RESOURCE: {
homekit_server_on_resource(context);
break;
}
case HOMEKIT_ENDPOINT_UNKNOWN: {
DEBUG("Unknown endpoint");
send_404_response(context);
break;
}
}
if (context->endpoint_params) {
query_params_free(context->endpoint_params);
context->endpoint_params = NULL;
}
if (context->body) {
free(context->body);
context->body = NULL;
context->body_length = 0;
}
return 0;
}
static http_parser_settings homekit_http_parser_settings = {
.on_url = homekit_server_on_url,
.on_body = homekit_server_on_body,
.on_message_complete = homekit_server_on_message_complete,
};
static void homekit_client_process(client_context_t *context) {
int data_len = read(
context->socket,
context->data+context->data_available,
context->data_size-context->data_available
);
if (data_len == 0) {
context->disconnect = true;
return;
}
if (data_len < 0) {
if (errno != EAGAIN) {
CLIENT_ERROR(context, "Error reading data from socket (code %d). Disconnecting", errno);
context->disconnect = true;
}
return;
}
CLIENT_DEBUG(context, "Got %d incomming data", data_len);
byte *payload = (byte *)context->data;
size_t payload_size = (size_t)data_len;
byte *decrypted = NULL;
size_t decrypted_size = 0;
if (context->encrypted) {
CLIENT_DEBUG(context, "Decrypting data");
client_decrypt(context, context->data, data_len, NULL, &decrypted_size);
decrypted = malloc(decrypted_size);
int r = client_decrypt(context, context->data, data_len, decrypted, &decrypted_size);
if (r < 0) {
CLIENT_ERROR(context, "Invalid client data");
free(decrypted);
return;
}
context->data_available = data_len - r;
if (r && context->data_available) {
memmove(context->data, &context->data[r], context->data_available);
}
CLIENT_DEBUG(context, "Decrypted %d bytes, available %d", decrypted_size, context->data_available);
payload = decrypted;
payload_size = decrypted_size;
if (payload_size)
print_binary("Decrypted data", payload, payload_size);
} else {
context->data_available = 0;
}
current_client_context = context;
http_parser_execute(
context->parser, &homekit_http_parser_settings,
(char *)payload, payload_size
);
current_client_context = NULL;
CLIENT_DEBUG(context, "Finished processing");
if (decrypted) {
free(decrypted);
}
}
void homekit_server_close_client(homekit_server_t *server, client_context_t *context) {
CLIENT_INFO(context, "Closing client connection");
FD_CLR(context->socket, &server->fds);
// TODO: recalc server->max_fd ?
server->nfds--;
close(context->socket);
if (context->server->pairing_context && context->server->pairing_context->client == context) {
pairing_context_free(context->server->pairing_context);
context->server->pairing_context = NULL;
}
if (context->server->clients == context) {
context->server->clients = context->next;
} else {
client_context_t *c = context->server->clients;
while (c->next && c->next != context)
c = c->next;
if (c->next)
c->next = c->next->next;
}
homekit_accessories_clear_notify_callbacks(
context->server->config->accessories,
client_notify_characteristic,
context
);
HOMEKIT_NOTIFY_EVENT(server, HOMEKIT_EVENT_CLIENT_DISCONNECTED);
client_context_free(context);
}
client_context_t *homekit_server_accept_client(homekit_server_t *server) {
int s = accept(server->listen_fd, (struct sockaddr *)NULL, (socklen_t *)NULL);
if (s < 0)
return NULL;
if (server->nfds > HOMEKIT_MAX_CLIENTS) {
INFO("No more room for client connections (max %d)", HOMEKIT_MAX_CLIENTS);
close(s);
return NULL;
}
INFO("Got new client connection: %d", s);
const struct timeval rcvtimeout = { 10, 0 }; /* 10 second timeout */
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &rcvtimeout, sizeof(rcvtimeout));
const int yes = 1; /* enable sending keepalive probes for socket */
setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes));
const int idle = 180; /* 180 sec idle before start sending probes */
setsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
const int interval = 20; /* 30 sec between probes */
setsockopt(s, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
const int maxpkt = 5; /* Drop connection after 4 probes without response */
setsockopt(s, IPPROTO_TCP, TCP_KEEPCNT, &maxpkt, sizeof(maxpkt));
client_context_t *context = client_context_new();
context->server = server;
context->socket = s;
context->next = server->clients;
server->clients = context;
FD_SET(s, &server->fds);
server->nfds++;
if (s > server->max_fd)
server->max_fd = s;
HOMEKIT_NOTIFY_EVENT(server, HOMEKIT_EVENT_CLIENT_CONNECTED);
return context;
}
client_context_t *homekit_server_find_client_by_fd(homekit_server_t *server, int fd) {
client_context_t *context = server->clients;
while (context) {
if (context->socket == fd)
return context;
context = context->next;
}
return NULL;
}
void homekit_server_process_notifications(homekit_server_t *server) {
client_context_t *context = server->clients;
while (context) {
characteristic_event_t *event = NULL;
if (xQueueReceive(context->event_queue, &event, 0)) {
// Get and coalesce all client events
client_event_t *events_head = malloc(sizeof(client_event_t));
events_head->characteristic = event->characteristic;
homekit_value_copy(&events_head->value, &event->value);
events_head->next = NULL;
homekit_value_destruct(&event->value);
free(event);
client_event_t *events_tail = events_head;
while (xQueueReceive(context->event_queue, &event, 0)) {
client_event_t *e = events_head;
while (e) {
if (e->characteristic == event->characteristic)
break;
e = e->next;
}
if (e) {
homekit_value_destruct(&e->value);
} else {
e = malloc(sizeof(client_event_t));
e->characteristic = event->characteristic;
e->next = NULL;
events_tail->next = e;
events_tail = e;
}
homekit_value_copy(&e->value, &event->value);
homekit_value_destruct(&event->value);
free(event);
}
send_client_events(context, events_head);
client_event_t *e = events_head;
while (e) {
client_event_t *next = e->next;
homekit_value_destruct(&e->value);
free(e);
e = next;
}
}
context = context->next;
}
}
void homekit_server_close_clients(homekit_server_t *server) {
client_context_t *context = server->clients;
while (context) {
client_context_t *next = context->next;
if (context->disconnect)
homekit_server_close_client(server, context);
context = next;
}
}
static void homekit_run_server(homekit_server_t *server)
{
DEBUG("Staring HTTP server");
struct sockaddr_in serv_addr;
server->listen_fd = socket(AF_INET, SOCK_STREAM, 0);
memset(&serv_addr, '0', sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(PORT);
bind(server->listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
listen(server->listen_fd, 10);
FD_SET(server->listen_fd, &server->fds);
server->max_fd = server->listen_fd;
server->nfds = 1;
for (;;) {
fd_set read_fds;
memcpy(&read_fds, &server->fds, sizeof(read_fds));
struct timeval timeout = { 1, 0 }; /* 1 second timeout */
int triggered_nfds = select(server->max_fd + 1, &read_fds, NULL, NULL, &timeout);
if (triggered_nfds > 0) {
if (FD_ISSET(server->listen_fd, &read_fds)) {
homekit_server_accept_client(server);
triggered_nfds--;
}
client_context_t *context = server->clients;
while (context && triggered_nfds) {
//INFO("triggered_nfds %d", triggered_nfds);
if (FD_ISSET(context->socket, &read_fds)) {
//INFO("homekit_client_process");
homekit_client_process(context);
triggered_nfds--;
}
context = context->next;
}
//INFO("homekit_server_close_clients");
homekit_server_close_clients(server);
}
homekit_server_process_notifications(server);
}
server_free(server);
}
void homekit_setup_mdns(homekit_server_t *server) {
INFO("Configuring mDNS");
homekit_accessory_t *accessory = server->config->accessories[0];
homekit_service_t *accessory_info =
homekit_service_by_type(accessory, HOMEKIT_SERVICE_ACCESSORY_INFORMATION);
if (!accessory_info) {
ERROR("Invalid accessory declaration: no Accessory Information service");
return;
}
homekit_characteristic_t *name =
homekit_service_characteristic_by_type(accessory_info, HOMEKIT_CHARACTERISTIC_NAME);
if (!name) {
ERROR("Invalid accessory declaration: "
"no Name characteristic in AccessoryInfo service");
return;
}
homekit_characteristic_t *model =
homekit_service_characteristic_by_type(accessory_info, HOMEKIT_CHARACTERISTIC_MODEL);
if (!model) {
ERROR("Invalid accessory declaration: "
"no Model characteristic in AccessoryInfo service");
return;
}
char unique_name[65]={0};
strncpy(unique_name, name->value.string_value, sizeof(unique_name)-6);
unique_name[strlen(unique_name)]='-';
unique_name[strlen(unique_name)]=server->accessory_id[0];
unique_name[strlen(unique_name)]=server->accessory_id[1];
unique_name[strlen(unique_name)]=server->accessory_id[3];
unique_name[strlen(unique_name)]=server->accessory_id[4];
homekit_mdns_configure_init(unique_name, PORT);
// accessory model name (required)
homekit_mdns_add_txt("md", "%s", model->value.string_value);
// protocol version (required)
homekit_mdns_add_txt("pv", "1.0");
// device ID (required)
// should be in format XX:XX:XX:XX:XX:XX, otherwise devices will ignore it
homekit_mdns_add_txt("id", "%s", server->accessory_id);
// current configuration number (required)
homekit_mdns_add_txt("c#", "%d", server->config->config_number);
// current state number (required)
homekit_mdns_add_txt("s#", "1");
// feature flags (required if non-zero)
// bit 0 - supports HAP pairing. required for all HomeKit accessories
// bits 1-7 - reserved
homekit_mdns_add_txt("ff", "0");
// status flags
// bit 0 - not paired
// bit 1 - not configured to join WiFi
// bit 2 - problem detected on accessory
// bits 3-7 - reserved
homekit_mdns_add_txt("sf", "%d", (server->paired) ? 0 : 1);
// accessory category identifier
homekit_mdns_add_txt("ci", "%d", server->config->category);
if (server->config->setupId) {
DEBUG("Accessory Setup ID = %s", server->config->setupId);
size_t data_size = strlen(server->config->setupId) + strlen(server->accessory_id) + 1;
char *data = malloc(data_size);
snprintf(data, data_size, "%s%s", server->config->setupId, server->accessory_id);
data[data_size-1] = 0;
unsigned char shaHash[SHA512_DIGEST_SIZE];
wc_Sha512Hash((const unsigned char *)data, data_size-1, shaHash);
free(data);
unsigned char encodedHash[9];
memset(encodedHash, 0, sizeof(encodedHash));
word32 len = sizeof(encodedHash);
Base64_Encode_NoNl((const unsigned char *)shaHash, 4, encodedHash, &len);
homekit_mdns_add_txt("sh", "%s", encodedHash);
}
homekit_mdns_configure_finalize();
}
char *homekit_accessory_id_generate() {
char *accessory_id = malloc(18);
byte buf[6];
homekit_random_fill(buf, sizeof(buf));
snprintf(accessory_id, 18, "%02X:%02X:%02X:%02X:%02X:%02X",
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
INFO("Generated new accessory ID: %s", accessory_id);
return accessory_id;
}
ed25519_key *homekit_accessory_key_generate() {
ed25519_key *key = crypto_ed25519_generate();
if (!key) {
ERROR("Failed to generate accessory key");
return NULL;
}
INFO("Generated new accessory key");
return key;
}
void homekit_server_task(void *args) {
homekit_server_t *server = args;
INFO("Starting server");
int r = homekit_storage_init();
if (r == 0) {
server->accessory_id = homekit_storage_load_accessory_id();
server->accessory_key = homekit_storage_load_accessory_key();
}
if (!server->accessory_id || !server->accessory_key) {
server->accessory_id = homekit_accessory_id_generate();
homekit_storage_save_accessory_id(server->accessory_id);
server->accessory_key = homekit_accessory_key_generate();
homekit_storage_save_accessory_key(server->accessory_key);
} else {
INFO("Using existing accessory ID: %s", server->accessory_id);
}
pairing_iterator_t *pairing_it = homekit_storage_pairing_iterator();
pairing_t *pairing;
while ((pairing = homekit_storage_next_pairing(pairing_it))) {
if (pairing->permissions & pairing_permissions_admin) {
break;
}
pairing_free(pairing);
}
homekit_storage_pairing_iterator_free(pairing_it);
if (pairing) {
INFO("Found admin pairing with %s, disabling pair setup", pairing->device_id);
pairing_free(pairing);
server->paired = true;
}
homekit_mdns_init();
homekit_setup_mdns(server);
HOMEKIT_NOTIFY_EVENT(server, HOMEKIT_EVENT_SERVER_INITIALIZED);
homekit_run_server(server);
vTaskDelete(NULL);
}
#define ISDIGIT(x) isdigit((unsigned char)(x))
#define ISBASE36(x) (isdigit((unsigned char)(x)) || (x >= 'A' && x <= 'Z'))
void homekit_server_init(homekit_server_config_t *config) {
if (!config->accessories) {
ERROR("Error initializing HomeKit accessory server: "
"accessories are not specified");
return;
}
if (!config->password && !config->password_callback) {
ERROR("Error initializing HomeKit accessory server: "
"neither password nor password callback is specified");
return;
}
if (config->password) {
const char *p = config->password;
if (strlen(p) != 10 ||
!(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2]) && p[3] == '-' &&
ISDIGIT(p[4]) && ISDIGIT(p[5]) && p[6] == '-' &&
ISDIGIT(p[7]) && ISDIGIT(p[8]) && ISDIGIT(p[9]))) {
ERROR("Error initializing HomeKit accessory server: "
"invalid password format");
return;
}
}
if (config->setupId) {
const char *p = config->setupId;
if (strlen(p) != 4 ||
!(ISBASE36(p[0]) && ISBASE36(p[1]) && ISBASE36(p[2]) && ISBASE36(p[3]))) {
ERROR("Error initializing HomeKit accessory server: "
"invalid setup ID format");
return;
}
}
homekit_accessories_init(config->accessories);
if (!config->config_number) {
config->config_number = config->accessories[0]->config_number;
if (!config->config_number) {
config->config_number = 1;
}
}
if (!config->category) {
config->category = config->accessories[0]->category;
}
homekit_server_t *server = server_new();
server->config = config;
xTaskCreate(homekit_server_task, "HomeKit Server", SERVER_TASK_STACK, server, 1, NULL);
}
void homekit_server_restart() {
ERROR("Not Implemented for ESp32");
}
void homekit_server_reset() {
homekit_storage_reset();
}
bool homekit_is_paired() {
pairing_iterator_t *pairing_it = homekit_storage_pairing_iterator();
pairing_t *pairing;
while ((pairing = homekit_storage_next_pairing(pairing_it))) {
if (pairing->permissions & pairing_permissions_admin) {
break;
}
pairing_free(pairing);
};
homekit_storage_pairing_iterator_free(pairing_it);
bool paired = false;
if (pairing) {
paired = true;
pairing_free(pairing);
}
return paired;
}
int homekit_get_accessory_id(char *buffer, size_t size) {
char *accessory_id = homekit_storage_load_accessory_id();
if (!accessory_id)
return -2;
if (size < strlen(accessory_id) + 1)
return -1;
strncpy(buffer, accessory_id, size);
free(accessory_id);
return 0;
}
int homekit_get_setup_uri(const homekit_server_config_t *config, char *buffer, size_t buffer_size) {
static const char base36Table[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (buffer_size < 20)
return -1;
if (!config->password)
return -1;
// TODO: validate password in case it is run beffore server is started
if (!config->setupId)
return -1;
// TODO: validate setupID in case it is run beffore server is started
homekit_accessory_t *accessory = homekit_accessory_by_id(config->accessories, 1);
if (!accessory)
return -1;
uint32_t setup_code = 0;
for (const char *s = config->password; *s; s++)
if ISDIGIT(*s)
setup_code = setup_code * 10 + *s - '0';
uint64_t payload = 0;
payload <<= 4; // reserved 4 bits
payload <<= 8;
payload |= accessory->category & 0xff;
payload <<= 4;
payload |= 2; // flags (2=IP, 4=BLE, 8=IP_WAC)
payload <<= 27;
payload |= setup_code & 0x7fffffff;
strcpy(buffer, "X-HM://");
buffer += 7;
for (int i=8; i >= 0; i--) {
buffer[i] = base36Table[payload % 36];
payload /= 36;
}
buffer += 9;
strcpy(buffer, config->setupId);
buffer += 4;
buffer[0] = 0;
return 0;
}
#endif