/*
 * LEAmDNS_Helpers.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.
 *
 */

#include "lwip/igmp.h"

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


namespace {

/*
 * strrstr (static)
 *
 * Backwards search for p_pcPattern in p_pcString
 * Based on: https://stackoverflow.com/a/1634398/2778898
 *
 */
const char* strrstr(const char*__restrict p_pcString, const char*__restrict p_pcPattern) {
    
    const char* pcResult = 0;
    
    size_t      stStringLength = (p_pcString ? strlen(p_pcString) : 0);
    size_t      stPatternLength = (p_pcPattern ? strlen(p_pcPattern) : 0);

    if ((stStringLength) &&
        (stPatternLength) &&
        (stPatternLength <= stStringLength)) {
        // Pattern is shorter or has the same length tham the string
        
        for (const char* s=(p_pcString + stStringLength - stPatternLength); s>=p_pcString; --s) {
            if (0 == strncmp(s, p_pcPattern, stPatternLength)) {
                pcResult = s;
                break;
            }
        }
    }
    return pcResult;
}


} // anonymous





namespace esp8266 {

/*
 * LEAmDNS
 */
namespace MDNSImplementation {

/**
 * HELPERS
 */

/*
 * MDNSResponder::indexDomain (static)
 *
 * Updates the given domain 'p_rpcHostname' by appending a delimiter and an index number.
 *
 * If the given domain already hasa numeric index (after the given delimiter), this index
 * incremented. If not, the delimiter and index '2' is added.
 *
 * If 'p_rpcHostname' is empty (==0), the given default name 'p_pcDefaultHostname' is used,
 * if no default is given, 'esp8266' is used.
 *
 */
/*static*/ bool MDNSResponder::indexDomain(char*& p_rpcDomain,
                                           const char* p_pcDivider /*= "-"*/,
                                           const char* p_pcDefaultDomain /*= 0*/) {

    bool    bResult = false;

    // Ensure a divider exists; use '-' as default
    const char*   pcDivider = (p_pcDivider ?: "-");

    if (p_rpcDomain) {
        const char* pFoundDivider = strrstr(p_rpcDomain, pcDivider);
        if (pFoundDivider) {    // maybe already extended
            char*         pEnd = 0;
            unsigned long ulIndex = strtoul((pFoundDivider + strlen(pcDivider)), &pEnd, 10);
            if ((ulIndex) &&
                ((pEnd - p_rpcDomain) == (ptrdiff_t)strlen(p_rpcDomain)) &&
                (!*pEnd)) {       // Valid (old) index found

                char    acIndexBuffer[16];
                sprintf(acIndexBuffer, "%lu", (++ulIndex));
                size_t  stLength = ((pFoundDivider - p_rpcDomain + strlen(pcDivider)) + strlen(acIndexBuffer) + 1);
                char*   pNewHostname = new char[stLength];
                if (pNewHostname) {
                    memcpy(pNewHostname, p_rpcDomain, (pFoundDivider - p_rpcDomain + strlen(pcDivider)));
                    pNewHostname[pFoundDivider - p_rpcDomain + strlen(pcDivider)] = 0;
                    strcat(pNewHostname, acIndexBuffer);

                    delete[] p_rpcDomain;
                    p_rpcDomain = pNewHostname;

                    bResult = true;
                }
                else {
                    DEBUG_EX_ERR(DEBUG_OUTPUT.println(F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!")););
                }
            }
            else {
                pFoundDivider = 0;  // Flag the need to (base) extend the hostname
            }
        }

        if (!pFoundDivider) {   // not yet extended (or failed to increment extension) -> start indexing
            size_t    stLength = strlen(p_rpcDomain) + (strlen(pcDivider) + 1 + 1);   // Name + Divider + '2' + '\0'
            char*     pNewHostname = new char[stLength];
            if (pNewHostname) {
                sprintf(pNewHostname, "%s%s2", p_rpcDomain, pcDivider);

                delete[] p_rpcDomain;
                p_rpcDomain = pNewHostname;

                bResult = true;
            }
            else {
                DEBUG_EX_ERR(DEBUG_OUTPUT.println(F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!")););
            }
        }
    }
    else {
        // No given host domain, use base or default
        const char* cpcDefaultName = (p_pcDefaultDomain ?: "esp8266");

        size_t      stLength = strlen(cpcDefaultName) + 1;   // '\0'
        p_rpcDomain = new char[stLength];
        if (p_rpcDomain) {
            strncpy(p_rpcDomain, cpcDefaultName, stLength);
            bResult = true;
        }
        else {
            DEBUG_EX_ERR(DEBUG_OUTPUT.println(F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!")););
        }
    }
    DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] indexDomain: %s\n"), p_rpcDomain););
    return bResult;
}


/*
 * UDP CONTEXT
 */

bool MDNSResponder::_callProcess(void) {
    DEBUG_EX_INFO(DEBUG_OUTPUT.printf("[MDNSResponder] _callProcess (%lu, triggered by: %s)\n", millis(), m_pUDPContext->getRemoteAddress().toString().c_str()););

    return _process(false);
}

/*
 * MDNSResponder::_allocUDPContext
 *
 * (Re-)Creates the one-and-only UDP context for the MDNS responder.
 * The context is added to the 'multicast'-group and listens to the MDNS port (5353).
 * The travel-distance for multicast messages is set to 1 (local, via MDNS_MULTICAST_TTL).
 * Messages are received via the MDNSResponder '_update' function. CAUTION: This function
 * is called from the WiFi stack side of the ESP stack system.
 *
 */
bool MDNSResponder::_allocUDPContext(void) {
    DEBUG_EX_INFO(DEBUG_OUTPUT.println("[MDNSResponder] _allocUDPContext"););
    
    bool    bResult = false;
    
    _releaseUDPContext();

#ifdef MDNS_IP4_SUPPORT
    ip_addr_t   multicast_addr = DNS_MQUERY_IPV4_GROUP_INIT;
#endif
#ifdef MDNS_IP6_SUPPORT
    //TODO: set multicast address (lwip_joingroup() is IPv4 only at the time of writing)
    multicast_addr.addr = DNS_MQUERY_IPV6_GROUP_INIT;
#endif
    if (ERR_OK == igmp_joingroup(IP4_ADDR_ANY4, ip_2_ip4(&multicast_addr))) {
        m_pUDPContext = new UdpContext;
        m_pUDPContext->ref();

        if (m_pUDPContext->listen(IP4_ADDR_ANY, DNS_MQUERY_PORT)) {
            m_pUDPContext->setMulticastTTL(MDNS_MULTICAST_TTL);
            m_pUDPContext->onRx(std::bind(&MDNSResponder::_callProcess, this));
            
            bResult = m_pUDPContext->connect(&multicast_addr, DNS_MQUERY_PORT);
        }
    }
    return bResult;
}

/*
 * MDNSResponder::_releaseUDPContext
 */
bool MDNSResponder::_releaseUDPContext(void) {

    if (m_pUDPContext) {
        m_pUDPContext->unref();
        m_pUDPContext = 0;
    }
    return true;
}


/*
 * SERVICE QUERY
 */

/*
 * MDNSResponder::_allocServiceQuery
 */
MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_allocServiceQuery(void) {
    
    stcMDNSServiceQuery*    pServiceQuery = new stcMDNSServiceQuery;
    if (pServiceQuery) {
        // Link to query list
        pServiceQuery->m_pNext = m_pServiceQueries;
        m_pServiceQueries = pServiceQuery;
    }
    return m_pServiceQueries;
}

/*
 * MDNSResponder::_removeServiceQuery
 */
bool MDNSResponder::_removeServiceQuery(MDNSResponder::stcMDNSServiceQuery* p_pServiceQuery) {
    
    bool    bResult = false;
    
    if (p_pServiceQuery) {
        stcMDNSServiceQuery*    pPred = m_pServiceQueries;
        while ((pPred) &&
               (pPred->m_pNext != p_pServiceQuery)) {
            pPred = pPred->m_pNext;
        }
        if (pPred) {
            pPred->m_pNext = p_pServiceQuery->m_pNext;
            delete p_pServiceQuery;
            bResult = true;
        }
        else {  // No predecesor
            if (m_pServiceQueries == p_pServiceQuery) {
                m_pServiceQueries = p_pServiceQuery->m_pNext;
                delete p_pServiceQuery;
                bResult = true;
            }
            else {
                DEBUG_EX_ERR(DEBUG_OUTPUT.println("[MDNSResponder] _releaseServiceQuery: INVALID service query!"););
            }
        }
    }
    return bResult;
}

/*
 * MDNSResponder::_removeLegacyServiceQuery
 */
bool MDNSResponder::_removeLegacyServiceQuery(void) {
    
    stcMDNSServiceQuery*    pLegacyServiceQuery = _findLegacyServiceQuery();
    return (pLegacyServiceQuery ? _removeServiceQuery(pLegacyServiceQuery) : true);
}

/*
 * MDNSResponder::_findServiceQuery
 *
 * 'Convert' hMDNSServiceQuery to stcMDNSServiceQuery* (ensure existance)
 *
 */
MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_findServiceQuery(MDNSResponder::hMDNSServiceQuery p_hServiceQuery) {
    
    stcMDNSServiceQuery*    pServiceQuery = m_pServiceQueries;
    while (pServiceQuery) {
        if ((hMDNSServiceQuery)pServiceQuery == p_hServiceQuery) {
            break;
        }
        pServiceQuery = pServiceQuery->m_pNext;
    }
    return pServiceQuery;
}

/*
 * MDNSResponder::_findLegacyServiceQuery
 */
MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_findLegacyServiceQuery(void) {
    
    stcMDNSServiceQuery*    pServiceQuery = m_pServiceQueries;
    while (pServiceQuery) {
        if (pServiceQuery->m_bLegacyQuery) {
            break;
        }
        pServiceQuery = pServiceQuery->m_pNext;
    }
    return pServiceQuery;
}

/*
 * MDNSResponder::_releaseServiceQueries
 */
bool MDNSResponder::_releaseServiceQueries(void) {
    while (m_pServiceQueries) {
        stcMDNSServiceQuery*    pNext = m_pServiceQueries->m_pNext;
        delete m_pServiceQueries;
        m_pServiceQueries = pNext;
    }
    return true;
}

/*
 * MDNSResponder::_findNextServiceQueryByServiceType
 */
MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_findNextServiceQueryByServiceType(const stcMDNS_RRDomain& p_ServiceTypeDomain,
                                                                                      const stcMDNSServiceQuery* p_pPrevServiceQuery) {
    stcMDNSServiceQuery*    pMatchingServiceQuery = 0;

    stcMDNSServiceQuery*    pServiceQuery = (p_pPrevServiceQuery ? p_pPrevServiceQuery->m_pNext : m_pServiceQueries);
    while (pServiceQuery) {
        if (p_ServiceTypeDomain == pServiceQuery->m_ServiceTypeDomain) {
            pMatchingServiceQuery = pServiceQuery;
            break;
        }
        pServiceQuery = pServiceQuery->m_pNext;
    }
    return pMatchingServiceQuery;
}


/*
 * HOSTNAME
 */

/*
 * MDNSResponder::_setHostname
 */
bool MDNSResponder::_setHostname(const char* p_pcHostname) {
    //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _allocHostname (%s)\n"), p_pcHostname););
    
    bool    bResult = false;
    
    _releaseHostname();
    
    size_t  stLength = 0;
    if ((p_pcHostname) &&
        (MDNS_DOMAIN_LABEL_MAXLENGTH >= (stLength = strlen(p_pcHostname)))) { // char max size for a single label
        // Copy in hostname characters as lowercase
        if ((bResult = (0 != (m_pcHostname = new char[stLength + 1])))) {
#ifdef MDNS_FORCE_LOWERCASE_HOSTNAME
            size_t i = 0;
            for (; i<stLength; ++i) {
                m_pcHostname[i] = (isupper(p_pcHostname[i]) ? tolower(p_pcHostname[i]) : p_pcHostname[i]);
            }
            m_pcHostname[i] = 0;
#else
            strncpy(m_pcHostname, p_pcHostname, (stLength + 1));
#endif
        }
    }
    return bResult;
}

/*
 * MDNSResponder::_releaseHostname
 */
bool MDNSResponder::_releaseHostname(void) {
    
    if (m_pcHostname) {
        delete[] m_pcHostname;
        m_pcHostname = 0;
    }
    return true;
}


/*
 * SERVICE
 */

/*
 * MDNSResponder::_allocService
 */
MDNSResponder::stcMDNSService* MDNSResponder::_allocService(const char* p_pcName,
                                                            const char* p_pcService,
                                                            const char* p_pcProtocol,
                                                            uint16_t p_u16Port) {
    
    stcMDNSService* pService = 0;
    if (((!p_pcName) ||
         (MDNS_DOMAIN_LABEL_MAXLENGTH >= strlen(p_pcName))) &&
        (p_pcService) &&
        (MDNS_SERVICE_NAME_LENGTH >= strlen(p_pcService)) &&
        (p_pcProtocol) &&
        (MDNS_SERVICE_PROTOCOL_LENGTH >= strlen(p_pcProtocol)) &&
        (p_u16Port) &&
        (0 != (pService = new stcMDNSService)) &&
        (pService->setName(p_pcName ?: m_pcHostname)) &&
        (pService->setService(p_pcService)) &&
        (pService->setProtocol(p_pcProtocol))) {
        
        pService->m_bAutoName = (0 == p_pcName);
        pService->m_u16Port = p_u16Port;
        
        // Add to list (or start list)
        pService->m_pNext = m_pServices;
        m_pServices = pService;
    }
    return pService;
}

/*
 * MDNSResponder::_releaseService
 */
bool MDNSResponder::_releaseService(MDNSResponder::stcMDNSService* p_pService) {
    
    bool    bResult = false;
    
    if (p_pService) {
        stcMDNSService* pPred = m_pServices;
        while ((pPred) &&
               (pPred->m_pNext != p_pService)) {
            pPred = pPred->m_pNext;
        }
        if (pPred) {
            pPred->m_pNext = p_pService->m_pNext;
            delete p_pService;
            bResult = true;
        }
        else {  // No predecesor
            if (m_pServices == p_pService) {
                m_pServices = p_pService->m_pNext;
                delete p_pService;
                bResult = true;
            }
            else {
                DEBUG_EX_ERR(DEBUG_OUTPUT.println("[MDNSResponder] _releaseService: INVALID service!"););
            }
        }
    }
    return bResult;
}

/*
 * MDNSResponder::_releaseServices
 */
bool MDNSResponder::_releaseServices(void) {
    
    stcMDNSService* pService = m_pServices;
    while (pService) {
        _releaseService(pService);
        pService = m_pServices;
    }
    return true;
}

/*
 * MDNSResponder::_findService
 */
MDNSResponder::stcMDNSService* MDNSResponder::_findService(const char* p_pcName,
                                                           const char* p_pcService,
                                                           const char* p_pcProtocol) {
    
    stcMDNSService* pService = m_pServices;
    while (pService) {
        if ((0 == strcmp(pService->m_pcName, p_pcName)) &&
            (0 == strcmp(pService->m_pcService, p_pcService)) &&
            (0 == strcmp(pService->m_pcProtocol, p_pcProtocol))) {
            
            break;
        }
        pService = pService->m_pNext;
    }
    return pService;
}

/*
 * MDNSResponder::_findService
 */
MDNSResponder::stcMDNSService* MDNSResponder::_findService(const MDNSResponder::hMDNSService p_hService) {
    
    stcMDNSService* pService = m_pServices;
    while (pService) {
        if (p_hService == (hMDNSService)pService) {
            break;
        }
        pService = pService->m_pNext;
    }
    return pService;
}


/*
 * SERVICE TXT
 */

/*
 * MDNSResponder::_allocServiceTxt
 */
MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_allocServiceTxt(MDNSResponder::stcMDNSService* p_pService,
                                                                  const char* p_pcKey,
                                                                  const char* p_pcValue,
                                                                  bool p_bTemp) {

    stcMDNSServiceTxt*  pTxt = 0;
    
    if ((p_pService) &&
        (p_pcKey) &&
        (MDNS_SERVICE_TXT_MAXLENGTH > (p_pService->m_Txts.length() +
                                        1 +                                 // Length byte
                                        (p_pcKey ? strlen(p_pcKey) : 0) +
                                        1 +                                 // '='
                                        (p_pcValue ? strlen(p_pcValue) : 0)))) {
        
        pTxt = new stcMDNSServiceTxt;
        if (pTxt) {
            size_t  stLength = (p_pcKey ? strlen(p_pcKey) : 0);
            pTxt->m_pcKey = new char[stLength + 1];
            if (pTxt->m_pcKey) {
                strncpy(pTxt->m_pcKey, p_pcKey, stLength); pTxt->m_pcKey[stLength] = 0;
            }
            
            if (p_pcValue) {
                stLength = (p_pcValue ? strlen(p_pcValue) : 0);
                pTxt->m_pcValue = new char[stLength + 1];
                if (pTxt->m_pcValue) {
                    strncpy(pTxt->m_pcValue, p_pcValue, stLength); pTxt->m_pcValue[stLength] = 0;
                }
            }
            pTxt->m_bTemp = p_bTemp;
            
            // Add to list (or start list)
            p_pService->m_Txts.add(pTxt);
        }
    }
    return pTxt;
}

/*
 * MDNSResponder::_releaseServiceTxt
 */
bool MDNSResponder::_releaseServiceTxt(MDNSResponder::stcMDNSService* p_pService,
                                       MDNSResponder::stcMDNSServiceTxt* p_pTxt) {
    
    return ((p_pService) &&
            (p_pTxt) &&
            (p_pService->m_Txts.remove(p_pTxt)));
}

/*
 * MDNSResponder::_updateServiceTxt
 */
MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_updateServiceTxt(MDNSResponder::stcMDNSService* p_pService,
                                                                   MDNSResponder::stcMDNSServiceTxt* p_pTxt,
                                                                   const char* p_pcValue,
                                                                   bool p_bTemp) {

    if ((p_pService) &&
        (p_pTxt) &&
        (MDNS_SERVICE_TXT_MAXLENGTH > (p_pService->m_Txts.length() -
                                        (p_pTxt->m_pcValue ? strlen(p_pTxt->m_pcValue) : 0) +
                                        (p_pcValue ? strlen(p_pcValue) : 0)))) {
        p_pTxt->update(p_pcValue);
        p_pTxt->m_bTemp = p_bTemp;
    }
    return p_pTxt;
}

/*
 * MDNSResponder::_findServiceTxt
 */
MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_findServiceTxt(MDNSResponder::stcMDNSService* p_pService,
                                                                 const char* p_pcKey) {
    
    return (p_pService ? p_pService->m_Txts.find(p_pcKey) : 0);
}

/*
 * MDNSResponder::_findServiceTxt
 */
MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_findServiceTxt(MDNSResponder::stcMDNSService* p_pService,
                                                                 const hMDNSTxt p_hTxt) {
    
    return (((p_pService) && (p_hTxt)) ? p_pService->m_Txts.find((stcMDNSServiceTxt*)p_hTxt) : 0);
}

/*
 * MDNSResponder::_addServiceTxt
 */
MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_addServiceTxt(MDNSResponder::stcMDNSService* p_pService,
                                                                const char* p_pcKey,
                                                                const char* p_pcValue,
                                                                bool p_bTemp) {
    stcMDNSServiceTxt*  pResult = 0;
    
    if ((p_pService) &&
        (p_pcKey) &&
        (strlen(p_pcKey))) {
        
        stcMDNSServiceTxt*  pTxt = p_pService->m_Txts.find(p_pcKey);
        if (pTxt) {
            pResult = _updateServiceTxt(p_pService, pTxt, p_pcValue, p_bTemp);
        }
        else {
            pResult = _allocServiceTxt(p_pService, p_pcKey, p_pcValue, p_bTemp);
        }
    }
    return pResult;
}

MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_answerKeyValue(const hMDNSServiceQuery p_hServiceQuery,
                                                                 const uint32_t p_u32AnswerIndex) {
    stcMDNSServiceQuery*            pServiceQuery = _findServiceQuery(p_hServiceQuery);
    stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
    // Fill m_pcTxts (if not already done)
    return (pSQAnswer) ?  pSQAnswer->m_Txts.m_pTxts : 0;
}

/*
 * MDNSResponder::_collectServiceTxts
 */
bool MDNSResponder::_collectServiceTxts(MDNSResponder::stcMDNSService& p_rService) {
    
	// Call Dynamic service callbacks
    if (m_fnServiceTxtCallback) {
    	m_fnServiceTxtCallback((hMDNSService)&p_rService);
    }
    if (p_rService.m_fnTxtCallback) {
        p_rService.m_fnTxtCallback((hMDNSService)&p_rService);
    }
    return true;
}

/*
 * MDNSResponder::_releaseTempServiceTxts
 */
bool MDNSResponder::_releaseTempServiceTxts(MDNSResponder::stcMDNSService& p_rService) {
    
    return (p_rService.m_Txts.removeTempTxts());
}


/*
 * MISC
 */

#ifdef DEBUG_ESP_MDNS_RESPONDER
    /*
     * MDNSResponder::_printRRDomain
     */
    bool MDNSResponder::_printRRDomain(const MDNSResponder::stcMDNS_RRDomain& p_RRDomain) const {

        //DEBUG_OUTPUT.printf_P(PSTR("Domain: "));

        const char* pCursor = p_RRDomain.m_acName;
        uint8_t     u8Length = *pCursor++;
        if (u8Length) {
            while (u8Length) {
                for (uint8_t u=0; u<u8Length; ++u) {
                    DEBUG_OUTPUT.printf_P(PSTR("%c"), *(pCursor++));
                }
                u8Length = *pCursor++;
                if (u8Length) {
                    DEBUG_OUTPUT.printf_P(PSTR("."));
                }
            }
        }
        else {  // empty domain
            DEBUG_OUTPUT.printf_P(PSTR("-empty-"));
        }
        //DEBUG_OUTPUT.printf_P(PSTR("\n"));

        return true;
    }

    /*
     * MDNSResponder::_printRRAnswer
     */
    bool MDNSResponder::_printRRAnswer(const MDNSResponder::stcMDNS_RRAnswer& p_RRAnswer) const {

        DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] RRAnswer: "));
        _printRRDomain(p_RRAnswer.m_Header.m_Domain);
        DEBUG_OUTPUT.printf_P(PSTR(" Type:0x%04X Class:0x%04X TTL:%u, "), p_RRAnswer.m_Header.m_Attributes.m_u16Type, p_RRAnswer.m_Header.m_Attributes.m_u16Class, p_RRAnswer.m_u32TTL);
        switch (p_RRAnswer.m_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"), ((const stcMDNS_RRAnswerA*)&p_RRAnswer)->m_IPAddress.toString().c_str());
            break;
#endif
        case DNS_RRTYPE_PTR:
            DEBUG_OUTPUT.printf_P(PSTR("PTR "));
            _printRRDomain(((const stcMDNS_RRAnswerPTR*)&p_RRAnswer)->m_PTRDomain);
            break;
        case DNS_RRTYPE_TXT: {
            size_t  stTxtLength = ((const stcMDNS_RRAnswerTXT*)&p_RRAnswer)->m_Txts.c_strLength();
            char*   pTxts = new char[stTxtLength];
            if (pTxts) {
                ((/*const c_str()!!*/stcMDNS_RRAnswerTXT*)&p_RRAnswer)->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 "), ((const stcMDNS_RRAnswerSRV*)&p_RRAnswer)->m_u16Port);
            _printRRDomain(((const stcMDNS_RRAnswerSRV*)&p_RRAnswer)->m_SRVDomain);
            break;
        default:
            DEBUG_OUTPUT.printf_P(PSTR("generic "));
            break;
        }
        DEBUG_OUTPUT.printf_P(PSTR("\n"));

        return true;
    }
#endif

}   // namespace MDNSImplementation

} // namespace esp8266




