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