Webserver+3d print

Dependents:   Nucleo

cyclone_tcp/dns_sd/dns_sd.c

Committer:
Sergunb
Date:
2017-02-04
Revision:
0:8918a71cdbe9

File content as of revision 0:8918a71cdbe9:

/**
 * @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