├── .gitignore ├── Makefile ├── README.md └── src ├── buf.c ├── buf.h ├── hap.c ├── hap.h ├── hap_convenience.c ├── hap_i.h ├── hap_network.c ├── hap_pairing.c ├── http_parser.c ├── http_parser.h ├── intvec.c ├── intvec.h ├── json.h └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | hap 3 | *~ 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROG=hap 2 | 3 | SRCS += src/hap.c \ 4 | src/hap_pairing.c \ 5 | src/hap_network.c \ 6 | src/hap_convenience.c \ 7 | src/buf.c \ 8 | src/http_parser.c \ 9 | src/intvec.c \ 10 | 11 | O ?= build 12 | 13 | SONAME=libhap.so.0 14 | 15 | PREFIX ?= /usr/local 16 | 17 | CFLAGS += -std=gnu99 -O2 -Wall -Werror -Wno-unused-function -g 18 | CFLAGS += -Wno-deprecated-declarations 19 | CFLAGS += $(shell pkg-config --cflags libcrypto avahi-client) 20 | LDFLAGS += $(shell pkg-config --libs libcrypto avahi-client) 21 | LDFLAGS += -lpthread 22 | 23 | ifeq (${ASAN},1) 24 | O = build.asan 25 | CFLAGS += -fsanitize=address 26 | LDFLAGS += -fsanitize=address 27 | PROG = hap.asan 28 | endif 29 | 30 | CFLAGS_shared = -fvisibility=hidden -fPIC -shared -DHAP_SHARED_OBJECT_BUILD 31 | 32 | OBJS = $(SRCS:%.c=$(O)/%.o) 33 | DEPS = ${OBJS:%.o=%.d} 34 | 35 | ${PROG}: ${OBJS} src/main.c Makefile 36 | @mkdir -p $(dir $@) 37 | ${CC} ${CFLAGS} -o $@ ${OBJS} src/main.c ${LDFLAGS} 38 | 39 | ${O}/%.o: %.c Makefile | checkextdeps 40 | @mkdir -p $(dir $@) 41 | ${CC} -MD ${CFLAGS} -c -o $@ $< 42 | 43 | ${O}/${SONAME}: ${SRCS} Makefile 44 | @mkdir -p $(dir $@) 45 | ${CC} -Wl,-soname,${SONAME} -Wl,--no-undefined ${CFLAGS} ${CFLAGS_shared} -o $@ ${SRCS} ${LDFLAGS} 46 | strip --strip-all --discard-all $@ 47 | 48 | solib: ${O}/${SONAME} 49 | 50 | install: ${O}/${SONAME} 51 | @mkdir -p "${PREFIX}/include/libhap" "${PREFIX}/lib/" 52 | cp src/hap.h "${PREFIX}/include/libhap/" 53 | cp "${O}/${SONAME}" "${PREFIX}/lib/" 54 | ln -srf "${PREFIX}/lib/${SONAME}" "${PREFIX}/lib/libhap.so" 55 | if [ "x`id -u $$USER`" = "x0" ]; then ldconfig ; fi 56 | 57 | uninstall: 58 | rm -rf "${PREFIX}/lib/libhap.so" "${PREFIX}/lib/${SONAME}" "${PREFIX}/include/libhap" 59 | 60 | 61 | checkextdeps: 62 | @which pkg-config >/dev/null || (echo "\nDependency unmet: Need pkg-config\n" && exit 1) 63 | @pkg-config --atleast-version=1.1.1 libcrypto || (echo "\nDependency unmet: Need at least openssl >= 1.1.1\n" && exit 1) 64 | @pkg-config avahi-client || (echo "\nDependency unmet: Need avahi-client\n" && exit 1) 65 | 66 | -include $(DEPS) 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libhap 2 | 3 | License: MIT 4 | 5 | ### Minimalist C library for with Apple HomeKit accessories. 6 | 7 | Note: This is still in active development, but works well enough for simple accessories such as a light bulb. 8 | 9 | ## External dependencies 10 | 11 | * openssl >= 1.1.1 12 | * avahi-client 13 | 14 | Note: OpenSSL 1.1.1 is relatively new and is only available on releases such as Ubuntu 18.04+, Raspbian Buster, etc. 15 | 16 | libhap itself includes these excellent libraries: 17 | 18 | * [nodejs/http-parser](https://github.com/nodejs/http-parser) - MIT License 19 | * [mjansson/json](https://github.com/mjansson/json) - Public Domain 20 | 21 | ## Building 22 | 23 | make 24 | 25 | ## Running example 26 | 27 | ./hap 28 | 29 | ## Building (and installing) a library 30 | 31 | make solib 32 | sudo make install # Installs in /usr/local 33 | 34 | make install PREFIX=/some/other/path # Install in ${PREFIX} instead 35 | 36 | ## Using 37 | 38 | The API is documented in [src/hap.h](src/hap.h) 39 | 40 | The example in [src/main.c](src/main.c) contains a few examples for how to create: 41 | 42 | * A light-bulb accessory using the `hap_light_builb_create()` convenience function. 43 | * An RGB light accessory using the `hap_rgb_light_create()` convenience function. 44 | * A Bridge combining the accessories above. 45 | 46 | To create more complex accessories, please read Apple's [Using the HomeKit Accessory Protocol Specification](https://developer.apple.com/support/homekit-accessory-protocol/) to understand the relationship between accessories, services and characteristics. 47 | 48 | The plan is to add many more convenience functions to ease usage. 49 | 50 | 51 | ## Made By 52 | 53 | Andreas Smas - https://lonelycoder.com - https://twitter.com/andoma 54 | -------------------------------------------------------------------------------- /src/buf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "buf.h" 9 | 10 | 11 | 12 | static void 13 | buf_reserve(buf_t *b, size_t len) 14 | { 15 | if(b->size + len > b->capacity) { 16 | b->capacity = MAX(b->size + len, b->capacity * 2); 17 | b->data = realloc(b->data, b->capacity); 18 | } 19 | } 20 | 21 | 22 | void 23 | buf_append(buf_t *b, const void *data, size_t len) 24 | { 25 | buf_reserve(b, len + 1); // Make space for extra 0 we add at the end 26 | memcpy(b->data + b->size, data, len); 27 | b->size += len; 28 | ((uint8_t *)b->data)[b->size] = 0; 29 | } 30 | 31 | void 32 | buf_append_str(buf_t *b, const char *str) 33 | { 34 | buf_append(b, str, strlen(str)); 35 | } 36 | 37 | 38 | void 39 | buf_append_u8(buf_t *b, uint8_t u8) 40 | { 41 | buf_append(b, &u8, sizeof(u8)); 42 | } 43 | 44 | 45 | void 46 | buf_reset(buf_t *b) 47 | { 48 | b->size = 0; 49 | } 50 | 51 | void 52 | buf_free(buf_t *b) 53 | { 54 | free(b->data); 55 | memset(b, 0, sizeof(buf_t)); 56 | } 57 | 58 | void 59 | buf_pop(buf_t *b, size_t bytes) 60 | { 61 | if(bytes == b->size) 62 | return buf_reset(b); 63 | 64 | assert(bytes < b->size); 65 | 66 | memmove(b->data, b->data + bytes, b->size - bytes); 67 | b->size -= bytes; 68 | } 69 | 70 | void 71 | buf_append_and_escape_jsonstr(buf_t *b, const char *str, int escape_slash) 72 | { 73 | const char *s = str; 74 | 75 | buf_append(b, "\"", 1); 76 | 77 | while(*s != 0) { 78 | if(*s == '"' || (escape_slash && *s == '/') || *s == '\\' || *s < 32) { 79 | buf_append(b, str, s - str); 80 | 81 | if(*s == '"') 82 | buf_append(b, "\\\"", 2); 83 | else if(*s == '/') 84 | buf_append(b, "\\/", 2); 85 | else if(*s == '\n') 86 | buf_append(b, "\\n", 2); 87 | else if(*s == '\r') 88 | buf_append(b, "\\r", 2); 89 | else if(*s == '\t') 90 | buf_append(b, "\\t", 2); 91 | else if(*s == '\\') 92 | buf_append(b, "\\\\", 2); 93 | else { 94 | char tmp[8]; 95 | buf_append(b, tmp, snprintf(tmp, sizeof(tmp), "\\u%04x", *s)); 96 | } 97 | s++; 98 | str = s; 99 | } else { 100 | s++; 101 | } 102 | } 103 | buf_append(b, str, s - str); 104 | buf_append(b, "\"", 1); 105 | } 106 | 107 | 108 | void 109 | buf_printf(buf_t *b, const char *fmt, ...) 110 | { 111 | va_list ap; 112 | va_start(ap, fmt); 113 | int size = vsnprintf(NULL, 0, fmt, ap); 114 | va_end(ap); 115 | 116 | if(size < 0) 117 | return; 118 | 119 | buf_reserve(b, size + 1); 120 | va_start(ap, fmt); 121 | vsnprintf(b->data + b->size, size + 1, fmt, ap); 122 | va_end(ap); 123 | b->size += size; 124 | } 125 | -------------------------------------------------------------------------------- /src/buf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct buf { 6 | void *data; 7 | size_t size; 8 | size_t capacity; 9 | } buf_t; 10 | 11 | 12 | void buf_append(buf_t *b, const void *data, size_t len); 13 | 14 | void buf_append_str(buf_t *b, const char *str); 15 | 16 | void buf_reset(buf_t *b); 17 | 18 | void buf_free(buf_t *b); 19 | 20 | void buf_pop(buf_t *b, size_t bytes); 21 | 22 | void buf_append_u8(buf_t *b, uint8_t u8); 23 | 24 | void buf_append_and_escape_jsonstr(buf_t *b, const char *str, int escape_slash); 25 | 26 | void buf_printf(buf_t *b, const char *fmt, ...) 27 | __attribute__((format (printf, 2, 3))); 28 | 29 | #define scoped_buf_t buf_t __attribute__((cleanup(buf_free))) 30 | 31 | -------------------------------------------------------------------------------- /src/hap.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2019 Andreas Smas 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #define _GNU_SOURCE 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include 40 | 41 | #include "json.h" 42 | 43 | #include "hap_i.h" 44 | 45 | 46 | //==================================================================== 47 | 48 | 49 | hap_service_t * 50 | hap_service_create(void *opaque, 51 | const char *type, 52 | int num_characteristics, 53 | hap_get_callback_t *get, 54 | hap_set_callback_t *set, 55 | hap_flush_callback_t *flush, 56 | hap_init_callback_t *init, 57 | hap_fini_callback_t *fini) 58 | { 59 | hap_service_t *hs = calloc(1, sizeof(hap_service_t) + 60 | num_characteristics * sizeof(hap_value_type_t)); 61 | hs->hs_opaque = opaque; 62 | hs->hs_type = strdup(type); 63 | hs->hs_num_characteristics = num_characteristics; 64 | hs->hs_characteristic_get = get; 65 | hs->hs_characteristic_set = set; 66 | hs->hs_flush = flush; 67 | hs->hs_init = init; 68 | hs->hs_fini = fini; 69 | return hs; 70 | } 71 | 72 | 73 | 74 | static int 75 | add_service_on_thread(hap_accessory_t *ha, hap_msg_t *hm) 76 | { 77 | const int aid = hm->hm_index; 78 | hap_service_t *hs = hm->hm_hs; 79 | 80 | const hap_service_t *last = TAILQ_LAST(&ha->ha_services, hap_service_queue); 81 | if(last == NULL) { 82 | hs->hs_iid = 1; 83 | } else { 84 | hs->hs_iid = last->hs_iid + last->hs_num_characteristics + 2; 85 | } 86 | 87 | hs->hs_aid = aid; 88 | 89 | EVP_MD_CTX *ctx = EVP_MD_CTX_new(); 90 | EVP_DigestInit_ex(ctx, EVP_sha1(), NULL); 91 | EVP_DigestUpdate(ctx, hs->hs_type, strlen(hs->hs_type)); 92 | EVP_DigestUpdate(ctx, &hs->hs_aid, sizeof(hs->hs_aid)); 93 | 94 | for(int i = 0; i < hs->hs_num_characteristics; i++) { 95 | hap_characteristic_t c = hs->hs_characteristic_get(hs->hs_opaque, i); 96 | EVP_DigestUpdate(ctx, c.type, strlen(c.type)); 97 | EVP_DigestUpdate(ctx, &c.value.type, sizeof(c.value.type)); 98 | EVP_DigestUpdate(ctx, &c.unit, sizeof(c.unit)); 99 | EVP_DigestUpdate(ctx, &c.minValue, sizeof(c.minValue)); 100 | EVP_DigestUpdate(ctx, &c.maxValue, sizeof(c.maxValue)); 101 | EVP_DigestUpdate(ctx, &c.minStep, sizeof(c.minStep)); 102 | hs->hs_formats[i] = c.value.type; 103 | free(c.storage); 104 | } 105 | EVP_DigestFinal_ex(ctx, hs->hs_config_digest, NULL); 106 | EVP_MD_CTX_free(ctx); 107 | 108 | TAILQ_INSERT_TAIL(&ha->ha_services, hs, hs_link); 109 | 110 | 111 | if(hs->hs_init != NULL) 112 | hs->hs_init(hs->hs_opaque); 113 | 114 | return 1; 115 | } 116 | 117 | 118 | 119 | void 120 | hap_accessory_add_service(hap_accessory_t *ha, hap_service_t *hs, int aid) 121 | { 122 | hs->hs_ha = ha; 123 | 124 | hap_msg_t *hm = calloc(1, sizeof(hap_msg_t)); 125 | hm->hm_hs = hs; 126 | hm->hm_index = aid; 127 | hap_msg_enqueue(ha, hm, add_service_on_thread); 128 | } 129 | 130 | 131 | //==================================================================== 132 | 133 | 134 | hap_characteristic_t 135 | hap_make_string_ro(const char *type, const char *string) 136 | { 137 | assert(string != NULL); 138 | return (hap_characteristic_t) {.type = type, 139 | .perms = HAP_PERM_PR, 140 | .value = { .type = HAP_STRING, .string = string } 141 | }; 142 | } 143 | 144 | //==================================================================== 145 | 146 | static hap_characteristic_t 147 | svc_accessory_information_get(void *opaque, int index) 148 | { 149 | hap_accessory_t *ha = opaque; 150 | 151 | switch(index) { 152 | case 0: return hap_make_string_ro(HAP_CHARACTERISTIC_NAME, 153 | ha->ha_name); 154 | case 1: return hap_make_string_ro(HAP_CHARACTERISTIC_MANUFACTURER, 155 | ha->ha_manufacturer); 156 | case 2: return hap_make_string_ro(HAP_CHARACTERISTIC_SERIAL_NUMBER, 157 | ha->ha_id); 158 | case 3: return hap_make_string_ro(HAP_CHARACTERISTIC_MODEL, 159 | ha->ha_model); 160 | case 4: return hap_make_string_ro(HAP_CHARACTERISTIC_FIRMWARE_REVISION, 161 | ha->ha_version); 162 | case 5: return (hap_characteristic_t) {.type = HAP_CHARACTERISTIC_IDENTIFY, 163 | .perms = HAP_PERM_PW 164 | }; 165 | default: 166 | return (hap_characteristic_t){}; 167 | } 168 | } 169 | 170 | 171 | static hap_status_t 172 | svc_accessory_information_set(void *opaque, int iid, hap_value_t value) 173 | { 174 | return HAP_STATUS_OK; 175 | } 176 | 177 | 178 | static hap_service_t * 179 | svc_accessory_information_create(hap_accessory_t *ha) 180 | { 181 | return hap_service_create(ha, HAP_SERVICE_ACCESSORY_INFORMATION, 6, 182 | svc_accessory_information_get, 183 | svc_accessory_information_set, 184 | NULL, NULL, NULL); 185 | } 186 | 187 | 188 | 189 | 190 | 191 | //==================================================================== 192 | 193 | static hap_characteristic_t 194 | svc_protocol_information_get(void *opaque, int index) 195 | { 196 | return hap_make_string_ro(HAP_CHARACTERISTIC_VERSION, "01.01.00"); 197 | } 198 | 199 | static hap_service_t * 200 | svc_protocol_information_create(void) 201 | { 202 | return hap_service_create(NULL, HAP_SERVICE_PROTOCOL_INFORMATION, 1, 203 | svc_protocol_information_get, 204 | NULL, NULL, NULL, NULL); 205 | } 206 | 207 | 208 | //==================================================================== 209 | 210 | 211 | #define HAP_CFG_ID 1 212 | #define HAP_CFG_PRIVATE_KEY 2 213 | #define HAP_CFG_PEER 3 214 | #define HAP_CFG_CONFIG_HASH 4 215 | #define HAP_CFG_CONFIG_NUMBER 5 216 | #define HAP_CFG_SERVICE_STATE 6 217 | 218 | 219 | static void 220 | add_peer_from_config(hap_accessory_t *ha, const uint8_t *buf, int len) 221 | { 222 | if(len < 34) 223 | return; 224 | hap_peer_t *hp = calloc(1, sizeof(hap_peer_t)); 225 | 226 | memcpy(hp->hp_public_key, buf, 32); 227 | hp->hp_flags = buf[32]; 228 | 229 | buf += 33; 230 | len -= 33; 231 | 232 | hp->hp_id = malloc(len + 1); 233 | memcpy(hp->hp_id, buf, len); 234 | hp->hp_id[len] = 0; 235 | LIST_INSERT_HEAD(&ha->ha_peers, hp, hp_link); 236 | } 237 | 238 | 239 | static void 240 | add_service_state(hap_accessory_t *ha, const uint8_t *buf, size_t size) 241 | { 242 | if(size < 20) 243 | return; 244 | 245 | hap_service_state_t *hss = malloc(sizeof(hap_service_state_t) + size - 20); 246 | hss->hss_service = NULL; 247 | hss->hss_size = size - 20; 248 | memcpy(hss->hss_config_digest, buf, sizeof(hss->hss_config_digest)); 249 | memcpy(hss->hss_data, buf + 20, size - 20); 250 | LIST_INSERT_HEAD(&ha->ha_service_states, hss, hss_link); 251 | } 252 | 253 | 254 | static void 255 | hap_accessory_lts_parse(hap_accessory_t *ha, const uint8_t *buf, size_t size) 256 | { 257 | while(size > 2) { 258 | const uint8_t type = buf[0]; 259 | const uint8_t len = buf[1]; 260 | buf += 2; 261 | size -= 2; 262 | 263 | if(len > size) 264 | break; 265 | 266 | switch(type) { 267 | case HAP_CFG_PRIVATE_KEY: 268 | ha->ha_long_term_key = 269 | EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, buf, len); 270 | break; 271 | case HAP_CFG_PEER: 272 | add_peer_from_config(ha, buf, len); 273 | break; 274 | case HAP_CFG_ID: 275 | ha->ha_id = strndup((const void *)buf, len); 276 | break; 277 | case HAP_CFG_CONFIG_HASH: 278 | if(len == sizeof(ha->ha_config_hash)) 279 | memcpy(ha->ha_config_hash, buf, len); 280 | break; 281 | case HAP_CFG_CONFIG_NUMBER: 282 | if(len == sizeof(ha->ha_config_number)) 283 | memcpy(&ha->ha_config_number, buf, len); 284 | break; 285 | case HAP_CFG_SERVICE_STATE: 286 | add_service_state(ha, buf, len); 287 | break; 288 | } 289 | 290 | buf += len; 291 | size -= len; 292 | } 293 | } 294 | 295 | 296 | static void 297 | hap_accessory_lts_load(hap_accessory_t *ha) 298 | { 299 | int fd = open(ha->ha_storage_path, O_RDONLY); 300 | if(fd == -1) 301 | return; 302 | struct stat st; 303 | if(fstat(fd, &st)) { 304 | close(fd); 305 | return; 306 | } 307 | uint8_t *buf = malloc(st.st_size); 308 | if(buf == NULL) { 309 | close(fd); 310 | return; 311 | } 312 | 313 | if(read(fd, buf, st.st_size) != st.st_size) { 314 | free(buf); 315 | close(fd); 316 | return; 317 | } 318 | close(fd); 319 | 320 | hap_accessory_lts_parse(ha, buf, st.st_size); 321 | free(buf); 322 | } 323 | 324 | static void 325 | lts_append_tlv(buf_t *buf, uint8_t type, uint8_t length, const void *value) 326 | { 327 | buf_append_u8(buf, type); 328 | buf_append_u8(buf, length); 329 | buf_append(buf, value, length); 330 | } 331 | 332 | 333 | 334 | static int 335 | safe_write_file(hap_accessory_t *ha, const char *filename, 336 | const void *data, size_t len) 337 | { 338 | char path[PATH_MAX]; 339 | snprintf(path, sizeof(path), "%s.tmp", filename); 340 | 341 | int fd = open(path, O_TRUNC | O_RDWR | O_CREAT, 0644); 342 | if(fd == -1) { 343 | hap_log(ha, NULL, LOG_WARNING, "Unable create tempfile %s -- %s", 344 | path, strerror(errno)); 345 | return -1; 346 | } 347 | 348 | if(write(fd, data, len) != len) { 349 | hap_log(ha, NULL, LOG_WARNING, "Unable to write state to %s", 350 | path); 351 | close(fd); 352 | return -1; 353 | } 354 | close(fd); 355 | 356 | if(rename(path, filename) == -1) { 357 | hap_log(ha, NULL, LOG_WARNING, "Unable rename %s -> %s -- %s", 358 | path, filename, strerror(errno)); 359 | return -1; 360 | } else { 361 | hap_log(ha, NULL, LOG_DEBUG, "Saved state to %s", 362 | ha->ha_storage_path); 363 | } 364 | return 0; 365 | } 366 | 367 | 368 | void 369 | hap_accessory_lts_save(hap_accessory_t *ha, bool only_if_service_states) 370 | { 371 | pthread_mutex_lock(&ha->ha_state_mutex); 372 | 373 | if(only_if_service_states && 374 | LIST_FIRST(&ha->ha_service_states) == NULL) { 375 | pthread_mutex_unlock(&ha->ha_state_mutex); 376 | return; 377 | } 378 | 379 | buf_t buf = {}; 380 | 381 | uint8_t key[32]; 382 | size_t key_size = sizeof(key); 383 | EVP_PKEY_get_raw_private_key(ha->ha_long_term_key, key, &key_size); 384 | 385 | lts_append_tlv(&buf, HAP_CFG_PRIVATE_KEY, sizeof(key), key); 386 | 387 | lts_append_tlv(&buf, HAP_CFG_ID, strlen(ha->ha_id), ha->ha_id); 388 | 389 | if(ha->ha_config_number) { 390 | lts_append_tlv(&buf, HAP_CFG_CONFIG_HASH, sizeof(ha->ha_config_hash), 391 | ha->ha_config_hash); 392 | 393 | lts_append_tlv(&buf, HAP_CFG_CONFIG_NUMBER, sizeof(ha->ha_config_number), 394 | &ha->ha_config_number); 395 | } 396 | 397 | const hap_peer_t *hp; 398 | LIST_FOREACH(hp, &ha->ha_peers, hp_link) { 399 | if(strlen(hp->hp_id) > 200) 400 | continue; 401 | buf_append_u8(&buf, HAP_CFG_PEER); 402 | buf_append_u8(&buf, sizeof(hp->hp_public_key) + 1 + strlen(hp->hp_id)); 403 | buf_append(&buf, hp->hp_public_key, sizeof(hp->hp_public_key)); 404 | buf_append_u8(&buf, hp->hp_flags); 405 | buf_append(&buf, hp->hp_id, strlen(hp->hp_id)); 406 | } 407 | 408 | 409 | const hap_service_state_t *hss; 410 | LIST_FOREACH(hss, &ha->ha_service_states, hss_link) { 411 | size_t s = hss->hss_size + sizeof(hss->hss_config_digest); 412 | if(s > 255) 413 | continue; 414 | buf_append_u8(&buf, HAP_CFG_SERVICE_STATE); 415 | buf_append_u8(&buf, s); 416 | buf_append(&buf, hss->hss_config_digest, sizeof(hss->hss_config_digest)); 417 | buf_append(&buf, hss->hss_data, hss->hss_size); 418 | } 419 | 420 | if(buf.size == ha->ha_written_state.size && 421 | !memcmp(buf.data, ha->ha_written_state.data, buf.size)) { 422 | // State already persisted to disk 423 | buf_free(&buf); 424 | pthread_mutex_unlock(&ha->ha_state_mutex); 425 | return; 426 | } 427 | 428 | if(!safe_write_file(ha, ha->ha_storage_path, buf.data, buf.size)) { 429 | // Written OK 430 | buf_free(&ha->ha_written_state); 431 | ha->ha_written_state = buf; 432 | } else { 433 | buf_free(&buf); 434 | } 435 | pthread_mutex_unlock(&ha->ha_state_mutex); 436 | } 437 | 438 | 439 | void * 440 | hap_service_state_recall(hap_service_t *hs, size_t state_size) 441 | { 442 | void *data; 443 | hap_accessory_t *ha = hs->hs_ha; 444 | 445 | pthread_mutex_lock(&ha->ha_state_mutex); 446 | if(hs->hs_state != NULL) { 447 | data = hs->hs_state->hss_data; 448 | pthread_mutex_unlock(&ha->ha_state_mutex); 449 | return data; 450 | } 451 | 452 | hap_service_state_t *hss; 453 | LIST_FOREACH(hss, &ha->ha_service_states, hss_link) { 454 | if(hss->hss_service != NULL) 455 | continue; // Already mapped 456 | 457 | if(hss->hss_size != state_size) 458 | continue; 459 | 460 | if(!memcmp(hss->hss_config_digest, hs->hs_config_digest, 461 | sizeof(hs->hs_config_digest))) { 462 | hss->hss_service = hs; 463 | hs->hs_state = hss; 464 | data = hs->hs_state->hss_data; 465 | pthread_mutex_unlock(&ha->ha_state_mutex); 466 | return data; 467 | } 468 | } 469 | 470 | hss = calloc(1, sizeof(hap_service_state_t) + state_size); 471 | hss->hss_size = state_size; 472 | memcpy(hss->hss_config_digest, hs->hs_config_digest, 473 | sizeof(hss->hss_config_digest)); 474 | LIST_INSERT_HEAD(&ha->ha_service_states, hss, hss_link); 475 | hss->hss_service = hs; 476 | hs->hs_state = hss; 477 | data = hs->hs_state->hss_data; 478 | pthread_mutex_unlock(&ha->ha_state_mutex); 479 | return data; 480 | } 481 | 482 | 483 | 484 | //==================================================================== 485 | 486 | 487 | 488 | static void 489 | value_buf_print(buf_t *b, hap_value_t value) 490 | { 491 | switch(value.type) { 492 | case HAP_INTEGER: 493 | case HAP_UINT8: 494 | buf_printf(b, ",\"value\":%d", value.integer); 495 | break; 496 | case HAP_FLOAT: 497 | if(isfinite(value.number)) 498 | buf_printf(b, ",\"value\":%f", value.number); 499 | else 500 | buf_append_str(b, ",\"value\":null"); 501 | break; 502 | case HAP_BOOLEAN: 503 | buf_printf(b, ",\"value\":%s", value.boolean ? "true" : "false"); 504 | break; 505 | case HAP_STRING: 506 | buf_append_str(b, ",\"value\":"); 507 | buf_append_and_escape_jsonstr(b, value.string, 1); 508 | break; 509 | default: 510 | break; 511 | } 512 | } 513 | 514 | 515 | 516 | static void __attribute__((unused)) 517 | characteristic_to_json(hap_characteristic_t c, int aid, int iid, 518 | const hap_connection_t *hc, 519 | buf_t *output) 520 | { 521 | buf_printf(output, "{\"type\":\"%s\",\"perms\":[", c.type); 522 | const char *sep = ""; 523 | 524 | if(c.perms & HAP_PERM_PR) { 525 | buf_printf(output, "%s\"pr\"", sep); sep = ","; 526 | } 527 | if(c.perms & HAP_PERM_PW) { 528 | buf_printf(output, "%s\"pw\"", sep); sep = ","; 529 | } 530 | if(c.perms & HAP_PERM_EV) { 531 | buf_printf(output, "%s\"ev\"", sep); sep = ","; 532 | } 533 | buf_append_str(output, "]"); 534 | if(iid) 535 | buf_printf(output, ",\"iid\":%d", iid); 536 | if(aid) 537 | buf_printf(output, ",\"aid\":%d", aid); 538 | 539 | const char *format = "bool"; 540 | switch(c.value.type) { 541 | case HAP_INTEGER: 542 | format = "int"; 543 | break; 544 | case HAP_STRING: 545 | format = "string"; 546 | break; 547 | case HAP_FLOAT: 548 | format = "float"; 549 | break; 550 | case HAP_UINT8: 551 | format = "uint8"; 552 | break; 553 | default: 554 | break; 555 | } 556 | 557 | if(c.minValue != c.maxValue) { 558 | buf_printf(output, 559 | ",\"minValue\":%f,\"maxValue\":%f", 560 | c.minValue, c.maxValue); 561 | } 562 | 563 | if(c.minStep) { 564 | buf_printf(output, ",\"minStep\":%f", c.minStep); 565 | } 566 | 567 | buf_printf(output, "%s,\"format\":\"%s\"", 568 | hc != NULL && 569 | intvec_find(&hc->hc_watched_iids, iid) != -1 ? 570 | ",\"ev\":true" : "", 571 | format); 572 | 573 | value_buf_print(output, c.value); 574 | 575 | buf_append_str(output, "}"); 576 | } 577 | 578 | 579 | 580 | 581 | 582 | int 583 | hap_accessories(hap_connection_t *hc, enum http_method method, 584 | uint8_t *request_body, size_t request_body_len, 585 | const hap_query_args_t *qa) 586 | { 587 | hap_accessory_t *ha = hc->hc_ha; 588 | hap_service_t *hs; 589 | scoped_buf_t json = {}; 590 | int current_aid = 0; 591 | 592 | buf_append_str(&json, "{\"accessories\":["); 593 | TAILQ_FOREACH(hs, &ha->ha_services, hs_link) { 594 | 595 | if(current_aid != hs->hs_aid) { 596 | if(current_aid) { 597 | buf_append_str(&json, "]},"); 598 | } 599 | buf_printf(&json, "{\"aid\":%d,\"services\":[", hs->hs_aid); 600 | current_aid = hs->hs_aid; 601 | } else { 602 | buf_append_str(&json, ","); 603 | } 604 | 605 | buf_printf(&json, 606 | "{\"type\":\"%s\",\"iid\":%d,\"characteristics\":[", 607 | hs->hs_type, hs->hs_iid); 608 | 609 | for(int i = 0; i < hs->hs_num_characteristics; i++) { 610 | if(i) 611 | buf_append_str(&json, ","); 612 | const int iid = hs->hs_iid + 1 + i; 613 | hap_characteristic_t c = hs->hs_characteristic_get(hs->hs_opaque, i); 614 | characteristic_to_json(c, current_aid, iid, hc, &json); 615 | free(c.storage); 616 | } 617 | buf_append_str(&json, "]}"); 618 | } 619 | buf_append_str(&json, "]}]}"); 620 | return hap_http_send_reply(hc, 200, json.data, json.size, 621 | "application/hap+json"); 622 | } 623 | 624 | 625 | 626 | 627 | static void 628 | hap_send_notify(hap_accessory_t *ha, int aid, int iid, 629 | hap_value_t value, hap_connection_t *skip) 630 | { 631 | char headers[128]; 632 | scoped_buf_t json = {}; 633 | 634 | hap_connection_t *hc; 635 | LIST_FOREACH(hc, &ha->ha_connections, hc_link) { 636 | if(skip == hc || intvec_find(&hc->hc_watched_iids, iid) == -1) 637 | continue; 638 | 639 | if(json.size == 0) { 640 | 641 | buf_printf(&json, 642 | "{\"characteristics\":[{\"aid\":%d,\"iid\":%d", 643 | aid, iid); 644 | value_buf_print(&json, value); 645 | buf_append_str(&json, "}]}"); 646 | 647 | snprintf(headers, sizeof(headers), 648 | "EVENT/1.0 200 OK\r\n" 649 | "Content-Type: application/hap+json\r\n" 650 | "Content-Length: %zd\r\n" 651 | "\r\n", json.size); 652 | 653 | } 654 | hap_log(ha, hc, LOG_DEBUG, "Sending update event for aid:%d iid:%d", 655 | aid, iid); 656 | hap_http_send_data(hc, headers, strlen(headers)); 657 | hap_http_send_data(hc, json.data, json.size); 658 | } 659 | } 660 | 661 | 662 | static int 663 | hap_notify_on_thread(hap_accessory_t *ha, hap_msg_t *hm) 664 | { 665 | hap_service_t *hs = hm->hm_hs; 666 | const int iid = hs->hs_iid + 1 + hm->hm_index; 667 | if(hm->hm_local_echo) { 668 | hs->hs_characteristic_set(hs->hs_opaque, hm->hm_index, hm->hm_value); 669 | 670 | if(hs->hs_flush != NULL) 671 | hs->hs_flush(hs->hs_opaque); 672 | } 673 | hap_accessory_lts_save(ha, true); 674 | 675 | hap_send_notify(ha, hs->hs_aid, iid, hm->hm_value, NULL); 676 | return 0; 677 | } 678 | 679 | 680 | void 681 | hap_service_notify(hap_service_t *hs, int index, hap_value_t value, 682 | bool local_echo) 683 | { 684 | hap_accessory_t *ha = hs->hs_ha; 685 | 686 | hap_msg_t *hm = calloc(1, sizeof(hap_msg_t)); 687 | hm->hm_hs = hs; 688 | hm->hm_index = index; 689 | hm->hm_value = value; 690 | hm->hm_local_echo = local_echo; 691 | if(value.type == HAP_STRING) { 692 | hm->hm_data = strdup(value.string); 693 | hm->hm_value.string = hm->hm_data; 694 | } 695 | hap_msg_enqueue(ha, hm, hap_notify_on_thread); 696 | } 697 | 698 | 699 | 700 | 701 | 702 | static int 703 | find_in_object(const char *json, struct json_token_t tokens[], int i, 704 | const char *name) 705 | { 706 | if(tokens[i].type != JSON_OBJECT) 707 | return 0; 708 | 709 | for(i = tokens[i].child; i != 0; i = tokens[i].sibling) { 710 | if(json_string_equal(json + tokens[i].id, 711 | tokens[i].id_length, name, strlen(name))) 712 | return i; 713 | } 714 | return 0; 715 | } 716 | 717 | 718 | static int 719 | get_int(const char *json, struct json_token_t tokens[], int i, int defval) 720 | { 721 | if(tokens[i].type == JSON_PRIMITIVE) { 722 | if(json[tokens[i].value] == 't') // true 723 | return 1; 724 | if(json[tokens[i].value] == 'f') // false 725 | return 0; 726 | return atoi(json + tokens[i].value); 727 | } 728 | return defval; 729 | 730 | } 731 | 732 | static int 733 | get_named_int(const char *json, struct json_token_t tokens[], int i, 734 | const char *name, int defval) 735 | { 736 | return get_int(json, tokens, find_in_object(json, tokens, i, name), defval); 737 | } 738 | 739 | 740 | typedef struct { 741 | hap_service_t *hs; 742 | hap_status_t status; 743 | pthread_t tid; 744 | hap_value_t hv; 745 | int aid; 746 | int iid; 747 | } update_t; 748 | 749 | 750 | 751 | 752 | static void * 753 | update_thread(void *arg) 754 | { 755 | update_t *u = arg; 756 | hap_service_t *hs = u->hs; 757 | const int idx = u->iid - hs->hs_iid - 1; 758 | u->status = hs->hs_characteristic_set(hs->hs_opaque, idx, u->hv); 759 | return NULL; 760 | } 761 | 762 | int 763 | hap_characteristics(hap_connection_t *hc, enum http_method method, 764 | uint8_t *request_body, size_t request_body_len, 765 | const hap_query_args_t *qa) 766 | { 767 | hap_accessory_t *ha = hc->hc_ha; 768 | 769 | char errbuf[512]; 770 | if(method == HTTP_PUT) { 771 | const char *req_json = (const char *)request_body; 772 | const int max_tokens = 4096; 773 | struct json_token_t tokens[max_tokens]; 774 | json_size_t num_tokens = json_parse(req_json, request_body_len, 775 | tokens, max_tokens); 776 | if(num_tokens == 0) { 777 | hap_log(ha, hc, LOG_WARNING, "Unable to parse JSON: %s", errbuf); 778 | return -1; 779 | } 780 | if(num_tokens > max_tokens) { 781 | hap_log(ha, hc, LOG_WARNING, "JSON request too big"); 782 | return -1; 783 | } 784 | 785 | int i = find_in_object(req_json, tokens, 0, "characteristics"); 786 | if(i == 0) { 787 | hap_log(ha, hc, LOG_WARNING, 788 | "JSON object does not have 'characteristics'"); 789 | return -1; 790 | } 791 | 792 | SIMPLEQ_HEAD(, hap_service) to_update; 793 | SIMPLEQ_INIT(&to_update); 794 | 795 | update_t updates[tokens[i].value_length]; 796 | 797 | size_t num_updates = 0; 798 | 799 | for(i = tokens[i].child; i != 0; i = tokens[i].sibling, num_updates++) { 800 | 801 | const int aid = get_named_int(req_json, tokens, i, "aid", 0); 802 | const int iid = get_named_int(req_json, tokens, i, "iid", 0); 803 | 804 | hap_service_t *hs; 805 | TAILQ_FOREACH(hs, &ha->ha_services, hs_link) { 806 | if(aid == hs->hs_aid && 807 | iid >= hs->hs_iid + 1 && 808 | iid < hs->hs_iid + 1 + hs->hs_num_characteristics) 809 | break; 810 | } 811 | 812 | update_t *u = &updates[num_updates]; 813 | u->aid = aid; 814 | u->iid = iid; 815 | u->hs = hs; 816 | u->tid = 0; 817 | 818 | if(hs == NULL) { 819 | hap_log(ha, hc, LOG_WARNING, 820 | "Attempt to write to non-existent aid:%d iid:%d", 821 | aid, iid); 822 | u->status = HAP_STATUS_RESOURCE_DOES_NOT_EXIST; 823 | } else { 824 | int ev; 825 | int val = find_in_object(req_json, tokens, i, "value"); 826 | 827 | if(val) { 828 | if(hs->hs_characteristic_set == NULL) { 829 | u->status = HAP_STATUS_RESOURCE_IS_READ_ONLY; 830 | } else { 831 | 832 | const int idx = u->iid - hs->hs_iid - 1; 833 | 834 | switch(hs->hs_formats[idx]) { 835 | case HAP_INTEGER: 836 | case HAP_UINT8: 837 | u->hv.integer = get_int(req_json, tokens, val, 0); 838 | break; 839 | case HAP_BOOLEAN: 840 | u->hv.boolean = get_int(req_json, tokens, val, 0); 841 | break; 842 | case HAP_FLOAT: 843 | u->hv.number = get_int(req_json, tokens, val, 0); 844 | break; 845 | case HAP_STRING: 846 | break; 847 | case HAP_UNDEFINED: 848 | break; 849 | } 850 | 851 | pthread_create(&u->tid, NULL, update_thread, u); 852 | } 853 | 854 | } else if((ev = find_in_object(req_json, tokens, i, "ev")) != 0) { 855 | 856 | int pos = intvec_find(&hc->hc_watched_iids, iid); 857 | const int on = get_int(req_json, tokens, ev, 0); 858 | if(on && pos == -1) { 859 | intvec_insert_sorted(&hc->hc_watched_iids, iid); 860 | hap_log(ha, hc, LOG_DEBUG, "Watching IID %d", iid); 861 | } else if(!on && pos != -1) { 862 | intvec_delete(&hc->hc_watched_iids, pos); 863 | hap_log(ha, hc, LOG_DEBUG, "Stopped watching IID %d", iid); 864 | } 865 | u->status = HAP_STATUS_OK; 866 | } else { 867 | u->status = HAP_STATUS_INVALID_WRITE_REQ; 868 | } 869 | } 870 | } 871 | 872 | scoped_buf_t json = {}; 873 | buf_append_str(&json, "{\"characteristics\":["); 874 | int all_ok = 1; 875 | const char *sep = ""; 876 | 877 | for(size_t i = 0; i < num_updates; i++) { 878 | update_t *u = &updates[i]; 879 | hap_service_t *hs = u->hs; 880 | if(u->tid) { 881 | pthread_join(u->tid, NULL); 882 | 883 | if(u->status == HAP_STATUS_OK) { 884 | if(hs->hs_flush && !hs->hs_on_tmp_link) { 885 | SIMPLEQ_INSERT_TAIL(&to_update, hs, hs_tmp_link); 886 | hs->hs_on_tmp_link = 1; 887 | } 888 | hap_send_notify(ha, hs->hs_aid, hs->hs_iid, u->hv, hc); 889 | } 890 | } 891 | 892 | if(u->status != HAP_STATUS_OK) { 893 | all_ok = 0; 894 | } 895 | 896 | buf_printf(&json, "%s{\"aid\":%d,\"iid\":%d,\"status\":%d}", 897 | sep, u->aid, u->iid, u->status); 898 | sep = ","; 899 | } 900 | 901 | hap_service_t *hs; 902 | SIMPLEQ_FOREACH(hs, &to_update, hs_tmp_link) { 903 | hs->hs_flush(hs->hs_opaque); 904 | hs->hs_on_tmp_link = 0; 905 | } 906 | 907 | hap_accessory_lts_save(ha, true); 908 | if(all_ok) 909 | return hap_http_send_reply(hc, 204, NULL, 0, NULL); 910 | 911 | buf_append_str(&json, "]}"); 912 | return hap_http_send_reply(hc, 207, json.data, json.size, 913 | "application/hap+json"); 914 | } 915 | 916 | if(method == HTTP_GET) { 917 | char *ids = hap_http_get_qa(qa, "id"); 918 | if(ids == NULL) 919 | return hap_http_send_reply(hc, 400, NULL, 0, NULL); 920 | 921 | scoped_buf_t json = {}; 922 | 923 | const int include_ev = !!hap_http_get_qa(qa, "ev"); 924 | #if 0 925 | const int include_meta = !!hap_http_get_qa(qa, "meta"); 926 | const int include_perms = !!hap_http_get_qa(qa, "perms"); 927 | const int include_type = !!hap_http_get_qa(qa, "type"); 928 | 929 | assert(include_meta == 0); 930 | assert(include_perms == 0); 931 | assert(include_type == 0); 932 | #endif 933 | 934 | char *sp, *id; 935 | 936 | buf_append_str(&json, "{\"characteristics\":["); 937 | 938 | const char *sep = ""; 939 | while((id = strtok_r(ids, ",", &sp)) != NULL) { 940 | ids = NULL; 941 | char *y = strchr(id, '.'); 942 | if(y == NULL) 943 | continue; 944 | *y++ = 0; 945 | const int aid = atoi(id); 946 | const int iid = atoi(y); 947 | 948 | hap_service_t *hs; 949 | TAILQ_FOREACH(hs, &ha->ha_services, hs_link) { 950 | if(aid == hs->hs_aid && 951 | iid >= hs->hs_iid + 1 && 952 | iid < hs->hs_iid + 1 + hs->hs_num_characteristics) 953 | break; 954 | } 955 | 956 | buf_append_str(&json, sep); 957 | sep = ","; 958 | 959 | hap_characteristic_t c = 960 | hs->hs_characteristic_get(hs->hs_opaque, iid - hs->hs_iid - 1); 961 | characteristic_to_json(c, aid, iid, include_ev ? hc : NULL, &json); 962 | free(c.storage); 963 | } 964 | buf_append_str(&json, "]}"); 965 | return hap_http_send_reply(hc, 200, json.data, json.size, 966 | "application/hap+json"); 967 | 968 | } 969 | 970 | return hap_http_send_reply(hc, 405, NULL, 0, NULL); 971 | } 972 | 973 | 974 | 975 | 976 | /** 977 | * 978 | */ 979 | int 980 | hap_get_random_bytes(void *out, size_t len) 981 | { 982 | return !RAND_bytes(out, len); 983 | } 984 | 985 | 986 | 987 | hap_accessory_t * 988 | hap_accessory_create(const char *name, 989 | const char *password, 990 | const char *storage_path, 991 | hap_accessory_category_t category, 992 | const char *manufacturer, 993 | const char *model, 994 | const char *version, 995 | hap_log_callback_t *logcb, 996 | void *opaque) 997 | { 998 | int need_save = 0; 999 | 1000 | // Must be in form XXX-XX-XXX 1001 | if(strlen(password) != 10 || password[3] != '-' || password[6] != '-') { 1002 | logcb(opaque, LOG_ERR, "Password not in form XXX-XX-XXX"); 1003 | return NULL; 1004 | } 1005 | 1006 | hap_accessory_t *ha = calloc(1, sizeof(hap_accessory_t)); 1007 | TAILQ_INIT(&ha->ha_services); 1008 | pthread_mutex_init(&ha->ha_state_mutex, NULL); 1009 | ha->ha_name = strdup(name); 1010 | ha->ha_storage_path = strdup(storage_path); 1011 | ha->ha_category = category; 1012 | ha->ha_manufacturer = strdup(manufacturer); 1013 | ha->ha_model = strdup(model); 1014 | ha->ha_version = strdup(version ?: "0.1"); 1015 | ha->ha_password = strdup(password); 1016 | ha->ha_logcb = logcb; 1017 | ha->ha_opaque = opaque; 1018 | 1019 | hap_accessory_lts_load(ha); 1020 | 1021 | if(ha->ha_long_term_key == NULL) { 1022 | ha->ha_long_term_key = EVP_PKEY_new(); 1023 | EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL); 1024 | EVP_PKEY_keygen_init(pctx); 1025 | EVP_PKEY_keygen(pctx, &ha->ha_long_term_key); 1026 | EVP_PKEY_CTX_free(pctx); 1027 | need_save = 1; 1028 | } 1029 | 1030 | 1031 | if(ha->ha_id == NULL) { 1032 | uint8_t b[6] = {}; 1033 | hap_get_random_bytes(b, sizeof(b)); 1034 | b[0] |= 0x2; 1035 | b[0] &= ~0x1; 1036 | ha->ha_id = fmt("%02x:%02x:%02x:%02x:%02x:%02x", 1037 | b[0], b[1], b[2], b[3], b[4], b[5]); 1038 | } 1039 | 1040 | TAILQ_INIT(&ha->ha_msg_queue); 1041 | pthread_mutex_init(&ha->ha_msg_mutex, NULL); 1042 | 1043 | hap_network_init(ha); 1044 | 1045 | hap_accessory_add_service(ha, svc_accessory_information_create(ha), 1); 1046 | hap_accessory_add_service(ha, svc_protocol_information_create(), 1); 1047 | 1048 | if(need_save) 1049 | hap_accessory_lts_save(ha, false); 1050 | 1051 | hap_log(ha, NULL, LOG_DEBUG, "Initialized as ID %s", ha->ha_id); 1052 | 1053 | return ha; 1054 | } 1055 | 1056 | 1057 | 1058 | 1059 | void 1060 | hap_accessory_start(hap_accessory_t *ha) 1061 | { 1062 | hap_network_start(ha); 1063 | } 1064 | 1065 | 1066 | 1067 | 1068 | 1069 | void 1070 | hap_accessory_destroy(hap_accessory_t *ha) 1071 | { 1072 | hap_network_stop(ha); 1073 | 1074 | hap_peer_t *hp; 1075 | while((hp = LIST_FIRST(&ha->ha_peers)) != NULL) { 1076 | LIST_REMOVE(hp, hp_link); 1077 | free(hp->hp_id); 1078 | free(hp); 1079 | } 1080 | 1081 | hap_service_state_t *hss; 1082 | while((hss = LIST_FIRST(&ha->ha_service_states)) != NULL) { 1083 | LIST_REMOVE(hss, hss_link); 1084 | free(hss); 1085 | } 1086 | 1087 | hap_service_t *hs; 1088 | while((hs = TAILQ_FIRST(&ha->ha_services)) != NULL) { 1089 | if(hs->hs_fini) 1090 | hs->hs_fini(hs->hs_opaque); 1091 | TAILQ_REMOVE(&ha->ha_services, hs, hs_link); 1092 | free(hs->hs_type); 1093 | free(hs); 1094 | } 1095 | 1096 | EVP_PKEY_free(ha->ha_long_term_key); 1097 | free(ha->ha_name); 1098 | free(ha->ha_manufacturer); 1099 | free(ha->ha_model); 1100 | free(ha->ha_version); 1101 | free(ha->ha_password); 1102 | free(ha->ha_storage_path); 1103 | free(ha->ha_id); 1104 | buf_free(&ha->ha_written_state); 1105 | 1106 | free(ha); 1107 | } 1108 | 1109 | 1110 | void 1111 | hap_log(const hap_accessory_t *ha, const hap_connection_t *hc, int level, 1112 | const char *format, ...) 1113 | { 1114 | va_list ap; 1115 | va_start(ap, format); 1116 | scoped_char *msg = fmtv(format, ap); 1117 | va_end(ap); 1118 | 1119 | scoped_char *line = fmt("%s%s%s", hc ? hc->hc_log_prefix : "", 1120 | hc ? ": " : "", 1121 | msg); 1122 | 1123 | if(ha->ha_logcb) { 1124 | ha->ha_logcb(ha->ha_opaque, level, line); 1125 | } else { 1126 | fprintf(stderr, "%s\n", line); 1127 | } 1128 | } 1129 | 1130 | 1131 | void 1132 | hap_freecharp(char **ptr) 1133 | { 1134 | free(*ptr); 1135 | *ptr = NULL; 1136 | } 1137 | 1138 | char * 1139 | fmtv(const char *fmt, va_list ap) 1140 | { 1141 | char *ret; 1142 | if(vasprintf(&ret, fmt, ap) == -1) 1143 | abort(); 1144 | return ret; 1145 | } 1146 | 1147 | char * 1148 | fmt(const char *fmt, ...) 1149 | { 1150 | va_list ap; 1151 | char *ret; 1152 | va_start(ap, fmt); 1153 | ret = fmtv(fmt, ap); 1154 | va_end(ap); 1155 | return ret; 1156 | } 1157 | -------------------------------------------------------------------------------- /src/hap.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2019 Andreas Smas 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | 29 | #ifdef HAP_SHARED_OBJECT_BUILD 30 | #define HAP_PUBLIC_API __attribute__((visibility("default"))) 31 | #else 32 | #define HAP_PUBLIC_API 33 | #endif 34 | 35 | typedef enum { 36 | // Table 6-11: HAP Status Codes 37 | HAP_STATUS_OK = 0, 38 | HAP_STATUS_INSUFFICIENT_PRIVILEGES = -70401, 39 | HAP_STATUS_UNABLE_TO_PERFORM = -70402, 40 | HAP_STATUS_RESOURCE_IS_BUSY = -70403, 41 | HAP_STATUS_RESOURCE_IS_READ_ONLY = -70404, 42 | HAP_STATUS_RESOURCE_IS_WRITE_ONLY = -70405, 43 | HAP_STATUS_NOTIFICATION_NOT_SUPPORTED = -70406, 44 | HAP_STATUS_OUT_OF_RESOURCES = -70407, 45 | HAP_STATUS_OPERATION_TIMED_OUT = -70408, 46 | HAP_STATUS_RESOURCE_DOES_NOT_EXIST = -70409, 47 | HAP_STATUS_INVALID_WRITE_REQ = -70410, 48 | HAP_STATUS_INSUFFICIENT_AUTHORIZATION = -70411, 49 | } hap_status_t; 50 | 51 | 52 | 53 | 54 | typedef enum { 55 | HAP_UNDEFINED = 0, 56 | HAP_INTEGER = 1, 57 | HAP_STRING = 2, 58 | HAP_BOOLEAN = 3, 59 | HAP_FLOAT = 4, 60 | HAP_UINT8 = 5, 61 | } hap_value_type_t; 62 | 63 | 64 | typedef struct { 65 | hap_value_type_t type; 66 | union { 67 | const char *string; 68 | int integer; 69 | bool boolean; 70 | float number; 71 | }; 72 | } hap_value_t; 73 | 74 | typedef enum { 75 | HAP_PERM_PR = 0x1, // Paired Read 76 | HAP_PERM_PW = 0x2, // Paired Write 77 | HAP_PERM_EV = 0x4, // Events 78 | } hap_perm_t; 79 | 80 | typedef enum { 81 | HAP_UNIT_UNDEFINED = 0, 82 | HAP_UNIT_CELSIUS = 1, 83 | HAP_UNIT_PERCENTAGE = 2, 84 | HAP_UNIT_ARCDEGREES = 3, 85 | HAP_UNIT_LUX = 4, 86 | HAP_UNIT_SECONDS = 5, 87 | } hap_unit_t; 88 | 89 | 90 | typedef struct { 91 | const char *type; 92 | hap_value_t value; 93 | hap_perm_t perms; 94 | hap_unit_t unit; 95 | double minValue; 96 | double maxValue; 97 | double minStep; 98 | void *storage; 99 | } hap_characteristic_t; 100 | 101 | 102 | hap_characteristic_t hap_make_string_ro(const char *type, const char *string) 103 | HAP_PUBLIC_API; 104 | 105 | typedef enum { 106 | HAP_CAT_OTHER = 1, 107 | HAP_CAT_BRIDGES = 2, 108 | HAP_CAT_FANS = 3, 109 | HAP_CAT_GARAGE_DOOR_OPENERS = 4, 110 | HAP_CAT_LIGHTING = 5, 111 | HAP_CAT_LOCKS = 6, 112 | HAP_CAT_OUTLETS = 7, 113 | HAP_CAT_SWITCHES = 8, 114 | HAP_CAT_THERMOSTATS = 9, 115 | HAP_CAT_SENSORS = 10, 116 | HAP_CAT_SECURITY_SYSTEMS = 11, 117 | HAP_CAT_DOORS = 12, 118 | HAP_CAT_WINDOWS = 13, 119 | HAP_CAT_WINDOW_COVERINGS = 14, 120 | HAP_CAT_PROGRAMMABLE_SWITCHES = 15, 121 | HAP_CAT_IP_CAMERAS = 17, 122 | HAP_CAT_VIDEO_DOORBELLS = 18, 123 | HAP_CAT_AIR_PURIFIERS = 19, 124 | HAP_CAT_HEATERS = 20, 125 | HAP_CAT_AIR_CONDITIONERS = 21, 126 | HAP_CAT_HUMIDIFIERS = 22, 127 | HAP_CAT_DEHUMIDIFIERS = 23, 128 | HAP_CAT_SPRINKLERS = 28, 129 | HAP_CAT_FAUCETS = 29, 130 | HAP_CAT_SHOWER_SYSTEMS = 30, 131 | HAP_CAT_REMOTES = 32, 132 | } hap_accessory_category_t; 133 | 134 | 135 | #define HAP_SERVICE_ACCESSORY_INFORMATION "3E" 136 | #define HAP_SERVICE_AIR_PURIFIER "BB" 137 | #define HAP_SERVICE_AIR_QUALITY_SENSOR "8D" 138 | #define HAP_SERVICE_AUDIO_STREAM_MANAGEMENT "127" 139 | #define HAP_SERVICE_BATTERY "96" 140 | #define HAP_SERVICE_CARBON_DIOXIDE_SENSOR "97" 141 | #define HAP_SERVICE_CARBON_MONOXIDE_SENSOR "7F" 142 | #define HAP_SERVICE_CONTACT_SENDOR "80" 143 | #define HAP_SERVICE_DOOR "81" 144 | #define HAP_SERVICE_DOORBELL "121" 145 | #define HAP_SERVICE_FAN "B7" 146 | #define HAP_SERVICE_FAUCET "D7" 147 | #define HAP_SERVICE_FILTER_MAINTENANCE "BA" 148 | #define HAP_SERVICE_GARAGE_DOOR_OPENER "41" 149 | #define HAP_SERVICE_PROTOCOL_INFORMATION "A2" 150 | #define HAP_SERVICE_HEATER_COOLER "BC" 151 | #define HAP_SERVICE_HUMIDIFIER_DEHUMIDIFIER "BD" 152 | #define HAP_SERVICE_HUMIDITY_SENSOR "82" 153 | #define HAP_SERVICE_IRRIGATION_SYSTEM "CF" 154 | #define HAP_SERVICE_LEAK_SENSOR "83" 155 | #define HAP_SERVICE_LIGHT_BULB "43" 156 | #define HAP_SERVICE_LIGHT_SENSOR "84" 157 | #define HAP_SERVICE_LOCK_MANAGEMENT "44" 158 | #define HAP_SERVICE_LOCK_MECHANISM "45" 159 | #define HAP_SERVICE_MOTION_SENSOR "85" 160 | #define HAP_SERVICE_OCCUPANCY_SENSOR "86" 161 | #define HAP_SERVICE_OUTLET "47" 162 | #define HAP_SERVICE_SECURITY_SYSTEM "7E" 163 | #define HAP_SERVICE_SERVICE_LABEL "CC" 164 | #define HAP_SERVICE_SLAT "B9" 165 | #define HAP_SERVICE_SMOKE_SENSOR "87" 166 | #define HAP_SERVICE_STATELESS_PROGRAMMABLE_SWITCH "89" 167 | #define HAP_SERVICE_SWITCH "49" 168 | #define HAP_SERVICE_TARGET_CONTROL "125" 169 | #define HAP_SERVICE_TARGET_CONTROL_MANAGEMENT "122" 170 | #define HAP_SERVICE_TEMPERATURE_SENSOR "8A" 171 | #define HAP_SERVICE_THERMOSTAT "4A" 172 | #define HAP_SERVICE_VAVLE "D0" 173 | #define HAP_SERVICE_WINDOW "8B" 174 | #define HAP_SERVICE_WINDOW_COVERING "8C" 175 | 176 | 177 | // This list is not yet complete 178 | #define HAP_CHARACTERISTIC_ACCESSORY_FLAGS "A6" 179 | #define HAP_CHARACTERISTIC_ACTIVE "B0" 180 | #define HAP_CHARACTERISTIC_ACTIVE_IDENTIFIER "E7" 181 | #define HAP_CHARACTERISTIC_CURRENT_TEMPERATURE "11" 182 | #define HAP_CHARACTERISTIC_ADMIN_ONLY_ACCESS "1" 183 | #define HAP_CHARACTERISTIC_AUDIO_FEEDBACK "5" 184 | #define HAP_CHARACTERISTIC_AIR_PARTICULATE_DENSITY "64" 185 | #define HAP_CHARACTERISTIC_AIR_PARTICULATE_SIZE "65" 186 | #define HAP_CHARACTERISTIC_BRIGHTNESS "8" 187 | #define HAP_CHARACTERISTIC_FIRMWARE_REVISION "52" 188 | #define HAP_CHARACTERISTIC_HUE "13" 189 | #define HAP_CHARACTERISTIC_IDENTIFY "14" 190 | #define HAP_CHARACTERISTIC_MANUFACTURER "20" 191 | #define HAP_CHARACTERISTIC_MODEL "21" 192 | #define HAP_CHARACTERISTIC_NAME "23" 193 | #define HAP_CHARACTERISTIC_ON "25" 194 | #define HAP_CHARACTERISTIC_POSITION_TARGET "7C" 195 | #define HAP_CHARACTERISTIC_POSITION_CURRENT "6D" 196 | #define HAP_CHARACTERISTIC_POSITION_STATE "72" 197 | #define HAP_CHARACTERISTIC_SATURATION "2F" 198 | #define HAP_CHARACTERISTIC_SERIAL_NUMBER "30" 199 | #define HAP_CHARACTERISTIC_VERSION "37" 200 | #define HAP_CHARACTERISTIC_CURRENT_HEATER_COOLER_STATE "B1" 201 | #define HAP_CHARACTERISTIC_TARGET_HEATER_COOLER_STATE "B2" 202 | #define HAP_CHARACTERISTIC_HEATING_THRESHOLD "12" 203 | #define HAP_CHARACTERISTIC_CURRENT_DOOR_STATE "E" 204 | #define HAP_CHARACTERISTIC_TARGET_DOOR_STATE "32" 205 | #define HAP_CHARACTERISTIC_OBSTRUCTION_DETECTED "24" 206 | 207 | typedef struct hap_accessory hap_accessory_t; 208 | 209 | typedef struct hap_service hap_service_t; 210 | 211 | typedef void (hap_log_callback_t)(void *opaque, int level, const char *message); 212 | 213 | typedef hap_characteristic_t (hap_get_callback_t)(void *opaque, int index); 214 | 215 | typedef hap_status_t (hap_set_callback_t)(void *opaque, int index, 216 | hap_value_t value); 217 | 218 | typedef void (hap_flush_callback_t)(void *opaque); 219 | 220 | typedef void (hap_init_callback_t)(void *opaque); 221 | 222 | typedef void (hap_fini_callback_t)(void *opaque); 223 | 224 | 225 | /** 226 | * Create a new accessory. 227 | * 228 | * Once accessory has been created you should add services to it. 229 | * 230 | * Finally it should be started using hap_accessory_start() 231 | * 232 | * @name Name as displaye to the user 233 | * @password 8 digit numerical password. Must be in form "###-##-###" 234 | * @storage_path Full path to a file that contains long term settings. 235 | * This include the peers we're paird to, our long-term 236 | * private key, etc. 237 | * @category Pick from @hap_accessory_category_t above. Primarily affects 238 | * which icon is shown to user when discovering device. 239 | * @manufaturer Only shown to user, no functional significance. 240 | * @model Only shown to user, no functional significance. 241 | * @version Must be in form "x.y" or "x.y.z" according to spec. 242 | * @logcb Callback for log messages. If NULL messages are writted to stderr. 243 | * If you want to suppress all log, pass a dummy function. 244 | * @opaque Pointer passed as-is to @logcb 245 | */ 246 | hap_accessory_t *hap_accessory_create(const char *name, 247 | const char *password, 248 | const char *storage_path, 249 | hap_accessory_category_t category, 250 | const char *manufacturer, 251 | const char *model, 252 | const char *version, 253 | hap_log_callback_t *logcb, 254 | void *opaque) HAP_PUBLIC_API; 255 | 256 | /** 257 | * Start accessory. 258 | * 259 | * This makes the accessory available on the network. 260 | */ 261 | void hap_accessory_start(hap_accessory_t *ha) HAP_PUBLIC_API; 262 | 263 | 264 | /** 265 | * Create a new service. 266 | * 267 | * @opaque is passed to the @get and @set callbacks. 268 | * @type should be one of the HAP_SERVICE_ -defines. 269 | * @num_characteristics Number of characteristics for this service. 270 | * This simples services (such as ligh_bulb) only have 271 | * one characteristic (on, which can be in on or off state) 272 | * See specification for more details. 273 | * @get Invoked when libhap needs to read a characteristic. 274 | * @set Invoked when libhap needs to write a characteristic. Can be NULL. 275 | * @flush Invoked after one or more @set callbacks (originating from 276 | * the same request) have finished. This is useful if you want 277 | * avoid glitches for values that are derived from multiple 278 | * characteristcs. For example HSV -> RGB conversion. 279 | * @init Invoked when libhap is about to announce the service. Can be NULL. 280 | * @fini Invoked when libhap is about to remove the service. Can be NULL. 281 | * 282 | * The callbacks are always invoked on the accessory's networking thread, thus 283 | * blocking for an extended period of time is not recommended. 284 | * 285 | * Note that you must add the service using hap_accessory_add_service() for 286 | * it to have any effect. 287 | */ 288 | hap_service_t *hap_service_create(void *opaque, 289 | const char *type, 290 | int num_characteristics, 291 | hap_get_callback_t *get, 292 | hap_set_callback_t *set, 293 | hap_flush_callback_t *flush, 294 | hap_init_callback_t *init, 295 | hap_fini_callback_t *fini) HAP_PUBLIC_API; 296 | 297 | 298 | /** 299 | * Add a service to an accessory. 300 | * 301 | * If done after the accessory is started a new configuration-number 302 | * is generated the mDNS TXT record is updated so all peers will re-request 303 | * the (cached) config. 304 | * 305 | * Generally it's more efficient to add all services before the accessory 306 | * start. However, for more advanced accessories like a bridge, services 307 | * may come and go. 308 | */ 309 | void hap_accessory_add_service(hap_accessory_t *ha, hap_service_t *hs, 310 | int aid) HAP_PUBLIC_API; 311 | 312 | /** 313 | * Notify about update for a characteristic. 314 | * 315 | * This will push notification to all connected peers that have subscribed 316 | * for updates. 317 | * 318 | * For example, if you have a motion sensor and you have automation rules 319 | * configured that trigger on motion, you need to invoke this function 320 | * to update the status of the motion sensor. 321 | * 322 | * Can be called from any thread. 323 | * 324 | * @hs The service for which the characteristic is updated 325 | * @index Index of the characteristic (must be < num_characteristics when 326 | * the service was created) 327 | * @value New value 328 | * @local_echo Iff true, the @set callback passed to hap_service_create() 329 | * will be invoked as well. Note that the @set callback 330 | * invokation is asynchronous with respect to this call. 331 | */ 332 | void hap_service_notify(hap_service_t *hs, int index, 333 | hap_value_t value, bool local_echo) HAP_PUBLIC_API; 334 | 335 | /** 336 | * Read back state from disk 337 | * 338 | * If no state has been stored, data will be initialized to zero 339 | * 340 | * Can be called from any thread. 341 | * 342 | * @hs The service for which to recall state 343 | * @index Size of state, limited to 200 bytes 344 | * 345 | * Returns pointer to data. State will automatically be freed when 346 | * service is destroyed 347 | * 348 | * libhap will automatically write data back to disk after every 349 | * notification and update event if state has changed. 350 | * 351 | */ 352 | void *hap_service_state_recall(hap_service_t *hs, size_t state_size) HAP_PUBLIC_API; 353 | 354 | /** 355 | * Remove an accessory and free up all resources 356 | */ 357 | void hap_accessory_destroy(hap_accessory_t *ha) HAP_PUBLIC_API; 358 | 359 | 360 | /** 361 | * Convenience functions for creating services. 362 | * (Unfortunately very sparse at the moment) 363 | */ 364 | hap_service_t *hap_light_builb_create(void *opaque, 365 | hap_status_t (*set)(void *opaque, 366 | bool on)) 367 | HAP_PUBLIC_API; 368 | 369 | hap_service_t * hap_rgb_light_create(void *opaque, 370 | hap_status_t (*set)(void *opaque, 371 | float r, float g, 372 | float b)) 373 | HAP_PUBLIC_API; 374 | -------------------------------------------------------------------------------- /src/hap_convenience.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2019 Andreas Smas 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | #include 25 | #include 26 | 27 | #include "hap_i.h" 28 | 29 | //--------------------------------------------------------------------- 30 | // Simple lightbulb 31 | //--------------------------------------------------------------------- 32 | 33 | typedef struct { 34 | void *opaque; 35 | hap_status_t (*set)(void *opaque, bool on); 36 | hap_service_t *hs; 37 | } lightbulb_t; 38 | 39 | 40 | static hap_characteristic_t 41 | lightbulb_get(void *opaque, int index) 42 | { 43 | const lightbulb_t *lb = opaque; 44 | bool *state = hap_service_state_recall(lb->hs, sizeof(bool)); 45 | 46 | return (hap_characteristic_t) { 47 | .type = HAP_CHARACTERISTIC_ON, 48 | .perms = HAP_PERM_PR | HAP_PERM_PW | HAP_PERM_EV, 49 | .value = { 50 | .type = HAP_BOOLEAN, 51 | .boolean = *state 52 | } 53 | }; 54 | } 55 | 56 | static hap_status_t 57 | lightbulb_set(void *opaque, int index, hap_value_t value) 58 | { 59 | const lightbulb_t *lb = opaque; 60 | bool *state = hap_service_state_recall(lb->hs, sizeof(bool)); 61 | 62 | *state = value.boolean; 63 | return lb->set(lb->opaque, value.boolean); 64 | } 65 | 66 | 67 | static void 68 | lightbulb_init(void *opaque) 69 | { 70 | const lightbulb_t *lb = opaque; 71 | bool *state = hap_service_state_recall(lb->hs, sizeof(bool)); 72 | lb->set(lb->opaque, *state); 73 | } 74 | 75 | static void 76 | lightbulb_fini(void *opaque) 77 | { 78 | lightbulb_t *lb = opaque; 79 | free(lb); 80 | } 81 | 82 | 83 | hap_service_t * 84 | hap_light_builb_create(void *opaque, 85 | hap_status_t (*set)(void *opaque, bool on)) 86 | { 87 | lightbulb_t *lb = calloc(1, sizeof(lightbulb_t)); 88 | lb->opaque = opaque; 89 | lb->set = set; 90 | lb->hs = hap_service_create(lb, HAP_SERVICE_LIGHT_BULB, 1, 91 | lightbulb_get, lightbulb_set, NULL, 92 | lightbulb_init, lightbulb_fini); 93 | return lb->hs; 94 | } 95 | 96 | 97 | //--------------------------------------------------------------------- 98 | // RGB light 99 | //--------------------------------------------------------------------- 100 | 101 | 102 | typedef struct { 103 | bool on; 104 | int brightness; 105 | int hue; 106 | float saturation; 107 | } rgb_state_t; 108 | 109 | 110 | typedef struct { 111 | void *opaque; 112 | hap_status_t (*set)(void *opaque, float r, float g, float b); 113 | hap_service_t *hs; 114 | } rgblight_t; 115 | 116 | 117 | static hap_characteristic_t 118 | rgblight_get(void *opaque, int index) 119 | { 120 | const rgblight_t *rgb = opaque; 121 | rgb_state_t *state = hap_service_state_recall(rgb->hs, sizeof(rgb_state_t)); 122 | 123 | switch(index) { 124 | case 0: 125 | return (hap_characteristic_t) { 126 | .type = HAP_CHARACTERISTIC_ON, 127 | .perms = HAP_PERM_PR | HAP_PERM_PW | HAP_PERM_EV, 128 | .value = { 129 | .type = HAP_BOOLEAN, 130 | .boolean = state->on 131 | } 132 | }; 133 | case 1: 134 | return (hap_characteristic_t) { 135 | .type = HAP_CHARACTERISTIC_BRIGHTNESS, 136 | .perms = HAP_PERM_PR | HAP_PERM_PW | HAP_PERM_EV, 137 | .value = { 138 | .type = HAP_INTEGER, 139 | .integer = state->brightness 140 | }, 141 | .unit = HAP_UNIT_PERCENTAGE, 142 | .minValue = 0, 143 | .maxValue = 100, 144 | .minStep = 1, 145 | }; 146 | case 2: 147 | return (hap_characteristic_t) { 148 | .type = HAP_CHARACTERISTIC_HUE, 149 | .perms = HAP_PERM_PR | HAP_PERM_PW | HAP_PERM_EV, 150 | .value = { 151 | .type = HAP_FLOAT, 152 | .number = state->hue 153 | }, 154 | .unit = HAP_UNIT_ARCDEGREES, 155 | .minValue = 0, 156 | .maxValue = 360, 157 | .minStep = 1, 158 | }; 159 | case 3: 160 | return (hap_characteristic_t) { 161 | .type = HAP_CHARACTERISTIC_SATURATION, 162 | .perms = HAP_PERM_PR | HAP_PERM_PW | HAP_PERM_EV, 163 | .value = { 164 | .type = HAP_FLOAT, 165 | .number = state->saturation 166 | }, 167 | .unit = HAP_UNIT_PERCENTAGE, 168 | .minValue = 0, 169 | .maxValue = 100, 170 | .minStep = 1, 171 | }; 172 | } 173 | return (hap_characteristic_t) {}; 174 | } 175 | 176 | static hap_status_t 177 | rgblight_update(const rgblight_t *rgb, const rgb_state_t *state) 178 | { 179 | if(!state->on) { 180 | return rgb->set(rgb->opaque, 0, 0, 0); 181 | } 182 | const float S = state->saturation / 100.0f; 183 | const float V = state->brightness / 100.0f; 184 | const float kr = (300 + state->hue) % 360 / 60.0; 185 | const float kg = (180 + state->hue) % 360 / 60.0; 186 | const float kb = (60 + state->hue) % 360 / 60.0; 187 | const float r = V - V * S * MAX(MIN(MIN(kr, 4 - kr), 1), 0); 188 | const float g = V - V * S * MAX(MIN(MIN(kg, 4 - kg), 1), 0); 189 | const float b = V - V * S * MAX(MIN(MIN(kb, 4 - kb), 1), 0); 190 | return rgb->set(rgb->opaque, r, g, b); 191 | 192 | } 193 | 194 | 195 | static int 196 | rgblight_set(void *opaque, int index, hap_value_t value) 197 | { 198 | rgblight_t *rgb = opaque; 199 | rgb_state_t *state = hap_service_state_recall(rgb->hs, sizeof(rgb_state_t)); 200 | 201 | switch(index) { 202 | case 0: 203 | state->on = value.boolean; 204 | break; 205 | case 1: 206 | state->brightness = value.integer; 207 | break; 208 | case 2: 209 | state->hue = value.number; 210 | break; 211 | case 3: 212 | state->saturation = value.number; 213 | break; 214 | } 215 | 216 | return 0; 217 | } 218 | 219 | static void 220 | rgblight_flush(void *opaque) 221 | { 222 | rgblight_t *rgb = opaque; 223 | rgb_state_t *state = hap_service_state_recall(rgb->hs, sizeof(rgb_state_t)); 224 | rgblight_update(rgb, state); 225 | } 226 | 227 | static void 228 | rgblight_init(void *opaque) 229 | { 230 | rgblight_t *rgb = opaque; 231 | rgb_state_t *state = hap_service_state_recall(rgb->hs, sizeof(rgb_state_t)); 232 | rgblight_update(rgb, state); 233 | } 234 | 235 | static void 236 | rgblight_fini(void *opaque) 237 | { 238 | rgblight_t *rgb = opaque; 239 | free(rgb); 240 | } 241 | 242 | 243 | 244 | hap_service_t * 245 | hap_rgb_light_create(void *opaque, 246 | hap_status_t (*set)(void *opaque, 247 | float r, float g, float b)) 248 | { 249 | rgblight_t *rgb = calloc(1, sizeof(rgblight_t)); 250 | rgb->opaque = opaque; 251 | rgb->set = set; 252 | rgb->hs = hap_service_create(rgb, HAP_SERVICE_LIGHT_BULB, 4, 253 | rgblight_get, rgblight_set, rgblight_flush, 254 | rgblight_init, rgblight_fini); 255 | return rgb->hs; 256 | } 257 | -------------------------------------------------------------------------------- /src/hap_i.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2019 Andreas Smas 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "hap.h" 28 | 29 | #include 30 | 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | #include "http_parser.h" 37 | #include "intvec.h" 38 | 39 | #include "buf.h" 40 | 41 | TAILQ_HEAD(hap_msg_queue, hap_msg); 42 | LIST_HEAD(hap_service_state_list, hap_service_state); 43 | TAILQ_HEAD(hap_service_queue, hap_service); 44 | LIST_HEAD(hap_conection_list, hap_connection); 45 | LIST_HEAD(hap_peer_list, hap_peer); 46 | 47 | struct mbuf; 48 | 49 | 50 | typedef struct hap_msg { 51 | TAILQ_ENTRY(hap_msg) hm_link; 52 | 53 | // If this returns 1 we recompute config hash 54 | int (*hm_cb)(hap_accessory_t *ha, struct hap_msg *hm); 55 | 56 | bool hm_local_echo; 57 | hap_value_t hm_value; 58 | 59 | hap_service_t *hm_hs; 60 | 61 | int hm_index; 62 | 63 | void *hm_data; 64 | 65 | } hap_msg_t; 66 | 67 | 68 | typedef struct hap_service_state { 69 | LIST_ENTRY(hap_service_state) hss_link; 70 | 71 | struct hap_service *hss_service; 72 | size_t hss_size; 73 | 74 | // Used to match saved state to hap_service instance 75 | uint8_t hss_config_digest[20]; 76 | uint8_t hss_data[0]; 77 | 78 | } hap_service_state_t; 79 | 80 | 81 | 82 | 83 | struct hap_service { 84 | 85 | TAILQ_ENTRY(hap_service) hs_link; 86 | int hs_aid; 87 | int hs_iid; 88 | 89 | struct hap_service_state *hs_state; 90 | 91 | char *hs_type; 92 | 93 | int hs_num_characteristics; 94 | 95 | hap_characteristic_t (*hs_characteristic_get)(void *opaque, int index); 96 | 97 | int (*hs_characteristic_set)(void *opaque, int index, hap_value_t value); 98 | 99 | void (*hs_flush)(void *opaque); 100 | 101 | void (*hs_init)(void *opaque); 102 | 103 | void (*hs_fini)(void *opaque); 104 | 105 | void *hs_opaque; 106 | 107 | struct hap_accessory *hs_ha; 108 | 109 | uint8_t hs_config_digest[20]; 110 | 111 | SIMPLEQ_ENTRY(hap_service) hs_tmp_link; 112 | int hs_on_tmp_link; 113 | 114 | hap_value_type_t hs_formats[0]; 115 | }; 116 | 117 | 118 | typedef struct hap_peer { 119 | LIST_ENTRY(hap_peer) hp_link; 120 | char *hp_id; 121 | uint8_t hp_public_key[32]; 122 | uint8_t hp_flags; 123 | } hap_peer_t; 124 | 125 | #define HAP_PEER_ADMIN 0x1 126 | 127 | 128 | struct hap_accessory { 129 | 130 | struct hap_peer_list ha_peers; 131 | 132 | struct hap_service_state_list ha_service_states; 133 | pthread_mutex_t ha_state_mutex; 134 | 135 | struct hap_service_queue ha_services; 136 | 137 | struct hap_conection_list ha_connections; 138 | 139 | char *ha_id; /* Announced over mDNS and also our AccessoryPairingID 140 | * Must be in mac-address form 'XX:XX:XX:XX:XX:XX' 141 | */ 142 | char *ha_name; 143 | char *ha_manufacturer; 144 | char *ha_model; 145 | char *ha_version; 146 | char *ha_password; 147 | 148 | char *ha_storage_path; 149 | hap_accessory_category_t ha_category; 150 | 151 | EVP_PKEY *ha_long_term_key; 152 | 153 | int ha_http_listen_port; 154 | 155 | const struct AvahiPoll *ha_ap; 156 | struct AvahiSimplePoll *ha_asp; 157 | struct AvahiEntryGroup *ha_group; 158 | 159 | pthread_t ha_tid; 160 | 161 | hap_log_callback_t *ha_logcb; 162 | void *ha_opaque; 163 | 164 | pthread_mutex_t ha_msg_mutex; 165 | struct hap_msg_queue ha_msg_queue; 166 | 167 | int ha_run; 168 | 169 | uint8_t ha_config_hash[20]; 170 | uint16_t ha_config_number; 171 | 172 | buf_t ha_written_state; 173 | }; 174 | 175 | 176 | 177 | 178 | 179 | typedef struct hap_connection { 180 | 181 | struct http_parser hc_parser; 182 | int hc_fd; 183 | struct AvahiWatch *hc_watch; 184 | 185 | buf_t hc_body; 186 | buf_t hc_path; 187 | buf_t hc_encrypted; 188 | 189 | struct pair_setup_ctx *hc_psc; 190 | 191 | struct sockaddr_in hc_remote_addr; 192 | 193 | hap_accessory_t *hc_ha; 194 | LIST_ENTRY(hap_connection) hc_link; 195 | 196 | uint8_t hc_SessionKey[32]; 197 | uint8_t hc_SendKey[32]; 198 | uint8_t hc_RecvKey[32]; 199 | 200 | uint8_t hc_peer_flags; 201 | 202 | EVP_CIPHER_CTX *hc_cipher_context; 203 | uint32_t hc_encrypted_frames; 204 | uint32_t hc_decrypted_frames; 205 | 206 | intvec_t hc_watched_iids; 207 | 208 | char *hc_log_prefix; 209 | 210 | } hap_connection_t; 211 | 212 | typedef struct hap_query_args hap_query_args_t; 213 | 214 | char *hap_http_get_qa(const hap_query_args_t *qa, const char *key); 215 | 216 | 217 | // ==== hap.c ============================================== 218 | 219 | void hap_accessory_lts_save(hap_accessory_t *ha, bool only_if_service_states); 220 | 221 | int hap_characteristics(hap_connection_t *hc, enum http_method method, 222 | uint8_t *request_body, size_t request_body_len, 223 | const hap_query_args_t *qa); 224 | 225 | int hap_accessories(hap_connection_t *hc, enum http_method method, 226 | uint8_t *request_body, size_t request_body_len, 227 | const hap_query_args_t *qa); 228 | 229 | void hap_msg_enqueue(hap_accessory_t *ha, hap_msg_t *hm, 230 | int (*cb)(hap_accessory_t *ha, hap_msg_t *hm)); 231 | 232 | void hap_log(const hap_accessory_t *ha, const hap_connection_t *hc, int level, 233 | const char *format, ...) 234 | __attribute__((format (printf, 4, 5))); 235 | 236 | // ==== hap_network.c ============================================== 237 | 238 | int hap_http_send_data(hap_connection_t *hc, const void *data, size_t len); 239 | 240 | int hap_http_send_reply(hap_connection_t *hc, int status, const void *body, 241 | size_t content_length, const char *content_type); 242 | 243 | void hap_network_init(hap_accessory_t *ha); 244 | 245 | void hap_network_start(hap_accessory_t *ha); 246 | 247 | void hap_network_stop(hap_accessory_t *ha); 248 | 249 | void hap_mdns_update(hap_accessory_t *ha); 250 | 251 | // ==== hap_pairing.c ============================================== 252 | 253 | int hap_pair_setup(hap_connection_t *hc, enum http_method method, 254 | uint8_t *request_body, size_t request_body_len, 255 | const hap_query_args_t *qa); 256 | 257 | int hap_pair_verify(hap_connection_t *hc, enum http_method method, 258 | uint8_t *request_body, size_t request_body_len, 259 | const hap_query_args_t *qa); 260 | 261 | int hap_pairings(hap_connection_t *hc, enum http_method method, 262 | uint8_t *request_body, size_t request_body_len, 263 | const hap_query_args_t *qa); 264 | 265 | void hap_pair_setup_ctx_free(struct pair_setup_ctx *psc); 266 | 267 | int hap_get_random_bytes(void *out, size_t len); 268 | 269 | // Misc things 270 | 271 | void hap_freecharp(char **ptr); 272 | 273 | #define scoped_char char __attribute__((cleanup(hap_freecharp))) 274 | 275 | char *fmtv(const char *fmt, va_list ap); 276 | 277 | char *fmt(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); 278 | 279 | #define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0])) 280 | 281 | 282 | -------------------------------------------------------------------------------- /src/hap_network.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2019 Andreas Smas 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "hap_i.h" 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | static void 44 | entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, 45 | void *userdata) 46 | { 47 | char *n; 48 | hap_accessory_t *ha = userdata; 49 | switch(state) { 50 | case AVAHI_ENTRY_GROUP_ESTABLISHED: 51 | hap_log(ha, NULL, LOG_INFO, "mDNS Service %s successfully established", 52 | ha->ha_name); 53 | break; 54 | 55 | case AVAHI_ENTRY_GROUP_COLLISION: 56 | n = avahi_alternative_service_name(ha->ha_name); 57 | free(ha->ha_name); 58 | ha->ha_name = n; 59 | 60 | hap_log(ha, NULL, LOG_INFO, 61 | "mDNS service name collision, renaming service to: %s", 62 | ha->ha_name); 63 | 64 | hap_mdns_update(ha); 65 | break; 66 | 67 | case AVAHI_ENTRY_GROUP_FAILURE: 68 | hap_log(ha, NULL, LOG_INFO, "mDNS group failure: %s", 69 | avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); 70 | break; 71 | 72 | case AVAHI_ENTRY_GROUP_UNCOMMITED: 73 | case AVAHI_ENTRY_GROUP_REGISTERING: 74 | break; 75 | } 76 | } 77 | 78 | 79 | 80 | void 81 | hap_mdns_update(hap_accessory_t *ha) 82 | { 83 | if(ha->ha_group == NULL) 84 | return; 85 | 86 | int ret; 87 | char txt_id[64]; 88 | snprintf(txt_id, sizeof(txt_id), "id=%s", ha->ha_id); 89 | 90 | char txt_md[128]; 91 | snprintf(txt_md, sizeof(txt_md), "md=%s", ha->ha_model); 92 | 93 | char txt_ci[32]; 94 | snprintf(txt_ci, sizeof(txt_ci), "ci=%d", ha->ha_category); 95 | 96 | char txt_sf[32]; 97 | snprintf(txt_sf, sizeof(txt_sf), "sf=%d", 98 | LIST_FIRST(&ha->ha_peers) ? 0 : 1); 99 | 100 | char txt_csharp[32]; 101 | snprintf(txt_csharp, sizeof(txt_csharp), "c#=%d", 102 | ha->ha_config_number); 103 | 104 | if(avahi_entry_group_is_empty(ha->ha_group)) { 105 | 106 | if((ret = avahi_entry_group_add_service(ha->ha_group, AVAHI_IF_UNSPEC, 107 | AVAHI_PROTO_UNSPEC, 0, ha->ha_name, 108 | "_hap._tcp", NULL, NULL, 109 | ha->ha_http_listen_port, 110 | "s#=1", // Must be 1 111 | txt_ci, 112 | txt_sf, 113 | "pv=1.1", // version, must be 1.1 114 | txt_md, 115 | txt_id, 116 | "ff=0", // Must be 0 117 | txt_csharp, 118 | NULL)) < 0) { 119 | 120 | if(ret == AVAHI_ERR_COLLISION) { 121 | char *n = avahi_alternative_service_name(ha->ha_name); 122 | avahi_free(ha->ha_name); 123 | ha->ha_name = n; 124 | 125 | hap_log(ha, NULL, LOG_INFO, 126 | "mDNS service name collision, renaming service to: %s", 127 | ha->ha_name); 128 | 129 | avahi_entry_group_reset(ha->ha_group); 130 | 131 | hap_mdns_update(ha); 132 | return; 133 | } 134 | 135 | hap_log(ha, NULL, LOG_ERR, 136 | "mDNS failed to add _hap._tcp service: %s", 137 | avahi_strerror(ret)); 138 | return; 139 | } 140 | 141 | if((ret = avahi_entry_group_commit(ha->ha_group)) < 0) { 142 | hap_log(ha, NULL, LOG_ERR, "mDNS failed to commit entry group: %s", 143 | avahi_strerror(ret)); 144 | return; 145 | } 146 | } else { 147 | avahi_entry_group_update_service_txt(ha->ha_group, AVAHI_IF_UNSPEC, 148 | AVAHI_PROTO_UNSPEC, 0, 149 | ha->ha_name, 150 | "_hap._tcp", NULL, 151 | "s#=1", // Must be 1 152 | txt_ci, 153 | txt_sf, 154 | "pv=1.1",// version, must be 1.1 155 | txt_md, 156 | txt_id, 157 | "ff=0", // Must be 0 158 | txt_csharp, 159 | NULL); 160 | } 161 | } 162 | 163 | 164 | /** 165 | * 166 | */ 167 | static void 168 | client_callback(AvahiClient *c, AvahiClientState state, void *userdata) 169 | { 170 | hap_accessory_t *ha = userdata; 171 | 172 | switch(state) { 173 | case AVAHI_CLIENT_S_RUNNING: 174 | ha->ha_group = avahi_entry_group_new(c, entry_group_callback, userdata); 175 | hap_mdns_update(ha); 176 | break; 177 | 178 | case AVAHI_CLIENT_FAILURE: 179 | hap_log(ha, NULL, LOG_ERR, "mDNS client failure: %s", 180 | avahi_strerror(avahi_client_errno(c))); 181 | break; 182 | 183 | case AVAHI_CLIENT_S_COLLISION: 184 | case AVAHI_CLIENT_S_REGISTERING: 185 | if(ha->ha_group) 186 | avahi_entry_group_reset(ha->ha_group); 187 | break; 188 | case AVAHI_CLIENT_CONNECTING: 189 | break; 190 | } 191 | } 192 | 193 | 194 | 195 | 196 | 197 | static int 198 | hap_prepare(hap_connection_t *hc, enum http_method method, 199 | uint8_t *request_body, size_t request_body_len, 200 | const hap_query_args_t *qa) 201 | { 202 | const char *resp = "{\"status\":0}"; 203 | return hap_http_send_reply(hc, 200, resp, strlen(resp), 204 | "application/hap+json"); 205 | } 206 | 207 | const static struct { 208 | const char *path; 209 | int (*fn)(hap_connection_t *hc, enum http_method method, 210 | uint8_t *request_body, size_t request_body_len, 211 | const hap_query_args_t *qas); 212 | int must_be_authed; 213 | } http_paths[] = { 214 | {"/pair-setup", hap_pair_setup, 0 }, 215 | {"/pair-verify", hap_pair_verify, 0 }, 216 | {"/pairings", hap_pairings, 1 }, 217 | {"/accessories", hap_accessories, 1 }, 218 | {"/characteristics", hap_characteristics, 1 }, 219 | {"/prepare", hap_prepare, 1 }, 220 | }; 221 | 222 | int 223 | hap_http_send_data(hap_connection_t *hc, const void *data, size_t len) 224 | { 225 | uint8_t buf[2 + 1024 + 16]; // 2 byte header + Max frame + authtag 226 | 227 | EVP_CIPHER_CTX *ctx = hc->hc_cipher_context; 228 | 229 | if(ctx == NULL) 230 | return write(hc->hc_fd, data, len) != len; 231 | 232 | EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, NULL, NULL); 233 | while(len) { 234 | int fs = MIN(len, 1024); 235 | 236 | uint8_t nonce[12] = {0,0,0,0, hc->hc_encrypted_frames, 237 | hc->hc_encrypted_frames >> 8, 238 | hc->hc_encrypted_frames >> 16, 239 | hc->hc_encrypted_frames >> 24, 0,0,0,0}; 240 | 241 | hc->hc_encrypted_frames++; 242 | int outlen = 0; 243 | buf[0] = fs; 244 | buf[1] = fs >> 8; 245 | 246 | EVP_EncryptInit_ex(ctx, NULL, NULL, hc->hc_SendKey, nonce); 247 | 248 | if(EVP_EncryptUpdate(ctx, NULL, &outlen, buf, 2) != 1) 249 | return -1; 250 | if(EVP_EncryptUpdate(ctx, buf + 2, &outlen, data, fs) != 1) 251 | return -1; 252 | if(EVP_EncryptFinal_ex(ctx, buf + 2 + outlen, &outlen) != 1) 253 | return -1; 254 | EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, 255 | buf + 2 + fs); 256 | 257 | if(write(hc->hc_fd, buf, 2 + fs + 16) != 2 + fs + 16) 258 | return -1; 259 | 260 | len -= fs; 261 | data += fs; 262 | } 263 | return 0; 264 | } 265 | 266 | 267 | 268 | int 269 | hap_http_send_reply(hap_connection_t *hc, int status, const void *body, 270 | size_t content_length, const char *content_type) 271 | { 272 | char buf[128]; 273 | 274 | if(status == 204) { 275 | snprintf(buf, sizeof(buf), 276 | "HTTP/1.1 %d %s\r\n" 277 | "\r\n", 278 | status, http_status_str(status)); 279 | } else { 280 | snprintf(buf, sizeof(buf), 281 | "HTTP/1.1 %d %s\r\n" 282 | "%s%s%s" 283 | "Content-Length: %zd\r\n" 284 | "\r\n", 285 | status, http_status_str(status), 286 | content_type ? "Content-Type: " : "", 287 | content_type ?: "", 288 | content_type ? "\r\n" : "", 289 | content_length); 290 | } 291 | 292 | if(hap_http_send_data(hc, buf, strlen(buf))) 293 | return -1; 294 | 295 | return body ? hap_http_send_data(hc, body, content_length) : 0; 296 | } 297 | 298 | 299 | static int 300 | on_message_begin(http_parser *p) 301 | { 302 | hap_connection_t *hc = p->data; 303 | buf_reset(&hc->hc_path); 304 | buf_reset(&hc->hc_body); 305 | return 0; 306 | } 307 | 308 | static int 309 | on_url(http_parser *p, const char *at, size_t length) 310 | { 311 | hap_connection_t *hc = p->data; 312 | if(hc->hc_path.size > 1024) 313 | return 1; 314 | buf_append(&hc->hc_path, at, length); 315 | return 0; 316 | } 317 | 318 | static int 319 | on_body(http_parser *p, const char *at, size_t length) 320 | { 321 | hap_connection_t *hc = p->data; 322 | if(hc->hc_path.size > 65536) 323 | return 1; 324 | buf_append(&hc->hc_body, at, length); 325 | return 0; 326 | } 327 | 328 | 329 | 330 | struct hap_query_args { 331 | char *key; 332 | char *value; 333 | }; 334 | 335 | 336 | char * 337 | hap_http_get_qa(const hap_query_args_t *qa, const char *key) 338 | { 339 | while(qa->key) { 340 | if(!strcmp(qa->key, key)) 341 | return qa->value; 342 | qa++; 343 | } 344 | return NULL; 345 | } 346 | 347 | 348 | static int 349 | on_message_complete(http_parser *p) 350 | { 351 | hap_connection_t *hc = p->data; 352 | char *path = hc->hc_path.data; 353 | 354 | hap_log(hc->hc_ha, hc, LOG_DEBUG, "%s %s", 355 | http_method_str(p->method), path); 356 | 357 | struct hap_query_args qas[16]; 358 | int num_query_args = 0; 359 | 360 | char *qa = strchr(path, '?'); 361 | if(qa != NULL) { 362 | *qa++ = 0; 363 | char *sp, *key; 364 | 365 | while(num_query_args < ARRAYSIZE(qas) - 1 && 366 | (key = strtok_r(qa, "&", &sp)) != NULL) { 367 | qa = NULL; 368 | qas[num_query_args].key = key; 369 | char *value = key + strcspn(key, "="); 370 | if(*value == '=') { 371 | *value++ = 0; 372 | } 373 | qas[num_query_args].value = value; 374 | num_query_args++; 375 | } 376 | } 377 | qas[num_query_args].key = NULL; 378 | 379 | for(size_t i = 0; i < ARRAYSIZE(http_paths); i++) { 380 | if(!strcmp(path, http_paths[i].path)) { 381 | 382 | if(http_paths[i].must_be_authed && !hc->hc_cipher_context) { 383 | return hap_http_send_reply(hc, 470, NULL, 0, NULL); 384 | } 385 | 386 | return http_paths[i].fn(hc, p->method, hc->hc_body.data, 387 | hc->hc_body.size, qas); 388 | } 389 | } 390 | hap_log(hc->hc_ha, hc, LOG_NOTICE, "Path %s not found", path); 391 | return hap_http_send_reply(hc, 404, NULL, 0, NULL); 392 | } 393 | 394 | 395 | static const http_parser_settings hap_http_parser_settings = { 396 | .on_message_begin = on_message_begin, 397 | .on_url = on_url, 398 | .on_body = on_body, 399 | .on_message_complete = on_message_complete, 400 | }; 401 | 402 | 403 | 404 | 405 | static int 406 | http_connection_decrypt_frame(hap_connection_t *hc, 407 | const uint8_t *frame, int frame_size) 408 | { 409 | uint8_t buf[frame_size]; 410 | 411 | EVP_DecryptInit_ex(hc->hc_cipher_context, EVP_chacha20_poly1305(), 412 | NULL, NULL, NULL); 413 | 414 | uint8_t n[12] = {0,0,0,0, hc->hc_decrypted_frames, 415 | hc->hc_decrypted_frames >> 8, 416 | hc->hc_decrypted_frames >> 16, 417 | hc->hc_decrypted_frames >> 24, 0,0,0,0}; 418 | hc->hc_decrypted_frames++; 419 | EVP_CIPHER_CTX_ctrl(hc->hc_cipher_context, EVP_CTRL_AEAD_SET_TAG, 16, 420 | (uint8_t *)frame + 2 + frame_size); 421 | 422 | EVP_DecryptInit_ex(hc->hc_cipher_context, NULL, NULL, hc->hc_RecvKey, n); 423 | 424 | int outlen = 0; 425 | int rv = EVP_DecryptUpdate(hc->hc_cipher_context, NULL, &outlen, frame, 2); 426 | if(rv != 1) 427 | return -1; 428 | 429 | rv = EVP_DecryptUpdate(hc->hc_cipher_context, buf, &outlen, 430 | frame + 2, frame_size); 431 | if(rv != 1) 432 | return -1; 433 | 434 | rv = EVP_DecryptFinal_ex(hc->hc_cipher_context, (uint8_t *)buf, &outlen); 435 | if(rv != 1) 436 | return -1; 437 | 438 | EVP_CIPHER_CTX_reset(hc->hc_cipher_context); 439 | 440 | const void *s = buf; 441 | int r = frame_size; 442 | 443 | // hexdump("HTTP INPUT", s, r); 444 | 445 | while(!hc->hc_parser.http_errno && r > 0) { 446 | size_t x = http_parser_execute(&hc->hc_parser, &hap_http_parser_settings, 447 | s, r); 448 | s += x; 449 | r -= x; 450 | } 451 | 452 | return 0; 453 | } 454 | 455 | 456 | 457 | static void 458 | http_delete_connection(hap_connection_t *hc) 459 | { 460 | hap_accessory_t *ha = hc->hc_ha; 461 | 462 | hap_log(hc->hc_ha, hc, LOG_DEBUG, "Disconnected"); 463 | 464 | if(hc->hc_psc != NULL) 465 | hap_pair_setup_ctx_free(hc->hc_psc); 466 | ha->ha_ap->watch_free(hc->hc_watch); 467 | close(hc->hc_fd); 468 | 469 | buf_free(&hc->hc_path); 470 | buf_free(&hc->hc_body); 471 | buf_free(&hc->hc_encrypted); 472 | 473 | if(hc->hc_cipher_context != NULL) 474 | EVP_CIPHER_CTX_free(hc->hc_cipher_context); 475 | 476 | LIST_REMOVE(hc, hc_link); 477 | intvec_reset(&hc->hc_watched_iids); 478 | free(hc->hc_log_prefix); 479 | free(hc); 480 | 481 | } 482 | 483 | 484 | static void 485 | http_input(AvahiWatch *w, int fd, AvahiWatchEvent event, void *userdata) 486 | { 487 | hap_connection_t *hc = userdata; 488 | char buf[1024]; 489 | 490 | int r = read(hc->hc_fd, buf, sizeof(buf)); 491 | if(r < 1) { 492 | http_delete_connection(hc); 493 | return; 494 | } 495 | 496 | if(hc->hc_cipher_context) { 497 | buf_append(&hc->hc_encrypted, buf, r); 498 | 499 | while(hc->hc_encrypted.size >= 2) { 500 | const uint8_t *u8 = hc->hc_encrypted.data; 501 | int frame_size = u8[0] | (u8[1] << 8); 502 | 503 | if(2 + 16 + frame_size >= hc->hc_encrypted.size) { 504 | // We have a complete frame 505 | if(http_connection_decrypt_frame(hc, hc->hc_encrypted.data, 506 | frame_size)) { 507 | 508 | hap_log(hc->hc_ha, hc, LOG_WARNING, "Stream decryption failed"); 509 | http_delete_connection(hc); 510 | return; 511 | } 512 | buf_pop(&hc->hc_encrypted, 2 + 16 + frame_size); 513 | } 514 | } 515 | 516 | } else { 517 | const char *s = buf; 518 | 519 | while(!hc->hc_parser.http_errno && r > 0) { 520 | size_t x = http_parser_execute(&hc->hc_parser, 521 | &hap_http_parser_settings, 522 | s, r); 523 | s += x; 524 | r -= x; 525 | } 526 | } 527 | if(hc->hc_parser.http_errno) { 528 | hap_log(hc->hc_ha, hc, LOG_WARNING, "HTTP parser error %s", 529 | http_errno_name(hc->hc_parser.http_errno)); 530 | http_delete_connection(hc); 531 | return; 532 | } 533 | } 534 | 535 | static void 536 | http_new_connection(AvahiWatch *w, int listen_fd, AvahiWatchEvent event, 537 | void *userdata) 538 | { 539 | hap_accessory_t *ha = userdata; 540 | struct sockaddr_in sin; 541 | socklen_t slen = sizeof(sin); 542 | 543 | int fd = accept(listen_fd, (struct sockaddr *)&sin, &slen); 544 | if(fd == -1) 545 | return; 546 | 547 | hap_connection_t *hc = calloc(1, sizeof(hap_connection_t)); 548 | LIST_INSERT_HEAD(&ha->ha_connections, hc, hc_link); 549 | hc->hc_ha = ha; 550 | hc->hc_fd = fd; 551 | hc->hc_log_prefix = strdup(inet_ntoa(sin.sin_addr)); 552 | hc->hc_parser.data = hc; 553 | http_parser_init(&hc->hc_parser, HTTP_REQUEST); 554 | 555 | hc->hc_remote_addr = sin; 556 | hap_log(hc->hc_ha, hc, LOG_DEBUG, "Connected"); 557 | 558 | hc->hc_watch = ha->ha_ap->watch_new(ha->ha_ap, fd, AVAHI_WATCH_IN, 559 | http_input, hc); 560 | } 561 | 562 | 563 | //==================================================================== 564 | 565 | 566 | static int 567 | hap_check_config_hash(hap_accessory_t *ha) 568 | { 569 | uint8_t digest[20]; 570 | const hap_service_t *hs; 571 | EVP_MD_CTX *ctx = EVP_MD_CTX_new(); 572 | EVP_DigestInit_ex(ctx, EVP_sha1(), NULL); 573 | TAILQ_FOREACH(hs, &ha->ha_services, hs_link) { 574 | EVP_DigestUpdate(ctx, hs->hs_config_digest, sizeof(hs->hs_config_digest)); 575 | } 576 | EVP_DigestFinal_ex(ctx, digest, NULL); 577 | EVP_MD_CTX_free(ctx); 578 | 579 | if(!memcmp(digest, ha->ha_config_hash, sizeof(ha->ha_config_hash))) 580 | return 0; 581 | 582 | memcpy(ha->ha_config_hash, digest, sizeof(ha->ha_config_hash)); 583 | ha->ha_config_number++; 584 | if(ha->ha_config_number == 0) 585 | ha->ha_config_number = 1; 586 | 587 | hap_log(ha, NULL, LOG_DEBUG, "Config number updated to %d", 588 | ha->ha_config_number); 589 | return 1; 590 | } 591 | 592 | 593 | 594 | 595 | static int 596 | dispatch_message(hap_accessory_t *ha) 597 | { 598 | pthread_mutex_lock(&ha->ha_msg_mutex); 599 | hap_msg_t *hm = TAILQ_FIRST(&ha->ha_msg_queue); 600 | if(hm != NULL) 601 | TAILQ_REMOVE(&ha->ha_msg_queue, hm, hm_link); 602 | pthread_mutex_unlock(&ha->ha_msg_mutex); 603 | if(hm == NULL) 604 | return 0; 605 | 606 | int r = hm->hm_cb(ha, hm); 607 | free(hm->hm_data); 608 | free(hm); 609 | 610 | if(r && ha->ha_run == 2) { 611 | if(hap_check_config_hash(ha)) { 612 | hap_mdns_update(ha); 613 | hap_accessory_lts_save(ha, false); 614 | } 615 | } 616 | 617 | return 1; 618 | } 619 | 620 | 621 | static void 622 | hap_remove_stale_service_state(hap_accessory_t *ha) 623 | { 624 | // Remove service state that it's not in use. 625 | hap_service_state_t *hss, *n; 626 | for(hss = LIST_FIRST(&ha->ha_service_states); hss != NULL; hss = n) { 627 | n = LIST_NEXT(hss, hss_link); 628 | if(hss->hss_service == NULL) { 629 | LIST_REMOVE(hss, hss_link); 630 | free(hss); 631 | } 632 | } 633 | } 634 | 635 | 636 | 637 | 638 | static void * 639 | hap_thread(void *aux) 640 | { 641 | hap_accessory_t *ha = aux; 642 | 643 | ha->ha_ap = avahi_simple_poll_get(ha->ha_asp); 644 | 645 | int fd = socket(AF_INET, SOCK_STREAM, 0); 646 | if(fd < 0) { 647 | hap_log(ha, NULL, LOG_ERR, "Failed to create socket: %s", strerror(errno)); 648 | return NULL; 649 | } 650 | struct sockaddr_in sin = {.sin_family = AF_INET}; 651 | if(bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) { 652 | hap_log(ha, NULL, LOG_ERR, "Failed to bind port for HTTP server: %s", 653 | strerror(errno)); 654 | close(fd); 655 | return NULL; 656 | } 657 | socklen_t slen = sizeof(sin); 658 | if(getsockname(fd, (struct sockaddr *)&sin, &slen)) { 659 | hap_log(ha, NULL, LOG_ERR, "getsockname(): %s", 660 | strerror(errno)); 661 | close(fd); 662 | return NULL; 663 | } 664 | 665 | ha->ha_http_listen_port = ntohs(sin.sin_port); 666 | hap_log(ha, NULL, LOG_INFO, "Listening on port %d", ha->ha_http_listen_port); 667 | 668 | listen(fd, 1); 669 | 670 | while(dispatch_message(ha)) {} 671 | 672 | if(hap_check_config_hash(ha)) 673 | hap_accessory_lts_save(ha, false); 674 | 675 | AvahiClient *mdns_client = 676 | avahi_client_new(ha->ha_ap, AVAHI_CLIENT_NO_FAIL, client_callback, ha, 677 | NULL); 678 | 679 | AvahiWatch *http_server_watch = 680 | ha->ha_ap->watch_new(ha->ha_ap, fd, AVAHI_WATCH_IN, 681 | http_new_connection, ha); 682 | 683 | if(ha->ha_run == 1) 684 | ha->ha_run = 2; 685 | 686 | while(ha->ha_run && (avahi_simple_poll_iterate(ha->ha_asp, -1)) != -1) { 687 | while(dispatch_message(ha)) {} 688 | } 689 | 690 | while(dispatch_message(ha)) {} 691 | 692 | hap_log(ha, NULL, LOG_INFO, "Shutting down"); 693 | 694 | avahi_client_free(mdns_client); 695 | ha->ha_ap->watch_free(http_server_watch); 696 | 697 | close(fd); 698 | 699 | hap_connection_t *hc; 700 | while((hc = LIST_FIRST(&ha->ha_connections)) != NULL) 701 | http_delete_connection(hc); 702 | 703 | hap_remove_stale_service_state(ha); 704 | 705 | hap_accessory_lts_save(ha, false); 706 | 707 | avahi_simple_poll_free(ha->ha_asp); 708 | return NULL; 709 | } 710 | 711 | void 712 | hap_msg_enqueue(hap_accessory_t *ha, hap_msg_t *hm, 713 | int (*cb)(hap_accessory_t *ha, hap_msg_t *hm)) 714 | { 715 | hm->hm_cb = cb; 716 | pthread_mutex_lock(&ha->ha_msg_mutex); 717 | TAILQ_INSERT_TAIL(&ha->ha_msg_queue, hm, hm_link); 718 | pthread_mutex_unlock(&ha->ha_msg_mutex); 719 | avahi_simple_poll_wakeup(ha->ha_asp); 720 | } 721 | 722 | void 723 | hap_network_init(hap_accessory_t *ha) 724 | { 725 | ha->ha_asp = avahi_simple_poll_new(); 726 | } 727 | 728 | 729 | void 730 | hap_network_start(hap_accessory_t *ha) 731 | { 732 | ha->ha_run = 1; 733 | pthread_create(&ha->ha_tid, NULL, hap_thread, ha); 734 | } 735 | 736 | 737 | static int 738 | hap_stop_on_thread(hap_accessory_t *ha, hap_msg_t *hm) 739 | { 740 | ha->ha_run = 0; 741 | return 0; 742 | } 743 | 744 | void 745 | hap_network_stop(hap_accessory_t *ha) 746 | { 747 | hap_msg_t *hm = calloc(1, sizeof(hap_msg_t)); 748 | hap_msg_enqueue(ha, hm, hap_stop_on_thread); 749 | pthread_join(ha->ha_tid, NULL); 750 | } 751 | 752 | 753 | -------------------------------------------------------------------------------- /src/hap_pairing.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2019 Andreas Smas 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "hap_i.h" 34 | 35 | // Table 5.5 36 | #define kTLVError_Authentication 0x02 37 | #define kTLVError_Unavailable 0x06 38 | 39 | // Table 5.6 40 | #define kTLVType_Method 0x00 41 | #define kTLVType_Identifier 0x01 42 | #define kTLVType_Salt 0x02 43 | #define kTLVType_PublicKey 0x03 44 | #define kTLVType_Proof 0x04 45 | #define kTLVType_EncryptedData 0x05 46 | #define kTLVType_State 0x06 47 | #define kTLVType_Error 0x07 48 | #define kTLVType_RetryDelay 0x08 49 | #define kTLVType_Certificate 0x09 50 | #define kTLVType_Signature 0x0A 51 | #define kTLVType_Permissions 0x0B 52 | #define kTLVType_FragmentData 0x0C 53 | #define kTLVType_FragmentLast 0x0D 54 | #define kTLVType_Flags 0x13 55 | #define kTLVType_Separator 0xFF 56 | 57 | 58 | 59 | 60 | 61 | typedef struct pair_setup_ctx { 62 | 63 | const BIGNUM *N; 64 | const BIGNUM *g; 65 | 66 | uint8_t salt[16]; 67 | 68 | BIGNUM *b; // our private key 69 | BIGNUM *B; // our public key 70 | 71 | BIGNUM *A; // Client's public key 72 | BIGNUM *v; // Verifier 73 | 74 | uint8_t K[64]; // Session Key 75 | 76 | uint8_t client_proof[64]; 77 | uint8_t server_proof[64]; 78 | 79 | } pair_setup_ctx_t; 80 | 81 | 82 | 83 | static void 84 | digest_bn(EVP_MD_CTX *ctx, const BIGNUM *bn) 85 | { 86 | const int len = BN_num_bytes(bn); 87 | uint8_t *data = alloca(len); 88 | BN_bn2bin(bn, data); 89 | EVP_DigestUpdate(ctx, data, len); 90 | } 91 | 92 | static BIGNUM * 93 | srp_padded_digest(const BIGNUM *x, const BIGNUM *y, const BIGNUM *N) 94 | { 95 | const int nlen = BN_num_bytes(N); 96 | uint8_t digest[64]; 97 | uint8_t buf[nlen * 2]; 98 | 99 | BN_bn2binpad(x, buf, nlen); 100 | BN_bn2binpad(y, buf + nlen, nlen); 101 | EVP_Digest(buf, nlen * 2, digest, NULL, EVP_sha512(), NULL); 102 | return BN_bin2bn(digest, sizeof(digest), NULL); 103 | } 104 | 105 | 106 | static BIGNUM * 107 | srp_calc_b(const BIGNUM *b, const BIGNUM *N, const BIGNUM *g, const BIGNUM *v) 108 | { 109 | BIGNUM *k = srp_padded_digest(N, g, N); 110 | BN_CTX *ctx = BN_CTX_new(); 111 | BIGNUM *B = BN_new(); 112 | BIGNUM *t0 = BN_new(); 113 | BIGNUM *t1 = BN_new(); 114 | 115 | BN_mod_exp(t0, g, b, N, ctx); 116 | BN_mod_mul(t1, v, k, N, ctx); 117 | BN_mod_add(B, t0, t1, N, ctx); 118 | 119 | BN_CTX_free(ctx); 120 | BN_clear_free(t0); 121 | BN_clear_free(t1); 122 | BN_clear_free(k); 123 | return B; 124 | } 125 | 126 | 127 | static pair_setup_ctx_t * 128 | pair_setup_ctx_init(const uint8_t *salt, const uint8_t *b, 129 | const char *username, const char *password) 130 | { 131 | uint8_t rb[32]; 132 | if(b == NULL) { 133 | if(hap_get_random_bytes(rb, sizeof(rb))) { 134 | return NULL; 135 | } 136 | b = rb; 137 | } 138 | 139 | pair_setup_ctx_t *psc = calloc(1, sizeof(pair_setup_ctx_t)); 140 | 141 | if(salt != NULL) { 142 | memcpy(psc->salt, salt, sizeof(psc->salt)); 143 | } else { 144 | if(hap_get_random_bytes(psc->salt, sizeof(psc->salt))) { 145 | free(psc); 146 | return NULL; 147 | } 148 | } 149 | 150 | SRP_gN *gN = SRP_get_default_gN("3072"); 151 | psc->N = gN->N; 152 | psc->g = gN->g; 153 | 154 | EVP_MD_CTX *ctx = EVP_MD_CTX_new(); 155 | unsigned char digest[64]; 156 | 157 | EVP_DigestInit_ex(ctx, EVP_sha512(), NULL); 158 | EVP_DigestUpdate(ctx, username, strlen(username)); 159 | EVP_DigestUpdate(ctx, ":", 1); 160 | EVP_DigestUpdate(ctx, password, strlen(password)); 161 | EVP_DigestFinal_ex(ctx, digest, NULL); 162 | 163 | EVP_DigestInit_ex(ctx, EVP_sha512(), NULL); 164 | EVP_DigestUpdate(ctx, psc->salt, sizeof(psc->salt)); 165 | EVP_DigestUpdate(ctx, digest, sizeof(digest)); 166 | EVP_DigestFinal_ex(ctx, digest, NULL); 167 | EVP_MD_CTX_free(ctx); 168 | 169 | BIGNUM *x = BN_bin2bn(digest, sizeof(digest), NULL); 170 | BN_CTX *bn_ctx = BN_CTX_new(); 171 | 172 | psc->v = BN_new(); 173 | BN_mod_exp(psc->v, psc->g, x, psc->N, bn_ctx); 174 | BN_CTX_free(bn_ctx); 175 | BN_clear_free(x); 176 | 177 | psc->b = BN_bin2bn(b, 32, NULL); 178 | psc->B = srp_calc_b(psc->b, psc->N, psc->g, psc->v); 179 | return psc; 180 | } 181 | 182 | 183 | static void 184 | pair_setup_ctx_calc_u_S_K(pair_setup_ctx_t *psc) 185 | { 186 | BIGNUM *u = srp_padded_digest(psc->A, psc->B, psc->N); 187 | BIGNUM *S = SRP_Calc_server_key(psc->A, psc->v, u, psc->b, psc->N); 188 | 189 | EVP_MD_CTX *ctx = EVP_MD_CTX_new(); 190 | EVP_DigestInit_ex(ctx, EVP_sha512(), NULL); 191 | digest_bn(ctx, S); 192 | EVP_DigestFinal_ex(ctx, psc->K, NULL); 193 | EVP_MD_CTX_free(ctx); 194 | BN_clear_free(u); 195 | BN_clear_free(S); 196 | } 197 | 198 | static void 199 | pair_setup_ctx_calc_proof(pair_setup_ctx_t *psc, const char *username) 200 | { 201 | uint8_t d1[64]; 202 | uint8_t d2[64]; 203 | 204 | EVP_MD_CTX *ctx = EVP_MD_CTX_new(); 205 | EVP_DigestInit_ex(ctx, EVP_sha512(), NULL); 206 | digest_bn(ctx, psc->N); 207 | EVP_DigestFinal_ex(ctx, d1, NULL); 208 | 209 | EVP_DigestInit_ex(ctx, EVP_sha512(), NULL); 210 | digest_bn(ctx, psc->g); 211 | EVP_DigestFinal_ex(ctx, d2, NULL); 212 | 213 | for(int i = 0; i < sizeof(d1); i++) 214 | d1[i] ^= d2[i]; 215 | 216 | EVP_DigestInit_ex(ctx, EVP_sha512(), NULL); 217 | EVP_DigestUpdate(ctx, username, strlen(username)); 218 | EVP_DigestFinal_ex(ctx, d2, NULL); 219 | 220 | EVP_DigestInit_ex(ctx, EVP_sha512(), NULL); 221 | EVP_DigestUpdate(ctx, d1, sizeof(d1)); 222 | EVP_DigestUpdate(ctx, d2, sizeof(d2)); 223 | EVP_DigestUpdate(ctx, psc->salt, sizeof(psc->salt)); 224 | digest_bn(ctx, psc->A); 225 | digest_bn(ctx, psc->B); 226 | EVP_DigestUpdate(ctx, psc->K, sizeof(psc->K)); 227 | EVP_DigestFinal_ex(ctx, psc->client_proof, NULL); 228 | 229 | EVP_DigestInit_ex(ctx, EVP_sha512(), NULL); 230 | digest_bn(ctx, psc->A); 231 | EVP_DigestUpdate(ctx, psc->client_proof, sizeof(psc->client_proof)); 232 | EVP_DigestUpdate(ctx, psc->K, sizeof(psc->K)); 233 | EVP_DigestFinal_ex(ctx, psc->server_proof, NULL); 234 | 235 | EVP_MD_CTX_free(ctx); 236 | 237 | } 238 | 239 | 240 | 241 | void 242 | hap_pair_setup_ctx_free(struct pair_setup_ctx *psc) 243 | { 244 | BN_clear_free(psc->b); 245 | BN_clear_free(psc->B); 246 | BN_clear_free(psc->A); 247 | BN_clear_free(psc->v); 248 | free(psc); 249 | } 250 | 251 | 252 | static void 253 | derive_key(const uint8_t *K, size_t K_len, uint8_t *output, size_t output_len, 254 | const char *salt, const char *info) 255 | { 256 | EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); 257 | 258 | EVP_PKEY_derive_init(pctx); 259 | EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha512()); 260 | EVP_PKEY_CTX_set1_hkdf_salt(pctx, (const uint8_t *)salt, strlen(salt)); 261 | EVP_PKEY_CTX_set1_hkdf_key(pctx, K, K_len); 262 | EVP_PKEY_CTX_add1_hkdf_info(pctx, (const uint8_t *)info, strlen(info)); 263 | 264 | EVP_PKEY_derive(pctx, output, &output_len); 265 | EVP_PKEY_CTX_free(pctx); 266 | } 267 | 268 | 269 | 270 | static void 271 | output_tlv(buf_t *b, uint8_t type, size_t length, const void *value) 272 | { 273 | while(1) { 274 | size_t c = MIN(length, 255); 275 | buf_append_u8(b, type); 276 | buf_append_u8(b, c); 277 | buf_append(b, value, c); 278 | length -= c; 279 | if(!length) 280 | return; 281 | value += c; 282 | } 283 | } 284 | 285 | 286 | 287 | static void 288 | output_tlv_bn(buf_t *b, uint8_t type, BIGNUM *bn) 289 | { 290 | const int len = BN_num_bytes(bn); 291 | uint8_t *data = alloca(len); 292 | BN_bn2bin(bn, data); 293 | output_tlv(b, type, len, data); 294 | } 295 | 296 | 297 | struct tlv { 298 | uint8_t *value; 299 | int len; 300 | }; 301 | 302 | 303 | static int 304 | parse_tlv(uint8_t *data, int len, struct tlv *tlvs, size_t max_tlv) 305 | { 306 | memset(tlvs, 0, sizeof(struct tlv) * max_tlv); 307 | while(len > 2) { 308 | const uint8_t tlv_type = data[0]; 309 | const uint8_t tlv_length = data[1]; 310 | len -= 2; 311 | data += 2; 312 | if(len < tlv_length) 313 | return -1; 314 | 315 | if(tlv_type < max_tlv) { 316 | if(tlvs[tlv_type].value) { 317 | memmove(tlvs[tlv_type].value + tlvs[tlv_type].len, 318 | data, tlv_length); 319 | } else { 320 | tlvs[tlv_type].value = data; 321 | } 322 | tlvs[tlv_type].len += tlv_length; 323 | } 324 | data += tlv_length; 325 | len -= tlv_length; 326 | } 327 | 328 | #if 0 329 | for(int i = 0; i < max_tlv; i++) { 330 | if(tlvs[i].value) { 331 | char tmp[64]; 332 | snprintf(tmp, sizeof(tmp), "TLV-0x%02x", i); 333 | hexdump(tmp, tlvs[i].value, tlvs[i].len); 334 | } 335 | } 336 | #endif 337 | return 0; 338 | } 339 | 340 | 341 | static int 342 | decrypt_data(uint8_t *data, int len, const uint8_t *key, 343 | const uint8_t nonce[static 12]) 344 | { 345 | if(len < 17) 346 | return -1; 347 | 348 | EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); 349 | EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, NULL, NULL); 350 | 351 | EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, NULL); 352 | EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, 353 | data + len - 16); 354 | EVP_DecryptInit_ex(ctx, NULL, NULL, key, (const void *)nonce); 355 | 356 | uint8_t plaintext[len - 16]; 357 | int outlen = 0; 358 | if(!EVP_DecryptUpdate(ctx, plaintext, &outlen, data, len - 16)) { 359 | EVP_CIPHER_CTX_free(ctx); 360 | return -1; 361 | } 362 | 363 | if(!EVP_DecryptFinal_ex(ctx, plaintext, &outlen)) { 364 | EVP_CIPHER_CTX_free(ctx); 365 | return -1; 366 | } 367 | memcpy(data, plaintext, len - 16); 368 | EVP_CIPHER_CTX_free(ctx); 369 | return 0; 370 | } 371 | 372 | 373 | static int 374 | decrypt_tlv(struct tlv *tlv, const uint8_t *key, const uint8_t nonce[static 12]) 375 | { 376 | if(decrypt_data(tlv->value, tlv->len, key, nonce)) 377 | return -1; 378 | 379 | tlv->len -= 16; 380 | return 0; 381 | } 382 | 383 | 384 | static size_t 385 | encrypt_buf(uint8_t *output, size_t output_len, 386 | const buf_t *src, const uint8_t *key, 387 | const uint8_t nonce[static 12]) 388 | { 389 | const size_t total_len = src->size + 16; 390 | if(total_len > output_len) 391 | return 0; 392 | 393 | EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); 394 | EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, NULL, NULL); 395 | EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, NULL); 396 | 397 | EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce); 398 | 399 | int outlen; 400 | if(!EVP_EncryptUpdate(ctx, output, &outlen, src->data, src->size)) { 401 | EVP_CIPHER_CTX_free(ctx); 402 | return 0; 403 | } 404 | 405 | output += outlen; 406 | if(!EVP_EncryptFinal_ex(ctx, output, &outlen)) { 407 | EVP_CIPHER_CTX_free(ctx); 408 | return 0; 409 | } 410 | 411 | EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, output); 412 | EVP_CIPHER_CTX_free(ctx); 413 | return total_len; 414 | } 415 | 416 | 417 | 418 | static EVP_PKEY * 419 | make_pkey(int id) 420 | { 421 | EVP_PKEY *pkey = EVP_PKEY_new(); 422 | EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(id, NULL); 423 | EVP_PKEY_keygen_init(pctx); 424 | EVP_PKEY_keygen(pctx, &pkey); 425 | EVP_PKEY_CTX_free(pctx); 426 | return pkey; 427 | } 428 | 429 | static void 430 | long_term_sign(const hap_accessory_t *ha, uint8_t signature[static 64], 431 | const void *tbs, size_t tbs_size) 432 | { 433 | size_t AccessorySignature_size = 64; 434 | EVP_MD_CTX *ctx = EVP_MD_CTX_new(); 435 | EVP_DigestSignInit(ctx, NULL, NULL, NULL, ha->ha_long_term_key); 436 | EVP_DigestSign(ctx, signature, &AccessorySignature_size, tbs, tbs_size); 437 | EVP_MD_CTX_free(ctx); 438 | } 439 | 440 | 441 | 442 | 443 | static size_t 444 | concat3(uint8_t *output, size_t output_len, 445 | const void *a, size_t a_len, 446 | const void *b, size_t b_len, 447 | const void *c, size_t c_len) 448 | { 449 | const size_t total_len = a_len + b_len + c_len; 450 | if(total_len > output_len) 451 | return 0; 452 | 453 | memcpy(output, a, a_len); 454 | memcpy(output + a_len, b, b_len); 455 | memcpy(output + a_len + b_len, c, c_len); 456 | return total_len; 457 | } 458 | 459 | 460 | static int 461 | pair_setup_error(hap_connection_t *hc, uint8_t state, uint8_t code) 462 | { 463 | scoped_buf_t b = {}; 464 | output_tlv(&b, kTLVType_State, 1, &state); 465 | output_tlv(&b, kTLVType_Error, 1, &code); 466 | return hap_http_send_reply(hc, 200, b.data, b.size, 467 | "application/pairing+tlv8"); 468 | } 469 | 470 | static int 471 | pair_setup_m1(hap_connection_t *hc, struct tlv *tlvs) 472 | { 473 | hap_accessory_t *ha = hc->hc_ha; 474 | if(LIST_FIRST(&ha->ha_peers) != NULL) { 475 | return pair_setup_error(hc, 4, kTLVError_Unavailable); 476 | } 477 | 478 | pair_setup_ctx_t *psc = hc->hc_psc; 479 | 480 | scoped_buf_t reply = {}; 481 | output_tlv(&reply, kTLVType_State, 1, (const uint8_t []){2}); 482 | output_tlv(&reply, kTLVType_Salt, sizeof(psc->salt), psc->salt); 483 | output_tlv_bn(&reply, kTLVType_PublicKey, psc->B); 484 | return hap_http_send_reply(hc, 200, reply.data, reply.size, 485 | "application/pairing+tlv8"); 486 | } 487 | 488 | 489 | static int 490 | pair_setup_m3(hap_connection_t *hc, struct tlv *tlvs) 491 | { 492 | pair_setup_ctx_t *psc = hc->hc_psc; 493 | 494 | if(tlvs[kTLVType_PublicKey].value == NULL || 495 | tlvs[kTLVType_Proof].value == NULL) 496 | return -1; 497 | 498 | psc->A = BN_bin2bn(tlvs[kTLVType_PublicKey].value, 499 | tlvs[kTLVType_PublicKey].len, NULL); 500 | 501 | pair_setup_ctx_calc_u_S_K(psc); 502 | pair_setup_ctx_calc_proof(psc, "Pair-Setup"); 503 | 504 | if(tlvs[kTLVType_Proof].len != sizeof(psc->client_proof) || 505 | memcmp(tlvs[kTLVType_Proof].value, psc->client_proof, 506 | sizeof(psc->client_proof))) { 507 | hap_log(hc->hc_ha, hc, LOG_WARNING, 508 | "pair-setup: Incorrect proof received (incorrect setup code?)"); 509 | return pair_setup_error(hc, 4, kTLVError_Authentication); 510 | } 511 | 512 | scoped_buf_t reply = {}; 513 | output_tlv(&reply, kTLVType_State, 1, (const uint8_t []){4}); 514 | output_tlv(&reply, kTLVType_Proof, sizeof(psc->server_proof), 515 | psc->server_proof); 516 | return hap_http_send_reply(hc, 200, reply.data, reply.size, 517 | "application/pairing+tlv8"); 518 | } 519 | 520 | 521 | 522 | static int 523 | pair_setup_m5(hap_connection_t *hc, struct tlv *tlvs) 524 | { 525 | hap_accessory_t *ha = hc->hc_ha; 526 | pair_setup_ctx_t *psc = hc->hc_psc; 527 | uint8_t tmp[4096]; 528 | static const uint8_t nonce1[12] = {0,0,0,0,'P','S','-','M','s','g','0','5'}; 529 | 530 | if(tlvs[kTLVType_EncryptedData].value == NULL) 531 | return -1; 532 | 533 | uint8_t key[32] = {}; 534 | derive_key(psc->K, sizeof(psc->K), key, sizeof(key), 535 | "Pair-Setup-Encrypt-Salt", 536 | "Pair-Setup-Encrypt-Info"); 537 | 538 | if(decrypt_tlv(&tlvs[kTLVType_EncryptedData], key, nonce1)) { 539 | return pair_setup_error(hc, 6, kTLVError_Authentication); 540 | } 541 | 542 | struct tlv subtlvs[kTLVType_Flags + 1]; 543 | if(parse_tlv(tlvs[kTLVType_EncryptedData].value, 544 | tlvs[kTLVType_EncryptedData].len, 545 | subtlvs, ARRAYSIZE(subtlvs))) { 546 | hap_log(hc->hc_ha, hc, LOG_WARNING, 547 | "pair-setup: Corrupted encrypted TLVs"); 548 | return -1; 549 | } 550 | 551 | if(subtlvs[kTLVType_Identifier].value == NULL || 552 | subtlvs[kTLVType_PublicKey].value == NULL || 553 | subtlvs[kTLVType_Signature].value == NULL) { 554 | return -1; 555 | } 556 | 557 | derive_key(psc->K, sizeof(psc->K), key, sizeof(key), 558 | "Pair-Setup-Controller-Sign-Salt", 559 | "Pair-Setup-Controller-Sign-Info"); 560 | 561 | const size_t iOSDeviceInfo_len = 562 | concat3(tmp, sizeof(tmp), 563 | key, sizeof(key), 564 | subtlvs[kTLVType_Identifier].value, 565 | subtlvs[kTLVType_Identifier].len, 566 | subtlvs[kTLVType_PublicKey].value, 567 | subtlvs[kTLVType_PublicKey].len); 568 | 569 | 570 | EVP_MD_CTX *ctx = EVP_MD_CTX_new(); 571 | EVP_PKEY *pkey = 572 | EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, 573 | subtlvs[kTLVType_PublicKey].value, 574 | subtlvs[kTLVType_PublicKey].len); 575 | 576 | EVP_DigestVerifyInit(ctx, NULL, NULL, NULL, pkey); 577 | int rv = EVP_DigestVerify(ctx, 578 | subtlvs[kTLVType_Signature].value, 579 | subtlvs[kTLVType_Signature].len, 580 | tmp, iOSDeviceInfo_len); 581 | EVP_MD_CTX_free(ctx); 582 | EVP_PKEY_free(pkey); 583 | pkey = NULL; 584 | if(rv != 1) 585 | return pair_setup_error(hc, 6, kTLVError_Authentication); 586 | 587 | hap_peer_t *hp = calloc(1, sizeof(hap_peer_t)); 588 | hp->hp_id = strndup((const void *)subtlvs[kTLVType_Identifier].value, 589 | subtlvs[kTLVType_Identifier].len); 590 | 591 | memcpy(hp->hp_public_key, subtlvs[kTLVType_PublicKey].value, 592 | sizeof(hp->hp_public_key)); 593 | LIST_INSERT_HEAD(&ha->ha_peers, hp, hp_link); 594 | hp->hp_flags = HAP_PEER_ADMIN; 595 | hap_accessory_lts_save(ha, false); 596 | 597 | hap_log(hc->hc_ha, hc, LOG_INFO, "Added first peer %s, admin", hp->hp_id); 598 | 599 | // Generate reply 600 | 601 | uint8_t *AccessoryLTPK; 602 | size_t AccessoryLTPK_size; 603 | EVP_PKEY_get_raw_public_key(hc->hc_ha->ha_long_term_key, NULL, 604 | &AccessoryLTPK_size); 605 | AccessoryLTPK = alloca(AccessoryLTPK_size); 606 | memset(AccessoryLTPK, 0, AccessoryLTPK_size); 607 | EVP_PKEY_get_raw_public_key(hc->hc_ha->ha_long_term_key, AccessoryLTPK, 608 | &AccessoryLTPK_size); 609 | 610 | derive_key(psc->K, sizeof(psc->K), key, sizeof(key), 611 | "Pair-Setup-Accessory-Sign-Salt", 612 | "Pair-Setup-Accessory-Sign-Info"); 613 | 614 | const size_t AccessoryInfo_len = 615 | concat3(tmp, sizeof(tmp), 616 | key, sizeof(key), 617 | hc->hc_ha->ha_id, strlen(hc->hc_ha->ha_id), 618 | AccessoryLTPK, AccessoryLTPK_size); 619 | 620 | uint8_t AccessorySignature[64]; 621 | long_term_sign(hc->hc_ha, AccessorySignature, tmp, AccessoryInfo_len); 622 | 623 | scoped_buf_t subtlv = {}; 624 | 625 | output_tlv(&subtlv, kTLVType_Identifier, strlen(hc->hc_ha->ha_id), 626 | hc->hc_ha->ha_id); 627 | output_tlv(&subtlv, kTLVType_PublicKey, AccessoryLTPK_size, 628 | AccessoryLTPK); 629 | output_tlv(&subtlv, kTLVType_Signature, sizeof(AccessorySignature), 630 | AccessorySignature); 631 | 632 | derive_key(psc->K, sizeof(psc->K), key, sizeof(key), 633 | "Pair-Setup-Encrypt-Salt", 634 | "Pair-Setup-Encrypt-Info"); 635 | 636 | static const uint8_t nonce2[12] = {0,0,0,0,'P','S','-','M','s','g','0','6'}; 637 | 638 | size_t EncryptedData_len = encrypt_buf(tmp, sizeof(tmp), &subtlv, key, nonce2); 639 | 640 | scoped_buf_t reply = {}; 641 | output_tlv(&reply, kTLVType_State, 1, (const uint8_t []){6}); 642 | output_tlv(&reply, kTLVType_EncryptedData, EncryptedData_len, tmp); 643 | 644 | if(hap_http_send_reply(hc, 200, reply.data, reply.size, 645 | "application/pairing+tlv8")) 646 | return -1; 647 | 648 | hap_mdns_update(ha); 649 | return 0; 650 | } 651 | 652 | 653 | int 654 | hap_pair_setup(hap_connection_t *hc, enum http_method method, 655 | uint8_t *request_body, size_t request_body_len, 656 | const hap_query_args_t *qa) 657 | { 658 | hap_accessory_t *ha = hc->hc_ha; 659 | scoped_buf_t reply = {}; 660 | 661 | if(hc->hc_psc == NULL) { 662 | hc->hc_psc = pair_setup_ctx_init(NULL, NULL, "Pair-Setup", 663 | ha->ha_password); 664 | if(hc->hc_psc == NULL) { 665 | hap_log(ha, hc, LOG_WARNING, "pair-setup: Unable to initialize context"); 666 | return -1; 667 | } 668 | } 669 | struct tlv tlvs[kTLVType_Flags + 1]; 670 | 671 | if(parse_tlv(request_body, request_body_len, tlvs, ARRAYSIZE(tlvs))) { 672 | hap_log(ha, hc, LOG_WARNING, "pair-setup: Corrupted TLVs"); 673 | return -1; 674 | } 675 | 676 | if(!tlvs[kTLVType_State].value || tlvs[kTLVType_State].len != 1) { 677 | hap_log(ha, hc, LOG_WARNING, "pair-setup: No state"); 678 | return -1; 679 | } 680 | const uint8_t state = tlvs[kTLVType_State].value[0]; 681 | hap_log(hc->hc_ha, hc, LOG_DEBUG, "pair-setup: In state %d", state); 682 | 683 | switch(state) { 684 | 685 | case 1: 686 | return pair_setup_m1(hc, tlvs); 687 | case 3: 688 | return pair_setup_m3(hc, tlvs); 689 | case 5: 690 | return pair_setup_m5(hc, tlvs); 691 | default: 692 | return -1; 693 | } 694 | } 695 | 696 | 697 | // 5.7.2 M2: Accessory - > iOS Device – ‘Verify Start Responseʼ 698 | static int 699 | pair_verify_m2(hap_connection_t *hc, struct tlv *tlvs) 700 | { 701 | if(tlvs[kTLVType_PublicKey].value == NULL) 702 | return -1; 703 | 704 | hap_accessory_t *ha = hc->hc_ha; 705 | uint8_t SharedSecret[32]; 706 | uint8_t tmp[4096]; 707 | 708 | EVP_PKEY *pkey = make_pkey(EVP_PKEY_X25519); 709 | 710 | EVP_PKEY *peer = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL, 711 | tlvs[kTLVType_PublicKey].value, 712 | tlvs[kTLVType_PublicKey].len); 713 | 714 | EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(pkey, NULL); 715 | EVP_PKEY_derive_init(pctx); 716 | EVP_PKEY_derive_set_peer(pctx, peer); 717 | size_t size = sizeof(SharedSecret); 718 | EVP_PKEY_derive(pctx, SharedSecret, &size); 719 | 720 | EVP_PKEY_CTX_free(pctx); 721 | EVP_PKEY_free(peer); 722 | 723 | uint8_t *AccessoryPK; 724 | size_t AccessoryPK_size; 725 | EVP_PKEY_get_raw_public_key(pkey, NULL, &AccessoryPK_size); 726 | AccessoryPK = alloca(AccessoryPK_size); 727 | EVP_PKEY_get_raw_public_key(pkey, AccessoryPK, &AccessoryPK_size); 728 | EVP_PKEY_free(pkey); 729 | 730 | const size_t AccessoryInfo_len = 731 | concat3(tmp, sizeof(tmp), 732 | AccessoryPK, AccessoryPK_size, 733 | ha->ha_id, strlen(ha->ha_id), 734 | tlvs[kTLVType_PublicKey].value, tlvs[kTLVType_PublicKey].len); 735 | 736 | 737 | uint8_t AccessorySignature[64]; 738 | long_term_sign(ha, AccessorySignature, tmp, AccessoryInfo_len); 739 | 740 | scoped_buf_t subtlv = {}; 741 | output_tlv(&subtlv, kTLVType_Identifier, strlen(ha->ha_id), 742 | ha->ha_id); 743 | output_tlv(&subtlv, kTLVType_Signature, sizeof(AccessorySignature), 744 | AccessorySignature); 745 | 746 | derive_key(SharedSecret, sizeof(SharedSecret), 747 | hc->hc_SessionKey, sizeof(hc->hc_SessionKey), 748 | "Pair-Verify-Encrypt-Salt", 749 | "Pair-Verify-Encrypt-Info"); 750 | 751 | derive_key(SharedSecret, sizeof(SharedSecret), 752 | hc->hc_SendKey, sizeof(hc->hc_SendKey), 753 | "Control-Salt", 754 | "Control-Read-Encryption-Key"); 755 | 756 | derive_key(SharedSecret, sizeof(SharedSecret), 757 | hc->hc_RecvKey, sizeof(hc->hc_RecvKey), 758 | "Control-Salt", 759 | "Control-Write-Encryption-Key"); 760 | 761 | static const uint8_t nonce[12] = {0,0,0,0,'P','V','-','M','s','g','0','2'}; 762 | 763 | size_t EncryptedData_len = 764 | encrypt_buf(tmp, sizeof(tmp), &subtlv, hc->hc_SessionKey, nonce); 765 | 766 | scoped_buf_t reply = {}; 767 | output_tlv(&reply, kTLVType_State, 1, (const uint8_t []){2}); 768 | output_tlv(&reply, kTLVType_PublicKey, AccessoryPK_size, AccessoryPK); 769 | output_tlv(&reply, kTLVType_EncryptedData, EncryptedData_len, tmp); 770 | return hap_http_send_reply(hc, 200, reply.data, reply.size, 771 | "application/pairing+tlv8"); 772 | } 773 | 774 | // 5.7.4 M4: Accessory - > iOS Device – ‘Verify Finish Responseʼ 775 | static int 776 | pair_verify_m4(hap_connection_t *hc, struct tlv *tlvs) 777 | { 778 | static const uint8_t nonce[12] = {0,0,0,0,'P','V','-','M','s','g','0','3'}; 779 | 780 | if(tlvs[kTLVType_EncryptedData].value == NULL) 781 | return -1; 782 | 783 | hap_accessory_t *ha = hc->hc_ha; 784 | if(decrypt_tlv(&tlvs[kTLVType_EncryptedData], hc->hc_SessionKey, nonce)) { 785 | return pair_setup_error(hc, 4, kTLVError_Authentication); 786 | } 787 | 788 | struct tlv subtlvs[kTLVType_Flags + 1]; 789 | 790 | if(parse_tlv(tlvs[kTLVType_EncryptedData].value, 791 | tlvs[kTLVType_EncryptedData].len, 792 | subtlvs, ARRAYSIZE(subtlvs))) { 793 | hap_log(hc->hc_ha, hc, LOG_WARNING, 794 | "pair-setup: Corrupted encrypted TLVs"); 795 | return -1; 796 | } 797 | 798 | if(subtlvs[kTLVType_Identifier].value == NULL) 799 | return -1; 800 | 801 | scoped_char *peername = 802 | strndup((const char *)subtlvs[kTLVType_Identifier].value, 803 | subtlvs[kTLVType_Identifier].len); 804 | 805 | hap_peer_t *hp; 806 | LIST_FOREACH(hp, &ha->ha_peers, hp_link) { 807 | if(!strcmp(hp->hp_id, peername)) 808 | break; 809 | } 810 | 811 | if(hp == NULL) { 812 | return pair_setup_error(hc, 4, kTLVError_Authentication); 813 | } 814 | 815 | scoped_buf_t reply = {}; 816 | output_tlv(&reply, kTLVType_State, 1, (const uint8_t []){4}); 817 | if(hap_http_send_reply(hc, 200, reply.data, reply.size, 818 | "application/pairing+tlv8")) 819 | return -1; 820 | // We're fully verified, turn on encryption 821 | hc->hc_cipher_context = EVP_CIPHER_CTX_new(); 822 | hc->hc_peer_flags = hp->hp_flags; 823 | 824 | hap_log(hc->hc_ha, hc, LOG_DEBUG, "Peer verified, encryption enabled"); 825 | 826 | char *log_prefix = fmt("%s [%s]", hc->hc_log_prefix, peername); 827 | free(hc->hc_log_prefix); 828 | hc->hc_log_prefix = log_prefix; 829 | return 0; 830 | } 831 | 832 | 833 | int 834 | hap_pair_verify(hap_connection_t *hc, enum http_method method, 835 | uint8_t *request_body, size_t request_body_len, 836 | const hap_query_args_t *qa) 837 | { 838 | hap_accessory_t *ha = hc->hc_ha; 839 | struct tlv tlvs[kTLVType_Flags + 1]; 840 | 841 | if(parse_tlv(request_body, request_body_len, tlvs, ARRAYSIZE(tlvs))) { 842 | hap_log(ha, hc, LOG_WARNING, "pair-verify: Corrupted TLVs"); 843 | return -1; 844 | } 845 | 846 | if(!tlvs[kTLVType_State].value || tlvs[kTLVType_State].len != 1) { 847 | hap_log(ha, hc, LOG_WARNING, "pair-verify: No state"); 848 | return -1; 849 | } 850 | const uint8_t state = tlvs[kTLVType_State].value[0]; 851 | hap_log(ha, hc, LOG_DEBUG, "pair-verify: In state %d", state); 852 | 853 | switch(state) { 854 | case 1: 855 | return pair_verify_m2(hc, tlvs); 856 | case 3: 857 | return pair_verify_m4(hc, tlvs); 858 | default: 859 | return -1; 860 | } 861 | } 862 | 863 | 864 | static int 865 | pairing_add(hap_connection_t *hc, struct tlv *tlvs) 866 | { 867 | if(tlvs[kTLVType_Identifier].value == NULL) 868 | return -1; 869 | 870 | hap_accessory_t *ha = hc->hc_ha; 871 | 872 | if(!(hc->hc_peer_flags & HAP_PEER_ADMIN)) 873 | return pair_setup_error(hc, 2, kTLVError_Authentication); 874 | 875 | scoped_char *peername = 876 | strndup((const char *)tlvs[kTLVType_Identifier].value, 877 | tlvs[kTLVType_Identifier].len); 878 | 879 | if(tlvs[kTLVType_PublicKey].len != 32) 880 | return -1; 881 | 882 | hap_peer_t *hp; 883 | LIST_FOREACH(hp, &ha->ha_peers, hp_link) { 884 | if(!strcmp(hp->hp_id, peername)) 885 | break; 886 | } 887 | 888 | const char *verb; 889 | 890 | if(hp == NULL) { 891 | hp = calloc(1, sizeof(hap_peer_t)); 892 | hp->hp_id = strdup(peername); 893 | LIST_INSERT_HEAD(&ha->ha_peers, hp, hp_link); 894 | memcpy(hp->hp_public_key, tlvs[kTLVType_PublicKey].value, 895 | sizeof(hp->hp_public_key)); 896 | verb = "Added"; 897 | } else { 898 | 899 | if(memcmp(hp->hp_public_key, tlvs[kTLVType_PublicKey].value, 900 | sizeof(hp->hp_public_key))) { 901 | 902 | return pair_setup_error(hc, 2, kTLVError_Authentication); 903 | } 904 | verb = "Updated"; 905 | } 906 | 907 | hp->hp_flags = tlvs[kTLVType_Permissions].len == 1 ? 908 | tlvs[kTLVType_Permissions].value[0] : 0; 909 | 910 | hap_log(hc->hc_ha, hc, LOG_INFO, 911 | "%s peer %s%s", verb, hp->hp_id, hp->hp_flags & 912 | HAP_PEER_ADMIN ? ", admin" : ""); 913 | 914 | hap_accessory_lts_save(ha, false); 915 | 916 | scoped_buf_t reply = {}; 917 | output_tlv(&reply, kTLVType_State, 1, (const uint8_t []){2}); 918 | return hap_http_send_reply(hc, 200, reply.data, reply.size, 919 | "application/pairing+tlv8"); 920 | } 921 | 922 | static int 923 | pairing_remove(hap_connection_t *hc, struct tlv *tlvs) 924 | { 925 | if(tlvs[kTLVType_Identifier].value == NULL) 926 | return -1; 927 | 928 | hap_accessory_t *ha = hc->hc_ha; 929 | 930 | if(!(hc->hc_peer_flags & HAP_PEER_ADMIN)) 931 | return pair_setup_error(hc, 2, kTLVError_Authentication); 932 | 933 | scoped_char *peername = 934 | strndup((const char *)tlvs[kTLVType_Identifier].value, 935 | tlvs[kTLVType_Identifier].len); 936 | 937 | hap_peer_t *hp; 938 | LIST_FOREACH(hp, &ha->ha_peers, hp_link) { 939 | if(!strcmp(hp->hp_id, peername)) 940 | break; 941 | } 942 | 943 | if(hp != NULL) { 944 | hap_log(hc->hc_ha, hc, LOG_INFO, 945 | "Removed peer %s%s", hp->hp_id, hp->hp_flags & 946 | HAP_PEER_ADMIN ? ", admin" : ""); 947 | free(hp->hp_id); 948 | LIST_REMOVE(hp, hp_link); 949 | free(hp); 950 | } 951 | 952 | hap_accessory_lts_save(ha, false); 953 | 954 | scoped_buf_t reply = {}; 955 | output_tlv(&reply, kTLVType_State, 1, (const uint8_t []){2}); 956 | return hap_http_send_reply(hc, 200, reply.data, reply.size, 957 | "application/pairing+tlv8"); 958 | } 959 | 960 | 961 | int 962 | hap_pairings(hap_connection_t *hc, enum http_method http_method, 963 | uint8_t *request_body, size_t request_body_len, 964 | const hap_query_args_t *qa) 965 | { 966 | hap_accessory_t *ha = hc->hc_ha; 967 | struct tlv tlvs[kTLVType_Flags + 1]; 968 | 969 | if(parse_tlv(request_body, request_body_len, tlvs, ARRAYSIZE(tlvs))) { 970 | hap_log(ha, hc, LOG_WARNING, "pairings: Corrupted TLVs"); 971 | return -1; 972 | } 973 | 974 | if(!tlvs[kTLVType_Method].value || tlvs[kTLVType_Method].len != 1) { 975 | hap_log(ha, hc, LOG_WARNING, "pairings: No method set"); 976 | return -1; 977 | } 978 | const uint8_t method = tlvs[kTLVType_Method].value[0]; 979 | 980 | switch(method) { 981 | case 3: // Add Pairing 982 | return pairing_add(hc, tlvs); 983 | case 4: // Remove Pairing 984 | return pairing_remove(hc, tlvs); 985 | case 5: // List Parings 986 | break; 987 | } 988 | 989 | return -1; 990 | } 991 | 992 | 993 | #if 0 994 | 995 | static void 996 | print_bignum(const char *prefix, BIGNUM *v) 997 | { 998 | fprintf(stdout, "%s=", prefix); 999 | BN_print_fp(stdout, v); 1000 | printf("\n"); 1001 | } 1002 | 1003 | static void 1004 | srp_test(void) 1005 | { 1006 | // 5.5.2 SRP Test Vectors 1007 | 1008 | const uint8_t test_salt[16] = { 1009 | 0xBE, 0xB2, 0x53, 0x79, 0xD1, 0xA8, 0x58, 0x1E, 1010 | 0xB5, 0xA7, 0x27, 0x67, 0x3A, 0x24, 0x41, 0xEE, 1011 | }; 1012 | 1013 | const uint8_t test_b[32] = { 1014 | 0xE4, 0x87, 0xCB, 0x59, 0xD3, 0x1A, 0xC5, 0x50, 1015 | 0x47, 0x1E, 0x81, 0xF0, 0x0F, 0x69, 0x28, 0xE0, 1016 | 0x1D, 0xDA, 0x08, 0xE9, 0x74, 0xA0, 0x04, 0xF4, 1017 | 0x9E, 0x61, 0xF5, 0xD1, 0x05, 0x28, 0x4D, 0x20 1018 | }; 1019 | 1020 | const uint8_t test_A[] = { 1021 | 0xFA, 0xB6, 0xF5, 0xD2, 0x61, 0x5D, 0x1E, 0x32, 1022 | 0x35, 0x12, 0xE7, 0x99, 0x1C, 0xC3, 0x74, 0x43, 1023 | 0xF4, 0x87, 0xDA, 0x60, 0x4C, 0xA8, 0xC9, 0x23, 1024 | 0x0F, 0xCB, 0x04, 0xE5, 0x41, 0xDC, 0xE6, 0x28, 1025 | 0x0B, 0x27, 0xCA, 0x46, 0x80, 0xB0, 0x37, 0x4F, 1026 | 0x17, 0x9D, 0xC3, 0xBD, 0xC7, 0x55, 0x3F, 0xE6, 1027 | 0x24, 0x59, 0x79, 0x8C, 0x70, 0x1A, 0xD8, 0x64, 1028 | 0xA9, 0x13, 0x90, 0xA2, 0x8C, 0x93, 0xB6, 0x44, 1029 | 0xAD, 0xBF, 0x9C, 0x00, 0x74, 0x5B, 0x94, 0x2B, 1030 | 0x79, 0xF9, 0x01, 0x2A, 0x21, 0xB9, 0xB7, 0x87, 1031 | 0x82, 0x31, 0x9D, 0x83, 0xA1, 0xF8, 0x36, 0x28, 1032 | 0x66, 0xFB, 0xD6, 0xF4, 0x6B, 0xFC, 0x0D, 0xDB, 1033 | 0x2E, 0x1A, 0xB6, 0xE4, 0xB4, 0x5A, 0x99, 0x06, 1034 | 0xB8, 0x2E, 0x37, 0xF0, 0x5D, 0x6F, 0x97, 0xF6, 1035 | 0xA3, 0xEB, 0x6E, 0x18, 0x20, 0x79, 0x75, 0x9C, 1036 | 0x4F, 0x68, 0x47, 0x83, 0x7B, 0x62, 0x32, 0x1A, 1037 | 0xC1, 0xB4, 0xFA, 0x68, 0x64, 0x1F, 0xCB, 0x4B, 1038 | 0xB9, 0x8D, 0xD6, 0x97, 0xA0, 0xC7, 0x36, 0x41, 1039 | 0x38, 0x5F, 0x4B, 0xAB, 0x25, 0xB7, 0x93, 0x58, 1040 | 0x4C, 0xC3, 0x9F, 0xC8, 0xD4, 0x8D, 0x4B, 0xD8, 1041 | 0x67, 0xA9, 0xA3, 0xC1, 0x0F, 0x8E, 0xA1, 0x21, 1042 | 0x70, 0x26, 0x8E, 0x34, 0xFE, 0x3B, 0xBE, 0x6F, 1043 | 0xF8, 0x99, 0x98, 0xD6, 0x0D, 0xA2, 0xF3, 0xE4, 1044 | 0x28, 0x3C, 0xBE, 0xC1, 0x39, 0x3D, 0x52, 0xAF, 1045 | 0x72, 0x4A, 0x57, 0x23, 0x0C, 0x60, 0x4E, 0x9F, 1046 | 0xBC, 0xE5, 0x83, 0xD7, 0x61, 0x3E, 0x6B, 0xFF, 1047 | 0xD6, 0x75, 0x96, 0xAD, 0x12, 0x1A, 0x87, 0x07, 1048 | 0xEE, 0xC4, 0x69, 0x44, 0x95, 0x70, 0x33, 0x68, 1049 | 0x6A, 0x15, 0x5F, 0x64, 0x4D, 0x5C, 0x58, 0x63, 1050 | 0xB4, 0x8F, 0x61, 0xBD, 0xBF, 0x19, 0xA5, 0x3E, 1051 | 0xAB, 0x6D, 0xAD, 0x0A, 0x18, 0x6B, 0x8C, 0x15, 1052 | 0x2E, 0x5F, 0x5D, 0x8C, 0xAD, 0x4B, 0x0E, 0xF8, 1053 | 0xAA, 0x4E, 0xA5, 0x00, 0x88, 0x34, 0xC3, 0xCD, 1054 | 0x34, 0x2E, 0x5E, 0x0F, 0x16, 0x7A, 0xD0, 0x45, 1055 | 0x92, 0xCD, 0x8B, 0xD2, 0x79, 0x63, 0x93, 0x98, 1056 | 0xEF, 0x9E, 0x11, 0x4D, 0xFA, 0xAA, 0xB9, 0x19, 1057 | 0xE1, 0x4E, 0x85, 0x09, 0x89, 0x22, 0x4D, 0xDD, 1058 | 0x98, 0x57, 0x6D, 0x79, 0x38, 0x5D, 0x22, 0x10, 1059 | 0x90, 0x2E, 0x9F, 0x9B, 0x1F, 0x2D, 0x86, 0xCF, 1060 | 0xA4, 0x7E, 0xE2, 0x44, 0x63, 0x54, 0x65, 0xF7, 1061 | 0x10, 0x58, 0x42, 0x1A, 0x01, 0x84, 0xBE, 0x51, 1062 | 0xDD, 0x10, 0xCC, 0x9D, 0x07, 0x9E, 0x6F, 0x16, 1063 | 0x04, 0xE7, 0xAA, 0x9B, 0x7C, 0xF7, 0x88, 0x3C, 1064 | 0x7D, 0x4C, 0xE1, 0x2B, 0x06, 0xEB, 0xE1, 0x60, 1065 | 0x81, 0xE2, 0x3F, 0x27, 0xA2, 0x31, 0xD1, 0x84, 1066 | 0x32, 0xD7, 0xD1, 0xBB, 0x55, 0xC2, 0x8A, 0xE2, 1067 | 0x1F, 0xFC, 0xF0, 0x05, 0xF5, 0x75, 0x28, 0xD1, 1068 | 0x5A, 0x88, 0x88, 0x1B, 0xB3, 0xBB, 0xB7, 0xFE, 1069 | }; 1070 | 1071 | pair_setup_ctx_t *psc = 1072 | pair_setup_ctx_init(test_salt, test_b, "alice", "password123"); 1073 | 1074 | print_bignum("b", psc->b); 1075 | print_bignum("B", psc->B); 1076 | 1077 | psc->A = BN_bin2bn(test_A, sizeof(test_A), NULL); 1078 | pair_setup_ctx_calc_u_S_K(psc); 1079 | 1080 | hexdump("K", psc->K, sizeof(psc->K)); 1081 | } 1082 | #endif 1083 | -------------------------------------------------------------------------------- /src/http_parser.h: -------------------------------------------------------------------------------- 1 | /* Copyright Joyent, Inc. and other Node contributors. All rights reserved. 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to 5 | * deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | * sell copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | * IN THE SOFTWARE. 20 | */ 21 | #ifndef http_parser_h 22 | #define http_parser_h 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | /* Also update SONAME in the Makefile whenever you change these. */ 28 | #define HTTP_PARSER_VERSION_MAJOR 2 29 | #define HTTP_PARSER_VERSION_MINOR 9 30 | #define HTTP_PARSER_VERSION_PATCH 2 31 | 32 | #include 33 | #if defined(_WIN32) && !defined(__MINGW32__) && \ 34 | (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__) 35 | #include 36 | typedef __int8 int8_t; 37 | typedef unsigned __int8 uint8_t; 38 | typedef __int16 int16_t; 39 | typedef unsigned __int16 uint16_t; 40 | typedef __int32 int32_t; 41 | typedef unsigned __int32 uint32_t; 42 | typedef __int64 int64_t; 43 | typedef unsigned __int64 uint64_t; 44 | #else 45 | #include 46 | #endif 47 | 48 | /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run 49 | * faster 50 | */ 51 | #ifndef HTTP_PARSER_STRICT 52 | # define HTTP_PARSER_STRICT 1 53 | #endif 54 | 55 | /* Maximium header size allowed. If the macro is not defined 56 | * before including this header then the default is used. To 57 | * change the maximum header size, define the macro in the build 58 | * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove 59 | * the effective limit on the size of the header, define the macro 60 | * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) 61 | */ 62 | #ifndef HTTP_MAX_HEADER_SIZE 63 | # define HTTP_MAX_HEADER_SIZE (80*1024) 64 | #endif 65 | 66 | typedef struct http_parser http_parser; 67 | typedef struct http_parser_settings http_parser_settings; 68 | 69 | 70 | /* Callbacks should return non-zero to indicate an error. The parser will 71 | * then halt execution. 72 | * 73 | * The one exception is on_headers_complete. In a HTTP_RESPONSE parser 74 | * returning '1' from on_headers_complete will tell the parser that it 75 | * should not expect a body. This is used when receiving a response to a 76 | * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: 77 | * chunked' headers that indicate the presence of a body. 78 | * 79 | * Returning `2` from on_headers_complete will tell parser that it should not 80 | * expect neither a body nor any futher responses on this connection. This is 81 | * useful for handling responses to a CONNECT request which may not contain 82 | * `Upgrade` or `Connection: upgrade` headers. 83 | * 84 | * http_data_cb does not return data chunks. It will be called arbitrarily 85 | * many times for each string. E.G. you might get 10 callbacks for "on_url" 86 | * each providing just a few characters more data. 87 | */ 88 | typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); 89 | typedef int (*http_cb) (http_parser*); 90 | 91 | 92 | /* Status Codes */ 93 | #define HTTP_STATUS_MAP(XX) \ 94 | XX(100, CONTINUE, Continue) \ 95 | XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \ 96 | XX(102, PROCESSING, Processing) \ 97 | XX(200, OK, OK) \ 98 | XX(201, CREATED, Created) \ 99 | XX(202, ACCEPTED, Accepted) \ 100 | XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \ 101 | XX(204, NO_CONTENT, No Content) \ 102 | XX(205, RESET_CONTENT, Reset Content) \ 103 | XX(206, PARTIAL_CONTENT, Partial Content) \ 104 | XX(207, MULTI_STATUS, Multi-Status) \ 105 | XX(208, ALREADY_REPORTED, Already Reported) \ 106 | XX(226, IM_USED, IM Used) \ 107 | XX(300, MULTIPLE_CHOICES, Multiple Choices) \ 108 | XX(301, MOVED_PERMANENTLY, Moved Permanently) \ 109 | XX(302, FOUND, Found) \ 110 | XX(303, SEE_OTHER, See Other) \ 111 | XX(304, NOT_MODIFIED, Not Modified) \ 112 | XX(305, USE_PROXY, Use Proxy) \ 113 | XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \ 114 | XX(308, PERMANENT_REDIRECT, Permanent Redirect) \ 115 | XX(400, BAD_REQUEST, Bad Request) \ 116 | XX(401, UNAUTHORIZED, Unauthorized) \ 117 | XX(402, PAYMENT_REQUIRED, Payment Required) \ 118 | XX(403, FORBIDDEN, Forbidden) \ 119 | XX(404, NOT_FOUND, Not Found) \ 120 | XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \ 121 | XX(406, NOT_ACCEPTABLE, Not Acceptable) \ 122 | XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \ 123 | XX(408, REQUEST_TIMEOUT, Request Timeout) \ 124 | XX(409, CONFLICT, Conflict) \ 125 | XX(410, GONE, Gone) \ 126 | XX(411, LENGTH_REQUIRED, Length Required) \ 127 | XX(412, PRECONDITION_FAILED, Precondition Failed) \ 128 | XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \ 129 | XX(414, URI_TOO_LONG, URI Too Long) \ 130 | XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \ 131 | XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \ 132 | XX(417, EXPECTATION_FAILED, Expectation Failed) \ 133 | XX(421, MISDIRECTED_REQUEST, Misdirected Request) \ 134 | XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \ 135 | XX(423, LOCKED, Locked) \ 136 | XX(424, FAILED_DEPENDENCY, Failed Dependency) \ 137 | XX(426, UPGRADE_REQUIRED, Upgrade Required) \ 138 | XX(428, PRECONDITION_REQUIRED, Precondition Required) \ 139 | XX(429, TOO_MANY_REQUESTS, Too Many Requests) \ 140 | XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \ 141 | XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \ 142 | XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \ 143 | XX(501, NOT_IMPLEMENTED, Not Implemented) \ 144 | XX(502, BAD_GATEWAY, Bad Gateway) \ 145 | XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \ 146 | XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \ 147 | XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \ 148 | XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \ 149 | XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \ 150 | XX(508, LOOP_DETECTED, Loop Detected) \ 151 | XX(510, NOT_EXTENDED, Not Extended) \ 152 | XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \ 153 | 154 | enum http_status 155 | { 156 | #define XX(num, name, string) HTTP_STATUS_##name = num, 157 | HTTP_STATUS_MAP(XX) 158 | #undef XX 159 | }; 160 | 161 | 162 | /* Request Methods */ 163 | #define HTTP_METHOD_MAP(XX) \ 164 | XX(0, DELETE, DELETE) \ 165 | XX(1, GET, GET) \ 166 | XX(2, HEAD, HEAD) \ 167 | XX(3, POST, POST) \ 168 | XX(4, PUT, PUT) \ 169 | /* pathological */ \ 170 | XX(5, CONNECT, CONNECT) \ 171 | XX(6, OPTIONS, OPTIONS) \ 172 | XX(7, TRACE, TRACE) \ 173 | /* WebDAV */ \ 174 | XX(8, COPY, COPY) \ 175 | XX(9, LOCK, LOCK) \ 176 | XX(10, MKCOL, MKCOL) \ 177 | XX(11, MOVE, MOVE) \ 178 | XX(12, PROPFIND, PROPFIND) \ 179 | XX(13, PROPPATCH, PROPPATCH) \ 180 | XX(14, SEARCH, SEARCH) \ 181 | XX(15, UNLOCK, UNLOCK) \ 182 | XX(16, BIND, BIND) \ 183 | XX(17, REBIND, REBIND) \ 184 | XX(18, UNBIND, UNBIND) \ 185 | XX(19, ACL, ACL) \ 186 | /* subversion */ \ 187 | XX(20, REPORT, REPORT) \ 188 | XX(21, MKACTIVITY, MKACTIVITY) \ 189 | XX(22, CHECKOUT, CHECKOUT) \ 190 | XX(23, MERGE, MERGE) \ 191 | /* upnp */ \ 192 | XX(24, MSEARCH, M-SEARCH) \ 193 | XX(25, NOTIFY, NOTIFY) \ 194 | XX(26, SUBSCRIBE, SUBSCRIBE) \ 195 | XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ 196 | /* RFC-5789 */ \ 197 | XX(28, PATCH, PATCH) \ 198 | XX(29, PURGE, PURGE) \ 199 | /* CalDAV */ \ 200 | XX(30, MKCALENDAR, MKCALENDAR) \ 201 | /* RFC-2068, section 19.6.1.2 */ \ 202 | XX(31, LINK, LINK) \ 203 | XX(32, UNLINK, UNLINK) \ 204 | /* icecast */ \ 205 | XX(33, SOURCE, SOURCE) \ 206 | 207 | enum http_method 208 | { 209 | #define XX(num, name, string) HTTP_##name = num, 210 | HTTP_METHOD_MAP(XX) 211 | #undef XX 212 | }; 213 | 214 | 215 | enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; 216 | 217 | 218 | /* Flag values for http_parser.flags field */ 219 | enum flags 220 | { F_CHUNKED = 1 << 0 221 | , F_CONNECTION_KEEP_ALIVE = 1 << 1 222 | , F_CONNECTION_CLOSE = 1 << 2 223 | , F_CONNECTION_UPGRADE = 1 << 3 224 | , F_TRAILING = 1 << 4 225 | , F_UPGRADE = 1 << 5 226 | , F_SKIPBODY = 1 << 6 227 | , F_CONTENTLENGTH = 1 << 7 228 | }; 229 | 230 | 231 | /* Map for errno-related constants 232 | * 233 | * The provided argument should be a macro that takes 2 arguments. 234 | */ 235 | #define HTTP_ERRNO_MAP(XX) \ 236 | /* No error */ \ 237 | XX(OK, "success") \ 238 | \ 239 | /* Callback-related errors */ \ 240 | XX(CB_message_begin, "the on_message_begin callback failed") \ 241 | XX(CB_url, "the on_url callback failed") \ 242 | XX(CB_header_field, "the on_header_field callback failed") \ 243 | XX(CB_header_value, "the on_header_value callback failed") \ 244 | XX(CB_headers_complete, "the on_headers_complete callback failed") \ 245 | XX(CB_body, "the on_body callback failed") \ 246 | XX(CB_message_complete, "the on_message_complete callback failed") \ 247 | XX(CB_status, "the on_status callback failed") \ 248 | XX(CB_chunk_header, "the on_chunk_header callback failed") \ 249 | XX(CB_chunk_complete, "the on_chunk_complete callback failed") \ 250 | \ 251 | /* Parsing-related errors */ \ 252 | XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ 253 | XX(HEADER_OVERFLOW, \ 254 | "too many header bytes seen; overflow detected") \ 255 | XX(CLOSED_CONNECTION, \ 256 | "data received after completed connection: close message") \ 257 | XX(INVALID_VERSION, "invalid HTTP version") \ 258 | XX(INVALID_STATUS, "invalid HTTP status code") \ 259 | XX(INVALID_METHOD, "invalid HTTP method") \ 260 | XX(INVALID_URL, "invalid URL") \ 261 | XX(INVALID_HOST, "invalid host") \ 262 | XX(INVALID_PORT, "invalid port") \ 263 | XX(INVALID_PATH, "invalid path") \ 264 | XX(INVALID_QUERY_STRING, "invalid query string") \ 265 | XX(INVALID_FRAGMENT, "invalid fragment") \ 266 | XX(LF_EXPECTED, "LF character expected") \ 267 | XX(INVALID_HEADER_TOKEN, "invalid character in header") \ 268 | XX(INVALID_CONTENT_LENGTH, \ 269 | "invalid character in content-length header") \ 270 | XX(UNEXPECTED_CONTENT_LENGTH, \ 271 | "unexpected content-length header") \ 272 | XX(INVALID_CHUNK_SIZE, \ 273 | "invalid character in chunk size header") \ 274 | XX(INVALID_CONSTANT, "invalid constant string") \ 275 | XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ 276 | XX(STRICT, "strict mode assertion failed") \ 277 | XX(PAUSED, "parser is paused") \ 278 | XX(UNKNOWN, "an unknown error occurred") 279 | 280 | 281 | /* Define HPE_* values for each errno value above */ 282 | #define HTTP_ERRNO_GEN(n, s) HPE_##n, 283 | enum http_errno { 284 | HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) 285 | }; 286 | #undef HTTP_ERRNO_GEN 287 | 288 | 289 | /* Get an http_errno value from an http_parser */ 290 | #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) 291 | 292 | 293 | struct http_parser { 294 | /** PRIVATE **/ 295 | unsigned int type : 2; /* enum http_parser_type */ 296 | unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */ 297 | unsigned int state : 7; /* enum state from http_parser.c */ 298 | unsigned int header_state : 7; /* enum header_state from http_parser.c */ 299 | unsigned int index : 7; /* index into current matcher */ 300 | unsigned int lenient_http_headers : 1; 301 | 302 | uint32_t nread; /* # bytes read in various scenarios */ 303 | uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ 304 | 305 | /** READ-ONLY **/ 306 | unsigned short http_major; 307 | unsigned short http_minor; 308 | unsigned int status_code : 16; /* responses only */ 309 | unsigned int method : 8; /* requests only */ 310 | unsigned int http_errno : 7; 311 | 312 | /* 1 = Upgrade header was present and the parser has exited because of that. 313 | * 0 = No upgrade header present. 314 | * Should be checked when http_parser_execute() returns in addition to 315 | * error checking. 316 | */ 317 | unsigned int upgrade : 1; 318 | 319 | /** PUBLIC **/ 320 | void *data; /* A pointer to get hook to the "connection" or "socket" object */ 321 | }; 322 | 323 | 324 | struct http_parser_settings { 325 | http_cb on_message_begin; 326 | http_data_cb on_url; 327 | http_data_cb on_status; 328 | http_data_cb on_header_field; 329 | http_data_cb on_header_value; 330 | http_cb on_headers_complete; 331 | http_data_cb on_body; 332 | http_cb on_message_complete; 333 | /* When on_chunk_header is called, the current chunk length is stored 334 | * in parser->content_length. 335 | */ 336 | http_cb on_chunk_header; 337 | http_cb on_chunk_complete; 338 | }; 339 | 340 | 341 | enum http_parser_url_fields 342 | { UF_SCHEMA = 0 343 | , UF_HOST = 1 344 | , UF_PORT = 2 345 | , UF_PATH = 3 346 | , UF_QUERY = 4 347 | , UF_FRAGMENT = 5 348 | , UF_USERINFO = 6 349 | , UF_MAX = 7 350 | }; 351 | 352 | 353 | /* Result structure for http_parser_parse_url(). 354 | * 355 | * Callers should index into field_data[] with UF_* values iff field_set 356 | * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and 357 | * because we probably have padding left over), we convert any port to 358 | * a uint16_t. 359 | */ 360 | struct http_parser_url { 361 | uint16_t field_set; /* Bitmask of (1 << UF_*) values */ 362 | uint16_t port; /* Converted UF_PORT string */ 363 | 364 | struct { 365 | uint16_t off; /* Offset into buffer in which field starts */ 366 | uint16_t len; /* Length of run in buffer */ 367 | } field_data[UF_MAX]; 368 | }; 369 | 370 | 371 | /* Returns the library version. Bits 16-23 contain the major version number, 372 | * bits 8-15 the minor version number and bits 0-7 the patch level. 373 | * Usage example: 374 | * 375 | * unsigned long version = http_parser_version(); 376 | * unsigned major = (version >> 16) & 255; 377 | * unsigned minor = (version >> 8) & 255; 378 | * unsigned patch = version & 255; 379 | * printf("http_parser v%u.%u.%u\n", major, minor, patch); 380 | */ 381 | unsigned long http_parser_version(void); 382 | 383 | void http_parser_init(http_parser *parser, enum http_parser_type type); 384 | 385 | 386 | /* Initialize http_parser_settings members to 0 387 | */ 388 | void http_parser_settings_init(http_parser_settings *settings); 389 | 390 | 391 | /* Executes the parser. Returns number of parsed bytes. Sets 392 | * `parser->http_errno` on error. */ 393 | size_t http_parser_execute(http_parser *parser, 394 | const http_parser_settings *settings, 395 | const char *data, 396 | size_t len); 397 | 398 | 399 | /* If http_should_keep_alive() in the on_headers_complete or 400 | * on_message_complete callback returns 0, then this should be 401 | * the last message on the connection. 402 | * If you are the server, respond with the "Connection: close" header. 403 | * If you are the client, close the connection. 404 | */ 405 | int http_should_keep_alive(const http_parser *parser); 406 | 407 | /* Returns a string version of the HTTP method. */ 408 | const char *http_method_str(enum http_method m); 409 | 410 | /* Returns a string version of the HTTP status code. */ 411 | const char *http_status_str(enum http_status s); 412 | 413 | /* Return a string name of the given error */ 414 | const char *http_errno_name(enum http_errno err); 415 | 416 | /* Return a string description of the given error */ 417 | const char *http_errno_description(enum http_errno err); 418 | 419 | /* Initialize all http_parser_url members to 0 */ 420 | void http_parser_url_init(struct http_parser_url *u); 421 | 422 | /* Parse a URL; return nonzero on failure */ 423 | int http_parser_parse_url(const char *buf, size_t buflen, 424 | int is_connect, 425 | struct http_parser_url *u); 426 | 427 | /* Pause or un-pause the parser; a nonzero value pauses */ 428 | void http_parser_pause(http_parser *parser, int paused); 429 | 430 | /* Checks if this is the final chunk of the body. */ 431 | int http_body_is_final(const http_parser *parser); 432 | 433 | /* Change the maximum header size provided at compile time. */ 434 | void http_parser_set_max_header_size(uint32_t size); 435 | 436 | #ifdef __cplusplus 437 | } 438 | #endif 439 | #endif 440 | -------------------------------------------------------------------------------- /src/intvec.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2019 Andreas Smas 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "intvec.h" 30 | 31 | 32 | void 33 | intvec_push(intvec_t *vec, int value) 34 | { 35 | if(vec->count + 1 >= vec->capacity) { 36 | vec->capacity = vec->capacity * 2 + 16; 37 | vec->v = realloc(vec->v, sizeof(vec->v[0]) * vec->capacity); 38 | } 39 | vec->v[vec->count++] = value; 40 | } 41 | 42 | void 43 | intvec_reset(intvec_t *vec) 44 | { 45 | vec->count = 0; 46 | vec->capacity = 0; 47 | free(vec->v); 48 | vec->v = NULL; 49 | } 50 | 51 | void 52 | intvec_insert(intvec_t *vec, int position, int value) 53 | { 54 | if(position == vec->count) 55 | return intvec_push(vec, value); 56 | 57 | if(vec->count + 1 >= vec->capacity) { 58 | vec->capacity = vec->capacity * 2 + 16; 59 | vec->v = realloc(vec->v, sizeof(vec->v[0]) * vec->capacity); 60 | } 61 | 62 | memmove(vec->v + position + 1, vec->v + position, 63 | (vec->count - position) * sizeof(vec->v[0])); 64 | 65 | vec->v[position] = value; 66 | vec->count++; 67 | } 68 | 69 | 70 | void 71 | intvec_delete(intvec_t *vec, unsigned int position) 72 | { 73 | assert(position < vec->count); 74 | memmove(vec->v + position, vec->v + position + 1, 75 | (vec->count - position - 1) * sizeof(vec->v[0])); 76 | vec->count--; 77 | } 78 | 79 | 80 | static int 81 | intvec_search(const intvec_t *vec, int value) 82 | { 83 | int imin = 0; 84 | int imax = vec->count; 85 | 86 | while(imin < imax) { 87 | int imid = (imin + imax) >> 1; 88 | 89 | if(vec->v[imid] < value) 90 | imin = imid + 1; 91 | else 92 | imax = imid; 93 | } 94 | return imin; 95 | } 96 | 97 | 98 | int 99 | intvec_insert_sorted(intvec_t *vec, int value) 100 | { 101 | int position = intvec_search(vec, value); 102 | intvec_insert(vec, position, value); 103 | return position; 104 | } 105 | 106 | 107 | int 108 | intvec_find(const intvec_t *vec, int value) 109 | { 110 | if(vec->count == 0) 111 | return -1; 112 | const int pos = intvec_search(vec, value); 113 | return pos < vec->count && vec->v[pos] == value ? pos : -1; 114 | } 115 | 116 | void 117 | intvec_copy(intvec_t *dst, const intvec_t *src) 118 | { 119 | // We trim the capacity down to the actual size here 120 | dst->count = dst->capacity = src->count; 121 | 122 | dst->v = malloc(dst->count * sizeof(dst->v[0])); 123 | memcpy(dst->v, src->v, dst->count * sizeof(dst->v[0])); 124 | } 125 | 126 | 127 | #ifdef TEST 128 | 129 | static void 130 | printvec(intvec_t *v) 131 | { 132 | for(int i = 0; i < v->count; i++) { 133 | printf("%d ", v->v[i]); 134 | } 135 | printf("\n"); 136 | } 137 | 138 | 139 | int main(void) 140 | { 141 | intvec_t v = {}; 142 | 143 | intvec_insert_sorted(&v, 5); 144 | printvec(&v); 145 | intvec_insert_sorted(&v, 3); 146 | printvec(&v); 147 | intvec_insert_sorted(&v, 7); 148 | printvec(&v); 149 | intvec_insert_sorted(&v, 1); 150 | printvec(&v); 151 | intvec_insert_sorted(&v, 11); 152 | printvec(&v); 153 | intvec_insert_sorted(&v, 7); 154 | printvec(&v); 155 | intvec_insert_sorted(&v, 7); 156 | printvec(&v); 157 | intvec_insert_sorted(&v, 7); 158 | printvec(&v); 159 | intvec_insert_sorted(&v, 7); 160 | printvec(&v); 161 | intvec_insert_sorted(&v, 7); 162 | printvec(&v); 163 | intvec_insert_sorted(&v, 7); 164 | printvec(&v); 165 | intvec_insert_sorted(&v, 7); 166 | printvec(&v); 167 | intvec_insert_sorted(&v, 1); 168 | printvec(&v); 169 | intvec_insert_sorted(&v, 1); 170 | printvec(&v); 171 | intvec_insert_sorted(&v, 1); 172 | printvec(&v); 173 | intvec_insert_sorted(&v, 1); 174 | printvec(&v); 175 | intvec_insert_sorted(&v, 0); 176 | printvec(&v); 177 | intvec_insert_sorted(&v, 11); 178 | printvec(&v); 179 | intvec_insert_sorted(&v, 12); 180 | printvec(&v); 181 | intvec_insert_sorted(&v, 13); 182 | printvec(&v); 183 | 184 | printf("Find %d = %d\n", 0, intvec_find(&v, 0)); 185 | printf("Find %d = %d\n", 7, intvec_find(&v, 7)); 186 | printf("Find %d = %d\n", 8, intvec_find(&v, 8)); 187 | printf("Find %d = %d\n", 12, intvec_find(&v, 12)); 188 | printf("Find %d = %d\n", 1200, intvec_find(&v, 1200)); 189 | printf("Find %d = %d\n", -120, intvec_find(&v, -120)); 190 | intvec_reset(&v); 191 | } 192 | 193 | #endif 194 | -------------------------------------------------------------------------------- /src/intvec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef struct intvec { 4 | size_t capacity; 5 | size_t count; 6 | int *v; 7 | } intvec_t; 8 | 9 | 10 | void intvec_push(intvec_t *vec, int value); 11 | 12 | void intvec_reset(intvec_t *vec); 13 | 14 | void intvec_insert(intvec_t *vec, int position, int value); 15 | 16 | void intvec_delete(intvec_t *vec, unsigned int position); 17 | 18 | int intvec_insert_sorted(intvec_t *vec, int value); 19 | 20 | int intvec_find(const intvec_t *vec, int value); 21 | 22 | void intvec_copy(intvec_t *dst, const intvec_t *src); 23 | 24 | #define intvec_get(x, i) (x)->v[i] 25 | 26 | #define scoped_intvec(x) intvec_t x __attribute__((cleanup(intvec_reset))) = {} 27 | 28 | -------------------------------------------------------------------------------- /src/json.h: -------------------------------------------------------------------------------- 1 | /* json.h - JSON/SJSON parser - Public Domain 2 | * 3 | * This library provides a in-place JSON/SJSON parser in C99. 4 | * The latest source code is always available at: 5 | * 6 | * https://github.com/mjansson/json 7 | * 8 | * This library is put in the public domain; you can redistribute 9 | * it and/or modify it without any restrictions. 10 | * 11 | * Author: Mattias Jansson (Twitter - @maniccoder) 12 | */ 13 | 14 | /*! \file json.h 15 | \brief JSON/SJSON parser 16 | 17 | Small in-place JSON parser without any allocation. Entry points for both 18 | standard JSON and simplified JSON data parsing. All character data must be 19 | in UTF-8 format. 20 | 21 | Strings are not automatically unescaped. Use json_unescape/json_escape to 22 | perform unescaping and espacing of strings. Unescaping can be done in-place 23 | to avoid memory allocations. 24 | 25 | Simplified JSON as parsed by this library has the following differences 26 | from standard JSON: 27 | - The equal sign = is used to define key-value pairs instead of the colon : 28 | - Quotes around string keys in key-value pairs are optional, unless you need 29 | the key to contain either whitespace or the equal sign = 30 | - Commas are optional in object and array definitions 31 | - Each SJSON file is always interpreted as a definition for a single object. 32 | You can think of this as an implicit set of curly quotes { ... } that surround 33 | the contents of the file 34 | 35 | Requires size_t, bool and memcmp to be declared prior to including this 36 | header. Headers are only included when compiled as the minimal test case. 37 | 38 | To compile the minimal test case, use 39 | gcc -D JSON_TEST -x c --std=c99 json.h 40 | 41 | Kudos to Niklas Gray for SJSON syntax, 42 | http://bitsquid.blogspot.se/2009/10/simplified-json-notation.html 43 | */ 44 | 45 | #ifdef JSON_TEST 46 | # include 47 | # include 48 | # include 49 | #else 50 | # pragma once 51 | #endif 52 | 53 | // Types 54 | 55 | /*! Base size type. Change this to reduce token storage footprint. */ 56 | typedef size_t json_size_t; 57 | 58 | /*! JSON token type */ 59 | enum json_type_t { 60 | /*! Invalid type */ 61 | JSON_UNDEFINED = 0, 62 | /*! Object */ 63 | JSON_OBJECT, 64 | /*! Array */ 65 | JSON_ARRAY, 66 | /*! String */ 67 | JSON_STRING, 68 | /*! Primitive */ 69 | JSON_PRIMITIVE 70 | }; 71 | 72 | /*! JSON token. The token points into the parsed data buffer using absolute offsets 73 | from start of buffer */ 74 | struct json_token_t { 75 | /*! Identifier string offset */ 76 | json_size_t id; 77 | /*! Length of identifier string. 0 if no identifier string */ 78 | json_size_t id_length; 79 | /*! Value string offset. For object tokens this is the start position of the opening bracket */ 80 | json_size_t value; 81 | /*! Length of value string. 0 if no or empty value string. For array tokens this holds number of items in array, and for object tokens it is the length of the entire object bracketed value */ 82 | json_size_t value_length; 83 | /*! Child token index in token array. 0 if no child token */ 84 | json_size_t child; 85 | /*! Sibling token index in token array. 0 if no sibling token */ 86 | json_size_t sibling; 87 | /*! Token type */ 88 | enum json_type_t type; 89 | }; 90 | 91 | // Interface 92 | 93 | /*! Parse a JSON blob. Number of parsed tokens can be greater than the supplied 94 | capacity to indicate the need for additional capacity for a full parse. Note that 95 | string identifiers and values are in escaped form. 96 | \param buffer Data buffer 97 | \param size Size of data buffer 98 | \param tokens Token array 99 | \param capacity Capacity of token array (number of tokens) 100 | \return Number of parsed tokens, 0 if error */ 101 | static json_size_t 102 | json_parse(const char* buffer, json_size_t size, 103 | struct json_token_t* tokens, json_size_t capacity); 104 | 105 | /*! Parse a simplified JSON blob. Number of parsed tokens can be greater than the supplied 106 | capacity to indicate the need for additional capacity for a full parse. Not that 107 | string identifiers and values are in escaped form. 108 | \param buffer Data buffer 109 | \param size Size of data buffer 110 | \param tokens Token array 111 | \param capacity Capacity of token array (number of tokens) 112 | \return Number of parsed tokens, 0 if error */ 113 | static json_size_t 114 | sjson_parse(const char* buffer, json_size_t size, 115 | struct json_token_t* tokens, json_size_t capacity); 116 | 117 | /*! Function to unescape a JSON identifier or value string. Buffer can be 118 | pointing to same memory area as string (in-place unescaping). 119 | \param buffer Output buffer 120 | \param capacity Capacity of output buffer 121 | \param string Input string identifier or value 122 | \param length Length of input string 123 | \return Length of unescaped string in buffer */ 124 | static json_size_t 125 | json_unescape(char* buffer, json_size_t capacity, const char* string, json_size_t length); 126 | 127 | /*! Function to escape a JSON identifier or value string 128 | \param buffer Output buffer 129 | \param capacity Capacity of output buffer 130 | \param string Input string identifier or value 131 | \param length Length of input string 132 | \return Escaped string in buffer */ 133 | static json_size_t 134 | json_escape(char* buffer, json_size_t capacity, const char* string, json_size_t length); 135 | 136 | /*! Utility function to do bounded string compare 137 | \param rhs First string 138 | \param rhs_length Length of first string in bytes 139 | \param lhs Second string 140 | \param lhs_length Length of second string in bytes 141 | \return true if strings are equal, false if not */ 142 | static bool 143 | json_string_equal(const char* rhs, size_t rhs_length, const char* lhs, size_t lhs_length); 144 | 145 | /*! \def JSON_STRING_CONST 146 | \brief Utility string macro for both data and length */ 147 | /*! Expands to two arguments (data and length) of a constant string expression, like JSON_STRING_CONST("foobar"). 148 | Useful with json_string_equal function: json_string_equal(myptr, mylength, JSON_STRING_CONST("foobar")). 149 | Be aware that it evaluates the s expression twice. */ 150 | #define JSON_STRING_CONST(s) (s), (sizeof((s))-1) 151 | 152 | 153 | // Implementation 154 | 155 | //! Identifier of invalid position or index 156 | #define JSON_INVALID_POS ((json_size_t)-1) 157 | 158 | static struct json_token_t* 159 | json_get_token(struct json_token_t* tokens, json_size_t capacity, json_size_t index) { 160 | return index < capacity ? tokens + index : 0; 161 | } 162 | 163 | static bool 164 | json_is_valid_token(struct json_token_t* tokens, json_size_t capacity, json_size_t index) { 165 | struct json_token_t* token = json_get_token(tokens, capacity, index); 166 | return token ? (token->type != JSON_UNDEFINED) : true; 167 | } 168 | 169 | static void 170 | json_set_token_primitive(struct json_token_t* tokens, json_size_t capacity, json_size_t current, 171 | enum json_type_t type, json_size_t value, json_size_t value_length) { 172 | struct json_token_t* token = json_get_token(tokens, capacity, current); 173 | if (token) { 174 | token->type = type; 175 | token->child = 0; 176 | token->sibling = 0; 177 | token->value = value; 178 | token->value_length = value_length; 179 | } 180 | } 181 | 182 | static struct json_token_t* 183 | json_set_token_complex(struct json_token_t* tokens, json_size_t capacity, json_size_t current, 184 | enum json_type_t type, json_size_t pos) { 185 | struct json_token_t* token = json_get_token(tokens, capacity, current); 186 | if (token) { 187 | token->type = type; 188 | token->child = current + 1; 189 | token->sibling = 0; 190 | token->value = pos; 191 | token->value_length = 0; 192 | } 193 | return token; 194 | } 195 | 196 | static void 197 | json_set_token_id(struct json_token_t* tokens, json_size_t capacity, json_size_t current, 198 | json_size_t id, json_size_t id_length) { 199 | struct json_token_t* token = json_get_token(tokens, capacity, current); 200 | if (token) { 201 | token->id = id; 202 | token->id_length = id_length; 203 | } 204 | } 205 | 206 | static bool 207 | json_is_whitespace(char c) { 208 | return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r'); 209 | } 210 | 211 | static bool 212 | json_is_token_delimiter(char c) { 213 | return json_is_whitespace(c) || (c == ']') || (c == '}') || (c == ','); 214 | } 215 | 216 | static json_size_t 217 | json_skip_whitespace(const char* buffer, json_size_t length, json_size_t pos) { 218 | while (pos < length) { 219 | if (!json_is_whitespace(buffer[pos])) 220 | return pos; 221 | ++pos; 222 | } 223 | return pos; 224 | } 225 | 226 | static char 227 | json_hex_char(unsigned char val) { 228 | if (val < 10) 229 | return '0' + (char)val; 230 | else if (val < 16) 231 | return 'a' + (char)(val - 10); 232 | return '0'; 233 | } 234 | 235 | static json_size_t 236 | json_parse_string(const char* buffer, json_size_t length, json_size_t pos, bool key, bool simple) { 237 | json_size_t start = pos; 238 | json_size_t esc; 239 | while (pos < length) { 240 | char c = buffer[pos]; 241 | if (simple && (json_is_token_delimiter(c) || (key && ((c == '=') || (c == ':'))))) 242 | return pos - start; 243 | if (c == '"') 244 | return pos - start; 245 | ++pos; 246 | if (c == '\\' && (pos < length)) { 247 | switch (buffer[pos]) { 248 | // Escaped symbols \X 249 | case '\"': case '/': case '\\': case 'b': 250 | case 'f': case 'r': case 'n': case 't': 251 | break; 252 | // Escaped symbol \uXXXX 253 | case 'u': 254 | for (esc = 0; esc < 4 && pos < length; ++esc) { 255 | ++pos; 256 | if (!((buffer[pos] >= 48 && buffer[pos] <= 57) || // 0-9 257 | (buffer[pos] >= 65 && buffer[pos] <= 70) || // A-F 258 | (buffer[pos] >= 97 && buffer[pos] <= 102))) // a-f 259 | return JSON_INVALID_POS; 260 | } 261 | break; 262 | default: 263 | return JSON_INVALID_POS; 264 | } 265 | ++pos; 266 | } 267 | } 268 | return simple ? pos - start : JSON_INVALID_POS; 269 | } 270 | 271 | static json_size_t 272 | json_parse_number(const char* buffer, json_size_t length, json_size_t pos) { 273 | json_size_t start = pos; 274 | bool has_dot = false; 275 | bool has_digit = false; 276 | bool has_exp = false; 277 | while (pos < length) { 278 | char c = buffer[pos]; 279 | if (json_is_token_delimiter(c)) 280 | break; 281 | if (c == '-') { 282 | if (start != pos) 283 | return JSON_INVALID_POS; 284 | } 285 | else if (c == '.') { 286 | if (has_dot || has_exp) 287 | return JSON_INVALID_POS; 288 | has_dot = true; 289 | } 290 | else if ((c == 'e') || (c == 'E')) { 291 | if (!has_digit || has_exp) 292 | return JSON_INVALID_POS; 293 | has_exp = true; 294 | if ((pos + 1) < length) { 295 | if ((buffer[pos + 1] == '+') || (buffer[pos + 1] == '-')) 296 | ++pos; 297 | } 298 | } 299 | else if ((c < '0') || (c > '9')) 300 | return JSON_INVALID_POS; 301 | else 302 | has_digit = true; 303 | ++pos; 304 | } 305 | return has_digit ? (pos - start) : JSON_INVALID_POS; 306 | } 307 | 308 | static json_size_t 309 | json_parse_object(const char* buffer, json_size_t length, json_size_t pos, 310 | struct json_token_t* tokens, json_size_t capacity, json_size_t* current, bool simple); 311 | 312 | static json_size_t 313 | json_parse_value(const char* buffer, json_size_t length, json_size_t pos, 314 | struct json_token_t* tokens, json_size_t capacity, json_size_t* current, bool simple); 315 | 316 | static json_size_t 317 | json_parse_array(const char* buffer, json_size_t length, json_size_t pos, 318 | struct json_token_t* tokens, json_size_t capacity, json_size_t owner, 319 | json_size_t* current, bool simple); 320 | 321 | static json_size_t 322 | json_parse_object(const char* buffer, json_size_t length, json_size_t pos, 323 | struct json_token_t* tokens, json_size_t capacity, json_size_t* current, bool simple) { 324 | struct json_token_t* token; 325 | json_size_t string; 326 | bool simple_string; 327 | json_size_t last = 0; 328 | 329 | pos = json_skip_whitespace(buffer, length, pos); 330 | while (pos < length) { 331 | char c = buffer[pos++]; 332 | 333 | switch (c) { 334 | case '}': 335 | if (last && !json_is_valid_token(tokens, capacity, last)) 336 | return JSON_INVALID_POS; 337 | return pos; 338 | 339 | case ',': 340 | if (!last || !json_is_valid_token(tokens, capacity, last)) 341 | return JSON_INVALID_POS; 342 | token = json_get_token(tokens, capacity, last); 343 | if (token) 344 | token->sibling = *current; 345 | last = 0; 346 | pos = json_skip_whitespace(buffer, length, pos); 347 | break; 348 | 349 | case '"': 350 | default: 351 | if (last) 352 | return JSON_INVALID_POS; 353 | if (c != '"') { 354 | if (!simple) 355 | return JSON_INVALID_POS; 356 | simple_string = true; 357 | --pos; 358 | } 359 | else { 360 | simple_string = false; 361 | } 362 | 363 | string = json_parse_string(buffer, length, pos, true, simple_string); 364 | if (string == JSON_INVALID_POS) 365 | return JSON_INVALID_POS; 366 | 367 | last = *current; 368 | json_set_token_id(tokens, capacity, *current, pos, string); 369 | //Skip terminating '"' (optional for simplified) 370 | if (!simple || ((pos + string < length) && (buffer[pos + string] == '"'))) 371 | ++string; 372 | pos += string; 373 | 374 | pos = json_skip_whitespace(buffer, length, pos); 375 | if ((buffer[pos] != ':') && 376 | (!simple || (buffer[pos] != '='))) 377 | return JSON_INVALID_POS; 378 | pos = json_parse_value(buffer, length, pos + 1, tokens, capacity, current, simple); 379 | pos = json_skip_whitespace(buffer, length, pos); 380 | if (simple_string && ((pos < length) && (buffer[pos] != ',') && (buffer[pos] != '}'))) { 381 | token = json_get_token(tokens, capacity, last); 382 | if (token) 383 | token->sibling = *current; 384 | last = 0; 385 | } 386 | break; 387 | } 388 | } 389 | 390 | return simple ? pos : JSON_INVALID_POS; 391 | } 392 | 393 | static json_size_t 394 | json_parse_array(const char* buffer, json_size_t length, json_size_t pos, 395 | struct json_token_t* tokens, json_size_t capacity, json_size_t owner, 396 | json_size_t* current, bool simple) { 397 | struct json_token_t* parent = json_get_token(tokens, capacity, owner); 398 | struct json_token_t* token; 399 | json_size_t now; 400 | json_size_t last = 0; 401 | 402 | pos = json_skip_whitespace(buffer, length, pos); 403 | if (buffer[pos] == ']') { 404 | if (parent) 405 | parent->child = 0; 406 | return json_skip_whitespace(buffer, length, ++pos); 407 | } 408 | 409 | while (pos < length) { 410 | now = *current; 411 | json_set_token_id(tokens, capacity, now, 0, 0); 412 | pos = json_parse_value(buffer, length, pos, tokens, capacity, current, simple); 413 | if (pos == JSON_INVALID_POS) 414 | return JSON_INVALID_POS; 415 | if (parent) 416 | parent->value_length++; 417 | if (last) { 418 | token = json_get_token(tokens, capacity, last); 419 | if (token) 420 | token->sibling = now; 421 | } 422 | last = now; 423 | pos = json_skip_whitespace(buffer, length, pos); 424 | if (buffer[pos] == ',') 425 | ++pos; 426 | else if (buffer[pos] == ']') 427 | return ++pos; 428 | else if (!simple || buffer[pos] == '}') 429 | return JSON_INVALID_POS; 430 | } 431 | 432 | return JSON_INVALID_POS; 433 | } 434 | 435 | static json_size_t 436 | json_parse_value(const char* buffer, json_size_t length, json_size_t pos, 437 | struct json_token_t* tokens, json_size_t capacity, json_size_t* current, bool simple) { 438 | struct json_token_t* subtoken; 439 | json_size_t string, owner; 440 | bool simple_string; 441 | 442 | pos = json_skip_whitespace(buffer, length, pos); 443 | while (pos < length) { 444 | char c = buffer[pos++]; 445 | switch (c) { 446 | case '{': 447 | subtoken = json_set_token_complex(tokens, capacity, *current, JSON_OBJECT, pos - 1); 448 | ++(*current); 449 | pos = json_parse_object(buffer, length, pos, tokens, capacity, current, simple); 450 | if (subtoken) { 451 | if (pos != JSON_INVALID_POS) 452 | subtoken->value_length = (pos - subtoken->value); 453 | if (subtoken->child == *current) 454 | subtoken->child = 0; 455 | } 456 | return pos; 457 | 458 | case '[': 459 | owner = *current; 460 | json_set_token_complex(tokens, capacity, *current, JSON_ARRAY, 0); 461 | ++(*current); 462 | pos = json_parse_array(buffer, length, pos, tokens, capacity, owner, current, simple); 463 | return pos; 464 | 465 | case '-': case '0': case '1': case '2': case '3': case '4': 466 | case '5': case '6': case '7': case '8': case '9': case '.': 467 | string = json_parse_number(buffer, length, pos - 1); 468 | if (string == JSON_INVALID_POS) 469 | return JSON_INVALID_POS; 470 | json_set_token_primitive(tokens, capacity, *current, JSON_PRIMITIVE, pos - 1, string); 471 | ++(*current); 472 | return pos + string - 1; 473 | 474 | case 't': 475 | case 'f': 476 | case 'n': 477 | if ((c == 't') && (length - pos >= 4) && 478 | (buffer[pos] == 'r') && (buffer[pos + 1] == 'u') && (buffer[pos + 2] == 'e') && 479 | json_is_token_delimiter(buffer[pos + 3])) { 480 | json_set_token_primitive(tokens, capacity, *current, JSON_PRIMITIVE, pos - 1, 4); 481 | ++(*current); 482 | return pos + 3; 483 | } 484 | if ((c == 'f') && (length - pos >= 5) && 485 | (buffer[pos] == 'a') && (buffer[pos + 1] == 'l') && (buffer[pos + 2] == 's') && 486 | (buffer[pos + 3] == 'e') && json_is_token_delimiter(buffer[pos + 4])) { 487 | json_set_token_primitive(tokens, capacity, *current, JSON_PRIMITIVE, pos - 1, 5); 488 | ++(*current); 489 | return pos + 4; 490 | } 491 | if ((c == 'n') && (length - pos >= 4) && 492 | (buffer[pos] == 'u') && (buffer[pos + 1] == 'l') && (buffer[pos + 2] == 'l') && 493 | json_is_token_delimiter(buffer[pos + 3])) { 494 | json_set_token_primitive(tokens, capacity, *current, JSON_PRIMITIVE, pos - 1, 4); 495 | ++(*current); 496 | return pos + 3; 497 | } 498 | if (!simple) 499 | return JSON_INVALID_POS; 500 | //Fall through to string handling 501 | 502 | case '"': 503 | default: 504 | if (c != '"') { 505 | if (!simple) 506 | return JSON_INVALID_POS; 507 | simple_string = true; 508 | --pos; 509 | } 510 | else { 511 | simple_string = false; 512 | } 513 | string = json_parse_string(buffer, length, pos, false, simple_string); 514 | if (string == JSON_INVALID_POS) 515 | return JSON_INVALID_POS; 516 | json_set_token_primitive(tokens, capacity, *current, JSON_STRING, pos, string); 517 | ++(*current); 518 | //Skip terminating '"' (optional for simplified) 519 | if (!simple_string || ((pos + string < length) && (buffer[pos + string] == '"'))) 520 | ++string; 521 | return pos + string; 522 | } 523 | } 524 | 525 | return JSON_INVALID_POS; 526 | } 527 | 528 | static json_size_t 529 | json_parse(const char* buffer, json_size_t size, struct json_token_t* tokens, 530 | json_size_t capacity) { 531 | json_size_t current = 0; 532 | json_set_token_id(tokens, capacity, current, 0, 0); 533 | json_set_token_primitive(tokens, capacity, current, JSON_UNDEFINED, 0, 0); 534 | if (json_parse_value(buffer, size, 0, tokens, capacity, ¤t, false) == JSON_INVALID_POS) 535 | return 0; 536 | return current; 537 | } 538 | 539 | static json_size_t 540 | sjson_parse(const char* buffer, json_size_t size, struct json_token_t* tokens, 541 | json_size_t capacity) { 542 | json_size_t current = 0; 543 | json_size_t pos = json_skip_whitespace(buffer, size, 0); 544 | if ((pos < size) && (buffer[pos] != '{')) { 545 | json_set_token_id(tokens, capacity, current, 0, 0); 546 | json_set_token_complex(tokens, capacity, current, JSON_OBJECT, pos); 547 | ++current; 548 | if (json_parse_object(buffer, size, pos, tokens, capacity, ¤t, true) == JSON_INVALID_POS) 549 | return 0; 550 | if (capacity) 551 | tokens[0].value_length = size - tokens[0].value; 552 | return current; 553 | } 554 | if (json_parse_value(buffer, size, pos, tokens, capacity, ¤t, true) == JSON_INVALID_POS) 555 | return 0; 556 | return current; 557 | } 558 | 559 | static json_size_t 560 | json_escape(char* buffer, json_size_t capacity, const char* string, json_size_t length) { 561 | json_size_t i; 562 | json_size_t outlength = 0; 563 | for (i = 0; (i < length) && (outlength < capacity); ++i) { 564 | char c = string[i]; 565 | if ((c == '\"') || (c == '\\')) { 566 | buffer[outlength++] = '\\'; 567 | if (outlength < capacity) buffer[outlength++] = c; 568 | } 569 | else if (c == '\b') { 570 | buffer[outlength++] = '\\'; 571 | if (outlength < capacity) buffer[outlength++] = 'b'; 572 | } 573 | else if (c == '\f') { 574 | buffer[outlength++] = '\\'; 575 | if (outlength < capacity) buffer[outlength++] = 'f'; 576 | } 577 | else if (c == '\r') { 578 | buffer[outlength++] = '\\'; 579 | if (outlength < capacity) buffer[outlength++] = 'r'; 580 | } 581 | else if (c == '\n') { 582 | buffer[outlength++] = '\\'; 583 | if (outlength < capacity) buffer[outlength++] = 'n'; 584 | } 585 | else if (c == '\t') { 586 | buffer[outlength++] = '\\'; 587 | if (outlength < capacity) buffer[outlength++] = 't'; 588 | } 589 | else if (c < 0x20) { 590 | buffer[outlength++] = '\\'; 591 | if (outlength < capacity) buffer[outlength++] = 'u'; 592 | if (outlength < capacity) buffer[outlength++] = '0'; 593 | if (outlength < capacity) buffer[outlength++] = '0'; 594 | if (outlength < capacity) buffer[outlength++] = json_hex_char((unsigned char)(c >> 4) & 0xf); 595 | if (outlength < capacity) buffer[outlength++] = json_hex_char((unsigned char)c & 0xf); 596 | } 597 | else { 598 | buffer[outlength++] = c; 599 | } 600 | } 601 | return outlength; 602 | } 603 | 604 | //! Define a bitmask with the given number of bits set to 1 605 | #define JSON_BITMASK(numbits) ((1U << (numbits)) - 1) 606 | 607 | static unsigned int 608 | json_get_num_bytes_as_utf8(unsigned int val) { 609 | if (val >= 0x04000000) return 6; 610 | else if (val >= 0x00200000) return 5; 611 | else if (val >= 0x00010000) return 4; 612 | else if (val >= 0x00000800) return 3; 613 | else if (val >= 0x00000080) return 2; 614 | return 1; 615 | } 616 | 617 | static json_size_t 618 | json_encode_utf8(char* str, unsigned int val) { 619 | unsigned int num, j; 620 | 621 | if (val < 0x80) { 622 | *str = (char)val; 623 | return 1; 624 | } 625 | 626 | //Get number of _extra_ bytes 627 | num = json_get_num_bytes_as_utf8(val) - 1; 628 | 629 | *str++ = (char)((0x80U | (JSON_BITMASK(num) << (7U - num))) | 630 | ((val >> (6U * num)) & JSON_BITMASK(6U - num))); 631 | for (j = 1; j <= num; ++j) 632 | *str++ = (char)(0x80U | ((val >> (6U * (num - j))) & 0x3F)); 633 | 634 | return num + 1; 635 | } 636 | 637 | static json_size_t 638 | json_unescape(char* buffer, json_size_t capacity, const char* string, json_size_t length) { 639 | json_size_t i, j; 640 | json_size_t outlength = 0; 641 | unsigned int hexval, numbytes; 642 | for (i = 0; (i < length) && (outlength < capacity); ++i) { 643 | char c = string[i]; 644 | if ((c == '\\') && (i + 1 < length)) { 645 | c = string[++i]; 646 | switch (c) { 647 | case '\"': 648 | case '/': 649 | case '\\': 650 | buffer[outlength++] = c; 651 | break; 652 | 653 | case 'b': 654 | buffer[outlength++] = '\b'; 655 | break; 656 | case 'f': 657 | buffer[outlength++] = '\f'; 658 | break; 659 | case 'r': 660 | buffer[outlength++] = '\r'; 661 | break; 662 | case 'n': 663 | buffer[outlength++] = '\n'; 664 | break; 665 | case 't': 666 | buffer[outlength++] = '\t'; 667 | break; 668 | 669 | case 'u': 670 | if (i + 4 < length) { 671 | hexval = 0; 672 | for (j = 0; j < 4; ++j) { 673 | char val = string[++i]; 674 | unsigned int uival = 0; 675 | if ((val >= 'a') && (val <= 'f')) 676 | uival = 10 + (val - 'a'); 677 | else if ((val >= 'A') && (val <= 'F')) 678 | uival = 10 + (val - 'A'); 679 | else if ((val >= '0') && (val <= '9')) 680 | uival = val - '0'; 681 | hexval |= uival << (3 - j); 682 | } 683 | numbytes = json_get_num_bytes_as_utf8(hexval); 684 | if ((outlength + numbytes) < capacity) 685 | outlength += json_encode_utf8(buffer + outlength, hexval); 686 | } 687 | break; 688 | 689 | default: 690 | break; 691 | } 692 | } 693 | else { 694 | buffer[outlength++] = c; 695 | } 696 | } 697 | return outlength; 698 | } 699 | 700 | static bool 701 | json_string_equal(const char* rhs, size_t rhs_length, const char* lhs, size_t lhs_length) { 702 | if (rhs_length && (lhs_length == rhs_length)) { 703 | return (memcmp(rhs, lhs, rhs_length) == 0); 704 | } 705 | return (!rhs_length && !lhs_length); 706 | } 707 | 708 | 709 | #ifdef JSON_TEST 710 | 711 | #include 712 | 713 | #define VERIFY_TOKEN(idx, type_, id_, id_len_, val_, val_len_) \ 714 | if ((tokens[idx].type != (type_)) || (tokens[idx].id != (id_)) || (tokens[idx].id_length != (id_len_)) || \ 715 | (tokens[idx].value != (val_)) || (tokens[idx].value_length != (val_len_))) { \ 716 | printf("Invalid token %d (%d, %d, %d, %d, %d)\n", idx, tokens[idx].type, tokens[idx].id, \ 717 | tokens[idx].id_length, tokens[idx].value, tokens[idx].value_length); \ 718 | return -1; \ 719 | } else do {} while(0) 720 | 721 | #define VERIFY_TOKEN_ID(idx, type_, idstr, val_, val_len_) \ 722 | if ((tokens[idx].type != (type_)) || \ 723 | !json_string_equal(input.str + tokens[idx].id, tokens[idx].id_length, JSON_STRING_CONST(idstr)) || \ 724 | (tokens[idx].value != (val_)) || (tokens[idx].value_length != (val_len_))) { \ 725 | printf("Invalid token %d (%d, %d, %d, %d, %d)\n", idx, tokens[idx].type, tokens[idx].id, \ 726 | tokens[idx].id_length, tokens[idx].value, tokens[idx].value_length); \ 727 | return -1; \ 728 | } else do {} while(0) 729 | 730 | #define VERIFY_TOKEN_ID_VALUE(idx, type_, idstr, valuestr) \ 731 | if ((tokens[idx].type != (type_)) || \ 732 | !json_string_equal(input.str + tokens[idx].id, tokens[idx].id_length, JSON_STRING_CONST(idstr)) || \ 733 | !json_string_equal(input.str + tokens[idx].value, tokens[idx].value_length, JSON_STRING_CONST(valuestr))) { \ 734 | printf("Invalid token %d (%d, %d, %d, %d, %d)\n", idx, tokens[idx].type, tokens[idx].id, \ 735 | tokens[idx].id_length, tokens[idx].value, tokens[idx].value_length); \ 736 | return -1; \ 737 | } else do {} while(0) 738 | 739 | int 740 | main(int argc, char** argv) { 741 | struct json_token_t tokens[32]; 742 | memset(tokens, 0, sizeof(tokens)); 743 | 744 | struct json_input { 745 | const char* str; 746 | size_t len; 747 | }; 748 | { 749 | struct json_input input = {JSON_STRING_CONST("\ 750 | {\"foo\" :{\"subobj\": false ,\ 751 | \"val\" :1.2345e45 \ 752 | } ,\"arr\" :[ \ 753 | \"string\",\ 754 | -.34523e-78,[\ 755 | true, \ 756 | \"subarr [] {} =:\", { \"key\": []}, [] \ 757 | ],[false],\ 758 | { \t\ 759 | \"final\" : null \ 760 | }\ 761 | ,{ } , \ 762 | 1234.43E+123 \ 763 | ]\ 764 | }")}; 765 | 766 | json_size_t num_tokens = 767 | json_parse(input.str, input.len, tokens, sizeof(tokens) / sizeof(tokens[0])); 768 | 769 | if (num_tokens != 19) { 770 | printf("Invalid number of tokens: %d\n", (int)num_tokens); 771 | return -1; 772 | } 773 | 774 | VERIFY_TOKEN(0, JSON_OBJECT, 0, 0, 1, input.len - 1); 775 | VERIFY_TOKEN_ID(1, JSON_OBJECT, "foo", 9, 39); 776 | VERIFY_TOKEN_ID_VALUE(2, JSON_PRIMITIVE, "subobj", "false"); 777 | VERIFY_TOKEN_ID_VALUE(3, JSON_PRIMITIVE, "val", "1.2345e45"); 778 | VERIFY_TOKEN_ID(4, JSON_ARRAY, "arr", 0, 7); 779 | VERIFY_TOKEN_ID_VALUE(5, JSON_STRING, "", "string"); 780 | VERIFY_TOKEN_ID_VALUE(6, JSON_PRIMITIVE, "", "-.34523e-78"); 781 | VERIFY_TOKEN(7, JSON_ARRAY, 0, 0, 0, 4); 782 | VERIFY_TOKEN_ID_VALUE(8, JSON_PRIMITIVE, "", "true"); 783 | VERIFY_TOKEN_ID_VALUE(9, JSON_STRING, "", "subarr [] {} =:"); 784 | VERIFY_TOKEN(10, JSON_OBJECT, 0, 0, 116, 12); 785 | VERIFY_TOKEN_ID_VALUE(11, JSON_ARRAY, "key", ""); 786 | VERIFY_TOKEN(12, JSON_ARRAY, 0, 0, 0, 0); 787 | VERIFY_TOKEN(13, JSON_ARRAY, 0, 0, 0, 1); 788 | VERIFY_TOKEN_ID_VALUE(14, JSON_PRIMITIVE, "", "false"); 789 | VERIFY_TOKEN(15, JSON_OBJECT, 0, 0, 147, 24); 790 | VERIFY_TOKEN_ID_VALUE(16, JSON_PRIMITIVE, "final", "null"); 791 | VERIFY_TOKEN(17, JSON_OBJECT, 0, 0, 174, 3); 792 | VERIFY_TOKEN_ID_VALUE(18, JSON_PRIMITIVE, "", "1234.43E+123"); 793 | } 794 | { 795 | struct json_input input = {JSON_STRING_CONST("\ 796 | foo ={subobj= false \ 797 | val =1.2345e45 \ 798 | } arr =[\ 799 | string\ 800 | -.34523e-78 [\ 801 | true\ 802 | \"subarr [] {} =:\" { key: []} []\ 803 | ] [false] \ 804 | { \t\ 805 | final = null\ 806 | }\ 807 | { } \ 808 | 1234.43E+123 \ 809 | ]\ 810 | ")}; 811 | 812 | json_size_t num_tokens = 813 | sjson_parse(input.str, input.len, tokens, sizeof(tokens) / sizeof(tokens[0])); 814 | 815 | if (num_tokens != 19) { 816 | printf("Invalid number of tokens: %d\n", (int)num_tokens); 817 | return -1; 818 | } 819 | 820 | VERIFY_TOKEN(0, JSON_OBJECT, 0, 0, 1, input.len - 1); 821 | VERIFY_TOKEN_ID(1, JSON_OBJECT, "foo", 6, 34); 822 | VERIFY_TOKEN_ID_VALUE(2, JSON_PRIMITIVE, "subobj", "false"); 823 | VERIFY_TOKEN_ID_VALUE(3, JSON_PRIMITIVE, "val", "1.2345e45"); 824 | VERIFY_TOKEN_ID(4, JSON_ARRAY, "arr", 0, 7); 825 | VERIFY_TOKEN_ID_VALUE(5, JSON_STRING, "", "string"); 826 | VERIFY_TOKEN_ID_VALUE(6, JSON_PRIMITIVE, "", "-.34523e-78"); 827 | VERIFY_TOKEN(7, JSON_ARRAY, 0, 0, 0, 4); 828 | VERIFY_TOKEN_ID_VALUE(8, JSON_PRIMITIVE, "", "true"); 829 | VERIFY_TOKEN_ID_VALUE(9, JSON_STRING, "", "subarr [] {} =:"); 830 | VERIFY_TOKEN(10, JSON_OBJECT, 0, 0, 98, 10); 831 | VERIFY_TOKEN_ID_VALUE(11, JSON_ARRAY, "key", ""); 832 | VERIFY_TOKEN(12, JSON_ARRAY, 0, 0, 0, 0); 833 | VERIFY_TOKEN(13, JSON_ARRAY, 0, 0, 0, 1); 834 | VERIFY_TOKEN_ID_VALUE(14, JSON_PRIMITIVE, "", "false"); 835 | VERIFY_TOKEN(15, JSON_OBJECT, 0, 0, 125, 21); 836 | VERIFY_TOKEN_ID_VALUE(16, JSON_PRIMITIVE, "final", "null"); 837 | VERIFY_TOKEN(17, JSON_OBJECT, 0, 0, 148, 3); 838 | VERIFY_TOKEN_ID_VALUE(18, JSON_PRIMITIVE, "", "1234.43E+123"); 839 | } 840 | printf("Minimal tests passed\n"); 841 | return 0; 842 | } 843 | 844 | #endif 845 | 846 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2019 Andreas Smas 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "hap.h" 33 | 34 | #define ACCESSORY_MANUFACTURER "Lonelycoder" 35 | #define ACCESSORY_MODEL "Model1" 36 | #define ACCESSORY_VERSION "1.0" 37 | #define ACCESSORY_PASSWORD "224-46-688" 38 | 39 | // Example to create a light-bulb accessory 40 | 41 | static hap_status_t 42 | lightbulb_set(void *opaque, bool on) 43 | { 44 | printf("Light builb is %s\n", on ? "ON" : "OFF"); 45 | return HAP_STATUS_OK; 46 | } 47 | 48 | static hap_accessory_t * 49 | lightbulb_main(void) 50 | { 51 | hap_accessory_t *ha = 52 | hap_accessory_create("Lightbulb", ACCESSORY_PASSWORD, "lightbuilb.cfg", 53 | HAP_CAT_LIGHTING, ACCESSORY_MANUFACTURER, 54 | ACCESSORY_MODEL, ACCESSORY_VERSION, NULL, NULL); 55 | 56 | hap_service_t *hs = hap_light_builb_create(NULL, lightbulb_set); 57 | hap_accessory_add_service(ha, hs, 1); 58 | return ha; 59 | } 60 | 61 | 62 | static hap_status_t 63 | rgb_set(void *opaque, float r, float g, float b) 64 | { 65 | char colorstr[64]; 66 | snprintf(colorstr, sizeof(colorstr), 67 | ";2;%d;%d;%dm", (int)(r * 255), (int)(g * 255), (int)(b * 255)); 68 | 69 | printf("\033[48%s \033[0m " 70 | "R=%1.3f G=%1.3f B=%1.3f " 71 | "\033[48%s \033[0m\n", colorstr, r, g, b, colorstr); 72 | return HAP_STATUS_OK; 73 | } 74 | 75 | static hap_accessory_t * 76 | rgb_main(void) 77 | { 78 | hap_accessory_t *ha = 79 | hap_accessory_create("RGB", ACCESSORY_PASSWORD, "rgb.cfg", 80 | HAP_CAT_LIGHTING, ACCESSORY_MANUFACTURER, 81 | ACCESSORY_MODEL, ACCESSORY_VERSION, NULL, NULL); 82 | 83 | hap_service_t *hs = hap_rgb_light_create(NULL, rgb_set); 84 | hap_accessory_add_service(ha, hs, 1); 85 | return ha; 86 | } 87 | 88 | 89 | 90 | 91 | 92 | typedef struct { 93 | char *name; 94 | char *manufacturer; 95 | char *sn; 96 | char *model; 97 | char *fw_revision; 98 | 99 | } bridged_accessory_t; 100 | 101 | 102 | static hap_characteristic_t 103 | bridged_accessory_info_get(void *opaque, int index) 104 | { 105 | bridged_accessory_t *ba = opaque; 106 | switch(index) { 107 | case 0: return hap_make_string_ro(HAP_CHARACTERISTIC_NAME, 108 | ba->name); 109 | case 1: return hap_make_string_ro(HAP_CHARACTERISTIC_MANUFACTURER, 110 | ba->manufacturer); 111 | case 2: return hap_make_string_ro(HAP_CHARACTERISTIC_SERIAL_NUMBER, 112 | ba->sn); 113 | case 3: return hap_make_string_ro(HAP_CHARACTERISTIC_MODEL, 114 | ba->model); 115 | case 4: return hap_make_string_ro(HAP_CHARACTERISTIC_FIRMWARE_REVISION, 116 | ba->fw_revision); 117 | case 5: return (hap_characteristic_t) {.type = HAP_CHARACTERISTIC_IDENTIFY, 118 | .perms = HAP_PERM_PW 119 | }; 120 | default: 121 | return (hap_characteristic_t){}; 122 | } 123 | } 124 | 125 | 126 | static void 127 | bridged_accessory_info_fini(void *opaque) 128 | { 129 | bridged_accessory_t *ba = opaque; 130 | free(ba->name); 131 | free(ba->manufacturer); 132 | free(ba->sn); 133 | free(ba->model); 134 | free(ba->fw_revision); 135 | free(ba); 136 | } 137 | 138 | 139 | 140 | static hap_service_t * 141 | bridged_accessory_info_create(const char *name, 142 | const char *manufacturer, 143 | const char *sn, 144 | const char *model, 145 | const char *fw_revision) 146 | { 147 | bridged_accessory_t *ba = calloc(1, sizeof(bridged_accessory_t)); 148 | 149 | ba->name = strdup(name); 150 | ba->manufacturer = strdup(manufacturer); 151 | ba->sn = strdup(sn); 152 | ba->model = strdup(model); 153 | ba->fw_revision = strdup(fw_revision); 154 | 155 | return hap_service_create(ba, HAP_SERVICE_ACCESSORY_INFORMATION, 6, 156 | bridged_accessory_info_get, 157 | NULL, NULL, NULL, 158 | bridged_accessory_info_fini); 159 | } 160 | 161 | 162 | 163 | 164 | 165 | 166 | static hap_accessory_t * 167 | bridge_main(void) 168 | { 169 | hap_accessory_t *ha = 170 | hap_accessory_create("Bridge", ACCESSORY_PASSWORD, "bridge.cfg", 171 | HAP_CAT_BRIDGES, ACCESSORY_MANUFACTURER, 172 | ACCESSORY_MODEL, ACCESSORY_VERSION, NULL, NULL); 173 | 174 | // Each accessory (identified by last argument to add service) 175 | // Needs to have its own accessory-info service 176 | // First the RGB light as #2 177 | 178 | hap_accessory_add_service(ha, 179 | bridged_accessory_info_create("RGB", 180 | "Lonelycoder", 181 | "1234", 182 | "test", 183 | "1.0"), 184 | 2); 185 | 186 | // ... And its actual service 187 | 188 | hap_accessory_add_service(ha, 189 | hap_rgb_light_create(NULL, rgb_set), 190 | 2); 191 | 192 | 193 | // #3 is a regular light bulb 194 | 195 | hap_accessory_add_service(ha, 196 | bridged_accessory_info_create("Lightbulb", 197 | "Lonelycoder", 198 | "0000", 199 | "test", 200 | "1.0"), 201 | 3); 202 | 203 | // ... Lightbulbs actual service 204 | 205 | hap_accessory_add_service(ha, 206 | hap_light_builb_create(NULL, lightbulb_set), 207 | 3); 208 | 209 | return ha; 210 | } 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | static void 219 | help(void) 220 | { 221 | printf("Usage: hap \n"); 222 | printf(" MODE:\n"); 223 | printf(" light-bulb On/Off light bulb\n"); 224 | printf(" rgb RGB/Multicolor light\n"); 225 | printf(" bridge Multifunction bridge\n"); 226 | } 227 | 228 | 229 | static void 230 | ctrlc(int sig) 231 | { 232 | return; 233 | } 234 | 235 | 236 | /** 237 | *", 238 | */ 239 | int 240 | main(int argc, char **argv) 241 | { 242 | hap_accessory_t * ha; 243 | 244 | if(argc != 2) { 245 | help(); 246 | return 1; 247 | } 248 | 249 | signal(SIGINT, ctrlc); 250 | 251 | if(!strcmp(argv[1], "light-bulb")) { 252 | ha = lightbulb_main(); 253 | } else if(!strcmp(argv[1], "rgb")) { 254 | ha = rgb_main(); 255 | } else if(!strcmp(argv[1], "bridge")) { 256 | ha = bridge_main(); 257 | } else { 258 | help(); 259 | return 1; 260 | } 261 | 262 | hap_accessory_start(ha); 263 | pause(); 264 | hap_accessory_destroy(ha); 265 | return 0; 266 | } 267 | --------------------------------------------------------------------------------