Tasmota/lib/libesp32_div/ESP32-HomeKit/src/esp_hap_ip_services.c
2021-11-15 12:35:48 +01:00

1631 lines
58 KiB
C

/*
* ESPRESSIF MIT License
*
* Copyright (c) 2020 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
*
* Permission is hereby granted for use on ESPRESSIF SYSTEMS products only, in which case,
* it is free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include <json_parser.h>
#include <hap_platform_memory.h>
#include <esp_mfi_debug.h>
#include <esp_hap_main.h>
#include <esp_hap_pair_setup.h>
#include <esp_hap_pair_verify.h>
#include <esp_hap_pairings.h>
#include <esp_hap_network_io.h>
#include <esp_hap_secure_message.h>
#include <esp_hap_acc.h>
#include <esp_hap_serv.h>
#include <esp_hap_char.h>
#include <esp_hap_mdns.h>
#include <esp_hap_wac.h>
#include <esp_hap_wifi.h>
#include <esp_hap_database.h>
#include <esp_mfi_base64.h>
#include <esp_timer.h>
#include <hexdump.h>
#include <lwip/sockets.h>
#include <esp_http_server.h>
#include <hap_platform_httpd.h>
#include <hap_platform_os.h>
#include <esp_hap_ip_services.h>
#include <json_generator.h> //???
#ifdef ESP_MFI_DEBUG_ENABLE
#define ESP_MFI_DEBUG_PLAIN(fmt, ...) \
if (http_debug) { \
printf("\e[1;35m" fmt "\e[0m", ##__VA_ARGS__); \
}
#else /* ESP_MFI_DEBUG_ENABLE */
#define ESP_MFI_DEBUG_PLAIN(fmt, ...)
#endif /* ESP_MFI_DEBUG_ENABLE */
static bool http_debug;
int hap_http_session_not_authorized(httpd_req_t *req)
{
char buf[50];
httpd_resp_set_status(req, "470 Connection Authorization Required");
httpd_resp_set_type(req, "application/hap+json");
snprintf(buf, sizeof(buf),"{\"status\":-70401}");
httpd_resp_send(req, buf, strlen(buf));
return HAP_SUCCESS;
}
int hap_httpd_get_data(httpd_req_t *req, char *buffer, int len)
{
int read_len = 0;
while (read_len < len) {
int tmp_len = httpd_req_recv(req, buffer + read_len, len - read_len);
if (tmp_len <= 0) {
return read_len;
}
read_len += tmp_len;
}
return read_len;
}
static int hap_http_pair_setup_handler(httpd_req_t *req)
{
uint8_t buf[1200];
int ret, ret1, outlen;
void *ctx = (hap_secure_session_t *)hap_platform_httpd_get_sess_ctx(req);
int fd = httpd_req_to_sockfd(req);
ESP_MFI_DEBUG_PLAIN("Socket fd: %d; HTTP Request %s %s\n", httpd_req_to_sockfd(req), hap_platform_httpd_get_req_method(req), hap_platform_httpd_get_req_uri(req));
if (!ctx) {
if (hap_pair_setup_context_init(fd, &ctx, buf, sizeof(buf), &outlen) == HAP_SUCCESS) {
hap_platform_httpd_set_sess_ctx(req, ctx, hap_pair_setup_ctx_clean, true);
} else {
httpd_resp_set_type(req, "application/pairing+tlv8");
httpd_resp_send(req, (char *)buf, outlen);
return HAP_SUCCESS;
}
}
int data_len = httpd_req_recv(req, (char *)buf, sizeof(buf));
ret = hap_pair_setup_process(&ctx, buf, data_len, sizeof(buf), &outlen);
httpd_resp_set_type(req, "application/pairing+tlv8");
ret1 = httpd_resp_send(req, (char *)buf, outlen);
if (ret != HAP_SUCCESS) {
hap_pair_setup_ctx_clean(ctx);
ctx = NULL;
} else {
/* A pair verify function is called here, because for Software Token Authentication,
* secure session keys are generated at the step M4 of Pair Setup, and there onwards,
* the behavior is like a pair verified session.
*/
if (hap_pair_verify_get_state(ctx) == STATE_VERIFIED) {
/* Saving socket fd since it will later be required for
* event notifications.
*/
((hap_secure_session_t *)ctx)->conn_identifier = fd;
hap_platform_httpd_set_sess_ctx(req, ctx, hap_free_session, true);
httpd_sess_set_send_override(hap_priv.server, fd, hap_httpd_send);
httpd_sess_set_recv_override(hap_priv.server, fd, hap_httpd_recv);
}
}
/* Context will be NULL, either if there was an error and a cleanup was required,
* or if the pair_setup_process cleared it after successful pairing.
* For both the cases, we will set the sess_ctx and free_ctx to NULL
*/
if (!ctx) {
hap_platform_httpd_set_sess_ctx(req, NULL, NULL, true);
}
return ret1;
}
static struct httpd_uri hap_pair_setup = {
.uri = "/pair-setup",
.method = HTTP_POST,
.handler = hap_http_pair_setup_handler,
};
static int hap_http_pair_verify_handler(httpd_req_t *req)
{
uint8_t buf[512];
int ret, outlen;
void *ctx = hap_platform_httpd_get_sess_ctx(req);
ESP_MFI_DEBUG_PLAIN("Socket fd: %d; HTTP Request %s %s\n", httpd_req_to_sockfd(req), hap_platform_httpd_get_req_method(req), hap_platform_httpd_get_req_uri(req));
if (!ctx) {
if (hap_pair_verify_context_init(&ctx, buf, sizeof(buf), &outlen) == HAP_SUCCESS) {
hap_platform_httpd_set_sess_ctx(req, ctx, NULL, true);
}
}
int data_len = httpd_req_recv(req, (char *)buf, sizeof(buf));
ret = hap_pair_verify_process(&ctx, buf, data_len, sizeof(buf), &outlen);
httpd_resp_set_type(req, "application/pairing+tlv8");
int ret1 = httpd_resp_send(req, (char *)buf, outlen);
if (ret == HAP_SUCCESS) {
if (hap_pair_verify_get_state(ctx) == STATE_VERIFIED) {
/* Saving socket fd since it will later be required for
* event notifications.
*/
int fd = httpd_req_to_sockfd(req);
((hap_secure_session_t *)ctx)->conn_identifier = fd;
struct timeval timeout;
timeout.tv_sec = hap_priv.cfg.recv_timeout;
timeout.tv_usec = 0;
if (setsockopt (fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
sizeof(timeout)) < 0) {
ESP_MFI_DEBUG(ESP_MFI_DEBUG_ERR, "setsockopt on pair verified socket failed for SO_RCVTIMEO");
}
timeout.tv_sec = hap_priv.cfg.send_timeout;
timeout.tv_usec = 0;
if (setsockopt (fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
sizeof(timeout)) < 0) {
ESP_MFI_DEBUG(ESP_MFI_DEBUG_ERR, "setsockopt on pair verified socket failed for SO_SNDTIMEO");
}
// ----
const int s = fd;
const int yes = 1; /* enable sending keepalive probes for socket */
setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes));
const int idle = 15; /* 180 sec idle before start sending probes */
setsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
const int interval = 5; /* 30 sec between probes */
setsockopt(s, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
const int maxpkt = 3; /* Drop connection after 4 probes without response */
setsockopt(s, IPPROTO_TCP, TCP_KEEPCNT, &maxpkt, sizeof(maxpkt));
// ----
hap_platform_httpd_set_sess_ctx(req, ctx, hap_free_session, true);
httpd_sess_set_send_override(hap_priv.server, fd, hap_httpd_send);
httpd_sess_set_recv_override(hap_priv.server, fd, hap_httpd_recv);
hap_secure_session_t* session = (hap_secure_session_t *)ctx;
int socket = session->conn_identifier;
//struct sockaddr_storage addr;
struct sockaddr_in6 addr;
socklen_t len = sizeof addr;
getpeername(fd, (struct sockaddr*)&addr, &len);
/*
struct sockaddr_in *sockaddr = (struct sockaddr_in *)&addr;
in_addr_t a = sockaddr->sin_addr.s_addr;
union {
uint8_t ui8[4];
uint32_t ui32;
}ip;
ip.ui32 = (uint32_t)a;
*/
//uint16_t port = ntohs(sockaddr->sin6_port);
uint16_t port = addr.sin6_port;
printf("---- STATE_VERIFIED ----\n");
//printf("ip32: %u\n", a);
//printf("fd: %d, %u.%u.%u.%u:%u \n", socket,
//ip.ui8[0], ip.ui8[1], ip.ui8[2], ip.ui8[3], port);
//https://github.com/espressif/esp-idf/issues/4863
printf("fd: %d, remote: %s:%u\n", fd, inet_ntoa(addr.sin6_addr.un.u32_addr[3]), port);
printf("ctrl: %s\n", session->ctrl->info.id);
printf("------------------------\n");
}
}
return ret1;
}
static struct httpd_uri hap_pair_verify = {
.uri = "/pair-verify",
.method = HTTP_POST,
.handler = hap_http_pair_verify_handler,
};
static int hap_add_char_val_json(hap_char_format_t format, const char *key,
hap_val_t *val, json_gen_str_t *jptr)
{
switch (format) {
case HAP_CHAR_FORMAT_BOOL : {
json_gen_obj_set_bool(jptr, key, val->b);
break;
}
case HAP_CHAR_FORMAT_UINT8:
case HAP_CHAR_FORMAT_UINT16:
case HAP_CHAR_FORMAT_UINT32:
case HAP_CHAR_FORMAT_INT: {
json_gen_obj_set_int(jptr, key, val->i);
break;
}
case HAP_CHAR_FORMAT_FLOAT : {
json_gen_obj_set_float(jptr, key, val->f);
break;
}
case HAP_CHAR_FORMAT_STRING : {
if (val->s) {
json_gen_obj_set_string(jptr, key, val->s);
} else {
json_gen_obj_set_null(jptr, key);
}
break;
}
case HAP_CHAR_FORMAT_DATA:
case HAP_CHAR_FORMAT_TLV8: {
if (val->d.buf) {
json_gen_obj_start_long_string(jptr, key, NULL);
uint8_t *buf = val->d.buf;
uint32_t buflen = val->d.buflen;
char tmp[100];
while (buflen) {
int tmp_len = sizeof(tmp);
if (buflen > 60) {
esp_mfi_base64_encode((char *)buf, 60, tmp, tmp_len, &tmp_len);
buflen -= 60;
buf += 60;
} else {
esp_mfi_base64_encode((char *)buf, buflen, tmp, tmp_len, &tmp_len);
buflen -= buflen;
}
tmp[tmp_len] = 0;
json_gen_add_to_long_string(jptr, tmp);
}
json_gen_end_long_string(jptr);
} else {
json_gen_obj_set_null(jptr, key);
}
break;
}
default :
break;
}
return HAP_SUCCESS;
}
static int hap_add_char_format_json(__hap_char_t *hc, json_gen_str_t *jptr)
{
switch (hc->format) {
case HAP_CHAR_FORMAT_UINT8:
return json_gen_obj_set_string(jptr, "format", "uint8");
case HAP_CHAR_FORMAT_UINT16:
return json_gen_obj_set_string(jptr, "format", "uint16");
case HAP_CHAR_FORMAT_UINT32:
return json_gen_obj_set_string(jptr, "format", "uint32");
case HAP_CHAR_FORMAT_INT:
return json_gen_obj_set_string(jptr, "format", "int");
case HAP_CHAR_FORMAT_BOOL:
return json_gen_obj_set_string(jptr, "format", "bool");
case HAP_CHAR_FORMAT_STRING:
return json_gen_obj_set_string(jptr, "format", "string");
case HAP_CHAR_FORMAT_FLOAT:
return json_gen_obj_set_string(jptr, "format", "float");
case HAP_CHAR_FORMAT_DATA:
return json_gen_obj_set_string(jptr, "format", "data");
case HAP_CHAR_FORMAT_TLV8:
return json_gen_obj_set_string(jptr, "format", "tlv8");
default:
break;
}
return HAP_SUCCESS;
}
static int hap_add_char_type(__hap_char_t *hc, json_gen_str_t *jptr)
{
return json_gen_obj_set_string(jptr, "type", (char *)hc->type_uuid);
}
static int hap_add_char_meta(__hap_char_t *hc, json_gen_str_t *jptr)
{
hap_add_char_format_json(hc, jptr);
if (hc->constraint_flags & HAP_CHAR_MIN_FLAG)
hap_add_char_val_json(hc->format, "minValue", &hc->min, jptr);
if (hc->constraint_flags & HAP_CHAR_MAX_FLAG)
hap_add_char_val_json(hc->format, "maxValue", &hc->max, jptr);
if (hc->constraint_flags & HAP_CHAR_STEP_FLAG)
hap_add_char_val_json(hc->format, "minStep", &hc->step, jptr);
/* maxLen and maxDataLen are constraints for "string" and "data" format
* of characteristics, respectively. However, the constraints themselves
* are integers. So, we pass the format as HAP_CHAR_FORMAT_INT
*/
if (hc->constraint_flags & HAP_CHAR_MAXLEN_FLAG)
hap_add_char_val_json(HAP_CHAR_FORMAT_INT, "maxLen", &hc->max, jptr);
if (hc->constraint_flags & HAP_CHAR_MAXDATALEN_FLAG)
hap_add_char_val_json(HAP_CHAR_FORMAT_INT, "maxDataLen", &hc->max, jptr);
if (hc->description)
json_gen_obj_set_string(jptr, "description", hc->description);
if (hc->unit)
json_gen_obj_set_string(jptr, "unit", hc->unit);
return HAP_SUCCESS;
}
static int hap_add_char_perms(__hap_char_t *hc, json_gen_str_t *jptr)
{
json_gen_push_array(jptr, "perms");
if (hc->permission & HAP_CHAR_PERM_PR)
json_gen_arr_set_string(jptr, "pr");
if (hc->permission & HAP_CHAR_PERM_PW)
json_gen_arr_set_string(jptr, "pw");
if (hc->permission & HAP_CHAR_PERM_EV)
json_gen_arr_set_string(jptr, "ev");
if (hc->permission & HAP_CHAR_PERM_AA)
json_gen_arr_set_string(jptr, "aa");
if (hc->permission & HAP_CHAR_PERM_TW)
json_gen_arr_set_string(jptr, "tw");
if (hc->permission & HAP_CHAR_PERM_HD)
json_gen_arr_set_string(jptr, "hd");
json_gen_pop_array(jptr);
return HAP_SUCCESS;
}
static int hap_add_char_ev(__hap_char_t *hc, json_gen_str_t *jptr, uint8_t session_index)
{
if (hap_char_is_ctrl_subscribed((hap_char_t *)hc, session_index)) {
return json_gen_obj_set_bool(jptr, "ev", true);
} else {
return json_gen_obj_set_bool(jptr, "ev", false);
}
}
static int hap_add_char_valid_vals(__hap_char_t *hc, json_gen_str_t *jptr)
{
if (hc->valid_vals) {
json_gen_push_array(jptr, "valid-values");
int i;
for (i = 0; i < hc->valid_vals_cnt; i++) {
json_gen_arr_set_int(jptr, hc->valid_vals[i]);
}
json_gen_pop_array(jptr);
}
if (hc->valid_vals_range) {
json_gen_push_array(jptr, "valid-values-range");
json_gen_arr_set_int(jptr, hc->valid_vals_range[0]);
json_gen_arr_set_int(jptr, hc->valid_vals_range[1]);
json_gen_pop_array(jptr);
}
return HAP_SUCCESS;
}
static int hap_prepare_char_db(__hap_char_t *hc, json_gen_str_t *jptr, int session_index)
{
json_gen_start_object(jptr);
json_gen_obj_set_int(jptr, "iid", hc->iid);
/* If the Update API has not been called from the service read routine,
* reset the owner controller value.
* Else, the controller will miss the next notification.
*/
if (!hc->update_called) {
hc->owner_ctrl = 0;
}
hc->update_called = false;
if (hc->permission & HAP_CHAR_PERM_PR) {
if (hc->permission & HAP_CHAR_PERM_SPECIAL_READ) {
json_gen_obj_set_null(jptr, "value");
} else {
hap_add_char_val_json(hc->format, "value", &hc->val, jptr);
}
}
hap_add_char_type(hc, jptr);
hap_add_char_perms(hc, jptr);
hap_add_char_ev(hc, jptr, session_index);
hap_add_char_meta(hc, jptr);
hap_add_char_valid_vals(hc, jptr);
json_gen_end_object(jptr);
return HAP_SUCCESS;
}
static int hap_prepare_serv_db(__hap_serv_t *hs, json_gen_str_t *jptr, int session_index)
{
json_gen_start_object(jptr);
json_gen_obj_set_int(jptr, "iid", hs->iid);
json_gen_obj_set_string(jptr, "type", hs->type_uuid);
if (hs->hidden)
json_gen_obj_set_bool(jptr, "hidden", "true");
if (hs->primary)
json_gen_obj_set_bool(jptr, "primary", "true");
if (hs->linked_servs) {
hap_linked_serv_t *linked = hs->linked_servs;
json_gen_push_array(jptr, "linked");
while (linked) {
json_gen_arr_set_int(jptr, ((__hap_serv_t *)linked->hs)->iid);
linked = linked->next;
}
json_gen_pop_array(jptr);
}
json_gen_push_array(jptr, "characteristics");
int char_cnt = 0;
hap_char_t *hc;
for (hc = hap_serv_get_first_char((hap_serv_t *)hs); hc; hc = hap_char_get_next(hc)) {
if (((__hap_char_t *)hc)->permission & HAP_CHAR_PERM_PR) {
char_cnt++;
}
}
if (char_cnt) {
hap_read_data_t *read_arr = hap_platform_memory_calloc(char_cnt, sizeof(hap_read_data_t));
if (!read_arr) {
return HAP_FAIL;
}
hap_status_t *status_codes = hap_platform_memory_calloc(char_cnt, sizeof(hap_status_t));
if (!status_codes) {
hap_platform_memory_free(read_arr);
return HAP_FAIL;
}
/* Create an array of characteristics to read, and then read them in one go */
char_cnt = 0;
for (hc = hap_serv_get_first_char((hap_serv_t *)hs); hc; hc = hap_char_get_next(hc)) {
if (((__hap_char_t *)hc)->permission & HAP_CHAR_PERM_PR) {
hap_char_set_owner_ctrl(hc, session_index);
((__hap_char_t *)hc)->update_called = false;
read_arr[char_cnt].hc = hc;
status_codes[char_cnt] = HAP_STATUS_SUCCESS;
read_arr[char_cnt].status = &status_codes[char_cnt];
char_cnt++;
}
}
hs->bulk_read(&read_arr[0], char_cnt, hs->priv, NULL);
hap_platform_memory_free(read_arr);
hap_platform_memory_free(status_codes);
}
for (hc = hap_serv_get_first_char((hap_serv_t *)hs); hc; hc = hap_char_get_next(hc)) {
hap_prepare_char_db((__hap_char_t *)hc, jptr, session_index);
}
json_gen_pop_array(jptr);
json_gen_end_object(jptr);
return HAP_SUCCESS;
}
static int hap_prepare_acc_db(__hap_acc_t *ha, json_gen_str_t *jptr, int session_index)
{
json_gen_start_object(jptr);
json_gen_obj_set_int(jptr, "aid", ha->aid);
json_gen_push_array(jptr, "services");
hap_serv_t *hs;
for (hs = hap_acc_get_first_serv((hap_acc_t *)ha); hs; hs = hap_serv_get_next(hs)) {
hap_prepare_serv_db((__hap_serv_t *)hs, jptr, session_index);
}
json_gen_pop_array(jptr);
json_gen_end_object(jptr);
return HAP_SUCCESS;
}
static int hap_prepare_json_database(char *buf, int bufsize, json_gen_flush_cb_t flush_cb, httpd_req_t *req)
{
if (!req) {
return HAP_FAIL;
}
hap_secure_session_t *session = (hap_secure_session_t *)hap_platform_httpd_get_sess_ctx(req);
if (!session) {
return HAP_FAIL;
}
json_gen_str_t jstr;
json_gen_str_start(&jstr, buf, bufsize, flush_cb, req);
json_gen_start_object(&jstr);
json_gen_push_array(&jstr, "accessories");
hap_acc_t *ha;
for (ha = hap_get_first_acc(); ha; ha = hap_acc_get_next(ha)) {
hap_prepare_acc_db((__hap_acc_t *)ha, &jstr, hap_get_ctrl_session_index(session));
}
json_gen_pop_array(&jstr);
json_gen_end_object(&jstr);
json_gen_str_end(&jstr);
return HAP_SUCCESS;
}
static void hap_http_json_flush_chunk(char *data, void *priv)
{
ESP_MFI_DEBUG_PLAIN("%s", data);
httpd_resp_send_chunk((httpd_req_t *)priv, data, strlen(data));
}
static int hap_http_get_accessories(httpd_req_t *req)
{
char buf[1000];
ESP_MFI_DEBUG_PLAIN("Socket fd: %d; HTTP Request %s %s\n", httpd_req_to_sockfd(req), hap_platform_httpd_get_req_method(req), hap_platform_httpd_get_req_uri(req));
hap_secure_session_t *session = (hap_secure_session_t *)hap_platform_httpd_get_sess_ctx(req);
if (!hap_is_req_secure(session)) {
return hap_http_session_not_authorized(req);
}
httpd_resp_set_type(req, "application/hap+json");
ESP_MFI_DEBUG_PLAIN("Generating HTTP Response\n");
/* Using chunked encoding since the response can be large, especially for bridges */
hap_prepare_json_database(buf, sizeof(buf), hap_http_json_flush_chunk, req);
/* This indicates the last chunk */
httpd_resp_send_chunk(req, NULL, 0);
ESP_MFI_DEBUG_PLAIN("\n");
hap_report_event(HAP_EVENT_GET_ACC_COMPLETED, NULL, 0);
return HAP_SUCCESS;
}
static struct httpd_uri hap_accessories = {
.uri = "/accessories",
.method = HTTP_GET,
.handler = hap_http_get_accessories,
};
static void hap_set_char_report_status(bool *include_status, json_gen_str_t *jstr,
int aid, int iid, int status)
{
if (!*include_status) {
json_gen_start_object(jstr);
json_gen_push_array(jstr, "characteristics");
*include_status = true;
}
json_gen_start_object(jstr);
json_gen_obj_set_int(jstr, "aid", aid);
json_gen_obj_set_int(jstr, "iid", iid);
json_gen_obj_set_int(jstr, "status", status);
json_gen_end_object(jstr);
}
static int hap_http_handle_set_char(jparse_ctx_t *jctx, char *outbuf, int buf_size,
httpd_req_t *req)
{
int cnt = 0, char_cnt = 0, i;
bool include_status = false;
uint64_t pid;
bool valid_tw = false;
bool req_tw = false;
hap_secure_session_t *session = (hap_secure_session_t *)hap_platform_httpd_get_sess_ctx(req);
if (!session)
return HAP_FAIL;
int64_t cur_time = esp_timer_get_time() / 1000;
int64_t prepare_time = session->prepare_time;
if (prepare_time) {
/* If prepare time is non zero, it means that a prepare was received
* before this request, and so this write needs to be timed write
*/
req_tw = true;
/* Reset prepare_time to 0, since a prepare is valid only for the immediate
* following write
*/
session->prepare_time = 0;
}
if (json_obj_get_int64(jctx, "pid", (int64_t *)&pid) == OS_SUCCESS) {
/* If the pid value is present, this must be a timed write.
* However, if there was no preceding prepare, the check below will
* fail (as ttl will be 0) and appropriate error will be reported subsequently
*/
req_tw = true;
if ((pid == session->pid) && ((cur_time - prepare_time) <= session->ttl)) {
valid_tw = true;
}
}
/* Resetting the values so that the session is ready for next prepare or write */
session->pid = 0;
session->ttl = 0;
json_obj_get_array(jctx, "characteristics", &cnt);
if (cnt <= 0)
return HAP_FAIL;
hap_write_data_t *write_arr = hap_platform_memory_calloc(cnt, sizeof(hap_write_data_t));
hap_status_t *status_arr = hap_platform_memory_calloc(cnt, sizeof(hap_status_t));
if (!write_arr || !status_arr)
goto set_char_end;
json_gen_str_t jstr;
json_gen_str_start(&jstr, outbuf, buf_size, hap_http_json_flush_chunk, req);
/* Dummy get, so that the loop can start by leaving the previous
* object and getting newer one
*/
json_arr_get_object(jctx, 0);
/* Loop through all characteristic objects {aid,iid,value}, handle
* errors if any, and if there are no errors, put the characteristic
* pointer and value in an array (with char_cnt)
*/
for (i = 0; i < cnt; i++) {
/* Leave the previous object and get a newer one from the array */
json_arr_leave_object(jctx);
int aid = 0, iid = 0;
json_arr_get_object(jctx, i);
json_obj_get_int(jctx, "aid", &aid);
json_obj_get_int(jctx, "iid", &iid);
hap_acc_t *ha = hap_acc_get_by_aid(aid);
__hap_char_t *hc = (__hap_char_t *)hap_acc_get_char_by_iid(ha, iid);
if (!ha || !hc) {
hap_set_char_report_status(&include_status, &jstr,
aid, iid, HAP_STATUS_RES_ABSENT);
continue;
}
/* If the previous request was a prepare, but the current
* one was not a valid timed write, report error.
*/
if (req_tw == true && valid_tw == false) {
hap_set_char_report_status(&include_status, &jstr,
aid, iid, HAP_STATUS_VAL_INVALID);
continue;
}
/* For characteristic that require a Mandatory Timed Write, return
* error if this write is not a valid timed write
*/
if (hc->permission & HAP_CHAR_PERM_TW) {
if (valid_tw == false) {
hap_set_char_report_status(&include_status, &jstr,
aid, iid, HAP_STATUS_VAL_INVALID);
continue;
}
}
/* Check if this write is just to enable/disable event notifications.
* This is valid even for read-only characteristics that support event
* notifications (like sensor readings), so we do not check the HAP_CHAR_PERM_PW
* here.
*/
bool ev;
if (json_obj_get_bool(jctx, "ev", &ev) == HAP_SUCCESS) {
if (hc->permission & HAP_CHAR_PERM_EV) {
int index = hap_get_ctrl_session_index(session);
hap_char_manage_notification((hap_char_t *)hc, index, ev);
ESP_MFI_DEBUG(ESP_MFI_DEBUG_INFO, "Events %s for aid=%d iid=%d",
ev ? "Enabled" : "Disabled", aid, iid);
} else {
hap_set_char_report_status(&include_status, &jstr,
aid, iid, HAP_STATUS_NO_NOTIF);
}
continue;
}
/* Check if the characteristic has write permission */
if (!(hc->permission & HAP_CHAR_PERM_PW)) {
hap_set_char_report_status(&include_status, &jstr,
aid, iid, HAP_STATUS_WR_ON_RDONLY);
continue;
}
/* Check if the characteristic needs Authorization Data.
* Actual authData value will be read later.
*/
if (hc->permission & HAP_CHAR_PERM_AA) {
int tmp_len;
if (json_obj_get_strlen(jctx, "authData", &tmp_len) != HAP_SUCCESS) {
hap_set_char_report_status(&include_status, &jstr,
aid, iid, HAP_STATUS_INSUFFICIENT_AUTH);
continue;
}
}
/* If there is no write routine registered, there is no point of having
* this write request. Return an error.
*/
if (!((__hap_serv_t *)(hap_char_get_parent((hap_char_t *)hc)))->write_cb) {
hap_set_char_report_status(&include_status, &jstr,
aid, iid, HAP_STATUS_VAL_INVALID);
continue;
}
hap_auth_data_t auth_data = {
.data = NULL,
.len = 0,
};
hap_val_t val;
int json_ret = HAP_FAIL;
switch (hc->format) {
case HAP_CHAR_FORMAT_BOOL:
json_ret = json_obj_get_bool(jctx, "value", &val.b);
break;
case HAP_CHAR_FORMAT_UINT8:
case HAP_CHAR_FORMAT_UINT16:
case HAP_CHAR_FORMAT_UINT32:
case HAP_CHAR_FORMAT_INT:
json_ret = json_obj_get_int(jctx, "value", &val.i);
break;
case HAP_CHAR_FORMAT_FLOAT:
json_ret = json_obj_get_float(jctx, "value", &val.f);
break;
case HAP_CHAR_FORMAT_STRING: {
int str_len = 0;
json_ret = json_obj_get_strlen(jctx, "value", &str_len);
if (json_ret == HAP_SUCCESS) {
/* Increment string length, for NULL termination byte */
str_len++;
val.s = hap_platform_memory_calloc(str_len, 1);
if (!val.s) {
hap_set_char_report_status(&include_status, &jstr,
aid, iid, HAP_STATUS_OO_RES);
continue;
}
json_obj_get_string(jctx, "value", val.s, str_len);
}
break;
}
case HAP_CHAR_FORMAT_DATA:
case HAP_CHAR_FORMAT_TLV8: {
int str_len = 0;
json_ret = json_obj_get_strlen(jctx, "value", &str_len);
if (json_ret == HAP_SUCCESS) {
val.d.buf = hap_platform_memory_calloc(1, str_len + 1);
if (!val.d.buf) {
hap_set_char_report_status(&include_status, &jstr,
aid, iid, HAP_STATUS_OO_RES);
continue;
}
val.d.buflen = str_len + 1;
json_obj_get_string(jctx, "value", (char *)val.d.buf, val.d.buflen);
if (esp_mfi_base64_decode((const char *)val.d.buf, strlen((char *)val.d.buf),
(char *)val.d.buf, val.d.buflen, (int *)&val.d.buflen) != 0) {
hap_platform_memory_free(val.d.buf);
hap_set_char_report_status(&include_status, &jstr,
aid, iid, HAP_STATUS_VAL_INVALID);
continue;
}
}
break;
}
default:
json_ret = HAP_FAIL;
}
if (json_ret != HAP_SUCCESS) {
hap_set_char_report_status(&include_status, &jstr,
aid, iid, HAP_STATUS_VAL_INVALID);
continue;
}
/* Check if the value is within constraints */
if (hap_char_check_val_constraints(hc, &val) != HAP_SUCCESS) {
hap_set_char_report_status(&include_status, &jstr,
aid, iid, HAP_STATUS_VAL_INVALID);
continue;
}
if (json_obj_get_strlen(jctx, "authData", &auth_data.len) == HAP_SUCCESS) {
auth_data.data = hap_platform_memory_calloc(1, auth_data.len + 1);
json_obj_get_string(jctx, "authData", (char *)auth_data.data, auth_data.len + 1);
esp_mfi_base64_decode((const char *)auth_data.data, auth_data.len, (char *)auth_data.data, auth_data.len + 1, &auth_data.len);
}
bool remote = false;
json_obj_get_bool(jctx, "remote", &remote);
int index = hap_get_ctrl_session_index(session);
hap_char_set_owner_ctrl((hap_char_t *)hc, index);
/* No errors in the object data itself. Save the characteristic
* pointer and value, to be used later
*/
write_arr[char_cnt].hc = (hap_char_t *)hc;
write_arr[char_cnt].val = val;
write_arr[char_cnt].auth_data = auth_data;
write_arr[char_cnt].remote = remote;
write_arr[char_cnt].status = &status_arr[char_cnt];
char_cnt++;
}
if (!char_cnt)
goto set_char_end;
/* The logic here is to loop through all the saved characteristic
* pointers, and invoke a single write callback for all consecutive
* characteristics of the same service.
* The write callback will be invoked if the service changes or
* if the last characteristic in the array is reached
*/
int hs_index = 0;
bool write_err = false;
__hap_serv_t *hs = (__hap_serv_t *)hap_char_get_parent(write_arr[0].hc);
/* The counter here will go till char_cnt instead of char_cnt - 1.
* When i == char_cnt, it will mean that all elements in the array
* have been looped through.
* So, last iteration will invoke the write callback for the last
* set of characteritics.
*/
for (i = 0; i <= char_cnt; i++) {
if ((i < char_cnt) && ((hap_serv_t *)hs == hap_char_get_parent(write_arr[i].hc)))
continue;
else {
/* Passing the pointers to the first elements of the array
* for a given service (indicated by hs_index).
* Number of elements of the array are indicated by
* i - hs_index
*/
if (hs->write_cb(&write_arr[hs_index], i - hs_index,
hs->priv, hap_platform_httpd_get_sess_ctx(req)) != HAP_SUCCESS)
write_err = true;
if (i < char_cnt) {
hs = (__hap_serv_t *)hap_char_get_parent(write_arr[i].hc);
hs_index = i;
}
}
}
if (write_err || include_status) {
for (i = 0; i < char_cnt; i++) {
/* TODO: The code to get aid looks complex. Simplify */
hap_set_char_report_status(&include_status, &jstr,
((__hap_acc_t *)hap_serv_get_parent(hap_char_get_parent(write_arr[i].hc)))->aid,
((__hap_char_t *)(write_arr[i].hc))->iid, *write_arr[i].status);
}
}
int ret = HAP_SUCCESS;
set_char_end:
if (include_status) {
json_gen_pop_array(&jstr);
json_gen_end_object(&jstr);
json_gen_str_end(&jstr);
ret = HAP_FAIL;
}
if (write_arr) {
for (i = 0; i < char_cnt; i++) {
if (((__hap_char_t *)write_arr[i].hc)->format == HAP_CHAR_FORMAT_STRING) {
if (write_arr[i].val.s) {
hap_platform_memory_free(write_arr[i].val.s);
}
} else if ((((__hap_char_t *)write_arr[i].hc)->format == HAP_CHAR_FORMAT_DATA) ||
(((__hap_char_t *)write_arr[i].hc)->format == HAP_CHAR_FORMAT_TLV8)) {
hap_platform_memory_free(write_arr[i].val.d.buf);
}
if (write_arr[i].auth_data.data) {
hap_platform_memory_free(write_arr[i].auth_data.data);
}
}
}
if (write_arr)
hap_platform_memory_free(write_arr);
if (status_arr)
hap_platform_memory_free(status_arr);
return ret;
}
static int hap_http_put_characteristics(httpd_req_t *req)
{
char stack_inbuf[512] = {0};
char outbuf[512] = {0};
char *heap_inbuf = NULL;
char *inbuf = stack_inbuf;
ESP_MFI_DEBUG_PLAIN("Socket fd: %d; HTTP Request %s %s\n", httpd_req_to_sockfd(req), hap_platform_httpd_get_req_method(req), hap_platform_httpd_get_req_uri(req));
hap_secure_session_t *session = (hap_secure_session_t *)hap_platform_httpd_get_sess_ctx(req);
if (!hap_is_req_secure(session)) {
return hap_http_session_not_authorized(req);
}
/* If received content is larger than the buffer on stack, allocate one from heap.
* This will mostly be required only in case of bridges, wherein there could be a request to
* control all/many accessories at once.
*/
int content_len = hap_platform_httpd_get_content_len(req);
if (content_len > sizeof(stack_inbuf)) {
heap_inbuf = hap_platform_memory_calloc(content_len + 1, 1); /* Allocating an extra byte for NULL termination */
if (!heap_inbuf) {
ESP_MFI_DEBUG(ESP_MFI_DEBUG_ERR, "Failed to read HTTPD Data");
httpd_resp_set_status(req, HTTPD_500);
return httpd_resp_send(req, NULL, 0);
}
ESP_MFI_DEBUG(ESP_MFI_DEBUG_INFO, "Allocated buffer of size %d for the large PUT",
content_len + 1)
inbuf = heap_inbuf;
}
int data_len = hap_httpd_get_data(req, inbuf, content_len);
if (data_len < 0) {
ESP_MFI_DEBUG(ESP_MFI_DEBUG_ERR, "Failed to read HTTPD Data");
httpd_resp_set_status(req, HTTPD_500);
if (heap_inbuf) {
hap_platform_memory_free(heap_inbuf);
ESP_MFI_DEBUG(ESP_MFI_DEBUG_INFO, "Freed allocated buffer for PUT");
}
return httpd_resp_send(req, NULL, 0);
}
ESP_MFI_DEBUG_PLAIN("Data Received: %s\n", inbuf);
jparse_ctx_t jctx;
if (json_parse_start(&jctx, inbuf, data_len) != HAP_SUCCESS) {
ESP_MFI_DEBUG(ESP_MFI_DEBUG_ERR, "Failed to parse HTTPD JSON Data");
httpd_resp_set_status(req, HTTPD_500);
if (heap_inbuf) {
hap_platform_memory_free(heap_inbuf);
ESP_MFI_DEBUG(ESP_MFI_DEBUG_INFO, "Freed allocated buffer for PUT");
}
return httpd_resp_send(req, NULL, 0);
}
httpd_resp_set_type(req, "application/hap+json");
/* Setting response type to indicate error.
* This will be actually sent out only if there is some error
* to be reported while handling the characteristic writes.
* Else, the response type will be set to 204
*/
httpd_resp_set_status(req, HTTPD_207);
if (hap_http_handle_set_char(&jctx, outbuf, sizeof(outbuf), req) == HAP_SUCCESS)
{
snprintf(outbuf, sizeof(outbuf), "HTTP/1.1 %s\r\n\r\n", HTTPD_204);
httpd_send(req, outbuf, strlen(outbuf));
} else {
/* If a failure was encountered, it would mean that a response has been generated,
* which will be chunk encoded. So, sending the last chunk here and also printing
* a new line to end the prints of the error string.
*/
httpd_resp_send_chunk(req, NULL, 0);
ESP_MFI_DEBUG_PLAIN("\n");
}
json_parse_end(&jctx);
if (heap_inbuf) {
hap_platform_memory_free(heap_inbuf);
ESP_MFI_DEBUG(ESP_MFI_DEBUG_INFO, "Freed allocated buffer for PUT");
}
hap_report_event(HAP_EVENT_SET_CHAR_COMPLETED, NULL, 0);
return HAP_SUCCESS;
}
static bool hap_get_bool_url_param(const char *query_str, const char *key)
{
char val[6]; /* Max string will be "false" */
if (httpd_query_key_value(query_str, key, val, sizeof(val)) == HAP_SUCCESS) {
if (!strcmp(val, "true") || !strcmp(val, "1"))
return true;
}
return false;
}
static int hap_http_get_characteristics(httpd_req_t *req)
{
char outbuf[512];
char stack_val_buf[512] = {0};
char *heap_val_buf = NULL;
char *val = stack_val_buf;
ESP_MFI_DEBUG_PLAIN("Socket fd: %d; HTTP Request %s %s\n", httpd_req_to_sockfd(req), hap_platform_httpd_get_req_method(req), hap_platform_httpd_get_req_uri(req));
hap_secure_session_t *session = (hap_secure_session_t *)hap_platform_httpd_get_sess_ctx(req);
if (!hap_is_req_secure(session)) {
return hap_http_session_not_authorized(req);
}
const char *uri = hap_platform_httpd_get_req_uri(req);
/* Allocate on heap, if URI is longer */
if (strlen(uri) > sizeof(stack_val_buf)) {
heap_val_buf = hap_platform_memory_calloc(strlen(uri) + 1, 1); /* Allocating an extra byte for NULL termination */
if (!heap_val_buf) {
ESP_MFI_DEBUG(ESP_MFI_DEBUG_ERR, "Failed to read URL");
httpd_resp_set_status(req, HTTPD_500);
return httpd_resp_send(req, NULL, 0);
}
ESP_MFI_DEBUG(ESP_MFI_DEBUG_INFO, "Allocated buffer of size %d for the large GET",
strlen(uri) + 1)
val = heap_val_buf;
}
size_t url_query_str_len = httpd_req_get_url_query_len(req);
char * url_query_str = hap_platform_memory_calloc(1, url_query_str_len + 1);
if (!url_query_str) {
httpd_resp_set_status(req, HTTPD_400);
httpd_resp_set_type(req, "application/hap+json");
snprintf(outbuf, sizeof(outbuf),"{\"status\":-70409}");
httpd_resp_send(req, outbuf, strlen(outbuf));
goto get_char_return;
}
httpd_req_get_url_query_str(req, url_query_str, url_query_str_len + 1);
/* Check for all the optional URL query paramaters */
bool meta = hap_get_bool_url_param(url_query_str, "meta");
bool perms = hap_get_bool_url_param(url_query_str, "perms");
bool type = hap_get_bool_url_param(url_query_str, "type");
bool ev = hap_get_bool_url_param(url_query_str, "ev");
/* Check for the mandatory "id" URL query parameter.
* If not found, return success
*/
if (httpd_query_key_value(url_query_str, "id", val, strlen(uri) + 1) != HAP_SUCCESS) {
httpd_resp_set_status(req, HTTPD_400);
httpd_resp_set_type(req, "application/hap+json");
snprintf(outbuf, sizeof(outbuf),"{\"status\":-70409}");
httpd_resp_send(req, outbuf, strlen(outbuf));
goto get_char_return;
}
/* Get the total count of characteristics in the request. This will be
* required to decide the size of the characteristic pointers' array
*/
int char_cnt = 0;
char *val_ptr = val;
/* We do not do any error checking here and just assume that the id
* tag has a characteristic list which is a comma separated list
* of <aid>.<iid> elements
*/
char *p = strsep(&val_ptr, ",");
while (p) {
char_cnt++;
p = strsep(&val_ptr, ",");
}
/* Normally, it would have been fine to just go on parsing the
* characteristics in the URL, fetch their values and prepare
* the response. However, if there is error for any characteristic
* a "status" field needs to be added for all characteristics.
*
* So, it is better to maintain a list of characteristics pointers,
* read all the values, and only then create the response
*/
hap_read_data_t *read_arr = hap_platform_memory_calloc(char_cnt, sizeof(hap_read_data_t));
if (!read_arr) {
httpd_resp_set_status(req, HTTPD_500);
httpd_resp_set_type(req, "application/hap+json");
snprintf(outbuf, sizeof(outbuf),"{\"status\":-70407}");
httpd_resp_send(req, outbuf, strlen(outbuf));
goto get_char_return;
}
hap_status_t *status_codes = hap_platform_memory_calloc(char_cnt, sizeof(hap_status_t));
if (!status_codes) {
hap_platform_memory_free(read_arr);
httpd_resp_set_status(req, HTTPD_500);
httpd_resp_set_type(req, "application/hap+json");
snprintf(outbuf, sizeof(outbuf),"{\"status\":-70407}");
httpd_resp_send(req, outbuf, strlen(outbuf));
goto get_char_return;
}
ESP_MFI_DEBUG_PLAIN("Generating HTTP Response\n");
/* Generate the JSON response */
bool include_status = 0;
httpd_resp_set_status(req, HTTPD_207);
httpd_resp_set_type(req, "application/hap+json");
json_gen_str_t jstr;
json_gen_str_start(&jstr, outbuf, sizeof(outbuf), hap_http_json_flush_chunk, req);
/* Get the ids once again. Not checking for success since that
* would be redundant
*/
httpd_query_key_value(url_query_str, "id", val, strlen(uri) + 1);
char_cnt = 0;
int aid, iid;
val_ptr = val;
/* Parse the AIDs and IIDs in the "id" field and fetch the
* characteristic pointer for each
*/
p = strsep(&val_ptr, ".");
while (p) {
aid = atoi(p);
p = strsep(&val_ptr, ",");
iid = atoi(p);
p = strsep(&val_ptr, ".");
hap_char_t *hc = hap_acc_get_char_by_iid(hap_acc_get_by_aid(aid), iid);
if (!hc) {
hap_set_char_report_status(&include_status, &jstr,
aid, iid, HAP_STATUS_RES_ABSENT);
continue;
}
if (!(((__hap_char_t *)hc)->permission & HAP_CHAR_PERM_PR)) {
hap_set_char_report_status(&include_status, &jstr,
aid, iid, HAP_STATUS_RD_ON_WRONLY);
continue;
}
hap_char_set_owner_ctrl(hc, hap_get_ctrl_session_index(session));
((__hap_char_t *)hc)->update_called = false;
/* Add the characteristic to the read array */
read_arr[char_cnt].hc = hc;
status_codes[char_cnt] = HAP_STATUS_SUCCESS;
read_arr[char_cnt].status = &status_codes[char_cnt];
char_cnt++;
}
if (!char_cnt) {
goto get_char_end;
}
int hs_index = 0;
bool read_err = false;
__hap_serv_t *hs = (__hap_serv_t *)hap_char_get_parent(read_arr[0].hc);
/* Read all the values first, before preparing the response, so that it
* would be known in advance, if any read error is encountered
*/
int i;
/* The counter here will go till char_cnt instead of char_cnt - 1.
* When i == char_cnt, it will mean that all elements in the array
* have been looped through.
* So, last iteration will invoke the read callback for the last
* set of characteritics.
*/
for (i = 0; i <= char_cnt; i++) {
if ((i < char_cnt) && ((hap_serv_t *)hs == hap_char_get_parent(read_arr[i].hc)))
continue;
else {
/* Passing the pointers to the first elements of the array
* for a given service (indicated by hs_index).
* Number of elements of the array are indicated by
* i - hs_index
*/
if (hs->bulk_read(&read_arr[hs_index], i - hs_index, hs->priv, hap_platform_httpd_get_sess_ctx(req)) != HAP_SUCCESS)
read_err = true;
if (i < char_cnt) {
hs = (__hap_serv_t *)hap_char_get_parent(read_arr[i].hc);
hs_index = i;
}
}
}
if (!include_status) {
if (!read_err) {
/* If "include_status" is false, it means there
* were no errors.
* So, set response type to 200 OK
*/
httpd_resp_set_status(req, HTTPD_200);
}
json_gen_start_object(&jstr);
json_gen_push_array(&jstr, "characteristics");
}
/* Loop through the characteristics and include their data
*/
for (i = 0; i < char_cnt; i++) {
__hap_char_t *hc = (__hap_char_t *)read_arr[i].hc;
/* If the Update API has not been called from the service read routine,
* reset the owner controller value.
* Else, the controller will miss the next notification.
*/
if (!hc->update_called) {
hc->owner_ctrl = 0;
}
hc->update_called = false;
json_gen_start_object(&jstr);
__hap_acc_t *ha = (__hap_acc_t *)hap_serv_get_parent(hc->parent);
json_gen_obj_set_int(&jstr, "aid", ha->aid);
json_gen_obj_set_int(&jstr, "iid", hc->iid);
if (hc->permission & HAP_CHAR_PERM_SPECIAL_READ) {
json_gen_obj_set_null(&jstr, "value");
} else {
/* Include "value" only if status is SUCCESS */
if (*read_arr[i].status == HAP_STATUS_SUCCESS) {
hap_add_char_val_json(hc->format, "value", &hc->val, &jstr);
}
}
/* Include status only if it was already included because of
* some parsing errors, or if an error was encountered while
* actually reading the characteristics.
*/
if (include_status || read_err) {
json_gen_obj_set_int(&jstr, "status", *read_arr[i].status);
}
if (type)
hap_add_char_type(hc, &jstr);
if (perms)
hap_add_char_perms(hc, &jstr);
if (ev) {
hap_add_char_ev(hc, &jstr, hap_get_ctrl_session_index(session));
}
if (meta)
hap_add_char_meta(hc, &jstr);
json_gen_end_object(&jstr);
}
get_char_end:
json_gen_pop_array(&jstr);
json_gen_end_object(&jstr);
json_gen_str_end(&jstr);
hap_platform_memory_free(read_arr);
hap_platform_memory_free(status_codes);
/* This indicates the last chunk */
httpd_resp_send_chunk(req, NULL, 0);
ESP_MFI_DEBUG_PLAIN("\n");
get_char_return:
if (heap_val_buf) {
hap_platform_memory_free(heap_val_buf);
ESP_MFI_DEBUG(ESP_MFI_DEBUG_INFO, "Freed allocated buffer for GET");
}
if (url_query_str) {
hap_platform_memory_free(url_query_str);
}
hap_report_event(HAP_EVENT_GET_CHAR_COMPLETED, NULL, 0);
return HAP_SUCCESS;
}
static struct httpd_uri hap_characteristics_get = {
.uri = "/characteristics",
.method = HTTP_GET,
.handler = hap_http_get_characteristics,
};
static struct httpd_uri hap_characteristics_put = {
.uri = "/characteristics",
.method = HTTP_PUT,
.handler = hap_http_put_characteristics,
};
static int hap_http_pairings_handler(httpd_req_t *req)
{
uint8_t buf[2048]; /* Large buffer to accommodate 16 pairings list */
void *ctx = hap_platform_httpd_get_sess_ctx(req);
ESP_MFI_DEBUG_PLAIN("Socket fd: %d; HTTP Request %s %s\n", httpd_req_to_sockfd(req), hap_platform_httpd_get_req_method(req), hap_platform_httpd_get_req_uri(req));
int data_len = httpd_req_recv(req, (char *)buf, sizeof(buf));
int outlen;
hap_secure_session_t *session = (hap_secure_session_t *)ctx;
if (!hap_is_req_secure(session)) {
/* Only setting the HTTP status here. Actual error TLV will be added
* by the hap_pairings_process() call
*/
httpd_resp_set_status(req, "470 Connection Authorization Required");
}
hap_pairings_process(ctx, buf, data_len, sizeof(buf), &outlen);
httpd_resp_set_type(req, "application/pairing+tlv8");
return httpd_resp_send(req, (char *)buf, outlen);
}
static struct httpd_uri hap_pairings = {
.uri = "/pairings",
.method = HTTP_POST,
.handler = hap_http_pairings_handler,
};
static int hap_http_post_identify(httpd_req_t *req)
{
char buf[100];
ESP_MFI_DEBUG_PLAIN("Socket fd: %d\nHTTP Request %s %s\n", httpd_req_to_sockfd(req), hap_platform_httpd_get_req_method(req), hap_platform_httpd_get_req_uri(req));
if (is_accessory_paired()) {
httpd_resp_set_status(req, HTTPD_400);
httpd_resp_set_type(req, "application/hap+json");
snprintf(buf, sizeof(buf),"{\"status\":-70401}");
httpd_resp_send(req, buf, strlen(buf));
} else {
hap_acc_t *ha = hap_get_first_acc();
__hap_acc_t *_ha = (__hap_acc_t *)ha;
_ha->identify_routine(ha);
snprintf(buf, sizeof(buf), "HTTP/1.1 %s\r\n\r\n", HTTPD_204);
httpd_send(req, buf, strlen(buf));
}
return HAP_SUCCESS;
}
static struct httpd_uri hap_identify = {
.uri = "/identify",
.method = HTTP_POST,
.handler = hap_http_post_identify,
};
static int hap_http_put_prepare(httpd_req_t *req)
{
char buf[512] = {0};
ESP_MFI_DEBUG_PLAIN("Socket fd: %d; HTTP Request %s %s\n", httpd_req_to_sockfd(req), hap_platform_httpd_get_req_method(req), hap_platform_httpd_get_req_uri(req));
hap_secure_session_t *session = (hap_secure_session_t *)hap_platform_httpd_get_sess_ctx(req);
if (!hap_is_req_secure(session)) {
return hap_http_session_not_authorized(req);
}
int data_len = httpd_req_recv(req, (char *)buf, sizeof(buf));
if (data_len < 0) {
ESP_MFI_DEBUG(ESP_MFI_DEBUG_ERR, "Failed to read HTTPD Data");
httpd_resp_set_status(req, HTTPD_500);
return httpd_resp_send(req, NULL, 0);
}
ESP_MFI_DEBUG_PLAIN("Data Received: %s\n", buf);
jparse_ctx_t jctx;
if (json_parse_start(&jctx, buf, data_len) != HAP_SUCCESS) {
ESP_MFI_DEBUG(ESP_MFI_DEBUG_ERR, "Failed to parse HTTPD JSON Data");
httpd_resp_set_status(req, HTTPD_500);
return httpd_resp_send(req, NULL, 0);
}
httpd_resp_set_type(req, "application/hap+json");
uint64_t pid;
int64_t ttl;
if ((json_obj_get_int64(&jctx, "pid", (int64_t *)&pid) != OS_SUCCESS) ||
(json_obj_get_int64(&jctx, "ttl", &ttl) != OS_SUCCESS)) {
snprintf(buf, sizeof(buf),"{\"status\":-70410}");
} else {
session->pid = pid;
session->ttl = ttl;
session->prepare_time = esp_timer_get_time() / 1000; /* Set current time in msec */
snprintf(buf, sizeof(buf),"{\"status\":0}");
}
json_parse_end(&jctx);
httpd_resp_send(req, buf, strlen(buf));
return HAP_SUCCESS;
}
static struct httpd_uri hap_prepare = {
.uri = "/prepare",
.method = HTTP_PUT,
.handler = hap_http_put_prepare,
};
static void hap_send_notification(void *arg)
{
int num_char = hap_priv.cfg.max_event_notif_chars;
hap_char_t *hc;
hap_char_t **char_arr = hap_platform_memory_calloc(num_char, sizeof(hap_char_t *));
if (!char_arr) {
return;
}
int i, num_notif_chars;
for (i = 0; i < num_char; i++) {
hc = hap_get_pending_notif_char();
if (hc) {
char_arr[i] = hc;
} else {
break;
}
}
/* If no characteristic notifications are pending, free char_arr and exit */
if (i == 0) {
hap_platform_memory_free(char_arr);
return;
}
num_notif_chars = i;
hap_secure_session_t *session;
/* Flag to indicate if any controller was connected */
bool ctrl_connected = false;
char buf[250];
for (i = 0; i < HAP_MAX_SESSIONS; i++) {
session = hap_priv.sessions[i];
if (!session)
continue;
ctrl_connected = true;
int fd = session->conn_identifier;
#define HTTPD_HDR_STR "EVENT/1.0 200 OK\r\n" \
"Content-Type: application/hap+json\r\n" \
"Content-Length: %d\r\n"
char notif_json[1024];
json_gen_str_t jstr;
json_gen_str_start(&jstr, notif_json, sizeof(notif_json), NULL, NULL);
json_gen_start_object(&jstr);
json_gen_push_array(&jstr, "characteristics");
int j;
bool notif_to_send = false;
for (j = 0; j < num_notif_chars; j++) {
hc = char_arr[j];
__hap_char_t *_hc = ( __hap_char_t *)hc;
/* If the controller is the owner, dont send notification to it */
if (hap_char_is_ctrl_owner(hc, i)) {
/* Since there can be only one owner, which we are anyways skipping,
* we can reset owner value to 0
*/
_hc->owner_ctrl = 0;
continue;
}
if (!hap_char_is_ctrl_subscribed(hc, i))
continue;
json_gen_start_object(&jstr);
hap_acc_t *ha = hap_serv_get_parent(hap_char_get_parent(hc));
int aid = ((__hap_acc_t *)ha)->aid;
json_gen_obj_set_int(&jstr, "aid", aid);
json_gen_obj_set_int(&jstr, "iid", _hc->iid);
hap_add_char_val_json(_hc->format, "value", &_hc->val, &jstr);
json_gen_end_object(&jstr);
notif_to_send = true;
}
if (!notif_to_send) {
/* No notification required for this controller. Just continue */
continue;
}
json_gen_pop_array(&jstr);
json_gen_end_object(&jstr);
json_gen_str_end(&jstr);
snprintf(buf, sizeof(buf), HTTPD_HDR_STR,
strlen(notif_json));
hap_httpd_send(hap_priv.server, fd, buf, strlen(buf), 0);
/* Space for sending additional headers based on set_header */
hap_httpd_send(hap_priv.server, fd, "\r\n", strlen("\r\n"), 0);
hap_httpd_send(hap_priv.server, fd, notif_json, strlen(notif_json), 0);
httpd_sess_update_lru_counter(hap_priv.server, fd);
ESP_MFI_DEBUG(ESP_MFI_DEBUG_INFO, "Notification Sent");
ESP_MFI_DEBUG_PLAIN("Socket fd: %d; Event message: %s\n", fd, notif_json);
}
/* If no controller was connected and no disconnected event was sent,
* reannaounce mDNS. That will increment state number as required
* by HAP Spec R15.
*/
if (!ctrl_connected && !hap_priv.disconnected_event_sent) {
hap_mdns_announce(false);
hap_priv.disconnected_event_sent = true;
}
hap_platform_memory_free(char_arr);
}
void hap_http_debug_enable()
{
http_debug = true;
}
void hap_http_debug_disable()
{
http_debug = false;
}
void hap_http_send_notif()
{
httpd_queue_work(hap_priv.server, hap_send_notification, NULL);
}
static bool hap_http_registered;
int hap_register_http_handlers()
{
if (!hap_http_registered) {
ESP_MFI_DEBUG(ESP_MFI_DEBUG_INFO, "Registering HomeKit web handlers");
httpd_register_uri_handler(hap_priv.server, &hap_pair_setup);
httpd_register_uri_handler(hap_priv.server, &hap_pair_verify);
httpd_register_uri_handler(hap_priv.server, &hap_pairings);
httpd_register_uri_handler(hap_priv.server, &hap_accessories);
httpd_register_uri_handler(hap_priv.server, &hap_characteristics_get);
httpd_register_uri_handler(hap_priv.server, &hap_characteristics_put);
httpd_register_uri_handler(hap_priv.server, &hap_identify);
httpd_register_uri_handler(hap_priv.server, &hap_prepare);
if (hap_priv.features & HAP_FF_SW_TOKEN_AUTH) {
hap_register_secure_message_handler(hap_priv.server);
}
}
hap_http_registered = true;
return HAP_SUCCESS;
}
int hap_unregister_http_handlers()
{
if (hap_http_registered) {
ESP_MFI_DEBUG(ESP_MFI_DEBUG_INFO, "Unegistering HomeKit web handlers");
httpd_unregister_uri_handler(hap_priv.server, "/pair-setup", HTTP_POST);
httpd_unregister_uri_handler(hap_priv.server, "/pair-verify", HTTP_POST);
httpd_unregister_uri_handler(hap_priv.server, "/pairings", HTTP_POST);
httpd_unregister_uri_handler(hap_priv.server, "/accessories", HTTP_GET);
httpd_unregister_uri_handler(hap_priv.server, "/characteristics", HTTP_GET);
httpd_unregister_uri_handler(hap_priv.server, "/characteristics", HTTP_PUT);
httpd_unregister_uri_handler(hap_priv.server, "/identify", HTTP_POST);
httpd_unregister_uri_handler(hap_priv.server, "/prepare", HTTP_PUT);
if (hap_priv.features & HAP_FF_SW_TOKEN_AUTH) {
hap_unregister_secure_message_handler(hap_priv.server);
}
}
hap_http_registered = false;
return HAP_SUCCESS;
}
int hap_httpd_start(void)
{
if (hap_platform_httpd_start(&hap_priv.server) == ESP_OK) {
return HAP_SUCCESS;
}
return HAP_FAIL;
}
httpd_handle_t * hap_httpd_get_handle()
{
return &hap_priv.server;
}
static bool first_announce_done;
int hap_mdns_deannounce(void)
{
int ret = HAP_SUCCESS;
if (first_announce_done) {
ESP_MFI_DEBUG(ESP_MFI_DEBUG_INFO, "Deannouncing _hap._tcp mDNS service");
ret = hap_mdns_serv_stop(&hap_priv.hap_mdns_handle);
if (ret == HAP_SUCCESS) {
/* Wait for some time for the packets to go out on network */
vTaskDelay(2000 / hap_platform_os_get_msec_per_tick());
first_announce_done = false;
}
}
return ret;
}
int hap_mdns_announce(bool first)
{
/* If the API is called with the "first" argument as true, Force announce the service,
* rather than just sending a re-announce packet
*/
if (first) {
first_announce_done = false;
}
static char config_num[6]; /* Max value can be 65535 */
static char state_num[6]; /* Max value can be 65535 */
static char ff[4];
static char sf[4];
static char ci[4];
mdns_txt_item_t txt[9];
int i = 0;
snprintf(config_num, sizeof(config_num), "%d", hap_priv.config_num);
txt[i].key = "c#";
txt[i++].value = config_num;
uint8_t features = 0;
/* Either hardware authentication, or software authentication
* can be enabled at a time.
*/
if (hap_priv.features & HAP_FF_HARDWARE_AUTH) {
features |= HAP_FF_HARDWARE_AUTH;
} else if (hap_priv.features & HAP_FF_SW_TOKEN_AUTH) {
features |= HAP_FF_SW_TOKEN_AUTH;
}
snprintf(ff, sizeof(ff), "%d", features);
txt[i].key = "ff";
txt[i++].value = ff;
txt[i].key = "id";
txt[i++].value = hap_priv.acc_id;
txt[i].key = "md";
txt[i++].value = hap_priv.primary_acc.model;
txt[i].key = "pv";
txt[i++].value = "1.1"; /* As per HAP Spec R10 */
if (first_announce_done) {
/* If first announcement was already done, this is a republish.
* Update the state number, since HAP Spec R15 requires that any Bonjour republish
* should update state number.
*/
if (is_accessory_paired()) {
hap_increment_and_save_state_num();
}
}
snprintf(state_num, sizeof(state_num), "%u", hap_priv.state_num);
txt[i].key = "s#";
txt[i++].value = state_num;
uint8_t status_flags = is_accessory_paired() ? 0 : HAP_SF_ACC_UNPAIRED;
if (!hap_is_network_configured())
status_flags |= HAP_SF_ACC_UNCONFIGURED;
snprintf(sf, sizeof(sf), "%d", status_flags);
txt[i].key = "sf";
txt[i++].value = sf;
snprintf(ci, sizeof(ci), "%d", hap_priv.cid);
txt[i].key = "ci";
txt[i++].value = ci;
txt[i].key = "sh";
txt[i++].value = hap_priv.setup_hash_str;
int ret;
/* If first announce is not done, the service will be added instead of just updating.
* Else, first add the service, instead of just updating.
*/
if (!first_announce_done) {
ESP_MFI_DEBUG(ESP_MFI_DEBUG_INFO, "Announcing _hap._tcp mDNS service");
ret = hap_mdns_serv_start(&hap_priv.hap_mdns_handle,
hap_priv.primary_acc.name, "_hap", "_tcp", hap_platform_httpd_get_port(), txt, i);
first_announce_done = true;
} else {
/* Else, just update TXT records. Not add new service.*/
ESP_MFI_DEBUG(ESP_MFI_DEBUG_INFO, "Re-announcing _hap._tcp mDNS service");
ret = hap_mdns_serv_update_txt(&hap_priv.hap_mdns_handle, txt, i);
}
if (ret != 0) {
ESP_MFI_DEBUG(ESP_MFI_DEBUG_ERR, "Failed to announce _hap mDNS service");
return HAP_FAIL;
}
return HAP_SUCCESS;
}
int hap_ip_services_start()
{
static bool hap_ip_services_started;
if (hap_ip_services_started) {
return HAP_SUCCESS;
}
hap_register_http_handlers();
if (hap_mdns_announce(false) != HAP_SUCCESS) {
hap_unregister_http_handlers();
return HAP_FAIL;
}
hap_ip_services_started = true;
return HAP_SUCCESS;
}