Webserver+3d print
Diff: cyclone_tcp/dns_sd/dns_sd.c
- Revision:
- 0:8918a71cdbe9
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cyclone_tcp/dns_sd/dns_sd.c Sat Feb 04 18:15:49 2017 +0000 @@ -0,0 +1,1998 @@ +/** + * @file dns_sd.c + * @brief DNS-SD (DNS-Based Service Discovery) + * + * @section License + * + * Copyright (C) 2010-2017 Oryx Embedded SARL. All rights reserved. + * + * This file is part of CycloneTCP Open. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * @section Description + * + * DNS-SD allows clients to discover a list of named instances of that + * desired service, using standard DNS queries. Refer to the following + * RFCs for complete details: + * - RFC 6763: DNS-Based Service Discovery + * - RFC 2782: A DNS RR for specifying the location of services (DNS SRV) + * + * @author Oryx Embedded SARL (www.oryx-embedded.com) + * @version 1.7.6 + **/ + +//Switch to the appropriate trace level +#define TRACE_LEVEL DNS_SD_TRACE_LEVEL + +//Dependencies +#include <stdlib.h> +#include <ctype.h> +#include "core/net.h" +#include "mdns/mdns_common.h" +#include "mdns/mdns_responder.h" +#include "dns/dns_debug.h" +#include "dns_sd/dns_sd.h" +#include "str.h" +#include "debug.h" + +//Check TCP/IP stack configuration +#if (DNS_SD_SUPPORT == ENABLED) + +//Tick counter to handle periodic operations +systime_t dnsSdTickCounter; + + +/** + * @brief Initialize settings with default values + * @param[out] settings Structure that contains DNS-SD settings + **/ + +void dnsSdGetDefaultSettings(DnsSdSettings *settings) +{ + //Use default interface + settings->interface = netGetDefaultInterface(); + + //Number of announcement packets + settings->numAnnouncements = MDNS_ANNOUNCE_NUM; + //TTL resource record + settings->ttl = DNS_SD_DEFAULT_RR_TTL; + //FSM state change event + settings->stateChangeEvent = NULL; +} + + +/** + * @brief DNS-DS initialization + * @param[in] context Pointer to the DNS-SD context + * @param[in] settings DNS-SD specific settings + * @return Error code + **/ + +error_t dnsSdInit(DnsSdContext *context, const DnsSdSettings *settings) +{ + NetInterface *interface; + + //Debug message + TRACE_INFO("Initializing DNS-SD...\r\n"); + + //Ensure the parameters are valid + if(context == NULL || settings == NULL) + return ERROR_INVALID_PARAMETER; + + //Invalid network interface? + if(settings->interface == NULL) + return ERROR_INVALID_PARAMETER; + + //Point to the underlying network interface + interface = settings->interface; + + //Clear the DNS-SD context + memset(context, 0, sizeof(DnsSdContext)); + //Save user settings + context->settings = *settings; + + //DNS-SD is currently suspended + context->running = FALSE; + //Initialize state machine + context->state = MDNS_STATE_INIT; + + //Attach the DNS-SD context to the network interface + interface->dnsSdContext = context; + + //Successful initialization + return NO_ERROR; +} + + +/** + * @brief Start mDNS responder + * @param[in] context Pointer to the DNS-SD context + * @return Error code + **/ + +error_t dnsSdStart(DnsSdContext *context) +{ + //Check parameter + if(context == NULL) + return ERROR_INVALID_PARAMETER; + + //Debug message + TRACE_INFO("Starting DNS-SD...\r\n"); + + //Get exclusive access + osAcquireMutex(&netMutex); + + //Start DNS-SD + context->running = TRUE; + //Initialize state machine + context->state = MDNS_STATE_INIT; + + //Release exclusive access + osReleaseMutex(&netMutex); + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Stop mDNS responder + * @param[in] context Pointer to the DNS-SD context + * @return Error code + **/ + +error_t dnsSdStop(DnsSdContext *context) +{ + //Check parameter + if(context == NULL) + return ERROR_INVALID_PARAMETER; + + //Debug message + TRACE_INFO("Stopping DNS-SD...\r\n"); + + //Get exclusive access + osAcquireMutex(&netMutex); + + //Suspend DNS-SD + context->running = FALSE; + //Reinitialize state machine + context->state = MDNS_STATE_INIT; + + //Release exclusive access + osReleaseMutex(&netMutex); + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Retrieve current state + * @param[in] context Pointer to the DNS-SD context + * @return Current DNS-SD state + **/ + +MdnsState dnsSdGetState(DnsSdContext *context) +{ + MdnsState state; + + //Get exclusive access + osAcquireMutex(&netMutex); + //Get current state + state = context->state; + //Release exclusive access + osReleaseMutex(&netMutex); + + //Return current state + return state; +} + + +/** + * @brief Set service instance name + * @param[in] context Pointer to the DNS-SD context + * @param[in] instanceName NULL-terminated string that contains the service + * instance name + * @return Error code + **/ + +error_t dnsSdSetInstanceName(DnsSdContext *context, const char_t *instanceName) +{ + NetInterface *interface; + + //Check parameters + if(context == NULL || instanceName == NULL) + return ERROR_INVALID_PARAMETER; + + //Get exclusive access + osAcquireMutex(&netMutex); + + //Point to the underlying network interface + interface = context->settings.interface; + + //Any registered services? + if(dnsSdGetNumServices(context) > 0) + { + //Check whether the link is up + if(interface->linkState) + { + //Send a goodbye packet + dnsSdSendGoodbye(context, NULL); + } + } + + //Set instance name + strSafeCopy(context->instanceName, instanceName, + DNS_SD_MAX_INSTANCE_NAME_LEN); + + //Restart probing process + dnsSdStartProbing(context); + + //Release exclusive access + osReleaseMutex(&netMutex); + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Register a DNS-SD service + * @param[in] context Pointer to the DNS-SD context + * @param[in] serviceName NULL-terminated string that contains the name of the + * service to be registered + * @param[in] priority Priority field + * @param[in] weight Weight field + * @param[in] port Port number + * @param[in] metadata NULL-terminated string that contains the discovery-time + * metadata (TXT record) + * @return Error code + **/ + +error_t dnsSdRegisterService(DnsSdContext *context, const char_t *serviceName, + uint16_t priority, uint16_t weight, uint16_t port, const char_t *metadata) +{ + error_t error; + size_t i; + size_t j; + size_t k; + size_t n; + DnsSdService *entry; + DnsSdService *firstFreeEntry; + + //Check parameters + if(context == NULL || serviceName == NULL || metadata == NULL) + return ERROR_INVALID_PARAMETER; + + //Get exclusive access + osAcquireMutex(&netMutex); + + //Keep track of the first free entry + firstFreeEntry = NULL; + + //Loop through the list of registered services + for(i = 0; i < DNS_SD_SERVICE_LIST_SIZE; i++) + { + //Point to the current entry + entry = &context->serviceList[i]; + + //Check if the entry is currently in use + if(entry->name[0] != '\0') + { + //Check whether the specified service is already registered + if(!strcasecmp(entry->name, serviceName)) + break; + } + else + { + //Keep track of the first free entry + if(firstFreeEntry == NULL) + firstFreeEntry = entry; + } + } + + //If the specified service is not yet registered, then a new + //entry should be created + if(i >= DNS_SD_SERVICE_LIST_SIZE) + entry = firstFreeEntry; + + //Check whether the service list runs out of space + if(entry != NULL) + { + //Service name + strSafeCopy(entry->name, serviceName, DNS_SD_MAX_SERVICE_NAME_LEN); + + //Priority field + entry->priority = priority; + //Weight field + entry->weight = weight; + //Port number + entry->port = port; + + //Clear TXT record + entry->metadataLength = 0; + + //Point to the beginning of the information string + i = 0; + j = 0; + + //Point to the beginning of the resulting TXT record data + k = 0; + + //Format TXT record + while(1) + { + //End of text data? + if(metadata[i] == '\0' || metadata[i] == ';') + { + //Calculate the length of the text data + n = MIN(i - j, UINT8_MAX); + + //Check the length of the resulting TXT record + if((entry->metadataLength + n + 1) > DNS_SD_MAX_METADATA_LEN) + break; + + //Write length field + entry->metadata[k] = n; + //Write text data + memcpy(entry->metadata + k + 1, metadata + j, n); + + //Jump to the next text data + j = i + 1; + //Advance write index + k += n + 1; + + //Update the length of the TXT record + entry->metadataLength += n + 1; + + //End of string detected? + if(metadata[i] == '\0') + break; + } + + //Advance read index + i++; + } + + //Empty TXT record? + if(!entry->metadataLength) + { + //An empty TXT record shall contain a single zero byte + entry->metadata[0] = 0; + entry->metadataLength = 1; + } + + //Restart probing process + dnsSdStartProbing(context); + + //Successful processing + error = NO_ERROR; + } + else + { + //The service list is full + error = ERROR_FAILURE; + } + + //Release exclusive access + osReleaseMutex(&netMutex); + + //Return error code + return error; +} + + +/** + * @brief Unregister a DNS-SD service + * @param[in] context Pointer to the DNS-SD context + * @param[in] serviceName NULL-terminated string that contains the name of the + * service to be unregistered + * @return Error code + **/ + +error_t dnsSdUnregisterService(DnsSdContext *context, const char_t *serviceName) +{ + uint_t i; + DnsSdService *entry; + + //Check parameters + if(context == NULL || serviceName == NULL) + return ERROR_INVALID_PARAMETER; + + //Get exclusive access + osAcquireMutex(&netMutex); + + //Loop through the list of registered services + for(i = 0; i < DNS_SD_SERVICE_LIST_SIZE; i++) + { + //Point to the current entry + entry = &context->serviceList[i]; + + //Service name found? + if(!strcasecmp(entry->name, serviceName)) + { + //Send a goodbye packet + dnsSdSendGoodbye(context, entry); + //Remove the service from the list + entry->name[0] = '\0'; + } + } + + //Release exclusive access + osReleaseMutex(&netMutex); + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Get the number of registered services + * @param[in] context Pointer to the DNS-SD context + * @return Number of registered services + **/ + +uint_t dnsSdGetNumServices(DnsSdContext *context) +{ + uint_t i; + uint_t n; + + //Number of registered services + n = 0; + + //Check parameter + if(context != NULL) + { + //Valid instance name? + if(context->instanceName[0] != '\0') + { + //Loop through the list of registered services + for(i = 0; i < DNS_SD_SERVICE_LIST_SIZE; i++) + { + //Check if the entry is currently in use + if(context->serviceList[i].name[0] != '\0') + n++; + } + } + } + + //Return the number of registered services + return n; +} + + +/** + * @brief Restart probing process + * @param[in] context Pointer to the DNS-SD context + * @return Error code + **/ + +error_t dnsSdStartProbing(DnsSdContext *context) +{ + //Check parameter + if(context == NULL) + return ERROR_INVALID_PARAMETER; + + //Force DNS-SD to start probing again + context->state = MDNS_STATE_INIT; + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief DNS-SD responder timer handler + * + * This routine must be periodically called by the TCP/IP stack to + * manage DNS-SD operation + * + * @param[in] context Pointer to the DNS-SD context + **/ + +void dnsSdTick(DnsSdContext *context) +{ + systime_t time; + systime_t delay; + NetInterface *interface; + MdnsState state; + + //Make sure DNS-SD has been properly instantiated + if(context == NULL) + return; + + //Point to the underlying network interface + interface = context->settings.interface; + + //Get current time + time = osGetSystemTime(); + + //Check current state + if(context->state == MDNS_STATE_INIT) + { + //Check whether the mDNS responder has been properly instantiated + if(interface->mdnsResponderContext != NULL) + state = interface->mdnsResponderContext->state; + else + state = MDNS_STATE_INIT; + + //Wait for mDNS probing to complete + if(state == MDNS_STATE_ANNOUNCING || state == MDNS_STATE_IDLE) + { + //Any registered services? + if(dnsSdGetNumServices(context) > 0) + { + //Initial random delay + delay = netGetRandRange(MDNS_RAND_DELAY_MIN, MDNS_RAND_DELAY_MAX); + //Perform probing + dnsSdChangeState(context, MDNS_STATE_PROBING, delay); + } + } + } + else if(context->state == MDNS_STATE_PROBING) + { + //Probing failed? + if(context->conflict && context->retransmitCount > 0) + { + //Programmatically change the service instance name + dnsSdChangeInstanceName(context); + //Probe again, and repeat as necessary until a unique name is found + dnsSdChangeState(context, MDNS_STATE_PROBING, 0); + } + //Tie-break lost? + else if(context->tieBreakLost && context->retransmitCount > 0) + { + //The host defers to the winning host by waiting one second, and + //then begins probing for this record again + dnsSdChangeState(context, MDNS_STATE_PROBING, MDNS_PROBE_DEFER); + } + else + { + //Check current time + if(timeCompare(time, context->timestamp + context->timeout) >= 0) + { + //Probing is on-going? + if(context->retransmitCount < MDNS_PROBE_NUM) + { + //First probe? + if(context->retransmitCount == 0) + { + //Apparently conflicting mDNS responses received before the + //first probe packet is sent must be silently ignored + context->conflict = FALSE; + context->tieBreakLost = FALSE; + } + + //Send probe packet + dnsSdSendProbe(context); + + //Save the time at which the packet was sent + context->timestamp = time; + //Time interval between subsequent probe packets + context->timeout = MDNS_PROBE_DELAY; + //Increment retransmission counter + context->retransmitCount++; + } + //Probing is complete? + else + { + //The mDNS responder must send unsolicited mDNS responses + //containing all of its newly registered resource records + if(context->settings.numAnnouncements > 0) + dnsSdChangeState(context, MDNS_STATE_ANNOUNCING, 0); + else + dnsSdChangeState(context, MDNS_STATE_IDLE, 0); + } + } + } + } + else if(context->state == MDNS_STATE_ANNOUNCING) + { + //Whenever a mDNS responder receives any mDNS response (solicited or + //otherwise) containing a conflicting resource record, the conflict + //must be resolved + if(context->conflict) + { + //Probe again, and repeat as necessary until a unique name is found + dnsSdChangeState(context, MDNS_STATE_PROBING, 0); + } + else + { + //Check current time + if(timeCompare(time, context->timestamp + context->timeout) >= 0) + { + //Send announcement packet + dnsSdSendAnnouncement(context); + + //Save the time at which the packet was sent + context->timestamp = time; + //Increment retransmission counter + context->retransmitCount++; + + //First announcement packet? + if(context->retransmitCount == 1) + { + //The mDNS responder must send at least two unsolicited + //responses, one second apart + context->timeout = MDNS_ANNOUNCE_DELAY; + } + else + { + //To provide increased robustness against packet loss, a mDNS + //responder may send up to eight unsolicited responses, provided + //that the interval between unsolicited responses increases by + //at least a factor of two with every response sent + context->timeout *= 2; + } + + //Last announcement packet? + if(context->retransmitCount >= context->settings.numAnnouncements) + { + //A mDNS responder must not send regular periodic announcements + dnsSdChangeState(context, MDNS_STATE_IDLE, 0); + } + } + } + } + else if(context->state == MDNS_STATE_IDLE) + { + //Whenever a mDNS responder receives any mDNS response (solicited or + //otherwise) containing a conflicting resource record, the conflict + //must be resolved + if(context->conflict) + { + //Probe again, and repeat as necessary until a unique name is found + dnsSdChangeState(context, MDNS_STATE_PROBING, 0); + } + } +} + + +/** + * @brief Callback function for link change event + * @param[in] context Pointer to the DNS-SD context + **/ + +void dnsSdLinkChangeEvent(DnsSdContext *context) +{ + //Make sure DNS-SD has been properly instantiated + if(context == NULL) + return; + + //Whenever a mDNS responder receives an indication of a link + //change event, it must perform probing and announcing + dnsSdChangeState(context, MDNS_STATE_INIT, 0); +} + + +/** + * @brief Update FSM state + * @param[in] context Pointer to the DNS-SD context + * @param[in] newState New state to switch to + * @param[in] delay Initial delay + **/ + +void dnsSdChangeState(DnsSdContext *context, + MdnsState newState, systime_t delay) +{ + NetInterface *interface; + + //Point to the underlying network interface + interface = context->settings.interface; + + //Set time stamp + context->timestamp = osGetSystemTime(); + //Set initial delay + context->timeout = delay; + //Reset retransmission counter + context->retransmitCount = 0; + //Switch to the new state + context->state = newState; + + //Any registered callback? + if(context->settings.stateChangeEvent != NULL) + { + //Release exclusive access + osReleaseMutex(&netMutex); + //Invoke user callback function + context->settings.stateChangeEvent(context, interface, newState); + //Get exclusive access + osAcquireMutex(&netMutex); + } +} + + +/** + * @brief Programmatically change the service instance name + * @param[in] context Pointer to the DNS-SD context + **/ + +void dnsSdChangeInstanceName(DnsSdContext *context) +{ + size_t i; + size_t m; + size_t n; + uint32_t index; + char_t s[16]; + + //Retrieve the length of the string + n = strlen(context->instanceName); + + //Parse the string backwards + for(i = n; i > 0; i--) + { + //Last character? + if(i == n) + { + //Check whether the last character is a bracket + if(context->instanceName[i - 1] != ')') + break; + } + else + { + //Check whether the current character is a digit + if(!isdigit((uint8_t) context->instanceName[i - 1])) + break; + } + } + + //Any number following the service instance name? + if(context->instanceName[i] != '\0') + { + //Retrieve the number at the end of the name + index = atoi(context->instanceName + i); + //Increment the value + index++; + + //Check the length of the name + if(i >= 2) + { + //Discard any space and bracket that may precede the number + if(context->instanceName[i - 2] == ' ' && + context->instanceName[i - 1] == '(') + { + i -= 2; + } + } + + //Strip the digits + context->instanceName[i] = '\0'; + } + else + { + //Append the digit "2" to the name + index = 2; + } + + //Convert the number to a string of characters + m = sprintf(s, " (%" PRIu32 ")", index); + + //Sanity check + if((i + m) <= DNS_SD_MAX_INSTANCE_NAME_LEN) + { + //Programmatically change the service instance name + strcat(context->instanceName, s); + } +} + + +/** + * @brief Send probe packet + * @param[in] context Pointer to the DNS-SD context + * @return Error code + **/ + +error_t dnsSdSendProbe(DnsSdContext *context) +{ + error_t error; + uint_t i; + NetInterface *interface; + DnsQuestion *dnsQuestion; + DnsSdService *service; + MdnsMessage message; + + //Point to the underlying network interface + interface = context->settings.interface; + + //Create an empty mDNS query message + error = mdnsCreateMessage(&message, FALSE); + //Any error to report? + if(error) + return error; + + //Start of exception handling block + do + { + //For all those resource records that a mDNS responder desires to be + //unique on the local link, it must send a mDNS query asking for those + //resource records, to see if any of them are already in use + if(dnsSdGetNumServices(context) > 0) + { + //Loop through the list of registered services + for(i = 0; i < DNS_SD_SERVICE_LIST_SIZE; i++) + { + //Point to the current entry + service = &context->serviceList[i]; + + //Valid service? + if(service->name[0] != '\0') + { + //Encode the service name using DNS notation + message.length += mdnsEncodeName(context->instanceName, service->name, + ".local", (uint8_t *) message.dnsHeader + message.length); + + //Point to the corresponding question structure + dnsQuestion = DNS_GET_QUESTION(message.dnsHeader, message.length); + + //The probes should be sent as QU questions with the unicast-response + //bit set, to allow a defending host to respond immediately via unicast + dnsQuestion->qtype = HTONS(DNS_RR_TYPE_ANY); + dnsQuestion->qclass = HTONS(MDNS_QCLASS_QU | DNS_RR_CLASS_IN); + + //Update the length of the mDNS query message + message.length += sizeof(DnsQuestion); + + //Number of questions in the Question Section + message.dnsHeader->qdcount++; + } + } + } + + //A probe query can be distinguished from a normal query by the fact that + //a probe query contains a proposed record in the Authority Section that + //answers the question in the Question Section + if(dnsSdGetNumServices(context) > 0) + { + //Loop through the list of registered services + for(i = 0; i < DNS_SD_SERVICE_LIST_SIZE; i++) + { + //Point to the current entry + service = &context->serviceList[i]; + + //Valid service? + if(service->name[0] != '\0') + { + //Format SRV resource record + error = dnsSdAddSrvRecord(interface, &message, + service, FALSE, DNS_SD_DEFAULT_RR_TTL); + //Any error to report? + if(error) + break; + + //Format TXT resource record + error = dnsSdAddTxtRecord(interface, &message, + service, FALSE, DNS_SD_DEFAULT_RR_TTL); + //Any error to report? + if(error) + break; + } + } + } + + //Propagate exception if necessary + if(error) + break; + + //Number of resource records in the Authority Section + message.dnsHeader->nscount = message.dnsHeader->ancount; + //Number of resource records in the Answer Section + message.dnsHeader->ancount = 0; + + //Send mDNS message + error = mdnsSendMessage(interface, &message, NULL, MDNS_PORT); + + //End of exception handling block + } while(0); + + //Free previously allocated memory + mdnsDeleteMessage(&message); + + //Return status code + return error; +} + + +/** + * @brief Send announcement packet + * @param[in] context Pointer to the DNS-SD context + * @return Error code + **/ + +error_t dnsSdSendAnnouncement(DnsSdContext *context) +{ + error_t error; + uint_t i; + NetInterface *interface; + DnsSdService *service; + MdnsMessage message; + + //Point to the underlying network interface + interface = context->settings.interface; + + //Create an empty mDNS response message + error = mdnsCreateMessage(&message, TRUE); + //Any error to report? + if(error) + return error; + + //Start of exception handling block + do + { + //Send an unsolicited mDNS response containing, in the Answer Section, + //all of its newly registered resource records + if(dnsSdGetNumServices(context) > 0) + { + //Loop through the list of registered services + for(i = 0; i < DNS_SD_SERVICE_LIST_SIZE; i++) + { + //Point to the current entry + service = &context->serviceList[i]; + + //Valid service? + if(service->name[0] != '\0') + { + //Format PTR resource record (service type enumeration) + error = dnsSdAddServiceEnumPtrRecord(interface, + &message, service, DNS_SD_DEFAULT_RR_TTL); + //Any error to report? + if(error) + break; + + //Format PTR resource record + error = dnsSdAddPtrRecord(interface, &message, + service, DNS_SD_DEFAULT_RR_TTL); + //Any error to report? + if(error) + break; + + //Format SRV resource record + error = dnsSdAddSrvRecord(interface, &message, + service, TRUE, DNS_SD_DEFAULT_RR_TTL); + //Any error to report? + if(error) + break; + + //Format TXT resource record + error = dnsSdAddTxtRecord(interface, &message, + service, TRUE, DNS_SD_DEFAULT_RR_TTL); + //Any error to report? + if(error) + break; + } + } + } + + //Propagate exception if necessary + if(error) + break; + + //Send mDNS message + error = mdnsSendMessage(interface, &message, NULL, MDNS_PORT); + + //End of exception handling block + } while(0); + + //Free previously allocated memory + mdnsDeleteMessage(&message); + + //Return status code + return error; +} + + +/** + * @brief Send goodbye packet + * @param[in] context Pointer to the DNS-SD context + * @param[in] service Pointer to a DNS-SD service + * @return Error code + **/ + +error_t dnsSdSendGoodbye(DnsSdContext *context, const DnsSdService *service) +{ + error_t error; + uint_t i; + NetInterface *interface; + DnsSdService *entry; + MdnsMessage message; + + //Point to the underlying network interface + interface = context->settings.interface; + + //Create an empty mDNS response message + error = mdnsCreateMessage(&message, TRUE); + //Any error to report? + if(error) + return error; + + //Loop through the list of registered services + for(i = 0; i < DNS_SD_SERVICE_LIST_SIZE; i++) + { + //Point to the current entry + entry = &context->serviceList[i]; + + //Valid service? + if(entry->name[0] != '\0') + { + if(service == entry || service == NULL) + { + //Format PTR resource record (service type enumeration) + error = dnsSdAddServiceEnumPtrRecord(interface, &message, entry, 0); + //Any error to report? + if(error) + break; + + //Format PTR resource record + error = dnsSdAddPtrRecord(interface, &message, entry, 0); + //Any error to report? + if(error) + break; + + //Format SRV resource record + error = dnsSdAddSrvRecord(interface, &message, entry, TRUE, 0); + //Any error to report? + if(error) + break; + + //Format TXT resource record + error = dnsSdAddTxtRecord(interface, &message, entry, TRUE, 0); + //Any error to report? + if(error) + break; + } + } + } + + //Check status code + if(!error) + { + //Send mDNS message + error = mdnsSendMessage(interface, &message, NULL, MDNS_PORT); + } + + //Free previously allocated memory + mdnsDeleteMessage(&message); + + //Return status code + return error; +} + + +/** + * @brief Parse a question + * @param[in] interface Underlying network interface + * @param[in] query Incoming mDNS query message + * @param[in] offset Offset to first byte of the question + * @param[in] question Pointer to the question + * @param[in,out] response mDNS response message + * @return Error code + **/ + +error_t dnsSdParseQuestion(NetInterface *interface, const MdnsMessage *query, + size_t offset, const DnsQuestion *question, MdnsMessage *response) +{ + error_t error; + uint_t i; + uint16_t qclass; + uint16_t qtype; + uint32_t ttl; + bool_t cacheFlush; + DnsSdContext *context; + DnsSdService *service; + + //Point to the DNS-SD context + context = interface->dnsSdContext; + //Make sure DNS-SD has been properly instantiated + if(context == NULL) + return NO_ERROR; + + //Check the state of the mDNS responder + if(context->state != MDNS_STATE_ANNOUNCING && + context->state != MDNS_STATE_IDLE) + { + //Do not respond to mDNS queries during probing + return NO_ERROR; + } + + //Convert the query class to host byte order + qclass = ntohs(question->qclass); + //Discard QU flag + qclass &= ~MDNS_QCLASS_QU; + + //Convert the query type to host byte order + qtype = ntohs(question->qtype); + + //Get the TTL resource record + ttl = context->settings.ttl; + + //Check whether the querier originating the query is a simple resolver + if(ntohs(query->udpHeader->srcPort) != MDNS_PORT) + { + //The resource record TTL given in a legacy unicast response should + //not be greater than ten seconds, even if the true TTL of the mDNS + //resource record is higher + ttl = MIN(ttl, MDNS_LEGACY_UNICAST_RR_TTL); + + //The cache-flush bit must not be set in legacy unicast responses + cacheFlush = FALSE; + } + else + { + //The cache-bit should be set for unique resource records + cacheFlush = TRUE; + } + + //Any registered services? + if(dnsSdGetNumServices(context) > 0) + { + //Loop through the list of registered services + for(i = 0; i < DNS_SD_SERVICE_LIST_SIZE; i++) + { + //Point to the current entry + service = &context->serviceList[i]; + + //Valid service? + if(service->name[0] != '\0') + { + //Check the class of the query + if(qclass == DNS_RR_CLASS_IN || qclass == DNS_RR_CLASS_ANY) + { + //Compare service name + if(!mdnsCompareName(query->dnsHeader, query->length, + offset, "", "_services._dns-sd._udp", ".local", 0)) + { + //PTR query? + if(qtype == DNS_RR_TYPE_PTR || qtype == DNS_RR_TYPE_ANY) + { + //Format PTR resource record (service type enumeration) + error = dnsSdAddServiceEnumPtrRecord(interface, + response, service, ttl); + //Any error to report? + if(error) + return error; + + //Update the number of shared resource records + response->sharedRecordCount++; + } + } + else if(!mdnsCompareName(query->dnsHeader, query->length, + offset, "", service->name, ".local", 0)) + { + //PTR query? + if(qtype == DNS_RR_TYPE_PTR || qtype == DNS_RR_TYPE_ANY) + { + //Format PTR resource record + error = dnsSdAddPtrRecord(interface, response, + service, ttl); + //Any error to report? + if(error) + return error; + + //Update the number of shared resource records + response->sharedRecordCount++; + } + } + else if(!mdnsCompareName(query->dnsHeader, query->length, offset, + context->instanceName, service->name, ".local", 0)) + { + //SRV query? + if(qtype == DNS_RR_TYPE_SRV || qtype == DNS_RR_TYPE_ANY) + { + //Format SRV resource record + error = dnsSdAddSrvRecord(interface, response, + service, cacheFlush, ttl); + //Any error to report? + if(error) + return error; + } + + //TXT query? + if(qtype == DNS_RR_TYPE_TXT || qtype == DNS_RR_TYPE_ANY) + { + //Format TXT resource record + error = dnsSdAddTxtRecord(interface, response, + service, cacheFlush, ttl); + //Any error to report? + if(error) + return error; + } + + //NSEC query? + if(qtype != DNS_RR_TYPE_SRV && qtype != DNS_RR_TYPE_TXT) + { + //Format NSEC resource record + error = dnsSdAddNsecRecord(interface, response, + service, cacheFlush, ttl); + //Any error to report? + if(error) + return error; + } + } + } + } + } + } + + //Successful processing + return NO_ERROR; +} + + + +/** + * @brief Parse a resource record from the Authority Section + * @param[in] interface Underlying network interface + * @param[in] query Incoming mDNS query message + * @param[in] offset Offset to first byte of the resource record + * @param[in] record Pointer to the resource record + **/ + +void dnsSdParseNsRecord(NetInterface *interface, const MdnsMessage *query, + size_t offset, const DnsResourceRecord *record) +{ + uint_t i; + uint16_t rclass; + DnsSdContext *context; + DnsSdService *service; + DnsSrvResourceRecord *srvRecord; + + //Point to the DNS-SD context + context = interface->dnsSdContext; + //Make sure DNS-SD has been properly instantiated + if(context == NULL) + return; + + //Any services registered? + if(dnsSdGetNumServices(context) > 0) + { + //Loop through the list of registered services + for(i = 0; i < DNS_SD_SERVICE_LIST_SIZE; i++) + { + //Point to the current entry + service = &context->serviceList[i]; + + //Valid service? + if(service->name[0] != '\0') + { + //Apply tie-breaking rules + if(!mdnsCompareName(query->dnsHeader, query->length, offset, + context->instanceName, service->name, ".local", 0)) + { + //Convert the class to host byte order + rclass = ntohs(record->rclass); + //Discard Cache Flush flag + rclass &= ~MDNS_RCLASS_CACHE_FLUSH; + + //Check the class of the resource record + if(rclass == DNS_RR_CLASS_IN) + { + //SRV resource record found? + if(ntohs(record->rtype) == DNS_RR_TYPE_SRV) + { + //Cast resource record + srvRecord = (DnsSrvResourceRecord *) record; + + //Compare Priority fields + if(ntohs(srvRecord->priority) > service->priority) + { + context->tieBreakLost = TRUE; + } + else if(ntohs(srvRecord->priority) == service->priority) + { + //Compare Weight fields + if(ntohs(srvRecord->weight) > service->weight) + { + context->tieBreakLost = TRUE; + } + else if(ntohs(srvRecord->weight) == service->weight) + { + //Compare Port fields + if(ntohs(srvRecord->port) > service->port) + { + context->tieBreakLost = TRUE; + } + else if(ntohs(srvRecord->port) == service->port) + { + //Compute the offset of the first byte of the target + offset = srvRecord->target - (uint8_t *) query->dnsHeader; + + if(mdnsCompareName(query->dnsHeader, query->length, offset, + context->instanceName, "", ".local", 0) > 0) + { + //The host has lost the tie-break + context->tieBreakLost = TRUE; + } + } + } + } + } + } + } + } + } + } +} + + +/** + * @brief Parse a resource record from the Answer Section + * @param[in] interface Underlying network interface + * @param[in] response Incoming mDNS response message + * @param[in] offset Offset to first byte of the resource record to be checked + * @param[in] record Pointer to the resource record + **/ + +void dnsSdParseAnRecord(NetInterface *interface, const MdnsMessage *response, + size_t offset, const DnsResourceRecord *record) +{ + uint_t i; + uint16_t rclass; + DnsSdContext *context; + DnsSdService *service; + + //Point to the DNS-SD context + context = interface->dnsSdContext; + //Make sure DNS-SD has been properly instantiated + if(context == NULL) + return; + + //Any services registered? + if(dnsSdGetNumServices(context) > 0) + { + //Loop through the list of registered services + for(i = 0; i < DNS_SD_SERVICE_LIST_SIZE; i++) + { + //Point to the current entry + service = &context->serviceList[i]; + + //Valid service? + if(service->name[0] != '\0') + { + //Check for conflicts + if(!mdnsCompareName(response->dnsHeader, response->length, offset, + context->instanceName, service->name, ".local", 0)) + { + //Convert the class to host byte order + rclass = ntohs(record->rclass); + //Discard Cache Flush flag + rclass &= ~MDNS_RCLASS_CACHE_FLUSH; + + //Check the class of the resource record + if(rclass == DNS_RR_CLASS_IN) + { + //SRV resource record found? + if(ntohs(record->rtype) == DNS_RR_TYPE_SRV) + { + //Compute the offset of the first byte of the rdata + offset = record->rdata - (uint8_t *) response->dnsHeader; + + //A conflict occurs when a mDNS responder has a unique record for + //which it is currently authoritative, and it receives a mDNS + //response message containing a record with the same name, rrtype + //and rrclass, but inconsistent rdata + if(mdnsCompareName(response->dnsHeader, response->length, offset, + context->instanceName, "", ".local", 0)) + { + //The service instance name is already in use by some other host + context->conflict = TRUE; + } + } + } + } + } + } + } +} + + +/** + * @brief Additional record generation + * @param[in] interface Underlying network interface + * @param[in,out] response mDNS response message + * @param[in] legacyUnicast This flag is set for legacy unicast responses + **/ + +void dnsSdGenerateAdditionalRecords(NetInterface *interface, + MdnsMessage *response, bool_t legacyUnicast) +{ + error_t error; + uint_t i; + uint_t j; + size_t n; + size_t offset; + uint_t ancount; + uint16_t rclass; + uint32_t ttl; + bool_t cacheFlush; + DnsSdContext *context; + DnsSdService *service; + DnsResourceRecord *record; + + //Point to the DNS-SD context + context = interface->dnsSdContext; + //Make sure DNS-SD has been properly instantiated + if(context == NULL) + return; + + //No registered services? + if(dnsSdGetNumServices(context) == 0) + return; + + //mDNS responses must not contain any questions in the Question Section + if(response->dnsHeader->qdcount != 0) + return; + + //Get the TTL resource record + ttl = context->settings.ttl; + + //Check whether the querier originating the query is a simple resolver + if(legacyUnicast) + { + //The resource record TTL given in a legacy unicast response should + //not be greater than ten seconds, even if the true TTL of the mDNS + //resource record is higher + ttl = MIN(ttl, MDNS_LEGACY_UNICAST_RR_TTL); + + //The cache-flush bit must not be set in legacy unicast responses + cacheFlush = FALSE; + } + else + { + //The cache-bit should be set for unique resource records + cacheFlush = TRUE; + } + + //Point to the first resource record + offset = sizeof(DnsHeader); + + //Save the number of resource records in the Answer Section + ancount = response->dnsHeader->ancount; + + //Parse the Answer Section + for(i = 0; i < ancount; i++) + { + //Parse resource record name + n = dnsParseName(response->dnsHeader, response->length, offset, NULL, 0); + //Invalid name? + if(!n) + break; + + //Point to the associated resource record + record = DNS_GET_RESOURCE_RECORD(response->dnsHeader, n); + //Point to the resource data + n += sizeof(DnsResourceRecord); + + //Make sure the resource record is valid + if(n > response->length) + break; + if((n + ntohs(record->rdlength)) > response->length) + break; + + //Convert the record class to host byte order + rclass = ntohs(record->rclass); + //Discard the cache-flush bit + rclass &= ~MDNS_RCLASS_CACHE_FLUSH; + + //Loop through the list of registered services + for(j = 0; j < DNS_SD_SERVICE_LIST_SIZE; j++) + { + //Point to the current entry + service = &context->serviceList[j]; + + //Valid service? + if(service->name[0] != '\0') + { + //Check the class of the resource record + if(rclass == DNS_RR_CLASS_IN) + { + //PTR record? + if(ntohs(record->rtype) == DNS_RR_TYPE_PTR) + { + //Compare service name + if(!mdnsCompareName(response->dnsHeader, response->length, + offset, "", service->name, ".local", 0)) + { + //Format SRV resource record + error = dnsSdAddSrvRecord(interface, + response, service, cacheFlush, ttl); + //Any error to report? + if(error) + return; + + //Format TXT resource record + error = dnsSdAddTxtRecord(interface, + response, service, cacheFlush, ttl); + //Any error to report? + if(error) + return; + } + } + //SRV record? + else if(ntohs(record->rtype) == DNS_RR_TYPE_SRV) + { + //Compare service name + if(!mdnsCompareName(response->dnsHeader, response->length, + offset, context->instanceName, service->name, ".local", 0)) + { + //Format TXT resource record + error = dnsSdAddTxtRecord(interface, + response, service, cacheFlush, ttl); + //Any error to report? + if(error) + return; + } + } + } + } + } + + //Point to the next resource record + offset = n + ntohs(record->rdlength); + } + + //Number of resource records in the Additional Section + response->dnsHeader->arcount += response->dnsHeader->ancount - ancount; + //Number of resource records in the Answer Section + response->dnsHeader->ancount = ancount; +} + + +/** + * @brief Add PTR record to a mDNS message (in response to a meta-query) + * @param[in] interface Underlying network interface + * @param[in,out] message Pointer to the mDNS message + * @param[in] service Pointer to a DNS-SD service + * @param[in] ttl Resource record TTL (cache lifetime) + * @return Error code + **/ + +error_t dnsSdAddServiceEnumPtrRecord(NetInterface *interface, + MdnsMessage *message, const DnsSdService *service, uint32_t ttl) +{ + size_t n; + size_t offset; + DnsResourceRecord *record; + + //Set the position to the end of the buffer + offset = message->length; + + //The first pass calculates the length of the DNS encoded service name + n = mdnsEncodeName("", "_services._dns-sd._udp", ".local", NULL); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //The second pass encodes the service name using the DNS name notation + offset += mdnsEncodeName("", "_services._dns-sd._udp", + ".local", (uint8_t *) message->dnsHeader + offset); + + //Consider the length of the resource record itself + if((offset + sizeof(DnsResourceRecord)) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //Point to the corresponding resource record + record = DNS_GET_RESOURCE_RECORD(message->dnsHeader, offset); + + //Fill in resource record + record->rtype = HTONS(DNS_RR_TYPE_PTR); + record->rclass = HTONS(DNS_RR_CLASS_IN); + record->ttl = htonl(ttl); + + //Advance write index + offset += sizeof(DnsResourceRecord); + + //The first pass calculates the length of the DNS encoded service name + n = mdnsEncodeName("", service->name, ".local", NULL); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //The second pass encodes the service name using DNS notation + n = mdnsEncodeName("", service->name, + ".local", record->rdata); + + //Convert length field to network byte order + record->rdlength = htons(n); + + //Number of resource records in the answer section + message->dnsHeader->ancount++; + //Update the length of the DNS message + message->length = offset + n; + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Add PTR record to a mDNS message + * @param[in] interface Underlying network interface + * @param[in,out] message Pointer to the mDNS message + * @param[in] service Pointer to a DNS-SD service + * @param[in] ttl Resource record TTL (cache lifetime) + * @return Error code + **/ + +error_t dnsSdAddPtrRecord(NetInterface *interface, + MdnsMessage *message, const DnsSdService *service, uint32_t ttl) +{ + size_t n; + size_t offset; + bool_t duplicate; + DnsSdContext *context; + DnsResourceRecord *record; + + //Point to the DNS-SD context + context = interface->dnsSdContext; + + //Check whether the resource record is already present in the Answer + //Section of the message + duplicate = mdnsCheckDuplicateRecord(message, "", + service->name, ".local", DNS_RR_TYPE_PTR); + + //The duplicates should be suppressed and the resource record should + //appear only once in the list + if(!duplicate) + { + //Set the position to the end of the buffer + offset = message->length; + + //The first pass calculates the length of the DNS encoded service name + n = mdnsEncodeName("", service->name, ".local", NULL); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //Encode the service name using the DNS name notation + offset += mdnsEncodeName("", service->name, + ".local", (uint8_t *) message->dnsHeader + offset); + + //Consider the length of the resource record itself + if((offset + sizeof(DnsResourceRecord)) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //Point to the corresponding resource record + record = DNS_GET_RESOURCE_RECORD(message->dnsHeader, offset); + + //Fill in resource record + record->rtype = HTONS(DNS_RR_TYPE_PTR); + record->rclass = HTONS(DNS_RR_CLASS_IN); + record->ttl = htonl(ttl); + + //Advance write index + offset += sizeof(DnsResourceRecord); + + //The first pass calculates the length of the DNS encoded instance name + n = mdnsEncodeName(context->instanceName, service->name, ".local", NULL); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //The second pass encodes the instance name using DNS notation + n = mdnsEncodeName(context->instanceName, + service->name, ".local", record->rdata); + + //Convert length field to network byte order + record->rdlength = htons(n); + + //Number of resource records in the answer section + message->dnsHeader->ancount++; + //Update the length of the DNS message + message->length = offset + n; + } + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Add SRV record to a mDNS message + * @param[in] interface Underlying network interface + * @param[in,out] message Pointer to the mDNS message + * @param[in] service Pointer to a DNS-SD service + * @param[in] cacheFlush Cache-flush bit + * @param[in] ttl Resource record TTL (cache lifetime) + * @return Error code + **/ + +error_t dnsSdAddSrvRecord(NetInterface *interface, MdnsMessage *message, + const DnsSdService *service, bool_t cacheFlush, uint32_t ttl) +{ + size_t n; + size_t offset; + bool_t duplicate; + MdnsResponderContext *mdnsResponderContext; + DnsSdContext *dnsSdContext; + DnsSrvResourceRecord *record; + + //Point to the mDNS responder context + mdnsResponderContext = interface->mdnsResponderContext; + //Point to the DNS-SD context + dnsSdContext = interface->dnsSdContext; + + //Check whether the resource record is already present in the Answer + //Section of the message + duplicate = mdnsCheckDuplicateRecord(message, dnsSdContext->instanceName, + service->name, ".local", DNS_RR_TYPE_SRV); + + //The duplicates should be suppressed and the resource record should + //appear only once in the list + if(!duplicate) + { + //Set the position to the end of the buffer + offset = message->length; + + //The first pass calculates the length of the DNS encoded instance name + n = mdnsEncodeName(dnsSdContext->instanceName, + service->name, ".local", NULL); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //The second pass encodes the instance name using DNS notation + offset += mdnsEncodeName(dnsSdContext->instanceName, + service->name, ".local", (uint8_t *) message->dnsHeader + offset); + + //Consider the length of the resource record itself + if((offset + sizeof(DnsSrvResourceRecord)) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //Point to the corresponding resource record + record = (DnsSrvResourceRecord *) DNS_GET_RESOURCE_RECORD(message->dnsHeader, offset); + + //Fill in resource record + record->rtype = HTONS(DNS_RR_TYPE_SRV); + record->rclass = HTONS(DNS_RR_CLASS_IN); + record->ttl = htonl(ttl); + record->priority = htons(service->priority); + record->weight = htons(service->weight); + record->port = htons(service->port); + + //Check whether the cache-flush bit should be set + if(cacheFlush) + record->rclass |= HTONS(MDNS_RCLASS_CACHE_FLUSH); + + //Advance write index + offset += sizeof(DnsSrvResourceRecord); + + //The first pass calculates the length of the DNS encoded target name + n = mdnsEncodeName("", mdnsResponderContext->hostname, + ".local", NULL); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //The second pass encodes the target name using DNS notation + n = mdnsEncodeName("", mdnsResponderContext->hostname, + ".local", record->target); + + //Calculate data length + record->rdlength = htons(sizeof(DnsSrvResourceRecord) - + sizeof(DnsResourceRecord) + n); + + //Number of resource records in the answer section + message->dnsHeader->ancount++; + //Update the length of the DNS message + message->length = offset + n; + } + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Add TXT record to a mDNS message + * @param[in] interface Underlying network interface + * @param[in,out] message Pointer to the mDNS message + * @param[in] service Pointer to a DNS-SD service + * @param[in] cacheFlush Cache-flush bit + * @param[in] ttl Resource record TTL (cache lifetime) + * @return Error code + **/ + +error_t dnsSdAddTxtRecord(NetInterface *interface, MdnsMessage *message, + const DnsSdService *service, bool_t cacheFlush, uint32_t ttl) +{ + size_t n; + size_t offset; + bool_t duplicate; + DnsSdContext *context; + DnsResourceRecord *record; + + //Point to the DNS-SD context + context = interface->dnsSdContext; + + //Check whether the resource record is already present in the Answer + //Section of the message + duplicate = mdnsCheckDuplicateRecord(message, context->instanceName, + service->name, ".local", DNS_RR_TYPE_TXT); + + //The duplicates should be suppressed and the resource record should + //appear only once in the list + if(!duplicate) + { + //Set the position to the end of the buffer + offset = message->length; + + //The first pass calculates the length of the DNS encoded instance name + n = mdnsEncodeName(context->instanceName, service->name, ".local", NULL); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //The second pass encodes the instance name using DNS notation + offset += mdnsEncodeName(context->instanceName, + service->name, ".local", (uint8_t *) message->dnsHeader + offset); + + //Consider the length of the resource record itself + if((offset + sizeof(DnsResourceRecord)) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //Point to the corresponding resource record + record = DNS_GET_RESOURCE_RECORD(message->dnsHeader, offset); + + //Fill in resource record + record->rtype = HTONS(DNS_RR_TYPE_TXT); + record->rclass = HTONS(DNS_RR_CLASS_IN); + record->ttl = htonl(ttl); + record->rdlength = htons(service->metadataLength); + + //Check whether the cache-flush bit should be set + if(cacheFlush) + record->rclass |= HTONS(MDNS_RCLASS_CACHE_FLUSH); + + //Advance write index + offset += sizeof(DnsResourceRecord); + + //Check the length of the resulting mDNS message + if((offset + service->metadataLength) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //Copy metadata + memcpy(record->rdata, service->metadata, service->metadataLength); + + //Update the length of the DNS message + message->length = offset + service->metadataLength; + //Number of resource records in the answer section + message->dnsHeader->ancount++; + } + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Add NSEC record to a mDNS message + * @param[in] interface Underlying network interface + * @param[in,out] message Pointer to the mDNS message + * @param[in] service Pointer to a DNS-SD service + * @param[in] cacheFlush Cache-flush bit + * @param[in] ttl Resource record TTL (cache lifetime) + * @return Error code + **/ + +error_t dnsSdAddNsecRecord(NetInterface *interface, MdnsMessage *message, + const DnsSdService *service, bool_t cacheFlush, uint32_t ttl) +{ + size_t n; + size_t offset; + bool_t duplicate; + size_t bitmapLength; + uint8_t bitmap[8]; + DnsSdContext *context; + DnsResourceRecord *record; + + //Point to the DNS-SD context + context = interface->dnsSdContext; + + //Check whether the resource record is already present in the Answer + //Section of the message + duplicate = mdnsCheckDuplicateRecord(message, context->instanceName, + service->name, ".local", DNS_RR_TYPE_NSEC); + + //The duplicates should be suppressed and the resource record should + //appear only once in the list + if(!duplicate) + { + //The bitmap identifies the resource record types that exist + memset(bitmap, 0, sizeof(bitmap)); + + //TXT resource record is supported + DNS_SET_NSEC_BITMAP(bitmap, DNS_RR_TYPE_TXT); + //SRV resource record is supported + DNS_SET_NSEC_BITMAP(bitmap, DNS_RR_TYPE_SRV); + + //Compute the length of the bitmap + for(bitmapLength = sizeof(bitmap); bitmapLength > 0; bitmapLength--) + { + //Trailing zero octets in the bitmap must be omitted... + if(bitmap[bitmapLength - 1] != 0x00) + break; + } + + //Set the position to the end of the buffer + offset = message->length; + + //The first pass calculates the length of the DNS encoded instance name + n = mdnsEncodeName(context->instanceName, service->name, ".local", NULL); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //The second pass encodes the instance name using the DNS name notation + offset += mdnsEncodeName(context->instanceName, service->name, + ".local", (uint8_t *) message->dnsHeader + offset); + + //Consider the length of the resource record itself + if((offset + sizeof(DnsResourceRecord)) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //Point to the corresponding resource record + record = DNS_GET_RESOURCE_RECORD(message->dnsHeader, offset); + + //Fill in resource record + record->rtype = HTONS(DNS_RR_TYPE_NSEC); + record->rclass = HTONS(DNS_RR_CLASS_IN); + record->ttl = htonl(ttl); + + //Check whether the cache-flush bit should be set + if(cacheFlush) + record->rclass |= HTONS(MDNS_RCLASS_CACHE_FLUSH); + + //Advance write index + offset += sizeof(DnsResourceRecord); + + //Check the length of the resulting mDNS message + if((offset + n + 2) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //The Next Domain Name field contains the record's own name + mdnsEncodeName(context->instanceName, service->name, + ".local", record->rdata); + + //DNS NSEC record is limited to Window Block number zero + record->rdata[n++] = 0; + //The Bitmap Length is a value in the range 1-32 + record->rdata[n++] = bitmapLength; + + //The Bitmap data identifies the resource record types that exist + memcpy(record->rdata + n, bitmap, bitmapLength); + + //Convert length field to network byte order + record->rdlength = htons(n + bitmapLength); + + //Number of resource records in the answer section + message->dnsHeader->ancount++; + //Update the length of the DNS message + message->length = offset + n + bitmapLength; + } + + //Successful processing + return NO_ERROR; +} + +#endif +