/* * 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(), IPAddress(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(ip_2_ip4(&m_netif->ip_addr), 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= 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; um_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