/********************************************************************* PicoTCP. Copyright (c) 2014-2015 Altran Intelligent Systems. Some rights reserved. See LICENSE and COPYING for usage. . Author: Jelle De Vleeschouwer *********************************************************************/ #include "pico_dns_sd.h" #ifdef PICO_SUPPORT_DNS_SD /* --- Debugging --- */ #define dns_sd_dbg(...) do {} while(0) //#define dns_sd_dbg dbg /* --- PROTOTYPES --- */ key_value_pair_t * pico_dns_sd_kv_vector_get( kv_vector *vector, uint16_t index ); int pico_dns_sd_kv_vector_erase( kv_vector *vector ); /* ------------------- */ typedef PACKED_STRUCT_DEF pico_dns_srv_record_prefix { uint16_t priority; uint16_t weight; uint16_t port; } pico_dns_srv_record; /* **************************************************************************** * Determines the length of the resulting string when a string would be * created from a key-value pair vector. * * @param vector Key-Value pair vector to determine the length of. * @return The length of the key-value pair vector in bytes as if it would be * converted to a string. * ****************************************************************************/ static uint16_t pico_dns_sd_kv_vector_strlen( kv_vector *vector ) { key_value_pair_t *iterator = NULL; uint16_t i = 0, len = 0; /* Check params */ if (!vector) { pico_err = PICO_ERR_EINVAL; return 0; } /* Iterate over the key-value pairs */ for (i = 0; i < vector->count; i++) { iterator = pico_dns_sd_kv_vector_get(vector, i); len = (uint16_t) (len + 1u + /* Length byte */ strlen(iterator->key) /* Length of the key */); if (iterator->value) len = (uint16_t) (len + 1u /* '=' char */ + strlen(iterator->value) /* Length of value */); } return len; } /* **************************************************************************** * Creates an mDNS record with the SRV record format. * * @param url Name of the SRV record in URL format. * @param priority Priority, should be 0. * @param weight Weight, should be 0. * @param port Port to register the service on. * @param target_url Hostname of the service-target, in URL-format * @param ttl TTL of the SRV Record * @param flags mDNS record flags to set specifications of the record. * @return Pointer to newly created record on success, NULL on failure. * ****************************************************************************/ static struct pico_mdns_record * pico_dns_sd_srv_record_create( const char *url, uint16_t priority, uint16_t weight, uint16_t port, const char *target_url, uint32_t ttl, uint8_t flags ) { struct pico_mdns_record *record = NULL; pico_dns_srv_record *srv_data = NULL; char *target_rname = NULL; uint16_t srv_length = 0; /* Check params */ if (!url || !target_url) { pico_err = PICO_ERR_EINVAL; return NULL; } /* Determine the length the rdata buf needs to be */ srv_length = (uint16_t) (6u + strlen(target_url) + 2u); /* Provide space for the data-buf */ if (!(srv_data = (pico_dns_srv_record *) PICO_ZALLOC(srv_length))) { pico_err = PICO_ERR_ENOMEM; return NULL; } /* Set the fields */ srv_data->priority = short_be(priority); srv_data->weight = short_be(weight); srv_data->port = short_be(port); /* Copy in the URL and convert to DNS notation */ if (!(target_rname = pico_dns_url_to_qname(target_url))) { dns_sd_dbg("Could not convert URL to qname!\n"); PICO_FREE(srv_data); return NULL; } strcpy((char *)srv_data + 6u, target_rname); PICO_FREE(target_rname); /* Create and return new mDNS record */ record = pico_mdns_record_create(url, srv_data, srv_length, PICO_DNS_TYPE_SRV, ttl, flags); PICO_FREE(srv_data); return record; } /* **************************************************************************** * Creates an mDNS record with the TXT record format. * * @param url Name of the TXT record in URL format. * @param key_value_pairs Key-Value pair vector to generate the data from. * @param ttl TTL of the TXT record. * @param flags mDNS record flags to set specifications of the record * @return Pointer to newly created record on success, NULL on failure. * ****************************************************************************/ static struct pico_mdns_record * pico_dns_sd_txt_record_create( const char *url, kv_vector key_value_pairs, uint32_t ttl, uint8_t flags ) { struct pico_mdns_record *record = NULL; key_value_pair_t *iterator = NULL; char *txt = NULL; uint16_t i = 0, txt_i = 0, pair_len = 0, key_len = 0, value_len = 0; /* Determine the length of the string to fit in all pairs */ uint16_t len = (uint16_t)(pico_dns_sd_kv_vector_strlen(&key_value_pairs) + 1u); /* If kv-vector is empty don't bother to create a TXT record */ if (len <= 1) return NULL; /* Provide space for the txt buf */ if (!(txt = (char *)PICO_ZALLOC(len))) { pico_err = PICO_ERR_ENOMEM; return NULL; } /* Iterate over all the key-value pairs */ for (i = 0; i < key_value_pairs.count; i++) { iterator = pico_dns_sd_kv_vector_get(&key_value_pairs, i); /* Determine the length of the key */ key_len = (uint16_t) strlen(iterator->key); pair_len = key_len; /* If value is not a NULL-ptr */ if (iterator->value) { value_len = (uint16_t) strlen(iterator->value); pair_len = (uint16_t) (pair_len + 1u + value_len); } /* Set the pair length label */ txt[txt_i] = (char)pair_len; /* Copy the key */ strcpy(txt + txt_i + 1u, iterator->key); /* Copy the value if it is not a NULL-ptr */ if (iterator->value) { strcpy(txt + txt_i + 1u + key_len, "="); strcpy(txt + txt_i + 2u + key_len, iterator->value); txt_i = (uint16_t) (txt_i + 2u + key_len + value_len); } else { txt_i = (uint16_t) (txt_i + 1u + key_len); } } record = pico_mdns_record_create(url, txt, (uint16_t)(len - 1u), PICO_DNS_TYPE_TXT, ttl, flags); PICO_FREE(txt); return record; } /* **************************************************************************** * Deletes a single key-value pair instance * * @param kv_pair Pointer-pointer to to delete instance * @return Returns 0 on success, something else on failure. * ****************************************************************************/ static int pico_dns_sd_kv_delete( key_value_pair_t **kv_pair ) { /* Check params */ if (!kv_pair || !(*kv_pair)) { pico_err = PICO_ERR_EINVAL; return -1; } /* Delete the fields */ if ((*kv_pair)->key) PICO_FREE((*kv_pair)->key); if ((*kv_pair)->value) PICO_FREE((*kv_pair)->value); PICO_FREE(*kv_pair); *kv_pair = NULL; kv_pair = NULL; return 0; } /* **************************************************************************** * Creates a single key-value pair-instance * * @param key Key of the pair, cannot be NULL. * @param value Value of the pair, can be NULL, empty ("") or filled ("qkejq") * @return Pointer to newly created KV-instance on success, NULL on failure. * ****************************************************************************/ static key_value_pair_t * pico_dns_sd_kv_create( const char *key, const char *value ) { key_value_pair_t *kv_pair = NULL; /* Check params */ if (!key || !(kv_pair = PICO_ZALLOC(sizeof(key_value_pair_t)))) { pico_dns_sd_kv_delete(&kv_pair); pico_err = PICO_ERR_EINVAL; return NULL; } /* Provide space to copy the values */ if (!(kv_pair->key = PICO_ZALLOC((size_t)(strlen(key) + 1)))) { pico_err = PICO_ERR_ENOMEM; pico_dns_sd_kv_delete(&kv_pair); return NULL; } strcpy(kv_pair->key, key); if (value) { if (!(kv_pair->value = PICO_ZALLOC((size_t)(strlen(value) + 1)))) { pico_err = PICO_ERR_ENOMEM; pico_dns_sd_kv_delete(&kv_pair); return NULL; } strcpy(kv_pair->value, value); } else kv_pair->value = NULL; return kv_pair; } /* **************************************************************************** * Checks whether the type is correctly formatted ant it's label length are * between the allowed boundaries. * * @param type Servicetype to check the format of. * @return Returns 0 when the type is correctly formatted, something else when * it's not. * ****************************************************************************/ static int pico_dns_sd_check_type_format( const char *type ) { uint16_t first_lbl = 0; int8_t subtype_present = 0; /* Check params */ if (!(first_lbl = pico_dns_first_label_length(type))) return -1; subtype_present = !memcmp(type + first_lbl + 1, "_sub", 4); /* Check if there is a subtype present */ if (subtype_present && (first_lbl > 63)) return -1; else if (subtype_present) /* Get the length of the service name */ first_lbl = pico_dns_first_label_length(type + first_lbl + 6); else { /* Check if type is not greater then 21 bytes (22 - 1, since the length byte of the service name isn't included yet) */ if (strlen(type) > (size_t) 21) return -1; } /* Check if the service name is not greater then 16 bytes (17 - 1) */ return (first_lbl > ((uint16_t) 16u)); } /* **************************************************************************** * Checks whether the service instance name is correctly formatted and it's * label length falls between the allowed boundaries. * * @param name Instance name to check the format of. * @return Returns 0 when the name is correctly formatted, something else when * it's not. * ****************************************************************************/ static int pico_dns_sd_check_instance_name_format( const char *name ) { /* First of all check if the total length is larger than 63 bytes */ if (pico_dns_strlen(name) > 63 || !pico_dns_strlen(name)) return -1; return 0; } /* **************************************************************************** * Append the instance name adn service type to create a '.local' service SIN. * * @param name Instance Name of the service, f.e. "Printer 2nd Floor". * @param type ServiceType of the service, f.e. "_http._tcp". * @return Pointer to newly created SIN on success, NULL on failure. * ****************************************************************************/ static char * pico_dns_sd_create_service_url( const char *name, const char *type ) { char *url = NULL; uint16_t len = 0, namelen = 0, typelen = 0; if (pico_dns_sd_check_type_format(type)) { pico_err = PICO_ERR_EINVAL; return NULL; } if (pico_dns_sd_check_instance_name_format(name)) { pico_err = PICO_ERR_EINVAL; return NULL; } namelen = (uint16_t)strlen(name); typelen = (uint16_t)strlen(type); /* Determine the length that the URL needs to be */ len = (uint16_t)(namelen + 1u /* for '.'*/ + typelen + 7u /* for '.local\0' */); url = (char *)PICO_ZALLOC(len); if (!url) { pico_err = PICO_ERR_ENOMEM; return NULL; } /* Append the parts together */ strcpy(url, name); strcpy(url + namelen, "."); strcpy(url + namelen + 1, type); strcpy(url + namelen + 1 + typelen, ".local"); return url; } /* **************************************************************************** * This function actually does exactly the same as pico_mdns_init(); * ****************************************************************************/ int pico_dns_sd_init( const char *_hostname, struct pico_ip4 address, void (*callback)(pico_mdns_rtree *, char *, void *), void *arg ) { return pico_mdns_init(_hostname, address, callback, arg); } /* **************************************************************************** * Just calls pico_mdns_init in it's turn to initialise the mDNS-module. * See pico_mdns.h for description. * ****************************************************************************/ int pico_dns_sd_register_service( const char *name, const char *type, uint16_t port, kv_vector *txt_data, uint16_t ttl, void (*callback)(pico_mdns_rtree *, char *, void *), void *arg) { PICO_MDNS_RTREE_DECLARE(rtree); struct pico_mdns_record *srv_record = NULL; struct pico_mdns_record *txt_record = NULL; const char *hostname = pico_mdns_get_hostname(); char *url = NULL; /* Try to create a service URL to create records with */ if (!(url = pico_dns_sd_create_service_url(name, type)) || !txt_data || !hostname) { if (url) PICO_FREE(url); pico_err = PICO_ERR_EINVAL; return -1; } dns_sd_dbg("\n>>>>>>>>>> Target: %s <<<<<<<<<<\n\n", hostname); /* Create the SRV record */ srv_record = pico_dns_sd_srv_record_create(url, 0, 0, port, hostname, ttl, PICO_MDNS_RECORD_UNIQUE); if (!srv_record) { PICO_FREE(url); return -1; } /* Create the TXT record */ txt_record = pico_dns_sd_txt_record_create(url, *txt_data, ttl, PICO_MDNS_RECORD_UNIQUE); PICO_FREE(url); /* Erase the key-value pair vector, it's no longer needed */ pico_dns_sd_kv_vector_erase(txt_data); if (txt_record) pico_tree_insert(&rtree, txt_record); pico_tree_insert(&rtree, srv_record); if (pico_mdns_claim(rtree, callback, arg)) { PICO_MDNS_RTREE_DESTROY(&rtree); return -1; } pico_tree_destroy(&rtree, NULL); return 0; } /* **************************************************************************** * Does nothing for now. * * @param type Type to browse for. * @param callback Callback to call when something particular happens. * @return When the module successfully started browsing the servicetype. * ****************************************************************************/ int pico_dns_sd_browse_service( const char *type, void (*callback)(pico_mdns_rtree *, char *, void *), void *arg ) { IGNORE_PARAMETER(type); IGNORE_PARAMETER(callback); IGNORE_PARAMETER(arg); return 0; } /* **************************************************************************** * Add a key-value pair the a key-value pair vector. * * @param vector Vector to add the pair to. * @param key Key of the pair, cannot be NULL. * @param value Value of the pair, can be NULL, empty ("") or filled ("qkejq") * @return Returns 0 when the pair is added successfully, something else on * failure. * ****************************************************************************/ int pico_dns_sd_kv_vector_add( kv_vector *vector, char *key, char *value ) { key_value_pair_t *kv_pair = NULL; key_value_pair_t **new_pairs = NULL; uint16_t i = 0; /* Check params */ if (!vector || !key || !(kv_pair = pico_dns_sd_kv_create(key, value))) { pico_err = PICO_ERR_EINVAL; pico_dns_sd_kv_delete(&kv_pair); return -1; } /* Provide enough space for the new pair pointers */ if (!(new_pairs = PICO_ZALLOC(sizeof(key_value_pair_t *) * (vector->count + 1u)))) { pico_err = PICO_ERR_ENOMEM; pico_dns_sd_kv_delete(&kv_pair); return -1; } /* Copy previous pairs and add new one */ for (i = 0; i < vector->count; i++) new_pairs[i] = vector->pairs[i]; new_pairs[i] = kv_pair; /* Free the previous array */ if (vector->pairs) PICO_FREE(vector->pairs); vector->pairs = new_pairs; vector->count++; return 0; } /* **************************************************************************** * Gets a single key-value pair form a Key-Value pair vector @ certain index. * * @param vector Vector to get KV-pair from. * @param index Index of the KV-pair. * @return key_value_pair_t* on success, NULL on failure. * ****************************************************************************/ key_value_pair_t * pico_dns_sd_kv_vector_get( kv_vector *vector, uint16_t index ) { /* Check params */ if (!vector) return NULL; /* Return record with conditioned index */ if (index < vector->count) return vector->pairs[index]; return NULL; } /* **************************************************************************** * Erase all the contents of a key-value pair vector. * * @param vector Key-Value pair vector. * @return 0 on success, something else on failure. * ****************************************************************************/ int pico_dns_sd_kv_vector_erase( kv_vector *vector ) { uint16_t i = 0; /* Iterate over each key-value pair */ for (i = 0; i < vector->count; i++) { if (pico_dns_sd_kv_delete(&(vector->pairs[i])) < 0) { dns_sd_dbg("Could not delete key-value pairs from vector"); return -1; } } PICO_FREE(vector->pairs); vector->pairs = NULL; vector->count = 0; return 0; } #endif