/*
 * LEAmDNS_Transfer.cpp
 *
 *  License (MIT license):
 *    Permission is hereby granted, free of charge, to any person obtaining a copy
 *    of this software and associated documentation files (the "Software"), to deal
 *    in the Software without restriction, including without limitation the rights
 *    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *    copies of the Software, and to permit persons to whom the Software is
 *    furnished to do so, subject to the following conditions:
 *
 *    The above copyright notice and this permission notice shall be included in
 *    all copies or substantial portions of the Software.
 *
 *    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *    THE SOFTWARE.
 *
 */

extern "C" {
    #include "user_interface.h"
}

#include "LEAmDNS_lwIPdefs.h"
#include "LEAmDNS_Priv.h"


namespace esp8266 {

/*
 * LEAmDNS
 */
namespace MDNSImplementation {

/**
 *  CONST STRINGS
 */
static const char*                      scpcLocal               = "local";
static const char*                      scpcServices            = "services";
static const char*                      scpcDNSSD               = "dns-sd";
static const char*                      scpcUDP                 = "udp";
//static const char*                    scpcTCP                 = "tcp";

#ifdef MDNS_IP4_SUPPORT
    static const char*                  scpcReverseIP4Domain    = "in-addr";
#endif
#ifdef MDNS_IP6_SUPPORT
    static const char*                  scpcReverseIP6Domain    = "ip6";
#endif
static const char*                      scpcReverseTopDomain    = "arpa";

/**
 * TRANSFER
 */


/**
 * SENDING
 */

/*
 * MDNSResponder::_sendMDNSMessage
 *
 * Unicast responses are prepared and sent directly to the querier.
 * Multicast responses or queries are transferred to _sendMDNSMessage_Multicast
 *
 * Any reply flags in installed services are removed at the end!
 *
 */
bool MDNSResponder::_sendMDNSMessage(MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {
    
    bool    bResult = true;
    
    if (p_rSendParameter.m_bResponse) {
        if (p_rSendParameter.m_bUnicast) {  // Unicast response  -> Send to querier
            DEBUG_EX_ERR(if (!m_pUDPContext->getRemoteAddress()) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSMessage: MISSING remote address for response!\n")); });
            ip_addr_t   ipRemote;
            ipRemote.addr = m_pUDPContext->getRemoteAddress();
            bResult = ((_prepareMDNSMessage(p_rSendParameter, _getResponseMulticastInterface(SOFTAP_MODE | STATION_MODE))) &&
                       (m_pUDPContext->send(&ipRemote, m_pUDPContext->getRemotePort())));
        }
        else {                              // Multicast response -> Send via the same network interface, that received the query
            bResult = _sendMDNSMessage_Multicast(p_rSendParameter, (SOFTAP_MODE | STATION_MODE));
        }
    }
    else {                                  // Multicast query -> Send by all available network interfaces
        const int   caiWiFiOpModes[2] = { SOFTAP_MODE, STATION_MODE };
        for (int iInterfaceId=0; ((bResult) && (iInterfaceId<=1)); ++iInterfaceId) {
            if (wifi_get_opmode() & caiWiFiOpModes[iInterfaceId]) {
                bResult = _sendMDNSMessage_Multicast(p_rSendParameter, caiWiFiOpModes[iInterfaceId]);
            }
        }
    }

    // Finally clear service reply masks
    for (stcMDNSService* pService=m_pServices; pService; pService=pService->m_pNext) {
        pService->m_u8ReplyMask = 0;
    }

    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSMessage: FAILED!\n")); });
    return bResult;
}

/*
 * MDNSResponder::_sendMDNSMessage_Multicast
 *
 * Fills the UDP output buffer (via _prepareMDNSMessage) and sends the buffer
 * via the selected WiFi interface (Station or AP)
 */
bool MDNSResponder::_sendMDNSMessage_Multicast(MDNSResponder::stcMDNSSendParameter& p_rSendParameter,
                                               int p_iWiFiOpMode) {
    bool    bResult = false;

    ip_addr_t   ifFromAddress;
    ifFromAddress.addr = _getResponseMulticastInterface(p_iWiFiOpMode);
    IPAddress   fromIPAddress(ifFromAddress.addr);
#if LWIP_VERSION_MAJOR == 1
    m_pUDPContext->setMulticastInterface(ifFromAddress);
#else
    m_pUDPContext->setMulticastInterface(&ifFromAddress);
#endif

    ip_addr_t   toMulticastAddress;
#ifdef MDNS_IP4_SUPPORT
    toMulticastAddress.addr = DNS_MQUERY_IPV4_GROUP_INIT;
#endif
#ifdef MDNS_IP6_SUPPORT
    //TODO: set multicast address
    toMulticastAddress.addr = DNS_MQUERY_IPV6_GROUP_INIT;
#endif
    DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSMessage_Multicast: Will send to '%s'.\n"), IPAddress(toMulticastAddress.addr).toString().c_str()););
    bResult = ((_prepareMDNSMessage(p_rSendParameter, fromIPAddress)) &&
               (m_pUDPContext->send(&toMulticastAddress, DNS_MQUERY_PORT)));

    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSMessage_Multicast: FAILED!\n")); });
    return bResult;
}

/*
 * MDNSResponder::_prepareMDNSMessage
 *
 * The MDNS message is composed in a two-step process.
 * In the first loop 'only' the header informations (mainly number of answers) are collected,
 * while in the seconds loop, the header and all queries and answers are written to the UDP
 * output buffer.
 *
 */
bool MDNSResponder::_prepareMDNSMessage(MDNSResponder::stcMDNSSendParameter& p_rSendParameter,
                                        IPAddress p_IPAddress) {
    DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage\n")););
    bool    bResult = true;
    
    // Prepare header; count answers
    stcMDNS_MsgHeader  msgHeader(0, p_rSendParameter.m_bResponse, 0, p_rSendParameter.m_bAuthorative);
    // If this is a response, the answers are anwers,
    // else this is a query or probe and the answers go into auth section
    uint16_t&           ru16Answers = (p_rSendParameter.m_bResponse
                                        ? msgHeader.m_u16ANCount
                                        : msgHeader.m_u16NSCount);
    
    /**
     * enuSequence
     */
    enum enuSequence {
        Sequence_Count  = 0,
        Sequence_Send   = 1
    };
    
    // Two step sequence: 'Count' and 'Send'
    for (uint32_t sequence=Sequence_Count; ((bResult) && (sequence<=Sequence_Send)); ++sequence) {
        DEBUG_EX_INFO(
            if (Sequence_Send == sequence) {
                DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"),
                (unsigned)msgHeader.m_u16ID,
                (unsigned)msgHeader.m_1bQR, (unsigned)msgHeader.m_4bOpcode, (unsigned)msgHeader.m_1bAA, (unsigned)msgHeader.m_1bTC, (unsigned)msgHeader.m_1bRD,
                (unsigned)msgHeader.m_1bRA, (unsigned)msgHeader.m_4bRCode,
                (unsigned)msgHeader.m_u16QDCount,
                (unsigned)msgHeader.m_u16ANCount,
                (unsigned)msgHeader.m_u16NSCount,
                (unsigned)msgHeader.m_u16ARCount);
            }
        );
        // Count/send
        // Header
        bResult = ((Sequence_Count == sequence)
                    ? true
                    : _writeMDNSMsgHeader(msgHeader, p_rSendParameter));
        DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSMsgHeader FAILED!\n")););
        // Questions
        for (stcMDNS_RRQuestion* pQuestion=p_rSendParameter.m_pQuestions; ((bResult) && (pQuestion)); pQuestion=pQuestion->m_pNext) {
            ((Sequence_Count == sequence)
                ? ++msgHeader.m_u16QDCount
                : (bResult = _writeMDNSQuestion(*pQuestion, p_rSendParameter)));
            DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSQuestion FAILED!\n")););
        }

        // Answers and authorative answers
#ifdef MDNS_IP4_SUPPORT
        if ((bResult) &&
            (p_rSendParameter.m_u8HostReplyMask & ContentFlag_A)) {
            ((Sequence_Count == sequence)
                ? ++ru16Answers
                : (bResult = _writeMDNSAnswer_A(p_IPAddress, p_rSendParameter)));
            DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_A(A) FAILED!\n")););
        }
        if ((bResult) &&
            (p_rSendParameter.m_u8HostReplyMask & ContentFlag_PTR_IP4)) {
            ((Sequence_Count == sequence)
                ? ++ru16Answers
                : (bResult = _writeMDNSAnswer_PTR_IP4(p_IPAddress, p_rSendParameter)));
            DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_IP4 FAILED!\n")););
        }
#endif
#ifdef MDNS_IP6_SUPPORT
        if ((bResult) &&
            (p_rSendParameter.m_u8HostReplyMask & ContentFlag_AAAA)) {
            ((Sequence_Count == sequence)
                ? ++ru16Answers
                : (bResult = _writeMDNSAnswer_AAAA(p_IPAddress, p_rSendParameter)));
            DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_AAAA(A) FAILED!\n")););
        }
        if ((bResult) &&
            (p_rSendParameter.m_u8HostReplyMask & ContentFlag_PTR_IP6)) {
            ((Sequence_Count == sequence)
                ? ++ru16Answers
                : (bResult = _writeMDNSAnswer_PTR_IP6(p_IPAddress, p_rSendParameter)));
            DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_IP6 FAILED!\n")););
        }
#endif

        for (stcMDNSService* pService=m_pServices; ((bResult) && (pService)); pService=pService->m_pNext) {
            if ((bResult) &&
                (pService->m_u8ReplyMask & ContentFlag_PTR_TYPE)) {
                ((Sequence_Count == sequence)
                    ? ++ru16Answers
                    : (bResult = _writeMDNSAnswer_PTR_TYPE(*pService, p_rSendParameter)));
                DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_TYPE FAILED!\n")););
            }
            if ((bResult) &&
                (pService->m_u8ReplyMask & ContentFlag_PTR_NAME)) {
                ((Sequence_Count == sequence)
                    ? ++ru16Answers
                    : (bResult = _writeMDNSAnswer_PTR_NAME(*pService, p_rSendParameter)));
                DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_NAME FAILED!\n")););
            }
            if ((bResult) &&
                (pService->m_u8ReplyMask & ContentFlag_SRV)) {
                ((Sequence_Count == sequence)
                    ? ++ru16Answers
                    : (bResult = _writeMDNSAnswer_SRV(*pService, p_rSendParameter)));
                DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_SRV(A) FAILED!\n")););
            }
            if ((bResult) &&
                (pService->m_u8ReplyMask & ContentFlag_TXT)) {
                ((Sequence_Count == sequence)
                    ? ++ru16Answers
                    : (bResult = _writeMDNSAnswer_TXT(*pService, p_rSendParameter)));
                DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_TXT(A) FAILED!\n")););
            }
        }   // for services
        
        // Additional answers
#ifdef MDNS_IP4_SUPPORT
        bool    bNeedsAdditionalAnswerA = false;
#endif
#ifdef MDNS_IP6_SUPPORT
        bool    bNeedsAdditionalAnswerAAAA = false;
#endif
        for (stcMDNSService* pService=m_pServices; ((bResult) && (pService)); pService=pService->m_pNext) {
            if ((bResult) &&
                (pService->m_u8ReplyMask & ContentFlag_PTR_NAME) && // If PTR_NAME is requested, AND
                (!(pService->m_u8ReplyMask & ContentFlag_SRV))) {   // NOT SRV -> add SRV as additional answer
                ((Sequence_Count == sequence)
                    ? ++msgHeader.m_u16ARCount
                    : (bResult = _writeMDNSAnswer_SRV(*pService, p_rSendParameter)));
                DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_SRV(B) FAILED!\n")););
            }
            if ((bResult) &&
                (pService->m_u8ReplyMask & ContentFlag_PTR_NAME) && // If PTR_NAME is requested, AND
                (!(pService->m_u8ReplyMask & ContentFlag_TXT))) {   // NOT TXT -> add TXT as additional answer
                ((Sequence_Count == sequence)
                    ? ++msgHeader.m_u16ARCount
                    : (bResult = _writeMDNSAnswer_TXT(*pService, p_rSendParameter)));
                DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_TXT(B) FAILED!\n")););
            }
            if ((pService->m_u8ReplyMask & (ContentFlag_PTR_NAME | ContentFlag_SRV)) ||         // If service instance name or SRV OR
                (p_rSendParameter.m_u8HostReplyMask & (ContentFlag_A | ContentFlag_AAAA))) {    // any host IP address is requested
#ifdef MDNS_IP4_SUPPORT
                if ((bResult) &&
                    (!(p_rSendParameter.m_u8HostReplyMask & ContentFlag_A))) {          // Add IP4 address
                    bNeedsAdditionalAnswerA = true;
                }
#endif
#ifdef MDNS_IP6_SUPPORT
                if ((bResult) &&
                    (!(p_rSendParameter.m_u8HostReplyMask & ContentFlag_AAAA))) {       // Add IP6 address
                    bNeedsAdditionalAnswerAAAA = true;
                }
#endif
            }
        }   // for services
        
        // Answer A needed?
#ifdef MDNS_IP4_SUPPORT
        if ((bResult) &&
            (bNeedsAdditionalAnswerA)) {
            ((Sequence_Count == sequence)
                ? ++msgHeader.m_u16ARCount
                : (bResult = _writeMDNSAnswer_A(p_IPAddress, p_rSendParameter)));
            DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_A(B) FAILED!\n")););
        }
#endif
#ifdef MDNS_IP6_SUPPORT
        // Answer AAAA needed?
        if ((bResult) &&
            (bNeedsAdditionalAnswerAAAA)) {
            ((Sequence_Count == sequence)
                ? ++msgHeader.m_u16ARCount
                : (bResult = _writeMDNSAnswer_AAAA(p_IPAddress, p_rSendParameter)));
            DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_AAAA(B) FAILED!\n")););
        }
#endif
        DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: Loop %i FAILED!\n"), sequence););
    }   // for sequence
    DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: FAILED!\n")););
    return bResult;
}

/*
 * MDNSResponder::_sendMDNSServiceQuery
 *
 * Creates and sends a PTR query for the given service domain.
 *
 */
bool MDNSResponder::_sendMDNSServiceQuery(const MDNSResponder::stcMDNSServiceQuery& p_ServiceQuery) {

    return _sendMDNSQuery(p_ServiceQuery.m_ServiceTypeDomain, DNS_RRTYPE_PTR);
}

/*
 * MDNSResponder::_sendMDNSQuery
 *
 * Creates and sends a query for the given domain and query type.
 *
 */
bool MDNSResponder::_sendMDNSQuery(const MDNSResponder::stcMDNS_RRDomain& p_QueryDomain,
                                   uint16_t p_u16QueryType) {

    bool                    bResult = false;
    
    stcMDNSSendParameter    sendParameter;
    if (0 != ((sendParameter.m_pQuestions = new stcMDNS_RRQuestion))) {
        sendParameter.m_pQuestions->m_Header.m_Domain = p_QueryDomain;

        sendParameter.m_pQuestions->m_bUnicast = true;
        sendParameter.m_pQuestions->m_Header.m_Attributes.m_u16Type = p_u16QueryType;
        sendParameter.m_pQuestions->m_Header.m_Attributes.m_u16Class = (0x8000 | DNS_RRCLASS_IN);   // Unicast & INternet

        bResult = _sendMDNSMessage(sendParameter);
    }   // else: FAILED to alloc question
    DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSQuery: FAILED to alloc question!\n")););
    return bResult;
}

/*
 * MDNSResponder::_getResponseMulticastInterface
 *
 * Selects the appropriate interface for responses.
 * If AP mode is enabled and the remote contact is in the APs local net, then the
 * AP interface is used to send the response.
 * Otherwise the Station interface (if available) is used.
 *
 */
IPAddress MDNSResponder::_getResponseMulticastInterface(int p_iWiFiOpModes) const {
    
    ip_info IPInfo_Local;
    bool    bFoundMatch = false;
    
    if ((p_iWiFiOpModes & SOFTAP_MODE) &&
        (wifi_get_opmode() & SOFTAP_MODE)) {
        //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _getResponseMulticastInterface: SOFTAP_MODE\n")););
        // Get remote IP address
        ip_info IPInfo_Remote;
        IPInfo_Remote.ip.addr = m_pUDPContext->getRemoteAddress();
        // Get local (AP) IP address
        wifi_get_ip_info(SOFTAP_IF, &IPInfo_Local);
        
        if ((IPInfo_Local.ip.addr) &&                                                       // Has local AP IP address AND
            (ip_addr_netcmp(&IPInfo_Remote.ip, &IPInfo_Local.ip, &IPInfo_Local.netmask))) { // Remote address is in the same subnet as the AP
            bFoundMatch = true;
        }
    }
    if ((!bFoundMatch) &&
        (p_iWiFiOpModes & STATION_MODE) &&
        (wifi_get_opmode() & STATION_MODE)) {
        //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _getResponseMulticastInterface: STATION_MODE\n")););
        // Get local (STATION) IP address
        wifi_get_ip_info(STATION_IF, &IPInfo_Local);
    }
    //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _getResponseMulticastInterface(%i): %s\n"), p_iWiFiOpModes, IPAddress(IPInfo_Local.ip.addr).toString().c_str()););
    return IPAddress(IPInfo_Local.ip.addr);
}


/**
 * HELPERS
 */

/**
 * RESOURCE RECORDS
 */

/*
 * MDNSResponder::_readRRQuestion
 *
 * Reads a question (eg. MyESP._http._tcp.local ANY IN) from the UPD input buffer.
 *
 */
bool MDNSResponder::_readRRQuestion(MDNSResponder::stcMDNS_RRQuestion& p_rRRQuestion) {
    DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRQuestion\n")););
    
    bool    bResult = false;
    
    if ((bResult = _readRRHeader(p_rRRQuestion.m_Header))) {
        // Extract unicast flag from class field
        p_rRRQuestion.m_bUnicast = (p_rRRQuestion.m_Header.m_Attributes.m_u16Class & 0x8000);
        p_rRRQuestion.m_Header.m_Attributes.m_u16Class &= (~0x8000);

        DEBUG_EX_INFO(
                DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRQuestion "));
                _printRRDomain(p_rRRQuestion.m_Header.m_Domain);
                DEBUG_OUTPUT.printf_P(PSTR(" Type:0x%04X Class:0x%04X %s\n"), (unsigned)p_rRRQuestion.m_Header.m_Attributes.m_u16Type, (unsigned)p_rRRQuestion.m_Header.m_Attributes.m_u16Class, (p_rRRQuestion.m_bUnicast ? "Unicast" : "Multicast"));
                );
    }
    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRQuestion: FAILED!\n")); });
    return bResult;
}

/*
 * MDNSResponder::_readRRAnswer
 *
 * Reads an answer (eg. _http._tcp.local PTR OP TTL MyESP._http._tcp.local)
 * from the UDP input buffer.
 * After reading the domain and type info, the further processing of the answer
 * is transferred the answer specific reading functions.
 * Unknown answer types are processed by the generic answer reader (to remove them
 * from the input buffer).
 *
 */
bool MDNSResponder::_readRRAnswer(MDNSResponder::stcMDNS_RRAnswer*& p_rpRRAnswer) {
    DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer\n")););
    
    bool    bResult = false;
    
    stcMDNS_RRHeader    header;
    uint32_t            u32TTL;
    uint16_t            u16RDLength;
    if ((_readRRHeader(header)) &&
        (_udpRead32(u32TTL)) &&
        (_udpRead16(u16RDLength))) {

        DEBUG_EX_INFO(
                DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer: Reading 0x%04X answer (class:0x%04X, TTL:%u, RDLength:%u) for "), header.m_Attributes.m_u16Type, header.m_Attributes.m_u16Class, u32TTL, u16RDLength);
                _printRRDomain(header.m_Domain);
                DEBUG_OUTPUT.printf_P(PSTR("\n"));
                );
        
        switch (header.m_Attributes.m_u16Type & (~0x8000)) {    // Topmost bit might carry 'cache flush' flag
#ifdef MDNS_IP4_SUPPORT
        case DNS_RRTYPE_A:
            p_rpRRAnswer = new stcMDNS_RRAnswerA(header, u32TTL);
            bResult = _readRRAnswerA(*(stcMDNS_RRAnswerA*&)p_rpRRAnswer, u16RDLength);
            break;
#endif
        case DNS_RRTYPE_PTR:
            p_rpRRAnswer = new stcMDNS_RRAnswerPTR(header, u32TTL);
            bResult = _readRRAnswerPTR(*(stcMDNS_RRAnswerPTR*&)p_rpRRAnswer, u16RDLength);
            break;
        case DNS_RRTYPE_TXT:
            p_rpRRAnswer = new stcMDNS_RRAnswerTXT(header, u32TTL);
            bResult = _readRRAnswerTXT(*(stcMDNS_RRAnswerTXT*&)p_rpRRAnswer, u16RDLength);
            break;
#ifdef MDNS_IP6_SUPPORT
        case DNS_RRTYPE_AAAA:
            p_rpRRAnswer = new stcMDNS_RRAnswerAAAA(header, u32TTL);
            bResult = _readRRAnswerAAAA(*(stcMDNS_RRAnswerAAAA*&)p_rpRRAnswer, u16RDLength);
            break;
#endif
        case DNS_RRTYPE_SRV:
            p_rpRRAnswer = new stcMDNS_RRAnswerSRV(header, u32TTL);
            bResult = _readRRAnswerSRV(*(stcMDNS_RRAnswerSRV*&)p_rpRRAnswer, u16RDLength);
            break;
        default:
            p_rpRRAnswer = new stcMDNS_RRAnswerGeneric(header, u32TTL);
            bResult = _readRRAnswerGeneric(*(stcMDNS_RRAnswerGeneric*&)p_rpRRAnswer, u16RDLength);
            break;
        }
        DEBUG_EX_INFO(
                if ((bResult) &&
                    (p_rpRRAnswer)) {
                    DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer: "));
                    _printRRDomain(p_rpRRAnswer->m_Header.m_Domain);
                    DEBUG_OUTPUT.printf_P(PSTR(" Type:0x%04X Class:0x%04X TTL:%u, RDLength:%u "), p_rpRRAnswer->m_Header.m_Attributes.m_u16Type, p_rpRRAnswer->m_Header.m_Attributes.m_u16Class, p_rpRRAnswer->m_u32TTL, u16RDLength);
                    switch (header.m_Attributes.m_u16Type & (~0x8000)) {    // Topmost bit might carry 'cache flush' flag
#ifdef MDNS_IP4_SUPPORT
                    case DNS_RRTYPE_A:
                        DEBUG_OUTPUT.printf_P(PSTR("A IP:%s"), ((stcMDNS_RRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str());
                        break;
#endif
                    case DNS_RRTYPE_PTR:
                        DEBUG_OUTPUT.printf_P(PSTR("PTR "));
                        _printRRDomain(((stcMDNS_RRAnswerPTR*&)p_rpRRAnswer)->m_PTRDomain);
                        break;
                    case DNS_RRTYPE_TXT: {
                        size_t  stTxtLength = ((stcMDNS_RRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_strLength();
                        char*   pTxts = new char[stTxtLength];
                        if (pTxts) {
                            ((stcMDNS_RRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_str(pTxts);
                            DEBUG_OUTPUT.printf_P(PSTR("TXT(%u) %s"), stTxtLength, pTxts);
                            delete[] pTxts;
                        }
                        break;
                    }
#ifdef MDNS_IP6_SUPPORT
                    case DNS_RRTYPE_AAAA:
                        DEBUG_OUTPUT.printf_P(PSTR("AAAA IP:%s"), ((stcMDNS_RRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str());
                        break;
#endif
                    case DNS_RRTYPE_SRV:
                        DEBUG_OUTPUT.printf_P(PSTR("SRV Port:%u "), ((stcMDNS_RRAnswerSRV*&)p_rpRRAnswer)->m_u16Port);
                        _printRRDomain(((stcMDNS_RRAnswerSRV*&)p_rpRRAnswer)->m_SRVDomain);
                        break;
                    default:
                        DEBUG_OUTPUT.printf_P(PSTR("generic "));
                        break;
                    }
                    DEBUG_OUTPUT.printf_P(PSTR("\n"));
                }
                else {
                    DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer: FAILED to read specific answer of type 0x%04X!\n"), p_rpRRAnswer->m_Header.m_Attributes.m_u16Type);
                }
        );  // DEBUG_EX_INFO
    }
    DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer: FAILED!\n")););
    return bResult;
}

#ifdef MDNS_IP4_SUPPORT
    /*
     * MDNSResponder::_readRRAnswerA
     */
    bool MDNSResponder::_readRRAnswerA(MDNSResponder::stcMDNS_RRAnswerA& p_rRRAnswerA,
                                       uint16_t p_u16RDLength) {
        
        uint32_t    u32IP4Address;
        bool        bResult = ((MDNS_IP4_SIZE == p_u16RDLength) &&
                               (_udpReadBuffer((unsigned char*)&u32IP4Address, MDNS_IP4_SIZE)) &&
                               ((p_rRRAnswerA.m_IPAddress = IPAddress(u32IP4Address))));
        DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerA: FAILED!\n")););
        return bResult;
    }
#endif

/*
 * MDNSResponder::_readRRAnswerPTR
 */
bool MDNSResponder::_readRRAnswerPTR(MDNSResponder::stcMDNS_RRAnswerPTR& p_rRRAnswerPTR,
                                     uint16_t p_u16RDLength) {
    
    bool    bResult = ((p_u16RDLength) &&
                       (_readRRDomain(p_rRRAnswerPTR.m_PTRDomain)));
    DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerPTR: FAILED!\n")););
    return bResult;
}

/*
 * MDNSResponder::_readRRAnswerTXT
 *
 * Read TXT items from a buffer like 4c#=15ff=20
 */
bool MDNSResponder::_readRRAnswerTXT(MDNSResponder::stcMDNS_RRAnswerTXT& p_rRRAnswerTXT,
                                     uint16_t p_u16RDLength) {
    DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: RDLength:%u\n"), p_u16RDLength););
    bool    bResult = true;
    
    p_rRRAnswerTXT.clear();
    if (p_u16RDLength) {
        bResult = false;
        
        unsigned char*  pucBuffer = new unsigned char[p_u16RDLength];
        if (pucBuffer) {
            if (_udpReadBuffer(pucBuffer, p_u16RDLength)) {
                bResult = true;
                
                const unsigned char*    pucCursor = pucBuffer;
                while ((pucCursor < (pucBuffer + p_u16RDLength)) &&
                       (bResult)) {
                    bResult = false;
                    
                    stcMDNSServiceTxt*      pTxt = 0;
                    unsigned char   ucLength = *pucCursor++;    // Length of the next txt item
                    if (ucLength) {
                        DEBUG_EX_INFO(
                                static char sacBuffer[64]; *sacBuffer = 0;
                                uint8_t u8MaxLength = ((ucLength > (sizeof(sacBuffer) - 1)) ? (sizeof(sacBuffer) - 1) : ucLength);
                                os_strncpy(sacBuffer, (const char*)pucCursor, u8MaxLength); sacBuffer[u8MaxLength] = 0;
                                DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: Item(%u): %s\n"), ucLength, sacBuffer);
                                );

                        unsigned char*  pucEqualSign = (unsigned char*)os_strchr((const char*)pucCursor, '=');  // Position of the '=' sign
                        unsigned char   ucKeyLength;
                        if ((pucEqualSign) &&
                            ((ucKeyLength = (pucEqualSign - pucCursor)))) {
                            unsigned char   ucValueLength = (ucLength - (pucEqualSign - pucCursor + 1));
                            bResult = (((pTxt = new stcMDNSServiceTxt)) &&
                                       (pTxt->setKey((const char*)pucCursor, ucKeyLength)) &&
                                       (pTxt->setValue((const char*)(pucEqualSign + 1), ucValueLength)));
                        }
                        else {
                            DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: INVALID TXT format (No '=')!\n")););
                        }
                        pucCursor += ucLength;
                    }
                    else {  // no/zero length TXT
                        DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: TXT answer contains no items.\n")););
                        bResult = true;
                    }
                    
                    if ((bResult) &&
                        (pTxt)) {   // Everythings fine so far
                        // Link TXT item to answer TXTs
                        pTxt->m_pNext = p_rRRAnswerTXT.m_Txts.m_pTxts;
                        p_rRRAnswerTXT.m_Txts.m_pTxts = pTxt;
                    }
                    else {          // At least no TXT (migth be OK, if length was 0) OR an error
                        if (!bResult) {
                            DEBUG_EX_ERR(
                                    DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: FAILED to read TXT item!\n"));
                                    DEBUG_OUTPUT.printf_P(PSTR("RData dump:\n"));
                                    _udpDump((m_pUDPContext->tell() - p_u16RDLength), p_u16RDLength);
                                    DEBUG_OUTPUT.printf_P(PSTR("\n"));
                            );
                        }
                        if (pTxt) {
                            delete pTxt;
                            pTxt = 0;
                        }
                        p_rRRAnswerTXT.clear();
                    }
                }   // while
                
                DEBUG_EX_ERR(
                    if (!bResult) { // Some failure
                        DEBUG_OUTPUT.printf_P(PSTR("RData dump:\n"));
                        _udpDump((m_pUDPContext->tell() - p_u16RDLength), p_u16RDLength);
                        DEBUG_OUTPUT.printf_P(PSTR("\n"));
                    }
                );
            }
            else {
                DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: FAILED to read TXT content!\n")););
            }
            // Clean up
            delete[] pucBuffer;
        }
        else {
            DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: FAILED to alloc buffer for TXT content!\n")););
        }
    }
    else {
        DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: WARNING! No content!\n")););
    }
    DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: FAILED!\n")););
    return bResult;
}

#ifdef MDNS_IP6_SUPPORT
    bool MDNSResponder::_readRRAnswerAAAA(MDNSResponder::stcMDNS_RRAnswerAAAA& p_rRRAnswerAAAA,
                                          uint16_t p_u16RDLength) {
        bool    bResult = false;
        // TODO: Implement
        return bResult;
    }
#endif

/*
 * MDNSResponder::_readRRAnswerSRV
 */
bool MDNSResponder::_readRRAnswerSRV(MDNSResponder::stcMDNS_RRAnswerSRV& p_rRRAnswerSRV,
                                     uint16_t p_u16RDLength) {
    
    bool    bResult = (((3 * sizeof(uint16_t)) < p_u16RDLength) &&
                       (_udpRead16(p_rRRAnswerSRV.m_u16Priority)) &&
                       (_udpRead16(p_rRRAnswerSRV.m_u16Weight)) &&
                       (_udpRead16(p_rRRAnswerSRV.m_u16Port)) &&
                       (_readRRDomain(p_rRRAnswerSRV.m_SRVDomain)));
    DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerSRV: FAILED!\n")););
    return bResult;
}

/*
 * MDNSResponder::_readRRAnswerGeneric
 */
bool MDNSResponder::_readRRAnswerGeneric(MDNSResponder::stcMDNS_RRAnswerGeneric& p_rRRAnswerGeneric,
                                         uint16_t p_u16RDLength) {
    bool    bResult = (0 == p_u16RDLength);
    
    p_rRRAnswerGeneric.clear();
    if (((p_rRRAnswerGeneric.m_u16RDLength = p_u16RDLength)) &&
        ((p_rRRAnswerGeneric.m_pu8RDData = new unsigned char[p_rRRAnswerGeneric.m_u16RDLength]))) {

        bResult = _udpReadBuffer(p_rRRAnswerGeneric.m_pu8RDData, p_rRRAnswerGeneric.m_u16RDLength);
    }
    DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerGeneric: FAILED!\n")););
    return bResult;
}

/*
 * MDNSResponder::_readRRHeader
 */
bool MDNSResponder::_readRRHeader(MDNSResponder::stcMDNS_RRHeader& p_rRRHeader) {
    //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRHeader\n")););
    
    bool    bResult = ((_readRRDomain(p_rRRHeader.m_Domain)) &&
                       (_readRRAttributes(p_rRRHeader.m_Attributes)));
    DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRHeader: FAILED!\n")););
    return bResult;
}

/*
 * MDNSResponder::_readRRDomain
 *
 * Reads a (maybe multilevel compressed) domain from the UDP input buffer.
 *
 */
bool MDNSResponder::_readRRDomain(MDNSResponder::stcMDNS_RRDomain& p_rRRDomain) {
    //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain\n")););
    
    bool    bResult = ((p_rRRDomain.clear()) &&
                       (_readRRDomain_Loop(p_rRRDomain, 0)));
    DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain: FAILED!\n")););
    return bResult;
}

/*
 * MDNSResponder::_readRRDomain_Loop
 *
 * Reads a domain from the UDP input buffer. For every compression level, the functions
 * calls itself recursively. To avoid endless recursion because of malformed MDNS records,
 * the maximum recursion depth is set by MDNS_DOMAIN_MAX_REDIRCTION.
 *
 */
bool MDNSResponder::_readRRDomain_Loop(MDNSResponder::stcMDNS_RRDomain& p_rRRDomain,
                                       uint8_t p_u8Depth) {
    //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u)\n"), p_u8Depth););
    
    bool    bResult = false;
    
    if (MDNS_DOMAIN_MAX_REDIRCTION >= p_u8Depth) {
        bResult = true;
        
        uint8_t u8Len = 0;
        do {
            //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): Offset:%u p0:%02x\n"), p_u8Depth, m_pUDPContext->tell(), m_pUDPContext->peek()););
            _udpRead8(u8Len);
            
            if (u8Len & MDNS_DOMAIN_COMPRESS_MARK) {
                // Compressed label(s)
                uint16_t    u16Offset = ((u8Len & ~MDNS_DOMAIN_COMPRESS_MARK) << 8);    // Implicit BE to LE conversion!
                _udpRead8(u8Len);
                u16Offset |= u8Len;
                
                if (m_pUDPContext->isValidOffset(u16Offset)) {
                    size_t  stCurrentPosition = m_pUDPContext->tell();      // Prepare return from recursion
                    
                    //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): Redirecting from %u to %u!\n"), p_u8Depth, stCurrentPosition, u16Offset););
                    m_pUDPContext->seek(u16Offset);
                    if (_readRRDomain_Loop(p_rRRDomain, p_u8Depth + 1)) {   // Do recursion
                        //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): Succeeded to read redirected label! Returning to %u\n"), p_u8Depth, stCurrentPosition););
                        m_pUDPContext->seek(stCurrentPosition);             // Restore after recursion
                    }
                    else {
                        DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): FAILED to read redirected label!\n"), p_u8Depth););
                        bResult = false;
                    }
                }
                else {
                    DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): INVALID offset in redirection!\n"), p_u8Depth););
                    bResult = false;
                }
                break;
            }
            else {
                // Normal (uncompressed) label (maybe '\0' only)
                if (MDNS_DOMAIN_MAXLENGTH > (p_rRRDomain.m_u16NameLength + u8Len)) {
                    // Add length byte
                    p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength] = u8Len;
                    ++(p_rRRDomain.m_u16NameLength);
                    if (u8Len) {    // Add name
                        if ((bResult = _udpReadBuffer((unsigned char*)&(p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength]), u8Len))) {
                            /*DEBUG_EX_INFO(
                                    p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength + u8Len] = 0;  // Closing '\0' for printing
                                    DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): Domain label (%u): %s\n"), p_u8Depth, (unsigned)(p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength - 1]), &(p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength]));
                                    );*/

                            p_rRRDomain.m_u16NameLength += u8Len;
                        }
                    }
                    //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(2) offset:%u p0:%x\n"), m_pUDPContext->tell(), m_pUDPContext->peek()););
                }
                else {
                    DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): ERROR! Domain name too long (%u + %u)!\n"), p_u8Depth, p_rRRDomain.m_u16NameLength, u8Len););
                    bResult = false;
                    break;
                }
            }
        } while ((bResult) &&
                 (0 != u8Len));
    }
    else {
        DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): ERROR! Too many redirections!\n"), p_u8Depth););
    }
    return bResult;
}

/*
 * MDNSResponder::_readRRAttributes
 */
bool MDNSResponder::_readRRAttributes(MDNSResponder::stcMDNS_RRAttributes& p_rRRAttributes) {
    //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAttributes\n")););

    bool    bResult = ((_udpRead16(p_rRRAttributes.m_u16Type)) &&
                       (_udpRead16(p_rRRAttributes.m_u16Class)));
    DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAttributes: FAILED!\n")););
    return bResult;
}


/*
 * DOMAIN NAMES
 */

/*
 * MDNSResponder::_buildDomainForHost
 *
 * Builds a MDNS host domain (eg. esp8266.local) for the given hostname.
 *
 */
bool MDNSResponder::_buildDomainForHost(const char* p_pcHostname,
                                        MDNSResponder::stcMDNS_RRDomain& p_rHostDomain) const {
    
    p_rHostDomain.clear();
    bool    bResult = ((p_pcHostname) &&
                       (*p_pcHostname) &&
                       (p_rHostDomain.addLabel(p_pcHostname)) &&
                       (p_rHostDomain.addLabel(scpcLocal)) &&
                       (p_rHostDomain.addLabel(0)));
    DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForHost: FAILED!\n")););
    return bResult;
}

/*
 * MDNSResponder::_buildDomainForDNSSD
 *
 * Builds the '_services._dns-sd._udp.local' domain.
 * Used while detecting generic service enum question (DNS-SD) and answering these questions.
 *
 */
bool MDNSResponder::_buildDomainForDNSSD(MDNSResponder::stcMDNS_RRDomain& p_rDNSSDDomain) const {
    
    p_rDNSSDDomain.clear();
    bool    bResult = ((p_rDNSSDDomain.addLabel(scpcServices, true)) &&
                       (p_rDNSSDDomain.addLabel(scpcDNSSD, true)) &&
                       (p_rDNSSDDomain.addLabel(scpcUDP, true)) &&
                       (p_rDNSSDDomain.addLabel(scpcLocal)) &&
                       (p_rDNSSDDomain.addLabel(0)));
    DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForDNSSD: FAILED!\n")););
    return bResult;
}

/*
 * MDNSResponder::_buildDomainForService
 *
 * Builds the domain for the given service (eg. _http._tcp.local or
 * MyESP._http._tcp.local (if p_bIncludeName is set)).
 *
 */
bool MDNSResponder::_buildDomainForService(const MDNSResponder::stcMDNSService& p_Service,
                                           bool p_bIncludeName,
                                           MDNSResponder::stcMDNS_RRDomain& p_rServiceDomain) const {
    
    p_rServiceDomain.clear();
    bool    bResult = (((!p_bIncludeName) ||
                        (p_rServiceDomain.addLabel(p_Service.m_pcName))) &&
                       (p_rServiceDomain.addLabel(p_Service.m_pcService, true)) &&
                       (p_rServiceDomain.addLabel(p_Service.m_pcProtocol, true)) &&
                       (p_rServiceDomain.addLabel(scpcLocal)) &&
                       (p_rServiceDomain.addLabel(0)));
    DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForService: FAILED!\n")););
    return bResult;
}

/*
 * MDNSResponder::_buildDomainForService
 *
 * Builds the domain for the given service properties (eg. _http._tcp.local).
 * The usual prepended '_' are added, if missing in the input strings.
 *
 */
bool MDNSResponder::_buildDomainForService(const char* p_pcService,
                                           const char* p_pcProtocol,
                                           MDNSResponder::stcMDNS_RRDomain& p_rServiceDomain) const {
    
    p_rServiceDomain.clear();
    bool    bResult = ((p_pcService) &&
                       (p_pcProtocol) &&
                       (p_rServiceDomain.addLabel(p_pcService, ('_' != *p_pcService))) &&
                       (p_rServiceDomain.addLabel(p_pcProtocol, ('_' != *p_pcProtocol))) &&
                       (p_rServiceDomain.addLabel(scpcLocal)) &&
                       (p_rServiceDomain.addLabel(0)));
    DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForService: FAILED for (%s.%s)!\n"), (p_pcService ?: "-"), (p_pcProtocol ?: "-")););
    return bResult;
}           

#ifdef MDNS_IP4_SUPPORT
    /*
     * MDNSResponder::_buildDomainForReverseIP4
     *
     * The IP4 address is stringized by printing the four address bytes into a char buffer in reverse order
     * and adding 'in-addr.arpa' (eg. 012.789.456.123.in-addr.arpa).
     * Used while detecting reverse IP4 questions and answering these
     */
    bool MDNSResponder::_buildDomainForReverseIP4(IPAddress p_IP4Address,
                                                  MDNSResponder::stcMDNS_RRDomain& p_rReverseIP4Domain) const {
        
        bool    bResult = true;
        
        p_rReverseIP4Domain.clear();

        char    acBuffer[32];
        for (int i=MDNS_IP4_SIZE; ((bResult) && (i>=1)); --i) {
            itoa(p_IP4Address[i - 1], acBuffer, 10);
            bResult = p_rReverseIP4Domain.addLabel(acBuffer);
        }
        bResult = ((bResult) &&
                   (p_rReverseIP4Domain.addLabel(scpcReverseIP4Domain)) &&
                   (p_rReverseIP4Domain.addLabel(scpcReverseTopDomain)) &&
                   (p_rReverseIP4Domain.addLabel(0)));
        DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForReverseIP4: FAILED!\n")););
        return bResult;
    }
#endif

#ifdef MDNS_IP6_SUPPORT
    /*
     * MDNSResponder::_buildDomainForReverseIP6
     *
     * Used while detecting reverse IP6 questions and answering these
     */
    bool MDNSResponder::_buildDomainForReverseIP6(IPAddress p_IP4Address,
                                                  MDNSResponder::stcMDNS_RRDomain& p_rReverseIP6Domain) const {
        // TODO: Implement
        return false;
    }
#endif


/*
 * UDP
 */

/*
 * MDNSResponder::_udpReadBuffer
 */
bool MDNSResponder::_udpReadBuffer(unsigned char* p_pBuffer,
                                   size_t p_stLength) {
    
    bool    bResult = ((m_pUDPContext) &&
                       (true/*m_pUDPContext->getSize() > p_stLength*/) &&
                       (p_pBuffer) &&
                       (p_stLength) &&
                       ((p_stLength == m_pUDPContext->read((char*)p_pBuffer, p_stLength))));
    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _udpReadBuffer: FAILED!\n")); });
    return bResult;
}

/*
 * MDNSResponder::_udpRead8
 */
bool MDNSResponder::_udpRead8(uint8_t& p_ru8Value) {
    
   return _udpReadBuffer((unsigned char*)&p_ru8Value, sizeof(p_ru8Value));
}

/*
 * MDNSResponder::_udpRead16
 */
bool MDNSResponder::_udpRead16(uint16_t& p_ru16Value) {
    
    bool    bResult = false;

    if (_udpReadBuffer((unsigned char*)&p_ru16Value, sizeof(p_ru16Value))) {
        p_ru16Value = ntohs(p_ru16Value);
        bResult = true;
    }
    return bResult;
}

/*
 * MDNSResponder::_udpRead32
 */
bool MDNSResponder::_udpRead32(uint32_t& p_ru32Value) {
    
    bool    bResult = false;

    if (_udpReadBuffer((unsigned char*)&p_ru32Value, sizeof(p_ru32Value))) {
        p_ru32Value = ntohl(p_ru32Value);
        bResult = true;
    }
    return bResult;
}

/*
 * MDNSResponder::_udpAppendBuffer
 */
bool MDNSResponder::_udpAppendBuffer(const unsigned char* p_pcBuffer,
                                    size_t p_stLength) {
  
   bool bResult = ((m_pUDPContext) &&
                   (p_pcBuffer) &&
                   (p_stLength) &&
                   (p_stLength == m_pUDPContext->append((const char*)p_pcBuffer, p_stLength)));
    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _udpAppendBuffer: FAILED!\n")); });
    return bResult;
}

/*
 * MDNSResponder::_udpAppend8
 */
bool MDNSResponder::_udpAppend8(uint8_t p_u8Value) {
    
    return (_udpAppendBuffer((unsigned char*)&p_u8Value, sizeof(p_u8Value)));
}

/*
 * MDNSResponder::_udpAppend16
 */
bool MDNSResponder::_udpAppend16(uint16_t p_u16Value) {
    
    p_u16Value = htons(p_u16Value);
    return (_udpAppendBuffer((unsigned char*)&p_u16Value, sizeof(p_u16Value)));
}

/*
 * MDNSResponder::_udpAppend32
 */
bool MDNSResponder::_udpAppend32(uint32_t p_u32Value) {
    
    p_u32Value = htonl(p_u32Value);
    return (_udpAppendBuffer((unsigned char*)&p_u32Value, sizeof(p_u32Value)));
}

#ifdef DEBUG_ESP_MDNS_RESPONDER
    /*
     * MDNSResponder::_udpDump
     */
    bool MDNSResponder::_udpDump(bool p_bMovePointer /*= false*/) {
        
        const uint8_t   cu8BytesPerLine = 16;
        
        uint32_t        u32StartPosition = m_pUDPContext->tell();
        DEBUG_OUTPUT.println("UDP Context Dump:");
        uint32_t    u32Counter = 0;
        uint8_t     u8Byte = 0;

        while (_udpRead8(u8Byte)) {
            DEBUG_OUTPUT.printf_P(PSTR("%02x %s"), u8Byte, ((++u32Counter % cu8BytesPerLine) ? "" : "\n"));
        }
        DEBUG_OUTPUT.printf_P(PSTR("%sDone: %u bytes\n"), (((u32Counter) && (u32Counter % cu8BytesPerLine)) ? "\n" : ""), u32Counter);
        
        if (!p_bMovePointer) {  // Restore
            m_pUDPContext->seek(u32StartPosition);
        }
        return true;
    }

    /*
     * MDNSResponder::_udpDump
     */
    bool MDNSResponder::_udpDump(unsigned p_uOffset,
                                 unsigned p_uLength) {

        if ((m_pUDPContext) &&
            (m_pUDPContext->isValidOffset(p_uOffset))) {
            unsigned    uCurrentPosition = m_pUDPContext->tell();   // Remember start position

            m_pUDPContext->seek(p_uOffset);
            uint8_t u8Byte;
            for (unsigned u=0; ((u<p_uLength) && (_udpRead8(u8Byte))); ++u) {
                DEBUG_OUTPUT.printf_P(PSTR("%02x "), u8Byte);
            }
            // Return to start position
            m_pUDPContext->seek(uCurrentPosition);
        }
        return true;
    }
#endif


/**
 * READ/WRITE MDNS STRUCTS
 */

/*
 * MDNSResponder::_readMDNSMsgHeader
 *
 * Read a MDNS header from the UDP input buffer.
 *   |   8    |   8    |   8    |   8    |
 * 00|   Identifier    |  Flags & Codes  |
 * 01| Question count  |  Answer count   |
 * 02| NS answer count | Ad answer count |
 *
 * All 16-bit and 32-bit elements need to be translated form network coding to host coding (done in _udpRead16 and _udpRead32)
 * In addition, bitfield memory order is undefined in C standard (GCC doesn't order them in the coded direction...), so they
 * need some mapping here
 */
bool MDNSResponder::_readMDNSMsgHeader(MDNSResponder::stcMDNS_MsgHeader& p_rMsgHeader) {

    bool    bResult = false;

    uint8_t u8B1;
    uint8_t u8B2;
    if ((_udpRead16(p_rMsgHeader.m_u16ID)) &&
        (_udpRead8(u8B1))&&
        (_udpRead8(u8B2)) &&
        (_udpRead16(p_rMsgHeader.m_u16QDCount)) &&
        (_udpRead16(p_rMsgHeader.m_u16ANCount)) &&
        (_udpRead16(p_rMsgHeader.m_u16NSCount)) &&
        (_udpRead16(p_rMsgHeader.m_u16ARCount))) {

        p_rMsgHeader.m_1bQR     = (u8B1 & 0x80);    // Query/Responde flag
        p_rMsgHeader.m_4bOpcode = (u8B1 & 0x78);    // Operation code (0: Standard query, others ignored)
        p_rMsgHeader.m_1bAA     = (u8B1 & 0x04);    // Authorative answer
        p_rMsgHeader.m_1bTC     = (u8B1 & 0x02);    // Truncation flag
        p_rMsgHeader.m_1bRD     = (u8B1 & 0x01);    // Recursion desired

        p_rMsgHeader.m_1bRA     = (u8B2 & 0x80);    // Recursion available
        p_rMsgHeader.m_3bZ      = (u8B2 & 0x70);    // Zero
        p_rMsgHeader.m_4bRCode  = (u8B2 & 0x0F);    // Response code

        /*DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readMDNSMsgHeader: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"),
                (unsigned)p_rMsgHeader.m_u16ID,
                (unsigned)p_rMsgHeader.m_1bQR, (unsigned)p_rMsgHeader.m_4bOpcode, (unsigned)p_rMsgHeader.m_1bAA, (unsigned)p_rMsgHeader.m_1bTC, (unsigned)p_rMsgHeader.m_1bRD,
                (unsigned)p_rMsgHeader.m_1bRA, (unsigned)p_rMsgHeader.m_4bRCode,
                (unsigned)p_rMsgHeader.m_u16QDCount,
                (unsigned)p_rMsgHeader.m_u16ANCount,
                (unsigned)p_rMsgHeader.m_u16NSCount,
                (unsigned)p_rMsgHeader.m_u16ARCount););*/
        bResult = true;
    }
    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readMDNSMsgHeader: FAILED!\n")); } );
    return bResult;
}

/*
 * MDNSResponder::_write8
 */
bool MDNSResponder::_write8(uint8_t p_u8Value,
                            MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {
    
    return ((_udpAppend8(p_u8Value)) &&
            (p_rSendParameter.shiftOffset(sizeof(p_u8Value))));
}

/*
 * MDNSResponder::_write16
 */
bool MDNSResponder::_write16(uint16_t p_u16Value,
                             MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {
    
    return ((_udpAppend16(p_u16Value)) &&
            (p_rSendParameter.shiftOffset(sizeof(p_u16Value))));
}

/*
 * MDNSResponder::_write32
 */
bool MDNSResponder::_write32(uint32_t p_u32Value,
                             MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {
    
    return ((_udpAppend32(p_u32Value)) &&
            (p_rSendParameter.shiftOffset(sizeof(p_u32Value))));
}

/*
 * MDNSResponder::_writeMDNSMsgHeader
 *
 * Write MDNS header to the UDP output buffer.
 *
 * All 16-bit and 32-bit elements need to be translated form host coding to network coding (done in _udpAppend16 and _udpAppend32)
 * In addition, bitfield memory order is undefined in C standard (GCC doesn't order them in the coded direction...), so they
 * need some mapping here
 */
bool MDNSResponder::_writeMDNSMsgHeader(const MDNSResponder::stcMDNS_MsgHeader& p_MsgHeader,
                                        MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {
    /*DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSMsgHeader: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"),
            (unsigned)p_MsgHeader.m_u16ID,
            (unsigned)p_MsgHeader.m_1bQR, (unsigned)p_MsgHeader.m_4bOpcode, (unsigned)p_MsgHeader.m_1bAA, (unsigned)p_MsgHeader.m_1bTC, (unsigned)p_MsgHeader.m_1bRD,
            (unsigned)p_MsgHeader.m_1bRA, (unsigned)p_MsgHeader.m_4bRCode,
            (unsigned)p_MsgHeader.m_u16QDCount,
            (unsigned)p_MsgHeader.m_u16ANCount,
            (unsigned)p_MsgHeader.m_u16NSCount,
            (unsigned)p_MsgHeader.m_u16ARCount););*/

    uint8_t u8B1((p_MsgHeader.m_1bQR << 7) | (p_MsgHeader.m_4bOpcode << 3) | (p_MsgHeader.m_1bAA << 2) | (p_MsgHeader.m_1bTC << 1) | (p_MsgHeader.m_1bRD));
    uint8_t u8B2((p_MsgHeader.m_1bRA << 7) | (p_MsgHeader.m_3bZ << 4) | (p_MsgHeader.m_4bRCode));
    bool    bResult = ((_write16(p_MsgHeader.m_u16ID, p_rSendParameter)) &&
                       (_write8(u8B1, p_rSendParameter)) &&
                       (_write8(u8B2, p_rSendParameter)) &&
                       (_write16(p_MsgHeader.m_u16QDCount, p_rSendParameter)) &&
                       (_write16(p_MsgHeader.m_u16ANCount, p_rSendParameter)) &&
                       (_write16(p_MsgHeader.m_u16NSCount, p_rSendParameter)) &&
                       (_write16(p_MsgHeader.m_u16ARCount, p_rSendParameter)));

    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSMsgHeader: FAILED!\n")); });
    return bResult;
}

/*
 * MDNSResponder::_writeRRAttributes
 */
bool MDNSResponder::_writeMDNSRRAttributes(const MDNSResponder::stcMDNS_RRAttributes& p_Attributes,
                                           MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {

    bool    bResult = ((_write16(p_Attributes.m_u16Type, p_rSendParameter)) &&
                       (_write16(p_Attributes.m_u16Class, p_rSendParameter)));

    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSRRAttributes: FAILED!\n")); });
    return bResult;
}

/*
 * MDNSResponder::_writeMDNSRRDomain
 */
bool MDNSResponder::_writeMDNSRRDomain(const MDNSResponder::stcMDNS_RRDomain& p_Domain,
                                     MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {
    
    bool    bResult = ((_udpAppendBuffer((const unsigned char*)p_Domain.m_acName, p_Domain.m_u16NameLength)) &&
                       (p_rSendParameter.shiftOffset(p_Domain.m_u16NameLength)));

    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSRRDomain: FAILED!\n")); });
    return bResult;
}

/*
 * MDNSResponder::_writeMDNSHostDomain
 *
 * Write a host domain to the UDP output buffer.
 * If the domain record is part of the answer, the records length is
 * prepended (p_bPrependRDLength is set).
 *
 * A very simple form of name compression is applied here:
 * If the domain is written to the UDP output buffer, the write offset is stored
 * together with a domain id (the pointer) in a p_rSendParameter substructure (cache).
 * If the same domain (pointer) should be written to the UDP output later again,
 * the old offset is retrieved from the cache, marked as a compressed domain offset
 * and written to the output buffer.
 *
 */
bool MDNSResponder::_writeMDNSHostDomain(const char* p_pcHostname,
                                         bool p_bPrependRDLength,
                                         MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {

    // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV'
    uint16_t            u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)p_pcHostname, false);

    stcMDNS_RRDomain    hostDomain;
    bool    bResult = (u16CachedDomainOffset
                        // Found cached domain -> mark as compressed domain
                        ? ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset
                           ((!p_bPrependRDLength) ||
                            (_write16(2, p_rSendParameter))) &&                                     // Length of 'Cxxx'
                           (_write8(((u16CachedDomainOffset >> 8) | MDNS_DOMAIN_COMPRESS_MARK), p_rSendParameter)) &&   // Compression mark (and offset)
                           (_write8((uint8_t)(u16CachedDomainOffset & 0xFF), p_rSendParameter)))
                        // No cached domain -> add this domain to cache and write full domain name
                        : ((_buildDomainForHost(p_pcHostname, hostDomain)) &&                       // eg. esp8266.local
                           ((!p_bPrependRDLength) ||
                            (_write16(hostDomain.m_u16NameLength, p_rSendParameter))) &&            // RDLength (if needed)
                           (p_rSendParameter.addDomainCacheItem((const void*)p_pcHostname, false, p_rSendParameter.m_u16Offset)) &&
                           (_writeMDNSRRDomain(hostDomain, p_rSendParameter))));

    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSHostDomain: FAILED!\n")); });
    return bResult;

}

/*
 * MDNSResponder::_writeMDNSServiceDomain
 *
 * Write a service domain to the UDP output buffer.
 * If the domain record is part of the answer, the records length is
 * prepended (p_bPrependRDLength is set).
 *
 * A very simple form of name compression is applied here: see '_writeMDNSHostDomain'
 * The cache differentiates of course between service domains which includes
 * the instance name (p_bIncludeName is set) and thoose who don't.
 *
 */
bool MDNSResponder::_writeMDNSServiceDomain(const MDNSResponder::stcMDNSService& p_Service,
                                            bool p_bIncludeName,
                                            bool p_bPrependRDLength,
                                            MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {

    // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV'
    uint16_t            u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)&p_Service, p_bIncludeName);

    stcMDNS_RRDomain    serviceDomain;
    bool    bResult = (u16CachedDomainOffset
                        // Found cached domain -> mark as compressed domain
                        ? ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset
                           ((!p_bPrependRDLength) ||
                            (_write16(2, p_rSendParameter))) &&                                     // Lenght of 'Cxxx'
                           (_write8(((u16CachedDomainOffset >> 8) | MDNS_DOMAIN_COMPRESS_MARK), p_rSendParameter)) &&   // Compression mark (and offset)
                           (_write8((uint8_t)(u16CachedDomainOffset & 0xFF), p_rSendParameter)))
                        // No cached domain -> add this domain to cache and write full domain name
                        : ((_buildDomainForService(p_Service, p_bIncludeName, serviceDomain)) &&    // eg. MyESP._http._tcp.local
                           ((!p_bPrependRDLength) ||
                            (_write16(serviceDomain.m_u16NameLength, p_rSendParameter))) &&         // RDLength (if needed)
                           (p_rSendParameter.addDomainCacheItem((const void*)&p_Service, p_bIncludeName, p_rSendParameter.m_u16Offset)) &&
                           (_writeMDNSRRDomain(serviceDomain, p_rSendParameter))));

    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSServiceDomain: FAILED!\n")); });
    return bResult;

}

/*
 * MDNSResponder::_writeMDNSQuestion
 *
 * Write a MDNS question to the UDP output buffer
 *
 * QNAME  (host/service domain, eg. esp8266.local)
 * QTYPE  (16bit, eg. ANY)
 * QCLASS (16bit, eg. IN)
 *
 */
bool MDNSResponder::_writeMDNSQuestion(MDNSResponder::stcMDNS_RRQuestion& p_Question,
                                       MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {
    DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSQuestion\n")););

    bool    bResult = ((_writeMDNSRRDomain(p_Question.m_Header.m_Domain, p_rSendParameter)) &&
                       (_writeMDNSRRAttributes(p_Question.m_Header.m_Attributes, p_rSendParameter)));

    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSQuestion: FAILED!\n")); });
    return bResult;

}


#ifdef MDNS_IP4_SUPPORT
    /*
     * MDNSResponder::_writeMDNSAnswer_A
     *
     * Write a MDNS A answer to the UDP output buffer.
     *
     * NAME     (var, host/service domain, eg. esp8266.local
     * TYPE     (16bit, eg. A)
     * CLASS    (16bit, eg. IN)
     * TTL      (32bit, eg. 120)
     * RDLENGTH (16bit, eg 4)
     * RDATA    (var, eg. 123.456.789.012)
     *
     * eg. esp8266.local A 0x8001 120 4 123.456.789.012
     * Ref: http://www.zytrax.com/books/dns/ch8/a.html
     */
    bool MDNSResponder::_writeMDNSAnswer_A(IPAddress p_IPAddress,
                                           MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {
        DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_A (%s)\n"), p_IPAddress.toString().c_str()););

        stcMDNS_RRAttributes    attributes(DNS_RRTYPE_A,
                                           ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN));   // Cache flush? & INternet
        const unsigned char     aucIPAddress[MDNS_IP4_SIZE] = { p_IPAddress[0], p_IPAddress[1], p_IPAddress[2], p_IPAddress[3] };
        bool    bResult = ((_writeMDNSHostDomain(m_pcHostname, false, p_rSendParameter)) &&
                           (_writeMDNSRRAttributes(attributes, p_rSendParameter)) &&        // TYPE & CLASS
                           (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) &&    // TTL
                           (_write16(MDNS_IP4_SIZE, p_rSendParameter)) &&                   // RDLength
                           (_udpAppendBuffer(aucIPAddress, MDNS_IP4_SIZE)) &&               // RData
                           (p_rSendParameter.shiftOffset(MDNS_IP4_SIZE)));

        DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_A: FAILED!\n")); });
        return bResult;

    }

    /*
     * MDNSResponder::_writeMDNSAnswer_PTR_IP4
     *
     * Write a MDNS reverse IP4 PTR answer to the UDP output buffer.
     * See: '_writeMDNSAnswer_A'
     *
     * eg. 012.789.456.123.in-addr.arpa PTR 0x8001 120 15 esp8266.local
     * Used while answering reverse IP4 questions
     */
    bool MDNSResponder::_writeMDNSAnswer_PTR_IP4(IPAddress p_IPAddress,
                                                 MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {
        DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_IP4 (%s)\n"), p_IPAddress.toString().c_str()););
        
        stcMDNS_RRDomain        reverseIP4Domain;
        stcMDNS_RRAttributes    attributes(DNS_RRTYPE_PTR,
                                           ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN));   // Cache flush? & INternet
        stcMDNS_RRDomain        hostDomain;
        bool    bResult = ((_buildDomainForReverseIP4(p_IPAddress, reverseIP4Domain)) &&    // 012.789.456.123.in-addr.arpa
                           (_writeMDNSRRDomain(reverseIP4Domain, p_rSendParameter)) &&
                           (_writeMDNSRRAttributes(attributes, p_rSendParameter)) &&        // TYPE & CLASS
                           (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) &&    // TTL
                           (_writeMDNSHostDomain(m_pcHostname, true, p_rSendParameter)));   // RDLength & RData (host domain, eg. esp8266.local)

        DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_IP4: FAILED!\n")); });
        return bResult;
    }
#endif

/*
 * MDNSResponder::_writeMDNSAnswer_PTR_TYPE
 *
 * Write a MDNS PTR answer to the UDP output buffer.
 * See: '_writeMDNSAnswer_A'
 *
 * PTR all-services -> service type
 * eg. _services._dns-sd._udp.local PTR 0x8001 5400 xx _http._tcp.local
 * http://www.zytrax.com/books/dns/ch8/ptr.html
 */
bool MDNSResponder::_writeMDNSAnswer_PTR_TYPE(MDNSResponder::stcMDNSService& p_rService,
                                              MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {
    DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_TYPE\n")););
    
    stcMDNS_RRDomain        dnssdDomain;
    stcMDNS_RRDomain        serviceDomain;
    stcMDNS_RRAttributes    attributes(DNS_RRTYPE_PTR,
                                       ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN));   // Cache flush? & INternet
    bool    bResult = ((_buildDomainForDNSSD(dnssdDomain)) &&                                   // _services._dns-sd._udp.local
                       (_writeMDNSRRDomain(dnssdDomain, p_rSendParameter)) &&
                       (_writeMDNSRRAttributes(attributes, p_rSendParameter)) &&                // TYPE & CLASS
                       (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL
                       (_writeMDNSServiceDomain(p_rService, false, true, p_rSendParameter)));   // RDLength & RData (service domain, eg. _http._tcp.local)

    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_TYPE: FAILED!\n")); });
    return bResult;
}

/*
 * MDNSResponder::_writeMDNSAnswer_PTR_NAME
 *
 * Write a MDNS PTR answer to the UDP output buffer.
 * See: '_writeMDNSAnswer_A'
 *
 * PTR service type -> service name
 * eg. _http.tcp.local PTR 0x8001 120 xx myESP._http._tcp.local
 * http://www.zytrax.com/books/dns/ch8/ptr.html
 */ 
bool MDNSResponder::_writeMDNSAnswer_PTR_NAME(MDNSResponder::stcMDNSService& p_rService,
                                              MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {
    DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_NAME\n")););

    stcMDNS_RRAttributes    attributes(DNS_RRTYPE_PTR,
                                       ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN));   // Cache flush? & INternet
    bool    bResult = ((_writeMDNSServiceDomain(p_rService, false, false, p_rSendParameter)) && // _http._tcp.local
                       (_writeMDNSRRAttributes(attributes, p_rSendParameter)) &&                    // TYPE & CLASS
                       (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL
                       (_writeMDNSServiceDomain(p_rService, true, true, p_rSendParameter)));        // RDLength & RData (service domain, eg. MyESP._http._tcp.local)

    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_NAME: FAILED!\n")); });
    return bResult;
}


/*
 * MDNSResponder::_writeMDNSAnswer_TXT
 *
 * Write a MDNS TXT answer to the UDP output buffer.
 * See: '_writeMDNSAnswer_A'
 *
 * The TXT items in the RDATA block are 'length byte encoded': [len]vardata
 *
 * eg. myESP._http._tcp.local TXT 0x8001 120 4 c#=1
 * http://www.zytrax.com/books/dns/ch8/txt.html
 */
bool MDNSResponder::_writeMDNSAnswer_TXT(MDNSResponder::stcMDNSService& p_rService,
                                         MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {
    DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_TXT\n")););
    
    bool                    bResult = false;

    stcMDNS_RRAttributes    attributes(DNS_RRTYPE_TXT,
                                       ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN));   // Cache flush? & INternet
    
    if ((_collectServiceTxts(p_rService)) &&
        (_writeMDNSServiceDomain(p_rService, true, false, p_rSendParameter)) &&     // MyESP._http._tcp.local
        (_writeMDNSRRAttributes(attributes, p_rSendParameter)) &&                   // TYPE & CLASS
        (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) &&    // TTL
        (_write16(p_rService.m_Txts.length(), p_rSendParameter))) {                 // RDLength
        
        bResult = true;
        // RData    Txts
        for (stcMDNSServiceTxt* pTxt=p_rService.m_Txts.m_pTxts; ((bResult) && (pTxt)); pTxt = pTxt->m_pNext) {
            unsigned char       ucLengthByte = pTxt->length();
            bResult = ((_udpAppendBuffer((unsigned char*)&ucLengthByte, sizeof(ucLengthByte))) &&   // Length
                       (p_rSendParameter.shiftOffset(sizeof(ucLengthByte))) &&
                       ((size_t)os_strlen(pTxt->m_pcKey) == m_pUDPContext->append(pTxt->m_pcKey, os_strlen(pTxt->m_pcKey))) &&          // Key
                       (p_rSendParameter.shiftOffset((size_t)os_strlen(pTxt->m_pcKey))) &&
                       (1 == m_pUDPContext->append("=", 1)) &&                                                                          // =
                       (p_rSendParameter.shiftOffset(1)) &&
                       ((!pTxt->m_pcValue) ||
                        (((size_t)os_strlen(pTxt->m_pcValue) == m_pUDPContext->append(pTxt->m_pcValue, os_strlen(pTxt->m_pcValue))) &&  // Value
                         (p_rSendParameter.shiftOffset((size_t)os_strlen(pTxt->m_pcValue))))));

            DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_TXT: FAILED to write %sTxt %s=%s!\n"), (pTxt->m_bTemp ? "temp. " : ""), (pTxt->m_pcKey ?: "?"), (pTxt->m_pcValue ?: "?")); });
        }
    }
    _releaseTempServiceTxts(p_rService);
    
    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_TXT: FAILED!\n")); });
    return bResult;
}

#ifdef MDNS_IP6_SUPPORT
    /*
     * MDNSResponder::_writeMDNSAnswer_AAAA
     *
     * Write a MDNS AAAA answer to the UDP output buffer.
     * See: '_writeMDNSAnswer_A'
     *
     * eg. esp8266.local AAAA 0x8001 120 16 xxxx::xx
     * http://www.zytrax.com/books/dns/ch8/aaaa.html
     */
    bool MDNSResponder::_writeMDNSAnswer_AAAA(IPAddress p_IPAddress,
                                              MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {
        DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_AAAA\n")););

        stcMDNS_RRAttributes    attributes(DNS_RRTYPE_AAAA,
                                           ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN));   // Cache flush? & INternet
        bool    bResult = ((_writeMDNSHostDomain(m_pcHostname, false, p_rSendParameter)) && // esp8266.local
                           (_writeMDNSRRAttributes(attributes, p_rSendParameter)) &&            // TYPE & CLASS
                           (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) &&    // TTL
                           (_write16(MDNS_IP6_SIZE, p_rSendParameter)) &&                       // RDLength
                           (false /*TODO: IP6 version of: _udpAppendBuffer((uint32_t)p_IPAddress, MDNS_IP4_SIZE)*/));   // RData

        DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_AAAA: FAILED!\n")); });
        return bResult;
    }
    
    /*
     * MDNSResponder::_writeMDNSAnswer_PTR_IP6
     *
     * Write a MDNS reverse IP6 PTR answer to the UDP output buffer.
     * See: '_writeMDNSAnswer_A'
     *
     * eg. xxxx::xx.in6.arpa PTR 0x8001 120 15 esp8266.local
     * Used while answering reverse IP6 questions
     */
    bool MDNSResponder::_writeMDNSAnswer_PTR_IP6(IPAddress p_IPAddress,
                                                 MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {
        DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_IP6\n")););
        
        stcMDNS_RRDomain        reverseIP6Domain;
        stcMDNS_RRAttributes    attributes(DNS_RRTYPE_PTR,
                                           ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN));   // Cache flush? & INternet
        bool    bResult = ((_buildDomainForReverseIP6(p_IPAddress, reverseIP6Domain)) &&        // xxxx::xx.ip6.arpa
                           (_writeMDNSRRDomain(reverseIP6Domain, p_rSendParameter)) &&
                           (_writeMDNSRRAttributes(attributes, p_rSendParameter)) &&            // TYPE & CLASS
                           (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) &&    // TTL
                           (_writeMDNSHostDomain(m_pcHostname, true, p_rSendParameter)));       // RDLength & RData (host domain, eg. esp8266.local)

        DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_IP6: FAILED!\n")); });
        return bResult;
    }
#endif

/*
 * MDNSResponder::_writeMDNSAnswer_SRV
 *
 * eg. MyESP._http.tcp.local SRV 0x8001 120 0 0 60068 esp8266.local
 * http://www.zytrax.com/books/dns/ch8/srv.html ???? Include instance name ????
 */
bool MDNSResponder::_writeMDNSAnswer_SRV(MDNSResponder::stcMDNSService& p_rService,
                                         MDNSResponder::stcMDNSSendParameter& p_rSendParameter) {
    DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_SRV\n")););

    uint16_t                u16CachedDomainOffset = (p_rSendParameter.m_bLegacyQuery
                                                        ? 0
                                                        : p_rSendParameter.findCachedDomainOffset((const void*)m_pcHostname, false));

    stcMDNS_RRAttributes    attributes(DNS_RRTYPE_SRV,
                                       ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN));   // Cache flush? & INternet
    stcMDNS_RRDomain        hostDomain;
    bool    bResult = ((_writeMDNSServiceDomain(p_rService, true, false, p_rSendParameter)) &&  // MyESP._http._tcp.local
                       (_writeMDNSRRAttributes(attributes, p_rSendParameter)) &&                // TYPE & CLASS
                       (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL
                       (!u16CachedDomainOffset
                            // No cache for domain name (or no compression allowed)
                            ? ((_buildDomainForHost(m_pcHostname, hostDomain)) &&
                               (_write16((sizeof(uint16_t /*Prio*/) +                           // RDLength
                                          sizeof(uint16_t /*Weight*/) +
                                          sizeof(uint16_t /*Port*/) +
                                          hostDomain.m_u16NameLength), p_rSendParameter)) &&    // Domain length
                               (_write16(MDNS_SRV_PRIORITY, p_rSendParameter)) &&               // Priority
                               (_write16(MDNS_SRV_WEIGHT, p_rSendParameter)) &&                 // Weight
                               (_write16(p_rService.m_u16Port, p_rSendParameter)) &&            // Port
                               (p_rSendParameter.addDomainCacheItem((const void*)m_pcHostname, false, p_rSendParameter.m_u16Offset)) &&
                               (_writeMDNSRRDomain(hostDomain, p_rSendParameter)))              // Host, eg. esp8266.local
                            // Cache available for domain
                            : ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset
                               (_write16((sizeof(uint16_t /*Prio*/) +                           // RDLength
                                          sizeof(uint16_t /*Weight*/) +
                                          sizeof(uint16_t /*Port*/) +
                                          2), p_rSendParameter)) &&                             // Length of 'C0xx'
                               (_write16(MDNS_SRV_PRIORITY, p_rSendParameter)) &&               // Priority
                               (_write16(MDNS_SRV_WEIGHT, p_rSendParameter)) &&                 // Weight
                               (_write16(p_rService.m_u16Port, p_rSendParameter)) &&            // Port
                               (_write8(((u16CachedDomainOffset >> 8) | MDNS_DOMAIN_COMPRESS_MARK), p_rSendParameter)) &&   // Compression mark (and offset)
                               (_write8((uint8_t)u16CachedDomainOffset, p_rSendParameter)))));  // Offset

    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_SRV: FAILED!\n")); });
    return bResult;
}

}   // namespace MDNSImplementation

} // namespace esp8266






