1/********************************************************************* 2 PicoTCP. Copyright (c) 2014-2017 Altran Intelligent Systems. Some rights reserved. 3 See COPYING, LICENSE.GPLv2 and LICENSE.GPLv3 for usage. 4 . 5 Author: Jelle De Vleeschouwer 6 *********************************************************************/ 7 8#include "pico_dns_sd.h" 9 10#ifdef PICO_SUPPORT_DNS_SD 11 12/* --- Debugging --- */ 13#ifdef DEBUG_DNS_SD 14 #define dns_sd_dbg dbg 15#else 16 #define dns_sd_dbg(...) do {} while(0) 17#endif 18 19/* --- PROTOTYPES --- */ 20key_value_pair_t * 21pico_dns_sd_kv_vector_get( kv_vector *vector, uint16_t index ); 22int 23pico_dns_sd_kv_vector_erase( kv_vector *vector ); 24/* ------------------- */ 25 26typedef PACKED_STRUCT_DEF pico_dns_srv_record_prefix 27{ 28 uint16_t priority; 29 uint16_t weight; 30 uint16_t port; 31} pico_dns_srv_record; 32 33/* **************************************************************************** 34 * Determines the length of the resulting string when a string would be 35 * created from a key-value pair vector. 36 * 37 * @param vector Key-Value pair vector to determine the length of. 38 * @return The length of the key-value pair vector in bytes as if it would be 39 * converted to a string. 40 * ****************************************************************************/ 41static uint16_t 42pico_dns_sd_kv_vector_strlen( kv_vector *vector ) 43{ 44 key_value_pair_t *iterator = NULL; 45 uint16_t i = 0, len = 0; 46 47 /* Check params */ 48 if (!vector) { 49 pico_err = PICO_ERR_EINVAL; 50 return 0; 51 } 52 53 /* Iterate over the key-value pairs */ 54 for (i = 0; i < vector->count; i++) { 55 iterator = pico_dns_sd_kv_vector_get(vector, i); 56 len = (uint16_t) (len + 1u + /* Length byte */ 57 strlen(iterator->key) /* Length of the key */); 58 if (iterator->value) { 59 len = (uint16_t) (len + 1u /* '=' char */ + 60 strlen(iterator->value) /* Length of value */); 61 } 62 } 63 return len; 64} 65 66/* **************************************************************************** 67 * Creates an mDNS record with the SRV record format. 68 * 69 * @param url Name of the SRV record in URL format. 70 * @param priority Priority, should be 0. 71 * @param weight Weight, should be 0. 72 * @param port Port to register the service on. 73 * @param target_url Hostname of the service-target, in URL-format 74 * @param ttl TTL of the SRV Record 75 * @param flags mDNS record flags to set specifications of the record. 76 * @return Pointer to newly created record on success, NULL on failure. 77 * ****************************************************************************/ 78static struct pico_mdns_record * 79pico_dns_sd_srv_record_create( const char *url, 80 uint16_t priority, 81 uint16_t weight, 82 uint16_t port, 83 const char *target_url, 84 uint32_t ttl, 85 uint8_t flags ) 86{ 87 struct pico_mdns_record *record = NULL; 88 pico_dns_srv_record *srv_data = NULL; 89 char *target_rname = NULL; 90 uint16_t srv_length = 0; 91 92 /* Check params */ 93 if (!url || !target_url) { 94 pico_err = PICO_ERR_EINVAL; 95 return NULL; 96 } 97 98 /* Determine the length the rdata buf needs to be */ 99 srv_length = (uint16_t) (6u + strlen(target_url) + 2u); 100 101 /* Provide space for the data-buf */ 102 if (!(srv_data = (pico_dns_srv_record *) PICO_ZALLOC(srv_length))) { 103 pico_err = PICO_ERR_ENOMEM; 104 return NULL; 105 } 106 107 /* Set the fields */ 108 srv_data->priority = short_be(priority); 109 srv_data->weight = short_be(weight); 110 srv_data->port = short_be(port); 111 112 /* Copy in the URL and convert to DNS notation */ 113 if (!(target_rname = pico_dns_url_to_qname(target_url))) { 114 dns_sd_dbg("Could not convert URL to qname!\n"); 115 PICO_FREE(srv_data); 116 return NULL; 117 } 118 119 strcpy((char *)srv_data + 6u, target_rname); 120 PICO_FREE(target_rname); 121 122 /* Create and return new mDNS record */ 123 record = pico_mdns_record_create(url, srv_data, srv_length, 124 PICO_DNS_TYPE_SRV, 125 ttl, flags); 126 PICO_FREE(srv_data); 127 return record; 128} 129 130/* **************************************************************************** 131 * Creates an mDNS record with the TXT record format. 132 * 133 * @param url Name of the TXT record in URL format. 134 * @param key_value_pairs Key-Value pair vector to generate the data from. 135 * @param ttl TTL of the TXT record. 136 * @param flags mDNS record flags to set specifications of the record 137 * @return Pointer to newly created record on success, NULL on failure. 138 * ****************************************************************************/ 139static struct pico_mdns_record * 140pico_dns_sd_txt_record_create( const char *url, 141 kv_vector key_value_pairs, 142 uint32_t ttl, 143 uint8_t flags ) 144{ 145 struct pico_mdns_record *record = NULL; 146 key_value_pair_t *iterator = NULL; 147 char *txt = NULL; 148 uint16_t i = 0, txt_i = 0, pair_len = 0, key_len = 0, value_len = 0; 149 150 /* Determine the length of the string to fit in all pairs */ 151 uint16_t len = (uint16_t)(pico_dns_sd_kv_vector_strlen(&key_value_pairs) + 1u); 152 153 /* If kv-vector is empty don't bother to create a TXT record */ 154 if (len <= 1) { 155 return NULL; 156 } 157 158 /* Provide space for the txt buf */ 159 if (!(txt = (char *)PICO_ZALLOC(len))) { 160 pico_err = PICO_ERR_ENOMEM; 161 return NULL; 162 } 163 164 /* Iterate over all the key-value pairs */ 165 for (i = 0; i < key_value_pairs.count; i++) { 166 iterator = pico_dns_sd_kv_vector_get(&key_value_pairs, i); 167 168 /* Determine the length of the key */ 169 key_len = (uint16_t) strlen(iterator->key); 170 pair_len = key_len; 171 172 /* If value is not a NULL-ptr */ 173 if (iterator->value) { 174 value_len = (uint16_t) strlen(iterator->value); 175 pair_len = (uint16_t) (pair_len + 1u + value_len); 176 } 177 178 /* Set the pair length label */ 179 txt[txt_i] = (char)pair_len; 180 181 /* Copy the key */ 182 strcpy(txt + txt_i + 1u, iterator->key); 183 184 /* Copy the value if it is not a NULL-ptr */ 185 if (iterator->value) { 186 strcpy(txt + txt_i + 1u + key_len, "="); 187 strcpy(txt + txt_i + 2u + key_len, iterator->value); 188 txt_i = (uint16_t) (txt_i + 2u + key_len + value_len); 189 } else { 190 txt_i = (uint16_t) (txt_i + 1u + key_len); 191 } 192 } 193 record = pico_mdns_record_create(url, txt, (uint16_t)(len - 1u), PICO_DNS_TYPE_TXT, ttl, flags); 194 PICO_FREE(txt); 195 196 return record; 197} 198 199/* **************************************************************************** 200 * Deletes a single key-value pair instance 201 * 202 * @param kv_pair Pointer-pointer to to delete instance 203 * @return Returns 0 on success, something else on failure. 204 * ****************************************************************************/ 205static int 206pico_dns_sd_kv_delete( key_value_pair_t **kv_pair ) 207{ 208 /* Check params */ 209 if (!kv_pair || !(*kv_pair)) { 210 pico_err = PICO_ERR_EINVAL; 211 return -1; 212 } 213 214 /* Delete the fields */ 215 if ((*kv_pair)->key) 216 PICO_FREE((*kv_pair)->key); 217 218 if ((*kv_pair)->value) 219 PICO_FREE((*kv_pair)->value); 220 221 PICO_FREE(*kv_pair); 222 *kv_pair = NULL; 223 kv_pair = NULL; 224 225 return 0; 226} 227 228/* **************************************************************************** 229 * Creates a single key-value pair-instance 230 * 231 * @param key Key of the pair, cannot be NULL. 232 * @param value Value of the pair, can be NULL, empty ("") or filled ("qkejq") 233 * @return Pointer to newly created KV-instance on success, NULL on failure. 234 * ****************************************************************************/ 235static key_value_pair_t * 236pico_dns_sd_kv_create( const char *key, const char *value ) 237{ 238 key_value_pair_t *kv_pair = NULL; 239 240 /* Check params */ 241 if (!key || !(kv_pair = PICO_ZALLOC(sizeof(key_value_pair_t)))) { 242 pico_dns_sd_kv_delete(&kv_pair); 243 pico_err = PICO_ERR_EINVAL; 244 return NULL; 245 } 246 247 /* Provide space to copy the values */ 248 if (!(kv_pair->key = PICO_ZALLOC((size_t)(strlen(key) + 1)))) { 249 pico_err = PICO_ERR_ENOMEM; 250 pico_dns_sd_kv_delete(&kv_pair); 251 return NULL; 252 } 253 254 strcpy(kv_pair->key, key); 255 256 if (value) { 257 if (!(kv_pair->value = PICO_ZALLOC((size_t)(strlen(value) + 1)))) { 258 pico_err = PICO_ERR_ENOMEM; 259 pico_dns_sd_kv_delete(&kv_pair); 260 return NULL; 261 } 262 263 strcpy(kv_pair->value, value); 264 } else 265 kv_pair->value = NULL; 266 267 return kv_pair; 268} 269 270/* **************************************************************************** 271 * Checks whether the type is correctly formatted ant it's label length are 272 * between the allowed boundaries. 273 * 274 * @param type Servicetype to check the format of. 275 * @return Returns 0 when the type is correctly formatted, something else when 276 * it's not. 277 * ****************************************************************************/ 278static int 279pico_dns_sd_check_type_format( const char *type ) 280{ 281 uint16_t first_lbl = 0; 282 int8_t subtype_present = 0; 283 284 /* Check params */ 285 if (!(first_lbl = pico_dns_first_label_length(type))) 286 return -1; 287 288 subtype_present = !memcmp(type + first_lbl + 1, "_sub", 4); 289 290 /* Check if there is a subtype present */ 291 if (subtype_present && (first_lbl > 63)) 292 return -1; 293 else if (subtype_present) 294 /* Get the length of the service name */ 295 first_lbl = pico_dns_first_label_length(type + first_lbl + 6); 296 else { 297 /* Check if type is not greater then 21 bytes (22 - 1, since the length 298 byte of the service name isn't included yet) */ 299 if (strlen(type) > (size_t) 21) 300 return -1; 301 } 302 303 /* Check if the service name is not greater then 16 bytes (17 - 1) */ 304 return (first_lbl > ((uint16_t) 16u)); 305} 306 307/* **************************************************************************** 308 * Checks whether the service instance name is correctly formatted and it's 309 * label length falls between the allowed boundaries. 310 * 311 * @param name Instance name to check the format of. 312 * @return Returns 0 when the name is correctly formatted, something else when 313 * it's not. 314 * ****************************************************************************/ 315static int 316pico_dns_sd_check_instance_name_format( const char *name ) 317{ 318 /* First of all check if the total length is larger than 63 bytes */ 319 if (pico_dns_strlen(name) > 63 || !pico_dns_strlen(name)) 320 return -1; 321 322 return 0; 323} 324 325/* **************************************************************************** 326 * Append the instance name adn service type to create a '.local' service SIN. 327 * 328 * @param name Instance Name of the service, f.e. "Printer 2nd Floor". 329 * @param type ServiceType of the service, f.e. "_http._tcp". 330 * @return Pointer to newly created SIN on success, NULL on failure. 331 * ****************************************************************************/ 332static char * 333pico_dns_sd_create_service_url( const char *name, 334 const char *type ) 335{ 336 char *url = NULL; 337 uint16_t len = 0, namelen = 0, typelen = 0; 338 339 if (pico_dns_sd_check_type_format(type)) { 340 pico_err = PICO_ERR_EINVAL; 341 return NULL; 342 } 343 344 if (pico_dns_sd_check_instance_name_format(name)) { 345 pico_err = PICO_ERR_EINVAL; 346 return NULL; 347 } 348 349 namelen = (uint16_t)strlen(name); 350 typelen = (uint16_t)strlen(type); 351 352 /* Determine the length that the URL needs to be */ 353 len = (uint16_t)(namelen + 1u /* for '.'*/ + 354 typelen + 7u /* for '.local\0' */); 355 url = (char *)PICO_ZALLOC(len); 356 if (!url) { 357 pico_err = PICO_ERR_ENOMEM; 358 return NULL; 359 } 360 361 /* Append the parts together */ 362 strcpy(url, name); 363 strcpy(url + namelen, "."); 364 strcpy(url + namelen + 1, type); 365 strcpy(url + namelen + 1 + typelen, ".local"); 366 367 return url; 368} 369 370/* **************************************************************************** 371 * This function actually does exactly the same as pico_mdns_init(); 372 * ****************************************************************************/ 373int 374pico_dns_sd_init( const char *_hostname, 375 struct pico_ip4 address, 376 void (*callback)(pico_mdns_rtree *, 377 char *, 378 void *), 379 void *arg ) 380{ 381 return pico_mdns_init(_hostname, address, callback, arg); 382} 383 384/* **************************************************************************** 385 * Just calls pico_mdns_init in its turn to initialise the mDNS-module. 386 * See pico_mdns.h for description. 387 * ****************************************************************************/ 388int 389pico_dns_sd_register_service( const char *name, 390 const char *type, 391 uint16_t port, 392 kv_vector *txt_data, 393 uint16_t ttl, 394 void (*callback)(pico_mdns_rtree *, 395 char *, 396 void *), 397 void *arg) 398{ 399 PICO_MDNS_RTREE_DECLARE(rtree); 400 struct pico_mdns_record *srv_record = NULL; 401 struct pico_mdns_record *txt_record = NULL; 402 const char *hostname = pico_mdns_get_hostname(); 403 char *url = NULL; 404 405 /* Try to create a service URL to create records with */ 406 if (!(url = pico_dns_sd_create_service_url(name, type)) || !txt_data || !hostname) { 407 if (url) { 408 PICO_FREE(url); 409 } 410 411 pico_err = PICO_ERR_EINVAL; 412 return -1; 413 } 414 415 dns_sd_dbg("\n>>>>>>>>>> Target: %s <<<<<<<<<<\n\n", hostname); 416 417 /* Create the SRV record */ 418 srv_record = pico_dns_sd_srv_record_create(url, 0, 0, port, hostname, ttl, PICO_MDNS_RECORD_UNIQUE); 419 if (!srv_record) { 420 PICO_FREE(url); 421 return -1; 422 } 423 424 /* Create the TXT record */ 425 txt_record = pico_dns_sd_txt_record_create(url, *txt_data, ttl, PICO_MDNS_RECORD_UNIQUE); 426 PICO_FREE(url); 427 428 /* Erase the key-value pair vector, it's no longer needed */ 429 pico_dns_sd_kv_vector_erase(txt_data); 430 431 if (txt_record) { 432 if (pico_tree_insert(&rtree, txt_record) == &LEAF) { 433 PICO_MDNS_RTREE_DESTROY(&rtree); 434 pico_mdns_record_delete((void **)&txt_record); 435 pico_mdns_record_delete((void **)&srv_record); 436 return -1; 437 } 438 } 439 440 if (pico_tree_insert(&rtree, srv_record) == &LEAF) { 441 PICO_MDNS_RTREE_DESTROY(&rtree); 442 pico_mdns_record_delete((void **)&srv_record); 443 return -1; 444 } 445 446 if (pico_mdns_claim(rtree, callback, arg)) { 447 PICO_MDNS_RTREE_DESTROY(&rtree); 448 return -1; 449 } 450 451 /* Only destroy the tree, not its elements since they still exist in another tree */ 452 pico_tree_destroy(&rtree, NULL); 453 return 0; 454} 455 456/* **************************************************************************** 457 * Does nothing for now. 458 * 459 * @param type Type to browse for. 460 * @param callback Callback to call when something particular happens. 461 * @return When the module successfully started browsing the servicetype. 462 * ****************************************************************************/ 463int 464pico_dns_sd_browse_service( const char *type, 465 void (*callback)(pico_mdns_rtree *, 466 char *, 467 void *), 468 void *arg ) 469{ 470 IGNORE_PARAMETER(type); 471 IGNORE_PARAMETER(callback); 472 IGNORE_PARAMETER(arg); 473 return 0; 474} 475 476/* **************************************************************************** 477 * Add a key-value pair the a key-value pair vector. 478 * 479 * @param vector Vector to add the pair to. 480 * @param key Key of the pair, cannot be NULL. 481 * @param value Value of the pair, can be NULL, empty ("") or filled ("qkejq") 482 * @return Returns 0 when the pair is added successfully, something else on 483 * failure. 484 * ****************************************************************************/ 485int 486pico_dns_sd_kv_vector_add( kv_vector *vector, char *key, char *value ) 487{ 488 key_value_pair_t *kv_pair = NULL; 489 key_value_pair_t **new_pairs = NULL; 490 uint16_t i = 0; 491 492 /* Check params */ 493 if (!vector || !key || !(kv_pair = pico_dns_sd_kv_create(key, value))) { 494 pico_err = PICO_ERR_EINVAL; 495 pico_dns_sd_kv_delete(&kv_pair); 496 return -1; 497 } 498 499 /* Provide enough space for the new pair pointers */ 500 if (!(new_pairs = PICO_ZALLOC(sizeof(key_value_pair_t *) * 501 (vector->count + 1u)))) { 502 pico_err = PICO_ERR_ENOMEM; 503 pico_dns_sd_kv_delete(&kv_pair); 504 return -1; 505 } 506 507 /* Copy previous pairs and add new one */ 508 for (i = 0; i < vector->count; i++) 509 new_pairs[i] = vector->pairs[i]; 510 new_pairs[i] = kv_pair; 511 512 /* Free the previous array */ 513 if (vector->pairs) 514 PICO_FREE(vector->pairs); 515 516 vector->pairs = new_pairs; 517 vector->count++; 518 519 return 0; 520} 521 522/* **************************************************************************** 523 * Gets a single key-value pair form a Key-Value pair vector @ certain index. 524 * 525 * @param vector Vector to get KV-pair from. 526 * @param index Index of the KV-pair. 527 * @return key_value_pair_t* on success, NULL on failure. 528 * ****************************************************************************/ 529key_value_pair_t * 530pico_dns_sd_kv_vector_get( kv_vector *vector, uint16_t index ) 531{ 532 /* Check params */ 533 if (!vector) 534 return NULL; 535 536 /* Return record with conditioned index */ 537 if (index < vector->count) 538 return vector->pairs[index]; 539 540 return NULL; 541} 542 543/* **************************************************************************** 544 * Erase all the contents of a key-value pair vector. 545 * 546 * @param vector Key-Value pair vector. 547 * @return 0 on success, something else on failure. 548 * ****************************************************************************/ 549int 550pico_dns_sd_kv_vector_erase( kv_vector *vector ) 551{ 552 uint16_t i = 0; 553 554 /* Iterate over each key-value pair */ 555 for (i = 0; i < vector->count; i++) { 556 if (pico_dns_sd_kv_delete(&(vector->pairs[i])) < 0) { 557 dns_sd_dbg("Could not delete key-value pairs from vector"); 558 return -1; 559 } 560 } 561 PICO_FREE(vector->pairs); 562 vector->pairs = NULL; 563 vector->count = 0; 564 565 return 0; 566} 567 568#endif 569