]> granicus.if.org Git - esp-idf/commitdiff
Initial mDNS component and example
authorme-no-dev <hristo@espressif.com>
Wed, 4 Jan 2017 16:54:07 +0000 (18:54 +0200)
committerme-no-dev <hristo@espressif.com>
Fri, 13 Jan 2017 10:12:43 +0000 (12:12 +0200)
12 files changed:
components/mdns/component.mk [new file with mode: 0644]
components/mdns/include/mdns.h [new file with mode: 0644]
components/mdns/mdns.c [new file with mode: 0644]
components/tcpip_adapter/include/tcpip_adapter.h
docs/Doxyfile
docs/api/mdns.rst [new file with mode: 0644]
docs/index.rst
examples/30_mdns_example/Makefile [new file with mode: 0644]
examples/30_mdns_example/README.md [new file with mode: 0644]
examples/30_mdns_example/main/Kconfig.projbuild [new file with mode: 0644]
examples/30_mdns_example/main/component.mk [new file with mode: 0644]
examples/30_mdns_example/main/mdns_example_main.c [new file with mode: 0644]

diff --git a/components/mdns/component.mk b/components/mdns/component.mk
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/components/mdns/include/mdns.h b/components/mdns/include/mdns.h
new file mode 100644 (file)
index 0000000..58e588e
--- /dev/null
@@ -0,0 +1,235 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef ESP_MDNS_H_
+#define ESP_MDNS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <tcpip_adapter.h>
+
+struct mdns_server_s;
+typedef struct mdns_server_s mdns_server_t;
+
+/**
+ * @brief mDNS query result structure
+ *
+ */
+typedef struct mdns_result_s {
+    const char * host;                  /*!< hostname */
+    const char * instance;              /*!< instance */
+    const char * txt;                   /*!< txt data */
+    uint16_t priority;                  /*!< service priority */
+    uint16_t weight;                    /*!< service weight */
+    uint16_t port;                      /*!< service port */
+    struct ip4_addr addr;               /*!< ip4 address */
+    struct ip6_addr addrv6;             /*!< ip6 address */
+    const struct mdns_result_s * next;  /*!< next result, or NULL for the last result in the list */
+} mdns_result_t;
+
+/**
+ * @brief  Initialize mDNS on given interface
+ *
+ * @param  tcpip_if     Interface that the server will listen on
+ * @param  server       Server pointer to populate on success
+ *
+ * @return
+ *     - ESP_OK on success
+ *     - ESP_ERR_INVALID_ARG when bad tcpip_if is given
+ *     - ESP_ERR_INVALID_STATE when the network returned error
+ *     - ESP_ERR_NO_MEM on memory error
+ *     - ESP_ERR_WIFI_NOT_INIT when WiFi is not initialized by eps_wifi_init
+ */
+esp_err_t mdns_init(tcpip_adapter_if_t tcpip_if, mdns_server_t ** server);
+
+/**
+ * @brief  Stop and free mDNS server
+ *
+ * @param  server       mDNS Server to free
+ *
+ */
+void mdns_free(mdns_server_t * server);
+
+/**
+ * @brief  Set the hostname for mDNS server
+ *
+ * @param  server       mDNS Server
+ * @param  hostname     Hostname to set
+ *
+ * @return
+ *     - ESP_OK success
+ *     - ESP_ERR_INVALID_ARG Parameter error
+ *     - ESP_ERR_NO_MEM memory error
+ */
+esp_err_t mdns_set_hostname(mdns_server_t * server, const char * hostname);
+
+/**
+ * @brief  Set the default instance name for mDNS server
+ *
+ * @param  server       mDNS Server
+ * @param  instance     Instance name to set
+ *
+ * @return
+ *     - ESP_OK success
+ *     - ESP_ERR_INVALID_ARG Parameter error
+ *     - ESP_ERR_NO_MEM memory error
+ */
+esp_err_t mdns_set_instance(mdns_server_t * server, const char * instance);
+
+/**
+ * @brief  Add service to mDNS server
+ *
+ * @param  server       mDNS Server
+ * @param  service      service type (_http, _ftp, etc)
+ * @param  proto        service protocol (_tcp, _udp)
+ * @param  port         service port
+ *
+ * @return
+ *     - ESP_OK success
+ *     - ESP_ERR_INVALID_ARG Parameter error
+ *     - ESP_ERR_NO_MEM memory error
+ */
+esp_err_t mdns_service_add(mdns_server_t * server, const char * service, const char * proto, uint16_t port);
+
+/**
+ * @brief  Remove service from mDNS server
+ *
+ * @param  server       mDNS Server
+ * @param  service      service type (_http, _ftp, etc)
+ * @param  proto        service protocol (_tcp, _udp)
+ *
+ * @return
+ *     - ESP_OK success
+ *     - ESP_ERR_INVALID_ARG Parameter error
+ *     - ESP_ERR_NOT_FOUND Service not found
+ *     - ESP_FAIL unknown error
+ */
+esp_err_t mdns_service_remove(mdns_server_t * server, const char * service, const char * proto);
+
+/**
+ * @brief  Set instance name for service
+ *
+ * @param  server       mDNS Server
+ * @param  service      service type (_http, _ftp, etc)
+ * @param  proto        service protocol (_tcp, _udp)
+ * @param  instance     instance name to set
+ *
+ * @return
+ *     - ESP_OK success
+ *     - ESP_ERR_INVALID_ARG Parameter error
+ *     - ESP_ERR_NOT_FOUND Service not found
+ *     - ESP_ERR_NO_MEM memory error
+ */
+esp_err_t mdns_service_instance_set(mdns_server_t * server, const char * service, const char * proto, const char * instance);
+
+/**
+ * @brief  Set TXT data for service
+ *
+ * @param  server       mDNS Server
+ * @param  service      service type (_http, _ftp, etc)
+ * @param  proto        service protocol (_tcp, _udp)
+ * @param  num_items    number of items in TXT data
+ * @param  txt          string array of TXT data (eg. {"var=val","other=2"})
+ *
+ * @return
+ *     - ESP_OK success
+ *     - ESP_ERR_INVALID_ARG Parameter error
+ *     - ESP_ERR_NOT_FOUND Service not found
+ *     - ESP_ERR_NO_MEM memory error
+ */
+esp_err_t mdns_service_txt_set(mdns_server_t * server, const char * service, const char * proto, uint8_t num_items, const char ** txt);
+
+/**
+ * @brief  Set service port
+ *
+ * @param  server       mDNS Server
+ * @param  service      service type (_http, _ftp, etc)
+ * @param  proto        service protocol (_tcp, _udp)
+ * @param  port         service port
+ *
+ * @return
+ *     - ESP_OK success
+ *     - ESP_ERR_INVALID_ARG Parameter error
+ *     - ESP_ERR_NOT_FOUND Service not found
+ */
+esp_err_t mdns_service_port_set(mdns_server_t * server, const char * service, const char * proto, uint16_t port);
+
+/**
+ * @brief  Remove and free all services from mDNS server
+ *
+ * @param  server       mDNS Server
+ *
+ * @return
+ *     - ESP_OK success
+ *     - ESP_ERR_INVALID_ARG Parameter error
+ */
+esp_err_t mdns_service_remove_all(mdns_server_t * server);
+
+/**
+ * @brief  Query mDNS for host or service
+ *
+ * @param  server       mDNS Server
+ * @param  service      service type or host name
+ * @param  proto        service protocol or NULL if searching for host
+ * @param  timeout      time to wait for answers. If 0, mdns_query_end MUST be called to end the search
+ *
+ * @return the number of results found
+ */
+size_t mdns_query(mdns_server_t * server, const char * service, const char * proto, uint32_t timeout);
+
+/**
+ * @brief  Stop mDNS Query started with timeout = 0
+ *
+ * @param  server       mDNS Server
+ *
+ * @return the number of results found
+ */
+size_t mdns_query_end(mdns_server_t * server);
+
+/**
+ * @brief  get the number of results currently in memoty
+ *
+ * @param  server       mDNS Server
+ *
+ * @return the number of results
+ */
+size_t mdns_result_get_count(mdns_server_t * server);
+
+/**
+ * @brief  Get mDNS Search result with given index
+ *
+ * @param  server       mDNS Server
+ * @param  num          the index of the result
+ *
+ * @return the result or NULL if error
+ */
+const mdns_result_t * mdns_result_get(mdns_server_t * server, size_t num);
+
+/**
+ * @brief  Remove and free all search results from mDNS server
+ *
+ * @param  server       mDNS Server
+ *
+ * @return
+ *     - ESP_OK success
+ *     - ESP_ERR_INVALID_ARG Parameter error
+ */
+esp_err_t mdns_result_free(mdns_server_t * server);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ESP_MDNS_H_ */
diff --git a/components/mdns/mdns.c b/components/mdns/mdns.c
new file mode 100644 (file)
index 0000000..d636134
--- /dev/null
@@ -0,0 +1,1861 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include "mdns.h"
+
+#include <string.h>
+#include "sdkconfig.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/queue.h"
+#include "freertos/semphr.h"
+#include "lwip/ip_addr.h"
+#include "lwip/pbuf.h"
+#include "lwip/igmp.h"
+#include "lwip/udp.h"
+#include "esp_wifi.h"
+
+#define MDNS_FLAGS_AUTHORITATIVE    0x8400
+
+#define MDNS_NAME_REF               0xC000
+
+#define MDNS_TYPE_AAAA              0x001C
+#define MDNS_TYPE_A                 0x0001
+#define MDNS_TYPE_PTR               0x000C
+#define MDNS_TYPE_SRV               0x0021
+#define MDNS_TYPE_TXT               0x0010
+#define MDNS_TYPE_NSEC              0x002F
+#define MDNS_TYPE_ANY               0x00FF
+
+#define MDNS_CLASS_IN               0x0001
+#define MDNS_CLASS_IN_FLUSH_CACHE   0x8001
+
+#define MDNS_ANSWER_ALL             0x3F
+#define MDNS_ANSWER_PTR             0x08
+#define MDNS_ANSWER_TXT             0x04
+#define MDNS_ANSWER_SRV             0x02
+#define MDNS_ANSWER_A               0x01
+#define MDNS_ANSWER_AAAA            0x10
+#define MDNS_ANSWER_NSEC            0x20
+
+#define MDNS_SERVICE_PORT           5353                    // UDP port that the server runs on
+#define MDNS_SERVICE_STACK_DEPTH    4096                    // Stack size for the service thread
+#define MDNS_PACKET_QUEUE_LEN       16                      // Maximum packets that can be queued for parsing
+#define MDNS_TXT_MAX_LEN            1024                    // Maximum string length of text data in TXT record
+#define MDNS_NAME_MAX_LEN           64                      // Maximum string length of hostname, instance, service and proto
+#define MDNS_NAME_BUF_LEN           (MDNS_NAME_MAX_LEN+1)   // Maximum char buffer size to hold hostname, instance, service or proto
+#define MDNS_MAX_PACKET_SIZE        1460                    // Maximum size of mDNS  outgoing packet
+
+#define MDNS_ANSWER_PTR_TTL         4500
+#define MDNS_ANSWER_TXT_TTL         4500
+#define MDNS_ANSWER_SRV_TTL         120
+#define MDNS_ANSWER_A_TTL           120
+#define MDNS_ANSWER_AAAA_TTL        120
+
+#define MDNS_HEAD_LEN               12
+#define MDNS_HEAD_ID_OFFSET         0
+#define MDNS_HEAD_FLAGS_OFFSET      2
+#define MDNS_HEAD_QUESTIONS_OFFSET  4
+#define MDNS_HEAD_ANSWERS_OFFSET    6
+#define MDNS_HEAD_SERVERS_OFFSET    8
+#define MDNS_HEAD_ADDITIONAL_OFFSET 10
+
+#define MDNS_TYPE_OFFSET            0
+#define MDNS_CLASS_OFFSET           2
+#define MDNS_TTL_OFFSET             4
+#define MDNS_LEN_OFFSET             8
+#define MDNS_DATA_OFFSET            10
+
+#define MDNS_SRV_PRIORITY_OFFSET    0
+#define MDNS_SRV_WEIGHT_OFFSET      2
+#define MDNS_SRV_PORT_OFFSET        4
+#define MDNS_SRV_FQDN_OFFSET        6
+
+typedef struct {
+    char host[MDNS_NAME_BUF_LEN];
+    char service[MDNS_NAME_BUF_LEN];
+    char proto[MDNS_NAME_BUF_LEN];
+    char domain[MDNS_NAME_BUF_LEN];
+    uint8_t parts;
+    uint8_t sub;
+} mdns_name_t;
+
+typedef struct {
+    char host[MDNS_NAME_BUF_LEN];
+    char instance[MDNS_NAME_BUF_LEN];
+    char txt[MDNS_TXT_MAX_LEN];
+    uint16_t priority;
+    uint16_t weight;
+    uint16_t port;
+    uint32_t addr;
+    uint8_t addrv6[16];
+    uint8_t ptr;
+} mdns_result_temp_t;
+
+typedef struct {
+    const char * host;
+    const char * sub;
+    const char * service;
+    const char * proto;
+    const char * domain;
+    uint8_t parts;
+    uint8_t done;
+} mdns_string_t;
+
+typedef struct mdns_service_s {
+    const char * instance;
+    const char * service;
+    const char * proto;
+    uint16_t priority;
+    uint16_t weight;
+    uint16_t port;
+    uint8_t txt_num_items;
+    const char ** txt;
+} mdns_service_t;
+
+typedef struct mdns_srv_item_s {
+    mdns_service_t * service;
+    struct mdns_srv_item_s * next;
+} mdns_srv_item_t;
+
+typedef struct mdns_answer_item_s {
+    mdns_service_t * service;
+    uint8_t answer;
+    struct mdns_answer_item_s * next;
+} mdns_answer_item_t;
+
+struct mdns_server_s {
+    tcpip_adapter_if_t tcpip_if;
+    struct udp_pcb * pcb;
+    const char * hostname;
+    const char * instance;
+    mdns_srv_item_t * services;
+    xSemaphoreHandle lock;
+    xQueueHandle queue;
+    struct {
+        char host[MDNS_NAME_BUF_LEN];
+        char service[MDNS_NAME_BUF_LEN];
+        char proto[MDNS_NAME_BUF_LEN];
+        bool running;
+        xSemaphoreHandle lock;
+        mdns_result_t * results;
+    } search;
+};
+
+#define MDNS_MUTEX_LOCK()       xSemaphoreTake(server->lock, portMAX_DELAY)
+#define MDNS_MUTEX_UNLOCK()     xSemaphoreGive(server->lock)
+
+#define MDNS_SEARCH_LOCK()      xSemaphoreTake(server->search.lock, portMAX_DELAY)
+#define MDNS_SEARCH_UNLOCK()    xSemaphoreGive(server->search.lock)
+
+static const char * MDNS_DEFAULT_DOMAIN = "local";
+static const char * MDNS_SUB_STR = "_sub";
+
+static mdns_server_t * _mdns_servers[TCPIP_ADAPTER_IF_MAX] = {0,0,0};
+static TaskHandle_t _mdns_service_task_handle = NULL;
+static QueueSetHandle_t _mdns_queue_set = NULL;
+
+static xSemaphoreHandle _mdns_service_semaphore = NULL;
+#define MDNS_SERVICE_LOCK()     xSemaphoreTake(_mdns_service_semaphore, portMAX_DELAY)
+#define MDNS_SERVICE_UNLOCK()   xSemaphoreGive(_mdns_service_semaphore)
+
+/*
+ * MDNS Server Networking
+ * */
+
+/**
+ * @brief  the receive callback of the raw udp api. Packets are received here
+ *
+ */
+static void _mdns_server_recv(void *arg, struct udp_pcb *upcb, struct pbuf *pb, const ip_addr_t *addr, uint16_t port)
+{
+    while(pb != NULL) {
+        struct pbuf * this_pb = pb;
+        pb = pb->next;
+        this_pb->next = NULL;
+        mdns_server_t * server = (mdns_server_t *)arg;
+        if (!server || !server->queue || xQueueSend(server->queue, &this_pb, (portTickType)0) != pdPASS) {
+            pbuf_free(this_pb);
+        }
+    }
+}
+
+/**
+ * @brief  init the network of MDNS server
+ *
+ * @param  server       The server
+ *
+ * @return
+ *      - ESP_OK on success
+ *      - ESP_ERR_INVALID_STATE on igmp/bind error
+ *      - ESP_ERR_NO_MEM on memory error
+ */
+esp_err_t _mdns_server_init(mdns_server_t * server)
+{
+    esp_err_t err = ESP_OK;
+
+    tcpip_adapter_ip_info_t if_ip_info;
+    err = tcpip_adapter_get_ip_info(server->tcpip_if, &if_ip_info);
+    if (err) {
+        return err;
+    }
+
+    ip_addr_t laddr;
+    IP_ADDR4(&laddr, 224, 0, 0, 251);
+
+    ip_addr_t multicast_if_addr = IPADDR4_INIT(if_ip_info.ip.addr);
+
+    if (igmp_joingroup((const struct ip4_addr *)&multicast_if_addr.u_addr.ip4, (const struct ip4_addr *)&laddr.u_addr.ip4)) {
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    struct udp_pcb * pcb = udp_new();
+    if (!pcb) {
+        return ESP_ERR_NO_MEM;
+    }
+
+    pcb->remote_port = MDNS_SERVICE_PORT;
+
+    if (udp_bind(pcb, &multicast_if_addr, pcb->remote_port) != 0) {
+        udp_remove(pcb);
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    pcb->mcast_ttl = 1;
+    ip_addr_copy(pcb->multicast_ip, multicast_if_addr);
+    ip_addr_copy(pcb->remote_ip, laddr);
+
+    server->pcb = pcb;
+    udp_recv(pcb, &_mdns_server_recv, server);
+    return err;
+}
+
+/**
+ * @brief  stop the network of MDNS server
+ *
+ * @param  server       The server
+ *
+ * @return ESP_OK
+ */
+esp_err_t _mdns_server_deinit(mdns_server_t * server)
+{
+    if (server->pcb) {
+        udp_recv(server->pcb, NULL, NULL);
+        udp_disconnect(server->pcb);
+        udp_remove(server->pcb);
+        server->pcb = NULL;
+    }
+    return ESP_OK;
+}
+
+/**
+ * @brief  send packet over UDP
+ *
+ * @param  server       The server
+ * @param  data         byte array containing the packet data
+ * @param  len          length of the packet data
+ *
+ * @return length of sent packet or 0 on error
+ */
+static size_t _mdns_server_write(mdns_server_t * server, uint8_t * data, size_t len)
+{
+    struct pbuf* pbt = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM);
+    if (pbt != NULL) {
+        uint8_t* dst = (uint8_t *)pbt->payload;
+        memcpy(dst, data, len);
+        err_t err = udp_sendto(server->pcb, pbt, &(server->pcb->remote_ip), server->pcb->remote_port);
+        pbuf_free(pbt);
+        if (err) {
+            return 0;
+        }
+        return len;
+    }
+    return 0;
+}
+
+/*
+ * MDNS Servers
+ * */
+
+static void _mdns_parse_packet(mdns_server_t * server, const uint8_t * data, size_t len);
+
+/**
+ * @brief  the main MDNS service task. Packets are received and parsed here
+ */
+static void _mdns_service_task(void *pvParameters)
+{
+    uint8_t i;
+    struct pbuf * pb;
+    QueueSetMemberHandle_t queue;
+
+    for(;;) {
+        queue = xQueueSelectFromSet(_mdns_queue_set, portMAX_DELAY);
+        if (queue && xQueueReceive(queue, &pb, 0) == pdTRUE) {
+            for(i=0; i<TCPIP_ADAPTER_IF_MAX; i++) {
+                mdns_server_t * server = _mdns_servers[i];
+                if (server && server->queue == queue) {
+                    MDNS_MUTEX_LOCK();
+                    _mdns_parse_packet(server, (uint8_t*)pb->payload, pb->len);
+                    MDNS_MUTEX_UNLOCK();
+                    break;
+                }
+            }
+            pbuf_free(pb);
+        }
+    }
+}
+
+/**
+ * @brief  get the server assigned to particular interface
+ *
+ * @param  tcpip_if     The interface
+ *
+ * @return reference to the server from the server list or NULL if not found
+ */
+static mdns_server_t * _mdns_server_get(tcpip_adapter_if_t tcpip_if)
+{
+    if (tcpip_if < TCPIP_ADAPTER_IF_MAX) {
+        return _mdns_servers[tcpip_if];
+    }
+    return NULL;
+}
+
+/**
+ * @brief  add server to the server list. Start the service thread if not running
+ *
+ * @param  server       The server to add
+ *
+ * @return
+ *      - ESP_OK on success
+ *      - ESP_FAIL on error
+ *      - ESP_ERR_* on network error
+ */
+static esp_err_t _mdns_server_add(mdns_server_t * server)
+{
+    if (!_mdns_service_semaphore) {
+        _mdns_service_semaphore = xSemaphoreCreateMutex();
+        if (!_mdns_service_semaphore) {
+            return ESP_FAIL;
+        }
+    }
+    MDNS_SERVICE_LOCK();
+    if (!_mdns_service_task_handle) {
+        _mdns_queue_set = xQueueCreateSet(TCPIP_ADAPTER_IF_MAX * MDNS_PACKET_QUEUE_LEN);
+        if (!_mdns_queue_set) {
+            MDNS_SERVICE_UNLOCK();
+            return ESP_FAIL;
+        }
+        xTaskCreatePinnedToCore(_mdns_service_task, "mdns", MDNS_SERVICE_STACK_DEPTH, NULL, 1, &_mdns_service_task_handle, 0);
+        if (!_mdns_service_task_handle) {
+            vQueueDelete(_mdns_queue_set);
+            _mdns_queue_set = NULL;
+            MDNS_SERVICE_UNLOCK();
+            return ESP_FAIL;
+        }
+    }
+    MDNS_SERVICE_UNLOCK();
+
+    if (xQueueAddToSet(server->queue, _mdns_queue_set) != pdPASS) {
+        return ESP_FAIL;
+    }
+
+    //start UDP
+    esp_err_t err = _mdns_server_init(server);
+    if (err) {
+        return err;
+    }
+
+    _mdns_servers[server->tcpip_if] = server;
+
+    return ESP_OK;
+}
+
+/**
+ * @brief  remove server from server list. Stop the service thread in no more servers are running
+ *
+ * @param  server       The server to remove
+ *
+ * @return
+ *      - ESP_OK on success
+ *      - ESP_FAIL on error
+ */
+static esp_err_t _mdns_server_remove(mdns_server_t * server)
+{
+    //stop UDP
+    _mdns_server_deinit(server);
+
+    _mdns_servers[server->tcpip_if] = NULL;
+
+    if (xQueueRemoveFromSet(server->queue, _mdns_queue_set) != pdPASS) {
+        return ESP_FAIL;
+    }
+
+    uint8_t i;
+    for(i=0; i<TCPIP_ADAPTER_IF_MAX; i++) {
+        if (_mdns_servers[i]) {
+            break;
+        }
+    }
+    if (i == TCPIP_ADAPTER_IF_MAX) {
+        //none of the servers are running
+        MDNS_SERVICE_LOCK();
+        if (_mdns_service_task_handle) {
+            vTaskDelete(_mdns_service_task_handle);
+            vQueueDelete(_mdns_queue_set);
+            _mdns_queue_set = NULL;
+            _mdns_service_task_handle = NULL;
+        }
+        MDNS_SERVICE_UNLOCK();
+    }
+
+    return ESP_OK;
+}
+
+
+/*
+ * PARSING
+ * */
+
+/**
+ * @brief  queues service for answer (if service is already added, append the new answer type)
+ *
+ * @param  answers      Linked list of answers
+ * @param  service      Service to add to the answers
+ * @param  type         Type of the answer
+ *
+ * @return the new linked list of answers
+ */
+static mdns_answer_item_t * _mdns_add_answer(mdns_answer_item_t * answers, mdns_service_t * service, uint8_t type)
+{
+    //see if we already have the service queued
+    mdns_answer_item_t * a = answers;
+    while(a) {
+        if (a->service == service) {
+            //just add the new answer type to it
+            a->answer |= type;
+            return answers;
+        }
+        a = a->next;
+    }
+    //prepend the q with this new answer
+    a = (mdns_answer_item_t *)malloc(sizeof(mdns_answer_item_t));
+    if (!a) {
+        return answers;//fail!
+    }
+    a->service = service;
+    a->answer = type;
+    a->next = answers;
+    answers = a;
+    return a;
+}
+
+/**
+ * @brief  reads MDNS FQDN into mdns_name_t structure
+ *         FQDN is in format: [hostname.|[instance.]_service._proto.]local.
+ *
+ * @param  packet       MDNS packet
+ * @param  start        Starting point of FQDN
+ * @param  name         mdns_name_t structure to populate
+ * @param  buf          temporary char buffer
+ *
+ * @return the address after the parsed FQDN in the packet or NULL on error
+ */
+static const uint8_t * _mdns_read_fqdn(const uint8_t * packet, const uint8_t * start, mdns_name_t * name, char * buf)
+{
+    size_t index = 0;
+    while(start[index]) {
+        if (name->parts == 4) {
+            return NULL;
+        }
+        uint8_t len = start[index++];
+        if ((len & 0xC0) == 0) {
+            if (len > 64) {
+                //length can not be more than 64
+                return NULL;
+            }
+            uint8_t i;
+            for(i=0; i<len; i++) {
+                buf[i] = start[index++];
+            }
+            buf[len] = '\0';
+            if (name->parts == 1 && buf[0] != '_'
+                    && (strcmp(buf, MDNS_DEFAULT_DOMAIN) != 0)
+                    && (strcmp(buf, "ip6") != 0)
+                    && (strcmp(buf, "in-addr") != 0)) {
+                sprintf((char*)name, "%s.%s", name->host, buf);
+            } else if (strcmp(buf, MDNS_SUB_STR) == 0) {
+                name->sub = 1;
+            } else {
+                memcpy((uint8_t*)name + (name->parts++ * (MDNS_NAME_BUF_LEN)), buf, len+1);
+            }
+        } else {
+            size_t address = (((uint16_t)len & 0x3F) << 8) | start[index++];
+            if ((packet + address) > start) {
+                //reference address can not be after where we are
+                return NULL;
+            }
+            if (_mdns_read_fqdn(packet, packet + address, name, buf)) {
+                return start + index;
+            }
+            return NULL;
+        }
+    }
+    return start + index + 1;
+}
+
+/**
+ * @brief  reads and formats MDNS FQDN into mdns_name_t structure
+ *
+ * @param  packet       MDNS packet
+ * @param  start        Starting point of FQDN
+ * @param  name         mdns_name_t structure to populate
+ *
+ * @return the address after the parsed FQDN in the packet or NULL on error
+ */
+static const uint8_t * _mdns_parse_fqdn(const uint8_t * packet, const uint8_t * start, mdns_name_t * name)
+{
+    name->parts = 0;
+    name->sub = 0;
+    name->host[0] = 0;
+    name->service[0] = 0;
+    name->proto[0] = 0;
+    name->domain[0] = 0;
+
+    static char buf[MDNS_NAME_BUF_LEN];
+
+    const uint8_t * next_data = (uint8_t*)_mdns_read_fqdn(packet, start, name, buf);
+    if (!next_data || name->parts < 2) {
+        return 0;
+    }
+    if (name->parts == 3) {
+        memmove((uint8_t*)name + (MDNS_NAME_BUF_LEN), (uint8_t*)name, 3*(MDNS_NAME_BUF_LEN));
+        name->host[0] = 0;
+    } else if (name->parts == 2) {
+        memmove((uint8_t*)(name->domain), (uint8_t*)(name->service), (MDNS_NAME_BUF_LEN));
+        name->service[0] = 0;
+        name->proto[0] = 0;
+    }
+    if (strcmp(name->domain, MDNS_DEFAULT_DOMAIN) == 0 || strcmp(name->domain, "arpa") == 0) {
+        return next_data;
+    }
+    return 0;
+}
+
+/*
+ * Packet construction
+ * */
+
+/**
+ * @brief  sets uint16_t value in a packet
+ *
+ * @param  packet       MDNS packet
+ * @param  index        offset of uint16_t value
+ * @param  value        the value to set
+ */
+static inline void _mdns_set_u16(uint8_t * packet, uint16_t index, uint16_t value)
+{
+    if ((index + 1) >= MDNS_MAX_PACKET_SIZE) {
+        return;
+    }
+    packet[index] = (value >> 8) & 0xFF;
+    packet[index+1] = value & 0xFF;
+}
+
+/**
+ * @brief  appends byte in a packet, incrementing the index
+ *
+ * @param  packet       MDNS packet
+ * @param  index        offset in the packet
+ * @param  value        the value to set
+ *
+ * @return length of added data: 0 on error or 1 on success
+ */
+static inline uint8_t _mdns_append_u8(uint8_t * packet, uint16_t * index, uint8_t value)
+{
+    if (*index >= MDNS_MAX_PACKET_SIZE) {
+        return 0;
+    }
+    packet[*index] = value;
+    *index += 1;
+    return 1;
+}
+
+/**
+ * @brief  appends uint16_t in a packet, incrementing the index
+ *
+ * @param  packet       MDNS packet
+ * @param  index        offset in the packet
+ * @param  value        the value to set
+ *
+ * @return length of added data: 0 on error or 2 on success
+ */
+static inline uint8_t _mdns_append_u16(uint8_t * packet, uint16_t * index, uint16_t value)
+{
+    if ((*index + 1) >= MDNS_MAX_PACKET_SIZE) {
+        return 0;
+    }
+    _mdns_append_u8(packet, index, (value >> 8) & 0xFF);
+    _mdns_append_u8(packet, index, value & 0xFF);
+    return 2;
+}
+
+/**
+ * @brief  appends uint32_t in a packet, incrementing the index
+ *
+ * @param  packet       MDNS packet
+ * @param  index        offset in the packet
+ * @param  value        the value to set
+ *
+ * @return length of added data: 0 on error or 4 on success
+ */
+static inline uint8_t _mdns_append_u32(uint8_t * packet, uint16_t * index, uint32_t value)
+{
+    if ((*index + 3) >= MDNS_MAX_PACKET_SIZE) {
+        return 0;
+    }
+    _mdns_append_u8(packet, index, (value >> 24) & 0xFF);
+    _mdns_append_u8(packet, index, (value >> 16) & 0xFF);
+    _mdns_append_u8(packet, index, (value >> 8) & 0xFF);
+    _mdns_append_u8(packet, index, value & 0xFF);
+    return 4;
+}
+
+/**
+ * @brief  appends answer type, class, ttl and data length to a packet, incrementing the index
+ *
+ * @param  packet       MDNS packet
+ * @param  index        offset in the packet
+ * @param  type         answer type
+ * @param  ttl          answer ttl
+ *
+ * @return length of added data: 0 on error or 10 on success
+ */
+static inline uint8_t _mdns_append_type(uint8_t * packet, uint16_t * index, uint8_t type, uint32_t ttl)
+{
+    if ((*index + 10) >= MDNS_MAX_PACKET_SIZE) {
+        return 0;
+    }
+    if (type == MDNS_ANSWER_PTR) {
+        _mdns_append_u16(packet, index, MDNS_TYPE_PTR);
+        _mdns_append_u16(packet, index, MDNS_CLASS_IN);
+    } else if (type == MDNS_ANSWER_TXT) {
+        _mdns_append_u16(packet, index, MDNS_TYPE_TXT);
+        _mdns_append_u16(packet, index, MDNS_CLASS_IN_FLUSH_CACHE);
+    } else if (type == MDNS_ANSWER_SRV) {
+        _mdns_append_u16(packet, index, MDNS_TYPE_SRV);
+        _mdns_append_u16(packet, index, MDNS_CLASS_IN_FLUSH_CACHE);
+    } else if (type == MDNS_ANSWER_A) {
+        _mdns_append_u16(packet, index, MDNS_TYPE_A);
+        _mdns_append_u16(packet, index, MDNS_CLASS_IN_FLUSH_CACHE);
+    } else if (type == MDNS_ANSWER_AAAA) {
+        _mdns_append_u16(packet, index, MDNS_TYPE_AAAA);
+        _mdns_append_u16(packet, index, MDNS_CLASS_IN_FLUSH_CACHE);
+    } else {
+        return 0;
+    }
+    _mdns_append_u32(packet, index, ttl);
+    _mdns_append_u16(packet, index, 0);
+    return 10;
+}
+
+/**
+ * @brief  appends single string to a packet, incrementing the index
+ *
+ * @param  packet       MDNS packet
+ * @param  index        offset in the packet
+ * @param  string       the string to append
+ *
+ * @return length of added data: 0 on error or length of the string + 1 on success
+ */
+static inline uint8_t _mdns_append_string(uint8_t * packet, uint16_t * index, const char * string)
+{
+    uint8_t len = strlen(string);
+    if ((*index + len + 1) >= MDNS_MAX_PACKET_SIZE) {
+        return 0;
+    }
+    _mdns_append_u8(packet, index, len);
+    memcpy(packet + *index, string, len);
+    *index += len;
+    return len + 1;
+}
+
+/**
+ * @brief  appends FQDN to a packet, incrementing the index
+ *
+ * @param  packet       MDNS packet
+ * @param  index        offset in the packet
+ * @param  strings      string array containing the parts of the FQDN
+ * @param  count        number of strings in the array
+ *
+ * @return length of added data: 0 on error or length on success
+ */
+static uint16_t _mdns_append_fqdn(uint8_t * packet, uint16_t * index, const char * strings[], uint8_t count)
+{
+    if (!count) {
+        return _mdns_append_u8(packet, index, 0);
+    }
+    mdns_name_t name;
+    static char buf[MDNS_NAME_BUF_LEN];
+    uint8_t len = strlen(strings[0]);
+    uint8_t * len_location = (uint8_t *)memchr(packet, (char)len, *index);
+    while(len_location) {
+        if (memcmp(len_location+1, strings[0], len)) { //not continuing with our string
+search_next:
+            len_location = (uint8_t *)memchr(len_location+1, (char)len, *index - (len_location+1 - packet));
+            continue;
+        }
+        //read string into name and compare
+        name.parts = 0;
+        name.sub = 0;
+        name.host[0] = 0;
+        name.service[0] = 0;
+        name.proto[0] = 0;
+        name.domain[0] = 0;
+        const uint8_t * content = _mdns_read_fqdn(packet, len_location, &name, buf);
+        if (!content) {
+            return 0;
+        }
+        if (name.parts == count) {
+            uint8_t i;
+            for(i=0; i<count; i++) {
+                if (strcmp(strings[i], (const char *)&name + (i * (MDNS_NAME_BUF_LEN)))) {
+                    //not our string
+                    goto search_next;
+                }
+            }
+            //we actually have found the string
+            break;
+        } else {
+            goto search_next;
+        }
+    }
+    if (!len_location) {
+        uint8_t written = _mdns_append_string(packet, index, strings[0]);
+        if (!written) {
+            return 0;
+        }
+        return written + _mdns_append_fqdn(packet, index, &strings[1], count - 1);
+    }
+
+    uint16_t offset = len_location - packet;
+    offset |= MDNS_NAME_REF;
+    return _mdns_append_u16(packet, index, offset);
+}
+
+/**
+ * @brief  appends PTR record for service to a packet, incrementing the index
+ *
+ * @param  packet       MDNS packet
+ * @param  index        offset in the packet
+ * @param  server       the server that is hosting the service
+ * @param  service      the service to add record for
+ *
+ * @return length of added data: 0 on error or length on success
+ */
+static uint16_t _mdns_append_ptr_record(uint8_t * packet, uint16_t * index, mdns_server_t * server, mdns_service_t * service)
+{
+    const char * str[4];
+    uint16_t record_length = 0;
+    uint8_t part_length;
+
+    str[0] = (service->instance)?service->instance
+            :(server->instance)?server->instance
+            :server->hostname;
+    str[1] = service->service;
+    str[2] = service->proto;
+    str[3] = MDNS_DEFAULT_DOMAIN;
+
+    part_length = _mdns_append_fqdn(packet, index, str + 1, 3);
+    if (!part_length) {
+        return 0;
+    }
+    record_length += part_length;
+
+    part_length = _mdns_append_type(packet, index, MDNS_ANSWER_PTR, MDNS_ANSWER_PTR_TTL);
+    if (!part_length) {
+        return 0;
+    }
+    record_length += part_length;
+
+    uint16_t data_len_location = *index - 2;
+    part_length = _mdns_append_fqdn(packet, index, str, 4);
+    if (!part_length) {
+        return 0;
+    }
+    _mdns_set_u16(packet, data_len_location, part_length);
+    record_length += part_length;
+    return record_length;
+}
+
+/**
+ * @brief  appends TXT record for service to a packet, incrementing the index
+ *
+ * @param  packet       MDNS packet
+ * @param  index        offset in the packet
+ * @param  server       the server that is hosting the service
+ * @param  service      the service to add record for
+ *
+ * @return length of added data: 0 on error or length on success
+ */
+static uint16_t _mdns_append_txt_record(uint8_t * packet, uint16_t * index, mdns_server_t * server, mdns_service_t * service)
+{
+    const char * str[4];
+    uint16_t record_length = 0;
+    uint8_t part_length;
+
+    str[0] = (service->instance)?service->instance
+            :(server->instance)?server->instance
+            :server->hostname;
+    str[1] = service->service;
+    str[2] = service->proto;
+    str[3] = MDNS_DEFAULT_DOMAIN;
+
+    part_length = _mdns_append_fqdn(packet, index, str, 4);
+    if (!part_length) {
+        return 0;
+    }
+    record_length += part_length;
+
+    part_length = _mdns_append_type(packet, index, MDNS_ANSWER_TXT, MDNS_ANSWER_TXT_TTL);
+    if (!part_length) {
+        return 0;
+    }
+    record_length += part_length;
+
+    uint16_t data_len_location = *index - 2;
+    uint16_t data_len = 0;
+    if (service->txt_num_items) {
+        uint8_t len = service->txt_num_items;
+        const char ** txt = service->txt;
+        uint8_t i, l;
+        for(i=0; i<len; i++) {
+            l = _mdns_append_string(packet, index, txt[i]);
+            if (!l) {
+               return 0;
+            }
+            data_len += l;
+        }
+    }
+    _mdns_set_u16(packet, data_len_location, data_len);
+    record_length += data_len;
+    return record_length;
+}
+
+/**
+ * @brief  appends SRV record for service to a packet, incrementing the index
+ *
+ * @param  packet       MDNS packet
+ * @param  index        offset in the packet
+ * @param  server       the server that is hosting the service
+ * @param  service      the service to add record for
+ *
+ * @return length of added data: 0 on error or length on success
+ */
+static uint16_t _mdns_append_srv_record(uint8_t * packet, uint16_t * index, mdns_server_t * server, mdns_service_t * service)
+{
+    const char * str[4];
+    uint16_t record_length = 0;
+    uint8_t part_length;
+
+    str[0] = (service->instance)?service->instance
+            :(server->instance)?server->instance
+            :server->hostname;
+    str[1] = service->service;
+    str[2] = service->proto;
+    str[3] = MDNS_DEFAULT_DOMAIN;
+
+    part_length = _mdns_append_fqdn(packet, index, str, 4);
+    if (!part_length) {
+        return 0;
+    }
+    record_length += part_length;
+
+    part_length = _mdns_append_type(packet, index, MDNS_ANSWER_SRV, MDNS_ANSWER_SRV_TTL);
+    if (!part_length) {
+        return 0;
+    }
+    record_length += part_length;
+
+    uint16_t data_len_location = *index - 2;
+
+    part_length = 0;
+    part_length += _mdns_append_u16(packet, index, service->priority);
+    part_length += _mdns_append_u16(packet, index, service->weight);
+    part_length += _mdns_append_u16(packet, index, service->port);
+    if (part_length != 6) {
+        return 0;
+    }
+
+    str[0] = server->hostname;
+    str[1] = MDNS_DEFAULT_DOMAIN;
+    part_length = _mdns_append_fqdn(packet, index, str, 2);
+    if (!part_length) {
+        return 0;
+    }
+    _mdns_set_u16(packet, data_len_location, part_length + 6);
+
+    record_length += part_length + 6;
+    return record_length;
+}
+
+/**
+ * @brief  appends A record to a packet, incrementing the index
+ *
+ * @param  packet       MDNS packet
+ * @param  index        offset in the packet
+ * @param  server       the server
+ * @param  ip           the IP address to add
+ *
+ * @return length of added data: 0 on error or length on success
+ */
+static uint16_t _mdns_append_a_record(uint8_t * packet, uint16_t * index, mdns_server_t * server, uint32_t ip)
+{
+    const char * str[2];
+    uint16_t record_length = 0;
+    uint8_t part_length;
+
+    str[0] = server->hostname;
+    str[1] = MDNS_DEFAULT_DOMAIN;
+
+    part_length = _mdns_append_fqdn(packet, index, str, 2);
+    if (!part_length) {
+        return 0;
+    }
+    record_length += part_length;
+
+    part_length = _mdns_append_type(packet, index, MDNS_ANSWER_A, MDNS_ANSWER_A_TTL);
+    if (!part_length) {
+        return 0;
+    }
+    record_length += part_length;
+
+    uint16_t data_len_location = *index - 2;
+
+    if ((*index + 3) >= MDNS_MAX_PACKET_SIZE) {
+        return 0;
+    }
+    _mdns_append_u8(packet, index, ip & 0xFF);
+    _mdns_append_u8(packet, index, (ip >> 8) & 0xFF);
+    _mdns_append_u8(packet, index, (ip >> 16) & 0xFF);
+    _mdns_append_u8(packet, index, (ip >> 24) & 0xFF);
+    _mdns_set_u16(packet, data_len_location, 4);
+
+    record_length += 4;
+    return record_length;
+}
+
+/**
+ * @brief  appends AAAA record to a packet, incrementing the index
+ *
+ * @param  packet       MDNS packet
+ * @param  index        offset in the packet
+ * @param  server       the server
+ * @param  ipv6         the IPv6 address to add
+ *
+ * @return length of added data: 0 on error or length on success
+ */
+static uint16_t _mdns_append_aaaa_record(uint8_t * packet, uint16_t * index, mdns_server_t * server, uint8_t * ipv6)
+{
+    const char * str[2];
+    uint16_t record_length = 0;
+    uint8_t part_length;
+
+    str[0] = server->hostname;
+    str[1] = MDNS_DEFAULT_DOMAIN;
+
+    part_length = _mdns_append_fqdn(packet, index, str, 2);
+    if (!part_length) {
+        return 0;
+    }
+    record_length += part_length;
+
+    part_length = _mdns_append_type(packet, index, MDNS_ANSWER_AAAA, MDNS_ANSWER_AAAA_TTL);
+    if (!part_length) {
+        return 0;
+    }
+    record_length += part_length;
+
+    uint16_t data_len_location = *index - 2;
+
+    if ((*index + 15) >= MDNS_MAX_PACKET_SIZE) {
+        return 0;
+    }
+    
+    part_length = sizeof(ip6_addr_t);
+    memcpy(packet + *index, ipv6, part_length);
+    *index += part_length;
+    _mdns_set_u16(packet, data_len_location, part_length);
+    record_length += part_length;
+    return record_length;
+}
+
+/**
+ * @brief  sends all collected answers
+ *
+ * @param  server       the server
+ * @param  answers      linked list of answers
+ */
+static void _mdns_send_answers(mdns_server_t * server, mdns_answer_item_t * answers)
+{
+    bool send_ip = false;
+    static uint8_t packet[MDNS_MAX_PACKET_SIZE];
+    uint16_t index = MDNS_HEAD_LEN;
+    uint8_t answer_count = 0;
+
+    memset(packet, 0, MDNS_HEAD_LEN);
+
+    _mdns_set_u16(packet, MDNS_HEAD_FLAGS_OFFSET, MDNS_FLAGS_AUTHORITATIVE);
+
+    while(answers) {
+        if (answers->answer & MDNS_ANSWER_A) {
+            answers->answer &= ~MDNS_ANSWER_A;
+            send_ip = true;
+        }
+        if (answers->service) {
+
+            if (answers->answer & MDNS_ANSWER_PTR) {
+                if (!_mdns_append_ptr_record(packet, &index, server, answers->service)) {
+                    return;
+                }
+                answer_count += 1;
+            }
+
+            if (answers->answer & MDNS_ANSWER_TXT) {
+                if (!_mdns_append_txt_record(packet, &index, server, answers->service)) {
+                    return;
+                }
+                answer_count += 1;
+            }
+
+            if (answers->answer & MDNS_ANSWER_SRV) {
+                if (!_mdns_append_srv_record(packet, &index, server, answers->service)) {
+                    return;
+                }
+                answer_count += 1;
+            }
+        }
+        mdns_answer_item_t * a = answers;
+        answers = answers->next;
+        free(a);
+    }
+    if (send_ip) {
+        tcpip_adapter_ip_info_t if_ip_info;
+        tcpip_adapter_get_ip_info(server->tcpip_if, &if_ip_info);
+
+        if (!_mdns_append_a_record(packet, &index, server, if_ip_info.ip.addr)) {
+            return;
+        }
+        answer_count += 1;
+
+        //add ipv6 if available
+        struct ip6_addr if_ip6;
+        if (!tcpip_adapter_get_ip6_linklocal(server->tcpip_if, &if_ip6)) {
+            uint8_t * v6addr = (uint8_t*)if_ip6.addr;
+            //check if not 0
+            int i;
+            for(i=0;i<sizeof(ip6_addr_t);i++) {
+                if (v6addr[i]) {
+                    break;
+                }
+            }
+            if (i<sizeof(ip6_addr_t)) {
+                if (!_mdns_append_aaaa_record(packet, &index, server, v6addr)) {
+                    return;
+                }
+                answer_count += 1;
+            }
+        }
+    }
+
+    _mdns_set_u16(packet, MDNS_HEAD_ANSWERS_OFFSET, answer_count);
+    _mdns_server_write(server, packet, index);
+}
+
+/**
+ * @brief  appends search result from query
+ *
+ * @param  server       the server
+ * @param  r            the temporary result to copy
+ */
+static void _mdns_add_result(mdns_server_t * server, mdns_result_temp_t * r)
+{
+    mdns_result_t * n = (mdns_result_t *)malloc(sizeof(mdns_result_t));
+    if (!n) {
+        return;
+    }
+    n->priority = r->priority;
+    n->weight = r->weight;
+    n->port = r->port;
+    n->addr.addr = r->addr;
+
+    size_t hlen = strlen(r->host);
+    if (hlen) {
+        n->host = strdup(r->host);
+        if (!n->host) {
+            free(n);
+            return;
+        }
+    } else {
+        n->host = NULL;
+    }
+
+    size_t ilen = strlen(r->instance);
+    if (ilen) {
+        n->instance = strdup(r->instance);
+        if (!n->instance) {
+            free((char *)n->host);
+            free(n);
+            return;
+        }
+    } else {
+        n->instance = NULL;
+    }
+
+    size_t tlen = strlen(r->txt);
+    if (tlen) {
+        n->txt = strdup(r->txt);
+        if (!n->txt) {
+            free((char *)n->host);
+            free((char *)n->instance);
+            free(n);
+            return;
+        }
+    } else {
+        n->txt = NULL;
+    }
+
+    memcpy((uint8_t *)n->addrv6.addr, r->addrv6, sizeof(ip6_addr_t));
+
+    mdns_result_t * o = server->search.results;
+    server->search.results = n;
+    n->next = o;
+}
+
+/**
+ * @brief  finds service from given service type
+ * @param  server       the server
+ * @param  service      service type to match
+ * @param  proto        proto to match
+ *
+ * @return the service item if found or NULL on error
+ */
+static mdns_srv_item_t * _mdns_get_service_item(mdns_server_t * server, const char * service, const char * proto)
+{
+    mdns_srv_item_t * s = server->services;
+    while(s) {
+        if (!strcmp(s->service->service, service) && !strcmp(s->service->proto, proto)) {
+            return s;
+        }
+        s = s->next;
+    }
+    return NULL;
+}
+
+/**
+ * @brief  creates/allocates new service
+ * @param  service       service type
+ * @param  proto         service proto
+ * @param  port          service port
+ *
+ * @return pointer to the service or NULL on error
+ */
+static mdns_service_t * _mdns_create_service(const char * service, const char * proto, uint16_t port)
+{
+    mdns_service_t * s = (mdns_service_t *)malloc(sizeof(mdns_service_t));
+    if (!s) {
+        return NULL;
+    }
+
+    s->priority = 0;
+    s->weight = 0;
+    s->txt_num_items = 0;
+    s->instance = NULL;
+    s->txt = NULL;
+    s->port = port;
+
+    s->service = strdup(service);
+    if (!s->service) {
+        free(s);
+        return NULL;
+    }
+
+    s->proto = strdup(proto);
+    if (!s->proto) {
+        free((char *)s->service);
+        free(s);
+        return NULL;
+    }
+
+    return s;
+}
+
+/**
+ * @brief  free service memory
+ *
+ * @param  service      the service
+ */
+static void _mdns_free_service(mdns_service_t * service)
+{
+    if (!service) {
+        return;
+    }
+    free((char *)service->instance);
+    free((char *)service->service);
+    free((char *)service->proto);
+    if (service->txt_num_items) {
+        uint8_t i;
+        for(i=0; i<service->txt_num_items; i++) {
+            free((char *)service->txt[i]);
+        }
+    }
+    free(service->txt);
+    free(service);
+}
+
+/**
+ * @brief  read uint16_t from a packet
+ * @param  packet       the packet
+ * @param  index        index in the packet where the value starts
+ *
+ * @return the value
+ */
+static inline uint16_t _mdns_read_u16(const uint8_t * packet, uint16_t index)
+{
+    return (uint16_t)(packet[index]) << 8 | packet[index+1];
+}
+
+/**
+ * @brief  main packet parser
+ *
+ * @param  server       the server
+ * @param  data         byte array holding the packet data
+ * @param  len          length of the byte array
+ */
+static void _mdns_parse_packet(mdns_server_t * server, const uint8_t * data, size_t len)
+{
+    static mdns_name_t n;
+    static mdns_result_temp_t a;
+
+    const uint8_t * content = data + MDNS_HEAD_LEN;
+    mdns_name_t * name = &n;
+    memset(name, 0, sizeof(mdns_name_t));
+
+    uint16_t questions = _mdns_read_u16(data, MDNS_HEAD_QUESTIONS_OFFSET);
+    uint16_t answers = _mdns_read_u16(data, MDNS_HEAD_ANSWERS_OFFSET);
+    uint16_t additional = _mdns_read_u16(data, MDNS_HEAD_ADDITIONAL_OFFSET);
+
+    if (questions) {
+        uint8_t qs = questions;
+        mdns_answer_item_t * answers = NULL;
+
+        while(qs--) {
+            content = _mdns_parse_fqdn(data, content, name);
+            if (!content) {
+                break;//error
+            }
+
+            uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET);
+            content = content + 4;
+
+            if (!name->service[0] || !name->proto[0]) {
+                if (type == MDNS_TYPE_A || type == MDNS_TYPE_AAAA || type == MDNS_TYPE_ANY) {//send A + AAAA
+                    if (name->host[0] && server->hostname && server->hostname[0] && !strcmp(name->host, server->hostname)) {
+                        answers = _mdns_add_answer(answers, NULL, MDNS_ANSWER_A);
+                    }
+                }
+                continue;
+            }
+
+            if (name->sub) {
+                continue;
+            }
+
+            mdns_srv_item_t * si = _mdns_get_service_item(server, name->service, name->proto);
+            if (!si) {
+                //service not found
+                continue;
+            }
+
+            if (type == MDNS_TYPE_PTR) {
+                answers = _mdns_add_answer(answers, si->service, MDNS_ANSWER_ALL);
+            } else if (type == MDNS_TYPE_TXT) {
+                //match instance/host
+                const char * host = (si->service->instance)?si->service->instance
+                        :(server->instance)?server->instance
+                        :server->hostname;
+                if (!host || !host[0] || !name->host[0] || strcmp(name->host, host)) {
+                    continue;
+                }
+                answers = _mdns_add_answer(answers, si->service, MDNS_ANSWER_TXT);
+            } else if (type == MDNS_TYPE_SRV) {
+                //match instance/host
+                const char * host = (si->service->instance)?si->service->instance
+                        :(server->instance)?server->instance
+                        :server->hostname;
+                if (!host || !host[0] || !name->host[0] || strcmp(name->host, host)) {
+                    continue;
+                }
+                answers = _mdns_add_answer(answers, si->service, MDNS_ANSWER_SRV | MDNS_ANSWER_A);
+            } else if (type == MDNS_TYPE_ANY) {//send all
+                //match host
+                if (!name->host[0] || !server->hostname || !server->hostname[0] || strcmp(name->host, server->hostname)) {
+                    answers = _mdns_add_answer(answers, si->service, MDNS_ANSWER_ALL);
+                }
+            }
+        }
+        if (answers) {
+            _mdns_send_answers(server, answers);
+        }
+    }
+
+    if (server->search.running && (answers || additional)) {
+        mdns_result_temp_t * answer = &a;
+        memset(answer, 0, sizeof(mdns_result_temp_t));
+
+        while(content < (data + len)) {
+            content = _mdns_parse_fqdn(data, content, name);
+            if (!content) {
+                break;//error
+            }
+            uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET);
+            uint16_t data_len = _mdns_read_u16(content, MDNS_LEN_OFFSET);
+            const uint8_t * data_ptr = content + MDNS_DATA_OFFSET;
+
+            content = data_ptr + data_len;
+
+            if (type == MDNS_TYPE_PTR) {
+                if (!_mdns_parse_fqdn(data, data_ptr, name)) {
+                    continue;//error
+                }
+                if (server->search.host[0] ||
+                        (strcmp(name->service, server->search.service) != 0) ||
+                        (strcmp(name->proto, server->search.proto) != 0)) {
+                    continue;//not searching for service or wrong service/proto
+                }
+                sprintf(answer->instance, "%s", name->host);
+            } else if (type == MDNS_TYPE_SRV) {
+                if (server->search.host[0] ||
+                        (strcmp(name->service, server->search.service) != 0) ||
+                        (strcmp(name->proto, server->search.proto) != 0)) {
+                    continue;//not searching for service or wrong service/proto
+                }
+                if (answer->instance[0]) {
+                    if (strcmp(answer->instance, name->host) != 0) {
+                        continue;//instance name is not the same as the one in the PTR record
+                    }
+                } else {
+                    sprintf(answer->instance, "%s", name->host);
+                }
+                //parse record value
+                if (!_mdns_parse_fqdn(data, data_ptr + MDNS_SRV_FQDN_OFFSET, name)) {
+                    continue;//error
+                }
+
+                answer->ptr = 1;
+                answer->priority = _mdns_read_u16(data_ptr, MDNS_SRV_PRIORITY_OFFSET);
+                answer->weight = _mdns_read_u16(data_ptr, MDNS_SRV_WEIGHT_OFFSET);
+                answer->port = _mdns_read_u16(data_ptr, MDNS_SRV_PORT_OFFSET);
+                if (answer->host[0]) {
+                    if (strcmp(answer->host, name->host) != 0) {
+                        answer->addr = 0;
+                        sprintf(answer->host, "%s", name->host);
+                    }
+                } else {
+                    sprintf(answer->host, "%s", name->host);
+                }
+            } else if (type == MDNS_TYPE_TXT) {
+                uint16_t i=0,b=0, y;
+                while(i < data_len) {
+                    uint8_t partLen = data_ptr[i++];
+                    for(y=0; y<partLen; y++) {
+                        char d = data_ptr[i++];
+                        answer->txt[b++] = d;
+                    }
+                    if (i<data_len) {
+                        answer->txt[b++] = '&';
+                    }
+                }
+                answer->txt[b] = 0;
+            } else if (type == MDNS_TYPE_AAAA) {
+                if (server->search.host[0]) {
+                    if (strcmp(name->host, server->search.host) != 0) {
+                        continue;//wrong host
+                    }
+                } else if (!answer->ptr) {
+                    sprintf(answer->host, "%s", name->host);
+                } else if (strcmp(answer->host, name->host) != 0) {
+                    continue;//wrong host
+                }
+                memcpy(answer->addrv6, data_ptr, sizeof(ip6_addr_t));
+            } else if (type == MDNS_TYPE_A) {
+                if (server->search.host[0]) {
+                    if (strcmp(name->host, server->search.host) != 0) {
+                        continue;//wrong host
+                    }
+                } else if (!answer->ptr) {
+                    sprintf(answer->host, "%s", name->host);
+                } else if (strcmp(answer->host, name->host) != 0) {
+                    continue;//wrong host
+                }
+                if (server->search.running && answer->addr) {
+                    _mdns_add_result(server, answer);//another IP for our host
+                }
+                IP4_ADDR(answer, data_ptr[0], data_ptr[1], data_ptr[2], data_ptr[3]);
+            }
+        }
+        if (server->search.running && (server->search.host[0] || answer->ptr) && answer->addr) {
+            _mdns_add_result(server, answer);
+        }
+        //end while
+    }
+}
+
+
+
+/*
+ * Public Methods
+ * */
+esp_err_t mdns_init(tcpip_adapter_if_t tcpip_if, mdns_server_t ** mdns_server)
+{
+    esp_err_t err = ESP_OK;
+
+    if (tcpip_if >= TCPIP_ADAPTER_IF_MAX) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (_mdns_server_get(tcpip_if)) {
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    uint8_t mode;
+    err = esp_wifi_get_mode((wifi_mode_t*)&mode);
+    if (err) {
+        return err;
+    }
+
+    if ((tcpip_if == TCPIP_ADAPTER_IF_STA && !(mode & WIFI_MODE_STA))
+            || (tcpip_if == TCPIP_ADAPTER_IF_AP && !(mode & WIFI_MODE_AP))) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    mdns_server_t * server = (mdns_server_t *)malloc(sizeof(mdns_server_t));
+    if (!server) {
+        return ESP_ERR_NO_MEM;
+    }
+
+    server->tcpip_if = tcpip_if;
+    server->hostname = NULL;
+    server->instance = NULL;
+    server->services = NULL;
+    server->search.host[0] = 0;
+    server->search.service[0] = 0;
+    server->search.proto[0] = 0;
+    server->search.running = false;
+    server->search.results = NULL;
+    server->pcb = NULL;
+
+    server->lock = xSemaphoreCreateMutex();
+    if (!server->lock) {
+        free(server);
+        return ESP_ERR_NO_MEM;
+    }
+
+    server->search.lock = xSemaphoreCreateMutex();
+    if (!server->search.lock) {
+        vSemaphoreDelete(server->lock);
+        free(server);
+        return ESP_ERR_NO_MEM;
+    }
+
+    server->queue = xQueueCreate(MDNS_PACKET_QUEUE_LEN, sizeof(struct pbuf *));
+    if (!server->queue) {
+        vSemaphoreDelete(server->lock);
+        vSemaphoreDelete(server->search.lock);
+        free(server);
+        return ESP_ERR_NO_MEM;
+    }
+
+    if (_mdns_server_add(server)) {
+        //service start failed!
+        vSemaphoreDelete(server->lock);
+        vSemaphoreDelete(server->search.lock);
+        vQueueDelete(server->queue);
+        free(server);
+        return ESP_FAIL;
+    }
+
+    const char * hostname = NULL;
+    tcpip_adapter_get_hostname(server->tcpip_if, &hostname);
+    mdns_set_hostname(server, hostname);
+
+    *mdns_server = server;
+
+    return ESP_OK;
+}
+
+void mdns_free(mdns_server_t * server)
+{
+    if (!server) {
+        return;
+    }
+    _mdns_server_remove(server);
+    mdns_service_remove_all(server);
+    MDNS_MUTEX_LOCK();
+    free((char*)server->hostname);
+    free((char*)server->instance);
+    if (server->queue) {
+        struct pbuf * c;
+        while(xQueueReceive(server->queue, &c, 0) == pdTRUE) {
+            pbuf_free(c);
+        }
+        vQueueDelete(server->queue);
+    }
+    if (server->search.running) {
+        mdns_query_end(server);
+    }
+    mdns_result_free(server);
+    vSemaphoreDelete(server->search.lock);
+    MDNS_MUTEX_UNLOCK();
+    vSemaphoreDelete(server->lock);
+    free(server);
+}
+
+esp_err_t mdns_set_hostname(mdns_server_t * server, const char * hostname)
+{
+    if (!server) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    MDNS_MUTEX_LOCK();
+    free((char*)server->hostname);
+    server->hostname = (char *)malloc(strlen(hostname)+1);
+    if (!server->hostname) {
+        MDNS_MUTEX_UNLOCK();
+        return ESP_ERR_NO_MEM;
+    }
+    sprintf((char *)server->hostname, "%s", hostname);
+    MDNS_MUTEX_UNLOCK();
+    return ERR_OK;
+}
+
+esp_err_t mdns_set_instance(mdns_server_t * server, const char * instance)
+{
+    if (!server) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    MDNS_MUTEX_LOCK();
+    free((char*)server->instance);
+    server->instance = (char *)malloc(strlen(instance)+1);
+    if (!server->instance) {
+        MDNS_MUTEX_UNLOCK();
+        return ESP_ERR_NO_MEM;
+    }
+    sprintf((char *)server->instance, "%s", instance);
+    MDNS_MUTEX_UNLOCK();
+    return ERR_OK;
+}
+
+/*
+ * MDNS SERVICES
+ * */
+
+esp_err_t mdns_service_add(mdns_server_t * server, const char * service, const char * proto, uint16_t port)
+{
+    if (!server || !service || !proto || !port) {
+        //bad argument
+        return ESP_ERR_INVALID_ARG;
+    }
+    mdns_srv_item_t * item = _mdns_get_service_item(server, service, proto);
+    if (item) {
+        //we already have that service
+        return mdns_service_port_set(server, service, proto, port);
+    }
+
+    mdns_service_t * s = _mdns_create_service(service, proto, port);
+    if (!s) {
+        return ESP_ERR_NO_MEM;
+    }
+
+    item = (mdns_srv_item_t *)malloc(sizeof(mdns_srv_item_t));
+    if (!item) {
+        return ESP_ERR_NO_MEM;
+    }
+
+    item->service = s;
+    item->next = server->services;
+    server->services = item;
+    return ESP_OK;
+}
+
+esp_err_t mdns_service_port_set(mdns_server_t * server, const char * service, const char * proto, uint16_t port)
+{
+    if (!server || !server->services || !service || !proto || !port) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    mdns_srv_item_t * s = _mdns_get_service_item(server, service, proto);
+    if (!s) {
+        return ESP_ERR_NOT_FOUND;
+    }
+    MDNS_MUTEX_LOCK();
+    s->service->port = port;
+    MDNS_MUTEX_UNLOCK();
+    return ESP_OK;
+}
+
+esp_err_t mdns_service_txt_set(mdns_server_t * server, const char * service, const char * proto, uint8_t num_items, const char ** txt)
+{
+    if (!server || !server->services || !service || !proto) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    mdns_srv_item_t * s = _mdns_get_service_item(server, service, proto);
+    if (!s) {
+        return ESP_ERR_NOT_FOUND;
+    }
+    MDNS_MUTEX_LOCK();
+    if (s->service->txt_num_items) {
+        uint8_t i;
+        for(i=0; i<s->service->txt_num_items; i++) {
+            free((char *)s->service->txt[i]);
+        }
+    }
+    free(s->service->txt);
+    if (num_items) {
+        s->service->txt = (const char **)malloc(sizeof(char *) * num_items);
+        if (!s->service->txt) {
+            s->service->txt_num_items = 0;
+            goto fail;
+        }
+        uint8_t i;
+        s->service->txt_num_items = num_items;
+        for(i=0; i<num_items; i++) {
+            s->service->txt[i] = strdup(txt[i]);
+            if (!s->service->txt[i]) {
+                s->service->txt_num_items = i;
+                goto fail;
+            }
+        }
+    }
+    MDNS_MUTEX_UNLOCK();
+    return ESP_OK;
+fail:
+    MDNS_MUTEX_UNLOCK();
+    return ESP_ERR_NO_MEM;
+}
+
+esp_err_t mdns_service_instance_set(mdns_server_t * server, const char * service, const char * proto, const char * instance)
+{
+    if (!server || !server->services || !service || !proto) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    mdns_srv_item_t * s = _mdns_get_service_item(server, service, proto);
+    if (!s) {
+        return ESP_ERR_NOT_FOUND;
+    }
+    MDNS_MUTEX_LOCK();
+    free((char*)s->service->instance);
+    s->service->instance = strdup(instance);
+    if (!s->service->instance) {
+        MDNS_MUTEX_UNLOCK();
+        return ESP_ERR_NO_MEM;
+    }
+    MDNS_MUTEX_UNLOCK();
+    return ESP_OK;
+}
+
+esp_err_t mdns_service_remove(mdns_server_t * server, const char * service, const char * proto)
+{
+    if (!server || !server->services || !service || !proto) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    mdns_srv_item_t * s = _mdns_get_service_item(server, service, proto);
+    if (!s) {
+        return ESP_ERR_NOT_FOUND;
+    }
+    //first item
+    if (server->services == s) {
+        MDNS_MUTEX_LOCK();
+        server->services = server->services->next;
+        MDNS_MUTEX_UNLOCK();
+        _mdns_free_service(s->service);
+        free(s);
+        return ESP_OK;
+    }
+    //not first item
+    mdns_srv_item_t * a = server->services;
+    while(a->next && a->next != s) {
+        a = a->next;
+    }
+    //next item of the current item is our item
+    if (a->next == s) {
+        MDNS_MUTEX_LOCK();
+        a->next = s->next;
+        MDNS_MUTEX_UNLOCK();
+        _mdns_free_service(s->service);
+        free(s);
+        return ESP_OK;
+    }
+    //how did we end here?
+    return ESP_FAIL;
+}
+
+esp_err_t mdns_service_remove_all(mdns_server_t * server)
+{
+    if (!server) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    if (!server->services) {
+        return ESP_OK;
+    }
+    MDNS_MUTEX_LOCK();
+    mdns_srv_item_t * a = server->services;
+    server->services = NULL;
+    while(a) {
+        mdns_srv_item_t * s = a;
+        a = a->next;
+        _mdns_free_service(s->service);
+        free(s);
+    }
+    MDNS_MUTEX_UNLOCK();
+    return ESP_OK;
+}
+
+/*
+ * MDNS QUERY
+ * */
+
+uint32_t mdns_query(mdns_server_t * server, const char * service, const char * proto, uint32_t timeout)
+{
+    if (!server || !service) {
+        return 0;
+    }
+    MDNS_SEARCH_LOCK();
+    uint16_t qtype = MDNS_TYPE_PTR;
+    mdns_result_free(server);
+    if (proto) {
+        server->search.host[0] = 0;
+        snprintf(server->search.service, MDNS_NAME_MAX_LEN, "%s", service);
+        snprintf(server->search.proto, MDNS_NAME_MAX_LEN, "%s", proto);
+    } else {
+        snprintf(server->search.host, MDNS_NAME_MAX_LEN, "%s", service);
+        server->search.service[0] = 0;
+        server->search.proto[0] = 0;
+        qtype = MDNS_TYPE_A;
+    }
+
+    uint8_t hostname_len = strlen(server->search.host);
+    uint8_t service_type_len = strlen(server->search.service);
+    uint8_t protoname_len = strlen(server->search.proto);
+
+    size_t len = 23; //head+type+class+(strlen(local)+1)+fqdn_end
+    if (hostname_len) {
+        len += hostname_len + 1;
+    } else if (service_type_len) {
+        len += service_type_len + protoname_len + 2;
+    }
+
+    uint8_t * packet = (uint8_t *)malloc(len);
+    if (!packet) {
+        return 0;
+    }
+    memset(packet, 0, len);
+    _mdns_set_u16(packet, MDNS_HEAD_QUESTIONS_OFFSET, 1);
+    uint16_t index = MDNS_HEAD_LEN;
+
+    if (hostname_len) {
+        _mdns_append_string(packet, &index, server->search.host);
+    } else if (service_type_len) {
+        _mdns_append_string(packet, &index, server->search.service);
+        _mdns_append_string(packet, &index, server->search.proto);
+    }
+
+    _mdns_append_string(packet, &index, MDNS_DEFAULT_DOMAIN);
+    _mdns_append_u8(packet, &index, 0); //fqdn_end
+
+    _mdns_append_u16(packet, &index, qtype);
+    _mdns_append_u16(packet, &index, MDNS_CLASS_IN);
+
+    MDNS_MUTEX_LOCK();
+    size_t written = _mdns_server_write(server, packet, index);
+    MDNS_MUTEX_UNLOCK();
+    free(packet);
+    if (written != index) {
+        return 0;
+    }
+
+    server->search.running = true;
+    if (timeout) {
+        uint32_t startAt = xTaskGetTickCount() * portTICK_PERIOD_MS;
+        while(server->search.running && ((xTaskGetTickCount() * portTICK_PERIOD_MS) - startAt) < timeout) {
+            vTaskDelay(1 / portTICK_PERIOD_MS);
+        }
+        server->search.running = false;
+        MDNS_SEARCH_UNLOCK();
+        return mdns_result_get_count(server);
+    }
+    return 0;
+}
+
+size_t mdns_query_end(mdns_server_t * server)
+{
+    if (!server || !server->search.running) {
+        return 0;
+    }
+    server->search.running = false;
+    MDNS_SEARCH_UNLOCK();
+    return mdns_result_get_count(server);
+}
+
+esp_err_t mdns_result_free(mdns_server_t * server)
+{
+    if (!server || server->search.running || !server->search.results) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    while(server->search.results) {
+        const mdns_result_t * r = server->search.results;
+        server->search.results = (mdns_result_t *)r->next;
+        free((char *)r->host);
+        free((char *)r->instance);
+        free((char *)r->txt);
+        free((mdns_result_t *)r);
+    }
+    server->search.results = NULL;
+    return ESP_OK;
+}
+
+size_t mdns_result_get_count(mdns_server_t * server)
+{
+    if (!server || !server->search.results) {
+        return 0;
+    }
+    size_t len = 0;
+    const mdns_result_t * r = server->search.results;
+    while(r) {
+        len++;
+        r = r->next;
+    }
+    return len;
+}
+
+const mdns_result_t * mdns_result_get(mdns_server_t * server, size_t num)
+{
+    if (!server || !server->search.results) {
+        return NULL;
+    }
+    size_t len = 0;
+    const mdns_result_t * r = server->search.results;
+    while(r) {
+        if (len++ == num) {
+            return r;
+        }
+        r = r->next;
+    }
+    return NULL;
+}
index 07bdc12f913203a495ac7ffc06551385a23c038f..4f3b49ed270e8622ccb32370477a3f880b13db90 100644 (file)
@@ -58,6 +58,17 @@ extern "C" {
 
 #define IPSTR "%d.%d.%d.%d"
 
+#define IPV62STR(ipaddr) IP6_ADDR_BLOCK1(&(ipaddr)),     \
+    IP6_ADDR_BLOCK2(&(ipaddr)),     \
+    IP6_ADDR_BLOCK3(&(ipaddr)),     \
+    IP6_ADDR_BLOCK4(&(ipaddr)),     \
+    IP6_ADDR_BLOCK5(&(ipaddr)),     \
+    IP6_ADDR_BLOCK6(&(ipaddr)),     \
+    IP6_ADDR_BLOCK7(&(ipaddr)),     \
+    IP6_ADDR_BLOCK8(&(ipaddr))
+
+#define IPV6STR "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x"
+
 typedef struct {
     ip4_addr_t ip;
     ip4_addr_t netmask;
index a3fc5faa124b897165da134c7a0120230db53209..aa6c87476e61ee993d434510d117787d283deda4 100755 (executable)
@@ -36,7 +36,8 @@ INPUT = ../components/esp32/include/esp_wifi.h \
        ../components/sdmmc/include/sdmmc_cmd.h \
        ../components/fatfs/src/esp_vfs_fat.h \
        ../components/fatfs/src/diskio.h \
-       ../components/esp32/include/esp_core_dump.h
+       ../components/esp32/include/esp_core_dump.h \
+       ../components/mdns/include/mdns.h
 
 ## Get warnings for functions that have no documentation for their parameters or return value 
 ##
diff --git a/docs/api/mdns.rst b/docs/api/mdns.rst
new file mode 100644 (file)
index 0000000..9578944
--- /dev/null
@@ -0,0 +1,215 @@
+mDNS Service
+============
+
+Overview
+--------
+
+mDNS is a multicast UDP service that is used to provide local network service and host discovery.
+
+mDNS is installed by default on most operating systems or is available as separate package. On ``Mac OS`` it is installed by default and is called ``Bonjour``. Apple releases an installer for ``Windows`` that can be found `on Apple's support page <https://support.apple.com/downloads/bonjour%2520for%2520windows>`_. On ``Linux``, mDNS is provided by `avahi <https://github.com/lathiat/avahi>`_ and is usually installed by default.
+
+mDNS Properties
+^^^^^^^^^^^^^^^
+
+    * ``hostname``: the hostname that the device will respond to. If not set, the ``hostname`` will be read from the interface. Example: ``my-esp32`` will resolve to ``my-esp32.local``
+    * ``default_instance``: friendly name for your device, like ``Jhon's ESP32 Thing``. If not set, ``hostname`` will be used.
+
+Example method to start mDNS for the STA interface and set ``hostname`` and ``default_instance``:
+
+  ::
+
+    mdns_server_t * mdns = NULL;
+    
+    void start_mdns_service()
+    {
+        //initialize mDNS service on STA interface
+        esp_err_t err = mdns_init(TCPIP_ADAPTER_IF_STA, &mdns);
+        if (err) {
+            printf("MDNS Init failed: %d\n", err);
+            return;
+        }
+    
+        //set hostname
+        mdns_set_hostname(mdns, "my-esp32");
+        //set default instance
+        mdns_set_instance(mdns, "Jhon's ESP32 Thing");
+    }
+
+mDNS Services
+^^^^^^^^^^^^^
+
+mDNS can advertise information about network services that your device offers. Each service is defined by a few properties.
+
+    * ``service``: (required) service type, prepended with underscore. Some common types can be found `here <http://www.dns-sd.org/serviceTypes.html>`_.
+    * ``proto``: (required) protocol that the service runs on, prepended with underscore. Example: ``_tcp`` or ``_udp`` 
+    * ``port``: (required) network port that the service runs on
+    * ``instance``: friendly name for your service, like ``Jhon's ESP32 Web Server``. If not defined, ``default_instance`` will be used.
+    * ``txt``: ``var=val`` array of strings, used to define properties for your service
+
+Example method to add a few services and different properties:
+
+  ::
+
+    void add_mdns_services()
+    {
+        //add our services
+        mdns_service_add(mdns, "_http", "_tcp", 80);
+        mdns_service_add(mdns, "_arduino", "_tcp", 3232);
+        mdns_service_add(mdns, "_myservice", "_udp", 1234);
+        
+        //NOTE: services must be added before their properties can be set
+        //use custom instance for the web server
+        mdns_service_instance_set(mdns, "_http", "_tcp", "Jhon's ESP32 Web Server");
+
+        const char * arduTxtData[4] = {
+                "board=esp32",
+                "tcp_check=no",
+                "ssh_upload=no",
+                "auth_upload=no"
+        };
+        //set txt data for service (will free and replace current data)
+        mdns_service_txt_set(mdns, "_arduino", "_tcp", 4, arduTxtData);
+        
+        //change service port
+        mdns_service_port_set(mdns, "_myservice", "_udp", 4321);
+    }
+
+mDNS Query
+^^^^^^^^^^
+
+mDNS provides methods for browsing for services and resolving host's IP/IPv6 addresses.
+    Results are returned as a linked list of ``mdns_result_t`` objects. If the result is from host query, 
+    it will contain only ``addr`` and ``addrv6`` if found. Service queries will populate all fields 
+    in a result that were found.
+
+Example method to resolve host IPs:
+
+  ::
+
+    void resolve_mdns_host(const char * hostname)
+    {
+        printf("mDNS Host Lookup: %s.local\n", hostname);
+        //run search for 1000 ms
+        if (mdns_query(mdns, hostname, NULL, 1000)) {
+            //results were found
+            const mdns_result_t * results = mdns_result_get(mdns, 0);
+            //itterate through all results
+            size_t i = 1;
+            while(results) {
+                //print result information
+                printf("  %u: IP:" IPSTR ", IPv6:" IPV6STR "\n", i++
+                    IP2STR(&results->addr), IPV62STR(results->addrv6));
+                //load next result. Will be NULL if this was the last one
+                results = results->next;
+            }
+            //free the results from memory
+            mdns_result_free(mdns);
+        } else {
+            //host was not found
+            printf("  Host Not Found\n");
+        }
+    }
+
+Example method to resolve local services:
+
+  ::
+
+    void find_mdns_service(const char * service, const char * proto)
+    {
+        printf("mDNS Service Lookup: %s.%s\n", service, proto);
+        //run search for 1000 ms
+        if (mdns_query(mdns, service, proto, 1000)) {
+            //results were found
+            const mdns_result_t * results = mdns_result_get(mdns, 0);
+            //itterate through all results
+            size_t i = 1;
+            while(results) {
+                //print result information
+                printf("  %u: hostname:%s, instance:\"%s\", IP:" IPSTR ", IPv6:" IPV6STR ", port:%u, txt:%s\n", i++,
+                    (results->host)?results->host:"NULL", (results->instance)?results->instance:"NULL",
+                    IP2STR(&results->addr), IPV62STR(results->addrv6),
+                    results->port, (results->txt)?results->txt:"\r");
+                //load next result. Will be NULL if this was the last one
+                results = results->next;
+            }
+            //free the results from memory
+            mdns_result_free(mdns);
+        } else {
+            //service was not found
+            printf("  Service Not Found\n");
+        }
+    }
+
+Example of using the methods above:
+
+  ::
+
+    void my_app_some_method(){
+        //search for esp32-mdns.local
+        resolve_mdns_host("esp32-mdns");
+        
+        //search for HTTP servers
+        find_mdns_service("_http", "_tcp");
+        //or file servers
+        find_mdns_service("_smb", "_tcp"); //windows sharing
+        find_mdns_service("_afpovertcp", "_tcp"); //apple sharing
+        find_mdns_service("_nfs", "_tcp"); //NFS server
+        find_mdns_service("_ftp", "_tcp"); //FTP server
+        //or networked printer
+        find_mdns_service("_printer", "_tcp");
+        find_mdns_service("_ipp", "_tcp");
+    }
+
+Application Example
+-------------------
+
+mDNS server/scanner example: `examples/30_mdns_example <https://github.com/espressif/esp-idf/tree/master/examples/30_mdns_example>`_.
+
+API Reference
+-------------
+
+Header Files
+^^^^^^^^^^^^
+
+  * `components/mdns/include/mdns.h <https://github.com/espressif/esp-idf/blob/master/components/mdns/include/mdns.h>`_
+
+Macros
+^^^^^^
+
+
+Type Definitions
+^^^^^^^^^^^^^^^^
+
+.. doxygentypedef:: mdns_server_t
+.. doxygentypedef:: mdns_result_t
+
+Enumerations
+^^^^^^^^^^^^
+
+
+Structures
+^^^^^^^^^^
+
+.. doxygenstruct:: mdns_result_s
+    :members:
+
+
+Functions
+^^^^^^^^^
+
+.. doxygenfunction:: mdns_init
+.. doxygenfunction:: mdns_free
+.. doxygenfunction:: mdns_set_hostname
+.. doxygenfunction:: mdns_set_instance
+.. doxygenfunction:: mdns_service_add
+.. doxygenfunction:: mdns_service_remove
+.. doxygenfunction:: mdns_service_instance_set
+.. doxygenfunction:: mdns_service_txt_set
+.. doxygenfunction:: mdns_service_port_set
+.. doxygenfunction:: mdns_service_remove_all
+.. doxygenfunction:: mdns_query
+.. doxygenfunction:: mdns_query_end
+.. doxygenfunction:: mdns_result_get_count
+.. doxygenfunction:: mdns_result_get
+.. doxygenfunction:: mdns_result_free
+
index b994648848bcd80e7f90a530ee6e0e5bfac5e711..bcc3c923a240e5f616827adc3af8410928ccc0a5 100644 (file)
@@ -124,6 +124,7 @@ Contents:
    Deep Sleep <api/deep_sleep>
    deep-sleep-stub
 
+   mDNS <api/mdns>
    Template <api/template>
 
 .. toctree::
diff --git a/examples/30_mdns_example/Makefile b/examples/30_mdns_example/Makefile
new file mode 100644 (file)
index 0000000..0353c51
--- /dev/null
@@ -0,0 +1,9 @@
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := mdns-test
+
+include $(IDF_PATH)/make/project.mk
+
diff --git a/examples/30_mdns_example/README.md b/examples/30_mdns_example/README.md
new file mode 100644 (file)
index 0000000..1c29860
--- /dev/null
@@ -0,0 +1,5 @@
+# 30_mdns example
+
+Shows how to use mDNS to advertise lookup services and hosts
+
+See the README.md file in the upper level 'examples' directory for more information about examples.
diff --git a/examples/30_mdns_example/main/Kconfig.projbuild b/examples/30_mdns_example/main/Kconfig.projbuild
new file mode 100644 (file)
index 0000000..3122e03
--- /dev/null
@@ -0,0 +1,29 @@
+menu "Example Configuration"
+
+config WIFI_SSID
+    string "WiFi SSID"
+       default "myssid"
+       help
+               SSID (network name) for the example to connect to.
+
+config WIFI_PASSWORD
+    string "WiFi Password"
+       default "myssid"
+       help
+               WiFi password (WPA or WPA2) for the example to use.
+
+               Can be left blank if the network has no security set.
+
+config MDNS_HOSTNAME
+    string "mDNS Hostname"
+       default "esp32-mdns"
+       help
+               mDNS Hostname for example to use
+
+config MDNS_INSTANCE
+    string "mDNS Instance Name"
+       default "ESP32 with mDNS"
+       help
+               mDNS Instance Name for example to use
+
+endmenu
\ No newline at end of file
diff --git a/examples/30_mdns_example/main/component.mk b/examples/30_mdns_example/main/component.mk
new file mode 100644 (file)
index 0000000..a98f634
--- /dev/null
@@ -0,0 +1,4 @@
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
diff --git a/examples/30_mdns_example/main/mdns_example_main.c b/examples/30_mdns_example/main/mdns_example_main.c
new file mode 100644 (file)
index 0000000..d19fd1a
--- /dev/null
@@ -0,0 +1,184 @@
+/* MDNS-SD Query and advertise Example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+#include <string.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/event_groups.h"
+#include "esp_system.h"
+#include "esp_wifi.h"
+#include "esp_event_loop.h"
+#include "esp_log.h"
+#include "nvs_flash.h"
+#include "mdns.h"
+
+/* The examples use simple WiFi configuration that you can set via
+   'make menuconfig'.
+
+   If you'd rather not, just change the below entries to strings with
+   the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
+*/
+#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID
+#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD
+
+#define EXAMPLE_MDNS_HOSTNAME CONFIG_MDNS_HOSTNAME
+#define EXAMPLE_MDNS_INSTANCE CONFIG_MDNS_INSTANCE
+
+/* FreeRTOS event group to signal when we are connected & ready to make a request */
+static EventGroupHandle_t wifi_event_group;
+
+/* The event group allows multiple bits for each event,
+   but we only care about one event - are we connected
+   to the AP with an IP? */
+const int CONNECTED_BIT = BIT0;
+
+static const char *TAG = "mdns-test";
+
+static esp_err_t event_handler(void *ctx, system_event_t *event)
+{
+    switch(event->event_id) {
+    case SYSTEM_EVENT_STA_START:
+        esp_wifi_connect();
+        break;
+    case SYSTEM_EVENT_STA_CONNECTED:
+        /* enable ipv6 */
+        tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA);
+        break;
+    case SYSTEM_EVENT_STA_GOT_IP:
+        xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
+        break;
+    case SYSTEM_EVENT_STA_DISCONNECTED:
+        /* This is a workaround as ESP32 WiFi libs don't currently
+           auto-reassociate. */
+        esp_wifi_connect();
+        xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
+        break;
+    default:
+        break;
+    }
+    return ESP_OK;
+}
+
+static void initialise_wifi(void)
+{
+    tcpip_adapter_init();
+    wifi_event_group = xEventGroupCreate();
+    ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) );
+    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+    ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
+    ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
+    wifi_config_t wifi_config = {
+        .sta = {
+            .ssid = EXAMPLE_WIFI_SSID,
+            .password = EXAMPLE_WIFI_PASS,
+        },
+    };
+    ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
+    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
+    ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
+    ESP_ERROR_CHECK( esp_wifi_start() );
+}
+
+static void query_mdns_service(mdns_server_t * mdns, const char * service, const char * proto)
+{
+    if(!mdns) {
+        return;
+    }
+    uint32_t res;
+    if (!proto) {
+        ESP_LOGI(TAG, "Host Lookup: %s", service);
+        res = mdns_query(mdns, service, 0, 1000);
+        if (res) {
+            size_t i;
+            for(i=0; i<res; i++) {
+                const mdns_result_t * r = mdns_result_get(mdns, i);
+                if (r) {
+                    ESP_LOGI(TAG, "  %u: " IPSTR " " IPV6STR, i+1, 
+                        IP2STR(&r->addr), IPV62STR(r->addrv6));
+                }
+            }
+            mdns_result_free(mdns);
+        } else {
+            ESP_LOGI(TAG, "  Not Found");
+        }
+    } else {
+        ESP_LOGI(TAG, "Service Lookup: %s.%s ", service, proto);
+        res = mdns_query(mdns, service, proto, 1000);
+        if (res) {
+            size_t i;
+            for(i=0; i<res; i++) {
+                const mdns_result_t * r = mdns_result_get(mdns, i);
+                if (r) {
+                    ESP_LOGI(TAG, "  %u: %s \"%s\" " IPSTR " " IPV6STR " %u %s", i+1, 
+                        (r->host)?r->host:"", (r->instance)?r->instance:"", 
+                        IP2STR(&r->addr), IPV62STR(r->addrv6),
+                        r->port, (r->txt)?r->txt:"");
+                }
+            }
+            mdns_result_free(mdns);
+        }
+    }
+}
+
+static void mdns_task(void *pvParameters)
+{
+    mdns_server_t * mdns = NULL;
+    while(1) {
+        /* Wait for the callback to set the CONNECTED_BIT in the
+           event group.
+        */
+        xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
+                            false, true, portMAX_DELAY);
+        ESP_LOGI(TAG, "Connected to AP");
+
+        if (!mdns) {
+            esp_err_t err = mdns_init(TCPIP_ADAPTER_IF_STA, &mdns);
+            if (err) {
+                ESP_LOGE(TAG, "Failed starting MDNS: %u", err);
+                continue;
+            }
+
+            ESP_ERROR_CHECK( mdns_set_hostname(mdns, EXAMPLE_MDNS_HOSTNAME) );
+            ESP_ERROR_CHECK( mdns_set_instance(mdns, EXAMPLE_MDNS_INSTANCE) );
+
+            const char * arduTxtData[4] = {
+                "board=esp32",
+                "tcp_check=no",
+                "ssh_upload=no",
+                "auth_upload=no"
+            };
+
+            ESP_ERROR_CHECK( mdns_service_add(mdns, "_arduino", "_tcp", 3232) );
+            ESP_ERROR_CHECK( mdns_service_txt_set(mdns, "_arduino", "_tcp", 4, arduTxtData) );
+            ESP_ERROR_CHECK( mdns_service_add(mdns, "_http", "_tcp", 80) );
+            ESP_ERROR_CHECK( mdns_service_instance_set(mdns, "_http", "_tcp", "ESP32 WebServer") );
+            ESP_ERROR_CHECK( mdns_service_add(mdns, "_smb", "_tcp", 445) );
+        } else {
+            query_mdns_service(mdns, "esp32", NULL);
+            query_mdns_service(mdns, "_arduino", "_tcp");
+            query_mdns_service(mdns, "_http", "_tcp");
+            query_mdns_service(mdns, "_printer", "_tcp");
+            query_mdns_service(mdns, "_ipp", "_tcp");
+            query_mdns_service(mdns, "_afpovertcp", "_tcp");
+            query_mdns_service(mdns, "_smb", "_tcp");
+            query_mdns_service(mdns, "_ftp", "_tcp");
+            query_mdns_service(mdns, "_nfs", "_tcp");
+        }
+
+        ESP_LOGI(TAG, "Restarting in 10 seconds!");
+        vTaskDelay(10000 / portTICK_PERIOD_MS);
+        ESP_LOGI(TAG, "Starting again!");
+    }
+}
+
+void app_main()
+{
+    nvs_flash_init();
+    initialise_wifi();
+    xTaskCreate(&mdns_task, "mdns_task", 2048, NULL, 5, NULL);
+}