diff --git a/cores/esp8266/IPAddress.h b/cores/esp8266/IPAddress.h index 33305f424..c07011888 100644 --- a/cores/esp8266/IPAddress.h +++ b/cores/esp8266/IPAddress.h @@ -32,6 +32,13 @@ struct ip_addr: ipv4_addr { }; #endif // !LWIP_IPV6 +// to display a netif id with printf: +#define NETIFID_STR "%c%c%u" +#define NETIFID_VAL(netif) \ + ((netif)? (netif)->name[0]: '-'), \ + ((netif)? (netif)->name[1]: '-'), \ + ((netif)? netif_get_index(netif): 42) + // A class to make it easier to handle and pass around IP addresses // IPv6 update: // IPAddress is now a decorator class for lwIP's ip_addr_t diff --git a/cores/esp8266/LwipIntf.h b/cores/esp8266/LwipIntf.h new file mode 100644 index 000000000..cb8bb92dc --- /dev/null +++ b/cores/esp8266/LwipIntf.h @@ -0,0 +1,26 @@ + +#ifndef _LWIPINTF_H +#define _LWIPINTF_H + +#include + +#include + +class LwipIntf +{ +public: + + using CBType = std::function ; + + static bool stateUpCB (LwipIntf::CBType&& cb); + +private: + + LwipIntf () { } // private, cannot be directly allocated + +protected: + + static bool stateChangeSysCB (LwipIntf::CBType&& cb); +}; + +#endif // _LWIPINTF_H diff --git a/cores/esp8266/LwipIntfCB.cpp b/cores/esp8266/LwipIntfCB.cpp new file mode 100644 index 000000000..b978918a7 --- /dev/null +++ b/cores/esp8266/LwipIntfCB.cpp @@ -0,0 +1,42 @@ + +#include +#include +#include + +#define NETIF_STATUS_CB_SIZE 3 + +static int netifStatusChangeListLength = 0; +LwipIntf::CBType netifStatusChangeList [NETIF_STATUS_CB_SIZE]; + +extern "C" void netif_status_changed (struct netif* netif) +{ + // override the default empty weak function + for (int i = 0; i < netifStatusChangeListLength; i++) + netifStatusChangeList[i](netif); +} + +bool LwipIntf::stateChangeSysCB (LwipIntf::CBType&& cb) +{ + if (netifStatusChangeListLength >= NETIF_STATUS_CB_SIZE) + { +#if defined(DEBUG_ESP_CORE) + DEBUGV("NETIF_STATUS_CB_SIZE is too low\n"); +#endif + return false; + } + + netifStatusChangeList[netifStatusChangeListLength++] = cb; + return true; +} + +bool LwipIntf::stateUpCB (LwipIntf::CBType&& cb) +{ + return stateChangeSysCB([cb](netif* nif) + { + if (netif_is_up(nif)) + schedule_function([cb, nif]() + { + cb(nif); + }); + }); +} diff --git a/cores/esp8266/core_esp8266_noniso.cpp b/cores/esp8266/core_esp8266_noniso.cpp index c742904d2..b341599b0 100644 --- a/cores/esp8266/core_esp8266_noniso.cpp +++ b/cores/esp8266/core_esp8266_noniso.cpp @@ -117,4 +117,36 @@ char * dtostrf(double number, signed char width, unsigned char prec, char *s) { return s; } +/* + 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 than 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; +} + }; diff --git a/cores/esp8266/stdlib_noniso.h b/cores/esp8266/stdlib_noniso.h index 636cbf437..053ea4794 100644 --- a/cores/esp8266/stdlib_noniso.h +++ b/cores/esp8266/stdlib_noniso.h @@ -44,6 +44,9 @@ char* dtostrf (double val, signed char width, unsigned char prec, char *s); void reverse(char* begin, char* end); +const char* strrstr(const char*__restrict p_pcString, + const char*__restrict p_pcPattern); + #ifdef __cplusplus } // extern "C" #endif diff --git a/libraries/ESP8266WiFi/src/include/UdpContext.h b/libraries/ESP8266WiFi/src/include/UdpContext.h index 8053748cd..0c43cd2ad 100644 --- a/libraries/ESP8266WiFi/src/include/UdpContext.h +++ b/libraries/ESP8266WiFi/src/include/UdpContext.h @@ -30,6 +30,7 @@ void esp_schedule(); } #include +#include #define PBUF_ALIGNER_ADJUST 4 #define PBUF_ALIGNER(x) ((void*)((((intptr_t)(x))+3)&~3)) @@ -390,14 +391,39 @@ public: return size; } + void cancelBuffer () + { + if (_tx_buf_head) + pbuf_free(_tx_buf_head); + _tx_buf_head = 0; + _tx_buf_cur = 0; + _tx_buf_offset = 0; + } + bool send(const ip_addr_t* addr = 0, uint16_t port = 0) + { + return trySend(addr, port, /* don't keep buffer */false) == ERR_OK; + } + + bool sendTimeout(const ip_addr_t* addr, uint16_t port, + esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs) + { + err_t err; + esp8266::polledTimeout::oneShotFastMs timeout(timeoutMs); + while (((err = trySend(addr, port, /* keep buffer on error */true)) != ERR_OK) && !timeout) + delay(0); + if (err != ERR_OK) + cancelBuffer(); // get rid of buffer kept on error after timeout + return err == ERR_OK; + } + +private: + + err_t trySend(const ip_addr_t* addr, uint16_t port, bool keepBufferOnError) { size_t data_size = _tx_buf_offset; pbuf* tx_copy = pbuf_alloc(PBUF_TRANSPORT, data_size, PBUF_RAM); - if(!tx_copy){ - DEBUGV("failed pbuf_alloc"); - } - else{ + if (tx_copy) { uint8_t* dst = reinterpret_cast(tx_copy->payload); for (pbuf* p = _tx_buf_head; p; p = p->next) { size_t will_copy = (data_size < p->len) ? data_size : p->len; @@ -406,38 +432,32 @@ public: data_size -= will_copy; } } - if (_tx_buf_head) - pbuf_free(_tx_buf_head); - _tx_buf_head = 0; - _tx_buf_cur = 0; - _tx_buf_offset = 0; - if(!tx_copy){ - return false; - } + if (!keepBufferOnError) + cancelBuffer(); + + if (!tx_copy){ + DEBUGV("failed pbuf_alloc"); + return ERR_MEM; + } if (!addr) { addr = &_pcb->remote_ip; port = _pcb->remote_port; } -#ifdef LWIP_MAYBE_XCC - uint16_t old_ttl = _pcb->ttl; - if (ip_addr_ismulticast(addr)) { - _pcb->ttl = _mcast_ttl; - } -#endif + err_t err = udp_sendto(_pcb, tx_copy, addr, port); if (err != ERR_OK) { DEBUGV(":ust rc=%d\r\n", (int) err); } -#ifdef LWIP_MAYBE_XCC - _pcb->ttl = old_ttl; -#endif - pbuf_free(tx_copy); - return err == ERR_OK; - } -private: + pbuf_free(tx_copy); + + if (err == ERR_OK) + cancelBuffer(); // no error: get rid of buffer + + return err; + } size_t _processSize (const pbuf* pb) { diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock/mDNS_Clock.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock/mDNS_Clock.ino index 8ea7237f5..ec0320fdb 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock/mDNS_Clock.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock/mDNS_Clock.ino @@ -36,21 +36,9 @@ #include #include #include - -/* - Include the MDNSResponder (the library needs to be included also) - As LEA MDNSResponder is experimantal in the ESP8266 environment currently, the - legacy MDNSResponder is defaulted in th include file. - There are two ways to access LEA MDNSResponder: - 1. Prepend every declaration and call to global declarations or functions with the namespace, like: - 'LEAmDNS::MDNSResponder::hMDNSService hMDNSService;' - This way is used in the example. But be careful, if the namespace declaration is missing - somewhere, the call might go to the legacy implementation... - 2. Open 'ESP8266mDNS.h' and set LEAmDNS to default. - -*/ -#include #include +#include + /* Global defines and vars */ diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino new file mode 100644 index 000000000..a098bcb3e --- /dev/null +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino @@ -0,0 +1,269 @@ +/* + ESP8266 mDNS responder clock + + This example demonstrates two features of the LEA clsLEAMDNSHost: + 1. The host and service domain negotiation process that ensures + the uniqueness of the finally chosen host and service domain name. + 2. The dynamic MDNS service TXT feature + + A 'clock' service in announced via the MDNS responder and the current + time is set as a TXT item (eg. 'curtime=Mon Oct 15 19:54:35 2018'). + The time value is updated every second! + + The ESP is initially announced to clients as 'esp8266.local', if this host domain + is already used in the local network, another host domain is negotiated. Keep an + eye on the serial output to learn the final host domain for the clock service. + The service itself is is announced as 'host domain'._espclk._tcp.local. + As the service uses port 80, a very simple HTTP server is also installed to deliver + a small web page containing a greeting and the current time (not updated). + The web server code is taken nearly 1:1 from the 'mDNS_Web_Server.ino' example. + Point your browser to 'host domain'.local to see this web page. + + Instructions: + - Update WiFi SSID and password as necessary. + - Flash the sketch to the ESP8266 board + - Install host software: + - For Linux, install Avahi (http://avahi.org/). + - For Windows, install Bonjour (http://www.apple.com/support/bonjour/). + - For Mac OSX and iOS support is built in through Bonjour already. + - Use a MDNS/Bonjour browser like 'Discovery' to find the clock service in your local + network and see the current time updates. + +*/ + + +#include +#include +#include +#include +#include +#include + +// uses API MDNSApiVersion::LEAv2 +#define NO_GLOBAL_MDNS // our MDNS is defined below +#include + +/* + Global defines and vars +*/ + +#define TIMEZONE_OFFSET 1 // CET +#define DST_OFFSET 1 // CEST +#define UPDATE_CYCLE (1 * 1000) // every second + +#define START_AP_AFTER_MS 10000 // start AP after delay +#define SERVICE_PORT 80 // HTTP port + +#ifndef STASSID +#define STASSID "your-ssid" +#define STAPSK "your-password" +#endif + +#ifndef APSSID +#define APSSID "ap4mdnsClock" +#define APPSK "mdnsClock" +#endif + +const char* ssid = STASSID; +const char* password = STAPSK; + +clsLEAMDNSHost MDNSRESP; // MDNS responder +bool bHostDomainConfirmed = false; // Flags the confirmation of the host domain +clsLEAMDNSHost::clsService* hMDNSService = 0; // The handle of the clock service in the MDNS responder + +// HTTP server at port 'SERVICE_PORT' will respond to HTTP requests +ESP8266WebServer server(SERVICE_PORT); + +/* + getTimeString +*/ +const char* getTimeString(void) { + + static char acTimeString[32]; + time_t now = time(nullptr); + ctime_r(&now, acTimeString); + size_t stLength; + while (((stLength = strlen(acTimeString))) && + ('\n' == acTimeString[stLength - 1])) { + acTimeString[stLength - 1] = 0; // Remove trailing line break... + } + return acTimeString; +} + + +/* + setClock + + Set time via NTP +*/ +void setClock(void) { + configTime((TIMEZONE_OFFSET * 3600), (DST_OFFSET * 3600), "pool.ntp.org", "time.nist.gov", "time.windows.com"); + + Serial.print("Waiting for NTP time sync: "); + time_t now = time(nullptr); // Secs since 01.01.1970 (when uninitalized starts with (8 * 3600 = 28800) + while (now < 8 * 3600 * 2) { // Wait for realistic value + delay(500); + Serial.print("."); + now = time(nullptr); + } + Serial.println(""); + Serial.printf("Current time: %s\n", getTimeString()); +} + + +/* + setStationHostname +*/ +bool setStationHostname(const char* p_pcHostname) { + + if (p_pcHostname) { + WiFi.hostname(p_pcHostname); + Serial.printf("setDeviceHostname: Station hostname is set to '%s'\n", p_pcHostname); + } + return true; +} + + +/* + MDNSDynamicServiceTxtCallback + + Add a dynamic MDNS TXT item 'ct' to the clock service. + The callback function is called every time, the TXT items for the clock service + are needed. + This can be triggered by calling MDNSRESP.announce(). + +*/ +void MDNSDynamicServiceTxtCallback(const clsLEAMDNSHost::hMDNSService& p_hService) { + Serial.println("MDNSDynamicServiceTxtCallback"); + + if (hMDNSService == &p_hService) { + Serial.printf("Updating curtime TXT item to: %s\n", getTimeString()); + hMDNSService->addDynamicServiceTxt("curtime", getTimeString()); + } +} + + +/* + handleHTTPClient +*/ + +void handleHTTPRequest() { + Serial.println(""); + Serial.println("HTTP Request"); + + // Get current time + time_t now = time(nullptr);; + struct tm timeinfo; + gmtime_r(&now, &timeinfo); + + String s; + s.reserve(300); + + s = "\r\nHello from "; + s += WiFi.hostname() + " at " + WiFi.localIP().toString(); + // Simple addition of the current time + s += "\r\nCurrent time is: "; + s += getTimeString(); + // done :-) + s += "\r\n\r\n"; + Serial.println("Sending 200"); + server.send(200, "text/html", s); +} + +/* + setup +*/ +void setup(void) { + Serial.begin(115200); + + // Connect to WiFi network + + WiFi.persistent(false); + + // useless informative callback + if (!LwipIntf::stateUpCB([](netif * nif) { + Serial.printf("New interface %c%c/%d is up\n", + nif->name[0], + nif->name[1], + netif_get_index(nif)); + })) { + Serial.println("Error: could not add informative callback\n"); + } + + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + Serial.println(""); + + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + // Sync clock + setClock(); + + // Setup MDNS responder + // Init the (currently empty) host domain string with 'leamdnsv2' + if (MDNSRESP.begin("leamdnsv2", + [](clsLEAMDNSHost & p_rMDNSHost, const char* p_pcDomainName, bool p_bProbeResult)->void { + if (p_bProbeResult) { + Serial.printf("mDNSHost_AP::ProbeResultCallback: '%s' is %s\n", p_pcDomainName, (p_bProbeResult ? "FREE" : "USED!")); + // Unattended added service + hMDNSService = p_rMDNSHost.addService(0, "espclk", "tcp", 80); + hMDNSService->addDynamicServiceTxt("curtime", getTimeString()); + hMDNSService->setDynamicServiceTxtCallback(MDNSDynamicServiceTxtCallback); + } else { + // Change hostname, use '-' as divider between base name and index + MDNSRESP.setHostName(clsLEAMDNSHost::indexDomainName(p_pcDomainName, "-", 0)); + } + })) { + Serial.println("mDNS-AP started"); + } else { + Serial.println("FAILED to start mDNS-AP"); + } + + // Setup HTTP server + server.on("/", handleHTTPRequest); + server.begin(); + Serial.println("HTTP server started"); +} + +/* + loop +*/ +void loop(void) { + + // Check if a request has come in + server.handleClient(); + // Allow MDNS processing + MDNSRESP.update(); + + static esp8266::polledTimeout::periodicMs timeout(UPDATE_CYCLE); + if (timeout.expired()) { + + if (hMDNSService) { + // Just trigger a new MDNS announcement, this will lead to a call to + // 'MDNSDynamicServiceTxtCallback', which will update the time TXT item + Serial.printf("Announce trigger from user\n"); + MDNSRESP.announce(); + } + } + + static bool AP_started = false; + if (!AP_started && millis() > START_AP_AFTER_MS) { + AP_started = true; + Serial.printf("Starting AP...\n"); + WiFi.mode(WIFI_AP_STA); + WiFi.softAP(APSSID, APPSK); + Serial.printf("AP started...(%s:%s, %s)\n", + WiFi.softAPSSID().c_str(), + WiFi.softAPPSK().c_str(), + WiFi.softAPIP().toString().c_str()); + } +} diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor/mDNS_ServiceMonitor.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor/mDNS_ServiceMonitor.ino index 98269fb48..167a968c7 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor/mDNS_ServiceMonitor.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor/mDNS_ServiceMonitor.ino @@ -33,19 +33,6 @@ #include #include #include - -/* - Include the MDNSResponder (the library needs to be included also) - As LEA MDNSResponder is experimantal in the ESP8266 environment currently, the - legacy MDNSResponder is defaulted in th include file. - There are two ways to access LEA MDNSResponder: - 1. Prepend every declaration and call to global declarations or functions with the namespace, like: - 'LEAmDNS:MDNSResponder::hMDNSService hMDNSService;' - This way is used in the example. But be careful, if the namespace declaration is missing - somewhere, the call might go to the legacy implementation... - 2. Open 'ESP8266mDNS.h' and set LEAmDNS to default. - -*/ #include /* diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino new file mode 100644 index 000000000..1d24990be --- /dev/null +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino @@ -0,0 +1,259 @@ +/* + ESP8266 mDNS Responder Service Monitor + + This example demonstrates two features of the LEA clsLEAMDNSHost: + 1. The host and service domain negotiation process that ensures + the uniqueness of the finally choosen host and service domain name. + 2. The dynamic MDNS service lookup/query feature. + + A list of 'HTTP' services in the local network is created and kept up to date. + In addition to this, a (very simple) HTTP server is set up on port 80 + and announced as a service. + + The ESP itself is initially announced to clients as 'esp8266.local', if this host domain + is already used in the local network, another host domain is negociated. Keep an + eye to the serial output to learn the final host domain for the HTTP service. + The service itself is is announced as 'host domain'._http._tcp.local. + The HTTP server delivers a short greeting and the current list of other 'HTTP' services (not updated). + The web server code is taken nearly 1:1 from the 'mDNS_Web_Server.ino' example. + Point your browser to 'host domain'.local to see this web page. + + Instructions: + - Update WiFi SSID and password as necessary. + - Flash the sketch to the ESP8266 board + - Install host software: + - For Linux, install Avahi (http://avahi.org/). + - For Windows, install Bonjour (http://www.apple.com/support/bonjour/). + - For Mac OSX and iOS support is built in through Bonjour already. + - Use a browser like 'Safari' to see the page at http://'host domain'.local. + +*/ + +// THIS IS A WORK IN PROGRESS: some TODOs need completion + +#ifndef STASSID +#define STASSID "ssid" +#define STAPSK "psk" +#endif + +#ifndef APSSID +#define APSSID "esp8266" +//#define APPSK "psk" +#endif + +#include +#include +#include + +#define NO_GLOBAL_MDNS // our MDNS is defined below +#include + +/* + Global defines and vars +*/ + +#define SERVICE_PORT 80 // HTTP port +clsLEAMDNSHost MDNS; // MDNS responder + +char* pcHostDomain = 0; // Negociated host domain +bool bHostDomainConfirmed = false; // Flags the confirmation of the host domain +clsLEAMDNSHost::clsService* hMDNSService = 0; // The handle of the http service in the MDNS responder +clsLEAMDNSHost::clsQuery* hMDNSServiceQuery = 0; // The handle of the 'http.tcp' service query in the MDNS responder + +const String cstrNoHTTPServices = "Currently no 'http.tcp' services in the local network!
"; +String strHTTPServices = cstrNoHTTPServices; + +// HTTP server at port 'SERVICE_PORT' will respond to HTTP requests +ESP8266WebServer server(SERVICE_PORT); + + +/* + setStationHostname +*/ +bool setStationHostname(const char* p_pcHostname) { + + if (p_pcHostname) { + WiFi.hostname(p_pcHostname); + Serial.printf("setStationHostname: Station hostname is set to '%s'\n", p_pcHostname); + return true; + } + return false; +} + + +void MDNSServiceQueryCallback(const clsLEAMDNSHost::clsQuery& p_Query, + const clsLEAMDNSHost::clsQuery::clsAnswer& p_Answer, + clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, + bool p_bSetContent) { + (void)p_Query; + + String answerInfo; + switch (p_QueryAnswerTypeFlags) { + case static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain): + answerInfo = "ServiceDomain " + String(p_Answer.m_ServiceDomain.c_str()); + break; + + case static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::HostDomainPort): + answerInfo = "HostDomainAndPort " + String(p_Answer.m_HostDomain.c_str()) + ":" + String(p_Answer.m_u16Port); + break; + case static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address): + answerInfo = "IP4Address "; + for (auto ip : p_Answer.m_IPv4Addresses) { + answerInfo += "- " + ip->m_IPAddress.toString(); + }; + break; + case static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Txts): + answerInfo = "TXT "; + for (auto kv : p_Answer.m_Txts.m_Txts) { + answerInfo += "\nkv : " + String(kv->m_pcKey) + " : " + String(kv->m_pcValue); + } + break; + default : + answerInfo = "Unknown Answertype " + String(p_QueryAnswerTypeFlags); + + } + Serial.printf("Answer %s %s\n", answerInfo.c_str(), p_bSetContent ? "Modified" : "Deleted"); +} + +/* + MDNSServiceProbeResultCallback + Probe result callback for Services +*/ + +void serviceProbeResult(clsLEAMDNSHost::clsService& p_rMDNSService, + const char* p_pcInstanceName, + bool p_bProbeResult) { + (void)p_rMDNSService; + Serial.printf("MDNSServiceProbeResultCallback: Service %s probe %s\n", p_pcInstanceName, (p_bProbeResult ? "succeeded." : "failed!")); +} + +/* + MDNSHostProbeResultCallback + + Probe result callback for the host domain. + If the domain is free, the host domain is set and the http service is + added. + If the domain is already used, a new name is created and the probing is + restarted via p_pclsLEAMDNSHost->setHostname(). + +*/ + +void hostProbeResult(clsLEAMDNSHost & p_rMDNSHost, String p_pcDomainName, bool p_bProbeResult) { + + (void)p_rMDNSHost; + Serial.printf("MDNSHostProbeResultCallback: Host domain '%s.local' is %s\n", p_pcDomainName.c_str(), (p_bProbeResult ? "free" : "already USED!")); + + if (true == p_bProbeResult) { + // Set station hostname + setStationHostname(pcHostDomain); + + if (!bHostDomainConfirmed) { + // Hostname free -> setup clock service + bHostDomainConfirmed = true; + + if (!hMDNSService) { + // Add a 'http.tcp' service to port 'SERVICE_PORT', using the host domain as instance domain + hMDNSService = MDNS.addService(0, "http", "tcp", SERVICE_PORT, serviceProbeResult); + + if (hMDNSService) { + hMDNSService->setProbeResultCallback(serviceProbeResult); + // MDNS.setServiceProbeResultCallback(hMDNSService, serviceProbeResult); + + // Add some '_http._tcp' protocol specific MDNS service TXT items + // See: http://www.dns-sd.org/txtrecords.html#http + hMDNSService->addServiceTxt("user", ""); + hMDNSService->addServiceTxt("password", ""); + hMDNSService->addServiceTxt("path", "/"); + } + + // Install dynamic 'http.tcp' service query + if (!hMDNSServiceQuery) { + hMDNSServiceQuery = MDNS.installServiceQuery("http", "tcp", MDNSServiceQueryCallback); + if (hMDNSServiceQuery) { + Serial.printf("MDNSProbeResultCallback: Service query for 'http.tcp' services installed.\n"); + } else { + Serial.printf("MDNSProbeResultCallback: FAILED to install service query for 'http.tcp' services!\n"); + } + } + } + } + } else { + // Change hostname, use '-' as divider between base name and index + MDNS.setHostName(clsLEAMDNSHost::indexDomainName(p_pcDomainName.c_str(), "-", 0)); + } +} + +/* + HTTP request function (not found is handled by server) +*/ +void handleHTTPRequest() { + Serial.println(""); + Serial.println("HTTP Request"); + + IPAddress ip = server.client().localIP(); + String ipStr = ip.toString(); + String s; + s.reserve(200 /* + service listed */); + s = "\r\n

Hello from "; + s += WiFi.hostname() + ".local at " + server.client().localIP().toString() + "

"; + s += "

Local HTTP services are :

"; + s += "
    "; + + // TODO: list services + + s += "

"; + + Serial.println("Sending 200"); + server.send(200, "text/html", s); + Serial.println("Done with request"); +} + +/* + setup +*/ +void setup(void) { + Serial.begin(115200); + Serial.setDebugOutput(false); + + Serial.println(""); + Serial.println("THIS IS A WORK IN PROGRESS: some TODOs need completion"); + Serial.println(""); + + // Connect to WiFi network + WiFi.mode(WIFI_AP_STA); + WiFi.softAP(APSSID); + WiFi.begin(STASSID, STAPSK); + Serial.println(""); + + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.print("Connected to "); + Serial.println(STASSID); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + // Setup HTTP server + server.on("/", handleHTTPRequest); + + // Setup MDNS responders + MDNS.setProbeResultCallback(hostProbeResult); + + // Init the (currently empty) host domain string with 'leamdnsv2' + MDNS.begin("leamdnsv2"); + Serial.println("MDNS responder started"); + + // Start HTTP server + server.begin(); + Serial.println("HTTP server started"); +} + +void loop(void) { + // Check if a request has come in + server.handleClient(); + // Allow MDNS processing + MDNS.update(); +} diff --git a/libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-SPIFFS.ino b/libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-LittleFS.ino similarity index 93% rename from libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-SPIFFS.ino rename to libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-LittleFS.ino index 9d0d86147..5e4a5a081 100644 --- a/libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-SPIFFS.ino +++ b/libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-LittleFS.ino @@ -12,33 +12,38 @@ */ +#ifndef APSSID +#define APSSID "your-apssid" +#define APPSK "your-password" +#endif + #ifndef STASSID -#define STASSID "your-ssid" +#define STASSID "your-sta" #define STAPSK "your-password" #endif // includes #include -#include #include #include #include #include +#include /** @brief mDNS and OTA Constants @{ */ -#define HOSTNAME "ESP8266-OTA-" ///< Hostename. The setup function adds the Chip ID at the end. +#define HOSTNAME "ESP8266-OTA-" ///< Hostname. The setup function adds the Chip ID at the end. /// @} /** @brief Default WiFi connection information. @{ */ -const char* ap_default_ssid = STASSID; ///< Default SSID. -const char* ap_default_psk = STAPSK; ///< Default PSK. +const char* ap_default_ssid = APSSID; ///< Default SSID. +const char* ap_default_psk = APPSK; ///< Default PSK. /// @} /// Uncomment the next line for verbose output over UART. @@ -166,8 +171,8 @@ void setup() { // Load wifi connection information. if (! loadConfig(&station_ssid, &station_psk)) { - station_ssid = ""; - station_psk = ""; + station_ssid = STASSID; + station_psk = STAPSK; Serial.println("No WiFi connection information available."); } diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS.cpp b/libraries/ESP8266mDNS/src/ESP8266mDNS.cpp index c784a0d79..b5d7aac27 100644 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS.cpp +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS.cpp @@ -1,3 +1,25 @@ +/* + + 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 /* diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS.h b/libraries/ESP8266mDNS/src/ESP8266mDNS.h index 66d40b1b2..2e0e1fcec 100644 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS.h +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS.h @@ -10,7 +10,7 @@ - Presenting a DNS-SD service to interested observers, eg. a http server by presenting _http._tcp service - Support for multi-level compressed names in input; in output only a very simple one-leven full-name compression is implemented - Probing host and service domains for uniqueness in the local network - - Tiebreaking while probing is supportet in a very minimalistic way (the 'higher' IP address wins the tiebreak) + - Tiebreaking while probing is supported in a very minimalistic way (the 'higher' IP address wins the tiebreak) - Announcing available services after successful probing - Using fixed service TXT items or - Using dynamic service TXT items for presented services (via callback) @@ -42,15 +42,18 @@ */ -#include "ESP8266mDNS_Legacy.h" -#include "LEAmDNS.h" +enum class MDNSApiVersion { LEA, LEAv2 }; +#include "LEAmDNS.h" // LEA +#include "LEAmDNS2Host.h" // LEAv2 - API updated + +// clsLEAMDNSHost replaces MDNSResponder in LEAv2 +using clsLEAMDNSHost = esp8266::experimental::clsLEAMDNSHost; #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) // Maps the implementation to use to the global namespace type -//using MDNSResponder = Legacy_MDNSResponder::MDNSResponder; //legacy -using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; //new +using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; // LEA +//using MDNSResponder = clsLEAMDNSHost; // LEAv2 extern MDNSResponder MDNS; #endif - diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp b/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp deleted file mode 100644 index 879119552..000000000 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp +++ /dev/null @@ -1,1523 +0,0 @@ -/* - - ESP8266 Multicast DNS (port of CC3000 Multicast DNS library) - Version 1.1 - Copyright (c) 2013 Tony DiCola (tony@tonydicola.com) - ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com) - MDNS-SD Suport 2015 Hristo Gochkov - Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com) - - - 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. - -*/ - -// Important RFC's for reference: -// - DNS request and response: http://www.ietf.org/rfc/rfc1035.txt -// - Multicast DNS: http://www.ietf.org/rfc/rfc6762.txt -// - MDNS-SD: https://tools.ietf.org/html/rfc6763 - -#ifndef LWIP_OPEN_SRC -#define LWIP_OPEN_SRC -#endif - -#include "ESP8266mDNS.h" -#include - -#include "debug.h" - -extern "C" { -#include "osapi.h" -#include "ets_sys.h" -#include "user_interface.h" -} - -#include "WiFiUdp.h" -#include "lwip/opt.h" -#include "lwip/udp.h" -#include "lwip/inet.h" -#include "lwip/igmp.h" -#include "lwip/mem.h" -#include "include/UdpContext.h" - - - -namespace Legacy_MDNSResponder -{ - - -#ifdef DEBUG_ESP_MDNS -#define DEBUG_ESP_MDNS_ERR -#define DEBUG_ESP_MDNS_TX -#define DEBUG_ESP_MDNS_RX -#endif - -#define MDNS_NAME_REF 0xC000 - -#define MDNS_TYPE_AAAA 0x001C -#define MDNS_TYPE_A 0x0001 -#define MDNS_TYPE_PTR 0x000C -#define MDNS_TYPE_SRV 0x0021 -#define MDNS_TYPE_TXT 0x0010 - -#define MDNS_CLASS_IN 0x0001 -#define MDNS_CLASS_IN_FLUSH_CACHE 0x8001 - -#define MDNS_ANSWERS_ALL 0x0F -#define MDNS_ANSWER_PTR 0x08 -#define MDNS_ANSWER_TXT 0x04 -#define MDNS_ANSWER_SRV 0x02 -#define MDNS_ANSWER_A 0x01 - -#define _conn_read32() (((uint32_t)_conn->read() << 24) | ((uint32_t)_conn->read() << 16) | ((uint32_t)_conn->read() << 8) | _conn->read()) -#define _conn_read16() (((uint16_t)_conn->read() << 8) | _conn->read()) -#define _conn_read8() _conn->read() -#define _conn_readS(b,l) _conn->read((char*)(b),l); - -static const IPAddress MDNS_MULTICAST_ADDR(224, 0, 0, 251); -static const int MDNS_MULTICAST_TTL = 1; -static const int MDNS_PORT = 5353; - -struct MDNSService -{ - MDNSService* _next; - char _name[32]; - char _proto[4]; - uint16_t _port; - uint16_t _txtLen; // length of all txts - struct MDNSTxt * _txts; -}; - -struct MDNSTxt -{ - MDNSTxt * _next; - String _txt; -}; - -struct MDNSAnswer -{ - MDNSAnswer* next; - uint8_t ip[4]; - uint16_t port; - char *hostname; -}; - -struct MDNSQuery -{ - char _service[32]; - char _proto[4]; -}; - - -MDNSResponder::MDNSResponder() : _conn(0) -{ - _services = 0; - _instanceName = ""; - _answers = 0; - _query = 0; - _newQuery = false; - _waitingForAnswers = false; -} -MDNSResponder::~MDNSResponder() -{ - if (_query != 0) - { - os_free(_query); - _query = 0; - } - - // Clear answer list - MDNSAnswer *answer; - int numAnswers = _getNumAnswers(); - for (int n = numAnswers - 1; n >= 0; n--) - { - answer = _getAnswerFromIdx(n); - os_free(answer->hostname); - os_free(answer); - answer = 0; - } - _answers = 0; - - if (_conn) - { - _conn->unref(); - } -} - -bool MDNSResponder::begin(const char* hostname) -{ - size_t n = strlen(hostname); - if (n > 63) // max size for a single label. - { - return false; - } - - // Copy in hostname characters as lowercase - _hostName = hostname; - _hostName.toLowerCase(); - - // If instance name is not already set copy hostname to instance name - if (_instanceName.equals("")) - { - _instanceName = hostname; - } - - _gotIPHandler = WiFi.onStationModeGotIP([this](const WiFiEventStationModeGotIP & event) - { - (void) event; - _restart(); - }); - - _disconnectedHandler = WiFi.onStationModeDisconnected([this](const WiFiEventStationModeDisconnected & event) - { - (void) event; - _restart(); - }); - - return _listen(); -} - -void MDNSResponder::notifyAPChange() -{ - _restart(); -} - -void MDNSResponder::_restart() -{ - if (_conn) - { - _conn->unref(); - _conn = nullptr; - } - _listen(); -} - -bool MDNSResponder::_listen() -{ - // Open the MDNS socket if it isn't already open. - if (!_conn) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.println("MDNS listening"); -#endif - - IPAddress mdns(MDNS_MULTICAST_ADDR); - - if (igmp_joingroup(IP4_ADDR_ANY4, mdns) != ERR_OK) - { - return false; - } - - _conn = new UdpContext; - _conn->ref(); - - if (!_conn->listen(IP_ADDR_ANY, MDNS_PORT)) - { - return false; - } - _conn->setMulticastTTL(MDNS_MULTICAST_TTL); - _conn->onRx(std::bind(&MDNSResponder::update, this)); - _conn->connect(mdns, MDNS_PORT); - } - return true; -} - -void MDNSResponder::update() -{ - if (!_conn || !_conn->next()) - { - return; - } - _parsePacket(); -} - - -void MDNSResponder::setInstanceName(String name) -{ - if (name.length() > 63) - { - return; - } - _instanceName = name; -} - - -bool MDNSResponder::addServiceTxt(char *name, char *proto, char *key, char *value) -{ - MDNSService* servicePtr; - - uint8_t txtLen = os_strlen(key) + os_strlen(value) + 1; // Add one for equals sign - txtLen += 1; //accounts for length byte added when building the txt responce - //Find the service - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - //Checking Service names - if (strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) - { - //found a service name match - if (servicePtr->_txtLen + txtLen > 1300) - { - return false; //max txt record size - } - MDNSTxt *newtxt = new MDNSTxt; - newtxt->_txt = String(key) + '=' + String(value); - newtxt->_next = 0; - if (servicePtr->_txts == 0) //no services have been added - { - //Adding First TXT to service - servicePtr->_txts = newtxt; - servicePtr->_txtLen += txtLen; - return true; - } - else - { - MDNSTxt * txtPtr = servicePtr->_txts; - while (txtPtr->_next != 0) - { - txtPtr = txtPtr->_next; - } - //adding another TXT to service - txtPtr->_next = newtxt; - servicePtr->_txtLen += txtLen; - return true; - } - } - } - return false; -} - -void MDNSResponder::addService(char *name, char *proto, uint16_t port) -{ - if (_getServicePort(name, proto) != 0) - { - return; - } - if (os_strlen(name) > 32 || os_strlen(proto) != 3) - { - return; //bad arguments - } - struct MDNSService *srv = (struct MDNSService*)(os_malloc(sizeof(struct MDNSService))); - os_strcpy(srv->_name, name); - os_strcpy(srv->_proto, proto); - srv->_port = port; - srv->_next = 0; - srv->_txts = 0; - srv->_txtLen = 0; - - if (_services == 0) - { - _services = srv; - } - else - { - MDNSService* servicePtr = _services; - while (servicePtr->_next != 0) - { - servicePtr = servicePtr->_next; - } - servicePtr->_next = srv; - } - -} - -int MDNSResponder::queryService(char *service, char *proto) -{ -#ifdef DEBUG_ESP_MDNS_TX - DEBUG_ESP_PORT.printf("queryService %s %s\n", service, proto); -#endif - while (_answers != 0) - { - MDNSAnswer *currAnswer = _answers; - _answers = _answers->next; - os_free(currAnswer->hostname); - os_free(currAnswer); - currAnswer = 0; - } - if (_query != 0) - { - os_free(_query); - _query = 0; - } - _query = (struct MDNSQuery*)(os_malloc(sizeof(struct MDNSQuery))); - os_strcpy(_query->_service, service); - os_strcpy(_query->_proto, proto); - _newQuery = true; - - char underscore[] = "_"; - - // build service name with _ - char serviceName[os_strlen(service) + 2]; - os_strcpy(serviceName, underscore); - os_strcat(serviceName, service); - size_t serviceNameLen = os_strlen(serviceName); - - //build proto name with _ - char protoName[5]; - os_strcpy(protoName, underscore); - os_strcat(protoName, proto); - size_t protoNameLen = 4; - - //local string - char localName[] = "local"; - size_t localNameLen = 5; - - //terminator - char terminator[] = "\0"; - - // Only supports sending one PTR query - uint8_t questionCount = 1; - - _waitingForAnswers = true; - for (int itfn = 0; itfn < 2; itfn++) - { - struct ip_info ip_info; - - wifi_get_ip_info((!itfn) ? SOFTAP_IF : STATION_IF, &ip_info); - if (!ip_info.ip.addr) - { - continue; - } - _conn->setMulticastInterface(IPAddress(ip_info.ip.addr)); - - // Write the header - _conn->flush(); - uint8_t head[12] = - { - 0x00, 0x00, //ID = 0 - 0x00, 0x00, //Flags = response + authoritative answer - 0x00, questionCount, //Question count - 0x00, 0x00, //Answer count - 0x00, 0x00, //Name server records - 0x00, 0x00 //Additional records - }; - _conn->append(reinterpret_cast(head), 12); - - // Only supports sending one PTR query - // Send the Name field (eg. "_http._tcp.local") - _conn->append(reinterpret_cast(&serviceNameLen), 1); // lenght of "_" + service - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_" + service - _conn->append(reinterpret_cast(&protoNameLen), 1); // lenght of "_" + proto - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_" + proto - _conn->append(reinterpret_cast(&localNameLen), 1); // lenght of "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type and class - uint8_t ptrAttrs[4] = - { - 0x00, 0x0c, //PTR record query - 0x00, 0x01 //Class IN - }; - _conn->append(reinterpret_cast(ptrAttrs), 4); - _conn->send(); - } - -#ifdef DEBUG_ESP_MDNS_TX - DEBUG_ESP_PORT.println("Waiting for answers.."); -#endif - delay(1000); - - _waitingForAnswers = false; - - return _getNumAnswers(); -} - -String MDNSResponder::hostname(int idx) -{ - MDNSAnswer *answer = _getAnswerFromIdx(idx); - if (answer == 0) - { - return String(); - } - return answer->hostname; -} - -IPAddress MDNSResponder::IP(int idx) -{ - MDNSAnswer *answer = _getAnswerFromIdx(idx); - if (answer == 0) - { - return IPAddress(); - } - return IPAddress(answer->ip); -} - -uint16_t MDNSResponder::port(int idx) -{ - MDNSAnswer *answer = _getAnswerFromIdx(idx); - if (answer == 0) - { - return 0; - } - return answer->port; -} - -MDNSAnswer* MDNSResponder::_getAnswerFromIdx(int idx) -{ - MDNSAnswer *answer = _answers; - while (answer != 0 && idx-- > 0) - { - answer = answer->next; - } - if (idx > 0) - { - return 0; - } - return answer; -} - -int MDNSResponder::_getNumAnswers() -{ - int numAnswers = 0; - MDNSAnswer *answer = _answers; - while (answer != 0) - { - numAnswers++; - answer = answer->next; - } - return numAnswers; -} - -MDNSTxt * MDNSResponder::_getServiceTxt(char *name, char *proto) -{ - MDNSService* servicePtr; - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - if (servicePtr->_port > 0 && strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) - { - if (servicePtr->_txts == 0) - { - return nullptr; - } - return servicePtr->_txts; - } - } - return nullptr; -} - -uint16_t MDNSResponder::_getServiceTxtLen(char *name, char *proto) -{ - MDNSService* servicePtr; - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - if (servicePtr->_port > 0 && strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) - { - if (servicePtr->_txts == 0) - { - return false; - } - return servicePtr->_txtLen; - } - } - return 0; -} - -uint16_t MDNSResponder::_getServicePort(char *name, char *proto) -{ - MDNSService* servicePtr; - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - if (servicePtr->_port > 0 && strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) - { - return servicePtr->_port; - } - } - return 0; -} - -IPAddress MDNSResponder::_getRequestMulticastInterface() -{ - struct ip_info ip_info; - bool match_ap = false; - if (wifi_get_opmode() & SOFTAP_MODE) - { - const IPAddress& remote_ip = _conn->getRemoteAddress(); - wifi_get_ip_info(SOFTAP_IF, &ip_info); - IPAddress infoIp(ip_info.ip); - IPAddress infoMask(ip_info.netmask); - if (ip_info.ip.addr && ip_addr_netcmp((const ip_addr_t*)remote_ip, (const ip_addr_t*)infoIp, ip_2_ip4((const ip_addr_t*)infoMask))) - { - match_ap = true; - } - } - if (!match_ap) - { - wifi_get_ip_info(STATION_IF, &ip_info); - } - return IPAddress(ip_info.ip.addr); -} - -void MDNSResponder::_parsePacket() -{ - int i; - char tmp; - bool serviceParsed = false; - bool protoParsed = false; - bool localParsed = false; - - char hostName[255]; - uint8_t hostNameLen; - - char serviceName[32]; - uint8_t serviceNameLen; - uint16_t servicePort = 0; - - char protoName[32]; - protoName[0] = 0; - uint8_t protoNameLen = 0; - - uint16_t packetHeader[6]; - - for (i = 0; i < 6; i++) - { - packetHeader[i] = _conn_read16(); - } - - if ((packetHeader[1] & 0x8000) != 0) // Read answers - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("Reading answers RX: REQ, ID:%u, Q:%u, A:%u, NS:%u, ADD:%u\n", packetHeader[0], packetHeader[2], packetHeader[3], packetHeader[4], packetHeader[5]); -#endif - - if (!_waitingForAnswers) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.println("Not expecting any answers right now, returning"); -#endif - _conn->flush(); - return; - } - - int numAnswers = packetHeader[3] + packetHeader[5]; - // Assume that the PTR answer always comes first and that it is always accompanied by a TXT, SRV, AAAA (optional) and A answer in the same packet. - if (numAnswers < 4) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("Expected a packet with 4 or more answers, got %u\n", numAnswers); -#endif - _conn->flush(); - return; - } - - uint8_t tmp8; - uint16_t answerPort = 0; - uint8_t answerIp[4] = { 0, 0, 0, 0 }; - char answerHostName[255]; - bool serviceMatch = false; - MDNSAnswer *answer; - uint8_t partsCollected = 0; - uint8_t stringsRead = 0; - - answerHostName[0] = '\0'; - - // Clear answer list - if (_newQuery) - { - int oldAnswers = _getNumAnswers(); - for (int n = oldAnswers - 1; n >= 0; n--) - { - answer = _getAnswerFromIdx(n); - os_free(answer->hostname); - os_free(answer); - answer = 0; - } - _answers = 0; - _newQuery = false; - } - - while (numAnswers--) - { - // Read name - stringsRead = 0; - size_t last_bufferpos = 0; - do - { - tmp8 = _conn_read8(); - if (tmp8 == 0x00) // End of name - { - break; - } - if (tmp8 & 0xC0) // Compressed pointer - { - uint16_t offset = ((((uint16_t)tmp8) & ~0xC0) << 8) | _conn_read8(); - if (_conn->isValidOffset(offset)) - { - if (0 == last_bufferpos) - { - last_bufferpos = _conn->tell(); - } -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Compressed pointer, jumping from "); - DEBUG_ESP_PORT.print(last_bufferpos); - DEBUG_ESP_PORT.print(" to "); - DEBUG_ESP_PORT.println(offset); -#endif - _conn->seek(offset); - tmp8 = _conn_read8(); - } - else - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Skipping malformed compressed pointer"); -#endif - tmp8 = _conn_read8(); - break; - } - } - if (stringsRead > 3) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.println("failed to read the response name"); -#endif - _conn->flush(); - return; - } - _conn_readS(serviceName, tmp8); - serviceName[tmp8] = '\0'; -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf(" %d ", tmp8); - for (int n = 0; n < tmp8; n++) - { - DEBUG_ESP_PORT.printf("%c", serviceName[n]); - } - DEBUG_ESP_PORT.println(); -#endif - if (serviceName[0] == '_') - { - if (strcmp(&serviceName[1], _query->_service) == 0) - { - serviceMatch = true; -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("found matching service: %s\n", _query->_service); -#endif - } - } - stringsRead++; - } while (true); - if (last_bufferpos > 0) - { - _conn->seek(last_bufferpos); -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Compressed pointer, jumping back to "); - DEBUG_ESP_PORT.println(last_bufferpos); -#endif - } - - uint16_t answerType = _conn_read16(); // Read type - uint16_t answerClass = _conn_read16(); // Read class - uint32_t answerTtl = _conn_read32(); // Read ttl - uint16_t answerRdlength = _conn_read16(); // Read rdlength - - (void) answerClass; - (void) answerTtl; - - if (answerRdlength > 255) - { - if (answerType == MDNS_TYPE_TXT && answerRdlength < 1460) - { - while (--answerRdlength) - { - _conn->read(); - } - } - else - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("Data len too long! %u\n", answerRdlength); -#endif - _conn->flush(); - return; - } - } - -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("type: %04x rdlength: %d\n", answerType, answerRdlength); -#endif - - if (answerType == MDNS_TYPE_PTR) - { - partsCollected |= 0x01; - _conn_readS(hostName, answerRdlength); // Read rdata - if (hostName[answerRdlength - 2] & 0xc0) - { - memcpy(answerHostName, hostName + 1, answerRdlength - 3); - answerHostName[answerRdlength - 3] = '\0'; - } -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("PTR %d ", answerRdlength); - for (int n = 0; n < answerRdlength; n++) - { - DEBUG_ESP_PORT.printf("%c", hostName[n]); - } - DEBUG_ESP_PORT.println(); -#endif - } - - else if (answerType == MDNS_TYPE_TXT) - { - partsCollected |= 0x02; - _conn_readS(hostName, answerRdlength); // Read rdata -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("TXT %d ", answerRdlength); - for (int n = 0; n < answerRdlength; n++) - { - DEBUG_ESP_PORT.printf("%c", hostName[n]); - } - DEBUG_ESP_PORT.println(); -#endif - } - - else if (answerType == MDNS_TYPE_SRV) - { - partsCollected |= 0x04; - uint16_t answerPrio = _conn_read16(); // Read priority - uint16_t answerWeight = _conn_read16(); // Read weight - answerPort = _conn_read16(); // Read port - last_bufferpos = 0; - - (void) answerPrio; - (void) answerWeight; - - // Read hostname - tmp8 = _conn_read8(); - if (tmp8 & 0xC0) // Compressed pointer - { - uint16_t offset = ((((uint16_t)tmp8) & ~0xC0) << 8) | _conn_read8(); - if (_conn->isValidOffset(offset)) - { - last_bufferpos = _conn->tell(); -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Compressed pointer, jumping from "); - DEBUG_ESP_PORT.print(last_bufferpos); - DEBUG_ESP_PORT.print(" to "); - DEBUG_ESP_PORT.println(offset); -#endif - _conn->seek(offset); - tmp8 = _conn_read8(); - } - else - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Skipping malformed compressed pointer"); -#endif - tmp8 = _conn_read8(); - break; - } - } - _conn_readS(answerHostName, tmp8); - answerHostName[tmp8] = '\0'; -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("SRV %d ", tmp8); - for (int n = 0; n < tmp8; n++) - { - DEBUG_ESP_PORT.printf("%02x ", answerHostName[n]); - } - DEBUG_ESP_PORT.printf("\n%s\n", answerHostName); -#endif - if (last_bufferpos > 0) - { - _conn->seek(last_bufferpos); - tmp8 = 2; // Size of compression octets -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Compressed pointer, jumping back to "); - DEBUG_ESP_PORT.println(last_bufferpos); -#endif - } - if (answerRdlength - (6 + 1 + tmp8) > 0) // Skip any remaining rdata - { - _conn_readS(hostName, answerRdlength - (6 + 1 + tmp8)); - } - } - - else if (answerType == MDNS_TYPE_A) - { - partsCollected |= 0x08; - for (int i = 0; i < 4; i++) - { - answerIp[i] = _conn_read8(); - } - } - else - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("Ignoring unsupported type %02x\n", tmp8); -#endif - for (int n = 0; n < answerRdlength; n++) - { - (void)_conn_read8(); - } - } - - if ((partsCollected == 0x0F) && serviceMatch) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.println("All answers parsed, adding to _answers list.."); -#endif - // Add new answer to answer list - if (_answers == 0) - { - _answers = (struct MDNSAnswer*)(os_malloc(sizeof(struct MDNSAnswer))); - answer = _answers; - } - else - { - answer = _answers; - while (answer->next != 0) - { - answer = answer->next; - } - answer->next = (struct MDNSAnswer*)(os_malloc(sizeof(struct MDNSAnswer))); - answer = answer->next; - } - answer->next = 0; - answer->hostname = 0; - - // Populate new answer - answer->port = answerPort; - for (int i = 0; i < 4; i++) - { - answer->ip[i] = answerIp[i]; - } - answer->hostname = (char *)os_malloc(strlen(answerHostName) + 1); - os_strcpy(answer->hostname, answerHostName); - _conn->flush(); - return; - } - } - - _conn->flush(); - return; - } - - // PARSE REQUEST NAME - - hostNameLen = _conn_read8() % 255; - _conn_readS(hostName, hostNameLen); - hostName[hostNameLen] = '\0'; - - if (hostName[0] == '_') - { - serviceParsed = true; - memcpy(serviceName, hostName + 1, hostNameLen); - serviceNameLen = hostNameLen - 1; - hostNameLen = 0; - } - - if (hostNameLen > 0 && !_hostName.equals(hostName) && !_instanceName.equals(hostName)) - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_NO_HOST: %s\n", hostName); - DEBUG_ESP_PORT.printf("hostname: %s\n", _hostName.c_str()); - DEBUG_ESP_PORT.printf("instance: %s\n", _instanceName.c_str()); -#endif - _conn->flush(); - return; - } - - if (!serviceParsed) - { - serviceNameLen = _conn_read8() % 255; - _conn_readS(serviceName, serviceNameLen); - serviceName[serviceNameLen] = '\0'; - - if (serviceName[0] == '_') - { - memmove(serviceName, serviceName + 1, serviceNameLen); - serviceNameLen--; - serviceParsed = true; - } - else if (serviceNameLen == 5 && strcmp("local", serviceName) == 0) - { - tmp = _conn_read8(); - if (tmp == 0) - { - serviceParsed = true; - serviceNameLen = 0; - protoParsed = true; - protoNameLen = 0; - localParsed = true; - } - else - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_FQDN: %s\n", serviceName); -#endif - _conn->flush(); - return; - } - } - else - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_SERVICE: %s\n", serviceName); -#endif - _conn->flush(); - return; - } - } - - if (!protoParsed) - { - protoNameLen = _conn_read8() % 255; - _conn_readS(protoName, protoNameLen); - protoName[protoNameLen] = '\0'; - if (protoNameLen == 4 && protoName[0] == '_') - { - memmove(protoName, protoName + 1, protoNameLen); - protoNameLen--; - protoParsed = true; - } - else if (strcmp("services", serviceName) == 0 && strcmp("_dns-sd", protoName) == 0) - { - _conn->flush(); - IPAddress interface = _getRequestMulticastInterface(); - _replyToTypeEnumRequest(interface); - return; - } - else - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_PROTO: %s\n", protoName); -#endif - _conn->flush(); - return; - } - } - - if (!localParsed) - { - char localName[32]; - uint8_t localNameLen = _conn_read8() % 31; - _conn_readS(localName, localNameLen); - localName[localNameLen] = '\0'; - tmp = _conn_read8(); - if (localNameLen == 5 && strcmp("local", localName) == 0 && tmp == 0) - { - localParsed = true; - } - else - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_FQDN: %s\n", localName); -#endif - _conn->flush(); - return; - } - } - - if (serviceNameLen > 0 && protoNameLen > 0) - { - servicePort = _getServicePort(serviceName, protoName); - if (servicePort == 0) - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_NO_SERVICE: %s\n", serviceName); -#endif - _conn->flush(); - return; - } - } - else if (serviceNameLen > 0 || protoNameLen > 0) - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_SERVICE_PROTO: %s\n", serviceName); -#endif - _conn->flush(); - return; - } - - // RESPOND - -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("RX: REQ, ID:%u, Q:%u, A:%u, NS:%u, ADD:%u\n", packetHeader[0], packetHeader[2], packetHeader[3], packetHeader[4], packetHeader[5]); -#endif - - uint16_t currentType; - uint16_t currentClass; - - int numQuestions = packetHeader[2]; - if (numQuestions > 4) - { - numQuestions = 4; - } - uint16_t questions[4]; - int question = 0; - - while (numQuestions--) - { - currentType = _conn_read16(); - if (currentType & MDNS_NAME_REF) //new header handle it better! - { - currentType = _conn_read16(); - } - currentClass = _conn_read16(); - if (currentClass & MDNS_CLASS_IN) - { - questions[question++] = currentType; - } - - if (numQuestions > 0) - { - if (_conn_read16() != 0xC00C) //new question but for another host/service - { - _conn->flush(); - numQuestions = 0; - } - } - -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("REQ: "); - if (hostNameLen > 0) - { - DEBUG_ESP_PORT.printf("%s.", hostName); - } - if (serviceNameLen > 0) - { - DEBUG_ESP_PORT.printf("_%s.", serviceName); - } - if (protoNameLen > 0) - { - DEBUG_ESP_PORT.printf("_%s.", protoName); - } - DEBUG_ESP_PORT.printf("local. "); - - if (currentType == MDNS_TYPE_AAAA) - { - DEBUG_ESP_PORT.printf(" AAAA "); - } - else if (currentType == MDNS_TYPE_A) - { - DEBUG_ESP_PORT.printf(" A "); - } - else if (currentType == MDNS_TYPE_PTR) - { - DEBUG_ESP_PORT.printf(" PTR "); - } - else if (currentType == MDNS_TYPE_SRV) - { - DEBUG_ESP_PORT.printf(" SRV "); - } - else if (currentType == MDNS_TYPE_TXT) - { - DEBUG_ESP_PORT.printf(" TXT "); - } - else - { - DEBUG_ESP_PORT.printf(" 0x%04X ", currentType); - } - - if (currentClass == MDNS_CLASS_IN) - { - DEBUG_ESP_PORT.printf(" IN "); - } - else if (currentClass == MDNS_CLASS_IN_FLUSH_CACHE) - { - DEBUG_ESP_PORT.printf(" IN[F] "); - } - else - { - DEBUG_ESP_PORT.printf(" 0x%04X ", currentClass); - } - - DEBUG_ESP_PORT.printf("\n"); -#endif - } - uint8_t questionMask = 0; - uint8_t responseMask = 0; - for (i = 0; i < question; i++) - { - if (questions[i] == MDNS_TYPE_A) - { - questionMask |= 0x1; - responseMask |= 0x1; - } - else if (questions[i] == MDNS_TYPE_SRV) - { - questionMask |= 0x2; - responseMask |= 0x3; - } - else if (questions[i] == MDNS_TYPE_TXT) - { - questionMask |= 0x4; - responseMask |= 0x4; - } - else if (questions[i] == MDNS_TYPE_PTR) - { - questionMask |= 0x8; - responseMask |= 0xF; - } - } - - IPAddress interface = _getRequestMulticastInterface(); - return _replyToInstanceRequest(questionMask, responseMask, serviceName, protoName, servicePort, interface); -} - - -/** - STRINGIZE -*/ -#ifndef STRINGIZE -#define STRINGIZE(x) #x -#endif -#ifndef STRINGIZE_VALUE_OF -#define STRINGIZE_VALUE_OF(x) STRINGIZE(x) -#endif - - -void MDNSResponder::enableArduino(uint16_t port, bool auth) -{ - - addService("arduino", "tcp", port); - addServiceTxt("arduino", "tcp", "tcp_check", "no"); - addServiceTxt("arduino", "tcp", "ssh_upload", "no"); - addServiceTxt("arduino", "tcp", "board", STRINGIZE_VALUE_OF(ARDUINO_BOARD)); - addServiceTxt("arduino", "tcp", "auth_upload", (auth) ? "yes" : "no"); -} - -void MDNSResponder::_replyToTypeEnumRequest(IPAddress multicastInterface) -{ - MDNSService* servicePtr; - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - if (servicePtr->_port > 0) - { - char *service = servicePtr->_name; - char *proto = servicePtr->_proto; - //uint16_t port = servicePtr->_port; - -#ifdef DEBUG_ESP_MDNS_TX - DEBUG_ESP_PORT.printf("TX: service:%s, proto:%s\n", service, proto); -#endif - - char sdHostName[] = "_services"; - size_t sdHostNameLen = 9; - char sdServiceName[] = "_dns-sd"; - size_t sdServiceNameLen = 7; - char sdProtoName[] = "_udp"; - size_t sdProtoNameLen = 4; - - char underscore[] = "_"; - - // build service name with _ - char serviceName[os_strlen(service) + 2]; - os_strcpy(serviceName, underscore); - os_strcat(serviceName, service); - size_t serviceNameLen = os_strlen(serviceName); - - //build proto name with _ - char protoName[5]; - os_strcpy(protoName, underscore); - os_strcat(protoName, proto); - size_t protoNameLen = 4; - - //local string - char localName[] = "local"; - size_t localNameLen = 5; - - //terminator - char terminator[] = "\0"; - - //Write the header - _conn->flush(); - uint8_t head[12] = - { - 0x00, 0x00, //ID = 0 - 0x84, 0x00, //Flags = response + authoritative answer - 0x00, 0x00, //Question count - 0x00, 0x01, //Answer count - 0x00, 0x00, //Name server records - 0x00, 0x00, //Additional records - }; - _conn->append(reinterpret_cast(head), 12); - - // Send the Name field (ie. "_services._dns-sd._udp.local") - _conn->append(reinterpret_cast(&sdHostNameLen), 1); // length of "_services" - _conn->append(reinterpret_cast(sdHostName), sdHostNameLen); // "_services" - _conn->append(reinterpret_cast(&sdServiceNameLen), 1); // length of "_dns-sd" - _conn->append(reinterpret_cast(sdServiceName), sdServiceNameLen);// "_dns-sd" - _conn->append(reinterpret_cast(&sdProtoNameLen), 1); // length of "_udp" - _conn->append(reinterpret_cast(sdProtoName), sdProtoNameLen); // "_udp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type, class, ttl and rdata length - uint8_t ptrDataLen = serviceNameLen + protoNameLen + localNameLen + 4; // 4 is three label sizes and the terminator - uint8_t ptrAttrs[10] = - { - 0x00, 0x0c, //PTR record query - 0x00, 0x01, //Class IN - 0x00, 0x00, 0x11, 0x94, //TTL 4500 - 0x00, ptrDataLen, //RData length - }; - _conn->append(reinterpret_cast(ptrAttrs), 10); - - //Send the RData (ie. "_http._tcp.local") - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - _conn->setMulticastInterface(multicastInterface); - _conn->send(); - } - } -} - -void MDNSResponder::_replyToInstanceRequest(uint8_t questionMask, uint8_t responseMask, char * service, char *proto, uint16_t port, IPAddress multicastInterface) -{ - int i; - if (questionMask == 0) - { - return; - } - if (responseMask == 0) - { - return; - } - -#ifdef DEBUG_ESP_MDNS_TX - DEBUG_ESP_PORT.printf("TX: qmask:%01X, rmask:%01X, service:%s, proto:%s, port:%u\n", questionMask, responseMask, service, proto, port); -#endif - - - String instanceName = _instanceName; - size_t instanceNameLen = instanceName.length(); - - String hostName = _hostName; - size_t hostNameLen = hostName.length(); - - char underscore[] = "_"; - - // build service name with _ - char serviceName[os_strlen(service) + 2]; - os_strcpy(serviceName, underscore); - os_strcat(serviceName, service); - size_t serviceNameLen = os_strlen(serviceName); - - //build proto name with _ - char protoName[5]; - os_strcpy(protoName, underscore); - os_strcat(protoName, proto); - size_t protoNameLen = 4; - - //local string - char localName[] = "local"; - size_t localNameLen = 5; - - //terminator - char terminator[] = "\0"; - - uint8_t answerMask = responseMask & questionMask; - uint8_t answerCount = 0; - uint8_t additionalMask = responseMask & ~questionMask; - uint8_t additionalCount = 0; - for (i = 0; i < 4; i++) - { - if (answerMask & (1 << i)) - { - answerCount++; - } - if (additionalMask & (1 << i)) - { - additionalCount++; - } - } - - - //Write the header - _conn->flush(); - uint8_t head[12] = - { - 0x00, 0x00, //ID = 0 - 0x84, 0x00, //Flags = response + authoritative answer - 0x00, 0x00, //Question count - 0x00, answerCount, //Answer count - 0x00, 0x00, //Name server records - 0x00, additionalCount, //Additional records - }; - _conn->append(reinterpret_cast(head), 12); - - for (int responseSection = 0; responseSection < 2; ++responseSection) - { - - // PTR Response - if ((responseSection == 0 ? answerMask : additionalMask) & 0x8) - { - // Send the Name field (ie. "_http._tcp.local") - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type, class, ttl and rdata length - uint8_t ptrDataLen = instanceNameLen + serviceNameLen + protoNameLen + localNameLen + 5; // 5 is four label sizes and the terminator - uint8_t ptrAttrs[10] = - { - 0x00, 0x0c, //PTR record query - 0x00, 0x01, //Class IN - 0x00, 0x00, 0x00, 0x78, //TTL 120 - 0x00, ptrDataLen, //RData length - }; - _conn->append(reinterpret_cast(ptrAttrs), 10); - - //Send the RData (ie. "My IOT device._http._tcp.local") - _conn->append(reinterpret_cast(&instanceNameLen), 1); // length of "My IOT device" - _conn->append(reinterpret_cast(instanceName.c_str()), instanceNameLen);// "My IOT device" - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - } - - //TXT Responce - if ((responseSection == 0 ? answerMask : additionalMask) & 0x4) - { - //Send the name field (ie. "My IOT device._http._tcp.local") - _conn->append(reinterpret_cast(&instanceNameLen), 1); // length of "My IOT device" - _conn->append(reinterpret_cast(instanceName.c_str()), instanceNameLen);// "My IOT device" - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type, class, ttl and rdata length - uint8_t txtDataLen = _getServiceTxtLen(service, proto); - uint8_t txtAttrs[10] = - { - 0x00, 0x10, //TXT record query - 0x80, 0x01, //Class IN, with cache flush - 0x00, 0x00, 0x11, 0x94, //TTL 4500 - 0x00, txtDataLen, //RData length - }; - _conn->append(reinterpret_cast(txtAttrs), 10); - - //Send the RData - MDNSTxt * txtPtr = _getServiceTxt(service, proto); - while (txtPtr != 0) - { - uint8_t txtLen = txtPtr->_txt.length(); - _conn->append(reinterpret_cast(&txtLen), 1); // length of txt - _conn->append(reinterpret_cast(txtPtr->_txt.c_str()), txtLen);// the txt - txtPtr = txtPtr->_next; - } - } - - - //SRV Responce - if ((responseSection == 0 ? answerMask : additionalMask) & 0x2) - { - //Send the name field (ie. "My IOT device._http._tcp.local") - _conn->append(reinterpret_cast(&instanceNameLen), 1); // length of "My IOT device" - _conn->append(reinterpret_cast(instanceName.c_str()), instanceNameLen);// "My IOT device" - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type, class, ttl, rdata length, priority and weight - uint8_t srvDataSize = hostNameLen + localNameLen + 3; // 3 is 2 lable size bytes and the terminator - srvDataSize += 6; // Size of Priority, weight and port - uint8_t srvAttrs[10] = - { - 0x00, 0x21, //Type SRV - 0x80, 0x01, //Class IN, with cache flush - 0x00, 0x00, 0x00, 0x78, //TTL 120 - 0x00, srvDataSize, //RData length - }; - _conn->append(reinterpret_cast(srvAttrs), 10); - - //Send the RData Priority weight and port - uint8_t srvRData[6] = - { - 0x00, 0x00, //Priority 0 - 0x00, 0x00, //Weight 0 - (uint8_t)((port >> 8) & 0xFF), (uint8_t)(port & 0xFF) - }; - _conn->append(reinterpret_cast(srvRData), 6); - //Send the RData (ie. "esp8266.local") - _conn->append(reinterpret_cast(&hostNameLen), 1); // length of "esp8266" - _conn->append(reinterpret_cast(hostName.c_str()), hostNameLen);// "esp8266" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - } - - // A Response - if ((responseSection == 0 ? answerMask : additionalMask) & 0x1) - { - //Send the RData (ie. "esp8266.local") - _conn->append(reinterpret_cast(&hostNameLen), 1); // length of "esp8266" - _conn->append(reinterpret_cast(hostName.c_str()), hostNameLen);// "esp8266" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - uint8_t aaaAttrs[10] = - { - 0x00, 0x01, //TYPE A - 0x80, 0x01, //Class IN, with cache flush - 0x00, 0x00, 0x00, 0x78, //TTL 120 - 0x00, 0x04, //DATA LEN - }; - _conn->append(reinterpret_cast(aaaAttrs), 10); - - // Send RData - uint32_t ip = multicastInterface; - uint8_t aaaRData[4] = - { - (uint8_t)(ip & 0xFF), //IP first octet - (uint8_t)((ip >> 8) & 0xFF), //IP second octet - (uint8_t)((ip >> 16) & 0xFF), //IP third octet - (uint8_t)((ip >> 24) & 0xFF) //IP fourth octet - }; - _conn->append(reinterpret_cast(aaaRData), 4); - } - } - - _conn->setMulticastInterface(multicastInterface); - _conn->send(); -} - -#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) -MDNSResponder MDNS; -#endif - -} // namespace Legacy_MDNSResponder - - - - diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.h b/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.h deleted file mode 100644 index 9d3cfd2f6..000000000 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.h +++ /dev/null @@ -1,166 +0,0 @@ -/* - ESP8266 Multicast DNS (port of CC3000 Multicast DNS library) - Version 1.1 - Copyright (c) 2013 Tony DiCola (tony@tonydicola.com) - ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com) - Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com) - - This is a simple implementation of multicast DNS query support for an Arduino - running on ESP8266 chip. Only support for resolving address queries is currently - implemented. - - Requirements: - - ESP8266WiFi library - - Usage: - - Include the ESP8266 Multicast DNS library in the sketch. - - Call the begin method in the sketch's setup and provide a domain name (without - the '.local' suffix, i.e. just provide 'foo' to resolve 'foo.local'), and the - Adafruit CC3000 class instance. Optionally provide a time to live (in seconds) - for the DNS record--the default is 1 hour. - - Call the update method in each iteration of the sketch's loop function. - - 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. - -*/ -#ifndef ESP8266MDNS_LEGACY_H -#define ESP8266MDNS_LEGACY_H - -#include "ESP8266WiFi.h" -#include "WiFiUdp.h" - -//this should be defined at build time -#ifndef ARDUINO_BOARD -#define ARDUINO_BOARD "generic" -#endif - -class UdpContext; - - -namespace Legacy_MDNSResponder -{ - - -struct MDNSService; -struct MDNSTxt; -struct MDNSAnswer; - -class MDNSResponder -{ -public: - MDNSResponder(); - ~MDNSResponder(); - bool begin(const char* hostName); - bool begin(const String& hostName) - { - return begin(hostName.c_str()); - } - //for compatibility - bool begin(const char* hostName, IPAddress ip, uint32_t ttl = 120) - { - (void) ip; - (void) ttl; - return begin(hostName); - } - bool begin(const String& hostName, IPAddress ip, uint32_t ttl = 120) - { - return begin(hostName.c_str(), ip, ttl); - } - /* Application should call this whenever AP is configured/disabled */ - void notifyAPChange(); - void update(); - - void addService(char *service, char *proto, uint16_t port); - void addService(const char *service, const char *proto, uint16_t port) - { - addService((char *)service, (char *)proto, port); - } - void addService(const String& service, const String& proto, uint16_t port) - { - addService(service.c_str(), proto.c_str(), port); - } - - bool addServiceTxt(char *name, char *proto, char * key, char * value); - bool addServiceTxt(const char *name, const char *proto, const char *key, const char * value) - { - return addServiceTxt((char *)name, (char *)proto, (char *)key, (char *)value); - } - bool addServiceTxt(const String& name, const String& proto, const String& key, const String& value) - { - return addServiceTxt(name.c_str(), proto.c_str(), key.c_str(), value.c_str()); - } - - int queryService(char *service, char *proto); - int queryService(const char *service, const char *proto) - { - return queryService((char *)service, (char *)proto); - } - int queryService(const String& service, const String& proto) - { - return queryService(service.c_str(), proto.c_str()); - } - String hostname(int idx); - IPAddress IP(int idx); - uint16_t port(int idx); - - void enableArduino(uint16_t port, bool auth = false); - - void setInstanceName(String name); - void setInstanceName(const char * name) - { - setInstanceName(String(name)); - } - void setInstanceName(char * name) - { - setInstanceName(String(name)); - } - -private: - struct MDNSService * _services; - UdpContext* _conn; - String _hostName; - String _instanceName; - struct MDNSAnswer * _answers; - struct MDNSQuery * _query; - bool _newQuery; - bool _waitingForAnswers; - WiFiEventHandler _disconnectedHandler; - WiFiEventHandler _gotIPHandler; - - - uint16_t _getServicePort(char *service, char *proto); - MDNSTxt * _getServiceTxt(char *name, char *proto); - uint16_t _getServiceTxtLen(char *name, char *proto); - IPAddress _getRequestMulticastInterface(); - void _parsePacket(); - void _replyToTypeEnumRequest(IPAddress multicastInterface); - void _replyToInstanceRequest(uint8_t questionMask, uint8_t responseMask, char * service, char *proto, uint16_t port, IPAddress multicastInterface); - MDNSAnswer* _getAnswerFromIdx(int idx); - int _getNumAnswers(); - bool _listen(); - void _restart(); -}; - -} // namespace Legacy_MDNSResponder - -#endif //ESP8266MDNS_H - - - diff --git a/libraries/ESP8266mDNS/src/LEAmDNS.cpp b/libraries/ESP8266mDNS/src/LEAmDNS.cpp index 87ff5167f..9e784bfe9 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS.cpp @@ -25,6 +25,7 @@ #include #include +#include "ESP8266mDNS.h" #include "LEAmDNS_Priv.h" diff --git a/libraries/ESP8266mDNS/src/LEAmDNS.h b/libraries/ESP8266mDNS/src/LEAmDNS.h index 7dfd333f1..6037f0d13 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS.h @@ -14,7 +14,7 @@ - Presenting a DNS-SD service to interested observers, eg. a http server by presenting _http._tcp service - Support for multi-level compressed names in input; in output only a very simple one-leven full-name compression is implemented - Probing host and service domains for uniqueness in the local network - - Tiebreaking while probing is supportet in a very minimalistic way (the 'higher' IP address wins the tiebreak) + - Tiebreaking while probing is supported in a very minimalistic way (the 'higher' IP address wins the tiebreak) - Announcing available services after successful probing - Using fixed service TXT items or - Using dynamic service TXT items for presented services (via callback) @@ -172,6 +172,7 @@ class MDNSResponder { public: /* INTERFACE */ + MDNSResponder(void); virtual ~MDNSResponder(void); diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp new file mode 100644 index 000000000..f74739ae6 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -0,0 +1,1331 @@ +/* + LEAmDNS2Host.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 +#include // LwipIntf::stateUpCB() +#include // strrstr() + +#include "ESP8266mDNS.h" +#include "LEAmDNS2Host.h" +#include "LEAmDNS2_Priv.h" + +#ifdef MDNS_IPV4_SUPPORT +#include +#endif +#ifdef MDNS_IPV6_SUPPORT +#include +#endif + +/** + STRINGIZE +*/ +#ifndef STRINGIZE +#define STRINGIZE(x) #x +#endif +#ifndef STRINGIZE_VALUE_OF +#define STRINGIZE_VALUE_OF(x) STRINGIZE(x) +#endif + +namespace esp8266 +{ + + +namespace experimental +{ + + +/* + + HELPERS + +*/ + +/* + clsLEAmDNS2_Host::indexDomainName (static) + + Increments the given domain 'p_pcDomainName' by appending a delimiter and an index number. + + If the given domain name already has a numeric index (after the given delimiter), this index + is incremented. If not, the delimiter and index '2' is added. + + If 'p_pcDomainName' is empty (==0), the given default name 'p_pcDefaultDomainName' is used, + if no default is given, 'esp8266' is used. + +*/ + +clsLEAMDNSHost::fnProbeResultCallback clsLEAMDNSHost::stProbeResultCallback = nullptr; + +const char* clsLEAMDNSHost::indexDomainName(const char* p_pcDomainName, + const char* p_pcDivider /*= "-"*/, + const char* p_pcDefaultDomainName /*= 0*/) +{ + static char acResultDomainName[clsConsts::stDomainLabelMaxLength]; + *acResultDomainName = 0; + + // Ensure a divider exists; use '-' as default + const char* pcDivider = (p_pcDivider ? : "-"); + + if (p_pcDomainName) + { + // Given domain + const char* pFoundDivider = strrstr(p_pcDomainName, pcDivider); + if (pFoundDivider) // maybe already extended + { + char* pEnd = nullptr; + unsigned long ulIndex = strtoul((pFoundDivider + strlen(pcDivider)), &pEnd, 10); + if ((ulIndex) && + ((pEnd - p_pcDomainName) == (ptrdiff_t)strlen(p_pcDomainName)) && + (!*pEnd)) + { + // Valid (old) index found + char acIndexBuffer[16]; + sprintf(acIndexBuffer, "%lu", (++ulIndex)); + //size_t stLength = ((pFoundDivider - p_pcDomainName + strlen(pcDivider)) + strlen(acIndexBuffer) + 1); + + memcpy(acResultDomainName, p_pcDomainName, (pFoundDivider - p_pcDomainName + strlen(pcDivider))); + acResultDomainName[pFoundDivider - p_pcDomainName + strlen(pcDivider)] = 0; + strcat(acResultDomainName, acIndexBuffer); + } + else + { + pFoundDivider = nullptr; // 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_pcDomainName) + (strlen(pcDivider) + 1 + 1); // Name + Divider + '2' + '\0' + sprintf(acResultDomainName, "%s%s2", p_pcDomainName, pcDivider); + } + } + else + { + // No given domain, use base or default + const char* cpcDefaultName = (p_pcDefaultDomainName ? : "esp8266"); + strncpy(acResultDomainName, cpcDefaultName, sizeof(acResultDomainName)); + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[mDNS] indexDomainName: From '%s' to '%s'\n"), (p_pcDomainName ? : ""), acResultDomainName);); + return acResultDomainName; +} + + +/* + clsLEAmDNS2_Host::setStationHostName (static) + + Sets the staion hostname + +*/ +// static +bool clsLEAMDNSHost::setNetIfHostName(const char* p_pcHostName) +{ + if (p_pcHostName) + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + { + if (netif_is_up(pNetIf)) + { + netif_set_hostname(pNetIf, p_pcHostName); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[mDNS] setNetIfHostName host name: %s on " NETIFID_STR "!\n"), p_pcHostName, NETIFID_VAL(pNetIf));); + } + } + return true; +} + + +/** + clsLEAmDNS2_Host::sm_pBackbone + +*/ +clsLEAMDNSHost::clsBackbone* clsLEAMDNSHost::clsBackbone::sm_pBackbone = 0; + +/** + Consts::... + +*/ +const char* clsLEAMDNSHost::clsConsts::pcLocal = "local"; +const char* clsLEAMDNSHost::clsConsts::pcServices = "services"; +const char* clsLEAMDNSHost::clsConsts::pcDNSSD = "dns-sd"; +const char* clsLEAMDNSHost::clsConsts::pcUDP = "udp"; +//const char* clsLEAMDNSHost::clsConsts::pcTCP = "tcp"; + +#ifdef MDNS_IPV4_SUPPORT +const char* clsLEAMDNSHost::clsConsts::pcReverseIPv4Domain = "in-addr"; +#endif +#ifdef MDNS_IPV6_SUPPORT +const char* clsLEAMDNSHost::clsConsts::pcReverseIPv6Domain = "ip6"; +#endif +const char* clsLEAMDNSHost::clsConsts::pcReverseTopDomain = "arpa"; + + +/* + clsLEAmDNS2_Host::clsLEAmDNS2_Host constructor + +*/ +clsLEAMDNSHost::clsLEAMDNSHost(void) + : m_pUDPContext(0), + m_pcHostName(0), + m_pcDefaultInstanceName(0), + m_ProbeInformation() +{ +} + +/* + clsLEAmDNS2_Host::~clsLEAmDNS2_Host destructor + +*/ +clsLEAMDNSHost::~clsLEAMDNSHost(void) +{ + close(); +} + +/* + + INIT + +*/ + +/* + clsLEAmDNS2_Host::begin (hostname, probe_callback) + + setup global mDNS (adding all netif to the multicast groups), + sets up the instance data (hostname, ...) and starts the probing process + +*/ +bool clsLEAMDNSHost::begin(const char* p_pcHostName, + clsLEAMDNSHost::fnProbeResultCallback p_fnCallback /*= 0*/) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s)\n"), _DH(), (p_pcHostName ? : "_"));); + + bool bResult = false; + + if (m_pUDPContext) + { + close(); + } + + bResult = (setHostName(p_pcHostName)) && + (_joinMulticastGroups()) && + (p_fnCallback ? setProbeResultCallback(p_fnCallback) : true) && + ((m_pUDPContext = _allocBackbone())) && + (restart()); + + if (!bResult) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s begin: FAILED for '%s'!\n"), _DH(), (p_pcHostName ? : "-"));); + return false; + } + + bResult = LwipIntf::stateUpCB([this](netif * nif) + { + (void)nif; + // This is called when a new interface appears: + // resend announces on all available interfaces. + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s a new interface %c%c/%d is up, restarting mDNS\n"), + _DH(), nif->name[0], nif->name[1], netif_get_index(nif));); + if (restart()) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s restart: success!\n"), _DH())); + } + else + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s restart failed!\n"), _DH())); + } + // No need to react when an interface disappears, + // because mDNS always loop on all available interfaces. + }); + + if (!bResult) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s begin: could not add netif status callback\n"), _DH())); + } + return bResult; +} + +/* + clsLEAmDNS2_Host::close + +*/ +bool clsLEAMDNSHost::close(void) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s close\n"), _DH());); + + m_pUDPContext = nullptr; + return ((_leaveMulticastGroups()) && + (_releaseBackbone())); +} + + +/* + + HOSTNAME + +*/ + +/* + clsLEAmDNS2_Host::setHostName + +*/ +bool clsLEAMDNSHost::setHostName(const char* p_pcHostName) +{ + bool bResult; + if ((bResult = _allocHostName(p_pcHostName))) + { + m_ProbeInformation.clear(false); + m_ProbeInformation.m_ProbingStatus = clsProbeInformation_Base::enuProbingStatus::ReadyToStart; + + // Replace 'auto-set' service names + for (clsService* pService : m_Services) + { + if ((pService->m_bAutoName) && + (!m_pcDefaultInstanceName)) + { + if (!((bResult = pService->setInstanceName(p_pcHostName)))) + { + break; + } + } + } + } + return bResult; +} + +/* + clsLEAmDNS2_Host::indexHostName + +*/ +bool clsLEAMDNSHost::indexHostName(void) +{ + return setHostName(clsLEAMDNSHost::indexDomainName(hostName(), "-", 0)); +} + +/* + clsLEAmDNS2_Host::hostName + +*/ +const char* clsLEAMDNSHost::hostName(void) const +{ + return m_pcHostName; +} + +/* + clsLEAmDNS2_Host::setProbeResultCallback + +*/ +bool clsLEAMDNSHost::setProbeResultCallback(clsLEAMDNSHost::fnProbeResultCallback p_fnCallback) +{ + m_ProbeInformation.m_fnProbeResultCallback = p_fnCallback; + return true; +} + +/* + clsLEAmDNS2_Host::probeStatus + +*/ +bool clsLEAMDNSHost::probeStatus(void) const +{ + return (clsProbeInformation_Base::enuProbingStatus::DoneFinally == m_ProbeInformation.m_ProbingStatus); +} + + +/* + + SERVICES + +*/ + +/* + clsLEAmDNS2_Host::setDefaultInstanceName + +*/ +bool clsLEAMDNSHost::setDefaultInstanceName(const char* p_pcDefaultInstanceName) +{ + bool bResult; + if ((bResult = _allocDefaultInstanceName(p_pcDefaultInstanceName))) + { + // Replace 'auto-set' service names + for (clsService* pService : m_Services) + { + if (pService->m_bAutoName) + { + if (!((bResult = pService->setInstanceName(p_pcDefaultInstanceName)))) + { + break; + } + } + } + } + return bResult; +} + +/* + clsLEAmDNS2_Host::defaultInstanceName + +*/ +const char* clsLEAMDNSHost::defaultInstanceName(void) const +{ + return m_pcDefaultInstanceName; +} + +/* + clsLEAmDNS2_Host::addService + +*/ +clsLEAMDNSHost::clsService* clsLEAMDNSHost::addService(const char* p_pcInstanceName, + const char* p_pcType, + const char* p_pcProtocol, + uint16_t p_u16Port, + clsLEAMDNSHost::clsService::fnProbeResultCallback p_fnCallback /*= 0*/) +{ + clsService* pService = nullptr; + + if (!((pService = findService(_instanceName(p_pcInstanceName), p_pcType, p_pcProtocol, p_u16Port)))) + { + // Not already used + if ((pService = new clsService)) + { + if ((pService->setInstanceName(_instanceName(p_pcInstanceName))) && + (pService->setType(p_pcType)) && + (pService->setProtocol(p_pcProtocol)) && + (pService->setPort(p_u16Port)) && + (p_fnCallback ? pService->setProbeResultCallback(p_fnCallback) : true)) + { + m_Services.push_back(pService); + } + else + { + delete pService; + pService = nullptr; + } + } + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s addService: %s to add service '%s.%s.%s.local'!\n"), _DH(pService), (pService ? "Succeeded" : "FAILED"), _instanceName(p_pcInstanceName) ? : "-", (p_pcType ? : ""), (p_pcProtocol ? : ""));); + DEBUG_EX_ERR(if (!pService) DEBUG_OUTPUT.printf_P(PSTR("%s addService: FAILED to add service '%s.%s.%s.local'!\n"), _DH(pService), _instanceName(p_pcInstanceName) ? : "-", (p_pcType ? : ""), (p_pcProtocol ? : ""));); + return pService; +} + +/* + clsLEAmDNS2_Host::removeService + +*/ +bool clsLEAMDNSHost::removeService(clsLEAMDNSHost::clsService* p_pService) +{ + bool bResult = true; + + if (p_pService && + (m_Services.end() != std::find(m_Services.begin(), m_Services.end(), p_pService))) + { + bResult = bResult && _announceService(*p_pService, false); + } + + m_Services.remove(p_pService); + delete p_pService; + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _removeService: FAILED!\n"), _DH(p_pService));); + return bResult; +} + +/* + clsLEAmDNS2_Host::findService (const) + +*/ +const clsLEAMDNSHost::clsService* clsLEAMDNSHost::findService(const char* p_pcInstanceName, + const char* p_pcType, + const char* p_pcProtocol, + uint16_t p_u16Port/*= (uint16_t)(-1)*/) const +{ + clsService* pFoundService = nullptr; + + for (clsService* pService : m_Services) + { + if ((0 == strcmp(pService->instanceName(), _instanceName(p_pcInstanceName))) && + (0 == strcmp(pService->type(), p_pcType)) && + (0 == strcmp(pService->protocol(), p_pcProtocol)) && + (((uint16_t)(-1) == p_u16Port) || + (pService->port() == p_u16Port))) + { + pFoundService = pService; + break; + } + } + return pFoundService; +} + +/* + clsLEAmDNS2_Host::findService + +*/ +clsLEAMDNSHost::clsService* clsLEAMDNSHost::findService(const char* p_pcInstanceName, + const char* p_pcType, + const char* p_pcProtocol, + uint16_t p_u16Port /*= (uint16_t)(-1)*/) +{ + return (clsService*)((const clsLEAMDNSHost*)this)->findService(p_pcInstanceName, p_pcType, p_pcProtocol, p_u16Port); +} + +/* + clsLEAMDNSHost::services + +*/ +const clsLEAMDNSHost::clsService::list& clsLEAMDNSHost::services(void) const +{ + return m_Services; +} + + +/* + + QUERIES + +*/ + +/* + clsLEAmDNS2_Host::queryService + +*/ +clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryService(const char* p_pcService, + const char* p_pcProtocol, + const uint16_t p_u16Timeout) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryService '_%s._%s.local'\n"), _DH(), p_pcService, p_pcProtocol);); + + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector ret; + + if (_removeLegacyQuery() && + (p_pcService) && (*p_pcService) && + (p_pcProtocol) && (*p_pcProtocol) && + (p_u16Timeout)) + { + std::list queries; + + clsQuery* pQuery = nullptr; + if (((pQuery = _allocQuery(clsQuery::enuQueryType::Service))) && + (_buildDomainForService(p_pcService, p_pcProtocol, pQuery->m_Domain))) + { + if (((pQuery->m_bStaticQuery = true)) && (_sendQuery(*pQuery))) + { + queries.push_back(pQuery); + } + else + { + // FAILED to send query + _removeQuery(pQuery); + } + } + + if (queries.size()) + { + // Wait for answers to arrive + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryService: Waiting %u ms for answers...\n"), _DH(), p_u16Timeout);); + //XXXFIXME could this delay be ASYNC? + delay(p_u16Timeout); + + // All answers should have arrived by now -> stop adding new answers + for (auto& q : queries) + { + q->m_bAwaitingAnswers = false; + ret.insert(ret.end(), std::make_move_iterator(q->answerAccessors().begin()), std::make_move_iterator(q->answerAccessors().end())); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s queryService: INVALID input data!\n"), _DH());); + } + + } + return ret; +} + +/* + clsLEAmDNS2_Host::queryHost + +*/ +clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryHost(const char* p_pcHostName, + const uint16_t p_u16Timeout) +{ + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector ret; + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost '%s.local'\n"), _DH(), p_pcHostName);); + + if ((p_pcHostName) && (*p_pcHostName) && + (p_u16Timeout) && + (_removeLegacyQuery())) + { + std::list queries; + + clsQuery* pQuery = nullptr; + if (((pQuery = _allocQuery(clsQuery::enuQueryType::Host))) && + (_buildDomainForHost(p_pcHostName, pQuery->m_Domain))) + { + if (((pQuery->m_bStaticQuery = true)) && (_sendQuery(*pQuery))) + { + queries.push_back(pQuery); + } + else + { + // FAILED to send query + _removeQuery(pQuery); + } + } + + + if (queries.size()) + { + // Wait for answers to arrive + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost: Waiting %u ms for answers...\n"), _DH(), p_u16Timeout);); + //XXXFIXME could this delay be ASYNC? + delay(p_u16Timeout); + + // All answers should have arrived by now -> stop adding new answers + + for (auto& q : queries) + { + q->m_bAwaitingAnswers = false; + ret.insert(ret.end(), std::make_move_iterator(q->answerAccessors().begin()), std::make_move_iterator(q->answerAccessors().end())); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost: INVALID input data!\n"), _DH());); + } + } + + return ret; +} +/* + clsLEAmDNS2_Host::removeQuery + +*/ +bool clsLEAMDNSHost::removeQuery(void) +{ + return _removeLegacyQuery(); +} + +/* + clsLEAmDNS2_Host::hasQuery + +*/ +bool clsLEAMDNSHost::hasQuery(void) +{ + return (0 != _findLegacyQuery()); +} + +/* + clsLEAmDNS2_Host::getQuery + +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::getQuery(void) const +{ + return _findLegacyQuery(); +} + +/* + clsLEAmDNS2_Host::installServiceQuery (answer) + +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcService, + const char* p_pcProtocol, + clsLEAMDNSHost::clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer) +{ + clsQuery* pQuery = nullptr; + if ((pQuery = _installServiceQuery(p_pcService, p_pcProtocol))) + { + pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; + } + return pQuery; +} + +/* + clsLEAmDNS2_Host::installServiceQuery (accessor) + +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcService, + const char* p_pcProtocol, + clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor) +{ + clsQuery* pQuery = nullptr; + if ((pQuery = _installServiceQuery(p_pcService, p_pcProtocol))) + { + pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; + } + return pQuery; +} + +/* + clsLEAmDNS2_Host::installHostQuery (answer) +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installHostQuery(const char* p_pcHostName, + clsLEAMDNSHost::clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer) +{ + clsQuery* pQuery = nullptr; + if ((p_pcHostName) && (*p_pcHostName)) + { + clsRRDomain domain; + if ((pQuery = ((_buildDomainForHost(p_pcHostName, domain)) + ? _installDomainQuery(domain, clsQuery::enuQueryType::Host) + : nullptr))) + { + pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; + } + } + return pQuery; +} +/* + clsLEAmDNS2_Host::installHostQuery (accessor) +*/ + +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installHostQuery(const char* p_pcHostName, + clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor) +{ + clsQuery* pQuery = nullptr; + if ((p_pcHostName) && (*p_pcHostName)) + { + clsRRDomain domain; + if ((pQuery = ((_buildDomainForHost(p_pcHostName, domain)) + ? _installDomainQuery(domain, clsQuery::enuQueryType::Host) + : nullptr))) + { + pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; + } + } + return pQuery; +} + +/* + clsLEAmDNS2_Host::removeQuery +*/ +bool clsLEAMDNSHost::removeQuery(clsLEAMDNSHost::clsQuery * p_pMDNSQuery) +{ + bool bResult = ((p_pMDNSQuery) && + (_removeQuery(p_pMDNSQuery))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeQuery: FAILED!\n"), _DH());); + return bResult; +} + +/* + PROCESSING +*/ + +/* + clsLEAmDNS2_Host::update +*/ +bool clsLEAMDNSHost::update(void) +{ + if (!_updateProbeStatus()) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: FAILED\n"), _DH())); + return false; + } + + if (!_checkQueryCache()) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: FAILED\n"), _DH())); + return false; + } + + return true; +} + +/* + clsLEAmDNS2_Host::announce +*/ + +bool clsLEAMDNSHost::announce(bool p_bAnnounce /*= true*/, + bool p_bIncludeServices /*= true*/) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s ::announce() externally called\n"), _DH());); + return _announce(p_bAnnounce, p_bIncludeServices); +} + +/* + clsLEAmDNS2_Host::announceService +*/ + +bool clsLEAMDNSHost::announceService(clsService * p_pService, + bool p_bAnnounce /*= true*/) +{ + + return _announceService(*p_pService, p_bAnnounce); +} + + +/* + clsLEAmDNS2_Host::restart +*/ +bool clsLEAMDNSHost::restart(void) +{ + return (_resetProbeStatus(true)); // Stop and restart probing +} + + +/* + clsLEAMDNSHost_Legacy::enableArduino +*/ +clsLEAMDNSHost::clsService* clsLEAMDNSHost::enableArduino(uint16_t p_u16Port, + bool p_bAuthUpload /*= false*/) +{ + clsLEAMDNSHost::clsService* svc = addService(nullptr, "arduino", "tcp", p_u16Port); + if (svc) + { + if ((!svc->addServiceTxt("tcp_check", "no")) + || (!svc->addServiceTxt("ssh_upload", "no")) + || (!svc->addServiceTxt("board", STRINGIZE_VALUE_OF(ARDUINO_BOARD))) + || (!svc->addServiceTxt("auth_upload", (p_bAuthUpload) ? "yes" : "no"))) + { + removeService(svc); + svc = nullptr; + } + } + return svc; +} + + +/* + P R O T E C T E D +*/ + +/* + + BACKBONE + +*/ + +/* + clsLEAmDNS2_Host::_allocBackbone + +*/ +UdpContext* clsLEAMDNSHost::_allocBackbone(void) +{ + UdpContext* pUDPContext = nullptr; + + if (!clsBackbone::sm_pBackbone) + { + // Not yet created + clsBackbone::sm_pBackbone = new clsBackbone; + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocBackbone: Created backbone.\n"), _DH());); + + if ((clsBackbone::sm_pBackbone) && + (!clsBackbone::sm_pBackbone->init())) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _allocBackbone: FAILED to init backbone!\n"), _DH());); + + delete clsBackbone::sm_pBackbone; + clsBackbone::sm_pBackbone = nullptr; + } + } + if (clsBackbone::sm_pBackbone) + { + pUDPContext = clsBackbone::sm_pBackbone->addHost(this); + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocBackbone: %s to add host to backbone.\n"), _DH(), (pUDPContext ? "Succeeded" : "FAILED"));); + return pUDPContext; +} + +/* + clsLEAmDNS2_Host::_releaseBackbone + +*/ +bool clsLEAMDNSHost::_releaseBackbone(void) +{ + bool bResult = false; + + if ((clsBackbone::sm_pBackbone) && + ((bResult = clsBackbone::sm_pBackbone->removeHost(this))) && + (0 == clsBackbone::sm_pBackbone->hostCount())) + { + delete clsBackbone::sm_pBackbone; + clsBackbone::sm_pBackbone = nullptr; + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _releaseBackbone: Released backbone."), _DH());); + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _releaseBackbone: %s to remove host from backbone."), _DH(), (bResult ? "Succeeded" : "FAILED"));); + return bResult; +} + + +/* + + MULTICAST GROUPS + +*/ + +/* + clsLEAmDNS2_Host::_joinMulticastGroups +*/ +bool clsLEAMDNSHost::_joinMulticastGroups(void) +{ + bool bResult = false; + + // Join multicast group(s) + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + { + if (netif_is_up(pNetIf)) + { +#ifdef MDNS_IPV4_SUPPORT + ip_addr_t multicast_addr_V4 = DNS_MQUERY_IPV4_GROUP_INIT; + if (!(pNetIf->flags & NETIF_FLAG_IGMP)) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: Setting flag: flags & NETIF_FLAG_IGMP\n"), _DH());); + pNetIf->flags |= NETIF_FLAG_IGMP; + + if (ERR_OK != igmp_start(pNetIf)) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: igmp_start FAILED!\n"), _DH());); + } + } + + if ((ERR_OK == igmp_joingroup_netif(pNetIf, ip_2_ip4(&multicast_addr_V4)))) + { + bResult = true; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: igmp_joingroup_netif(" NETIFID_STR ": %s) FAILED!\n"), + _DH(), NETIFID_VAL(pNetIf), IPAddress(multicast_addr_V4).toString().c_str());); + } +#endif + +#ifdef MDNS_IPV6_SUPPORT + ip_addr_t multicast_addr_V6 = DNS_MQUERY_IPV6_GROUP_INIT; + bResult = ((bResult) && + (ERR_OK == mld6_joingroup_netif(pNetIf, ip_2_ip6(&multicast_addr_V6)))); + DEBUG_EX_ERR_IF(!bResult, DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: mld6_joingroup_netif (" NETIFID_STR ") FAILED!\n"), + _DH(), NETIFID_VAL(pNetIf))); +#endif + } + } + return bResult; +} + +/* + clsLEAmDNS2_Host::_leaveMulticastGroups +*/ +bool clsLEAMDNSHost::_leaveMulticastGroups() +{ + bool bResult = false; + + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + { + if (netif_is_up(pNetIf)) + { + bResult = true; + + // Leave multicast group(s) +#ifdef MDNS_IPV4_SUPPORT + ip_addr_t multicast_addr_V4 = DNS_MQUERY_IPV4_GROUP_INIT; + if (ERR_OK != igmp_leavegroup_netif(pNetIf, ip_2_ip4(&multicast_addr_V4))) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("\n"));); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + ip_addr_t multicast_addr_V6 = DNS_MQUERY_IPV6_GROUP_INIT; + if (ERR_OK != mld6_leavegroup_netif(pNetIf, ip_2_ip6(&multicast_addr_V6)/*&(multicast_addr_V6.u_addr.ip6)*/)) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("\n"));); + } +#endif + } + } + return bResult; +} + + +/* + PROCESSING +*/ + +/* + clsLEAmDNS2_Host::_processUDPInput +*/ +bool clsLEAMDNSHost::_processUDPInput(void) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput\n"), _DH());); + + bool bResult = _parseMessage(); + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s processUDPInput: FAILED!\n"), _DH());); + + return bResult; +} + + +/* + DOMAIN NAMES +*/ + +/* + clsLEAmDNS2_Host::_allocDomainName +*/ +bool clsLEAMDNSHost::_allocDomainName(const char* p_pcNewDomainName, + char*& p_rpcDomainName) +{ + bool bResult = false; + + _releaseDomainName(p_rpcDomainName); + + size_t stLength = 0; + if ((p_pcNewDomainName) && + (clsConsts::stDomainLabelMaxLength >= (stLength = strlen(p_pcNewDomainName)))) // char max size for a single label + { + // Copy in hostname characters as lowercase + if ((bResult = (0 != (p_rpcDomainName = new char[stLength + 1])))) + { +#ifdef MDNS_FORCE_LOWERCASE_HOSTNAME + size_t i = 0; + for (; i < stLength; ++i) + { + p_rpcDomainName[i] = tolower(p_pcNewDomainName[i]); + } + p_rpcDomainName[i] = 0; +#else + strncpy(p_rpcDomainName, p_pcNewDomainName, (stLength + 1)); +#endif + } + } + return bResult; +} + +/* + clsLEAmDNS2_Host::_releaseDomainName +*/ +bool clsLEAMDNSHost::_releaseDomainName(char*& p_rpcDomainName) +{ + bool bResult; + if ((bResult = (nullptr != p_rpcDomainName))) + { + delete[] p_rpcDomainName; + p_rpcDomainName = nullptr; + } + return bResult; +} + +/* + clsLEAmDNS2_Host::_allocHostName +*/ +bool clsLEAMDNSHost::_allocHostName(const char* p_pcHostName) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocHostName (%s)\n"), _DH(), p_pcHostName);); + return _allocDomainName(p_pcHostName, m_pcHostName); +} + +/* + clsLEAmDNS2_Host::_releaseHostName +*/ +bool clsLEAMDNSHost::_releaseHostName(void) +{ + return _releaseDomainName(m_pcHostName); +} + +/* + clsLEAmDNS2_Host::_allocDefaultInstanceName +*/ +bool clsLEAMDNSHost::_allocDefaultInstanceName(const char* p_pcDefaultInstanceName) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocDefaultInstanceName (%s)\n"), _DH(), p_pcDefaultInstanceName);); + return _allocDomainName(p_pcDefaultInstanceName, m_pcDefaultInstanceName); +} + +/* + clsLEAmDNS2_Host::_releaseDefaultInstanceName +*/ +bool clsLEAMDNSHost::_releaseDefaultInstanceName(void) +{ + return _releaseDomainName(m_pcDefaultInstanceName); +} + +/* + clsLEAmDNS2_Host::_instanceName +*/ +const char* clsLEAMDNSHost::_instanceName(const char* p_pcInstanceName) const +{ + return (p_pcInstanceName ? : (m_pcDefaultInstanceName ? : (m_pcHostName ? : nullptr))); +} + + +/* + SERVICE TXT +*/ + +/* + clsLEAmDNS2_Host::_collectServiceTxts +*/ +bool clsLEAMDNSHost::_collectServiceTxts(clsLEAMDNSHost::clsService & p_rService) +{ + if (p_rService.m_fnTxtCallback) + { + p_rService.m_fnTxtCallback(p_rService); + } + return true; +} + +/* + clsLEAmDNS2_Host::_releaseTempServiceTxts +*/ +bool clsLEAMDNSHost::_releaseTempServiceTxts(clsLEAMDNSHost::clsService & p_rService) +{ + return (p_rService.m_Txts.removeTempTxts()); +} + + +/* + + QUERIES + +*/ + +/* + MDNSResponder::_allocQuery + +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_allocQuery(clsLEAMDNSHost::clsQuery::enuQueryType p_QueryType) +{ + clsQuery* pQuery = new clsQuery(p_QueryType); + if (pQuery) + { + // Link to query list + m_Queries.push_back(pQuery); + } + return pQuery; +} + +/* + MDNSResponder:clsHost:::_removeQuery + +*/ +bool clsLEAMDNSHost::_removeQuery(clsLEAMDNSHost::clsQuery * p_pQuery) +{ + bool bResult = false; + + clsQuery::list::iterator it(p_pQuery + ? std::find(m_Queries.begin(), m_Queries.end(), p_pQuery) + : m_Queries.end()); + if (m_Queries.end() != it) + { + m_Queries.erase(it); + delete p_pQuery; + + bResult = true; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _releaseQuery: INVALID query!"), _DH());); + } + return bResult; +} + +/* + clsLEAmDNS2_Host::_removeLegacyQuery + +*/ +bool clsLEAMDNSHost::_removeLegacyQuery(void) +{ + clsQuery* pLegacyQuery = nullptr; + return (((pLegacyQuery = _findLegacyQuery())) + ? _removeQuery(pLegacyQuery) + : true); +} + +/* + clsLEAmDNS2_Host::_findLegacyQuery + +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_findLegacyQuery(void) const +{ + clsQuery* pLegacyQuery = nullptr; + + for (clsQuery* pQuery : m_Queries) + { + if (pQuery->m_bStaticQuery) + { + pLegacyQuery = pQuery; + break; + } + } + return pLegacyQuery; +} + +/* + clsLEAmDNS2_Host::_releaseQueries + +*/ +bool clsLEAMDNSHost::_releaseQueries(void) +{ + for (clsQuery* pQuery : m_Queries) + { + delete pQuery; + } + m_Queries.clear(); + return true; +} + +/* + clsLEAmDNS2_Host::_findNextQueryByDomain + +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_findNextQueryByDomain(const clsLEAMDNSHost::clsRRDomain & p_Domain, + const clsLEAMDNSHost::clsQuery::enuQueryType p_QueryType, + const clsQuery * p_pPrevQuery) +{ + clsQuery* pMatchingQuery = nullptr; + + clsQuery::list::iterator it(m_Queries.begin()); + if (p_pPrevQuery) + { + if (m_Queries.end() != ((it = std::find(m_Queries.begin(), m_Queries.end(), p_pPrevQuery)))) + { + // Found previous object + it++; + } + DEBUG_EX_ERR(else + { + DEBUG_OUTPUT.printf_P(PSTR("%s _findNextQueryByDomain: FAILED to find 'previous' object!\n"), _DH()); + }); // if not prev was found -> 'cancel' + } + + for (; it != m_Queries.end(); it++) + { + if (((clsQuery::enuQueryType::None == p_QueryType) || + ((*it)->m_QueryType == p_QueryType)) && + (p_Domain == (*it)->m_Domain)) + { + pMatchingQuery = *it; + break; + } + } + return pMatchingQuery; +} + +/* + clsLEAmDNS2_Host::_installServiceQuery + +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installServiceQuery( + const char* p_pcService, + const char* p_pcProtocol) +{ + clsQuery* pMDNSQuery = nullptr; + + if ((p_pcService) && (*p_pcService) && + (p_pcProtocol) && (*p_pcProtocol) && + ((pMDNSQuery = _allocQuery(clsQuery::enuQueryType::Service))) && + (_buildDomainForService(p_pcService, p_pcProtocol, pMDNSQuery->m_Domain))) + { + pMDNSQuery->m_bStaticQuery = false; + + if (_sendQuery(*pMDNSQuery)) + { + pMDNSQuery->m_u32SentCount = 1; + pMDNSQuery->m_ResendTimeout.reset(clsConsts::u32DynamicQueryResendDelay); + } + else + { + _removeQuery(pMDNSQuery); + } + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _installServiceQuery: %s for '_%s._%s.local'!\n\n"), _DH(), (pMDNSQuery ? "Succeeded" : "FAILED"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); + DEBUG_EX_ERR(if (!pMDNSQuery) DEBUG_OUTPUT.printf_P(PSTR("%s _installServiceQuery: FAILED for '_%s._%s.local'!\n\n"), _DH(), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); + return pMDNSQuery; +} + +/* + clsLEAmDNS2_Host::_installDomainQuery +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installDomainQuery( + clsLEAMDNSHost::clsRRDomain & p_Domain, + clsLEAMDNSHost::clsQuery::enuQueryType p_QueryType) +{ + clsQuery* pQuery = nullptr; + + if ((pQuery = _allocQuery(p_QueryType))) + { + pQuery->m_Domain = p_Domain; + pQuery->m_bStaticQuery = false; + + if (_sendQuery(*pQuery)) + { + pQuery->m_u32SentCount = 1; + pQuery->m_ResendTimeout.reset(clsConsts::u32DynamicQueryResendDelay); + } + else + { + _removeQuery(pQuery); + } + } + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _installDomainQuery: %s for "), (pQuery ? "Succeeded" : "FAILED"), _DH()); + _printRRDomain(p_Domain); + DEBUG_OUTPUT.println(); + ); + DEBUG_EX_ERR_IF(!pQuery, + DEBUG_OUTPUT.printf_P(PSTR("%s _installDomainQuery: FAILED for "), _DH()); + _printRRDomain(p_Domain); + DEBUG_OUTPUT.println()); + return pQuery; +} + +/* + clsLEAmDNS2_Host::_hasQueriesWaitingForAnswers +*/ +bool clsLEAMDNSHost::_hasQueriesWaitingForAnswers(void) const +{ + bool bOpenQueries = false; + + for (const clsQuery* pQuery : m_Queries) + { + if (pQuery->m_bAwaitingAnswers) + { + bOpenQueries = true; + break; + } + } + return bOpenQueries; +} + +/* + clsLEAmDNS2_Host::_executeQueryCallback +*/ +bool clsLEAMDNSHost::_executeQueryCallback(const clsQuery & p_Query, + const clsQuery::clsAnswer & p_Answer, + clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, + bool p_bSetContent) +{ + if (p_Query.m_fnCallbackAnswer) + { + p_Query.m_fnCallbackAnswer(p_Query, p_Answer, p_QueryAnswerTypeFlags, p_bSetContent); + } + if (p_Query.m_fnCallbackAccessor) + { + p_Query.m_fnCallbackAccessor(p_Query, clsQuery::clsAnswerAccessor(&p_Answer), p_QueryAnswerTypeFlags, p_bSetContent); + } + return true; +} + + +} // namespace MDNSImplementation + + +} // namespace esp8266 + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h new file mode 100644 index 000000000..ddc5fc1ad --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -0,0 +1,1584 @@ +/* + LEAmDNS2Host.h + (c) 2020, LaborEtArs + + Version 0.9 beta + + Some notes (from LaborEtArs, 2020): + + Supported mDNS features (in some cases somewhat limited): + - Announcing a DNS-SD service to interested observers, eg. a http server by announcing a esp8266._http._tcp.local. service + - Support for multi-level compressed names in input; in output only a very simple one-level full-name compression is implemented + - Probing host and service domains for uniqueness in the local network + - Tiebreaking while probing is supported in a very minimalistic way (the 'higher' IP address wins the tiebreak) + - Announcing available services after successful probing + - Using fixed service TXT items or + - Using dynamic service TXT items for presented services (via callback) + - Remove services (and un-announcing them to the observers by sending goodbye-messages) + - Static queries for hosts or DNS-SD services (creating a fixed answer set after a certain timeout period) + - Dynamic queries for hosts or DNS-SD services with cached and updated answers and user notifications + + + Usage: + A LEAmDNS2Host is attached to an existing netif (Network Interface). + If more than one netif is used (eg. in WIFI_AP_STA mode) and mDNS support is needed, every used netif needs its own LEAmDNS2Host! + + For presenting services: + In 'setup()': + Create an clsLEAMDNSHost instance for every netif you plan to use. + Call 'begin' on every instance with the intended hostname and the associated netif (or WiFi mode, WIFI_STA). + The given hostname is the 'probed' for uniqueness in the netifs local link. If domain name conflicts occur, the host name + will be automatically changed until it is unique in the local link. + Optionally a callback can be registered in 'begin', to control the probing process manually. + Next you can register DNS-SD services with 'addService("MyESP", "http", "tcp", 5000)' + All added service domain names are also probed for uniqueness and updated if needed. + Optionally a 'probe result' callback can be given for every service in 'addService', too. + + Finally you can add service TXT items with 'pService->addServiceTxt("c#", "1")' or by installing a service TXT callback + using 'pService->setDynamicServiceTxtCallback()' and calling 'pService->addDynamicServiceTxt("c#", "1")' inside. + + In 'loop()': + Call 'update()' for every clsLEAmDNS_Host instance. + + For querying services/hosts: + Static: + Call 'queryService("http", "tcp")' or 'queryHost("esp8266")'; + You should call MDNS.removeQuery() sometimes later (when the answers are not needed anymore) + + Dynamic: + Install a dynamic service query by calling 'installService/HostQuery("http", "tcp", serviceQueryCallback);' + The callback is called for any change in the answer set. + Call 'MDNS.removeQuery(pQuery)' when the answers are not needed anymore + + + Reference: + Used mDNS messages: + A (0x01): eg. esp8266.local A OP TTL 123.456.789.012 + AAAA (0x1C): eg. esp8266.local AAAA OP TTL 1234:5678::90 + PTR (0x0C, srv name): eg. _http._tcp.local PTR OP TTL MyESP._http._tcp.local + PTR (0x0C, srv type): eg. _services._dns-sd._udp.local PTR OP TTL _http._tcp.local + PTR (0x0C, IPv4): eg. 012.789.456.123.in-addr.arpa PTR OP TTL esp8266.local + PTR (0x0C, IPv6): eg. 90.0.0.0.0.0.0.0.0.0.0.0.78.56.34.12.ip6.arpa PTR OP TTL esp8266.local + SRV (0x21): eg. MyESP._http._tcp.local SRV OP TTL PRIORITY WEIGHT PORT esp8266.local + TXT (0x10): eg. MyESP._http._tcp.local TXT OP TTL c#=1 + NSEC (0x2F): eg. esp8266.local ... (DNSSEC) + + Some NOT used message types: + OPT (0x29): eDNS + + + 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. + +*/ + +#ifndef __LEAMDNS2HOST_H__ +#define __LEAMDNS2HOST_H__ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "LEAmDNS2_lwIPdefs.h" + + +#define MDNS_IPV4_SUPPORT +#if LWIP_IPV6 +#define MDNS_IPV6_SUPPORT // If we've got IPv6 support, then we need IPv6 support :-) +#endif + +namespace esp8266 +{ + + +namespace experimental +{ + + +/** + clsLEAMDNSHost +*/ +class clsLEAMDNSHost +{ +protected: + /* + clsConsts + */ + class clsConsts + { + public: +#ifdef MDNS_IPV4_SUPPORT + static constexpr uint16_t u16IPv4Size = 4; // IPv4 address size in bytes +#endif +#ifdef MDNS_IPV6_SUPPORT + static constexpr uint16_t u16IPv6Size = 16; // IPv6 address size in bytes +#endif + static constexpr size_t stServiceTxtMaxLength = 1300; // Maximum length for all service txts for one service + static constexpr size_t stDomainMaxLength = 256; // Maximum length for a full domain name eg. MyESP._http._tcp.local + static constexpr size_t stDomainLabelMaxLength = 63; // Maximum length of on label in a domain name (length info fits into 6 bits) + static constexpr size_t stServiceTypeMaxLength = 15; // Maximum length of a service name eg. http + static constexpr size_t stServiceProtocolMaxLength = 3; // Maximum length of a service protocol name eg. tcp + + static constexpr uint32_t u32LegacyTTL = 10; // Legacy DNS record TTL + static constexpr uint32_t u32HostTTL = 120; // Host level records are set to 2min (120s) + static constexpr uint32_t u32ServiceTTL = 4500; // Service level records are set to 75min (4500s) + + static constexpr uint16_t u16SRVPriority = 0; // Default service priority and weight in SRV answers + static constexpr uint16_t u16SRVWeight = 0; // + static constexpr uint8_t u8DomainCompressMark = 0xC0; // Compressed labels are flaged by the two topmost bits of the length byte being set + static constexpr uint8_t u8DomainMaxRedirections = 6; // Avoid endless recursion because of malformed compressed labels + + static constexpr uint32_t u32ProbeDelay = 1000; // Default 250, but ESP is slow...; delay between and number of probes for host and service domains + static constexpr uint32_t u32ProbeCount = 3; + static constexpr uint32_t u32AnnounceDelay = 1000; // Delay between and number of announces for host and service domains + static constexpr uint32_t u32AnnounceCount = 3; + static constexpr uint32_t u32DynamicQueryResendDelay = 1000; // Delay between and number of queries; the delay is multiplied by the resent number in '_checkQueryCache' + + static const char* pcLocal; // "local"; + static const char* pcServices; // "services"; + static const char* pcDNSSD; // "dns-sd"; + static const char* pcUDP; // "udp"; + +#ifdef MDNS_IPV4_SUPPORT + static const char* pcReverseIPv4Domain; // "in-addr"; +#endif +#ifdef MDNS_IPV6_SUPPORT + static const char* pcReverseIPv6Domain; // "ip6"; +#endif + static const char* pcReverseTopDomain; // "arpa"; + +#ifdef DNS_RRTYPE_NSEC + static constexpr uint8_t u8DNS_RRTYPE_NSEC = DNS_RRTYPE_NSEC; +#else + static constexpr uint8_t u8DNS_RRTYPE_NSEC = 0x2F; +#endif + static constexpr uint32_t u32SendTimeoutMs = 50; // timeout (ms) for a call to `UDPContext->send()` (12ms=1460B@1Mb/s) + + }; + + /** + list + */ + //using list = std::list; + + // File: ..._Backbone + /** + clsBackbone + XXXX should be merged with holder clsLEAMDNSHost because there is no list anymore in it + */ + class clsBackbone + { + public: + static clsBackbone* sm_pBackbone; + clsBackbone(void); + ~clsBackbone(void); + + bool init(void); + + UdpContext* addHost(clsLEAMDNSHost* p_pHost); + bool removeHost(clsLEAMDNSHost* p_pHost); + size_t hostCount(void) const; + bool setDelayUDPProcessing(bool p_bDelayProcessing); + + clsLEAMDNSHost* getUniqueHost() const + { + return m_uniqueHost; + } + + protected: + UdpContext* m_pUDPContext; + bool m_bDelayUDPProcessing; + uint32_t m_u32DelayedDatagrams; + //list m_HostList; + clsLEAMDNSHost* m_uniqueHost; + + bool _allocUDPContext(void); + bool _releaseUDPContext(void); + + bool _processUDPInput(void); + + const clsLEAMDNSHost* _findHost() const + { + return m_uniqueHost; + } + clsLEAMDNSHost* _findHost() + { + return m_uniqueHost; + } + + const char* _DH(void) const; + }; + + + // File: ..._Host_Structs + /** + typeIPProtocolType & enuIPProtocolType + */ + using typeIPProtocolType = uint8_t; + enum class enuIPProtocolType : typeIPProtocolType + { +#ifdef MDNS_IPV4_SUPPORT + V4 = 0x01, +#endif +#ifdef MDNS_IPV6_SUPPORT + V6 = 0x02, +#endif + }; + + /** + typeNetIfState & enuNetIfState + */ + using typeNetIfState = uint8_t; + enum class enuNetIfState : typeNetIfState + { + None = 0x00, + + IsUp = 0x01, + UpMask = (IsUp), + + LinkIsUp = 0x02, + LinkMask = (LinkIsUp), + + IPv4 = 0x04, + IPv6 = 0x08, + IPMask = (IPv4 | IPv6), + }; + +public: + + /** + clsServiceTxt + */ + class clsServiceTxt + { + public: + char* m_pcKey; + char* m_pcValue; + bool m_bTemp; + + clsServiceTxt(const char* p_pcKey = 0, + const char* p_pcValue = 0, + bool p_bTemp = false); + clsServiceTxt(const clsServiceTxt& p_Other); + ~clsServiceTxt(void); + + clsServiceTxt& operator=(const clsServiceTxt& p_Other); + bool clear(void); + + char* allocKey(size_t p_stLength); + bool setKey(const char* p_pcKey, + size_t p_stLength); + bool setKey(const char* p_pcKey); + bool releaseKey(void); + + char* allocValue(size_t p_stLength); + bool setValue(const char* p_pcValue, + size_t p_stLength); + bool setValue(const char* p_pcValue); + bool releaseValue(void); + + bool set(const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp = false); + + bool update(const char* p_pcValue); + + size_t length(void) const; + + /** + list + */ + using list = std::list; + }; + + /** + clsServiceTxts + */ + class clsServiceTxts + { + public: + clsServiceTxt::list m_Txts; + char* m_pcCache; + + clsServiceTxts(void); + clsServiceTxts(const clsServiceTxts& p_Other); + ~clsServiceTxts(void); + + clsServiceTxts& operator=(const clsServiceTxts& p_Other); + + bool clear(void); + bool clearCache(void); + + bool add(clsServiceTxt* p_pTxt); + bool remove(clsServiceTxt* p_pTxt); + + size_t count(void) const; + + bool removeTempTxts(void); + + clsServiceTxt* find(const char* p_pcKey); + const clsServiceTxt* find(const char* p_pcKey) const; + clsServiceTxt* find(const clsServiceTxt* p_pTxt); + + size_t length(void) const; + + size_t c_strLength(void) const; + bool c_str(char* p_pcBuffer); + const char* c_str(void) const; + + size_t bufferLength(void) const; + bool buffer(char* p_pcBuffer); + + bool compare(const clsServiceTxts& p_Other) const; + bool operator==(const clsServiceTxts& p_Other) const; + bool operator!=(const clsServiceTxts& p_Other) const; + }; + +protected: + /** + clsProbeInformation_Base + */ + class clsProbeInformation_Base + { + public: + /** + typeProbingStatus & enuProbingStatus + */ + using typeProbingStatus = uint8_t; + enum class enuProbingStatus : typeProbingStatus + { + WaitingForData, + ReadyToStart, + InProgress, + ReadyToAnnounce, + DoneFinally + }; + + enuProbingStatus m_ProbingStatus; + uint32_t m_u32SentCount; // Used for probes and announcements + esp8266::polledTimeout::oneShot m_Timeout; // Used for probes and announcements + bool m_bConflict; + bool m_bTiebreakNeeded; + + clsProbeInformation_Base(void); + + bool clear(void); // No 'virtual' needed, no polymorphic use (save 4 bytes) + }; + +public: + /** + fnProbeResultCallback + Callback function for host domain probe results + */ + using fnProbeResultCallback = std::function; + + static fnProbeResultCallback stProbeResultCallback; + +protected: + /** + clsProbeInformation + */ + class clsProbeInformation : public clsProbeInformation_Base + { + public: + fnProbeResultCallback m_fnProbeResultCallback; + + clsProbeInformation(void); + + bool clear(bool p_bClearUserdata = false); + }; + +public: + /** + clsService + */ + struct clsService + { + public: + /** + fnDynamicServiceTxtCallback + */ + using fnDynamicServiceTxtCallback = std::function; + + /** + fnProbeResultCallback + */ + using fnProbeResultCallback = std::function; + + protected: + friend clsLEAMDNSHost; + /** + clsProbeInformation + */ + class clsProbeInformation : public clsProbeInformation_Base + { + public: + fnProbeResultCallback m_fnProbeResultCallback; + + clsProbeInformation(void); + + bool clear(bool p_bClearUserdata = false); + }; + + char* m_pcInstanceName; + bool m_bAutoName; // Name was set automatically to hostname (if no name was supplied) + char* m_pcType; + char* m_pcProtocol; + uint16_t m_u16Port; + uint32_t m_u32ReplyMask; + clsServiceTxts m_Txts; + fnDynamicServiceTxtCallback m_fnTxtCallback; + clsProbeInformation m_ProbeInformation; + + clsService(void); + ~clsService(void); + + bool _releaseInstanceName(void); + bool _releaseType(void); + bool _releaseProtocol(void); + + void _resetProbeStatus(void); + + clsServiceTxt* _addServiceTxt(const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp); + clsServiceTxt* _addServiceTxt(const char* p_pcKey, + uint32_t p_u32Value, + bool p_bTemp); + clsServiceTxt* _addServiceTxt(const char* p_pcKey, + int32_t p_i32Value, + bool p_bTemp); + + public: + bool setInstanceName(const char* p_pcInstanceName); + bool indexInstanceName(void); + const char* instanceName(void) const; + + bool setType(const char* p_pcType); + const char* type(void) const; + + bool setProtocol(const char* p_pcProtocol); + const char* protocol(void) const; + + bool setPort(uint16_t p_i16Port); + uint16_t port(void) const; + + bool setProbeResultCallback(fnProbeResultCallback p_fnProbeResultCallback); + bool probeStatus(void) const; + + // TXT + // Add a (static) MDNS TXT item ('key' = 'value') to the service + clsServiceTxt* addServiceTxt(const char* p_pcKey, + const char* p_pcValue); + clsServiceTxt* addServiceTxt(const char* p_pcKey, + uint32_t p_u32Value); + clsServiceTxt* addServiceTxt(const char* p_pcKey, + uint16_t p_u16Value); + clsServiceTxt* addServiceTxt(const char* p_pcKey, + uint8_t p_u8Value); + clsServiceTxt* addServiceTxt(const char* p_pcKey, + int32_t p_i32Value); + clsServiceTxt* addServiceTxt(const char* p_pcKey, + int16_t p_i16Value); + clsServiceTxt* addServiceTxt(const char* p_pcKey, + int8_t p_i8Value); + + bool removeServiceTxt(const char* p_pcKey); + bool removeServiceTxt(clsServiceTxt* p_pTxt); + const clsServiceTxt* findServiceTxt(const char* p_pcKey) const; + clsServiceTxt* findServiceTxt(const char* p_pcKey); + + // Add a (dynamic) MDNS TXT item ('key' = 'value') to the service + // Dynamic TXT items are removed right after one-time use. So they need to be added + // every time the value s needed (via callback). + clsServiceTxt* addDynamicServiceTxt(const char* p_pcKey, + const char* p_pcValue); + clsServiceTxt* addDynamicServiceTxt(const char* p_pcKey, + uint32_t p_u32Value); + clsServiceTxt* addDynamicServiceTxt(const char* p_pcKey, + uint16_t p_u16Value); + clsServiceTxt* addDynamicServiceTxt(const char* p_pcKey, + uint8_t p_u8Value); + clsServiceTxt* addDynamicServiceTxt(const char* p_pcKey, + int32_t p_i32Value); + clsServiceTxt* addDynamicServiceTxt(const char* p_pcKey, + int16_t p_i16Value); + clsServiceTxt* addDynamicServiceTxt(const char* p_pcKey, + int8_t p_i8Value); + + bool setDynamicServiceTxtCallback(fnDynamicServiceTxtCallback p_fnCallback); + + /** + list + */ + using list = std::list; + }; + using hMDNSService = clsService; // backward compatibility with LEAmDNS + +protected: + /** + typeContentFlag & enuContentFlag + */ + using typeContentFlag = uint16_t; + enum class enuContentFlag : typeContentFlag + { + // Host + A = 0x0001, + PTR_IPv4 = 0x0002, + PTR_IPv6 = 0x0004, + AAAA = 0x0008, + // Service + PTR_TYPE = 0x0010, + PTR_NAME = 0x0020, + TXT = 0x0040, + SRV = 0x0080, + // DNSSEC + NSEC = 0x0100, + + PTR = (PTR_IPv4 | PTR_IPv6 | PTR_TYPE | PTR_NAME) + }; + + /** + clsMsgHeader + */ + class clsMsgHeader + { + public: + uint16_t m_u16ID; // Identifier + bool m_1bQR : 1; // Query/Response flag + uint8_t m_4bOpcode : 4; // Operation code + bool m_1bAA : 1; // Authoritative Answer flag + bool m_1bTC : 1; // Truncation flag + bool m_1bRD : 1; // Recursion desired + bool m_1bRA : 1; // Recursion available + uint8_t m_3bZ : 3; // Zero + uint8_t m_4bRCode : 4; // Response code + uint16_t m_u16QDCount; // Question count + uint16_t m_u16ANCount; // Answer count + uint16_t m_u16NSCount; // Authority Record count + uint16_t m_u16ARCount; // Additional Record count + + clsMsgHeader(uint16_t p_u16ID = 0, + bool p_bQR = false, + uint8_t p_u8Opcode = 0, + bool p_bAA = false, + bool p_bTC = false, + bool p_bRD = false, + bool p_bRA = false, + uint8_t p_u8RCode = 0, + uint16_t p_u16QDCount = 0, + uint16_t p_u16ANCount = 0, + uint16_t p_u16NSCount = 0, + uint16_t p_u16ARCount = 0); + }; + + /** + clsRRDomain + */ + class clsRRDomain + { + public: + char m_acName[clsConsts::stDomainMaxLength]; // Encoded domain name + uint16_t m_u16NameLength; // Length (incl. '\0') + char* m_pcDecodedName; + + clsRRDomain(void); + clsRRDomain(const clsRRDomain& p_Other); + ~clsRRDomain(void); + + clsRRDomain& operator=(const clsRRDomain& p_Other); + + bool clear(void); + bool clearNameCache(void); + + bool addLabel(const char* p_pcLabel, + bool p_bPrependUnderline = false); + + bool compare(const clsRRDomain& p_Other) const; + bool operator==(const clsRRDomain& p_Other) const; + bool operator!=(const clsRRDomain& p_Other) const; + bool operator>(const clsRRDomain& p_Other) const; + + size_t c_strLength(void) const; + bool c_str(char* p_pcBuffer) const; + const char* c_str(void) const; + }; + + /** + clsRRAttributes + */ + class clsRRAttributes + { + public: + uint16_t m_u16Type; // Type + uint16_t m_u16Class; // Class, nearly always 'IN' + + clsRRAttributes(uint16_t p_u16Type = 0, + uint16_t p_u16Class = 1 /*DNS_RRCLASS_IN Internet*/); + clsRRAttributes(const clsRRAttributes& p_Other); + + clsRRAttributes& operator=(const clsRRAttributes& p_Other); + }; + + /** + clsRRHeader + */ + class clsRRHeader + { + public: + clsRRDomain m_Domain; + clsRRAttributes m_Attributes; + + clsRRHeader(void); + clsRRHeader(const clsRRHeader& p_Other); + + clsRRHeader& operator=(const clsRRHeader& p_Other); + + bool clear(void); + }; + + /** + clsRRQuestion + */ + struct clsRRQuestion + { + clsRRHeader m_Header; + bool m_bUnicast; // Unicast reply requested + + /** + list + */ + using list = std::list; + + clsRRQuestion(void); + }; + + /** + clsNSECBitmap + */ + class clsNSECBitmap + { + public: + uint8_t m_au8BitmapData[6]; // 6 bytes data + + clsNSECBitmap(void); + + bool clear(void); + uint16_t length(void) const; + bool setBit(uint16_t p_u16Bit); + bool getBit(uint16_t p_u16Bit) const; + }; + + /** + typeAnswerType & enuAnswerType + */ + using typeAnswerType = uint8_t; + enum class enuAnswerType : typeAnswerType + { + A, + PTR, + TXT, + AAAA, + SRV, + //NSEC, not used - https://tools.ietf.org/html/rfc6762#section-6.1 + Generic + }; + + /** + clsRRAnswer + */ + struct clsRRAnswer + { + clsRRAnswer* m_pNext; + const enuAnswerType m_AnswerType; + clsRRHeader m_Header; + bool m_bCacheFlush; // Cache flush command bit + uint32_t m_u32TTL; // Validity time in seconds + + virtual ~clsRRAnswer(void); + + enuAnswerType answerType(void) const; + + bool clear(void); + + protected: + clsRRAnswer(enuAnswerType p_AnswerType, + const clsRRHeader& p_Header, + uint32_t p_u32TTL); + }; + +#ifdef MDNS_IPV4_SUPPORT + /** + clsRRAnswerA + */ + class clsRRAnswerA : public clsRRAnswer + { + public: + IPAddress m_IPAddress; + + clsRRAnswerA(const clsRRHeader& p_Header, + uint32_t p_u32TTL); + ~clsRRAnswerA(void); + + bool clear(void); + }; +#endif + + /** + clsRRAnswerPTR + */ + class clsRRAnswerPTR : public clsRRAnswer + { + public: + clsRRDomain m_PTRDomain; + + clsRRAnswerPTR(const clsRRHeader& p_Header, + uint32_t p_u32TTL); + ~clsRRAnswerPTR(void); + + bool clear(void); + }; + + /** + clsRRAnswerTXT + */ + class clsRRAnswerTXT : public clsRRAnswer + { + public: + clsServiceTxts m_Txts; + + clsRRAnswerTXT(const clsRRHeader& p_Header, + uint32_t p_u32TTL); + ~clsRRAnswerTXT(void); + + bool clear(void); + }; + +#ifdef MDNS_IPV6_SUPPORT + /** + clsRRAnswerAAAA + */ + class clsRRAnswerAAAA : public clsRRAnswer + { + public: + IPAddress m_IPAddress; + + clsRRAnswerAAAA(const clsRRHeader& p_Header, + uint32_t p_u32TTL); + ~clsRRAnswerAAAA(void); + + bool clear(void); + }; +#endif + + /** + clsRRAnswerSRV + */ + class clsRRAnswerSRV : public clsRRAnswer + { + public: + uint16_t m_u16Priority; + uint16_t m_u16Weight; + uint16_t m_u16Port; + clsRRDomain m_SRVDomain; + + clsRRAnswerSRV(const clsRRHeader& p_Header, + uint32_t p_u32TTL); + ~clsRRAnswerSRV(void); + + bool clear(void); + }; + + /** + clsRRAnswerGeneric + */ + class clsRRAnswerGeneric : public clsRRAnswer + { + public: + uint16_t m_u16RDLength; // Length of variable answer + uint8_t* m_pu8RDData; // Offset of start of variable answer in packet + + clsRRAnswerGeneric(const clsRRHeader& p_Header, + uint32_t p_u32TTL); + ~clsRRAnswerGeneric(void); + + bool clear(void); + }; + + + /** + clsSendParameter + */ + class clsSendParameter + { + protected: + /** + clsDomainCacheItem + */ + class clsDomainCacheItem + { + public: + const void* m_pHostNameOrService; // Opaque id for host or service domain (pointer) + bool m_bAdditionalData; // Opaque flag for special info (service domain included) + uint16_t m_u16Offset; // Offset in UDP output buffer + + /** + list + */ + using list = std::list; + + clsDomainCacheItem(const void* p_pHostNameOrService, + bool p_bAdditionalData, + uint32_t p_u16Offset); + }; + + public: + /** + typeResponseType & enuResponseType + */ + using typeResponseType = uint8_t; + enum class enuResponseType : typeResponseType + { + None, + Response, + Unsolicited + }; + + uint16_t m_u16ID; // Query ID (used only in lagacy queries) + clsRRQuestion::list m_RRQuestions; // A list of queries + uint32_t m_u32HostReplyMask; // Flags for reply components/answers + bool m_bLegacyDNSQuery; // Flag: Legacy query + enuResponseType m_Response; // Enum: Response to a query + bool m_bAuthorative; // Flag: Authorative (owner) response + bool m_bCacheFlush; // Flag: Clients should flush their caches + bool m_bUnicast; // Flag: Unicast response + bool m_bUnannounce; // Flag: Unannounce service + + // Temp content; created while processing _prepareMessage + uint16_t m_u16Offset; // Current offset in UDP write buffer (mainly for domain cache) + clsDomainCacheItem::list m_DomainCacheItems; // Cached host and service domains + + clsSendParameter(void); + ~clsSendParameter(void); + + bool clear(void); + bool flushQuestions(void); + bool flushDomainCache(void); + bool flushTempContent(void); + + bool shiftOffset(uint16_t p_u16Shift); + + bool addDomainCacheItem(const void* p_pHostNameOrService, + bool p_bAdditionalData, + uint16_t p_u16Offset); + uint16_t findCachedDomainOffset(const void* p_pHostNameOrService, + bool p_bAdditionalData) const; + }; + +public: + // QUERIES & ANSWERS + + /** + clsQuery + */ + class clsQuery + { + public: + /** + clsAnswer + */ + class clsAnswer + { + public: + /** + typeQueryAnswerType & enuQueryAnswerType + */ + using typeQueryAnswerType = uint8_t; + enum class enuQueryAnswerType : typeQueryAnswerType + { + Unknown = 0x00, + ServiceDomain = 0x01, // Service domain + HostDomain = 0x02, // Host domain + Port = 0x04, // Port + HostDomainPort = 0x06, + Txts = 0x08, // TXT items +#ifdef MDNS_IPV4_SUPPORT + IPv4Address = 0x10, // IPv4 address +#endif +#ifdef MDNS_IPV6_SUPPORT + IPv6Address = 0x20, // IPv6 address +#endif + }; + + /** + stcTTL + */ + class clsTTL + { + public: + /** + typeTimeoutLevel & enuTimeoutLevel + */ + using typeTimeoutLevel = uint8_t; + enum class enuTimeoutLevel : typeTimeoutLevel + { + None = 0, + Base = 80, + Interval = 5, + Final = 100 + }; + + uint32_t m_u32TTL; + esp8266::polledTimeout::oneShot m_TTLTimeout; + typeTimeoutLevel m_TimeoutLevel; + + clsTTL(void); + bool set(uint32_t p_u32TTL); + + bool flagged(void) const; + bool restart(void); + + bool prepareDeletion(void); + bool finalTimeoutLevel(void) const; + + unsigned long timeout(void) const; + }; + + /** + clsIPAddressWithTTL + */ + class clsIPAddressWithTTL + { + public: + IPAddress m_IPAddress; + clsTTL m_TTL; + + /** + list + */ + using list = std::list; + + clsIPAddressWithTTL(IPAddress p_IPAddress, + uint32_t p_u32TTL = 0); + }; + + // The service domain is the first 'answer' (from PTR answer, using service and protocol) to be set + // Defines the key for additional answer, like host domain, etc. + clsRRDomain m_ServiceDomain; // 1. level answer (PTR), eg. MyESP._http._tcp.local + clsTTL m_TTLServiceDomain; + clsRRDomain m_HostDomain; // 2. level answer (SRV, using service domain), eg. esp8266.local + uint16_t m_u16Port; // 2. level answer (SRV, using service domain), eg. 5000 + clsTTL m_TTLHostDomainAndPort; + clsServiceTxts m_Txts; // 2. level answer (TXT, using service domain), eg. c#=1 + clsTTL m_TTLTxts; +#ifdef MDNS_IPV4_SUPPORT + clsIPAddressWithTTL::list m_IPv4Addresses; // 3. level answer (A, using host domain), eg. 123.456.789.012 +#endif +#ifdef MDNS_IPV6_SUPPORT + clsIPAddressWithTTL::list m_IPv6Addresses; // 3. level answer (AAAA, using host domain), eg. 1234::09 +#endif + typeQueryAnswerType m_QueryAnswerFlags; // enuQueryAnswerType + + /** + list + */ + using list = std::list; + + clsAnswer(void); + ~clsAnswer(void); + + bool clear(void); + +#ifdef MDNS_IPV4_SUPPORT + bool releaseIPv4Addresses(void); + bool addIPv4Address(clsIPAddressWithTTL* p_pIPAddress); + bool removeIPv4Address(clsIPAddressWithTTL* p_pIPAddress); + const clsIPAddressWithTTL* findIPv4Address(const IPAddress& p_IPAddress) const; + clsIPAddressWithTTL* findIPv4Address(const IPAddress& p_IPAddress); + uint32_t IPv4AddressCount(void) const; + const clsIPAddressWithTTL* IPv4AddressAtIndex(uint32_t p_u32Index) const; + clsIPAddressWithTTL* IPv4AddressAtIndex(uint32_t p_u32Index); +#endif +#ifdef MDNS_IPV6_SUPPORT + bool releaseIPv6Addresses(void); + bool addIPv6Address(clsIPAddressWithTTL* p_pIPAddress); + bool removeIPv6Address(clsIPAddressWithTTL* p_pIPAddress); + const clsIPAddressWithTTL* findIPv6Address(const IPAddress& p_IPAddress) const; + clsIPAddressWithTTL* findIPv6Address(const IPAddress& p_IPAddress); + uint32_t IPv6AddressCount(void) const; + const clsIPAddressWithTTL* IPv6AddressAtIndex(uint32_t p_u32Index) const; + clsIPAddressWithTTL* IPv6AddressAtIndex(uint32_t p_u32Index); +#endif + }; // clsAnswer + + /** + clsAnswerAccessor + */ + class clsAnswerAccessor + { + protected: + /** + stcCompareTxtKey + */ + struct stcCompareTxtKey + { + bool operator()(char const* p_pA, char const* p_pB) const; + }; + public: + clsAnswerAccessor(const clsAnswer* p_pAnswer); + ~clsAnswerAccessor(void); + + /** + clsTxtKeyValueMap + */ + using clsTxtKeyValueMap = std::map; + /** + clsIPAddressVector + */ + using clsIPAddressVector = std::vector; + /** + vector + */ + using vector = std::vector; + + bool serviceDomainAvailable(void) const; + const char* serviceDomain(void) const; + bool hostDomainAvailable(void) const; + const char* hostDomain(void) const; + bool hostPortAvailable(void) const; + uint16_t hostPort(void) const; +#ifdef MDNS_IPV4_SUPPORT + bool IPv4AddressAvailable(void) const; + clsIPAddressVector IPv4Addresses(void) const; +#endif +#ifdef MDNS_IPV6_SUPPORT + bool IPv6AddressAvailable(void) const; + clsIPAddressVector IPv6Addresses(void) const; +#endif + bool txtsAvailable(void) const; + const char* txts(void) const; + const clsTxtKeyValueMap& txtKeyValues(void) const; + const char* txtValue(const char* p_pcKey) const; + + size_t printTo(Print& p_Print) const; + + protected: + const clsAnswer* m_pAnswer; + clsTxtKeyValueMap m_TxtKeyValueMap; + }; + + /** + typeQueryType & enuQueryType + */ + using typeQueryType = uint8_t; + enum class enuQueryType : typeQueryType + { + None, + Service, + Host + }; + + /** + QueryCallbackAnswerFn + */ + using QueryCallbackAnswerFn = std::function; // true: Answer component set, false: component deleted + /** + QueryCallbackAccessorFn + */ + using QueryCallbackAccessorFn = std::function; // true: Answer component set, false: component deleted + + protected: + friend clsLEAMDNSHost; + + enuQueryType m_QueryType; + clsRRDomain m_Domain; // Type:Service -> _http._tcp.local; Type:Host -> esp8266.local + QueryCallbackAnswerFn m_fnCallbackAnswer; + QueryCallbackAccessorFn m_fnCallbackAccessor; + bool m_bStaticQuery; + uint32_t m_u32SentCount; + esp8266::polledTimeout::oneShot m_ResendTimeout; + bool m_bAwaitingAnswers; + clsAnswer::list m_Answers; + + /** + list + */ + using list = std::list; + + clsQuery(const enuQueryType p_QueryType); + ~clsQuery(void); + + bool clear(void); + + bool addAnswer(clsAnswer* p_pAnswer); + bool removeAnswer(clsAnswer* p_pAnswer); + + clsAnswer* findAnswerForServiceDomain(const clsRRDomain& p_ServiceDomain); + clsAnswer* findAnswerForHostDomain(const clsRRDomain& p_HostDomain); + + public: + uint32_t answerCount(void) const; + const clsAnswer* answer(uint32_t p_u32Index) const; + uint32_t indexOfAnswer(const clsAnswer* p_pAnswer) const; + + clsAnswerAccessor::vector answerAccessors(void) const; + clsAnswerAccessor answerAccessor(uint32 p_u32AnswerIndex) const; + }; + +public: + static const char* indexDomainName(const char* p_pcDomainName, + const char* p_pcDivider = "-", + const char* p_pcDefaultDomainName = 0); + static bool setNetIfHostName(const char* p_pcHostName); + + clsLEAMDNSHost(void); + ~clsLEAMDNSHost(void); + + // INIT + // Create a MDNS host by setting the default hostname + // Later call 'update()' in every 'loop' to run the process loop + // (probing, announcing, responding, ...) + // If no callback is given, the (maybe) already installed callback stays set + bool begin(const char* p_pcHostName, + fnProbeResultCallback p_fnCallback = 0); + + bool close(void); + + // HOST + bool setHostName(const char* p_pcHostName); + bool indexHostName(void); + const char* hostName(void) const; + + bool setProbeResultCallback(fnProbeResultCallback p_fnCallback); + + // Returns 'true' is host domain probing is done + bool probeStatus(void) const; + + // SERVICE + bool setDefaultInstanceName(const char* p_pcInstanceName); + const char* defaultInstanceName(void) const; + + clsService* addService(const char* p_pcInstanceName, + const char* p_pcServiceType, + const char* p_pcProtocol, + uint16_t p_u16Port, + clsService::fnProbeResultCallback p_fnCallback = 0); + bool removeService(clsService* p_pMDNSService); + + const clsService* findService(const char* p_pcInstanceName, + const char* p_pcType, + const char* p_pcProtocol, + uint16_t p_u16Port = (uint16_t)(-1)) const; + clsService* findService(const char* p_pcInstanceName, + const char* p_pcType, + const char* p_pcProtocol, + uint16_t p_u16Port = (uint16_t)(-1)); + const clsService::list& services(void) const; + + // QUERIES + + // - STATIC + // Perform a (static) service/host query. The function returns after p_u16Timeout milliseconds + // The answers (the number of received answers is returned) can be retrieved by calling + // - answerHostName (or hostname) + // - answerIP (or IP) + // - answerPort (or port) + clsQuery::clsAnswerAccessor::vector queryService(const char* p_pcService, + const char* p_pcProtocol, + const uint16_t p_u16Timeout); + clsQuery::clsAnswerAccessor::vector queryHost(const char* p_pcHostName, + const uint16_t p_u16Timeout); + bool removeQuery(void); + bool hasQuery(void); + clsQuery* getQuery(void) const; + + // - DYNAMIC + // Install a dynamic service/host query. For every received answer (part) the given callback + // function is called. The query will be updated every time, the TTL for an answer + // has timed-out. + // The answers can also be retrieved by calling + // - answerCount service/host (for host queries, this should never be >1) + // - answerServiceDomain service + // - hasAnswerHostDomain/answerHostDomain service/host + // - hasAnswerIPv4Address/answerIPv4Address service/host + // - hasAnswerIPv6Address/answerIPv6Address service/host + // - hasAnswerPort/answerPort service + // - hasAnswerTxts/answerTxts service + + /* + install*Query() creates several queries on the interfaces. + it does not return a single query but a boolean until the API is adapted + */ + clsQuery* installServiceQuery(const char* p_pcServiceType, + const char* p_pcProtocol, + clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer); + clsQuery* installServiceQuery(const char* p_pcServiceType, + const char* p_pcProtocol, + clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor); + clsQuery* installHostQuery(const char* p_pcHostName, + clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer); + clsQuery* installHostQuery(const char* p_pcHostName, + clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor); + // Remove a dynamic service query + bool removeQuery(clsQuery* p_pQuery); + + // PROCESSING + bool update(void); + + bool announce(bool p_bAnnounce = true, + bool p_bIncludeServices = true); + bool announceService(clsService* p_pService, + bool p_bAnnounce = true); + + bool restart(void); + + clsService* enableArduino(uint16_t p_u16Port, bool p_bAuthUpload = false); + +protected: + // File: ..._Host + UdpContext* _allocBackbone(void); + bool _releaseBackbone(void); + + bool _joinMulticastGroups(void); + bool _leaveMulticastGroups(void); + + // NETIF + typeNetIfState _getNetIfState(void) const; + bool _checkNetIfState(void); + + // PROCESSING + bool _processUDPInput(void); + + // DOMAIN NAMES + bool _allocDomainName(const char* p_pcNewDomainName, + char*& p_rpcDomainName); + bool _releaseDomainName(char*& p_rpcDomainName); + bool _allocHostName(const char* p_pcHostName); + bool _releaseHostName(void); + + bool _allocDefaultInstanceName(const char* p_pcInstanceName); + bool _releaseDefaultInstanceName(void); + const char* _instanceName(const char* p_pcInstanceName) const; + + // SERVICE + clsService* _allocService(const char* p_pcName, + const char* p_pcServiceType, + const char* p_pcProtocol, + uint16_t p_u16Port); + bool _releaseService(clsService* p_pService); + + // SERVICE TXT + clsServiceTxt* _allocServiceTxt(clsService* p_pService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp); + bool _releaseServiceTxt(clsService* p_pService, + clsServiceTxt* p_pTxt); + clsServiceTxt* _updateServiceTxt(clsService* p_pService, + clsServiceTxt* p_pTxt, + const char* p_pcValue, + bool p_bTemp); + clsServiceTxt* _findServiceTxt(clsService* p_pService, + const char* p_pcKey); + clsServiceTxt* _addServiceTxt(clsService* p_pService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp); + clsServiceTxt* _answerKeyValue(const clsQuery p_pQuery, + const uint32_t p_u32AnswerIndex); + bool _collectServiceTxts(clsService& p_rService); + bool _releaseTempServiceTxts(clsService& p_rService); + + + // QUERIES + clsQuery* _allocQuery(clsQuery::enuQueryType p_QueryType); + bool _removeQuery(clsQuery* p_pQuery); + bool _removeLegacyQuery(void); + clsQuery* _findLegacyQuery(void) const; + bool _releaseQueries(void); + clsQuery* _findNextQueryByDomain(const clsRRDomain& p_Domain, + const clsQuery::enuQueryType p_QueryType, + const clsQuery* p_pPrevQuery); + clsQuery* _installServiceQuery(const char* p_pcService, + const char* p_pcProtocol); + clsQuery* _installDomainQuery(clsRRDomain& p_Domain, + clsQuery::enuQueryType p_QueryType); + bool _hasQueriesWaitingForAnswers(void) const; + bool _executeQueryCallback(const clsQuery& p_Query, + const clsQuery::clsAnswer& p_Answer, + clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, + bool p_SetContent); + + + // File: ..._Host_Control + // RECEIVING + bool _parseMessage(); + bool _parseQuery(netif* pNetIf, + const clsMsgHeader& p_Header); + + bool _parseResponse(netif* pNetIf, const clsMsgHeader& p_Header); + bool _processAnswers(netif* pNetIf, const clsRRAnswer* p_pPTRAnswers); + bool _processPTRAnswer(const clsRRAnswerPTR* p_pPTRAnswer, + bool& p_rbFoundNewKeyAnswer); + bool _processSRVAnswer(const clsRRAnswerSRV* p_pSRVAnswer, + bool& p_rbFoundNewKeyAnswer); + bool _processTXTAnswer(const clsRRAnswerTXT* p_pTXTAnswer); +#ifdef MDNS_IPV4_SUPPORT + bool _processAAnswer(const clsRRAnswerA* p_pAAnswer); +#endif +#ifdef MDNS_IPV6_SUPPORT + bool _processAAAAAnswer(const clsRRAnswerAAAA* p_pAAAAAnswer); +#endif + + // PROBING + bool _updateProbeStatus(); + bool _resetProbeStatus(bool p_bRestart = true); + bool _hasProbesWaitingForAnswers(void) const; + bool _sendHostProbe(); + bool _sendServiceProbe(clsService& p_rService); + bool _cancelProbingForHost(void); + bool _cancelProbingForService(clsService& p_rService); + bool _callHostProbeResultCallback(bool p_bResult); + bool _callServiceProbeResultCallback(clsService& p_rService, + bool p_bResult); + + // ANNOUNCE + bool _announce(bool p_bAnnounce, + bool p_bIncludeServices); + bool _announceService(clsService& p_pService, + bool p_bAnnounce = true); + + // QUERY CACHE + bool _checkQueryCache(); + + uint32_t _replyMaskForHost(netif* pNetIf, + const clsRRHeader& p_RRHeader, + bool* p_pbFullNameMatch = 0) const; + uint32_t _replyMaskForService(const clsRRHeader& p_RRHeader, + clsService& p_rService, + bool* p_pbFullNameMatch = 0); + + + // File: ..._Host_Transfer + // SENDING + bool _sendMessage(netif* pNetIf, clsSendParameter& p_SendParameter); + bool _sendMessage(clsSendParameter& p_SendParameter); + bool _sendMessage_Multicast(netif* pNetIf, + clsSendParameter& p_rSendParameter, + uint8_t p_IPProtocolTypes); + bool _prepareMessage(netif* pNetIf, clsSendParameter& p_SendParameter); + bool _addQueryRecord(clsSendParameter& p_rSendParameter, + const clsRRDomain& p_QueryDomain, + uint16_t p_u16QueryType); + bool _sendQuery(const clsQuery& p_Query, + clsQuery::clsAnswer::list* p_pKnownAnswers = 0); + bool _sendQuery(const clsRRDomain& p_QueryDomain, + uint16_t p_u16RecordType, + clsQuery::clsAnswer::list* p_pKnownAnswers = 0); + + IPAddress _getResponderIPAddress(netif* pNetIf, + enuIPProtocolType p_IPProtocolType) const; + + // RESOURCE RECORD + bool _readRRQuestion(clsRRQuestion& p_rQuestion); + bool _readRRAnswer(clsRRAnswer*& p_rpAnswer); +#ifdef MDNS_IPV4_SUPPORT + bool _readRRAnswerA(clsRRAnswerA& p_rRRAnswerA, + uint16_t p_u16RDLength); +#endif + bool _readRRAnswerPTR(clsRRAnswerPTR& p_rRRAnswerPTR, + uint16_t p_u16RDLength); + bool _readRRAnswerTXT(clsRRAnswerTXT& p_rRRAnswerTXT, + uint16_t p_u16RDLength); +#ifdef MDNS_IPV6_SUPPORT + bool _readRRAnswerAAAA(clsRRAnswerAAAA& p_rRRAnswerAAAA, + uint16_t p_u16RDLength); +#endif + bool _readRRAnswerSRV(clsRRAnswerSRV& p_rRRAnswerSRV, + uint16_t p_u16RDLength); + bool _readRRAnswerGeneric(clsRRAnswerGeneric& p_rRRAnswerGeneric, + uint16_t p_u16RDLength); + + bool _readRRHeader(clsRRHeader& p_rHeader); + bool _readRRDomain(clsRRDomain& p_rRRDomain); + bool _readRRDomain_Loop(clsRRDomain& p_rRRDomain, + uint8_t p_u8Depth); + bool _readRRAttributes(clsRRAttributes& p_rAttributes); + + // DOMAIN NAMES + bool _buildDomainForHost(const char* p_pcHostName, + clsRRDomain& p_rHostDomain) const; + bool _buildDomainForDNSSD(clsRRDomain& p_rDNSSDDomain) const; + bool _buildDomainForService(const clsService& p_Service, + bool p_bIncludeName, + clsRRDomain& p_rServiceDomain) const; + bool _buildDomainForService(const char* p_pcService, + const char* p_pcProtocol, + clsRRDomain& p_rServiceDomain) const; +#ifdef MDNS_IPV4_SUPPORT + bool _buildDomainForReverseIPv4(IPAddress p_IPv4Address, + clsRRDomain& p_rReverseIPv4Domain) const; +#endif +#ifdef MDNS_IPV6_SUPPORT + bool _buildDomainForReverseIPv6(IPAddress p_IPv4Address, + clsRRDomain& p_rReverseIPv6Domain) const; +#endif + + // UDP + bool _udpReadBuffer(unsigned char* p_pBuffer, + size_t p_stLength); + bool _udpRead8(uint8_t& p_ru8Value); + bool _udpRead16(uint16_t& p_ru16Value); + bool _udpRead32(uint32_t& p_ru32Value); + + bool _udpAppendBuffer(const unsigned char* p_pcBuffer, + size_t p_stLength); + bool _udpAppend8(uint8_t p_u8Value); + bool _udpAppend16(uint16_t p_u16Value); + bool _udpAppend32(uint32_t p_u32Value); + +#if not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_MDNS_RESPONDER + bool _udpDump(bool p_bMovePointer = false); + bool _udpDump(unsigned p_uOffset, + unsigned p_uLength); +#endif + + // READ/WRITE MDNS STRUCTS + bool _readMDNSMsgHeader(clsMsgHeader& p_rMsgHeader); + + bool _write8(uint8_t p_u8Value, + clsSendParameter& p_rSendParameter); + bool _write16(uint16_t p_u16Value, + clsSendParameter& p_rSendParameter); + bool _write32(uint32_t p_u32Value, + clsSendParameter& p_rSendParameter); + + bool _writeMDNSMsgHeader(const clsMsgHeader& p_MsgHeader, + clsSendParameter& p_rSendParameter); + bool _writeMDNSRRAttributes(const clsRRAttributes& p_Attributes, + clsSendParameter& p_rSendParameter); + bool _writeMDNSRRDomain(const clsRRDomain& p_Domain, + clsSendParameter& p_rSendParameter); + bool _writeMDNSHostDomain(const char* m_pcHostName, + bool p_bPrependRDLength, + uint16_t p_u16AdditionalLength, + clsSendParameter& p_rSendParameter); + bool _writeMDNSServiceDomain(const clsService& p_Service, + bool p_bIncludeName, + bool p_bPrependRDLength, + uint16_t p_u16AdditionalLength, + clsSendParameter& p_rSendParameter); + + bool _writeMDNSQuestion(clsRRQuestion& p_Question, + clsSendParameter& p_rSendParameter); + +#ifdef MDNS_IPV4_SUPPORT + bool _writeMDNSAnswer_A(IPAddress p_IPAddress, + clsSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_PTR_IPv4(IPAddress p_IPAddress, + clsSendParameter& p_rSendParameter); +#endif + bool _writeMDNSAnswer_PTR_TYPE(clsService& p_rService, + clsSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_PTR_NAME(clsService& p_rService, + clsSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_TXT(clsService& p_rService, + clsSendParameter& p_rSendParameter); +#ifdef MDNS_IPV6_SUPPORT + bool _writeMDNSAnswer_AAAA(IPAddress p_IPAddress, + clsSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_PTR_IPv6(IPAddress p_IPAddress, + clsSendParameter& p_rSendParameter); +#endif + bool _writeMDNSAnswer_SRV(clsService& p_rService, + clsSendParameter& p_rSendParameter); + clsNSECBitmap* _createNSECBitmap(uint32_t p_u32NSECContent); + bool _writeMDNSNSECBitmap(const clsNSECBitmap& p_NSECBitmap, + clsSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_NSEC(uint32_t p_u32NSECContent, + clsSendParameter& p_rSendParameter); +#ifdef MDNS_IPV4_SUPPORT + bool _writeMDNSAnswer_NSEC_PTR_IPv4(IPAddress p_IPAddress, + clsSendParameter& p_rSendParameter); +#endif +#ifdef MDNS_IPV6_SUPPORT + bool _writeMDNSAnswer_NSEC_PTR_IPv6(IPAddress p_IPAddress, + clsSendParameter& p_rSendParameter); +#endif + bool _writeMDNSAnswer_NSEC(clsService& p_rService, + uint32_t p_u32NSECContent, + clsSendParameter& p_rSendParameter); + + + // File: ..._Host_Debug +#if not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_MDNS_RESPONDER + const char* _DH(const clsService* p_pMDNSService = 0) const; + const char* _service2String(const clsService* p_pMDNSService) const; + + bool _printRRDomain(const clsRRDomain& p_rRRDomain) const; + bool _printRRAnswer(const clsRRAnswer& p_RRAnswer) const; + const char* _RRType2Name(uint16_t p_u16RRType) const; + const char* _RRClass2String(uint16_t p_u16RRClass, + bool p_bIsQuery) const; + const char* _replyFlags2String(uint32_t p_u32ReplyFlags) const; + const char* _NSECBitmap2String(const clsNSECBitmap* p_pNSECBitmap) const; +#endif + + +protected: + UdpContext* m_pUDPContext; + + char* m_pcHostName; + char* m_pcDefaultInstanceName; + clsService::list m_Services; + clsQuery::list m_Queries; + clsProbeInformation m_ProbeInformation; +}; + + +} // namespace MDNSImplementation + + +} // namespace esp8266 + + +#endif // __LEAMDNS2HOST_H__ + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp new file mode 100644 index 000000000..c3b79c02b --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -0,0 +1,2219 @@ +/* + LEAmDNS2Host_Control.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 "ESP8266mDNS.h" +#include "LEAmDNS2Host.h" +#include "LEAmDNS2_Priv.h" + +namespace esp8266 +{ + + +namespace experimental +{ + + +/* + + RECEIVING + +*/ + +/* + clsLEAmDNS2_Host::_parseMessage + +*/ +bool clsLEAMDNSHost::_parseMessage() +{ + DEBUG_EX_INFO( + unsigned long ulStartTime = millis(); + unsigned uStartMemory = ESP.getFreeHeap(); + DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage (Time: %lu ms, heap: %u bytes, from %s, to %s)\n"), _DH(), ulStartTime, uStartMemory, + m_pUDPContext->getRemoteAddress().toString().c_str(), + m_pUDPContext->getDestAddress().toString().c_str()); + ); + //DEBUG_EX_INFO(_udpDump();); + netif* pNetIf = m_pUDPContext->getInputNetif(); + + bool bResult = false; + + clsMsgHeader header; + if (_readMDNSMsgHeader(header)) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: 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"), + _DH(), + (unsigned)header.m_u16ID, + (unsigned)header.m_1bQR, (unsigned)header.m_4bOpcode, (unsigned)header.m_1bAA, (unsigned)header.m_1bTC, (unsigned)header.m_1bRD, + (unsigned)header.m_1bRA, (unsigned)header.m_4bRCode, + (unsigned)header.m_u16QDCount, + (unsigned)header.m_u16ANCount, + (unsigned)header.m_u16NSCount, + (unsigned)header.m_u16ARCount)); + if (0 == header.m_4bOpcode) + { + // A standard query + if (header.m_1bQR) + { + // Received a response -> answers to a query + //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: Reading answers: ID:%u, Q:%u, A:%u, NS:%u, AR:%u\n"), _DH(), header.m_u16ID, header.m_u16QDCount, header.m_u16ANCount, header.m_u16NSCount, header.m_u16ARCount);); + bResult = _parseResponse(pNetIf, header); + } + else + { + // Received a query (Questions) + //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: Reading query: ID:%u, Q:%u, A:%u, NS:%u, AR:%u\n"), _DH(), header.m_u16ID, header.m_u16QDCount, header.m_u16ANCount, header.m_u16NSCount, header.m_u16ARCount);); + bResult = _parseQuery(m_pUDPContext->getInputNetif(), header); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: Received UNEXPECTED opcode:%u. Ignoring message!\n"), _DH(), header.m_4bOpcode);); + m_pUDPContext->flush(); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: FAILED to read header\n"), _DH());); + m_pUDPContext->flush(); + } + DEBUG_EX_INFO( + unsigned uFreeHeap = ESP.getFreeHeap(); + DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: Done (%s after %lu ms, ate %i bytes, remaining %u)\n\n"), _DH(), (bResult ? "Succeeded" : "FAILED"), (millis() - ulStartTime), (uStartMemory - uFreeHeap), uFreeHeap); + ); + return bResult; +} + +/* + clsLEAmDNS2_Host::_parseQuery + + Queries are of interest in two cases: + 1. allow for tiebreaking while probing in the case of a race condition between two instances probing for + the same name at the same time + 2. provide answers to questions for our host domain or any presented service + + When reading the questions, a set of (planned) responses is created, eg. a reverse PTR question for the host domain + gets an A (IP address) response, a PTR question for the _services._dns-sd domain gets a PTR (type) response for any + registered service, ... + + As any mDNS responder should be able to handle 'legacy' queries (from DNS clients), this case is handled here also. + Legacy queries have got only one (unicast) question and are directed to the local DNS port (not the multicast port). + +*/ +bool clsLEAMDNSHost::_parseQuery(netif* pNetIf, + const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader) +{ + bool bResult = true; + + clsSendParameter sendParameter; + uint32_t u32HostOrServiceReplies = 0; + bool bHostOrServiceTiebreakNeeded = false; + for (uint16_t qd = 0; ((bResult) && (qd < p_MsgHeader.m_u16QDCount)); ++qd) + { + clsRRQuestion questionRR; + if ((bResult = _readRRQuestion(questionRR))) + { + // Define host replies, BUT only answer queries after probing is done + u32HostOrServiceReplies = + sendParameter.m_u32HostReplyMask |= ((probeStatus()) + ? _replyMaskForHost(pNetIf, questionRR.m_Header, 0) + : 0); + DEBUG_EX_INFO(if (u32HostOrServiceReplies) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Host reply needed %s\n"), _DH(), _replyFlags2String(u32HostOrServiceReplies));); + + // Check tiebreak need for host domain + if (clsProbeInformation_Base::clsProbeInformation_Base::enuProbingStatus::InProgress == m_ProbeInformation.m_ProbingStatus) + { + bool bFullNameMatch = false; + if ((_replyMaskForHost(pNetIf, questionRR.m_Header, &bFullNameMatch)) && + (bFullNameMatch)) + { + // We're in 'probing' state and someone is asking for our host domain: this might be + // a race-condition: Two hosts with the same domain names try simutanously to probe their domains + // See: RFC 6762, 8.2 (Tiebraking) + // However, we're using a max. reduced approach for tiebreaking here: The higher IP-address wins! + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Possible race-condition for host domain detected while probing.\n"), _DH());); + + bHostOrServiceTiebreakNeeded = + m_ProbeInformation.m_bTiebreakNeeded = true; + } + } + + // Define service replies + for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) + { + clsService* pService = *it; + + // Define service replies, BUT only answer queries after probing is done + uint32_t u32ReplyMaskForQuestion = ((pService->probeStatus()) + ? _replyMaskForService(questionRR.m_Header, *pService, 0) + : 0); + u32HostOrServiceReplies |= (pService->m_u32ReplyMask |= u32ReplyMaskForQuestion); + DEBUG_EX_INFO(if (u32ReplyMaskForQuestion) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service reply needed: %s\n"), _DH(pService), _replyFlags2String(u32ReplyMaskForQuestion));); + + // Check tiebreak need for service domain + if (clsProbeInformation_Base::clsProbeInformation_Base::enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) + { + bool bFullNameMatch = false; + if ((_replyMaskForService(questionRR.m_Header, *pService, &bFullNameMatch)) && + (bFullNameMatch)) + { + // We're in 'probing' state and someone is asking for this service domain: this might be + // a race-condition: Two services with the same domain names try simutanously to probe their domains + // See: RFC 6762, 8.2 (Tiebraking) + // However, we're using a max. reduced approach for tiebreaking here: The 'higher' SRV host wins! + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Possible race-condition for service domain detected while probing.\n"), _DH(pService));); + + bHostOrServiceTiebreakNeeded = + pService->m_ProbeInformation.m_bTiebreakNeeded = true; + } + } + } + + // Handle unicast and legacy specialities + // If only one question asks for unicast reply, the whole reply packet is send unicast + if (((DNS_MQUERY_PORT != m_pUDPContext->getRemotePort()) || // Unicast (maybe legacy) query OR + (questionRR.m_bUnicast)) && // Expressivly unicast query + (!sendParameter.m_bUnicast)) + { + sendParameter.m_bUnicast = true; + //sendParameter.m_bCacheFlush = false; + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Unicast response asked for %s!\n"), _DH(), m_pUDPContext->getRemoteAddress().toString().c_str());); + //Serial.printf_P(PSTR("%s _parseQuery: Ignored Unicast response asked for by %s!\n"), _DH(), IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str()); + + if ((DNS_MQUERY_PORT != m_pUDPContext->getRemotePort()) && // Unicast (maybe legacy) query AND + (1 == p_MsgHeader.m_u16QDCount) && // Only one question AND + ((sendParameter.m_u32HostReplyMask) || // Host replies OR + (u32HostOrServiceReplies))) // Host or service replies available + { + // Local host check + // We're a match for this legacy query, BUT + // make sure, that the query comes from a local host + if ((m_pUDPContext) && +#ifdef MDNS_IPV4_SUPPORT + (m_pUDPContext->getRemoteAddress().isV4()) && + (ip4_addr_netcmp(ip_2_ip4((const ip_addr_t*)m_pUDPContext->getRemoteAddress()), + ip_2_ip4(&m_pUDPContext->getInputNetif()->ip_addr), + ip_2_ip4(&m_pUDPContext->getInputNetif()->netmask))) +#else + (true) +#endif + && +#ifdef MDNS_IPV6_SUPPORT + (m_pUDPContext->getRemoteAddress().isV6()) && + (ip6_addr_islinklocal(ip_2_ip6((const ip_addr_t*)m_pUDPContext->getRemoteAddress()))) +#else + (true) +#endif + ) + { + DEBUG_EX_RX(DEBUG_OUTPUT.println("\n\n\nUNICAST QUERY\n\n")); + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Legacy DNS query from local host %s!\n"), _DH(), m_pUDPContext->getRemoteAddress().toString().c_str());); + + sendParameter.m_u16ID = p_MsgHeader.m_u16ID; + sendParameter.m_bLegacyDNSQuery = true; + sendParameter.m_bCacheFlush = false; + clsRRQuestion* pNewRRQuestion = new clsRRQuestion; + if (pNewRRQuestion) + { + pNewRRQuestion->m_Header.m_Domain = questionRR.m_Header.m_Domain; + pNewRRQuestion->m_Header.m_Attributes.m_u16Type = questionRR.m_Header.m_Attributes.m_u16Type; + pNewRRQuestion->m_Header.m_Attributes.m_u16Class = questionRR.m_Header.m_Attributes.m_u16Class; + + sendParameter.m_RRQuestions.push_back(pNewRRQuestion); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: FAILED to add legacy DNS question!\n"), _DH());); + } + } + else + { + DEBUG_EX_RX(DEBUG_OUTPUT.printf("\n\n\nINVALID UNICAST QUERY from %s\n\n\n", m_pUDPContext->getRemoteAddress().toString().c_str())); + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Legacy DNS query from NON-LOCAL host at %s!\n"), _DH(), m_pUDPContext->getRemoteAddress().toString().c_str());); + bResult = false; + } + } + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: FAILED to read question!\n"), _DH());); + } + } // for questions + + //DEBUG_EX_INFO(if (u8HostOrServiceReplies) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Reply needed: %u (%s: %s->%s)\n"), _DH(), u8HostOrServiceReplies, clsTimeSyncer::timestr(), IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str(), IPAddress(m_pUDPContext->getDestAddress()).toString().c_str());); + //bHostOrServiceTiebreakNeeded = false; + + // Handle known answers + uint32_t u32Answers = (p_MsgHeader.m_u16ANCount + p_MsgHeader.m_u16NSCount + p_MsgHeader.m_u16ARCount); + if (((u32HostOrServiceReplies) || + (bHostOrServiceTiebreakNeeded)) && + (u32Answers)) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Reading known answers(%u):\n"), _DH(), u32Answers);); + + for (uint32_t an = 0; ((bResult) && (an < u32Answers)); ++an) + { + clsRRAnswer* pKnownRRAnswer = 0; + if (((bResult = _readRRAnswer(pKnownRRAnswer))) && + (pKnownRRAnswer)) + { + if ((DNS_RRTYPE_ANY != pKnownRRAnswer->m_Header.m_Attributes.m_u16Type) && // No ANY type answer + (DNS_RRCLASS_ANY != (pKnownRRAnswer->m_Header.m_Attributes.m_u16Class & (~0x8000)))) // No ANY class answer + { + /* - RFC6762 7.1 Suppression only for 'Shared Records' - + // Find match between planned answer (sendParameter.m_u8HostReplyMask) and this 'known answer' + uint32_t u32HostMatchMask = (sendParameter.m_u32HostReplyMask & _replyMaskForHost(pNetIf, pKnownRRAnswer->m_Header)); + if ((u32HostMatchMask) && // The RR in the known answer matches an RR we are planning to send, AND + ((Consts::u32HostTTL / 2) <= pKnownRRAnswer->m_u32TTL)) // The TTL of the known answer is longer than half of the new host TTL (120s) + { + + // Compare contents + if (enuAnswerType::PTR == pKnownRRAnswer->answerType()) + { + stcRRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostName, hostDomain)) && + (((stcRRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain == hostDomain)) + { + // Host domain match + #ifdef MDNS_IPV4_SUPPORT + if (u32HostMatchMask & static_cast(enuContentFlag::PTR_IPv4)) + { + // IPv4 PTR was asked for, but is already known -> skipping + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: IPv4 PTR already known (TTL:%u)... skipping!\n"), _DH(), pKnownRRAnswer->m_u32TTL);); + sendParameter.m_u32HostReplyMask &= ~static_cast(enuContentFlag::PTR_IPv4); + } + #endif + #ifdef MDNS_IPV6_SUPPORT + if (u32HostMatchMask & static_cast(enuContentFlag::PTR_IPv6)) + { + // IPv6 PTR was asked for, but is already known -> skipping + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: IPv6 PTR already known (TTL:%u)... skipping!\n"), _DH(), pKnownRRAnswer->m_u32TTL);); + sendParameter.m_u32HostReplyMask &= ~static_cast(enuContentFlag::PTR_IPv6); + } + #endif + } + } + else if (u32HostMatchMask & static_cast(enuContentFlag::A)) + { + // IPv4 address was asked for + #ifdef MDNS_IPV4_SUPPORT + if ((enuAnswerType::A == pKnownRRAnswer->answerType()) && + (((stcRRAnswerA*)pKnownRRAnswer)->m_IPAddress == _getResponderIPAddress(pNetIf, enuIPProtocolType::V4))) + { + + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: IPv4 address already known (TTL:%u)... skipping!\n"), _DH(), pKnownRRAnswer->m_u32TTL);); + sendParameter.m_u32HostReplyMask &= ~static_cast(enuContentFlag::A); + } // else: RData NOT IPv4 length !! + #endif + } + else if (u32HostMatchMask & static_cast(enuContentFlag::AAAA)) + { + // IPv6 address was asked for + #ifdef MDNS_IPV6_SUPPORT + if ((enuAnswerType::AAAA == pKnownRRAnswer->answerType()) && + (((stcRRAnswerAAAA*)pKnownRRAnswer)->m_IPAddress == _getResponderIPAddress(pNetIf, enuIPProtocolType::V6))) + { + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: IPv6 address already known... skipping!\n"), _DH());); + sendParameter.m_u32HostReplyMask &= ~static_cast(enuContentFlag::AAAA); + } // else: RData NOT IPv6 length !! + #endif + } + } // Host match and TTL + */ + + // + // Check host tiebreak possibility + if (m_ProbeInformation.m_bTiebreakNeeded) + { + clsRRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostName, hostDomain)) && + (pKnownRRAnswer->m_Header.m_Domain == hostDomain)) + { + // Host domain match +#ifdef MDNS_IPV4_SUPPORT + if (enuAnswerType::A == pKnownRRAnswer->answerType()) + { + // CHECK + IPAddress localIPAddress(_getResponderIPAddress(pNetIf, enuIPProtocolType::V4)); + if (((clsRRAnswerA*)pKnownRRAnswer)->m_IPAddress == localIPAddress) + { + // SAME IP address -> We've received an old message from ourselfs (same IP) + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv4) WON (was an old message)!\n"), _DH());); + m_ProbeInformation.m_bTiebreakNeeded = false; + } + else + { + if ((uint32_t)(((clsRRAnswerA*)pKnownRRAnswer)->m_IPAddress) > (uint32_t)localIPAddress) // The OTHER IP is 'higher' -> LOST + { + // LOST tiebreak + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv4) LOST (lower IPv4)!\n"), _DH());); + _cancelProbingForHost(); + m_ProbeInformation.m_bTiebreakNeeded = false; + } + else + { + // WON tiebreak + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv4) WON (higher IPv4)!\n"), _DH());); + m_ProbeInformation.m_bTiebreakNeeded = false; + } + } + } +#endif +#ifdef MDNS_IPV6_SUPPORT + if (enuAnswerType::AAAA == pKnownRRAnswer->answerType()) + { + IPAddress localIPAddress(_getResponderIPAddress(pNetIf, enuIPProtocolType::V6)); + if (((clsRRAnswerAAAA*)pKnownRRAnswer)->m_IPAddress == localIPAddress) + { + // SAME IP address -> We've received an old message from ourselfs (same IP) + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv6) WON (was an old message)!\n"), _DH());); + m_ProbeInformation.m_bTiebreakNeeded = false; + } + else + { + // memcmp delivers >0 (positive) if the first non-matching byte in A is higher than in B + if (0 < memcmp((((clsRRAnswerAAAA*)pKnownRRAnswer)->m_IPAddress).raw6(), localIPAddress.raw6(), clsConsts::u16IPv6Size)) // The OTHER IP is 'higher' -> LOST + { + // LOST tiebreak + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv6) LOST (lower IPv6)!\n"), _DH());); + _cancelProbingForHost(); + m_ProbeInformation.m_bTiebreakNeeded = false; + } + else + { + // WON tiebreak + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv6) WON (higher IPv6)!\n"), _DH());); + m_ProbeInformation.m_bTiebreakNeeded = false; + } + } + } +#endif + } + } // Host tiebreak possibility + + // Check service answers + for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) + { + clsService* pService = *it; + + uint32_t u32ServiceMatchMask = (pService->m_u32ReplyMask & _replyMaskForService(pKnownRRAnswer->m_Header, *pService)); + + if ((u32ServiceMatchMask) && // The RR in the known answer matches an RR we are planning to send, AND + ((clsConsts::u32ServiceTTL / 2) <= pKnownRRAnswer->m_u32TTL)) // The TTL of the known answer is longer than half of the new service TTL (4500s) + { + if (enuAnswerType::PTR == pKnownRRAnswer->answerType()) + { + clsRRDomain serviceDomain; + if ((u32ServiceMatchMask & static_cast(enuContentFlag::PTR_TYPE)) && + (_buildDomainForService(*pService, false, serviceDomain)) && + (serviceDomain == ((clsRRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain)) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service type PTR already known (TTL:%u)... skipping!\n"), _DH(pService), pKnownRRAnswer->m_u32TTL);); + pService->m_u32ReplyMask &= ~static_cast(enuContentFlag::PTR_TYPE); + } + if ((u32ServiceMatchMask & static_cast(enuContentFlag::PTR_NAME)) && + (_buildDomainForService(*pService, true, serviceDomain)) && + (serviceDomain == ((clsRRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain)) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service name PTR already known (TTL:%u)... skipping!\n"), _DH(pService), pKnownRRAnswer->m_u32TTL);); + pService->m_u32ReplyMask &= ~static_cast(enuContentFlag::PTR_NAME); + } + } + /* - RFC6762 7.1 Suppression only for 'Shared Records' + else if (u32ServiceMatchMask & static_cast(enuContentFlag::SRV)) + { + DEBUG_EX_ERR(if (enuAnswerType::SRV != pKnownRRAnswer->answerType()) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: ERROR! INVALID answer type (SRV)!\n"), _DH(pService));); + stcRRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostName, hostDomain)) && + (hostDomain == ((stcRRAnswerSRV*)pKnownRRAnswer)->m_SRVDomain)) // Host domain match + { + + if ((Consts::u16SRVPriority == ((stcRRAnswerSRV*)pKnownRRAnswer)->m_u16Priority) && + (Consts::u16SRVWeight == ((stcRRAnswerSRV*)pKnownRRAnswer)->m_u16Weight) && + (pService->m_u16Port == ((stcRRAnswerSRV*)pKnownRRAnswer)->m_u16Port)) + { + + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service SRV answer already known (TTL:%u)... skipping!\n"), _DH(pService), pKnownRRAnswer->m_u32TTL);); + pService->m_u32ReplyMask &= ~static_cast(enuContentFlag::SRV); + } // else: Small differences -> send update message + } + }*/ + /* - RFC6762 7.1 Suppression only for 'Shared Records' + else if (u32ServiceMatchMask & static_cast(enuContentFlag::TXT)) + { + DEBUG_EX_ERR(if (enuAnswerType::TXT != pKnownRRAnswer->answerType()) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: ERROR! INVALID answer type (TXT)!\n"), _DH(pService));); + _collectServiceTxts(*pService); + if (pService->m_Txts == ((stcRRAnswerTXT*)pKnownRRAnswer)->m_Txts) + { + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service TXT answer already known (TTL:%u)... skipping!\n"), _DH(pService), pKnownRRAnswer->m_u32TTL);); + pService->m_u32ReplyMask &= ~static_cast(enuContentFlag::TXT); + } + _releaseTempServiceTxts(*pService); + }*/ + } // Service match and enough TTL + + // + // Check service tiebreak possibility + if (pService->m_ProbeInformation.m_bTiebreakNeeded) + { + clsRRDomain serviceDomain; + if ((_buildDomainForService(*pService, true, serviceDomain)) && + (pKnownRRAnswer->m_Header.m_Domain == serviceDomain)) + { + // Service domain match + if (enuAnswerType::SRV == pKnownRRAnswer->answerType()) + { + clsRRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostName, hostDomain)) && + (hostDomain == ((clsRRAnswerSRV*)pKnownRRAnswer)->m_SRVDomain)) // Host domain match + { + // We've received an old message from ourselfs (same SRV) + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (SRV) won (was an old message)!\n"), _DH(pService));); + pService->m_ProbeInformation.m_bTiebreakNeeded = false; + } + else + { + if (((clsRRAnswerSRV*)pKnownRRAnswer)->m_SRVDomain > hostDomain) // The OTHER domain is 'higher' -> LOST + { + // LOST tiebreak + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (SRV) LOST (lower)!\n"), _DH(pService));); + _cancelProbingForService(*pService); + pService->m_ProbeInformation.m_bTiebreakNeeded = false; + } + else + { + // WON tiebreak + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (SRV) won (higher)!\n"), _DH(pService));); + pService->m_ProbeInformation.m_bTiebreakNeeded = false; + } + } + } + } + } // service tiebreak possibility + } // for services + } // ANY answers + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: FAILED to read known answer!\n"), _DH());); + } + + if (pKnownRRAnswer) + { + delete pKnownRRAnswer; + pKnownRRAnswer = 0; + } + } // for answers + } + else + { + DEBUG_EX_INFO(if (u32Answers) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Skipped %u known answers!\n"), _DH(), u32Answers);); + m_pUDPContext->flush(); + } + + if (bResult) + { + // Check, if a reply is needed + uint32_t u32ReplyNeeded = sendParameter.m_u32HostReplyMask; + for (const clsService* pService : m_Services) + { + u32ReplyNeeded |= pService->m_u32ReplyMask; + } + + if (u32ReplyNeeded) + { + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Sending %s answer(%s)...\n"), _DH(), (sendParameter.m_bUnicast ? "UC" : "MC"), _replyFlags2String(u32ReplyNeeded));); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Sending %s answer(%s)...\n"), _DH(), (sendParameter.m_bUnicast ? "UC" : "MC"), _replyFlags2String(u32ReplyNeeded));); + + sendParameter.m_Response = clsSendParameter::enuResponseType::Response; + sendParameter.m_bAuthorative = true; + + bResult = _sendMessage(pNetIf, sendParameter); + } + DEBUG_EX_INFO(else + { + DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: No reply needed\n"), _DH()); + }); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Something FAILED!\n"), _DH());); + m_pUDPContext->flush(); + } + + // + // Check and reset tiebreak-states + if (m_ProbeInformation.m_bTiebreakNeeded) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: UNSOLVED tiebreak-need for host domain!\n"), _DH());); + m_ProbeInformation.m_bTiebreakNeeded = false; + } + for (clsService* pService : m_Services) + { + if (pService->m_ProbeInformation.m_bTiebreakNeeded) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: UNSOLVED tiebreak-need for service domain '%s')\n"), _DH(), _service2String(pService));); + pService->m_ProbeInformation.m_bTiebreakNeeded = false; + } + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: FAILED!\n"), _DH());); + return bResult; +} + +/* + clsLEAmDNS2_Host::_parseResponse + + Responses are of interest in two cases: + 1. find domain name conflicts while probing + 2. get answers to service queries + + In both cases any included questions are ignored + + 1. If any answer has a domain name similar to one of the domain names we're planning to use (and are probing for), + then we've got a 'probing conflict'. The conflict has to be solved on our side of the conflict (eg. by + setting a new hostname and restart probing). The callback 'm_fnProbeResultCallback' is called with + 'p_bProbeResult=false' in this case. + + 2. Service queries like '_http._tcp.local' will (if available) produce PTR, SRV, TXT and A/AAAA answers. + All stored answers are pivoted by the service instance name (from the PTR record). Other answer parts, + like host domain or IP address are than attached to this element. + Any answer part carries a TTL, this is also stored (incl. the reception time); if the TTL is '0' the + answer (part) is withdrawn by the sender and should be removed from any cache. RFC 6762, 10.1 proposes to + set the caches TTL-value to 1 second in such a case and to delete the item only, if no update has + has taken place in this second. + Answer parts may arrive in 'unsorted' order, so they are grouped into three levels: + Level 1: PRT - names the service instance (and is used as pivot), voids all other parts if is withdrawn or outdates + Level 2: SRV - links the instance name to a host domain and port, voids A/AAAA parts if is withdrawn or outdates + TXT - links the instance name to services TXTs + Level 3: A/AAAA - links the host domain to an IP address +*/ +bool clsLEAMDNSHost::_parseResponse(netif* pNetIf, const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse\n"));); + //DEBUG_EX_INFO(_udpDump();); + + bool bResult = false; + + // A response should be the result of a query or a probe + if ((_hasQueriesWaitingForAnswers()) || // Waiting for query answers OR + (_hasProbesWaitingForAnswers())) // Probe responses + { + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: Received a response\n"), _DH()); + //_udpDump(); + ); + + bResult = true; + // + // Ignore questions here + clsRRQuestion dummyRRQ; + for (uint16_t qd = 0; ((bResult) && (qd < p_MsgHeader.m_u16QDCount)); ++qd) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: Received a response containing a question... ignoring!\n"), _DH());); + bResult = _readRRQuestion(dummyRRQ); + } // for queries + + // + // Read and collect answers + clsRRAnswer* pCollectedRRAnswers = 0; + uint32_t u32NumberOfAnswerRRs = (p_MsgHeader.m_u16ANCount + p_MsgHeader.m_u16NSCount + p_MsgHeader.m_u16ARCount); + for (uint32_t an = 0; ((bResult) && (an < u32NumberOfAnswerRRs)); ++an) + { + clsRRAnswer* pRRAnswer = 0; + if (((bResult = _readRRAnswer(pRRAnswer))) && + (pRRAnswer)) + { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: ADDING answer!\n"));); + pRRAnswer->m_pNext = pCollectedRRAnswers; + pCollectedRRAnswers = pRRAnswer; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: FAILED to read answer!\n"), _DH());); + if (pRRAnswer) + { + delete pRRAnswer; + pRRAnswer = 0; + } + bResult = false; + } + } // for answers + + // + // Process answers + if (bResult) + { + bResult = ((!pCollectedRRAnswers) || + (_processAnswers(pNetIf, pCollectedRRAnswers))); + } + else // Some failure while reading answers + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: FAILED to read answers!\n"), _DH());); + m_pUDPContext->flush(); + } + + // Delete collected answers + while (pCollectedRRAnswers) + { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: DELETING answer!\n"), _DH());); + clsRRAnswer* pNextAnswer = pCollectedRRAnswers->m_pNext; + delete pCollectedRRAnswers; + pCollectedRRAnswers = pNextAnswer; + } + } + else // Received an unexpected response -> ignore + { + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: Received an unexpected response... ignoring!\n"), _DH()); + /* + DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: Received an unexpected response... ignoring!\nDUMP:\n"), _DH()); + bool bDumpResult = true; + for (uint16_t qd=0; ((bDumpResult) && (qdflush(); + bResult = true; + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: FAILED!\n"), _DH());); + return bResult; +} + +/* + clsLEAmDNS2_Host::_processAnswers + Host: + A (0x01): eg. esp8266.local A OP TTL 123.456.789.012 + AAAA (01Cx): eg. esp8266.local AAAA OP TTL 1234:5678::90 + PTR (0x0C, IPv4): eg. 012.789.456.123.in-addr.arpa PTR OP TTL esp8266.local + PTR (0x0C, IPv6): eg. 90.0.0.0.0.0.0.0.0.0.0.0.78.56.34.12.ip6.arpa PTR OP TTL esp8266.local + Service: + PTR (0x0C, srv name): eg. _http._tcp.local PTR OP TTL MyESP._http._tcp.local + PTR (0x0C, srv type): eg. _services._dns-sd._udp.local PTR OP TTL _http._tcp.local + SRV (0x21): eg. MyESP._http._tcp.local SRV OP TTL PRIORITY WEIGHT PORT esp8266.local + TXT (0x10): eg. MyESP._http._tcp.local TXT OP TTL c#=1 + +*/ +bool clsLEAMDNSHost::_processAnswers(netif* pNetIf, const clsLEAMDNSHost::clsRRAnswer* p_pAnswers) +{ + bool bResult = false; + + if (p_pAnswers) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processAnswers: Processing answers...\n"), _DH());); + bResult = true; + + // Answers may arrive in an unexpected order. So we loop our answers as long, as we + // can connect new information to service queries + bool bFoundNewKeyAnswer; + do + { + bFoundNewKeyAnswer = false; + + const clsRRAnswer* pRRAnswer = p_pAnswers; + while ((pRRAnswer) && + (bResult)) + { + // 1. level answer (PTR) + if (enuAnswerType::PTR == pRRAnswer->answerType()) + { + // eg. _http._tcp.local PTR xxxx xx MyESP._http._tcp.local + bResult = _processPTRAnswer((clsRRAnswerPTR*)pRRAnswer, bFoundNewKeyAnswer); // May 'enable' new SRV or TXT answers to be linked to queries + } + // 2. level answers + // SRV -> host domain and port + else if (enuAnswerType::SRV == pRRAnswer->answerType()) + { + // eg. MyESP._http._tcp.local SRV xxxx xx yy zz 5000 esp8266.local + bResult = _processSRVAnswer((clsRRAnswerSRV*)pRRAnswer, bFoundNewKeyAnswer); // May 'enable' new A/AAAA answers to be linked to queries + } + // TXT -> Txts + else if (enuAnswerType::TXT == pRRAnswer->answerType()) + { + // eg. MyESP_http._tcp.local TXT xxxx xx c#=1 + bResult = _processTXTAnswer((clsRRAnswerTXT*)pRRAnswer); + } + // 3. level answers +#ifdef MDNS_IPV4_SUPPORT + // A -> IPv4Address + else if (enuAnswerType::A == pRRAnswer->answerType()) + { + // eg. esp8266.local A xxxx xx 192.168.2.120 + bResult = _processAAnswer((clsRRAnswerA*)pRRAnswer); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + // AAAA -> IPv6Address + else if (enuAnswerType::AAAA == pRRAnswer->answerType()) + { + // eg. esp8266.local AAAA xxxx xx 09cf::0c + bResult = _processAAAAAnswer((clsRRAnswerAAAA*)pRRAnswer); + } +#endif + + // Finally check for probing conflicts + // Host domain + if ((clsProbeInformation_Base::enuProbingStatus::InProgress == m_ProbeInformation.m_ProbingStatus) && + ((enuAnswerType::A == pRRAnswer->answerType()) || + (enuAnswerType::AAAA == pRRAnswer->answerType()))) + { + clsRRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostName, hostDomain)) && + (pRRAnswer->m_Header.m_Domain == hostDomain)) + { + bool bPossibleEcho = false; +#ifdef MDNS_IPV4_SUPPORT + if ((enuAnswerType::A == pRRAnswer->answerType()) && + (((clsRRAnswerA*)pRRAnswer)->m_IPAddress == _getResponderIPAddress(pNetIf, enuIPProtocolType::V4))) + { + bPossibleEcho = true; + } +#endif +#ifdef MDNS_IPV6_SUPPORT + if ((enuAnswerType::AAAA == pRRAnswer->answerType()) && + (((clsRRAnswerAAAA*)pRRAnswer)->m_IPAddress == _getResponderIPAddress(pNetIf, enuIPProtocolType::V6))) + { + bPossibleEcho = true; + } +#endif + if (!bPossibleEcho) + { + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _processAnswers: Probing CONFLICT found with '%s.local'\n"), _DH(), m_pcHostName);); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processAnswers: Probing CONFLICT found with '%s.local'\n"), _DH(), m_pcHostName);); + _cancelProbingForHost(); + } + else + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processAnswers: Ignoring CONFLICT found with '%s.local' as echo!\n"), _DH(), m_pcHostName);); + } + } + } + // Service domains + for (clsService* pService : m_Services) + { + if ((clsProbeInformation_Base::enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) && + ((enuAnswerType::TXT == pRRAnswer->answerType()) || + (enuAnswerType::SRV == pRRAnswer->answerType()))) + { + clsRRDomain serviceDomain; + if ((_buildDomainForService(*pService, true, serviceDomain)) && + (pRRAnswer->m_Header.m_Domain == serviceDomain)) + { + // TODO: Echo management needed? + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _processAnswers: Probing CONFLICT found with '%s'\n"), _DH(), _service2String(pService));); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processAnswers: Probing CONFLICT found with '%s'\n"), _DH(), _service2String(pService));); + _cancelProbingForService(*pService); + } + } + } + + pRRAnswer = pRRAnswer->m_pNext; // Next collected answer + } // while (answers) + } while ((bFoundNewKeyAnswer) && + (bResult)); + } // else: No answers provided + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _processAnswers: FAILED!\n"), _DH());); + return bResult; +} + +/* + clsLEAmDNS2_Host::_processPTRAnswer (level 1) +*/ +bool clsLEAMDNSHost::_processPTRAnswer(const clsLEAMDNSHost::clsRRAnswerPTR* p_pPTRAnswer, + bool& p_rbFoundNewKeyAnswer) +{ + bool bResult = false; + + if ((bResult = (0 != p_pPTRAnswer))) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processPTRAnswer: Processing PTR answers...\n"), _DH());); + // eg. _http._tcp.local PTR xxxx xx MyESP._http._tcp.local + // Check pending service queries for eg. '_http._tcp' + + clsQuery* pQuery = _findNextQueryByDomain(p_pPTRAnswer->m_Header.m_Domain, clsQuery::enuQueryType::Service, 0); + while (pQuery) + { + if (pQuery->m_bAwaitingAnswers) + { + // Find answer for service domain (eg. MyESP._http._tcp.local) + clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForServiceDomain(p_pPTRAnswer->m_PTRDomain); + if (pSQAnswer) + { + // existing answer + if (p_pPTRAnswer->m_u32TTL) + { + // Received update message + pSQAnswer->m_TTLServiceDomain.set(p_pPTRAnswer->m_u32TTL); // Update TTL tag + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processPTRAnswer: Updated TTL(%lu) for "), _DH(), p_pPTRAnswer->m_u32TTL); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR("\n")); + ); + } + else + { + // received goodbye-message + pSQAnswer->m_TTLServiceDomain.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processPTRAnswer: 'Goodbye' received for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR("\n")); + ); + } + } + else if ((p_pPTRAnswer->m_u32TTL) && // Not just a goodbye-message + ((pSQAnswer = new clsQuery::clsAnswer))) // Not yet included -> add answer + { + pSQAnswer->m_ServiceDomain = p_pPTRAnswer->m_PTRDomain; + pSQAnswer->m_QueryAnswerFlags |= static_cast(clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain); + pSQAnswer->m_TTLServiceDomain.set(p_pPTRAnswer->m_u32TTL); + //pSQAnswer->releaseServiceDomain(); + + bResult = pQuery->addAnswer(pSQAnswer); + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processPTRAnswer: Added service domain to answer: "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.println(); + ); + + p_rbFoundNewKeyAnswer = true; + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain), true); + } + } + pQuery = _findNextQueryByDomain(p_pPTRAnswer->m_Header.m_Domain, clsQuery::enuQueryType::Service, pQuery); + } + } // else: No p_pPTRAnswer + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _processPTRAnswer: FAILED!\n"), _DH());); + return bResult; +} + +/* + clsLEAmDNS2_Host::_processSRVAnswer (level 2) +*/ +bool clsLEAMDNSHost::_processSRVAnswer(const clsLEAMDNSHost::clsRRAnswerSRV* p_pSRVAnswer, + bool& p_rbFoundNewKeyAnswer) +{ + bool bResult = false; + + if ((bResult = (0 != p_pSRVAnswer))) + { + // eg. MyESP._http._tcp.local SRV xxxx xx yy zz 5000 esp8266.local + for (clsQuery::list::iterator it = m_Queries.begin(); ((bResult) && (it != m_Queries.end())); it++) + { + clsQuery* pQuery = *it; + + if (pQuery->m_bAwaitingAnswers) + { + clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForServiceDomain(p_pSRVAnswer->m_Header.m_Domain); + if (pSQAnswer) + { + // Answer for this service domain (eg. MyESP._http._tcp.local) available + if (p_pSRVAnswer->m_u32TTL) + { + // First or update message (TTL != 0) + pSQAnswer->m_TTLHostDomainAndPort.set(p_pSRVAnswer->m_u32TTL); // Update TTL tag + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processSRVAnswer: Updated TTL(%lu) for "), _DH(), p_pSRVAnswer->m_u32TTL); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" host domain and port\n")); + ); + // Host domain & Port + if ((pSQAnswer->m_HostDomain != p_pSRVAnswer->m_SRVDomain) || + (pSQAnswer->m_u16Port != p_pSRVAnswer->m_u16Port)) + { + + pSQAnswer->m_HostDomain = p_pSRVAnswer->m_SRVDomain; + //pSQAnswer->releaseHostDomain(); + pSQAnswer->m_u16Port = p_pSRVAnswer->m_u16Port; + pSQAnswer->m_QueryAnswerFlags |= (static_cast(clsQuery::clsAnswer::enuQueryAnswerType::HostDomain) | static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Port)); + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processSVRAnswer: Added host domain and port to "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(": ")); + _printRRDomain(pSQAnswer->m_HostDomain); + DEBUG_OUTPUT.printf_P(PSTR(": %u\n"), pSQAnswer->m_u16Port); + ); + + p_rbFoundNewKeyAnswer = true; + _executeQueryCallback(*pQuery, *pSQAnswer, (static_cast(clsQuery::clsAnswer::enuQueryAnswerType::HostDomain) | static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Port)), true); + } + } + else + { + // Goodby message + pSQAnswer->m_TTLHostDomainAndPort.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processSRVAnswer: 'Goodbye' received for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" host domain and port\n")); + ); + } + } + } // m_bAwaitingAnswers + } // for(queries) + } // else: No p_pSRVAnswer + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _processSRVAnswer: FAILED!\n"), _DH());); + return bResult; +} + +/* + clsLEAmDNS2_Host::_processTXTAnswer (level 2) +*/ +bool clsLEAMDNSHost::_processTXTAnswer(const clsLEAMDNSHost::clsRRAnswerTXT* p_pTXTAnswer) +{ + bool bResult = false; + + if ((bResult = (0 != p_pTXTAnswer))) + { + // eg. MyESP._http._tcp.local TXT xxxx xx c#=1 + for (clsQuery::list::iterator it = m_Queries.begin(); ((bResult) && (it != m_Queries.end())); it++) + { + clsQuery* pQuery = *it; + + if (pQuery->m_bAwaitingAnswers) + { + clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForServiceDomain(p_pTXTAnswer->m_Header.m_Domain); + if (pSQAnswer) + { + // Answer for this service domain (eg. MyESP._http._tcp.local) available + if (p_pTXTAnswer->m_u32TTL) + { + // First or update message + pSQAnswer->m_TTLTxts.set(p_pTXTAnswer->m_u32TTL); // Update TTL tag + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processTXTAnswer: Updated TTL(%lu) for "), _DH(), p_pTXTAnswer->m_u32TTL); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" TXTs\n")); + ); + if (!pSQAnswer->m_Txts.compare(p_pTXTAnswer->m_Txts)) + { + pSQAnswer->m_Txts = p_pTXTAnswer->m_Txts; + pSQAnswer->m_QueryAnswerFlags |= static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Txts); + //pSQAnswer->releaseTxts(); + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processTXTAnswer: Added TXT to "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.println(); + ); + + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Txts), true); + } + } + else + { + // Goodby message + pSQAnswer->m_TTLTxts.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processTXTAnswer: 'Goodbye' received for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" TXTs\n")); + ); + } + } + } // m_bAwaitingAnswers + } // for(queries) + } // else: No p_pTXTAnswer + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _processTXTAnswer: FAILED!\n"), _DH());); + return bResult; +} + +#ifdef MDNS_IPV4_SUPPORT +/* + clsLEAmDNS2_Host::_processAAnswer (level 3) +*/ +bool clsLEAMDNSHost::_processAAnswer(const clsLEAMDNSHost::clsRRAnswerA* p_pAAnswer) +{ + bool bResult = false; + + if ((bResult = (0 != p_pAAnswer))) + { + // eg. esp8266.local A xxxx xx 192.168.2.120 + for (clsQuery::list::iterator it = m_Queries.begin(); ((bResult) && (it != m_Queries.end())); it++) + { + clsQuery* pQuery = *it; + + if (pQuery->m_bAwaitingAnswers) + { + // Look for answers to host queries + if ((p_pAAnswer->m_u32TTL) && // NOT just a goodbye message + (clsQuery::enuQueryType::Host == pQuery->m_QueryType) && // AND a host query + (pQuery->m_Domain == p_pAAnswer->m_Header.m_Domain)) // AND a matching host domain + { + clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAnswer->m_Header.m_Domain); + if ((!pSQAnswer) && + ((pSQAnswer = new clsQuery::clsAnswer))) + { + // Add not yet included answer + pSQAnswer->m_HostDomain = p_pAAnswer->m_Header.m_Domain; + //pSQAnswer->releaseHostDomain(); + + bResult = pQuery->addAnswer(pSQAnswer); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: Added host query answer for "), _DH()); + _printRRDomain(pQuery->m_Domain); + DEBUG_OUTPUT.println(); + ); + + pSQAnswer->m_QueryAnswerFlags |= static_cast(clsQuery::clsAnswer::enuQueryAnswerType::HostDomain); + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::HostDomain), true); + } + } + + // Look for answers to service queries + clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAnswer->m_Header.m_Domain); + if (pSQAnswer) + { + // Answer for this host domain (eg. esp8266.local) available + clsQuery::clsAnswer::clsIPAddressWithTTL* pIPAddress = pSQAnswer->findIPv4Address(p_pAAnswer->m_IPAddress); + if (pIPAddress) + { + // Already known IPv4 address + if (p_pAAnswer->m_u32TTL) + { + // Valid TTL -> Update answers TTL + pIPAddress->m_TTL.set(p_pAAnswer->m_u32TTL); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: Updated TTL(%lu) for "), _DH(), p_pAAnswer->m_u32TTL); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IPv4 address (%s)\n"), pIPAddress->m_IPAddress.toString().c_str()); + ); + } + else + { + // 'Goodbye' message for known IPv4 address + pIPAddress->m_TTL.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: 'Goodbye' received for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IPv4 address (%s)\n"), pIPAddress->m_IPAddress.toString().c_str()); + ); + } + } + else + { + // Until now unknown IPv4 address -> Add (if the message isn't just a 'Goodbye' note) + if (p_pAAnswer->m_u32TTL) + { + // NOT just a 'Goodbye' message + pIPAddress = new clsQuery::clsAnswer::clsIPAddressWithTTL(p_pAAnswer->m_IPAddress, p_pAAnswer->m_u32TTL); + if ((pIPAddress) && + (pSQAnswer->addIPv4Address(pIPAddress))) + { + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: Added IPv4 address to "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(": %s\n"), pIPAddress->m_IPAddress.toString().c_str()); + ); + + pSQAnswer->m_QueryAnswerFlags |= static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address); + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address), true); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: FAILED to add IPv4 address (%s)!\n"), _DH(), p_pAAnswer->m_IPAddress.toString().c_str());); + } + } + } + } + } // m_bAwaitingAnswers + } // for(queries) + } // else: No p_pAAnswer + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: FAILED!\n"), _DH());); + return bResult; +} +#endif + +#ifdef MDNS_IPV6_SUPPORT +/* + clsLEAmDNS2_Host::_processAAAAAnswer (level 3) +*/ +bool clsLEAMDNSHost::_processAAAAAnswer(const clsLEAMDNSHost::clsRRAnswerAAAA* p_pAAAAAnswer) +{ + bool bResult = false; + + if ((bResult = (0 != p_pAAAAAnswer))) + { + // eg. esp8266.local AAAA xxxx xx 0bf3::0c + for (clsQuery::list::iterator it = m_Queries.begin(); ((bResult) && (it != m_Queries.end())); it++) + { + clsQuery* pQuery = *it; + + if (pQuery->m_bAwaitingAnswers) + { + // Look for answers to host queries + if ((p_pAAAAAnswer->m_u32TTL) && // NOT just a goodbye message + (clsQuery::enuQueryType::Host == pQuery->m_QueryType) && // AND a host query + (pQuery->m_Domain == p_pAAAAAnswer->m_Header.m_Domain)) // AND a matching host domain + { + clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAAAAnswer->m_Header.m_Domain); + if ((!pSQAnswer) && + ((pSQAnswer = new clsQuery::clsAnswer))) + { + // Add not yet included answer + pSQAnswer->m_HostDomain = p_pAAAAAnswer->m_Header.m_Domain; + //pSQAnswer->releaseHostDomain(); + + bResult = pQuery->addAnswer(pSQAnswer); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processAAAAAnswer: Added host query answer for "), _DH()); + _printRRDomain(pQuery->m_Domain); + DEBUG_OUTPUT.println(); + ); + + pSQAnswer->m_QueryAnswerFlags |= static_cast(clsQuery::clsAnswer::enuQueryAnswerType::HostDomain); + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::HostDomain), true); + } + } + + // Look for answers to service queries + clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAAAAnswer->m_Header.m_Domain); + if (pSQAnswer) // Answer for this host domain (eg. esp8266.local) available + { + clsQuery::clsAnswer::clsIPAddressWithTTL* pIPAddress = pSQAnswer->findIPv6Address(p_pAAAAAnswer->m_IPAddress); + if (pIPAddress) + { + // Already known IPv6 address + if (p_pAAAAAnswer->m_u32TTL) + { + // Valid TTL -> Update answers TTL + pIPAddress->m_TTL.set(p_pAAAAAnswer->m_u32TTL); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processAAAAAnswer: Updated TTL(%lu) for "), _DH(), p_pAAAAAnswer->m_u32TTL); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IPv6 address (%s)\n"), pIPAddress->m_IPAddress.toString().c_str()); + ); + } + else + { + // 'Goodbye' message for known IPv6 address + pIPAddress->m_TTL.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processAAAAAnswer: 'Goodbye' received for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IPv6 address (%s)\n"), pIPAddress->m_IPAddress.toString().c_str()); + ); + } + } + else + { + // Until now unknown IPv6 address -> Add (if the message isn't just a 'Goodbye' note) + if (p_pAAAAAnswer->m_u32TTL) + { + // NOT just a 'Goodbye' message + pIPAddress = new clsQuery::clsAnswer::clsIPAddressWithTTL(p_pAAAAAnswer->m_IPAddress, p_pAAAAAnswer->m_u32TTL); + if ((pIPAddress) && + (pSQAnswer->addIPv6Address(pIPAddress))) + { + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processAAAAAnswer: Added IPv6 address to "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(": %s\n"), pIPAddress->m_IPAddress.toString().c_str()); + ); + + pSQAnswer->m_QueryAnswerFlags |= static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address); + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address), true); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _processAAAAAnswer: FAILED to add IPv6 address (%s)!\n"), _DH(), p_pAAAAAnswer->m_IPAddress.toString().c_str());); + } + } + } + } + } // m_bAwaitingAnswers + } // for(queries) + } // else: No p_pAAAAAnswer + + return bResult; +} +#endif + + +/* + + PROBING + +*/ + +/* + clsLEAmDNS2_Host::_updateProbeStatus + + Manages the (outgoing) probing process. + - If probing has not been started yet (ProbingStatus_NotStarted), the initial delay (see RFC 6762) is determined and + the process is started + - After timeout (of initial or subsequential delay) a probe message is send out for three times. If the message has + already been sent out three times, the probing has been successful and is finished. + + Conflict management is handled in '_parseResponse ff.' + Tiebraking is handled in 'parseQuery ff.' +*/ +bool clsLEAMDNSHost::_updateProbeStatus() +{ + bool bResult = true; + + // + // Probe host domain + if ((clsProbeInformation_Base::enuProbingStatus::ReadyToStart == m_ProbeInformation.m_ProbingStatus))// && // Ready to get started AND + /* + (( + #ifdef MDNS_IPV4_SUPPORT + _getResponderIPAddress(pNetIf, enuIPProtocolType::V4).isSet() // AND has IPv4 address + #else + true + #endif + ) || ( + #ifdef MDNS_IPV6_SUPPORT + _getResponderIPAddress(pNetIf, enuIPProtocolType::V6).isSet() // OR has IPv6 address + #else + true + #endif + ))) // Has IP address + */ + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Starting host probing...\n"), _DH());); + + // First probe delay SHOULD be random 0-250 ms + m_ProbeInformation.m_Timeout.reset(rand() % clsConsts::u32ProbeDelay); + m_ProbeInformation.m_ProbingStatus = clsProbeInformation_Base::enuProbingStatus::InProgress; + } + else if ((clsProbeInformation_Base::enuProbingStatus::InProgress == m_ProbeInformation.m_ProbingStatus) && // Probing AND + (m_ProbeInformation.m_Timeout.expired())) // Time for next probe + { + if (clsConsts::u32ProbeCount > m_ProbeInformation.m_u32SentCount) + { + // Send next probe + if ((bResult = _sendHostProbe())) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Did sent host probe for '%s.local'\n\n"), _DH(), (m_pcHostName ? : ""));); + m_ProbeInformation.m_Timeout.reset(clsConsts::u32ProbeDelay); + ++m_ProbeInformation.m_u32SentCount; + } + } + else + { + // Probing finished + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("\n%s _updateProbeStatus: Done host probing for '%s.local'.\n\n\n"), _DH(), (m_pcHostName ? : ""));); + m_ProbeInformation.m_ProbingStatus = clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce; + m_ProbeInformation.m_Timeout.reset(esp8266::polledTimeout::oneShot::neverExpires); + + _callHostProbeResultCallback(true); + + // Prepare to announce host + m_ProbeInformation.m_u32SentCount = 0; + m_ProbeInformation.m_Timeout.reset(clsConsts::u32AnnounceDelay); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Prepared host announcing.\n\n"), _DH());); + } + } // else: Probing already finished OR waiting for next time slot + else if ((clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == m_ProbeInformation.m_ProbingStatus) && + (m_ProbeInformation.m_Timeout.expired())) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: ReadyToAnnounce => Announce (without services), now\n"), _DH());); + if ((bResult = _announce(true, false))) + { + // Don't announce services here + ++m_ProbeInformation.m_u32SentCount; // 1.. + + if (clsConsts::u32AnnounceCount > m_ProbeInformation.m_u32SentCount) + { + m_ProbeInformation.m_Timeout.reset(clsConsts::u32AnnounceDelay * pow(2, (m_ProbeInformation.m_u32SentCount - 1))); // 2^(0..) -> 1, 2, 4, ... + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Announcing host '%s.local' (%lu).\n\n"), _DH(), (m_pcHostName ? : ""), m_ProbeInformation.m_u32SentCount);); + } + else + { + m_ProbeInformation.m_ProbingStatus = clsProbeInformation_Base::enuProbingStatus::DoneFinally; + m_ProbeInformation.m_Timeout.reset(esp8266::polledTimeout::oneShot::neverExpires); + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Done host announcing for '%s.local'.\n"), _DH(), (m_pcHostName ? : ""));); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Done host announcing for '%s.local'.\n\n"), _DH(), (m_pcHostName ? : ""));); + //DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Done host announcing for '%s.local'.\n"), _DH(), (m_pcHostName ? : "")); + } + } + } + + // + // Probe services + for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) + { + clsService* pService = *it; + + if (clsProbeInformation_Base::enuProbingStatus::ReadyToStart == pService->m_ProbeInformation.m_ProbingStatus) + { + // Ready to get started + pService->m_ProbeInformation.m_Timeout.reset(clsConsts::u32ProbeDelay); // More or equal than first probe for host domain + pService->m_ProbeInformation.m_ProbingStatus = clsProbeInformation_Base::enuProbingStatus::InProgress; + } + else if ((clsProbeInformation_Base::enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) && // Probing AND + (pService->m_ProbeInformation.m_Timeout.expired())) // Time for next probe + { + if (clsConsts::u32ProbeCount > pService->m_ProbeInformation.m_u32SentCount) + { + // Send next probe + if ((bResult = _sendServiceProbe(*pService))) + { + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Did sent service probe for '%s' (%u)\n\n"), _DH(), _service2String(pService), (pService->m_ProbeInformation.m_u32SentCount + 1));); + pService->m_ProbeInformation.m_Timeout.reset(clsConsts::u32ProbeDelay); + ++pService->m_ProbeInformation.m_u32SentCount; + } + } + else + { + // Probing finished + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("\n%s _updateProbeStatus: Done service probing '%s'\n\n\n"), _DH(), _service2String(pService));); + pService->m_ProbeInformation.m_ProbingStatus = clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce; + pService->m_ProbeInformation.m_Timeout.reset(esp8266::polledTimeout::oneShot::neverExpires); + + _callServiceProbeResultCallback(*pService, true); + + // Prepare to announce service + pService->m_ProbeInformation.m_u32SentCount = 0; + pService->m_ProbeInformation.m_Timeout.reset(clsConsts::u32AnnounceDelay); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Prepared service announcing.\n\n"), _DH());); + } + } + else if ((clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == pService->m_ProbeInformation.m_ProbingStatus) && + (pService->m_ProbeInformation.m_Timeout.expired())) + { + // Probing already finished OR waiting for next time slot + if ((bResult = _announceService(*pService))) + { + // Announce service + ++pService->m_ProbeInformation.m_u32SentCount; // 1.. + + if (clsConsts::u32AnnounceCount > pService->m_ProbeInformation.m_u32SentCount) + { + pService->m_ProbeInformation.m_Timeout.reset(clsConsts::u32AnnounceDelay * pow(2, (pService->m_ProbeInformation.m_u32SentCount - 1))); // 2^(0..) -> 1, 2, 4, ... + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Announcing service '%s' (%lu)\n\n"), _DH(), _service2String(pService), pService->m_ProbeInformation.m_u32SentCount);); + } + else + { + pService->m_ProbeInformation.m_ProbingStatus = clsProbeInformation_Base::enuProbingStatus::DoneFinally; + pService->m_ProbeInformation.m_Timeout.reset(esp8266::polledTimeout::oneShot::neverExpires); + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Done service announcing for '%s'\n"), _DH(), _service2String(pService));); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Done service announcing for '%s'\n\n"), _DH(), _service2String(pService));); + //DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Done service announcing for '%s'\n"), _DH(), _service2String(pService)); + } + } + } + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: FAILED!\n\n"), _DH());); + return bResult; +} + +/* + clsLEAmDNS2_Host::_resetProbeStatus + + Resets the probe status. + If 'p_bRestart' is set, the status is set to ProbingStatus_NotStarted. Consequently, + when running 'updateProbeStatus' (which is done in every '_update' loop), the probing + process is restarted. + +*/ +bool clsLEAMDNSHost::_resetProbeStatus(bool p_bRestart /*= true*/) +{ + m_ProbeInformation.clear(false); + m_ProbeInformation.m_ProbingStatus = (p_bRestart ? clsProbeInformation_Base::enuProbingStatus::ReadyToStart : clsProbeInformation_Base::enuProbingStatus::DoneFinally); + + for (clsService* pService : m_Services) + { + pService->m_ProbeInformation.clear(false); + pService->m_ProbeInformation.m_ProbingStatus = m_ProbeInformation.m_ProbingStatus; + } + return true; +} + +/* + clsLEAmDNS2_Host::_hasProbesWaitingForAnswers + +*/ +bool clsLEAMDNSHost::_hasProbesWaitingForAnswers(void) const +{ + bool bResult = ((clsProbeInformation_Base::enuProbingStatus::InProgress == m_ProbeInformation.m_ProbingStatus) && // Probing + (0 < m_ProbeInformation.m_u32SentCount)); // And really probing + + for (clsService::list::const_iterator it = m_Services.cbegin(); ((!bResult) && (it != m_Services.cend())); it++) + { + clsService* pService = *it; + + bResult = ((clsProbeInformation_Base::enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) && // Probing + (0 < pService->m_ProbeInformation.m_u32SentCount)); // And really probing + } + return bResult; +} + +/* + clsLEAmDNS2_Host::_sendHostProbe + + Asks (probes) in the local network for the planned host domain + - (eg. esp8266.local) + + To allow 'tiebreaking' (see '_parseQuery'), the answers for these questions are delivered in + the 'known answers' section of the query. + Host domain: + - A/AAAA (eg. esp8266.esp -> 192.168.2.120) + +*/ +bool clsLEAMDNSHost::_sendHostProbe() +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendHostProbe (%s.local, %lu)\n"), _DH(), m_pcHostName, millis());); + + bool bResult = true; + + // Requests for host domain + clsSendParameter sendParameter; + sendParameter.m_bCacheFlush = false; // RFC 6762 10.2 + + clsRRQuestion* pNewRRQuestion = new clsRRQuestion; + if (((bResult = (0 != pNewRRQuestion))) && + ((bResult = _buildDomainForHost(m_pcHostName, pNewRRQuestion->m_Header.m_Domain)))) + { + //sendParameter.m_pQuestions->m_bUnicast = true; + pNewRRQuestion->m_Header.m_Attributes.m_u16Type = DNS_RRTYPE_ANY; + pNewRRQuestion->m_Header.m_Attributes.m_u16Class = (0x8000 | DNS_RRCLASS_IN); // Unicast & INternet + + sendParameter.m_RRQuestions.push_back(pNewRRQuestion); + + // Add known answers +#ifdef MDNS_IPV4_SUPPORT + sendParameter.m_u32HostReplyMask |= static_cast(enuContentFlag::A); // Add A answer +#endif +#ifdef MDNS_IPV6_SUPPORT + sendParameter.m_u32HostReplyMask |= static_cast(enuContentFlag::AAAA); // Add AAAA answer +#endif + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _sendHostProbe: FAILED to create host question!\n"), _DH());); + if (pNewRRQuestion) + { + delete pNewRRQuestion; + pNewRRQuestion = 0; + } + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendHostProbe: FAILED!\n"), _DH());); + return ((bResult) && + (_sendMessage(sendParameter))); +} + +/* + clsLEAmDNS2_Host::_sendServiceProbe + + Asks (probes) in the local network for the planned service instance domain + - (eg. MyESP._http._tcp.local). + + To allow 'tiebreaking' (see '_parseQuery'), the answers for these questions are delivered in + the 'known answers' section of the query. + Service domain: + - SRV (eg. MyESP._http._tcp.local -> 5000 esp8266.local) + - PTR NAME (eg. _http._tcp.local -> MyESP._http._tcp.local) (TODO: Check if needed, maybe TXT is better) + +*/ +bool clsLEAMDNSHost::_sendServiceProbe(clsService& p_rService) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendServiceProbe (%s, %lu)\n"), _DH(), _service2String(&p_rService), millis());); + + bool bResult = true; + + // Requests for service instance domain + clsSendParameter sendParameter; + sendParameter.m_bCacheFlush = false; // RFC 6762 10.2 + + clsRRQuestion* pNewRRQuestion = new clsRRQuestion; + if (((bResult = (0 != pNewRRQuestion))) && + ((bResult = _buildDomainForService(p_rService, true, pNewRRQuestion->m_Header.m_Domain)))) + { + pNewRRQuestion->m_bUnicast = true; + pNewRRQuestion->m_Header.m_Attributes.m_u16Type = DNS_RRTYPE_ANY; + pNewRRQuestion->m_Header.m_Attributes.m_u16Class = (0x8000 | DNS_RRCLASS_IN); // Unicast & INternet + + sendParameter.m_RRQuestions.push_back(pNewRRQuestion); + + // Add known answers + p_rService.m_u32ReplyMask = (static_cast(enuContentFlag::SRV) | static_cast(enuContentFlag::PTR_NAME)); // Add SRV and PTR NAME answers + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _sendServiceProbe: FAILED to create service question!\n"), _DH());); + if (pNewRRQuestion) + { + delete pNewRRQuestion; + pNewRRQuestion = 0; + } + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendServiceProbe: FAILED!\n"), _DH());); + return ((bResult) && + (_sendMessage(sendParameter))); +} + +/* + clsLEAmDNS2_Host::_cancelProbingForHost + +*/ +bool clsLEAMDNSHost::_cancelProbingForHost(void) +{ + bool bResult; + + m_ProbeInformation.clear(false); + + // Send host notification + bResult = _callHostProbeResultCallback(false); + + for (clsService* pService : m_Services) + { + bResult = bResult && _cancelProbingForService(*pService); + } + return bResult; +} + +/* + clsLEAmDNS2_Host::_cancelProbingForService + +*/ +bool clsLEAMDNSHost::_cancelProbingForService(clsService& p_rService) +{ + p_rService.m_ProbeInformation.clear(false); + + // Send notification + return _callServiceProbeResultCallback(p_rService, false); +} + +/* + clsLEAmDNS2_Host::_callHostProbeResultCallback + +*/ +bool clsLEAMDNSHost::_callHostProbeResultCallback(bool p_bResult) +{ + if (m_ProbeInformation.m_fnProbeResultCallback) + { + m_ProbeInformation.m_fnProbeResultCallback(*this, m_pcHostName, p_bResult); + } + else if (!p_bResult) + { + // Auto-Handle failure by changing the host name, use '-' as divider between base name and index + indexHostName(); + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _callHostProbeResultCallback: Changed Host Name: %s\n"), _DH(), (m_pcHostName ? : ""))); + } + return true; +} + +/* + clsLEAmDNS2_Host::_callServiceProbeResultCallback + +*/ +bool clsLEAMDNSHost::_callServiceProbeResultCallback(clsLEAMDNSHost::clsService& p_rService, + bool p_bResult) +{ + if (p_rService.m_ProbeInformation.m_fnProbeResultCallback) + { + p_rService.m_ProbeInformation.m_fnProbeResultCallback(p_rService, p_rService.instanceName(), p_bResult); + } + else if (!p_bResult) + { + // Auto-Handle failure by changing the service name, use ' #' as divider between base name and index + p_rService.indexInstanceName(); + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _callServiceProbeResultCallback: Changed Service Domain: %s\n"), _DH(), _service2String(&p_rService))); + } + return true; +} + + +/* + + ANNOUNCING + +*/ + +/* + clsLEAmDNS2_Host::_announce + + Announces the host domain: + - A/AAAA (eg. esp8266.local -> 192.168.2.120) + - PTR (eg. 192.168.2.120.in-addr.arpa -> esp8266.local) + + and all presented services: + - PTR_TYPE (_services._dns-sd._udp.local -> _http._tcp.local) + - PTR_NAME (eg. _http._tcp.local -> MyESP8266._http._tcp.local) + - SRV (eg. MyESP8266._http._tcp.local -> 5000 esp8266.local) + - TXT (eg. MyESP8266._http._tcp.local -> c#=1) + + Goodbye (Un-Announcing) for the host domain and all services is also handled here. + Goodbye messages are created by setting the TTL for the answer to 0, this happens + inside the '_writeXXXAnswer' procs via 'sendParameter.m_bUnannounce = true' + +*/ +bool clsLEAMDNSHost::_announce(bool p_bAnnounce, + bool p_bIncludeServices) +{ + bool bResult = false; + + clsSendParameter sendParameter; + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s ::announce() status=%d (waiting4data:%d ready2start:%d inprogress:%d ready2announce:%d done:%d\r\n"), + _DH(), m_ProbeInformation.m_ProbingStatus, + clsProbeInformation_Base::enuProbingStatus::WaitingForData, + clsProbeInformation_Base::enuProbingStatus::ReadyToStart, + clsProbeInformation_Base::enuProbingStatus::InProgress, + clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce, + clsProbeInformation_Base::enuProbingStatus::DoneFinally);); + + if ((clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == m_ProbeInformation.m_ProbingStatus) || + (clsProbeInformation_Base::enuProbingStatus::DoneFinally == m_ProbeInformation.m_ProbingStatus)) + { + bResult = true; + + sendParameter.m_Response = clsSendParameter::enuResponseType::Unsolicited; // Announces are 'Unsolicited authorative responses' + sendParameter.m_bAuthorative = true; + sendParameter.m_bCacheFlush = true; // RFC 6762 8.3 + sendParameter.m_bUnannounce = !p_bAnnounce; // When unannouncing, the TTL is set to '0' while creating the answers + + // Announce host + sendParameter.m_u32HostReplyMask = 0; +#ifdef MDNS_IPV4_SUPPORT + sendParameter.m_u32HostReplyMask |= static_cast(enuContentFlag::A); // A answer + sendParameter.m_u32HostReplyMask |= static_cast(enuContentFlag::PTR_IPv4); // PTR_IPv4 answer +#endif +#ifdef MDNS_IPV6_SUPPORT + sendParameter.m_u32HostReplyMask |= static_cast(enuContentFlag::AAAA); // AAAA answer + sendParameter.m_u32HostReplyMask |= static_cast(enuContentFlag::PTR_IPv6); // PTR_IPv6 answer +#endif + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _announce: Announcing host %s (content: %s)\n"), _DH(), m_pcHostName, _replyFlags2String(sendParameter.m_u32HostReplyMask));); + + if (p_bIncludeServices) + { + // Announce services (service type, name, SRV (location) and TXTs) + for (clsService* pService : m_Services) + { + if ((clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == pService->m_ProbeInformation.m_ProbingStatus) || + (clsProbeInformation_Base::enuProbingStatus::DoneFinally == pService->m_ProbeInformation.m_ProbingStatus)) + { + pService->m_u32ReplyMask = (static_cast(enuContentFlag::PTR_TYPE) | + static_cast(enuContentFlag::PTR_NAME) | + static_cast(enuContentFlag::SRV) | + static_cast(enuContentFlag::TXT)); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _announce: Announcing service '%s' (content %s)\n"), _DH(), _service2String(pService), _replyFlags2String(pService->m_u32ReplyMask));); + } + } + } + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _announce: FAILED!\n\n"), _DH());); + return ((bResult) && + (_sendMessage(sendParameter))); +} + +/* + clsLEAmDNS2_Host::_announceService + +*/ +bool clsLEAMDNSHost::_announceService(clsLEAMDNSHost::clsService& p_rService, + bool p_bAnnounce /*= true*/) +{ + bool bResult = false; + + clsSendParameter sendParameter; + if (clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == p_rService.m_ProbeInformation.m_ProbingStatus) + { + sendParameter.m_Response = clsSendParameter::enuResponseType::Unsolicited; // Announces are 'Unsolicited authorative responses' + sendParameter.m_bAuthorative = true; + sendParameter.m_bUnannounce = !p_bAnnounce; // When unannouncing, the TTL is set to '0' while creating the answers + + // DON'T announce host + sendParameter.m_u32HostReplyMask = 0; + + // Announce services (service type, name, SRV (location) and TXTs) + p_rService.m_u32ReplyMask = (static_cast(enuContentFlag::PTR_TYPE) | + static_cast(enuContentFlag::PTR_NAME) | + static_cast(enuContentFlag::SRV) | + static_cast(enuContentFlag::TXT)); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _announceService: Announcing service '%s' (content: %s)\n"), _DH(), _service2String(&p_rService), _replyFlags2String(p_rService.m_u32ReplyMask));); + + bResult = true; + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _announceService: FAILED!\n"), _DH());); + return ((bResult) && + (_sendMessage(sendParameter))); +} + + +/* + + QUERY CACHE + +*/ + +/* + clsLEAmDNS2_Host::_checkQueryCache + + For any 'living' query (m_bAwaitingAnswers == true) all available answers (their components) + are checked for topicality based on the stored reception time and the answers TTL. + When the components TTL is outlasted by more than 80%, a new question is generated, to get updated information. + When no update arrived (in time), the component is removed from the answer (cache). + +*/ +bool clsLEAMDNSHost::_checkQueryCache() +{ + bool bResult = true; + + DEBUG_EX_INFO( + bool printedInfo = false; + ); + for (clsQuery::list::iterator itQ = m_Queries.begin(); ((bResult) && (itQ != m_Queries.end())); itQ++) + { + clsQuery* pQuery = *itQ; + // + // Resend dynamic queries, if not already done often enough + if ((!pQuery->m_bStaticQuery) && + (pQuery->m_ResendTimeout.expired())) + { + if ((bResult = _sendQuery(*pQuery))) + { + // The re-query rate is increased to more than one hour (RFC 6762 5.2) + ++pQuery->m_u32SentCount; + uint32_t u32NewDelay = (clsConsts::u32DynamicQueryResendDelay * pow(2, std::min((pQuery->m_u32SentCount - 1), (uint32_t)12))); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Next query in %u seconds!\n"), _DH(), (u32NewDelay));); + pQuery->m_ResendTimeout.reset(u32NewDelay); + } + else + { + break; + } + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: %s to resend query!\n"), _DH(), (bResult ? "Succeeded" : "FAILED")); + printedInfo = true; + ); + } + + // + // Schedule updates for cached answers + if (pQuery->m_bAwaitingAnswers) + { + clsQuery::clsAnswer::list expiredAnswers; + for (clsQuery::clsAnswer::list::iterator itQA = pQuery->m_Answers.begin(); ((bResult) && (itQA != pQuery->m_Answers.end())); itQA++) + { + clsQuery::clsAnswer* pQAnswer = *itQA; + + // 1. level answer + if ((bResult) && + (pQAnswer->m_TTLServiceDomain.flagged())) + { + if (!pQAnswer->m_TTLServiceDomain.finalTimeoutLevel()) + { + bResult = ((_sendQuery(*pQuery)) && + (pQAnswer->m_TTLServiceDomain.restart())); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: PTR update scheduled for "), _DH()); + _printRRDomain(pQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" %s\n"), (bResult ? "OK" : "FAILURE")); + printedInfo = true; + ); + } + else + { + // Timed out! -> Delete + _executeQueryCallback(*pQuery, *pQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain), false); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Will remove PTR answer for "), _DH()); + _printRRDomain(pQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR("\n")); + printedInfo = true; + ); + + expiredAnswers.push_back(pQAnswer); + continue; // Don't use this answer anymore + } + } // ServiceDomain flagged + + // 2. level answers + // HostDomain & Port (from SRV) + if ((bResult) && + (pQAnswer->m_TTLHostDomainAndPort.flagged())) + { + if (!pQAnswer->m_TTLHostDomainAndPort.finalTimeoutLevel()) + { + bResult = ((_sendQuery(pQAnswer->m_ServiceDomain, DNS_RRTYPE_SRV)) && + (pQAnswer->m_TTLHostDomainAndPort.restart())); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: SRV update scheduled for "), _DH()); + _printRRDomain(pQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" host domain and port %s\n"), (bResult ? "OK" : "FAILURE")); + printedInfo = true; + ); + } + else + { + // Timed out! -> Delete + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Will remove SRV answer for "), _DH()); + _printRRDomain(pQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" host domain and port\n")); + printedInfo = true; + ); + // Delete + pQAnswer->m_HostDomain.clear(); + //pSQAnswer->releaseHostDomain(); + pQAnswer->m_u16Port = 0; + pQAnswer->m_TTLHostDomainAndPort.set(0); + clsQuery::clsAnswer::typeQueryAnswerType queryAnswerContentFlags = (static_cast(clsQuery::clsAnswer::enuQueryAnswerType::HostDomain) | static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Port)); + // As the host domain is the base for the IPv4- and IPv6Address, remove these too +#ifdef MDNS_IPV4_SUPPORT + pQAnswer->releaseIPv4Addresses(); + queryAnswerContentFlags |= static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address); +#endif +#ifdef MDNS_IPV6_SUPPORT + pQAnswer->releaseIPv6Addresses(); + queryAnswerContentFlags |= static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address); +#endif + + // Remove content flags for deleted answer parts + pQAnswer->m_QueryAnswerFlags &= ~queryAnswerContentFlags; + _executeQueryCallback(*pQuery, *pQAnswer, queryAnswerContentFlags, false); + } + } // HostDomainAndPort flagged + + // Txts (from TXT) + if ((bResult) && + (pQAnswer->m_TTLTxts.flagged())) + { + if (!pQAnswer->m_TTLTxts.finalTimeoutLevel()) + { + bResult = ((_sendQuery(pQAnswer->m_ServiceDomain, DNS_RRTYPE_TXT)) && + (pQAnswer->m_TTLTxts.restart())); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: TXT update scheduled for "), _DH()); + _printRRDomain(pQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" TXTs %s\n"), (bResult ? "OK" : "FAILURE")); + printedInfo = true; + ); + } + else + { + // Timed out! -> Delete + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Will remove TXT answer for "), _DH()); + _printRRDomain(pQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" TXTs\n")); + printedInfo = true; + ); + // Delete + pQAnswer->m_Txts.clear(); + pQAnswer->m_TTLTxts.set(0); + + // Remove content flags for deleted answer parts + pQAnswer->m_QueryAnswerFlags &= ~static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Txts); + _executeQueryCallback(*pQuery, *pQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Txts), false); + } + } // TXTs flagged + + // 3. level answers +#ifdef MDNS_IPV4_SUPPORT + // IPv4Address (from A) + clsQuery::clsAnswer::clsIPAddressWithTTL::list expiredIPv4Addresses; + bool bAUpdateQuerySent = false; + for (clsQuery::clsAnswer::clsIPAddressWithTTL::list::iterator itQAIPv4 = pQAnswer->m_IPv4Addresses.begin(); ((bResult) && (itQAIPv4 != pQAnswer->m_IPv4Addresses.end())); itQAIPv4++) + { + clsQuery::clsAnswer::clsIPAddressWithTTL* pIPv4Address = *itQAIPv4; + + if (pIPv4Address->m_TTL.flagged()) + { + if (!pIPv4Address->m_TTL.finalTimeoutLevel()) + { + // Needs update + if ((bAUpdateQuerySent) || + ((bResult = _sendQuery(pQAnswer->m_HostDomain, DNS_RRTYPE_A)))) + { + pIPv4Address->m_TTL.restart(); + bAUpdateQuerySent = true; + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: IPv4 update scheduled for "), _DH()); + _printRRDomain(pQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IPv4 address (%s)\n"), (pIPv4Address->m_IPAddress.toString().c_str())); + printedInfo = true; + ); + } + } + else + { + // Timed out! -> Delete + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Will remove IPv4 answer for "), _DH()); + _printRRDomain(pQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IPv4 address\n")); + printedInfo = true; + ); + if (1 == pQAnswer->m_IPv4Addresses.size()) + { + // NO IPv4 address left after this -> remove content flag + pQAnswer->m_QueryAnswerFlags &= ~static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address); + } + // Notify client + _executeQueryCallback(*pQuery, *pQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address), false); + expiredIPv4Addresses.push_back(pIPv4Address); + } + } // IPv4 flagged + } // for + // Finally remove expired IPv4 addresses + for (clsQuery::clsAnswer::clsIPAddressWithTTL* pIPv4Address : expiredIPv4Addresses) + { + pQAnswer->removeIPv4Address(pIPv4Address); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + // IPv6Address (from AAAA) + clsQuery::clsAnswer::clsIPAddressWithTTL::list expiredIPv6Addresses; + bool bAAAAUpdateQuerySent = false; + for (clsQuery::clsAnswer::clsIPAddressWithTTL::list::iterator itQAIPv6 = pQAnswer->m_IPv6Addresses.begin(); ((bResult) && (itQAIPv6 != pQAnswer->m_IPv6Addresses.end())); itQAIPv6++) + { + clsQuery::clsAnswer::clsIPAddressWithTTL* pIPv6Address = *itQAIPv6; + + if (pIPv6Address->m_TTL.flagged()) + { + if (!pIPv6Address->m_TTL.finalTimeoutLevel()) + { + // Needs update + if ((bAAAAUpdateQuerySent) || + ((bResult = _sendQuery(pQAnswer->m_HostDomain, DNS_RRTYPE_AAAA)))) + { + pIPv6Address->m_TTL.restart(); + bAAAAUpdateQuerySent = true; + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: IPv6 update scheduled for "), _DH()); + _printRRDomain(pQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IPv6 address (%s)\n"), (pIPv6Address->m_IPAddress.toString().c_str())); + printedInfo = true; + ); + } + } + else + { + // Timed out! -> Delete + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Will remove answer for "), _DH()); + _printRRDomain(pQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IPv6 address\n")); + printedInfo = true; + ); + if (1 == pQAnswer->m_IPv6Addresses.size()) + { + // NO IPv6 address left after this -> remove content flag + pQAnswer->m_QueryAnswerFlags &= ~static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address); + } + // Notify client + _executeQueryCallback(*pQuery, *pQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address), false); + expiredIPv6Addresses.push_back(pIPv6Address); + } + } // IPv6 flagged + // Finally remove expired IPv6 addresses + for (clsQuery::clsAnswer::clsIPAddressWithTTL* pIPv6Address : expiredIPv6Addresses) + { + pQAnswer->removeIPv6Address(pIPv6Address); + } + } // while +#endif + } + + // Finally remove expired answers + for (clsQuery::clsAnswer* pAnswer : expiredAnswers) + { + pQuery->removeAnswer(pAnswer); + } + } + } + DEBUG_EX_INFO(if (printedInfo) DEBUG_OUTPUT.printf_P(PSTR("\n"));); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: FAILED!\n"), _DH());); + return bResult; +} + + +/* + clsLEAmDNS2_Host::_replyMaskForHost + + Determines the relevant host answers for the given question. + - A question for the hostname (eg. esp8266.local) will result in an A/AAAA (eg. 192.168.2.129) reply. + - A question for the reverse IP address (eg. 192-168.2.120.inarpa.arpa) will result in an PTR_IPv4 (eg. esp8266.local) reply. + + In addition, a full name match (question domain == host domain) is marked. +*/ +uint32_t clsLEAMDNSHost::_replyMaskForHost(netif* pNetIf, + const clsLEAMDNSHost::clsRRHeader& p_RRHeader, + bool* p_pbFullNameMatch /*= 0*/) const +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _replyMaskForHost\n"));); + + uint32_t u32ReplyMask = 0; + (p_pbFullNameMatch ? *p_pbFullNameMatch = false : 0); + + if ((DNS_RRCLASS_IN == (p_RRHeader.m_Attributes.m_u16Class & (~0x8000))) || + (DNS_RRCLASS_ANY == (p_RRHeader.m_Attributes.m_u16Class & (~0x8000)))) + { + + if ((DNS_RRTYPE_PTR == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) + { + // PTR request +#ifdef MDNS_IPV4_SUPPORT + clsRRDomain reverseIPv4Domain; + if ((_getResponderIPAddress(pNetIf, enuIPProtocolType::V4).isSet()) && + (_buildDomainForReverseIPv4(_getResponderIPAddress(pNetIf, enuIPProtocolType::V4), reverseIPv4Domain)) && + (p_RRHeader.m_Domain == reverseIPv4Domain)) + { + // Reverse domain match + u32ReplyMask |= static_cast(enuContentFlag::PTR_IPv4); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + clsRRDomain reverseIPv6Domain; + if ((_getResponderIPAddress(pNetIf, enuIPProtocolType::V6).isSet()) && + (_buildDomainForReverseIPv6(_getResponderIPAddress(pNetIf, enuIPProtocolType::V6), reverseIPv6Domain)) && + (p_RRHeader.m_Domain == reverseIPv6Domain)) + { + // Reverse domain match + u32ReplyMask |= static_cast(enuContentFlag::PTR_IPv6); + } +#endif + } // Address qeuest + + clsRRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostName, hostDomain)) && + (p_RRHeader.m_Domain == hostDomain)) // Host domain match + { + (p_pbFullNameMatch ? (*p_pbFullNameMatch = true) : (0)); + +#ifdef MDNS_IPV4_SUPPORT + if ((DNS_RRTYPE_A == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) + { + // IPv4 address request + u32ReplyMask |= static_cast(enuContentFlag::A); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + if ((DNS_RRTYPE_AAAA == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) + { + // IPv6 address request + u32ReplyMask |= static_cast(enuContentFlag::AAAA); + } +#endif + } + } + else + { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _replyMaskForHost: INVALID RR-class (0x%04X)!\n"), p_RRHeader.m_Attributes.m_u16Class);); + } + DEBUG_EX_INFO(if (u32ReplyMask) DEBUG_OUTPUT.printf_P(PSTR("%s _replyMaskForHost: %s\n"), _DH(), _replyFlags2String(u32ReplyMask));); + return u32ReplyMask; +} + +/* + clsLEAmDNS2_Host::_replyMaskForService + + Determines the relevant service answers for the given question + - A PTR dns-sd service enum question (_services.dns-sd._udp.local) will result into an PTR_TYPE (eg. _http._tcp.local) answer + - A PTR service type question (eg. _http._tcp.local) will result into an PTR_NAME (eg. MyESP._http._tcp.local) answer + - A PTR service name question (eg. MyESP._http._tcp.local) will result into an PTR_NAME (eg. MyESP._http._tcp.local) answer + - A SRV service name question (eg. MyESP._http._tcp.local) will result into an SRV (eg. 5000 MyESP.local) answer + - A TXT service name question (eg. MyESP._http._tcp.local) will result into an TXT (eg. c#=1) answer + + In addition, a full name match (question domain == service instance domain) is marked. + +*/ +uint32_t clsLEAMDNSHost::_replyMaskForService(const clsLEAMDNSHost::clsRRHeader& p_RRHeader, + clsLEAMDNSHost::clsService& p_rService, + bool* p_pbFullNameMatch /*= 0*/) +{ + uint32_t u32ReplyMask = 0; + (p_pbFullNameMatch ? *p_pbFullNameMatch = false : 0); + + if ((DNS_RRCLASS_IN == (p_RRHeader.m_Attributes.m_u16Class & (~0x8000))) || + (DNS_RRCLASS_ANY == (p_RRHeader.m_Attributes.m_u16Class & (~0x8000)))) + { + clsRRDomain DNSSDDomain; + if ((_buildDomainForDNSSD(DNSSDDomain)) && // _services._dns-sd._udp.local + (p_RRHeader.m_Domain == DNSSDDomain) && + ((DNS_RRTYPE_PTR == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type))) + { + // Common service info requested + u32ReplyMask |= static_cast(enuContentFlag::PTR_TYPE); + } + + clsRRDomain serviceDomain; + if ((_buildDomainForService(p_rService, false, serviceDomain)) && // eg. _http._tcp.local + (p_RRHeader.m_Domain == serviceDomain) && + ((DNS_RRTYPE_PTR == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type))) + { + // Special service info requested + u32ReplyMask |= static_cast(enuContentFlag::PTR_NAME); + } + + if ((_buildDomainForService(p_rService, true, serviceDomain)) && // eg. MyESP._http._tcp.local + (p_RRHeader.m_Domain == serviceDomain)) + { + (p_pbFullNameMatch ? (*p_pbFullNameMatch = true) : (0)); + + if ((DNS_RRTYPE_SRV == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) + { + // Instance info SRV requested + u32ReplyMask |= static_cast(enuContentFlag::SRV); + } + if ((DNS_RRTYPE_TXT == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) + { + // Instance info TXT requested + u32ReplyMask |= static_cast(enuContentFlag::TXT); + } + } + } + else + { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _replyMaskForService: INVALID RR-class (0x%04X)!\n"), p_RRHeader.m_Attributes.m_u16Class);); + } + DEBUG_EX_INFO(if (u32ReplyMask) DEBUG_OUTPUT.printf_P(PSTR("%s _replyMaskForService(%s.%s.%s): %s\n"), _DH(), p_rService.m_pcInstanceName, p_rService.m_pcType, p_rService.m_pcProtocol, _replyFlags2String(u32ReplyMask));); + return u32ReplyMask; +} + + +} // namespace MDNSImplementation + + +} // namespace esp8266 + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp new file mode 100644 index 000000000..186d913a3 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp @@ -0,0 +1,328 @@ +/* + LEAmDNS2Host_Debug.h + + 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 "ESP8266mDNS.h" +#include "LEAmDNS2Host.h" +#include "LEAmDNS2_Priv.h" + +namespace esp8266 +{ + + +namespace experimental +{ + + +#ifdef DEBUG_ESP_PORT + +/* + clsLEAmDNS2_Host::_DH + +*/ +const char* clsLEAMDNSHost::_DH(const clsLEAMDNSHost::clsService* p_pService /*= 0*/) const +{ + static char acBuffer[16 + 64]; + + *acBuffer = 0; + sprintf_P(acBuffer, PSTR("[mDNS]")); + if (p_pService) + { + strcat_P(acBuffer, PSTR(">")); + strcat(acBuffer, _service2String(p_pService)); + } + return acBuffer; +} + +/* + clsLEAmDNS2_Host::_service2String + +*/ +const char* clsLEAMDNSHost::_service2String(const clsLEAMDNSHost::clsService* p_pService) const +{ + static char acBuffer[64]; + + *acBuffer = 0; + if (p_pService) + { + sprintf_P(acBuffer, PSTR("%s.%s%s.%s%s.local"), + (p_pService->m_pcInstanceName ? : "-"), + (p_pService->m_pcType ? ('_' == *(p_pService->m_pcType) ? "" : "_") : "-"), + (p_pService->m_pcType ? : "-"), + (p_pService->m_pcProtocol ? ('_' == *(p_pService->m_pcProtocol) ? "" : "_") : "-"), + (p_pService->m_pcProtocol ? : "-")); + } + return acBuffer; +} + +/* + clsLEAmDNS2_Host::_printRRDomain + +*/ +bool clsLEAMDNSHost::_printRRDomain(const clsLEAMDNSHost::clsRRDomain& 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; +} + +/* + clsLEAmDNS2_Host::_printRRAnswer + +*/ +bool clsLEAMDNSHost::_printRRAnswer(const clsLEAMDNSHost::clsRRAnswer& p_RRAnswer) const +{ + DEBUG_OUTPUT.printf_P(PSTR("%s RRAnswer: "), _DH()); + _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_IPV4_SUPPORT + case DNS_RRTYPE_A: + DEBUG_OUTPUT.printf_P(PSTR("A IP:%s"), ((const clsRRAnswerA*)&p_RRAnswer)->m_IPAddress.toString().c_str()); + break; +#endif + case DNS_RRTYPE_PTR: + DEBUG_OUTPUT.printf_P(PSTR("PTR ")); + _printRRDomain(((const clsRRAnswerPTR*)&p_RRAnswer)->m_PTRDomain); + break; + case DNS_RRTYPE_TXT: + { + size_t stTxtLength = ((const clsRRAnswerTXT*)&p_RRAnswer)->m_Txts.c_strLength(); + char* pTxts = new char[stTxtLength]; + if (pTxts) + { + ((/*const c_str()!!*/clsRRAnswerTXT*)&p_RRAnswer)->m_Txts.c_str(pTxts); + DEBUG_OUTPUT.printf_P(PSTR("TXT(%u) %s"), stTxtLength, pTxts); + delete[] pTxts; + } + break; + } +#ifdef MDNS_IPV6_SUPPORT + case DNS_RRTYPE_AAAA: + DEBUG_OUTPUT.printf_P(PSTR("AAAA IP:%s"), ((clsRRAnswerAAAA*&)p_RRAnswer)->m_IPAddress.toString().c_str()); + break; +#endif + case DNS_RRTYPE_SRV: + DEBUG_OUTPUT.printf_P(PSTR("SRV Port:%u "), ((const clsRRAnswerSRV*)&p_RRAnswer)->m_u16Port); + _printRRDomain(((const clsRRAnswerSRV*)&p_RRAnswer)->m_SRVDomain); + break; + default: + DEBUG_OUTPUT.printf_P(PSTR("generic ")); + break; + } + DEBUG_OUTPUT.printf_P(PSTR("\n")); + + return true; +} + +/* + clsLEAmDNS2_Host::_RRType2Name + +*/ +const char* clsLEAMDNSHost::_RRType2Name(uint16_t p_u16RRType) const +{ + static char acRRName[16]; + *acRRName = 0; + + switch (p_u16RRType & (~0x8000)) // Topmost bit might carry 'cache flush' flag + { +#ifdef MDNS_IPV4_SUPPORT + case DNS_RRTYPE_A: strcpy_P(acRRName, PSTR("A")); break; +#endif + case DNS_RRTYPE_PTR: strcpy_P(acRRName, PSTR("PTR")); break; + case DNS_RRTYPE_TXT: strcpy_P(acRRName, PSTR("TXT")); break; +#ifdef MDNS_IPV6_SUPPORT + case DNS_RRTYPE_AAAA: strcpy_P(acRRName, PSTR("AAAA")); break; +#endif + case DNS_RRTYPE_SRV: strcpy_P(acRRName, PSTR("SRV")); break; + case clsConsts::u8DNS_RRTYPE_NSEC: strcpy_P(acRRName, PSTR("NSEC")); break; + case DNS_RRTYPE_ANY: strcpy_P(acRRName, PSTR("ANY")); break; + default: sprintf_P(acRRName, PSTR("Unknown(0x%04X"), p_u16RRType); // MAX 15! + } + return acRRName; +} + +/* + clsLEAmDNS2_Host::_RRClass2String + +*/ +const char* clsLEAMDNSHost::_RRClass2String(uint16_t p_u16RRClass, + bool p_bIsQuery) const +{ + static char acClassString[16]; + *acClassString = 0; + + if (p_u16RRClass & 0x0001) + { + strcat_P(acClassString, PSTR("IN ")); // 3 + } + if (p_u16RRClass & 0x8000) + { + strcat_P(acClassString, (p_bIsQuery ? PSTR("UNICAST ") : PSTR("FLUSH "))); // 8/6 + } + + return acClassString; // 11 +} + +/* + clsLEAmDNS2_Host::_replyFlags2String + +*/ +const char* clsLEAMDNSHost::_replyFlags2String(uint32_t p_u32ReplyFlags) const +{ + static char acFlagsString[64]; + + *acFlagsString = 0; + if (p_u32ReplyFlags & static_cast(enuContentFlag::A)) + { + strcat_P(acFlagsString, PSTR("A ")); // 2 + } + if (p_u32ReplyFlags & static_cast(enuContentFlag::PTR_IPv4)) + { + strcat_P(acFlagsString, PSTR("PTR_IPv4 ")); // 7 + } + if (p_u32ReplyFlags & static_cast(enuContentFlag::PTR_IPv6)) + { + strcat_P(acFlagsString, PSTR("PTR_IPv6 ")); // 7 + } + if (p_u32ReplyFlags & static_cast(enuContentFlag::AAAA)) + { + strcat_P(acFlagsString, PSTR("AAAA ")); // 5 + } + if (p_u32ReplyFlags & static_cast(enuContentFlag::PTR_TYPE)) + { + strcat_P(acFlagsString, PSTR("PTR_TYPE ")); // 9 + } + if (p_u32ReplyFlags & static_cast(enuContentFlag::PTR_NAME)) + { + strcat_P(acFlagsString, PSTR("PTR_NAME ")); // 9 + } + if (p_u32ReplyFlags & static_cast(enuContentFlag::TXT)) + { + strcat_P(acFlagsString, PSTR("TXT ")); // 4 + } + if (p_u32ReplyFlags & static_cast(enuContentFlag::SRV)) + { + strcat_P(acFlagsString, PSTR("SRV ")); // 4 + } + if (p_u32ReplyFlags & static_cast(enuContentFlag::NSEC)) + { + strcat_P(acFlagsString, PSTR("NSEC ")); // 5 + } + + if (0 == p_u32ReplyFlags) + { + strcpy_P(acFlagsString, PSTR("none")); + } + + // Remove trailing spaces + while ((*acFlagsString) && + (' ' == acFlagsString[strlen(acFlagsString) - 1])) + { + acFlagsString[strlen(acFlagsString) - 1] = 0; + } + + return acFlagsString; // 63 +} + +/* + clsLEAmDNS2_Host::_NSECBitmap2String + +*/ +const char* clsLEAMDNSHost::_NSECBitmap2String(const clsNSECBitmap* p_pNSECBitmap) const +{ + static char acFlagsString[32]; + + *acFlagsString = 0; +#ifdef MDNS_IPV4_SUPPORT + if (p_pNSECBitmap->getBit(DNS_RRTYPE_A)) + { + strcat_P(acFlagsString, PSTR("A ")); // 2 + } +#endif + if (p_pNSECBitmap->getBit(DNS_RRTYPE_PTR)) + { + strcat_P(acFlagsString, PSTR("PTR ")); // 4 + } +#ifdef MDNS_IPV6_SUPPORT + if (p_pNSECBitmap->getBit(DNS_RRTYPE_AAAA)) + { + strcat_P(acFlagsString, PSTR("AAAA ")); // 5 + } +#endif + if (p_pNSECBitmap->getBit(DNS_RRTYPE_TXT)) + { + strcat_P(acFlagsString, PSTR("TXT ")); // 4 + } + if (p_pNSECBitmap->getBit(DNS_RRTYPE_SRV)) + { + strcat_P(acFlagsString, PSTR("SRV ")); // 4 + } + if (p_pNSECBitmap->getBit(clsConsts::u8DNS_RRTYPE_NSEC)) + { + strcat_P(acFlagsString, PSTR("NSEC ")); // 5 + } + + if (!*acFlagsString) + { + strcpy_P(acFlagsString, PSTR("none")); + } + + return acFlagsString; // 31 +} + +#endif // DEBUG_ESP_PORT + + +} // namespace MDNSImplementation + + +} // namespace esp8266 + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp new file mode 100644 index 000000000..dcf3c4b76 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -0,0 +1,3185 @@ +/* + LEAmDNS2Host_Structs.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 + +#include "ESP8266mDNS.h" +#include "LEAmDNS2Host.h" +#include "LEAmDNS2_Priv.h" + +namespace esp8266 +{ + + +namespace experimental +{ + + +/** + Internal CLASSES & STRUCTS +*/ + +/** + clsLEAMDNSHost::clsServiceTxt + + One MDNS TXT item. + m_pcValue may be '\0'. + Objects can be chained together (list). + A 'm_bTemp' flag differentiates between static and dynamic items. + Output as byte array 'c#=1' is supported. +*/ + +/* + clsLEAMDNSHost::clsServiceTxt::clsServiceTxt constructor + +*/ +clsLEAMDNSHost::clsServiceTxt::clsServiceTxt(const char* p_pcKey /*= 0*/, + const char* p_pcValue /*= 0*/, + bool p_bTemp /*= false*/) + : m_pcKey(0), + m_pcValue(0), + m_bTemp(p_bTemp) +{ + setKey(p_pcKey); + setValue(p_pcValue); +} + +/* + clsLEAMDNSHost::clsServiceTxt::clsServiceTxt copy-constructor + +*/ +clsLEAMDNSHost::clsServiceTxt::clsServiceTxt(const clsLEAMDNSHost::clsServiceTxt& p_Other) + : m_pcKey(0), + m_pcValue(0), + m_bTemp(false) +{ + operator=(p_Other); +} + +/* + clsLEAMDNSHost::clsServiceTxt::~stcServiceTxt destructor + +*/ +clsLEAMDNSHost::clsServiceTxt::~clsServiceTxt(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsServiceTxt::operator= + +*/ +clsLEAMDNSHost::clsServiceTxt& clsLEAMDNSHost::clsServiceTxt::operator=(const clsLEAMDNSHost::clsServiceTxt& p_Other) +{ + if (&p_Other != this) + { + clear(); + set(p_Other.m_pcKey, p_Other.m_pcValue, p_Other.m_bTemp); + } + return *this; +} + +/* + clsLEAMDNSHost::clsServiceTxt::clear + +*/ +bool clsLEAMDNSHost::clsServiceTxt::clear(void) +{ + releaseKey(); + releaseValue(); + return true; +} + +/* + clsLEAMDNSHost::clsServiceTxt::allocKey + +*/ +char* clsLEAMDNSHost::clsServiceTxt::allocKey(size_t p_stLength) +{ + releaseKey(); + if (p_stLength) + { + m_pcKey = new char[p_stLength + 1]; + } + return m_pcKey; +} + +/* + clsLEAMDNSHost::clsServiceTxt::setKey + +*/ +bool clsLEAMDNSHost::clsServiceTxt::setKey(const char* p_pcKey, + size_t p_stLength) +{ + bool bResult = false; + + releaseKey(); + if (p_stLength) + { + if (allocKey(p_stLength)) + { + strncpy(m_pcKey, p_pcKey, p_stLength + 1); + m_pcKey[p_stLength] = 0; + bResult = true; + } + } + return bResult; +} + +/* + clsLEAMDNSHost::clsServiceTxt::setKey + +*/ +bool clsLEAMDNSHost::clsServiceTxt::setKey(const char* p_pcKey) +{ + return setKey(p_pcKey, (p_pcKey ? strlen(p_pcKey) : 0)); +} + +/* + clsLEAMDNSHost::clsServiceTxt::releaseKey + +*/ +bool clsLEAMDNSHost::clsServiceTxt::releaseKey(void) +{ + if (m_pcKey) + { + delete[] m_pcKey; + m_pcKey = 0; + } + return true; +} + +/* + clsLEAMDNSHost::clsServiceTxt::allocValue + +*/ +char* clsLEAMDNSHost::clsServiceTxt::allocValue(size_t p_stLength) +{ + releaseValue(); + if (p_stLength) + { + m_pcValue = new char[p_stLength + 1]; + } + return m_pcValue; +} + +/* + clsLEAMDNSHost::clsServiceTxt::setValue + +*/ +bool clsLEAMDNSHost::clsServiceTxt::setValue(const char* p_pcValue, + size_t p_stLength) +{ + bool bResult = false; + + releaseValue(); + if (p_stLength) + { + if (allocValue(p_stLength)) + { + strncpy(m_pcValue, p_pcValue, p_stLength + 1); + m_pcValue[p_stLength] = 0; + bResult = true; + } + } + else + { + // No value -> also OK + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsServiceTxt::setValue + +*/ +bool clsLEAMDNSHost::clsServiceTxt::setValue(const char* p_pcValue) +{ + return setValue(p_pcValue, (p_pcValue ? strlen(p_pcValue) : 0)); +} + +/* + clsLEAMDNSHost::clsServiceTxt::releaseValue + +*/ +bool clsLEAMDNSHost::clsServiceTxt::releaseValue(void) +{ + if (m_pcValue) + { + delete[] m_pcValue; + m_pcValue = 0; + } + return true; +} + +/* + clsLEAMDNSHost::clsServiceTxt::set + +*/ +bool clsLEAMDNSHost::clsServiceTxt::set(const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp /*= false*/) +{ + m_bTemp = p_bTemp; + return ((setKey(p_pcKey)) && + (setValue(p_pcValue))); +} + +/* + clsLEAMDNSHost::clsServiceTxt::update + +*/ +bool clsLEAMDNSHost::clsServiceTxt::update(const char* p_pcValue) +{ + return setValue(p_pcValue); +} + +/* + clsLEAMDNSHost::clsServiceTxt::length + + length of eg. 'c#=1' without any closing '\0' + +*/ +size_t clsLEAMDNSHost::clsServiceTxt::length(void) const +{ + size_t stLength = 0; + if (m_pcKey) + { + stLength += strlen(m_pcKey); // Key + stLength += 1; // '=' + stLength += (m_pcValue ? strlen(m_pcValue) : 0); // Value + } + return stLength; +} + + +/** + clsLEAMDNSHost::clsServiceTxts + + A list of zero or more MDNS TXT (stcServiceTxt) items. + Dynamic TXT items can be removed by 'removeTempTxts'. + A TXT item can be looked up by its 'key' member. + Export as ';'-separated byte array is supported. + Export as 'length byte coded' byte array is supported. + Comparison ((all A TXT items in B and equal) AND (all B TXT items in A and equal)) is supported. + +*/ + +/* + clsLEAMDNSHost::clsServiceTxts::clsServiceTxts contructor + +*/ +clsLEAMDNSHost::clsServiceTxts::clsServiceTxts(void) + : m_pcCache(0) +{ +} + +/* + clsLEAMDNSHost::clsServiceTxts::clsServiceTxts copy-constructor + +*/ +clsLEAMDNSHost::clsServiceTxts::clsServiceTxts(const clsServiceTxts& p_Other) + : m_pcCache(0) +{ + operator=(p_Other); +} + +/* + clsLEAMDNSHost::clsServiceTxts::~stcServiceTxts destructor + +*/ +clsLEAMDNSHost::clsServiceTxts::~clsServiceTxts(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsServiceTxts::operator= + +*/ +clsLEAMDNSHost::clsServiceTxts& clsLEAMDNSHost::clsServiceTxts::operator=(const clsServiceTxts& p_Other) +{ + if (this != &p_Other) + { + clear(); + + for (const clsServiceTxt* pOtherTxt : p_Other.m_Txts) + { + add(new clsServiceTxt(*pOtherTxt)); + } + } + return *this; +} + +/* + clsLEAMDNSHost::clsServiceTxts::clear + +*/ +bool clsLEAMDNSHost::clsServiceTxts::clear(void) +{ + for (clsServiceTxt* pTxt : m_Txts) + { + delete pTxt; + } + m_Txts.clear(); + + return clearCache(); +} + +/* + clsLEAMDNSHost::clsServiceTxts::clearCache + +*/ +bool clsLEAMDNSHost::clsServiceTxts::clearCache(void) +{ + if (m_pcCache) + { + delete[] m_pcCache; + m_pcCache = 0; + } + return true; +} + +/* + clsLEAMDNSHost::clsServiceTxts::add + +*/ +bool clsLEAMDNSHost::clsServiceTxts::add(clsLEAMDNSHost::clsServiceTxt* p_pTxt) +{ + bool bResult = false; + + if (p_pTxt) + { + m_Txts.push_back(p_pTxt); + bResult = true; + } + return ((clearCache()) && + (bResult)); +} + +/* + clsLEAMDNSHost::clsServiceTxts::remove + +*/ +bool clsLEAMDNSHost::clsServiceTxts::remove(clsServiceTxt* p_pTxt) +{ + bool bResult = false; + + clsServiceTxt::list::iterator it(p_pTxt + ? std::find(m_Txts.begin(), m_Txts.end(), p_pTxt) + : m_Txts.end()); + if (m_Txts.end() != it) + { + m_Txts.erase(it); + delete p_pTxt; + + bResult = true; + } + return ((clearCache()) && + (bResult)); +} + +/* + clsLEAMDNSHost::clsServiceTxts::count + +*/ +size_t clsLEAMDNSHost::clsServiceTxts::count(void) const +{ + size_t stResult = m_Txts.size(); + return stResult; +} + +/* + clsLEAMDNSHost::clsServiceTxts::removeTempTxts + +*/ +bool clsLEAMDNSHost::clsServiceTxts::removeTempTxts(void) +{ + bool bResult = true; + + // Delete content + clsServiceTxt::list tempTxts; + for (clsServiceTxt* pTxt : m_Txts) + { + if (pTxt->m_bTemp) + { + tempTxts.push_back(pTxt); + delete pTxt; + } + } + // Remove objects from list + for (clsServiceTxt* pTempTxt : tempTxts) + { + m_Txts.remove(pTempTxt); + } + return ((clearCache()) && + (bResult)); +} + +/* + clsLEAMDNSHost::clsServiceTxts::find + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsServiceTxts::find(const char* p_pcKey) +{ + clsServiceTxt* pResult = 0; + + for (clsServiceTxt* pTxt : m_Txts) + { + if ((p_pcKey) && + (0 == strcmp(pTxt->m_pcKey, p_pcKey))) + { + pResult = pTxt; + break; + } + } + return pResult; +} + +/* + clsLEAMDNSHost::clsServiceTxts::find (const) + +*/ +const clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsServiceTxts::find(const char* p_pcKey) const +{ + const clsServiceTxt* pResult = 0; + + for (const clsServiceTxt* pTxt : m_Txts) + { + if ((p_pcKey) && + (0 == strcmp(pTxt->m_pcKey, p_pcKey))) + { + pResult = pTxt; + break; + } + } + return pResult; +} + +/* + clsLEAMDNSHost::clsServiceTxts::find + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsServiceTxts::find(const clsServiceTxt* p_pTxt) +{ + clsServiceTxt* pResult = 0; + + for (clsServiceTxt* pTxt : m_Txts) + { + if (p_pTxt == pTxt) + { + pResult = pTxt; + break; + } + } + return pResult; +} + +/* + clsLEAMDNSHost::clsServiceTxts::length + +*/ +size_t clsLEAMDNSHost::clsServiceTxts::length(void) const +{ + size_t szLength = 0; + + for (clsServiceTxt* pTxt : m_Txts) + { + szLength += 1; // Length byte + szLength += pTxt->length(); // Text + } + return szLength; +} + +/* + clsLEAMDNSHost::clsServiceTxts::c_strLength + + (incl. closing '\0'). Length bytes place is used for delimiting ';' and closing '\0' + +*/ +size_t clsLEAMDNSHost::clsServiceTxts::c_strLength(void) const +{ + return length(); +} + +/* + clsLEAMDNSHost::clsServiceTxts::c_str + +*/ +bool clsLEAMDNSHost::clsServiceTxts::c_str(char* p_pcBuffer) +{ + bool bResult = false; + + if (p_pcBuffer) + { + bResult = true; + + char* pcCursor = p_pcBuffer; + *pcCursor = 0; + for (const clsServiceTxt* pTxt : m_Txts) + { + size_t stLength; + if ((bResult = (0 != (stLength = (pTxt->m_pcKey ? strlen(pTxt->m_pcKey) : 0))))) + { + if (pcCursor != p_pcBuffer) + { + *pcCursor++ = ';'; + } + strcpy(pcCursor, pTxt->m_pcKey); + pcCursor += stLength; + *pcCursor++ = '='; + if ((stLength = (pTxt->m_pcValue ? strlen(pTxt->m_pcValue) : 0))) + { + strcpy(pcCursor, pTxt->m_pcValue); + pcCursor += stLength; + } + } + else + { + break; + } + } + } + return bResult; +} + +/* + clsLEAMDNSHost::clsServiceTxts::c_str + +*/ +const char* clsLEAMDNSHost::clsServiceTxts::c_str(void) const +{ + + if ((!m_pcCache) && + (m_Txts.size()) && + ((((clsServiceTxts*)this)->m_pcCache = new char[c_strLength()]))) // TRANSPARENT caching + { + ((clsServiceTxts*)this)->c_str(m_pcCache); + } + return m_pcCache; +} + +/* + clsLEAMDNSHost::clsServiceTxts::bufferLength + + (incl. closing '\0'). + +*/ +size_t clsLEAMDNSHost::clsServiceTxts::bufferLength(void) const +{ + return (length() + 1); +} + +/* + clsLEAMDNSHost::clsServiceTxts::buffer + +*/ +bool clsLEAMDNSHost::clsServiceTxts::buffer(char* p_pcBuffer) +{ + bool bResult = false; + + if (p_pcBuffer) + { + bResult = true; + + *p_pcBuffer = 0; + for (const clsServiceTxt* pTxt : m_Txts) + { + *(unsigned char*)p_pcBuffer++ = pTxt->length(); + size_t stLength; + if ((bResult = (0 != (stLength = (pTxt->m_pcKey ? strlen(pTxt->m_pcKey) : 0))))) + { + strcpy(p_pcBuffer, pTxt->m_pcKey); + p_pcBuffer += stLength; + *p_pcBuffer++ = '='; + if ((stLength = (pTxt->m_pcValue ? strlen(pTxt->m_pcValue) : 0))) + { + strcpy(p_pcBuffer, pTxt->m_pcValue); + p_pcBuffer += stLength; + } + } + else + { + break; + } + } + } + return bResult; +} + +/* + clsLEAMDNSHost::clsServiceTxts::compare + +*/ +bool clsLEAMDNSHost::clsServiceTxts::compare(const clsLEAMDNSHost::clsServiceTxts& p_Other) const +{ + bool bResult = false; + + if ((bResult = (length() == p_Other.length()))) + { + // Compare A->B + for (const clsServiceTxt* pTxt : m_Txts) + { + const clsServiceTxt* pOtherTxt = p_Other.find(pTxt->m_pcKey); + if (!((bResult = ((pOtherTxt) && + (pTxt->m_pcValue) && + (pOtherTxt->m_pcValue) && + (0 == strcmp(pTxt->m_pcValue, pOtherTxt->m_pcValue)))))) + { + break; + } + } + // Compare B->A + for (const clsServiceTxt* pOtherTxt : p_Other.m_Txts) + { + const clsServiceTxt* pTxt = find(pOtherTxt->m_pcKey); + if (!((bResult = ((pTxt) && + (pOtherTxt->m_pcValue) && + (pTxt->m_pcValue) && + (0 == strcmp(pOtherTxt->m_pcValue, pTxt->m_pcValue)))))) + { + break; + } + } + } + return bResult; +} + +/* + clsLEAMDNSHost::clsServiceTxts::operator== + +*/ +bool clsLEAMDNSHost::clsServiceTxts::operator==(const clsServiceTxts& p_Other) const +{ + return compare(p_Other); +} + +/* + clsLEAMDNSHost::clsServiceTxts::operator!= + +*/ +bool clsLEAMDNSHost::clsServiceTxts::operator!=(const clsServiceTxts& p_Other) const +{ + return !compare(p_Other); +} + + +/** + clsLEAMDNSHost::clsProbeInformation_Base + + Probing status information for a host or service domain + +*/ + +/* + clsLEAMDNSHost::clsProbeInformation_Base::clsProbeInformation_Base constructor +*/ +clsLEAMDNSHost::clsProbeInformation_Base::clsProbeInformation_Base(void) + : m_ProbingStatus(enuProbingStatus::WaitingForData), + m_u32SentCount(0), + m_Timeout(std::numeric_limits::max()), + m_bConflict(false), + m_bTiebreakNeeded(false) +{ +} + +/* + clsLEAMDNSHost::clsProbeInformation_Base::clear +*/ +bool clsLEAMDNSHost::clsProbeInformation_Base::clear(void) +{ + m_ProbingStatus = enuProbingStatus::WaitingForData; + m_u32SentCount = 0; + m_Timeout.reset(std::numeric_limits::max()); + m_bConflict = false; + m_bTiebreakNeeded = false; + + return true; +} + + +/** + clsLEAMDNSHost::clsProbeInformation_Host + + Probing status information for a host or service domain + +*/ + +/* + clsLEAMDNSHost::clsProbeInformation::clsProbeInformation constructor +*/ +clsLEAMDNSHost::clsProbeInformation::clsProbeInformation(void) + : m_fnProbeResultCallback(0) +{ +} + +/* + clsLEAMDNSHost::clsProbeInformation::clear +*/ +bool clsLEAMDNSHost::clsProbeInformation::clear(bool p_bClearUserdata /*= false*/) +{ + if (p_bClearUserdata) + { + m_fnProbeResultCallback = 0; + } + return clsProbeInformation_Base::clear(); +} + + +/** + clsLEAMDNSHost::clsService::clsProbeInformation + + Probing status information for a host or service domain + +*/ + +/* + clsLEAMDNSHost::clsService::clsProbeInformation::clsProbeInformation constructor +*/ +clsLEAMDNSHost::clsService::clsProbeInformation::clsProbeInformation(void) + : m_fnProbeResultCallback(0) +{ +} + +/* + clsLEAMDNSHost::clsService::clsProbeInformation::clear +*/ +bool clsLEAMDNSHost::clsService::clsProbeInformation::clear(bool p_bClearUserdata /*= false*/) +{ + if (p_bClearUserdata) + { + m_fnProbeResultCallback = 0; + } + return clsProbeInformation_Base::clear(); +} + + +/** + clsLEAMDNSHost::clsService + + A MDNS service object (to be announced by the MDNS responder) + The service instance may be '\0'; in this case the hostname is used + and the flag m_bAutoName is set. If the hostname changes, all 'auto- + named' services are renamed also. + m_u8Replymask is used while preparing a response to a MDNS query. It is + resetted in '_sendMDNSMessage' afterwards. +*/ + +/* + clsLEAMDNSHost::clsService::clsService constructor + +*/ +clsLEAMDNSHost::clsService::clsService(void) + : m_pcInstanceName(0), + m_bAutoName(false), + m_pcType(0), + m_pcProtocol(0), + m_u16Port(0), + m_u32ReplyMask(0), + m_fnTxtCallback(0) +{ +} + +/* + clsLEAMDNSHost::clsService::~clsService destructor + +*/ +clsLEAMDNSHost::clsService::~clsService(void) +{ + _releaseInstanceName(); + _releaseType(); + _releaseProtocol(); +} + +/* + clsLEAMDNSHost::clsService::setInstanceName + +*/ +bool clsLEAMDNSHost::clsService::setInstanceName(const char* p_pcInstanceName) +{ + bool bResult = false; + + _releaseInstanceName(); + size_t stLength = (p_pcInstanceName ? strlen(p_pcInstanceName) : 0); + if ((stLength) && + (stLength <= clsConsts::stDomainLabelMaxLength)) + { + if ((bResult = (0 != (m_pcInstanceName = new char[stLength + 1])))) + { + strcpy(m_pcInstanceName, p_pcInstanceName); + _resetProbeStatus(); + } + } + return bResult; +} + +/* + clsLEAMDNSHost::clsService::indexInstanceName + +*/ +bool clsLEAMDNSHost::clsService::indexInstanceName(void) +{ + bool bResult = false; + + if ((bResult = setInstanceName(clsLEAMDNSHost::indexDomainName(m_pcInstanceName, "#", 0)))) + { + _resetProbeStatus(); + } + return bResult; +} + +/* + clsLEAMDNSHost::clsService::instanceName + +*/ +const char* clsLEAMDNSHost::clsService::instanceName(void) const +{ + return m_pcInstanceName; +} + +/* + clsLEAMDNSHost::clsService::_releaseInstanceName + +*/ +bool clsLEAMDNSHost::clsService::_releaseInstanceName(void) +{ + if (m_pcInstanceName) + { + delete[] m_pcInstanceName; + m_pcInstanceName = 0; + } + return true; +} + +/* + clsLEAMDNSHost::clsService::setType + +*/ +bool clsLEAMDNSHost::clsService::setType(const char* p_pcType) +{ + bool bResult = false; + + _releaseType(); + size_t stLength = (p_pcType ? strlen(p_pcType) : 0); + if ((stLength) && + (stLength <= clsConsts::stServiceTypeMaxLength)) + { + if ((bResult = (0 != (m_pcType = new char[stLength + 1])))) + { + strcpy(m_pcType, p_pcType); + _resetProbeStatus(); + } + } + return bResult; +} + +/* + clsLEAMDNSHost::clsService::type + +*/ +const char* clsLEAMDNSHost::clsService::type(void) const +{ + return m_pcType; +} + +/* + clsLEAMDNSHost::clsService::_releaseType + +*/ +bool clsLEAMDNSHost::clsService::_releaseType(void) +{ + if (m_pcType) + { + delete[] m_pcType; + m_pcType = 0; + } + return true; +} + +/* + clsLEAMDNSHost::clsService::setProtocol + +*/ +bool clsLEAMDNSHost::clsService::setProtocol(const char* p_pcProtocol) +{ + bool bResult = false; + + _releaseProtocol(); + size_t stLength = (p_pcProtocol ? strlen(p_pcProtocol) : 0); + if ((stLength) && + (stLength <= clsConsts::stServiceProtocolMaxLength)) + { + if ((bResult = (0 != (m_pcProtocol = new char[stLength + 1])))) + { + strcpy(m_pcProtocol, p_pcProtocol); + _resetProbeStatus(); + } + } + return bResult; +} + +/* + clsLEAMDNSHost::clsService::protocol + +*/ +const char* clsLEAMDNSHost::clsService::protocol(void) const +{ + return m_pcProtocol; +} + +/* + clsLEAMDNSHost::clsService::_releaseProtocol + +*/ +bool clsLEAMDNSHost::clsService::_releaseProtocol(void) +{ + if (m_pcProtocol) + { + delete[] m_pcProtocol; + m_pcProtocol = 0; + } + return true; +} + +/* + clsLEAMDNSHost::clsService::setPort + +*/ +bool clsLEAMDNSHost::clsService::setPort(uint16_t p_u16Port) +{ + bool bResult = false; + + if ((bResult = (0 != p_u16Port))) + { + m_u16Port = p_u16Port; + + _resetProbeStatus(); + } + return bResult; +} + +/* + clsLEAMDNSHost::clsService::port + +*/ +uint16_t clsLEAMDNSHost::clsService::port(void) const +{ + return m_u16Port; +} + +/* + clsLEAMDNSHost::clsService::setProbeResultCallback + +*/ +bool clsLEAMDNSHost::clsService::setProbeResultCallback(fnProbeResultCallback p_fnProbeResultCallback) +{ + m_ProbeInformation.m_fnProbeResultCallback = p_fnProbeResultCallback; + return true; +} + +/* + clsLEAMDNSHost::clsService::probeStatus + +*/ +bool clsLEAMDNSHost::clsService::probeStatus(void) const +{ + return (clsProbeInformation_Base::enuProbingStatus::DoneFinally == m_ProbeInformation.m_ProbingStatus); +} + +/* + clsLEAMDNSHost::clsService::_resetProbeStatus + +*/ +void clsLEAMDNSHost::clsService::_resetProbeStatus(void) +{ + m_ProbeInformation.clear(false); + m_ProbeInformation.m_ProbingStatus = clsProbeInformation_Base::enuProbingStatus::ReadyToStart; +} + +/* + clsLEAMDNSHost::clsService::addServiceTxt (const char*) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, + const char* p_pcValue) +{ + return _addServiceTxt(p_pcKey, p_pcValue, false); +} + +/* + clsLEAMDNSHost::clsService::addServiceTxt (uint32_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, + uint32_t p_u32Value) +{ + return _addServiceTxt(p_pcKey, p_u32Value, false); +} + +/* + clsLEAMDNSHost::clsService::addServiceTxt (uint16_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, + uint16_t p_u16Value) +{ + return _addServiceTxt(p_pcKey, p_u16Value, false); +} + +/* + clsLEAMDNSHost::clsService::addServiceTxt (uint8_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, + uint8_t p_u8Value) +{ + return _addServiceTxt(p_pcKey, p_u8Value, false); +} + +/* + clsLEAMDNSHost::clsService::addServiceTxt (int32_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, + int32_t p_i32Value) +{ + return _addServiceTxt(p_pcKey, p_i32Value, false); +} + +/* + clsLEAMDNSHost::clsService::addServiceTxt (int16_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, + int16_t p_i16Value) +{ + return _addServiceTxt(p_pcKey, p_i16Value, false); +} + +/* + clsLEAMDNSHost::clsService::addServiceTxt (int8_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, + int8_t p_i8Value) +{ + return _addServiceTxt(p_pcKey, p_i8Value, false); +} + +/* + clsLEAMDNSHost::clsService::addDynamicServiceTxt (const char*) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, + const char* p_pcValue) +{ + return _addServiceTxt(p_pcKey, p_pcValue, true); +} + +/* + clsLEAMDNSHost::clsService::addDynamicServiceTxt (uint32_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, + uint32_t p_u32Value) +{ + return _addServiceTxt(p_pcKey, p_u32Value, true); +} + +/* + clsLEAMDNSHost::clsService::addDynamicServiceTxt (uint16_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, + uint16_t p_u16Value) +{ + return _addServiceTxt(p_pcKey, p_u16Value, true); +} + +/* + clsLEAMDNSHost::clsService::addDynamicServiceTxt (uint8_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, + uint8_t p_u8Value) +{ + return _addServiceTxt(p_pcKey, p_u8Value, true); +} + +/* + clsLEAMDNSHost::clsService::addDynamicServiceTxt (int32_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, + int32_t p_i32Value) +{ + return _addServiceTxt(p_pcKey, p_i32Value, true); +} + +/* + clsLEAMDNSHost::clsService::addDynamicServiceTxt (int16_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, + int16_t p_i16Value) +{ + return _addServiceTxt(p_pcKey, p_i16Value, true); +} + +/* + clsLEAMDNSHost::clsService::addDynamicServiceTxt (int8_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, + int8_t p_i8Value) +{ + return _addServiceTxt(p_pcKey, p_i8Value, true); +} + +/* + clsLEAMDNSHost::clsService::setDynamicServiceTxtCallback + +*/ +bool clsLEAMDNSHost::clsService::setDynamicServiceTxtCallback(fnDynamicServiceTxtCallback p_fnCallback) +{ + m_fnTxtCallback = p_fnCallback; + return true; +} + +/* + clsLEAMDNSHost::clsService::_addServiceTxt (const char*) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp) +{ + clsServiceTxt* pServiceTxt = 0; + + if ((p_pcKey) && + (*p_pcKey)) + { + if ((pServiceTxt = m_Txts.find(p_pcKey))) + { + // Change existing TXT + if (clsConsts::stServiceTxtMaxLength > (m_Txts.length() - + (pServiceTxt->m_pcValue ? strlen(pServiceTxt->m_pcValue) : 0) + + (p_pcValue ? strlen(p_pcValue) : 0))) + { + // Enough space left for changed content + if (!pServiceTxt->update(p_pcValue)) + { + // FAILED to update + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[LEAmDNS2_Host] clsService::_addServiceTxt: FAILED to update TXT item '%s'!\n"), p_pcKey)); + pServiceTxt = 0; + } + } + else + { + // NOT enough space for changed TXT content + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[LEAmDNS2_Host] clsService::_addServiceTxt: FAILED to change TXT item '%s' (too large)!\n"), p_pcKey)); + pServiceTxt = 0; + } + } + else + { + // Create new TXT + if (clsConsts::stServiceTxtMaxLength > (m_Txts.length() + + 1 + // Length byte + (p_pcKey ? strlen(p_pcKey) : 0) + + 1 + // '=' + (p_pcValue ? strlen(p_pcValue) : 0))) + { + if (!(((pServiceTxt = new clsServiceTxt)) && + (pServiceTxt->set(p_pcKey, p_pcValue, p_bTemp)) && + (m_Txts.add(pServiceTxt)))) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[LEAmDNS2_Host] clsService::_addServiceTxt: FAILED to add TXT item '%s'!\n"), p_pcKey)); + if (pServiceTxt) + { + delete pServiceTxt; + pServiceTxt = 0; + } + } + } + else + { + // NOT enough space for added TXT item + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[LEAmDNS2_Host] clsService::_addServiceTxt: FAILED to add TXT item '%s' (too large)!\n"), p_pcKey)); + pServiceTxt = 0; + } + } + } + return pServiceTxt; +} + +/* + clsLEAMDNSHost::clsService::_addServiceTxt (uint32_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, + uint32_t p_u32Value, + bool p_bTemp) +{ + char acValueBuffer[16]; // 32-bit max 10 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%u", p_u32Value); + + return _addServiceTxt(p_pcKey, acValueBuffer, p_bTemp); +} + +/* + clsLEAMDNSHost::clsService::_addServiceTxt (int32_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, + int32_t p_i32Value, + bool p_bTemp) +{ + char acValueBuffer[16]; // 32-bit max 10 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%i", p_i32Value); + + return _addServiceTxt(p_pcKey, acValueBuffer, p_bTemp); +} + +/** + clsLEAMDNSHost::clsMsgHeader + + A MDNS message header. + +*/ + +/* + clsLEAMDNSHost::clsMsgHeader::clsMsgHeader + +*/ +clsLEAMDNSHost::clsMsgHeader::clsMsgHeader(uint16_t p_u16ID /*= 0*/, + bool p_bQR /*= false*/, + uint8_t p_u8Opcode /*= 0*/, + bool p_bAA /*= false*/, + bool p_bTC /*= false*/, + bool p_bRD /*= false*/, + bool p_bRA /*= false*/, + uint8_t p_u8RCode /*= 0*/, + uint16_t p_u16QDCount /*= 0*/, + uint16_t p_u16ANCount /*= 0*/, + uint16_t p_u16NSCount /*= 0*/, + uint16_t p_u16ARCount /*= 0*/) + : m_u16ID(p_u16ID), + m_1bQR(p_bQR), m_4bOpcode(p_u8Opcode), m_1bAA(p_bAA), m_1bTC(p_bTC), m_1bRD(p_bRD), + m_1bRA(p_bRA), m_3bZ(0), m_4bRCode(p_u8RCode), + m_u16QDCount(p_u16QDCount), + m_u16ANCount(p_u16ANCount), + m_u16NSCount(p_u16NSCount), + m_u16ARCount(p_u16ARCount) +{ +} + + +/** + clsLEAMDNSHost::clsRRDomain + + A MDNS domain object. + The labels of the domain are stored (DNS-like encoded) in 'm_acName': + [length byte]varlength label[length byte]varlength label[0] + 'm_u16NameLength' stores the used length of 'm_acName'. + Dynamic label addition is supported. + Comparison is supported. + Export as byte array 'esp8266.local' is supported. + +*/ + +/* + clsLEAMDNSHost::clsRRDomain::clsRRDomain constructor + +*/ +clsLEAMDNSHost::clsRRDomain::clsRRDomain(void) + : m_u16NameLength(0), + m_pcDecodedName(0) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsRRDomain::clsRRDomain copy-constructor + +*/ +clsLEAMDNSHost::clsRRDomain::clsRRDomain(const clsRRDomain& p_Other) + : m_u16NameLength(0), + m_pcDecodedName(0) +{ + operator=(p_Other); +} + +/* + clsLEAMDNSHost::clsRRDomain::clsRRDomain destructor + +*/ +clsLEAMDNSHost::clsRRDomain::~clsRRDomain(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsRRDomain::operator = + +*/ +clsLEAMDNSHost::clsRRDomain& clsLEAMDNSHost::clsRRDomain::operator=(const clsRRDomain& p_Other) +{ + if (&p_Other != this) + { + clear(); + memcpy(m_acName, p_Other.m_acName, sizeof(m_acName)); + m_u16NameLength = p_Other.m_u16NameLength; + } + return *this; +} + +/* + clsLEAMDNSHost::clsRRDomain::clear + +*/ +bool clsLEAMDNSHost::clsRRDomain::clear(void) +{ + memset(m_acName, 0, sizeof(m_acName)); + m_u16NameLength = 0; + return clearNameCache(); +} + +/* + clsLEAMDNSHost::clsRRDomain::clearNameCache + +*/ +bool clsLEAMDNSHost::clsRRDomain::clearNameCache(void) +{ + if (m_pcDecodedName) + { + delete[] m_pcDecodedName; + m_pcDecodedName = 0; + } + return true; +} + +/* + clsLEAMDNSHost::clsRRDomain::addLabel + +*/ +bool clsLEAMDNSHost::clsRRDomain::addLabel(const char* p_pcLabel, + bool p_bPrependUnderline /*= false*/) +{ + bool bResult = false; + + size_t stLength = (p_pcLabel + ? (strlen(p_pcLabel) + (p_bPrependUnderline ? 1 : 0)) + : 0); + if ((clsConsts::stDomainLabelMaxLength >= stLength) && + (clsConsts::stDomainMaxLength >= (m_u16NameLength + (1 + stLength)))) + { + // Length byte + m_acName[m_u16NameLength] = (unsigned char)stLength; // Might be 0! + ++m_u16NameLength; + // Label + if (stLength) + { + if (p_bPrependUnderline) + { + m_acName[m_u16NameLength++] = '_'; + --stLength; + } + strcpy(&(m_acName[m_u16NameLength]), p_pcLabel); + m_u16NameLength += stLength; + } + bResult = clearNameCache(); + } + return bResult; +} + +/* + clsLEAMDNSHost::clsRRDomain::compare + +*/ +bool clsLEAMDNSHost::clsRRDomain::compare(const clsRRDomain& p_Other) const +{ + bool bResult = false; + + if (m_u16NameLength == p_Other.m_u16NameLength) + { + const char* pT = m_acName; + const char* pO = p_Other.m_acName; + while ((pT) && + (pO) && + (*((unsigned char*)pT) == *((unsigned char*)pO)) && // Same length AND + (0 == strncasecmp((pT + 1), (pO + 1), *((unsigned char*)pT)))) // Same content + { + if (*((unsigned char*)pT)) // Not 0 + { + pT += (1 + * ((unsigned char*)pT)); // Shift by length byte and length + pO += (1 + * ((unsigned char*)pO)); + } + else // Is 0 -> Successfully reached the end + { + bResult = true; + break; + } + } + } + return bResult; +} + +/* + clsLEAMDNSHost::clsRRDomain::operator == + +*/ +bool clsLEAMDNSHost::clsRRDomain::operator==(const clsRRDomain& p_Other) const +{ + return compare(p_Other); +} + +/* + clsLEAMDNSHost::clsRRDomain::operator != + +*/ +bool clsLEAMDNSHost::clsRRDomain::operator!=(const clsRRDomain& p_Other) const +{ + return !compare(p_Other); +} + +/* + clsLEAMDNSHost::clsRRDomain::operator > + +*/ +bool clsLEAMDNSHost::clsRRDomain::operator>(const clsRRDomain& p_Other) const +{ + // TODO: Check, if this is a good idea... + return !compare(p_Other); +} + +/* + clsLEAMDNSHost::clsRRDomain::c_strLength + +*/ +size_t clsLEAMDNSHost::clsRRDomain::c_strLength(void) const +{ + size_t stLength = 0; + + unsigned char* pucLabelLength = (unsigned char*)m_acName; + while (*pucLabelLength) + { + stLength += (*pucLabelLength + 1 /* +1 for '.' or '\0'*/); + pucLabelLength += (*pucLabelLength + 1); + } + return stLength; +} + +/* + clsLEAMDNSHost::clsRRDomain::c_str (const) + +*/ +bool clsLEAMDNSHost::clsRRDomain::c_str(char* p_pcBuffer) const +{ + bool bResult = false; + + if (p_pcBuffer) + { + *p_pcBuffer = 0; + unsigned char* pucLabelLength = (unsigned char*)m_acName; + while (*pucLabelLength) + { + memcpy(p_pcBuffer, (const char*)(pucLabelLength + 1), *pucLabelLength); + p_pcBuffer += *pucLabelLength; + pucLabelLength += (*pucLabelLength + 1); + *p_pcBuffer++ = (*pucLabelLength ? '.' : '\0'); + } + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsRRDomain::c_str + +*/ +const char* clsLEAMDNSHost::clsRRDomain::c_str(void) const +{ + if ((!m_pcDecodedName) && + (m_u16NameLength) && + ((((clsRRDomain*)this)->m_pcDecodedName = new char[c_strLength()]))) // TRANSPARENT caching + { + ((clsRRDomain*)this)->c_str(m_pcDecodedName); + } + return m_pcDecodedName; +} + + +/** + clsLEAMDNSHost::clsRRAttributes + + A MDNS attributes object. + +*/ + +/* + clsLEAMDNSHost::clsRRAttributes::clsRRAttributes constructor + +*/ +clsLEAMDNSHost::clsRRAttributes::clsRRAttributes(uint16_t p_u16Type /*= 0*/, + uint16_t p_u16Class /*= 1 DNS_RRCLASS_IN Internet*/) + : m_u16Type(p_u16Type), + m_u16Class(p_u16Class) +{ +} + +/* + clsLEAMDNSHost::clsRRAttributes::clsRRAttributes copy-constructor + +*/ +clsLEAMDNSHost::clsRRAttributes::clsRRAttributes(const clsLEAMDNSHost::clsRRAttributes& p_Other) +{ + operator=(p_Other); +} + +/* + clsLEAMDNSHost::clsRRAttributes::operator = + +*/ +clsLEAMDNSHost::clsRRAttributes& clsLEAMDNSHost::clsRRAttributes::operator=(const clsLEAMDNSHost::clsRRAttributes& p_Other) +{ + if (&p_Other != this) + { + m_u16Type = p_Other.m_u16Type; + m_u16Class = p_Other.m_u16Class; + } + return *this; +} + + +/** + clsLEAMDNSHost::clsRRHeader + + A MDNS record header (domain and attributes) object. + +*/ + +/* + clsLEAMDNSHost::clsRRHeader::clsRRHeader constructor + +*/ +clsLEAMDNSHost::clsRRHeader::clsRRHeader(void) +{ +} + +/* + clsLEAMDNSHost::clsRRHeader::clsRRHeader copy-constructor + +*/ +clsLEAMDNSHost::clsRRHeader::clsRRHeader(const clsRRHeader& p_Other) +{ + operator=(p_Other); +} + +/* + clsLEAMDNSHost::clsRRHeader::operator = + +*/ +clsLEAMDNSHost::clsRRHeader& clsLEAMDNSHost::clsRRHeader::operator=(const clsLEAMDNSHost::clsRRHeader& p_Other) +{ + if (&p_Other != this) + { + m_Domain = p_Other.m_Domain; + m_Attributes = p_Other.m_Attributes; + } + return *this; +} + +/* + clsLEAMDNSHost::clsRRHeader::clear + +*/ +bool clsLEAMDNSHost::clsRRHeader::clear(void) +{ + m_Domain.clear(); + return true; +} + + +/** + clsLEAMDNSHost::clsRRQuestion + + A MDNS question record object (header + question flags) + +*/ + +/* + clsLEAMDNSHost::clsRRQuestion::clsRRQuestion constructor +*/ +clsLEAMDNSHost::clsRRQuestion::clsRRQuestion(void) + : m_bUnicast(false) +{ +} + + +/** + clsLEAMDNSHost::clsNSECBitmap + + A MDNS question record object (header + question flags) + +*/ + +/* + clsLEAMDNSHost::clsNSECBitmap::clsNSECBitmap constructor + +*/ +clsLEAMDNSHost::clsNSECBitmap::clsNSECBitmap(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsNSECBitmap::clsNSECBitmap destructor + +*/ +bool clsLEAMDNSHost::clsNSECBitmap::clear(void) +{ + memset(m_au8BitmapData, 0, sizeof(m_au8BitmapData)); + return true; +} + +/* + clsLEAMDNSHost::clsNSECBitmap::length + +*/ +uint16_t clsLEAMDNSHost::clsNSECBitmap::length(void) const +{ + return sizeof(m_au8BitmapData); // 6 +} + +/* + clsLEAMDNSHost::clsNSECBitmap::setBit + +*/ +bool clsLEAMDNSHost::clsNSECBitmap::setBit(uint16_t p_u16Bit) +{ + bool bResult = false; + + if ((p_u16Bit) && + (length() > (p_u16Bit / 8))) // bit between 0..47(2F) + { + + uint8_t& ru8Byte = m_au8BitmapData[p_u16Bit / 8]; + uint8_t u8Flag = 1 << (7 - (p_u16Bit % 8)); // (7 - (0..7)) = 7..0 + + ru8Byte |= u8Flag; + + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsNSECBitmap::getBit + +*/ +bool clsLEAMDNSHost::clsNSECBitmap::getBit(uint16_t p_u16Bit) const +{ + bool bResult = false; + + if ((p_u16Bit) && + (length() > (p_u16Bit / 8))) // bit between 0..47(2F) + { + + uint8_t u8Byte = m_au8BitmapData[p_u16Bit / 8]; + uint8_t u8Flag = 1 << (7 - (p_u16Bit % 8)); // (7 - (0..7)) = 7..0 + + bResult = (u8Byte & u8Flag); + } + return bResult; +} + + +/** + clsLEAMDNSHost::clsRRAnswer + + A MDNS answer record object (header + answer content). + This is a 'virtual' base class for all other MDNS answer classes. + +*/ + +/* + clsLEAMDNSHost::clsRRAnswer::clsRRAnswer constructor + +*/ +clsLEAMDNSHost::clsRRAnswer::clsRRAnswer(enuAnswerType p_AnswerType, + const clsLEAMDNSHost::clsRRHeader& p_Header, + uint32_t p_u32TTL) + : m_pNext(0), + m_AnswerType(p_AnswerType), + m_Header(p_Header), + m_u32TTL(p_u32TTL) +{ + // Extract 'cache flush'-bit + m_bCacheFlush = (m_Header.m_Attributes.m_u16Class & 0x8000); + m_Header.m_Attributes.m_u16Class &= (~0x8000); +} + +/* + clsLEAMDNSHost::clsRRAnswer::~stcRRAnswer destructor + +*/ +clsLEAMDNSHost::clsRRAnswer::~clsRRAnswer(void) +{ +} + +/* + clsLEAMDNSHost::clsRRAnswer::answerType + +*/ +clsLEAMDNSHost::enuAnswerType clsLEAMDNSHost::clsRRAnswer::answerType(void) const +{ + return m_AnswerType; +} + +/* + clsLEAMDNSHost::clsRRAnswer::clear + +*/ +bool clsLEAMDNSHost::clsRRAnswer::clear(void) +{ + m_pNext = 0; + m_Header.clear(); + return true; +} + + +/** + clsLEAMDNSHost::clsRRAnswerA + + A MDNS A answer object. + Extends the base class by an IPv4 address member. + +*/ + +#ifdef MDNS_IPV4_SUPPORT +/* + clsLEAMDNSHost::clsRRAnswerA::clsRRAnswerA constructor + +*/ +clsLEAMDNSHost::clsRRAnswerA::clsRRAnswerA(const clsLEAMDNSHost::clsRRHeader& p_Header, + uint32_t p_u32TTL) + : clsRRAnswer(enuAnswerType::A, p_Header, p_u32TTL), + m_IPAddress() +{ +} + +/* + clsLEAMDNSHost::clsRRAnswerA::clsRRAnswerA destructor + +*/ +clsLEAMDNSHost::clsRRAnswerA::~clsRRAnswerA(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsRRAnswerA::clear + +*/ +bool clsLEAMDNSHost::clsRRAnswerA::clear(void) +{ + m_IPAddress = IPAddress(); + return true; +} +#endif + + +/** + clsLEAMDNSHost::clsRRAnswerPTR + + A MDNS PTR answer object. + Extends the base class by a MDNS domain member. + +*/ + +/* + clsLEAMDNSHost::clsRRAnswerPTR::clsRRAnswerPTR constructor + +*/ +clsLEAMDNSHost::clsRRAnswerPTR::clsRRAnswerPTR(const clsLEAMDNSHost::clsRRHeader& p_Header, + uint32_t p_u32TTL) + : clsRRAnswer(enuAnswerType::PTR, p_Header, p_u32TTL) +{ +} + +/* + clsLEAMDNSHost::clsRRAnswerPTR::~stcRRAnswerPTR destructor + +*/ +clsLEAMDNSHost::clsRRAnswerPTR::~clsRRAnswerPTR(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsRRAnswerPTR::clear + +*/ +bool clsLEAMDNSHost::clsRRAnswerPTR::clear(void) +{ + m_PTRDomain.clear(); + return true; +} + + +/** + clsLEAMDNSHost::clsRRAnswerTXT + + A MDNS TXT answer object. + Extends the base class by a MDNS TXT items list member. + +*/ + +/* + clsLEAMDNSHost::clsRRAnswerTXT::clsRRAnswerTXT constructor + +*/ +clsLEAMDNSHost::clsRRAnswerTXT::clsRRAnswerTXT(const clsLEAMDNSHost::clsRRHeader& p_Header, + uint32_t p_u32TTL) + : clsRRAnswer(enuAnswerType::TXT, p_Header, p_u32TTL) +{ +} + +/* + clsLEAMDNSHost::clsRRAnswerTXT::~stcRRAnswerTXT destructor + +*/ +clsLEAMDNSHost::clsRRAnswerTXT::~clsRRAnswerTXT(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsRRAnswerTXT::clear + +*/ +bool clsLEAMDNSHost::clsRRAnswerTXT::clear(void) +{ + m_Txts.clear(); + return true; +} + + +/** + clsLEAMDNSHost::clsRRAnswerAAAA + + A MDNS AAAA answer object. + Extends the base class by an IPv6 address member. + +*/ + +#ifdef MDNS_IPV6_SUPPORT +/* + clsLEAMDNSHost::clsRRAnswerAAAA::clsRRAnswerAAAA constructor + +*/ +clsLEAMDNSHost::clsRRAnswerAAAA::clsRRAnswerAAAA(const clsLEAMDNSHost::clsRRHeader& p_Header, + uint32_t p_u32TTL) + : clsRRAnswer(enuAnswerType::AAAA, p_Header, p_u32TTL), + m_IPAddress() +{ +} + +/* + clsLEAMDNSHost::clsRRAnswerAAAA::~stcRRAnswerAAAA destructor + +*/ +clsLEAMDNSHost::clsRRAnswerAAAA::~clsRRAnswerAAAA(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsRRAnswerAAAA::clear + +*/ +bool clsLEAMDNSHost::clsRRAnswerAAAA::clear(void) +{ + m_IPAddress = IPAddress(); + return true; +} +#endif + + +/** + clsLEAMDNSHost::clsRRAnswerSRV + + A MDNS SRV answer object. + Extends the base class by a port member. + +*/ + +/* + clsLEAMDNSHost::clsRRAnswerSRV::clsRRAnswerSRV constructor + +*/ +clsLEAMDNSHost::clsRRAnswerSRV::clsRRAnswerSRV(const clsLEAMDNSHost::clsRRHeader& p_Header, + uint32_t p_u32TTL) + : clsRRAnswer(enuAnswerType::SRV, p_Header, p_u32TTL), + m_u16Priority(0), + m_u16Weight(0), + m_u16Port(0) +{ +} + +/* + clsLEAMDNSHost::clsRRAnswerSRV::~stcRRAnswerSRV destructor + +*/ +clsLEAMDNSHost::clsRRAnswerSRV::~clsRRAnswerSRV(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsRRAnswerSRV::clear + +*/ +bool clsLEAMDNSHost::clsRRAnswerSRV::clear(void) +{ + m_u16Priority = 0; + m_u16Weight = 0; + m_u16Port = 0; + m_SRVDomain.clear(); + return true; +} + + +/** + clsLEAMDNSHost::clsRRAnswerGeneric + + An unknown (generic) MDNS answer object. + Extends the base class by a RDATA buffer member. + +*/ + +/* + clsLEAMDNSHost::clsRRAnswerGeneric::clsRRAnswerGeneric constructor + +*/ +clsLEAMDNSHost::clsRRAnswerGeneric::clsRRAnswerGeneric(const clsRRHeader& p_Header, + uint32_t p_u32TTL) + : clsRRAnswer(enuAnswerType::Generic, p_Header, p_u32TTL), + m_u16RDLength(0), + m_pu8RDData(0) +{ +} + +/* + clsLEAMDNSHost::clsRRAnswerGeneric::~stcRRAnswerGeneric destructor + +*/ +clsLEAMDNSHost::clsRRAnswerGeneric::~clsRRAnswerGeneric(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsRRAnswerGeneric::clear + +*/ +bool clsLEAMDNSHost::clsRRAnswerGeneric::clear(void) +{ + if (m_pu8RDData) + { + delete[] m_pu8RDData; + m_pu8RDData = 0; + } + m_u16RDLength = 0; + + return true; +} + + +/** + clsLEAMDNSHost::clsSendParameter + + A 'collection' of properties and flags for one MDNS query or response. + Mainly managed by the 'Control' functions. + The current offset in the UPD output buffer is tracked to be able to do + a simple host or service domain compression. + +*/ + +/** + clsLEAMDNSHost::clsSendParameter::clsDomainCacheItem + + A cached host or service domain, incl. the offset in the UDP output buffer. + +*/ + +/* + clsLEAMDNSHost::clsSendParameter::clsDomainCacheItem::clsDomainCacheItem constructor + +*/ +clsLEAMDNSHost::clsSendParameter::clsDomainCacheItem::clsDomainCacheItem(const void* p_pHostNameOrService, + bool p_bAdditionalData, + uint32_t p_u16Offset) + : m_pHostNameOrService(p_pHostNameOrService), + m_bAdditionalData(p_bAdditionalData), + m_u16Offset(p_u16Offset) +{ +} + +/** + clsLEAMDNSHost::clsSendParameter + +*/ + +/* + clsLEAMDNSHost::clsSendParameter::clsSendParameter constructor + +*/ +clsLEAMDNSHost::clsSendParameter::clsSendParameter(void) + : m_u16ID(0), + m_u32HostReplyMask(0), + m_bLegacyDNSQuery(false), + m_Response(enuResponseType::None), + m_bAuthorative(false), + m_bCacheFlush(false), + m_bUnicast(false), + m_bUnannounce(false), + m_u16Offset(0) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsSendParameter::~stcSendParameter destructor + +*/ +clsLEAMDNSHost::clsSendParameter::~clsSendParameter(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsSendParameter::clear + +*/ +bool clsLEAMDNSHost::clsSendParameter::clear(void) +{ + m_u16ID = 0; + flushQuestions(); + m_u32HostReplyMask = 0; + + m_bLegacyDNSQuery = false; + m_Response = enuResponseType::None; + m_bAuthorative = false; + m_bCacheFlush = true; + m_bUnicast = false; + m_bUnannounce = false; + + m_u16Offset = 0; + flushDomainCache(); + return true; +} + +/* + clsLEAMDNSHost::clsSendParameter::flushQuestions + +*/ +bool clsLEAMDNSHost::clsSendParameter::flushQuestions(void) +{ + for (clsRRQuestion* pRRQuestion : m_RRQuestions) + { + delete pRRQuestion; + } + m_RRQuestions.clear(); + return true; +} + +/* + clsLEAMDNSHost::clsSendParameter::flushDomainCache + +*/ +bool clsLEAMDNSHost::clsSendParameter::flushDomainCache(void) +{ + for (clsDomainCacheItem* pDomainCacheItem : m_DomainCacheItems) + { + delete pDomainCacheItem; + } + m_DomainCacheItems.clear(); + return true; +} + +/* + clsLEAMDNSHost::clsSendParameter::flushTempContent + +*/ +bool clsLEAMDNSHost::clsSendParameter::flushTempContent(void) +{ + m_u16Offset = 0; + flushDomainCache(); + return true; +} + +/* + clsLEAMDNSHost::clsSendParameter::shiftOffset + +*/ +bool clsLEAMDNSHost::clsSendParameter::shiftOffset(uint16_t p_u16Shift) +{ + m_u16Offset += p_u16Shift; + return true; +} + +/* + clsLEAMDNSHost::clsSendParameter::addDomainCacheItem + +*/ +bool clsLEAMDNSHost::clsSendParameter::addDomainCacheItem(const void* p_pHostNameOrService, + bool p_bAdditionalData, + uint16_t p_u16Offset) +{ + bool bResult = false; + + clsDomainCacheItem* pNewItem = 0; + if ((p_pHostNameOrService) && + (p_u16Offset) && + ((pNewItem = new clsDomainCacheItem(p_pHostNameOrService, p_bAdditionalData, p_u16Offset)))) + { + m_DomainCacheItems.push_back(pNewItem); + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsSendParameter::findCachedDomainOffset + +*/ +uint16_t clsLEAMDNSHost::clsSendParameter::findCachedDomainOffset(const void* p_pHostNameOrService, + bool p_bAdditionalData) const +{ + const clsDomainCacheItem* pMatchingCacheItem = 0; + + for (const clsDomainCacheItem* pCacheItem : m_DomainCacheItems) + { + if ((pCacheItem->m_pHostNameOrService == p_pHostNameOrService) && + (pCacheItem->m_bAdditionalData == p_bAdditionalData)) // Found cache item + { + pMatchingCacheItem = pCacheItem; + break; + } + } + return (pMatchingCacheItem ? pMatchingCacheItem->m_u16Offset : 0); +} + + +/** + clsLEAMDNSHost::clsQuery + + A MDNS service query object. + Service queries may be static or dynamic. + As the static service query is processed in the blocking function 'queryService', + only one static service service may exist. The processing of the answers is done + on the WiFi-stack side of the ESP stack structure (via 'UDPContext.onRx(_update)'). + +*/ + +/** + clsLEAMDNSHost::clsQuery::clsAnswer + + One answer for a query. + Every answer must contain + - a service instance entry (pivot), + and may contain + - a host domain, + - a port + - an IPv4 address + (- an IPv6 address) + - a MDNS TXTs + The existance of a component is flaged in 'm_u32ContentFlags'. + For every answer component a TTL value is maintained. + Answer objects can be connected to a linked list. + + For the host domain, service domain and TXTs components, a char array + representation can be retrieved (which is created on demand). + +*/ + +/** + clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL + + The TTL (Time-To-Live) for an specific answer content. + If the answer is scheduled for an update, the corresponding flag should be set. + +*/ + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::clsTTL constructor + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::clsTTL(void) + : m_u32TTL(0), + m_TTLTimeout(std::numeric_limits::max()), + m_TimeoutLevel(static_cast(enuTimeoutLevel::None)) +{ +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::set + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::set(uint32_t p_u32TTL) +{ + m_u32TTL = p_u32TTL; + if (m_u32TTL) + { + m_TimeoutLevel = static_cast(enuTimeoutLevel::Base); // Set to 80% + m_TTLTimeout.reset(timeout()); + } + else + { + m_TimeoutLevel = static_cast(enuTimeoutLevel::None); // undef + m_TTLTimeout.reset(std::numeric_limits::max()); + } + return true; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::flagged + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::flagged(void) const +{ + return ((m_u32TTL) && + (static_cast(enuTimeoutLevel::None) != m_TimeoutLevel) && + (((esp8266::polledTimeout::timeoutTemplate*)&m_TTLTimeout)->expired())); // Cast-away the const; in case of oneShot-timer OK (but ugly...) +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::restart + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::restart(void) +{ + bool bResult = true; + + if ((static_cast(enuTimeoutLevel::Base) <= m_TimeoutLevel) && // >= 80% AND + (static_cast(enuTimeoutLevel::Final) > m_TimeoutLevel)) // < 100% + { + m_TimeoutLevel += static_cast(enuTimeoutLevel::Interval); // increment by 5% + m_TTLTimeout.reset(timeout()); + } + else + { + bResult = false; + m_TTLTimeout.reset(std::numeric_limits::max()); + m_TimeoutLevel = static_cast(enuTimeoutLevel::None); + } + return bResult; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::prepareDeletion + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::prepareDeletion(void) +{ + m_TimeoutLevel = static_cast(enuTimeoutLevel::Final); + m_TTLTimeout.reset(1 * 1000); // See RFC 6762, 10.1 + + return true; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::finalTimeoutLevel + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::finalTimeoutLevel(void) const +{ + return (static_cast(enuTimeoutLevel::Final) == m_TimeoutLevel); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::timeout + +*/ +unsigned long clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::timeout(void) const +{ + uint32_t u32Timeout = std::numeric_limits::max(); + + if (static_cast(enuTimeoutLevel::Base) == m_TimeoutLevel) // 80% + { + u32Timeout = (m_u32TTL * 800); // to milliseconds + } + else if ((static_cast(enuTimeoutLevel::Base) < m_TimeoutLevel) && // >80% AND + (static_cast(enuTimeoutLevel::Final) >= m_TimeoutLevel)) // <= 100% + { + u32Timeout = (m_u32TTL * 50); + } // else: invalid + return u32Timeout; +} + + +/** + clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddress + +*/ + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddress::clsIPAddress constructor + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL::clsIPAddressWithTTL(IPAddress p_IPAddress, + uint32_t p_u32TTL /*= 0*/) + : m_IPAddress(p_IPAddress) +{ + m_TTL.set(p_u32TTL); +} + + +/** + clsLEAMDNSHost::clsQuery::clsAnswer + +*/ + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsAnswer constructor + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer::clsAnswer(void) + : m_u16Port(0), + m_QueryAnswerFlags(0) +{ +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::~clsAnswer destructor + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer::~clsAnswer(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clear + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::clear(void) +{ + return ( +#ifdef MDNS_IPV4_SUPPORT + (releaseIPv4Addresses()) +#else + (true) +#endif + && +#ifdef MDNS_IPV6_SUPPORT + (releaseIPv6Addresses()) +#else + (true) +#endif + ); +} + +#ifdef MDNS_IPV4_SUPPORT +/* + clsLEAMDNSHost::clsQuery::clsAnswer::releaseIPv4Addresses + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::releaseIPv4Addresses(void) +{ + for (clsIPAddressWithTTL* pIPAddress : m_IPv4Addresses) + { + delete pIPAddress; + } + m_IPv4Addresses.clear(); + return true; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::addIPv4Address + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::addIPv4Address(clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* p_pIPv4Address) +{ + bool bResult = false; + + if (p_pIPv4Address) + { + m_IPv4Addresses.push_back(p_pIPv4Address); + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::removeIPv4Address + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::removeIPv4Address(clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* p_pIPv4Address) +{ + bool bResult = false; + + clsIPAddressWithTTL::list::iterator it(p_pIPv4Address + ? std::find(m_IPv4Addresses.begin(), m_IPv4Addresses.end(), p_pIPv4Address) + : m_IPv4Addresses.end()); + if (m_IPv4Addresses.end() != it) + { + m_IPv4Addresses.erase(it); + delete p_pIPv4Address; + + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::findIPv4Address (const) + +*/ +const clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost::clsQuery::clsAnswer::findIPv4Address(const IPAddress& p_IPAddress) const +{ + return (clsIPAddressWithTTL*)(((const clsAnswer*)this)->findIPv4Address(p_IPAddress)); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::findIPv4Address + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost::clsQuery::clsAnswer::findIPv4Address(const IPAddress& p_IPAddress) +{ + clsIPAddressWithTTL* pMatchingIPv4Address = 0; + + for (clsIPAddressWithTTL* pIPv4Address : m_IPv4Addresses) + { + if (pIPv4Address->m_IPAddress == p_IPAddress) + { + pMatchingIPv4Address = pIPv4Address; + break; + } + } + return pMatchingIPv4Address; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::IPv4AddressCount + +*/ +uint32_t clsLEAMDNSHost::clsQuery::clsAnswer::IPv4AddressCount(void) const +{ + uint32_t u32Count = m_IPv4Addresses.size(); + return u32Count; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::IPv4AddressAtIndex + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost::clsQuery::clsAnswer::IPv4AddressAtIndex(uint32_t p_u32Index) +{ + return (clsIPAddressWithTTL*)(((const clsAnswer*)this)->IPv4AddressAtIndex(p_u32Index)); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::IPv4AddressAtIndex (const) + +*/ +const clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost::clsQuery::clsAnswer::IPv4AddressAtIndex(uint32_t p_u32Index) const +{ + const clsIPAddressWithTTL* pIPv4AddressAtIndex = 0; + + uint32_t u32CurIndex = 0; + for (clsIPAddressWithTTL::list::const_iterator it = m_IPv4Addresses.begin(); + (((uint32_t)(-1) != p_u32Index) && (u32CurIndex <= p_u32Index) && (it != m_IPv4Addresses.end())); + it++, u32CurIndex++) + { + if (p_u32Index == u32CurIndex++) + { + pIPv4AddressAtIndex = *it; + break; + } + } + return pIPv4AddressAtIndex; +} +#endif + +#ifdef MDNS_IPV6_SUPPORT +/* + clsLEAMDNSHost::clsQuery::clsAnswer::releaseIPv6Addresses + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::releaseIPv6Addresses(void) +{ + for (clsIPAddressWithTTL* pIPAddress : m_IPv6Addresses) + { + delete pIPAddress; + } + m_IPv6Addresses.clear(); + return true; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::addIPv6Address + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::addIPv6Address(clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* p_pIPv6Address) +{ + bool bResult = false; + + if (p_pIPv6Address) + { + m_IPv6Addresses.push_back(p_pIPv6Address); + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::removeIPv6Address + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::removeIPv6Address(clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* p_pIPv6Address) +{ + bool bResult = false; + + clsIPAddressWithTTL::list::iterator it(p_pIPv6Address + ? std::find(m_IPv6Addresses.begin(), m_IPv6Addresses.end(), p_pIPv6Address) + : m_IPv6Addresses.end()); + if (m_IPv6Addresses.end() != it) + { + m_IPv6Addresses.erase(it); + delete p_pIPv6Address; + + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::findIPv6Address + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost::clsQuery::clsAnswer::findIPv6Address(const IPAddress& p_IPAddress) +{ + return (clsIPAddressWithTTL*)(((const clsAnswer*)this)->findIPv6Address(p_IPAddress)); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::findIPv6Address (const) + +*/ +const clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost::clsQuery::clsAnswer::findIPv6Address(const IPAddress& p_IPAddress) const +{ + clsIPAddressWithTTL* pMatchingIPv6Address = 0; + + for (clsIPAddressWithTTL* pIPv6Address : m_IPv6Addresses) + { + if (pIPv6Address->m_IPAddress == p_IPAddress) + { + pMatchingIPv6Address = pIPv6Address; + break; + } + } + return pMatchingIPv6Address; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::IPv6AddressCount + +*/ +uint32_t clsLEAMDNSHost::clsQuery::clsAnswer::IPv6AddressCount(void) const +{ + uint32_t u32Count = m_IPv6Addresses.size(); + return u32Count; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::IPv6AddressAtIndex + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost::clsQuery::clsAnswer::IPv6AddressAtIndex(uint32_t p_u32Index) +{ + return (clsIPAddressWithTTL*)(((const clsAnswer*)this)->IPv6AddressAtIndex(p_u32Index)); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::IPv6AddressAtIndex (const) + +*/ +const clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost::clsQuery::clsAnswer::IPv6AddressAtIndex(uint32_t p_u32Index) const +{ + const clsIPAddressWithTTL* pIPv6AddressAtIndex = 0; + + uint32_t u32CurIndex = 0; + for (clsIPAddressWithTTL::list::const_iterator it = m_IPv6Addresses.begin(); + (((uint32_t)(-1) != p_u32Index) && (u32CurIndex <= p_u32Index) && (it != m_IPv6Addresses.end())); + it++, u32CurIndex++) + { + if (p_u32Index == u32CurIndex++) + { + pIPv6AddressAtIndex = *it; + break; + } + } + return pIPv6AddressAtIndex; +} +#endif + + +/** + clsLEAMDNSHost::clsQuery::clsAnswerAccessor + +*/ + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::clsAnswerAccessor constructor + +*/ +clsLEAMDNSHost::clsQuery::clsAnswerAccessor::clsAnswerAccessor(const clsLEAMDNSHost::clsQuery::clsAnswer* p_pAnswer) + : m_pAnswer(p_pAnswer) +{ + if ((m_pAnswer) && + (txtsAvailable())) + { + // Prepare m_TxtKeyValueMap + for (const clsLEAMDNSHost::clsServiceTxt* pTxt : m_pAnswer->m_Txts.m_Txts) + { + m_TxtKeyValueMap.emplace(std::pair(pTxt->m_pcKey, pTxt->m_pcValue)); + } + } +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::~clsAnswerAccessor destructor +*/ +clsLEAMDNSHost::clsQuery::clsAnswerAccessor::~clsAnswerAccessor(void) +{ +} + +/** + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::clsCompareTxtKey + +*/ + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::stcCompareTxtKey::operator() + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswerAccessor::stcCompareTxtKey::operator()(char const* p_pA, + char const* p_pB) const +{ + return (0 > strcasecmp(p_pA, p_pB)); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::serviceDomainAvailable + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswerAccessor::serviceDomainAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain))); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::serviceDomain + +*/ +const char* clsLEAMDNSHost::clsQuery::clsAnswerAccessor::serviceDomain(void) const +{ + return ((m_pAnswer) + ? m_pAnswer->m_ServiceDomain.c_str() + : 0); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::hostDomainAvailable + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswerAccessor::hostDomainAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(clsQuery::clsAnswer::enuQueryAnswerType::HostDomain))); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::hostDomain + +*/ +const char* clsLEAMDNSHost::clsQuery::clsAnswerAccessor::hostDomain(void) const +{ + return ((m_pAnswer) + ? m_pAnswer->m_HostDomain.c_str() + : 0); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::hostPortAvailable + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswerAccessor::hostPortAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Port))); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::hostPort + +*/ +uint16_t clsLEAMDNSHost::clsQuery::clsAnswerAccessor::hostPort(void) const +{ + return ((m_pAnswer) + ? (m_pAnswer->m_u16Port) + : 0); +} + +#ifdef MDNS_IPV4_SUPPORT +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::IPv4AddressAvailable + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswerAccessor::IPv4AddressAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address))); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::IPv4Addresses + +*/ +clsLEAMDNSHost::clsQuery::clsAnswerAccessor::clsIPAddressVector clsLEAMDNSHost::clsQuery::clsAnswerAccessor::IPv4Addresses(void) const +{ + clsIPAddressVector internalIP; + if ((m_pAnswer) && + (IPv4AddressAvailable())) + { + for (uint32_t u = 0; u < m_pAnswer->IPv4AddressCount(); ++u) + { + const clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* pIPAddr = m_pAnswer->IPv4AddressAtIndex(u); + if (pIPAddr) + { + internalIP.emplace_back(pIPAddr->m_IPAddress); + } + } + } + return internalIP; +} +#endif + +#ifdef MDNS_IPV6_SUPPORT +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::IPv6AddressAvailable + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswerAccessor::IPv6AddressAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address))); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::IPv6Addresses + +*/ +clsLEAMDNSHost::clsQuery::clsAnswerAccessor::clsIPAddressVector clsLEAMDNSHost::clsQuery::clsAnswerAccessor::IPv6Addresses(void) const +{ + clsIPAddressVector internalIP; + if ((m_pAnswer) && + (IPv6AddressAvailable())) + { + for (uint32_t u = 0; u < m_pAnswer->IPv6AddressCount(); ++u) + { + const clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* pIPAddr = m_pAnswer->IPv6AddressAtIndex(u); + if (pIPAddr) + { + internalIP.emplace_back(pIPAddr->m_IPAddress); + } + } + } + return internalIP; +} +#endif + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::txtsAvailable + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswerAccessor::txtsAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Txts))); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::txts + + Returns all TXT items for the given service as a ';'-separated string. + If not already existing; the string is alloced, filled and attached to the answer. + +*/ +const char* clsLEAMDNSHost::clsQuery::clsAnswerAccessor::txts(void) const +{ + return ((m_pAnswer) + ? m_pAnswer->m_Txts.c_str() + : 0); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::txtKeyValues + +*/ +const clsLEAMDNSHost::clsQuery::clsAnswerAccessor::clsTxtKeyValueMap& clsLEAMDNSHost::clsQuery::clsAnswerAccessor::txtKeyValues(void) const +{ + return m_TxtKeyValueMap; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::txtValue + +*/ +const char* clsLEAMDNSHost::clsQuery::clsAnswerAccessor::txtValue(const char* p_pcKey) const +{ + char* pcResult = 0; + + if (m_pAnswer) + { + for (const clsLEAMDNSHost::clsServiceTxt* pTxt : m_pAnswer->m_Txts.m_Txts) + { + if ((p_pcKey) && + (0 == strcasecmp(pTxt->m_pcKey, p_pcKey))) + { + pcResult = pTxt->m_pcValue; + break; + } + } + } + return pcResult; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::printTo + +*/ +size_t clsLEAMDNSHost::clsQuery::clsAnswerAccessor::printTo(Print& p_Print) const +{ + size_t stLen = 0; + const char* cpcI = " * "; + const char* cpcS = " "; + + stLen += p_Print.println(" * * * * *"); + if (hostDomainAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print("Host domain: "); + stLen += p_Print.println(hostDomain()); + } +#ifdef MDNS_IPV4_SUPPORT + if (IPv4AddressAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.println("IPv4 address(es):"); + for (const IPAddress& addr : IPv4Addresses()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print(cpcS); + stLen += p_Print.println(addr); + } + } +#endif +#ifdef MDNS_IPV6_SUPPORT + if (IPv6AddressAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.println("IPv6 address(es):"); + for (const IPAddress& addr : IPv6Addresses()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print(cpcS); + stLen += p_Print.println(addr); + } + } +#endif + if (serviceDomainAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print("Service domain: "); + stLen += p_Print.println(serviceDomain()); + } + if (hostPortAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print("Host port: "); + stLen += p_Print.println(hostPort()); + } + if (txtsAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print("TXTs:"); + for (auto const& x : txtKeyValues()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print(cpcS); + stLen += p_Print.print(x.first); + stLen += p_Print.print("="); + stLen += p_Print.println(x.second); + } + } + stLen += p_Print.println(" * * * * *"); + + return stLen; +} + + +/** + clsLEAMDNSHost::clsQuery + + A service or host query object. + A static query is flaged via 'm_bLegacyQuery'; while the function 'queryService' + is waiting for answers, the internal flag 'm_bAwaitingAnswers' is set. When the + timeout is reached, the flag is removed. These two flags are only used for static + service queries. + All answers to the query are stored in the 'm_Answers' list. + Individual answers may be addressed by index (in the list of answers). + Every time a answer component is added (or changed) in a dynamic query, + the callback 'm_fnCallback' is called. + The answer list may be searched by service and host domain. + + Query object may be connected to a linked list. + +*/ + +/* + clsLEAMDNSHost::clsQuery::clsQuery constructor + +*/ +clsLEAMDNSHost::clsQuery::clsQuery(const enuQueryType p_QueryType) + : m_QueryType(p_QueryType), + m_fnCallbackAnswer(0), + m_fnCallbackAccessor(0), + m_bStaticQuery(false), + m_u32SentCount(0), + m_ResendTimeout(std::numeric_limits::max()), + m_bAwaitingAnswers(true) +{ + clear(); + m_QueryType = p_QueryType; +} + +/* + clsLEAMDNSHost::clsQuery::~stcQuery destructor + +*/ +clsLEAMDNSHost::clsQuery::~clsQuery(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsQuery::clear + +*/ +bool clsLEAMDNSHost::clsQuery::clear(void) +{ + m_QueryType = enuQueryType::None; + m_fnCallbackAnswer = 0; + m_fnCallbackAccessor = 0; + m_bStaticQuery = false; + m_u32SentCount = 0; + m_ResendTimeout.reset(std::numeric_limits::max()); + m_bAwaitingAnswers = true; + for (clsAnswer* pAnswer : m_Answers) + { + delete pAnswer; + } + m_Answers.clear(); + return true; +} + +/* + clsLEAMDNSHost::clsQuery::answerCount + +*/ +uint32_t clsLEAMDNSHost::clsQuery::answerCount(void) const +{ + uint32_t u32Count = m_Answers.size(); + return u32Count; +} + +/* + clsLEAMDNSHost::clsQuery::answer + +*/ +const clsLEAMDNSHost::clsQuery::clsAnswer* clsLEAMDNSHost::clsQuery::answer(uint32_t p_u32Index) const +{ + const clsAnswer* pAnswerAtIndex = 0; + + uint32_t u32CurIndex = 0; + for (clsAnswer::list::const_iterator it = m_Answers.begin(); + (((uint32_t)(-1) != p_u32Index) && (u32CurIndex <= p_u32Index) && (it != m_Answers.end())); + it++, u32CurIndex++) + { + if (p_u32Index == u32CurIndex++) + { + pAnswerAtIndex = *it; + break; + } + } + return pAnswerAtIndex; +} + +/* + clsLEAMDNSHost::clsQuery::indexOfAnswer + +*/ +uint32_t clsLEAMDNSHost::clsQuery::indexOfAnswer(const clsLEAMDNSHost::clsQuery::clsAnswer* p_pAnswer) const +{ + uint32_t u32IndexOfAnswer = ((uint32_t)(-1)); + + uint32_t u32CurIndex = 0; + for (const clsAnswer* pAnswer : m_Answers) + { + if (pAnswer == p_pAnswer) + { + u32IndexOfAnswer = u32CurIndex; + break; + } + ++u32CurIndex; + } + return u32IndexOfAnswer; +} + +/* + clsLEAMDNSHost::clsQuery::answerAccessors + +*/ +clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::clsQuery::answerAccessors(void) const +{ + clsAnswerAccessor::vector tempAccessors; + for (const clsAnswer* pAnswer : m_Answers) + { + tempAccessors.emplace_back(pAnswer); + } + return tempAccessors; +} + +/* + clsLEAMDNSHost::clsQuery::answerAccessor + +*/ +clsLEAMDNSHost::clsQuery::clsAnswerAccessor clsLEAMDNSHost::clsQuery::answerAccessor(uint32 p_u32AnswerIndex) const +{ + return clsAnswerAccessor(answer(p_u32AnswerIndex)); +} + +/* + clsLEAMDNSHost::clsQuery::addAnswer + +*/ +bool clsLEAMDNSHost::clsQuery::addAnswer(clsLEAMDNSHost::clsQuery::clsAnswer* p_pAnswer) +{ + bool bResult = false; + + if (p_pAnswer) + { + m_Answers.push_back(p_pAnswer); + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsQuery::removeAnswer + +*/ +bool clsLEAMDNSHost::clsQuery::removeAnswer(clsLEAMDNSHost::clsQuery::clsAnswer* p_pAnswer) +{ + bool bResult = false; + + clsAnswer::list::iterator it(p_pAnswer + ? std::find(m_Answers.begin(), m_Answers.end(), p_pAnswer) + : m_Answers.end()); + if (m_Answers.end() != it) + { + m_Answers.erase(it); + delete p_pAnswer; + + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsQuery::findAnswerForServiceDomain + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer* clsLEAMDNSHost::clsQuery::findAnswerForServiceDomain(const clsLEAMDNSHost::clsRRDomain& p_ServiceDomain) +{ + clsAnswer* pAnswerForServiceDomain = 0; + + for (clsAnswer* pAnswer : m_Answers) + { + if (pAnswer->m_ServiceDomain == p_ServiceDomain) + { + pAnswerForServiceDomain = pAnswer; + break; + } + } + return pAnswerForServiceDomain; +} + +/* + clsLEAMDNSHost::clsQuery::findAnswerForHostDomain + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer* clsLEAMDNSHost::clsQuery::findAnswerForHostDomain(const clsLEAMDNSHost::clsRRDomain& p_HostDomain) +{ + clsAnswer* pAnswerForHostDomain = 0; + + for (clsAnswer* pAnswer : m_Answers) + { + if (pAnswer->m_HostDomain == p_HostDomain) + { + pAnswerForHostDomain = pAnswer; + break; + } + } + return pAnswerForHostDomain; +} + + +} // namespace MDNSImplementation + + +} // namespace esp8266 + + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp new file mode 100644 index 000000000..f77e0a5e1 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -0,0 +1,2451 @@ +/* + LEAmDNS2Host_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. + +*/ + +#include // for can_yield() +#include "ESP8266mDNS.h" +#include "LEAmDNS2Host.h" +#include "LEAmDNS2_Priv.h" + +namespace esp8266 +{ + + +namespace experimental +{ + + +/* + + SENDING + +*/ + +/* + MDNSResponder::_sendMessage + + Unicast responses are prepared and sent directly to the querier. + Multicast responses or queries are transferred to _sendMessage_Multicast + + Any reply flags in installed services are removed at the end! + +*/ +bool clsLEAMDNSHost::_sendMessage(clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + bool bResult = true; + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + if (netif_is_up(pNetIf)) + { + bResult = bResult && _sendMessage(pNetIf, p_rSendParameter); + } + + // Finally clear service reply masks + for (clsService* pService : m_Services) + { + pService->m_u32ReplyMask = 0; + } + return bResult; +} +bool clsLEAMDNSHost::_sendMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + bool bResult = false; + uint8_t u8AvailableProtocols = 0; + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage: if=" NETIFID_STR "\n"), _DH(), NETIFID_VAL(pNetIf))); + +#ifdef MDNS_IPV4_SUPPORT + // Only send out IPv4 messages, if we've got an IPv4 address + if (_getResponderIPAddress(pNetIf, enuIPProtocolType::V4).isSet()) + { + u8AvailableProtocols |= static_cast(enuIPProtocolType::V4); + } + DEBUG_EX_INFO(else + { + DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage: No IPv4 address available!\n"), _DH()); + }); +#endif +#ifdef MDNS_IPV6_SUPPORT + // Only send out IPv6 messages, if we've got an IPv6 address + if (_getResponderIPAddress(pNetIf, enuIPProtocolType::V6).isSet()) + { + u8AvailableProtocols |= static_cast(enuIPProtocolType::V6); + } + DEBUG_EX_INFO(else + { + DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage: No IPv6 address available!\n"), _DH()); + }); +#endif + + if (clsBackbone::sm_pBackbone->setDelayUDPProcessing(true)) + { + // Avoid 're-entry-like problems because delay() is called! + if (clsSendParameter::enuResponseType::None != p_rSendParameter.m_Response) + { + IPAddress ipRemote = ((clsSendParameter::enuResponseType::Response == p_rSendParameter.m_Response) + ? m_pUDPContext->getRemoteAddress() + : IPAddress()); + + if (p_rSendParameter.m_bUnicast) + { + // Unicast response -> Send to querier + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage: Will send unicast to '%s'.\n"), _DH(), ipRemote.toString().c_str());); + DEBUG_EX_ERR(if (!ipRemote.isSet()) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage: MISSING remote address for unicast response!\n"), _DH());); + + bResult = ((ipRemote.isSet()) && + (_prepareMessage(pNetIf, p_rSendParameter)) && + (m_pUDPContext->sendTimeout(ipRemote, m_pUDPContext->getRemotePort(), clsConsts::u32SendTimeoutMs)) /*&& + (Serial.println("Did send UC"), true)*/); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage (V4): FAILED!\n"), _DH());); + } + else + { + // Multicast response -> Send via the same network interface, that received the query +#ifdef MDNS_IPV4_SUPPORT + if (((!ipRemote.isSet()) || // NO remote IP + (ipRemote.isV4())) && // OR IPv4 + (u8AvailableProtocols & static_cast(enuIPProtocolType::V4))) // AND IPv4 protocol available + { + bResult = _sendMessage_Multicast(pNetIf, p_rSendParameter, static_cast(enuIPProtocolType::V4)); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + if (((!ipRemote.isSet()) || // NO remote IP + (ipRemote.isV6())) && // OR IPv6 + (u8AvailableProtocols & static_cast(enuIPProtocolType::V6))) // AND IPv6 protocol available + { + bResult = _sendMessage_Multicast(pNetIf, p_rSendParameter, static_cast(enuIPProtocolType::V6)); + } +#endif + } + } + else + { + // Multicast query -> Send by all available protocols + bResult = ((u8AvailableProtocols) && + (_sendMessage_Multicast(pNetIf, p_rSendParameter, u8AvailableProtocols))); + } + + clsBackbone::sm_pBackbone->setDelayUDPProcessing(false); + } + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_sendMessage_Multicast + + Fills the UDP output buffer (via _prepareMessage) and sends the buffer + via the selected WiFi protocols +*/ +bool clsLEAMDNSHost::_sendMessage_Multicast(netif* pNetIf, clsLEAMDNSHost::clsSendParameter& p_rSendParameter, + uint8_t p_IPProtocolTypes) +{ + bool bIPv4Result = true; + bool bIPv6Result = true; + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast: if=" NETIFID_STR "\n"), _DH(), NETIFID_VAL(pNetIf))); + +#ifdef MDNS_IPV4_SUPPORT + if (p_IPProtocolTypes & static_cast(enuIPProtocolType::V4)) + { + IPAddress ip4MulticastAddress(DNS_MQUERY_IPV4_GROUP_INIT); + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast IPv4: Will send to '%s'.\n"), _DH(), ip4MulticastAddress.toString().c_str());); + DEBUG_EX_INFO(if (!_getResponderIPAddress(pNetIf, enuIPProtocolType::V4)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast IPv4: NO IPv4 address!.\n"), _DH());); + bIPv4Result = ((_prepareMessage(pNetIf, p_rSendParameter)) && + (m_pUDPContext->setMulticastInterface(pNetIf), true) && + (m_pUDPContext->sendTimeout(ip4MulticastAddress, DNS_MQUERY_PORT, clsConsts::u32SendTimeoutMs)) && + (m_pUDPContext->setMulticastInterface(0), true) /*&& + (Serial.println("Did send MC V4"), true)*/); + DEBUG_EX_ERR(if (!bIPv4Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast (V4): FAILED!\n"), _DH());); + } +#endif + +#ifdef MDNS_IPV6_SUPPORT + if (p_IPProtocolTypes & static_cast(enuIPProtocolType::V6)) + { + IPAddress ip6MulticastAddress(DNS_MQUERY_IPV6_GROUP_INIT); + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast IPv6: Will send to '%s'.\n"), _DH(), ip6MulticastAddress.toString().c_str());); + DEBUG_EX_INFO(if (!_getResponderIPAddress(pNetIf, enuIPProtocolType::V6)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast IPv6: NO IPv6 address!.\n"), _DH());); + DEBUG_EX_ERR( + bool bPrepareMessage = false; + bool bUDPContextSend = false; + ); + bIPv6Result = ((DEBUG_EX_ERR(bPrepareMessage =)_prepareMessage(pNetIf, p_rSendParameter)) && + (m_pUDPContext->setMulticastInterface(pNetIf), true) && + (DEBUG_EX_ERR(bUDPContextSend =)m_pUDPContext->sendTimeout(ip6MulticastAddress, DNS_MQUERY_PORT, clsConsts::u32SendTimeoutMs)) && + (m_pUDPContext->setMulticastInterface(0), true) /*&& + (Serial.println("Did send MC V6"), true)*/); + DEBUG_EX_ERR(if (!bIPv6Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast (IPv6): FAILED! (%s, %s, %s)\n"), _DH(), (_getResponderIPAddress(pNetIf, enuIPProtocolType::V6).isSet() ? "1" : "0"), (bPrepareMessage ? "1" : "0"), (bUDPContextSend ? "1" : "0"));); + } +#endif + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast: %s!\n\n"), _DH(), ((bIPv4Result && bIPv6Result) ? "Succeeded" : "FAILED"));); + DEBUG_EX_ERR(if (!(bIPv4Result && bIPv6Result)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast: FAILED!\n"), _DH());); + return (bIPv4Result && bIPv6Result); +} + +/* + MDNSResponder::_prepareMessage + + 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 second loop, the header and all queries and answers are written to the UDP + output buffer. + +*/ +bool clsLEAMDNSHost::_prepareMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage\n"));); + bool bResult = true; + + // Prepare output buffer for potential reuse + p_rSendParameter.flushTempContent(); + + // Prepare header; count answers + clsMsgHeader msgHeader(p_rSendParameter.m_u16ID, + (static_cast(clsSendParameter::enuResponseType::None) != p_rSendParameter.m_Response), + 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 = ((clsSendParameter::enuResponseType::None != p_rSendParameter.m_Response) + ? msgHeader.m_u16ANCount // Usual answers + : msgHeader.m_u16NSCount); // Authorative answers + + /** + enuSequence + */ + using typeSequence = uint8_t; + enum class enuSequence : typeSequence + { + Count = 0, + Send = 1 + }; + + // Two step sequence: 'Count' and 'Send' + for (typeSequence sequence = static_cast(enuSequence::Count); ((bResult) && (sequence <= static_cast(enuSequence::Send))); ++sequence) + { + /* + DEBUG_EX_INFO( + if (static_cast(enuSequence::Send) == sequence) + DEBUG_OUTPUT.printf_P(PSTR("%s _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"), + _DH(), + (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 = ((static_cast(enuSequence::Count) == sequence) + ? true + : _writeMDNSMsgHeader(msgHeader, p_rSendParameter)); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSMsgHeader FAILED!\n"), _DH());); + // Questions + for (clsRRQuestion::list::iterator it = p_rSendParameter.m_RRQuestions.begin(); ((bResult) && (it != p_rSendParameter.m_RRQuestions.end())); it++) + { + clsRRQuestion* pQuestion = *it; + + ((static_cast(enuSequence::Count) == sequence) + ? ++msgHeader.m_u16QDCount + : (bResult = _writeMDNSQuestion(*pQuestion, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSQuestion FAILED!\n"), _DH());); + } + + // Answers and authorative answers + // NSEC host (part 1) + uint32_t u32NSECContent = 0; +#ifdef MDNS_IPV4_SUPPORT + // A + if ((bResult) && + (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::A)) && + (_getResponderIPAddress(pNetIf, enuIPProtocolType::V4).isSet())) + { + + u32NSECContent |= static_cast(enuContentFlag::A); + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_A(_getResponderIPAddress(pNetIf, enuIPProtocolType::V4), p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_A(A) FAILED!\n"), _DH());); + } + // PTR_IPv4 + if ((bResult) && + (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::PTR_IPv4)) && + (_getResponderIPAddress(pNetIf, enuIPProtocolType::V4).isSet())) + { + + u32NSECContent |= static_cast(enuContentFlag::PTR_IPv4); + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_PTR_IPv4(_getResponderIPAddress(pNetIf, enuIPProtocolType::V4), p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_PTR_IPv4 FAILED!\n"), _DH());); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + // AAAA + if ((bResult) && + (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::AAAA)) && + (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)).isSet())) + { + + u32NSECContent |= static_cast(enuContentFlag::AAAA); + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_AAAA(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)), p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_AAAA(A) FAILED!\n"), _DH());); + } + // PTR_IPv6 + if ((bResult) && + (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::PTR_IPv6)) && + (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)).isSet())) + { + + u32NSECContent |= static_cast(enuContentFlag::PTR_IPv6); + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_PTR_IPv6(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)), p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_PTR_IPv6 FAILED!\n"), _DH());); + } +#endif + + for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) + { + clsService* pService = *it; + + // PTR_TYPE + if ((bResult) && + (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_TYPE))) + { + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_PTR_TYPE(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_PTR_TYPE FAILED!\n"), _DH());); + } + // PTR_NAME + if ((bResult) && + (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_NAME))) + { + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_PTR_NAME(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_PTR_NAME FAILED!\n"), _DH());); + } + // SRV + if ((bResult) && + (pService->m_u32ReplyMask & static_cast(enuContentFlag::SRV))) + { + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_SRV(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_SRV(A) FAILED!\n"), _DH());); + } + // TXT + if ((bResult) && + (pService->m_u32ReplyMask & static_cast(enuContentFlag::TXT))) + { + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_TXT(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_TXT(A) FAILED!\n"), _DH());); + } + } // for services + + // Additional answers + uint16_t& ru16AdditionalAnswers = msgHeader.m_u16ARCount; + +#ifdef MDNS_IPV4_SUPPORT + bool bNeedsAdditionalAnswerA = false; +#endif +#ifdef MDNS_IPV6_SUPPORT + bool bNeedsAdditionalAnswerAAAA = false; +#endif + for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) + { + clsService* pService = *it; + + if ((bResult) && + (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_NAME)) && // If PTR_NAME is requested, AND + (!(pService->m_u32ReplyMask & static_cast(enuContentFlag::SRV)))) // NOT SRV -> add SRV as additional answer + { + + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16AdditionalAnswers + : (bResult = _writeMDNSAnswer_SRV(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_SRV(B) FAILED!\n"), _DH());); + } + /* AppleTV doesn't add TXT + if ((bResult) && + (pService->m_u32ReplyMask & ContentFlag_PTR_NAME) && // If PTR_NAME is requested, AND + (!(pService->m_u32ReplyMask & ContentFlag_TXT))) { // NOT TXT -> add TXT as additional answer + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16AdditionalAnswers + : (bResult = _writeMDNSAnswer_TXT(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_TXT(B) FAILED!\n"));); + } + */ + if ((pService->m_u32ReplyMask & (static_cast(enuContentFlag::PTR_NAME) | static_cast(enuContentFlag::SRV))) || // If service instance name or SRV OR + (p_rSendParameter.m_u32HostReplyMask & (static_cast(enuContentFlag::A) | static_cast(enuContentFlag::AAAA)))) // any host IP address is requested + { +#ifdef MDNS_IPV4_SUPPORT + if ((bResult) && + (!(p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::A)))) // Add IPv4 address + { + bNeedsAdditionalAnswerA = true; + } +#endif +#ifdef MDNS_IPV6_SUPPORT + if ((bResult) && + (!(p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::AAAA)))) // Add IPv6 address + { + bNeedsAdditionalAnswerAAAA = true; + } +#endif + } + // NSEC record for service + if ((bResult) && + (pService->m_u32ReplyMask) && + ((clsSendParameter::enuResponseType::None != p_rSendParameter.m_Response))) + { + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16AdditionalAnswers + : (bResult = _writeMDNSAnswer_NSEC(*pService, (static_cast(enuContentFlag::TXT) | static_cast(enuContentFlag::SRV)), p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_NSEC(Service) FAILED!\n"), _DH());); + } + } // for services + +#ifdef MDNS_IPV4_SUPPORT + // Answer A needed? + if ((bResult) && + (bNeedsAdditionalAnswerA) && + (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4)).isSet())) + { + // Additional A + u32NSECContent |= static_cast(enuContentFlag::A); + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16AdditionalAnswers + : (bResult = _writeMDNSAnswer_A(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4)), p_rSendParameter))); + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_A(B) FAILED!\n"), _DH());); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + // Answer AAAA needed? + if ((bResult) && + (bNeedsAdditionalAnswerAAAA) && + (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)).isSet())) + { + // Additional AAAA + u32NSECContent |= static_cast(enuContentFlag::AAAA); + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16AdditionalAnswers + : (bResult = _writeMDNSAnswer_AAAA(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)), p_rSendParameter))); + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_AAAA(B) FAILED!\n"), _DH());); + } +#endif + + // NSEC host (part 2) + if ((bResult) && + ((clsSendParameter::enuResponseType::None != p_rSendParameter.m_Response)) && + (u32NSECContent)) + { + // NSEC PTR IPv4/IPv6 are separate answers; make sure, that this is counted for +#ifdef MDNS_IPV4_SUPPORT + uint32_t u32NSECContent_PTR_IPv4 = (u32NSECContent & static_cast(enuContentFlag::PTR_IPv4)); + u32NSECContent &= ~static_cast(enuContentFlag::PTR_IPv4); +#endif +#ifdef MDNS_IPV6_SUPPORT + uint32_t u32NSECContent_PTR_IPv6 = (u32NSECContent & static_cast(enuContentFlag::PTR_IPv6)); + u32NSECContent &= ~static_cast(enuContentFlag::PTR_IPv6); +#endif + + ((static_cast(enuSequence::Count) == sequence) + ? (ru16AdditionalAnswers += ((u32NSECContent ? 1 : 0) +#ifdef MDNS_IPV4_SUPPORT + + (u32NSECContent_PTR_IPv4 ? 1 : 0) +#endif +#ifdef MDNS_IPV6_SUPPORT + + (u32NSECContent_PTR_IPv6 ? 1 : 0) +#endif + )) + : (bResult = (((!u32NSECContent) || + // Write host domain NSEC answer + (_writeMDNSAnswer_NSEC(u32NSECContent, p_rSendParameter))) +#ifdef MDNS_IPV4_SUPPORT + // Write separate answer for host PTR IPv4 + && ((!u32NSECContent_PTR_IPv4) || + ((!_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4)).isSet()) || + (_writeMDNSAnswer_NSEC_PTR_IPv4(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4)), p_rSendParameter)))) +#endif +#ifdef MDNS_IPV6_SUPPORT + // Write separate answer for host PTR IPv6 + && ((!u32NSECContent_PTR_IPv6) || + ((!_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)).isSet()) || + (_writeMDNSAnswer_NSEC_PTR_IPv6(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)), p_rSendParameter)))) +#endif + ))); + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_NSEC(Host) FAILED!\n"), _DH());); + } + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: Loop %i FAILED!\n"), _DH(), sequence);); + } // for sequence + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_addQueryRecord + + Adds a query for the given domain and query type. + +*/ +bool clsLEAMDNSHost::_addQueryRecord(clsLEAMDNSHost::clsSendParameter& p_rSendParameter, + const clsLEAMDNSHost::clsRRDomain& p_QueryDomain, + uint16_t p_u16RecordType) +{ + bool bResult = false; + + clsRRQuestion* pNewRRQuestion = new clsRRQuestion; + if ((bResult = (0 != pNewRRQuestion))) + { + // Link to list of questions + p_rSendParameter.m_RRQuestions.push_back(pNewRRQuestion); + + pNewRRQuestion->m_Header.m_Domain = p_QueryDomain; + + pNewRRQuestion->m_Header.m_Attributes.m_u16Type = p_u16RecordType; + // It seems, that some mDNS implementations don't support 'unicast response' questions... + pNewRRQuestion->m_Header.m_Attributes.m_u16Class = (/*0x8000 |*/ DNS_RRCLASS_IN); // /*Unicast &*/ INternet + } + return bResult; +} + +/* + MDNSResponder::_sendQuery + + Creates and sends a query for the given domain and query type. + +*/ +bool clsLEAMDNSHost::_sendQuery(const clsLEAMDNSHost::clsQuery& p_Query, + clsLEAMDNSHost::clsQuery::clsAnswer::list* p_pKnownAnswers /*= 0*/) +{ + bool bResult = false; + + clsSendParameter sendParameter; + switch (p_Query.m_QueryType) + { + case clsQuery::enuQueryType::Host: +#ifdef MDNS_IPV4_SUPPORT + bResult = _addQueryRecord(sendParameter, p_Query.m_Domain, DNS_RRTYPE_A); +#endif +#ifdef MDNS_IPV6_SUPPORT + bResult = _addQueryRecord(sendParameter, p_Query.m_Domain, DNS_RRTYPE_AAAA); +#endif + break; + + case clsQuery::enuQueryType::Service: + bResult = _addQueryRecord(sendParameter, p_Query.m_Domain, DNS_RRTYPE_PTR); + break; + + case clsQuery::enuQueryType::None: + default: + break; + } + + // TODO: Add known answers to query + (void)p_pKnownAnswers; + bResult = ((bResult) && + (_sendMessage(sendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendQuery: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_sendQuery + + Creates and sends a query for the given domain and record type. + +*/ +bool clsLEAMDNSHost::_sendQuery(const clsLEAMDNSHost::clsRRDomain& p_QueryDomain, + uint16_t p_u16RecordType, + clsLEAMDNSHost::clsQuery::clsAnswer::list* p_pKnownAnswers /*= 0*/) +{ + bool bResult = false; + + clsSendParameter sendParameter; + bResult = ((_addQueryRecord(sendParameter, p_QueryDomain, p_u16RecordType)) && + (_sendMessage(sendParameter))); + + // TODO: Add known answer records + (void) p_pKnownAnswers; + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendQuery: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_getResponderIPAddress +*/ +IPAddress clsLEAMDNSHost::_getResponderIPAddress(netif* pNetIf, enuIPProtocolType p_IPProtocolType) const +{ + IPAddress ipResponder; +#ifdef MDNS_IPV4_SUPPORT + if (enuIPProtocolType::V4 == p_IPProtocolType) + { + ipResponder = netif_ip_addr4(pNetIf); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + if (enuIPProtocolType::V6 == p_IPProtocolType) + { + bool bCheckLinkLocal = true; + for (int i = 0; ((!ipResponder.isSet()) && (i < 2)); ++i) // Two loops: First with link-local check, second without + { + for (int idx = 0; idx < LWIP_IPV6_NUM_ADDRESSES; ++idx) + { + //DEBUG_EX_INFO(if ip6_addr_isvalid(netif_ip6_addr_state(&pNetIf, idx)) DEBUG_OUTPUT.printf_P(PSTR("%s _getResponderIPAddress: Checking IPv6 address %s (LL: %s)\n"), _DH(), IPAddress(netif_ip_addr6(pNetIf, idx)).toString().c_str(), (bCheckLinkLocal ? "YES" : "NO"));); + if ((ip6_addr_isvalid(netif_ip6_addr_state(pNetIf, idx))) && + (((!bCheckLinkLocal) || + (ip6_addr_islinklocal(netif_ip6_addr(pNetIf, idx)))))) + { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _getResponderIPAddress: Selected IPv6 address %s (LL: %s)\n"), _DH(), IPAddress(netif_ip_addr6(pNetIf, idx)).toString().c_str(), (bCheckLinkLocal ? "YES" : "NO"));); + ipResponder = netif_ip_addr6(pNetIf, idx); + break; + } + } + bCheckLinkLocal = false; + } + } +#endif + return ipResponder; +} + + +/** + HELPERS +*/ + +/** + RESOURCE RECORDS +*/ + +/* + MDNSResponder::_readRRQuestion + + Reads a question (eg. MyESP._http._tcp.local ANY IN) from the UPD input buffer. + +*/ +bool clsLEAMDNSHost::_readRRQuestion(clsLEAMDNSHost::clsRRQuestion& p_rRRQuestion) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _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("%s _readRRQuestion "), _DH()); + _printRRDomain(p_rRRQuestion.m_Header.m_Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:%s Class:%s\n"), _RRType2Name(p_rRRQuestion.m_Header.m_Attributes.m_u16Type), _RRClass2String(p_rRRQuestion.m_Header.m_Attributes.m_u16Class, true)); + ); + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRQuestion: FAILED!\n"), _DH());); + 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 clsLEAMDNSHost::_readRRAnswer(clsLEAMDNSHost::clsRRAnswer*& p_rpRRAnswer) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswer\n"));); + + bool bResult = false; + + clsRRHeader header; + uint32_t u32TTL; + uint16_t u16RDLength; + if ((_readRRHeader(header)) && + (_udpRead32(u32TTL)) && + (_udpRead16(u16RDLength))) + { + /* DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _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_IPV4_SUPPORT + case DNS_RRTYPE_A: + p_rpRRAnswer = new clsRRAnswerA(header, u32TTL); + bResult = _readRRAnswerA(*(clsRRAnswerA*&)p_rpRRAnswer, u16RDLength); + break; +#endif + case DNS_RRTYPE_PTR: + p_rpRRAnswer = new clsRRAnswerPTR(header, u32TTL); + bResult = _readRRAnswerPTR(*(clsRRAnswerPTR*&)p_rpRRAnswer, u16RDLength); + break; + case DNS_RRTYPE_TXT: + p_rpRRAnswer = new clsRRAnswerTXT(header, u32TTL); + bResult = _readRRAnswerTXT(*(clsRRAnswerTXT*&)p_rpRRAnswer, u16RDLength); + break; +#ifdef MDNS_IPV6_SUPPORT + case DNS_RRTYPE_AAAA: + p_rpRRAnswer = new clsRRAnswerAAAA(header, u32TTL); + bResult = _readRRAnswerAAAA(*(clsRRAnswerAAAA*&)p_rpRRAnswer, u16RDLength); + break; +#endif + case DNS_RRTYPE_SRV: + p_rpRRAnswer = new clsRRAnswerSRV(header, u32TTL); + bResult = _readRRAnswerSRV(*(clsRRAnswerSRV*&)p_rpRRAnswer, u16RDLength); + break; + default: + p_rpRRAnswer = new clsRRAnswerGeneric(header, u32TTL); + bResult = _readRRAnswerGeneric(*(clsRRAnswerGeneric*&)p_rpRRAnswer, u16RDLength); + break; + } + + DEBUG_EX_INFO_IF((bResult) && (p_rpRRAnswer), + { + DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswer: "), _DH()); + _printRRDomain(p_rpRRAnswer->m_Header.m_Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:%s Class:0x%04X TTL:%u, RDLength:%u "), + _RRType2Name(p_rpRRAnswer->m_Header.m_Attributes.m_u16Type), + (p_rpRRAnswer->m_Header.m_Attributes.m_u16Class | (p_rpRRAnswer->m_bCacheFlush ? 0x8000 : 0)), + p_rpRRAnswer->m_u32TTL, + u16RDLength); + switch (header.m_Attributes.m_u16Type /*& (~0x8000)*/) // Topmost bit might carry 'cache flush' flag + { +#ifdef MDNS_IPV4_SUPPORT + case DNS_RRTYPE_A: + DEBUG_OUTPUT.printf_P(PSTR("A IP:%s"), ((clsRRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); + break; +#endif + case DNS_RRTYPE_PTR: + DEBUG_OUTPUT.printf_P(PSTR("PTR ")); + _printRRDomain(((clsRRAnswerPTR*&)p_rpRRAnswer)->m_PTRDomain); + break; + case DNS_RRTYPE_TXT: + { + size_t stTxtLength = ((clsRRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_strLength(); + char* pTxts = new char[stTxtLength]; + if (pTxts) + { + ((clsRRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_str(pTxts); + DEBUG_OUTPUT.printf_P(PSTR("TXT(%u) %s"), stTxtLength, pTxts); + delete[] pTxts; + } + break; + } +#ifdef MDNS_IPV6_SUPPORT + case DNS_RRTYPE_AAAA: + DEBUG_OUTPUT.printf_P(PSTR("AAAA IP:%s"), ((clsRRAnswerAAAA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); + break; +#endif + case DNS_RRTYPE_SRV: + DEBUG_OUTPUT.printf_P(PSTR("SRV Port:%u "), ((clsRRAnswerSRV*&)p_rpRRAnswer)->m_u16Port); + _printRRDomain(((clsRRAnswerSRV*&)p_rpRRAnswer)->m_SRVDomain); + break; + /* case DNS_RRTYPE_NSEC: + DEBUG_OUTPUT.printf_P(PSTR("NSEC ")); + _printRRDomain(((stcRRAnswerNSEC*&)p_rpRRAnswer)->m_NSECDomain); + for (uint32_t u=0; u<(((stcRRAnswerNSEC*&)p_rpRRAnswer)->m_pNSECBitmap->m_u16BitmapLength * 8); ++u) { + uint8_t byte = ((stcRRAnswerNSEC*&)p_rpRRAnswer)->m_pNSECBitmap->m_pu8BitmapData[u / 8]; + uint8_t flag = 1 << (7 - (u % 8)); // (7 - (0..7)) = 7..0 + if (byte & flag) { + DEBUG_OUTPUT.printf_P(PSTR(" %s"), _RRType2Name(u)); + } + } + break;*/ + default: + DEBUG_OUTPUT.printf_P(PSTR("generic ")); + break; + } + DEBUG_OUTPUT.printf_P(PSTR("\n")); + }); // DEBUG_EX_INFO + + DEBUG_EX_INFO_IF(!((bResult) && (p_rpRRAnswer)), + { + DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswer: FAILED to read specific answer of type 0x%04X!\n"), _DH(), p_rpRRAnswer->m_Header.m_Attributes.m_u16Type); + }); + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswer: FAILED!\n"), _DH());); + return bResult; +} + +#ifdef MDNS_IPV4_SUPPORT +/* + MDNSResponder::_readRRAnswerA +*/ +bool clsLEAMDNSHost::_readRRAnswerA(clsLEAMDNSHost::clsRRAnswerA& p_rRRAnswerA, + uint16_t p_u16RDLength) +{ + uint32_t u32IPv4Address; + bool bResult = ((clsConsts::u16IPv4Size == p_u16RDLength) && + (_udpReadBuffer((unsigned char*)&u32IPv4Address, clsConsts::u16IPv4Size)) && + ((p_rRRAnswerA.m_IPAddress = IPAddress(u32IPv4Address)))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerA: FAILED!\n"), _DH());); + return bResult; +} +#endif + +/* + MDNSResponder::_readRRAnswerPTR +*/ +bool clsLEAMDNSHost::_readRRAnswerPTR(clsLEAMDNSHost::clsRRAnswerPTR& 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("%s _readRRAnswerPTR: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_readRRAnswerTXT + + Read TXT items from a buffer like 4c#=15ff=20 +*/ +bool clsLEAMDNSHost::_readRRAnswerTXT(clsLEAMDNSHost::clsRRAnswerTXT& p_rRRAnswerTXT, + uint16_t p_u16RDLength) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: RDLength:%u\n"), _DH(), 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; + + clsServiceTxt* pTxt = 0; + unsigned char ucLength = *pucCursor++; // Length of the next txt item + if (ucLength) + { + DEBUG_EX_INFO( + char sacBuffer[64]; + *sacBuffer = 0; + uint8_t u8MaxLength = ((ucLength > (sizeof(sacBuffer) - 1)) ? (sizeof(sacBuffer) - 1) : ucLength); + os_strncpy(sacBuffer, (const char*)pucCursor, u8MaxLength + 1); + sacBuffer[u8MaxLength] = 0; + DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: Item(%u): %s\n"), _DH(), 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 clsServiceTxt)) && + (pTxt->setKey((const char*)pucCursor, ucKeyLength)) && + (pTxt->setValue((const char*)(pucEqualSign + 1), ucValueLength))); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: INVALID TXT format (No '=')!\n"), _DH());); + } + pucCursor += ucLength; + } + else // no/zero length TXT + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: INFO! TXT answer contains no items.\n"), _DH());); + bResult = true; + } + + if ((bResult) && + (pTxt)) + { + // Everythings fine so far + // Link TXT item to answer TXTs + p_rRRAnswerTXT.m_Txts.add(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("%s _readRRAnswerTXT: FAILED to read TXT item!\n"), _DH()); + 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("%s _readRRAnswerTXT: FAILED to read TXT content!\n"), _DH());); + } + // Clean up + delete[] pucBuffer; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: FAILED to alloc buffer for TXT content!\n"), _DH());); + } + } + else + { + DEBUG_EX_ERR( + DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: WARNING! No content in TXT answer from "), _DH()); + _printRRDomain(p_rRRAnswerTXT.m_Header.m_Domain); + DEBUG_OUTPUT.printf_P(PSTR("\n")); + ); + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: FAILED!\n"), _DH());); + return bResult; +} + +#ifdef MDNS_IPV6_SUPPORT +bool clsLEAMDNSHost::_readRRAnswerAAAA(clsLEAMDNSHost::clsRRAnswerAAAA& p_rRRAnswerAAAA, + uint16_t p_u16RDLength) +{ + bool bResult = false; + + uint32_t au32IPv6Address[4]; // 16 bytes + if ((bResult = ((clsConsts::u16IPv6Size == p_u16RDLength) && + (_udpReadBuffer((uint8_t*)&au32IPv6Address[0], clsConsts::u16IPv6Size))))) + { + // ?? IPADDR6_INIT_HOST ?? + ip_addr_t addr = IPADDR6_INIT(au32IPv6Address[0], au32IPv6Address[1], au32IPv6Address[2], au32IPv6Address[3]); + p_rRRAnswerAAAA.m_IPAddress = IPAddress(addr); + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerAAAA: FAILED!\n"), _DH());); + return bResult; +} +#endif + +/* + MDNSResponder::_readRRAnswerSRV +*/ +bool clsLEAMDNSHost::_readRRAnswerSRV(clsLEAMDNSHost::clsRRAnswerSRV& 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("%s _readRRAnswerSRV: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_readRRAnswerGeneric +*/ +bool clsLEAMDNSHost::_readRRAnswerGeneric(clsLEAMDNSHost::clsRRAnswerGeneric& 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("%s _readRRAnswerGeneric: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_readRRHeader +*/ +bool clsLEAMDNSHost::_readRRHeader(clsLEAMDNSHost::clsRRHeader& p_rRRHeader) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRHeader\n"));); + + bool bResult = ((_readRRDomain(p_rRRHeader.m_Domain)) && + (_readRRAttributes(p_rRRHeader.m_Attributes))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRHeader: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_readRRDomain + + Reads a (maybe multilevel compressed) domain from the UDP input buffer. + +*/ +bool clsLEAMDNSHost::_readRRDomain(clsLEAMDNSHost::clsRRDomain& p_rRRDomain) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain\n"));); + + bool bResult = ((p_rRRDomain.clear()) && + (_readRRDomain_Loop(p_rRRDomain, 0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain: FAILED!\n"), _DH());); + 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 clsConsts::u8DomainMaxRedirections. + +*/ +bool clsLEAMDNSHost::_readRRDomain_Loop(clsLEAMDNSHost::clsRRDomain& p_rRRDomain, + uint8_t p_u8Depth) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u)\n"), _DH(), p_u8Depth);); + + bool bResult = false; + + if (clsConsts::u8DomainMaxRedirections >= p_u8Depth) + { + bResult = true; + + uint8_t u8Len = 0; + do + { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u): Offset:%u p0:%02x\n"), _DH(), p_u8Depth, m_pUDPContext->tell(), m_pUDPContext->peek());); + _udpRead8(u8Len); + + if (u8Len & clsConsts::u8DomainCompressMark) + { + // Compressed label(s) + uint16_t u16Offset = ((u8Len & ~clsConsts::u8DomainCompressMark) << 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("%s _readRRDomain_Loop(%u): Redirecting from %u to %u!\n"), _DH(), 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("%s _readRRDomain_Loop(%u): Succeeded to read redirected label! Returning to %u\n"), _DH(), p_u8Depth, stCurrentPosition);); + m_pUDPContext->seek(stCurrentPosition); // Restore after recursion + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u): FAILED to read redirected label!\n"), _DH(), p_u8Depth);); + bResult = false; + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u): INVALID offset in redirection!\n"), _DH(), p_u8Depth);); + bResult = false; + } + break; + } + else + { + // Normal (uncompressed) label (maybe '\0' only) + if (clsConsts::stDomainMaxLength > (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("%s _readRRDomain_Loop(%u): Domain label (%u): %s\n"), _DH(), 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("%s _readRRDomain_Loop(2) offset:%u p0:%x\n"), _DH(), m_pUDPContext->tell(), m_pUDPContext->peek());); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u): ERROR! Domain name too long (%u + %u)!\n"), _DH(), p_u8Depth, p_rRRDomain.m_u16NameLength, u8Len);); + bResult = false; + break; + } + } + } while ((bResult) && + (0 != u8Len)); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u): ERROR! Too many redirections!\n"), _DH(), p_u8Depth);); + } + return bResult; +} + +/* + MDNSResponder::_readRRAttributes + +*/ +bool clsLEAMDNSHost::_readRRAttributes(clsLEAMDNSHost::clsRRAttributes& p_rRRAttributes) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAttributes\n"));); + + bool bResult = ((_udpRead16(p_rRRAttributes.m_u16Type)) && + (_udpRead16(p_rRRAttributes.m_u16Class))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAttributes: FAILED!\n"), _DH());); + return bResult; +} + + +/* + + DOMAIN NAMES + +*/ + +/* + MDNSResponder::_buildDomainForHost + + Builds a MDNS host domain (eg. esp8266.local) for the given hostname. + +*/ +bool clsLEAMDNSHost::_buildDomainForHost(const char* p_pcHostName, + clsLEAMDNSHost::clsRRDomain& p_rHostDomain) const +{ + + p_rHostDomain.clear(); + bool bResult = ((p_pcHostName) && + (*p_pcHostName) && + (p_rHostDomain.addLabel(p_pcHostName)) && + (p_rHostDomain.addLabel(clsConsts::pcLocal)) && + (p_rHostDomain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _buildDomainForHost: FAILED!\n"), _DH());); + 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 clsLEAMDNSHost::_buildDomainForDNSSD(clsLEAMDNSHost::clsRRDomain& p_rDNSSDDomain) const +{ + p_rDNSSDDomain.clear(); + bool bResult = ((p_rDNSSDDomain.addLabel(clsConsts::pcServices, true)) && + (p_rDNSSDDomain.addLabel(clsConsts::pcDNSSD, true)) && + (p_rDNSSDDomain.addLabel(clsConsts::pcUDP, true)) && + (p_rDNSSDDomain.addLabel(clsConsts::pcLocal)) && + (p_rDNSSDDomain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _buildDomainForDNSSD: FAILED!\n"), _DH());); + 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 clsLEAMDNSHost::_buildDomainForService(const clsLEAMDNSHost::clsService& p_Service, + bool p_bIncludeName, + clsLEAMDNSHost::clsRRDomain& p_rServiceDomain) const +{ + p_rServiceDomain.clear(); + bool bResult = (((!p_bIncludeName) || + (p_rServiceDomain.addLabel(p_Service.instanceName()))) && + (p_rServiceDomain.addLabel(p_Service.type(), ('_' != *p_Service.type()))) && + (p_rServiceDomain.addLabel(p_Service.protocol(), ('_' != *p_Service.protocol()))) && + (p_rServiceDomain.addLabel(clsConsts::pcLocal)) && + (p_rServiceDomain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _buildDomainForService: FAILED!\n"), _DH());); + 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 clsLEAMDNSHost::_buildDomainForService(const char* p_pcServiceType, + const char* p_pcProtocol, + clsLEAMDNSHost::clsRRDomain& p_rServiceDomain) const +{ + p_rServiceDomain.clear(); + bool bResult = ((p_pcServiceType) && + (p_pcProtocol) && + (p_rServiceDomain.addLabel(p_pcServiceType, ('_' != *p_pcServiceType))) && + (p_rServiceDomain.addLabel(p_pcProtocol, ('_' != *p_pcProtocol))) && + (p_rServiceDomain.addLabel(clsConsts::pcLocal)) && + (p_rServiceDomain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _buildDomainForService: FAILED for (%s.%s)!\n"), _DH(), (p_pcServiceType ? : "-"), (p_pcProtocol ? : "-"));); + return bResult; +} + +#ifdef MDNS_IPV4_SUPPORT +/* + MDNSResponder::_buildDomainForReverseIPv4 + + The IPv4 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 IPv4 questions and answering these + +*/ +bool clsLEAMDNSHost::_buildDomainForReverseIPv4(IPAddress p_IPv4Address, + clsLEAMDNSHost::clsRRDomain& p_rReverseIPv4Domain) const +{ + bool bResult = ((p_IPv4Address.isSet()) && + (p_IPv4Address.isV4())); + + p_rReverseIPv4Domain.clear(); + + char acBuffer[32]; + for (int i = clsConsts::u16IPv4Size; ((bResult) && (i >= 1)); --i) + { + itoa(p_IPv4Address[i - 1], acBuffer, 10); + bResult = p_rReverseIPv4Domain.addLabel(acBuffer); + } + bResult = ((bResult) && + (p_rReverseIPv4Domain.addLabel(clsConsts::pcReverseIPv4Domain)) && + (p_rReverseIPv4Domain.addLabel(clsConsts::pcReverseTopDomain)) && + (p_rReverseIPv4Domain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _buildDomainForReverseIPv4: FAILED!\n"), _DH());); + return bResult; +} +#endif + +#ifdef MDNS_IPV6_SUPPORT +/* + MDNSResponder::_buildDomainForReverseIPv6 + + The IPv6 address is stringized by printing the 16 address bytes (32 nibbles) into a char buffer in reverse order + and adding 'ip6.arpa' (eg. 3.B.6.E.A.1.B.B.A.B.F.7.F.8.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.8.E.F.ip6.arpa). + Used while detecting reverse IPv6 questions and answering these + +*/ +bool clsLEAMDNSHost::_buildDomainForReverseIPv6(IPAddress p_IPv6Address, + clsLEAMDNSHost::clsRRDomain& p_rReverseIPv6Domain) const +{ + bool bResult = ((p_IPv6Address.isSet()) && + (p_IPv6Address.isV6())); + + p_rReverseIPv6Domain.clear(); + + const uint16_t* pRaw = p_IPv6Address.raw6(); + for (int8_t i8 = (clsConsts::u16IPv6Size / 2); ((bResult) && (i8 > 0)); --i8) // 8..1 + { + uint16_t u16Part = ntohs(pRaw[i8 - 1] & 0xFFFF); + char acBuffer[2]; + for (uint8_t u8 = 0; ((bResult) && (u8 < 4)); ++u8) // 0..3 + { + itoa((u16Part & 0xF), acBuffer, 16); + bResult = p_rReverseIPv6Domain.addLabel(acBuffer); + u16Part >>= 4; + } + } + bResult = ((bResult) && + (p_rReverseIPv6Domain.addLabel(clsConsts::pcReverseIPv6Domain)) && // .ip6.arpa + (p_rReverseIPv6Domain.addLabel(clsConsts::pcReverseTopDomain)) && // .local + (p_rReverseIPv6Domain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _buildDomainForReverseIPv6: FAILED!\n"), _DH());); + return bResult; +} +#endif + + +/* + + UDP + +*/ + +/* + MDNSResponder::_udpReadBuffer + +*/ +bool clsLEAMDNSHost::_udpReadBuffer(unsigned char* p_pBuffer, + size_t p_stLength) +{ + bool bResult = ((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("%s _udpReadBuffer: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_udpRead8 + +*/ +bool clsLEAMDNSHost::_udpRead8(uint8_t& p_ru8Value) +{ + return _udpReadBuffer((unsigned char*)&p_ru8Value, sizeof(p_ru8Value)); +} + +/* + MDNSResponder::_udpRead16 + +*/ +bool clsLEAMDNSHost::_udpRead16(uint16_t& p_ru16Value) +{ + bool bResult = false; + + if (_udpReadBuffer((unsigned char*)&p_ru16Value, sizeof(p_ru16Value))) + { + p_ru16Value = lwip_ntohs(p_ru16Value); + bResult = true; + } + return bResult; +} + +/* + MDNSResponder::_udpRead32 + +*/ +bool clsLEAMDNSHost::_udpRead32(uint32_t& p_ru32Value) +{ + bool bResult = false; + + if (_udpReadBuffer((unsigned char*)&p_ru32Value, sizeof(p_ru32Value))) + { + p_ru32Value = lwip_ntohl(p_ru32Value); + bResult = true; + } + return bResult; +} + +/* + MDNSResponder::_udpAppendBuffer + +*/ +bool clsLEAMDNSHost::_udpAppendBuffer(const unsigned char* p_pcBuffer, + size_t p_stLength) +{ + bool bResult = ((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("%s _udpAppendBuffer: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_udpAppend8 + +*/ +bool clsLEAMDNSHost::_udpAppend8(uint8_t p_u8Value) +{ + return (_udpAppendBuffer((unsigned char*)&p_u8Value, sizeof(p_u8Value))); +} + +/* + MDNSResponder::_udpAppend16 + +*/ +bool clsLEAMDNSHost::_udpAppend16(uint16_t p_u16Value) +{ + p_u16Value = lwip_htons(p_u16Value); + return (_udpAppendBuffer((unsigned char*)&p_u16Value, sizeof(p_u16Value))); +} + +/* + MDNSResponder::_udpAppend32 + +*/ +bool clsLEAMDNSHost::_udpAppend32(uint32_t p_u32Value) +{ + p_u32Value = lwip_htonl(p_u32Value); + return (_udpAppendBuffer((unsigned char*)&p_u32Value, sizeof(p_u32Value))); +} + +#ifdef DEBUG_ESP_PORT +/* + MDNSResponder::_udpDump + +*/ +bool clsLEAMDNSHost::_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 clsLEAMDNSHost::_udpDump(unsigned p_uOffset, + unsigned p_uLength) +{ + if (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 // DEBUG_ESP_PORT + + +/** + 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 from 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 clsLEAMDNSHost::_readMDNSMsgHeader(clsLEAMDNSHost::clsMsgHeader& 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/Response 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("%s _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"), + _DH(), + (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("%s _readMDNSMsgHeader: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_write8 + +*/ +bool clsLEAMDNSHost::_write8(uint8_t p_u8Value, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + return ((_udpAppend8(p_u8Value)) && + (p_rSendParameter.shiftOffset(sizeof(p_u8Value)))); +} + +/* + MDNSResponder::_write16 + +*/ +bool clsLEAMDNSHost::_write16(uint16_t p_u16Value, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + return ((_udpAppend16(p_u16Value)) && + (p_rSendParameter.shiftOffset(sizeof(p_u16Value)))); +} + +/* + MDNSResponder::_write32 + +*/ +bool clsLEAMDNSHost::_write32(uint32_t p_u32Value, + clsLEAMDNSHost::clsSendParameter& 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 from 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 clsLEAMDNSHost::_writeMDNSMsgHeader(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + /* DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _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"), + _DH(), + (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("%s _writeMDNSMsgHeader: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_writeRRAttributes + +*/ +bool clsLEAMDNSHost::_writeMDNSRRAttributes(const clsLEAMDNSHost::clsRRAttributes& p_Attributes, + clsLEAMDNSHost::clsSendParameter& 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("%s _writeMDNSRRAttributes: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_writeMDNSRRDomain + +*/ +bool clsLEAMDNSHost::_writeMDNSRRDomain(const clsLEAMDNSHost::clsRRDomain& p_Domain, + clsLEAMDNSHost::clsSendParameter& 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("%s _writeMDNSRRDomain: FAILED!\n"), _DH());); + 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 clsLEAMDNSHost::_writeMDNSHostDomain(const char* p_pcHostName, + bool p_bPrependRDLength, + uint16_t p_u16AdditionalLength, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV' + uint16_t u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)p_pcHostName, false); + + clsRRDomain hostDomain; + bool bResult = (u16CachedDomainOffset + // Found cached domain -> mark as compressed domain + ? ((clsConsts::u8DomainCompressMark > ((u16CachedDomainOffset >> 8) & ~clsConsts::u8DomainCompressMark)) && // Valid offset + ((!p_bPrependRDLength) || + (_write16((2 + p_u16AdditionalLength), p_rSendParameter))) && // Length of 'Cxxx' + (_write8(((u16CachedDomainOffset >> 8) | clsConsts::u8DomainCompressMark), 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_u16AdditionalLength), 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("%s _writeMDNSHostDomain: FAILED!\n"), _DH());); + 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 clsLEAMDNSHost::_writeMDNSServiceDomain(const clsLEAMDNSHost::clsService& p_Service, + bool p_bIncludeName, + bool p_bPrependRDLength, + uint16_t p_u16AdditionalLength, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV' + uint16_t u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)&p_Service, p_bIncludeName); + + clsRRDomain serviceDomain; + bool bResult = (u16CachedDomainOffset + // Found cached domain -> mark as compressed domain + ? ((clsConsts::u8DomainCompressMark > ((u16CachedDomainOffset >> 8) & ~clsConsts::u8DomainCompressMark)) && // Valid offset + ((!p_bPrependRDLength) || + (_write16((2 + p_u16AdditionalLength), p_rSendParameter))) && // Lenght of 'Cxxx' + (_write8(((u16CachedDomainOffset >> 8) | clsConsts::u8DomainCompressMark), 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_u16AdditionalLength), 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("%s _writeMDNSServiceDomain: FAILED!\n"), _DH());); + 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 clsLEAMDNSHost::_writeMDNSQuestion(clsLEAMDNSHost::clsRRQuestion& p_Question, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSQuestion\n"));); + + bool bResult = ((_writeMDNSRRDomain(p_Question.m_Header.m_Domain, p_rSendParameter)) && + (_writeMDNSRRAttributes(p_Question.m_Header.m_Attributes, p_rSendParameter))); + + DEBUG_EX_INFO(if (bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSQuestion "), _DH()); + _printRRDomain(p_Question.m_Header.m_Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:%s Class:0x%04X\n"), + _RRType2Name(p_Question.m_Header.m_Attributes.m_u16Type), + p_Question.m_Header.m_Attributes.m_u16Class); + }); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSQuestion: FAILED!\n"), _DH());); + return bResult; +} + + +#ifdef MDNS_IPV4_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 clsLEAMDNSHost::_writeMDNSAnswer_A(IPAddress p_IPAddress, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_A (%s)%s\n"), p_IPAddress.toString().c_str(), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); + + clsRRAttributes attributes(DNS_RRTYPE_A, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + const unsigned char aucIPAddress[clsConsts::u16IPv4Size] = { p_IPAddress[0], p_IPAddress[1], p_IPAddress[2], p_IPAddress[3] }; + bool bResult = ((p_IPAddress.isV4()) && + (_writeMDNSHostDomain(m_pcHostName, false, 0, p_rSendParameter)) && + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), p_rSendParameter)) && // TTL + (_write16(clsConsts::u16IPv4Size, p_rSendParameter)) && // RDLength + (_udpAppendBuffer(aucIPAddress, clsConsts::u16IPv4Size)) && // RData + (p_rSendParameter.shiftOffset(clsConsts::u16IPv4Size))); + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_A %s.local Type:%s Class:0x%04X TTL:%u %s\n"), + _DH(), + m_pcHostName, + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), + p_IPAddress.toString().c_str()); + ); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_A: FAILED!\n"), _DH());); + return bResult; + +} + +/* + MDNSResponder::_writeMDNSAnswer_PTR_IPv4 + + Write a MDNS reverse IPv4 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 IPv4 questions + +*/ +bool clsLEAMDNSHost::_writeMDNSAnswer_PTR_IPv4(IPAddress p_IPAddress, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv4 (%s)%s\n"), p_IPAddress.toString().c_str(), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); + + clsRRDomain reverseIPv4Domain; + clsRRAttributes attributes(DNS_RRTYPE_PTR, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + clsRRDomain hostDomain; + bool bResult = ((p_IPAddress.isV4()) && + (_buildDomainForReverseIPv4(p_IPAddress, reverseIPv4Domain)) && // 012.789.456.123.in-addr.arpa + (_writeMDNSRRDomain(reverseIPv4Domain, p_rSendParameter)) && + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), p_rSendParameter)) && // TTL + (_writeMDNSHostDomain(m_pcHostName, true, 0, p_rSendParameter))); // RDLength & RData (host domain, eg. esp8266.local) + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv4 "), _DH()); + _printRRDomain(reverseIPv4Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:%s Class:0x%04X TTL:%u %s.local\n"), + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), + m_pcHostName); + ); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv4: FAILED!\n"), _DH());); + 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 clsLEAMDNSHost::_writeMDNSAnswer_PTR_TYPE(clsLEAMDNSHost::clsService& p_rService, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_TYPE\n"));); + + clsRRDomain dnssdDomain; + clsRRDomain serviceDomain; + clsRRAttributes attributes(DNS_RRTYPE_PTR, DNS_RRCLASS_IN); // No cache flush for shared records! only 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 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL)), p_rSendParameter)) && // TTL + (_writeMDNSServiceDomain(p_rService, false, true, 0, p_rSendParameter))); // RDLength & RData (service domain, eg. _http._tcp.local) + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_TYPE "), _DH()); + _printRRDomain(dnssdDomain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:%s Class:0x%04X TTL:%u _%s._%s.local\n"), + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL)), + p_rService.m_pcType, + p_rService.m_pcProtocol); + ); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_TYPE: FAILED!\n"), _DH());); + 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 clsLEAMDNSHost::_writeMDNSAnswer_PTR_NAME(clsLEAMDNSHost::clsService& p_rService, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_NAME\n"), _DH());); + + clsRRAttributes attributes(DNS_RRTYPE_PTR, DNS_RRCLASS_IN); // No cache flush for shared records! only INternet + bool bResult = ((_writeMDNSServiceDomain(p_rService, false, false, 0, p_rSendParameter)) && // _http._tcp.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL)), p_rSendParameter)) && // TTL + (_writeMDNSServiceDomain(p_rService, true, true, 0, p_rSendParameter))); // RDLength & RData (service domain, eg. MyESP._http._tcp.local) + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_NAME _%s._%s.local Type:%s Class:0x%04X TTL:%u %s._%s._%s.local\n"), + _DH(), + p_rService.m_pcType, + p_rService.m_pcProtocol, + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL)), + p_rService.m_pcInstanceName, + p_rService.m_pcType, + p_rService.m_pcProtocol); + ); + DEBUG_EX_ERR(if (!bResult)DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_NAME: FAILED!\n"), _DH());); + 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 clsLEAMDNSHost::_writeMDNSAnswer_TXT(clsLEAMDNSHost::clsService& p_rService, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_TXT%s\n"), (p_rSendParameter.m_bCacheFlush ? "" : " nF"), _DH());); + + bool bResult = false; + + clsRRAttributes 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, 0, p_rSendParameter)) && // MyESP._http._tcp.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery // TTL + ? clsConsts::u32LegacyTTL + : clsConsts::u32ServiceTTL)), p_rSendParameter)) && + (_write16((p_rService.m_Txts.count() // RDLength + ? p_rService.m_Txts.length() // default case + : 1), p_rSendParameter))) // If no TXT records exist, a single 0 byte is sent + { + bResult = true; + // RData Txts + if (p_rService.m_Txts.count()) + { + for (const clsServiceTxt* pTxt : p_rService.m_Txts.m_Txts) + { + unsigned char ucLengthByte = pTxt->length(); + if (!((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(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_TXT: FAILED to write %sTxt %s=%s!\n"), _DH(), (pTxt->m_bTemp ? "temp. " : ""), (pTxt->m_pcKey ? : "?"), (pTxt->m_pcValue ? : "?"));); + break; + } + } + } + else + { + // RFC 6763 Ch.6: Every DNS-SD service MUST have a TXT record in addition to its SRV record, ... + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_TXT: Adding EMPTY TXT record!\n"), _DH());); + unsigned char ucLengthByte = 0; + bResult = ((_udpAppendBuffer((unsigned char*)&ucLengthByte, sizeof(ucLengthByte))) && // Length + (p_rSendParameter.shiftOffset(sizeof(ucLengthByte)))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_TXT: FAILED to write EMPTY TXT record!\n"), _DH());); + } + } + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_TXT %s._%s._%s.local Type:%s Class:0x%04X TTL:%u \n"), + _DH(), + p_rService.m_pcInstanceName, + p_rService.m_pcType, + p_rService.m_pcProtocol, + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL))); + ); + + _releaseTempServiceTxts(p_rService); + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_TXT: FAILED!\n"), _DH());); + return bResult; +} + +#ifdef MDNS_IPV6_SUPPORT +/* + MDNSResponder::_writeMDNSAnswer_AAAA + + Write a MDNS AAAA answer to the UDP output buffer. + See: '_writeMDNSAnswer_AAAA' + + eg. esp8266.local AAAA 0x8001 120 16 xxxx::xx + http://www.zytrax.com/books/dns/ch8/aaaa.html + +*/ +bool clsLEAMDNSHost::_writeMDNSAnswer_AAAA(IPAddress p_IPAddress, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_AAAA (%s)%s\n"), p_IPAddress.toString().c_str(), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); + + clsRRAttributes attributes(DNS_RRTYPE_AAAA, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + bool bResult = ((p_IPAddress.isV6()) && + (_writeMDNSHostDomain(m_pcHostName, false, 0, p_rSendParameter)) && // esp8266.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), p_rSendParameter)) && // TTL + (_write16(clsConsts::u16IPv6Size, p_rSendParameter)) && // RDLength + (_udpAppendBuffer((uint8_t*)p_IPAddress.raw6(), clsConsts::u16IPv6Size)) && // RData + (p_rSendParameter.shiftOffset(clsConsts::u16IPv6Size))); + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_AAAA %s.local Type:%s Class:0x%04X TTL:%u %s\n"), + _DH(), + m_pcHostName, + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), + p_IPAddress.toString().c_str()); + ); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_AAAA: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_writeMDNSAnswer_PTR_IPv6 + + Write a MDNS reverse IPv6 PTR answer to the UDP output buffer. + See: '_writeMDNSAnswer_AAAA' + + eg. xxxx::xx.ip6.arpa PTR 0x8001 120 15 esp8266.local + Used while answering reverse IPv6 questions + +*/ +bool clsLEAMDNSHost::_writeMDNSAnswer_PTR_IPv6(IPAddress p_IPAddress, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv6%s\n"), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); + + clsRRDomain reverseIPv6Domain; + clsRRAttributes attributes(DNS_RRTYPE_PTR, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + bool bResult = ((p_IPAddress.isV6()) && + (_buildDomainForReverseIPv6(p_IPAddress, reverseIPv6Domain)) && // xxxx::xx.ip6.arpa + (_writeMDNSRRDomain(reverseIPv6Domain, p_rSendParameter)) && + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), p_rSendParameter)) && // TTL + (_writeMDNSHostDomain(m_pcHostName, true, 0, p_rSendParameter))); // RDLength & RData (host domain, eg. esp8266.local) + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv6 "), _DH()); + _printRRDomain(reverseIPv6Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:%s Class:0x%04X TTL:%u %s.local\n"), + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), + m_pcHostName); + ); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv6: FAILED!\n"), _DH());); + 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 clsLEAMDNSHost::_writeMDNSAnswer_SRV(clsLEAMDNSHost::clsService& p_rService, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_SRV%s\n"), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); + + uint16_t u16CachedDomainOffset = (p_rSendParameter.m_bLegacyDNSQuery + ? 0 + : p_rSendParameter.findCachedDomainOffset((const void*)m_pcHostName, false)); + + clsRRAttributes attributes(DNS_RRTYPE_SRV, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + clsRRDomain hostDomain; + bool bResult = ((_writeMDNSServiceDomain(p_rService, true, false, 0, p_rSendParameter)) && // MyESP._http._tcp.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL/*Consts::u32ServiceTTL*/)), 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(clsConsts::u16SRVPriority, p_rSendParameter)) && // Priority + (_write16(clsConsts::u16SRVWeight, 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 + : ((clsConsts::u8DomainCompressMark > ((u16CachedDomainOffset >> 8) & ~clsConsts::u8DomainCompressMark)) && // Valid offset + (_write16((sizeof(uint16_t /*Prio*/) + // RDLength + sizeof(uint16_t /*Weight*/) + + sizeof(uint16_t /*Port*/) + + 2), p_rSendParameter)) && // Length of 'C0xx' + (_write16(clsConsts::u16SRVPriority, p_rSendParameter)) && // Priority + (_write16(clsConsts::u16SRVWeight, p_rSendParameter)) && // Weight + (_write16(p_rService.m_u16Port, p_rSendParameter)) && // Port + (_write8(((u16CachedDomainOffset >> 8) | clsConsts::u8DomainCompressMark), p_rSendParameter)) && // Compression mark (and offset) + (_write8((uint8_t)u16CachedDomainOffset, p_rSendParameter))))); // Offset + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_SRV %s._%s._%s.local Type:%s Class:0x%04X TTL:%u %u %u %u %s.local\n"), + _DH(), + p_rService.m_pcInstanceName, + p_rService.m_pcType, + p_rService.m_pcProtocol, + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), + clsConsts::u16SRVPriority, + clsConsts::u16SRVWeight, + p_rService.m_u16Port, + m_pcHostName); + ); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_SRV: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_createNSECBitmap + +*/ +clsLEAMDNSHost::clsNSECBitmap* clsLEAMDNSHost::_createNSECBitmap(uint32_t p_u32NSECContent) +{ + // Currently 6 bytes (6*8 -> 0..47) are long enough, and only this is implemented + clsNSECBitmap* pNSECBitmap = new clsNSECBitmap; + if (pNSECBitmap) + { +#ifdef MDNS_IPV4_SUPPORT + if (p_u32NSECContent & static_cast(enuContentFlag::A)) + { + pNSECBitmap->setBit(DNS_RRTYPE_A); // 01/0x01 + } +#endif + if ((p_u32NSECContent & static_cast(enuContentFlag::PTR_IPv4)) || + (p_u32NSECContent & static_cast(enuContentFlag::PTR_IPv6))) + { + pNSECBitmap->setBit(DNS_RRTYPE_PTR); // 12/0x0C + } +#ifdef MDNS_IPV6_SUPPORT + if (p_u32NSECContent & static_cast(enuContentFlag::AAAA)) + { + pNSECBitmap->setBit(DNS_RRTYPE_AAAA); // 28/0x1C + } +#endif + if (p_u32NSECContent & static_cast(enuContentFlag::TXT)) + { + pNSECBitmap->setBit(DNS_RRTYPE_TXT); // 16/0x10 + } + if (p_u32NSECContent & static_cast(enuContentFlag::SRV)) + { + pNSECBitmap->setBit(DNS_RRTYPE_SRV); // 33/0x21 + } + if (p_u32NSECContent & static_cast(enuContentFlag::NSEC)) + { + pNSECBitmap->setBit(clsConsts::u8DNS_RRTYPE_NSEC); // 47/0x2F + } + } + return pNSECBitmap; +} + +/* + MDNSResponder::_writeMDNSNSECBitmap + +*/ +bool clsLEAMDNSHost::_writeMDNSNSECBitmap(const clsLEAMDNSHost::clsNSECBitmap& p_NSECBitmap, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + /* DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("_writeMDNSNSECBitmap: ")); + for (uint16_t u=0; ulength()), p_rSendParameter)) && // XX esp8266.local + (_writeMDNSNSECBitmap(*pNSECBitmap, p_rSendParameter))); // NSEC bitmap + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC %s.local Type:%s Class:0x%04X TTL:%u %s %s\n"), + _DH(), + m_pcHostName, + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), + m_pcHostName, + _NSECBitmap2String(pNSECBitmap)); + ); + + if (pNSECBitmap) + { + delete pNSECBitmap; + pNSECBitmap = 0; + } + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC (host): FAILED!\n"), _DH());); + return bResult; +} + + +#ifdef MDNS_IPV4_SUPPORT +/* + MDNSResponder::_writeMDNSAnswer_NSEC_PTR_IPv4(host) + + eg. 012.789.456.123.in-addr.arpa NSEC 0x8001 120 XX 012.789.456.123.in-addr.arpa xyz + http://www.zytrax.com/books/dns/ch8/nsec.html + +*/ +bool clsLEAMDNSHost::_writeMDNSAnswer_NSEC_PTR_IPv4(IPAddress p_IPAddress, + clsSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC_PTR_IPv4\n"));); + + clsRRAttributes attributes(clsConsts::u8DNS_RRTYPE_NSEC, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + clsNSECBitmap* pNSECBitmap = _createNSECBitmap(static_cast(enuContentFlag::PTR_IPv4)); + clsRRDomain reverseIPv4Domain; + bool bResult = ((p_IPAddress.isV4()) && + (pNSECBitmap) && // NSEC bitmap created + (_buildDomainForReverseIPv4(p_IPAddress, reverseIPv4Domain)) && // 012.789.456.123.in-addr.arpa + (_writeMDNSRRDomain(reverseIPv4Domain, p_rSendParameter)) && // 012.789.456.123.in-addr.arpa + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), p_rSendParameter)) && // TTL + (_write16((reverseIPv4Domain.m_u16NameLength + (2 + pNSECBitmap->length())), p_rSendParameter)) && + (_writeMDNSRRDomain(reverseIPv4Domain, p_rSendParameter)) && // 012.789.456.123.in-addr.arpa + (_writeMDNSNSECBitmap(*pNSECBitmap, p_rSendParameter))); // NSEC bitmap + + DEBUG_EX_INFO(if (bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC_PTR_IPv4 "), _DH()); + _printRRDomain(reverseIPv4Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:%s Class:0x%04X TTL:%u "), + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL))); + _printRRDomain(reverseIPv4Domain); + DEBUG_OUTPUT.printf_P(PSTR(" %s\n"), _NSECBitmap2String(pNSECBitmap)); + }); + + if (pNSECBitmap) + { + delete pNSECBitmap; + pNSECBitmap = 0; + } + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC_PTR_IPv4 (host): FAILED!\n"), _DH());); + return bResult; +} +#endif + + +#ifdef MDNS_IPV6_SUPPORT +/* + MDNSResponder::_writeMDNSAnswer_NSEC_PTR_IPv6(host) + + eg. 9.0.0.0.0.0.0.0.0.0.0.0.0.7.8.5.6.3.4.1.2.ip6.arpa NSEC 0x8001 120 XX 9.0.0.0.0.0.0.0.0.0.0.0.0.7.8.5.6.3.4.1.2.ip6.arpa xyz + http://www.zytrax.com/books/dns/ch8/nsec.html + +*/ +bool clsLEAMDNSHost::_writeMDNSAnswer_NSEC_PTR_IPv6(IPAddress p_IPAddress, + clsSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC_PTR_IPv6\n"));); + + clsRRAttributes attributes(clsConsts::u8DNS_RRTYPE_NSEC, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + clsNSECBitmap* pNSECBitmap = _createNSECBitmap(static_cast(enuContentFlag::PTR_IPv6)); + clsRRDomain reverseIPv6Domain; + bool bResult = ((p_IPAddress.isV6()) && + (pNSECBitmap) && // NSEC bitmap created + (_buildDomainForReverseIPv6(p_IPAddress, reverseIPv6Domain)) && // 9.0.0.0.0.0.0.0.0.0.0.0.0.7.8.5.6.3.4.1.2.ip6.arpa + (_writeMDNSRRDomain(reverseIPv6Domain, p_rSendParameter)) && // 9.0.0.0.0.0.0.0.0.0.0.0.0.7.8.5.6.3.4.1.2.ip6.arpa + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), p_rSendParameter)) && // TTL + (_write16((reverseIPv6Domain.m_u16NameLength + (2 + pNSECBitmap->length())), p_rSendParameter)) && + (_writeMDNSRRDomain(reverseIPv6Domain, p_rSendParameter)) && // 9.0.0.0.0.0.0.0.0.0.0.0.0.7.8.5.6.3.4.1.2.ip6.arpa + (_writeMDNSNSECBitmap(*pNSECBitmap, p_rSendParameter))); // NSEC bitmap + + DEBUG_EX_INFO(if (bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC_PTR_IPv6 "), _DH()); + _printRRDomain(reverseIPv6Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:%s Class:0x%04X TTL:%u "), + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL))); + _printRRDomain(reverseIPv6Domain); + DEBUG_OUTPUT.printf_P(PSTR(" %s\n"), _NSECBitmap2String(pNSECBitmap)); + }); + + if (pNSECBitmap) + { + delete pNSECBitmap; + pNSECBitmap = 0; + } + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC_PTR_IPv6 (host): FAILED!\n"), _DH());); + return bResult; +} +#endif + +/* + MDNSResponder::_writeMDNSAnswer_NSEC(service) + + eg. MyESP._http.tcp.local NSEC 0x8001 4500 XX MyESP._http.tcp.local xyz + http://www.zytrax.com/books/dns/ch8/nsec.html + +*/ +bool clsLEAMDNSHost::_writeMDNSAnswer_NSEC(clsLEAMDNSHost::clsService& p_rService, + uint32_t p_u32NSECContent, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC (service: %s)\n"), _DH(), _replyFlags2String(p_u32NSECContent));); + + clsRRAttributes attributes(clsConsts::u8DNS_RRTYPE_NSEC, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + clsNSECBitmap* pNSECBitmap = _createNSECBitmap(p_u32NSECContent); + bool bResult = ((pNSECBitmap) && // NSEC bitmap created + (_writeMDNSServiceDomain(p_rService, true, false, 0, p_rSendParameter)) && // MyESP._http._tcp.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL)), p_rSendParameter)) && // TTL + (_writeMDNSServiceDomain(p_rService, true, true, (2 + pNSECBitmap->length()), p_rSendParameter)) && // XX MyESP._http._tcp.local + (_writeMDNSNSECBitmap(*pNSECBitmap, p_rSendParameter))); // NSEC bitmap + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC %s._%s._%s.local Type:%s Class:0x%04X TTL:%u %s\n"), + _DH(), + p_rService.m_pcInstanceName, + p_rService.m_pcType, + p_rService.m_pcProtocol, + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL)), + _NSECBitmap2String(pNSECBitmap)); + ); + + if (pNSECBitmap) + { + delete pNSECBitmap; + pNSECBitmap = 0; + } + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC (service): FAILED!\n"), _DH());); + return bResult; +} + + +} // namespace MDNSImplementation + + +} // namespace esp8266 + + + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp new file mode 100644 index 000000000..87f263eff --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp @@ -0,0 +1,262 @@ +/* + LEAmDNS2_Backbone.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 "ESP8266mDNS.h" +#include "LEAmDNS2Host.h" +#include "LEAmDNS2_Priv.h" + +namespace esp8266 +{ + + +namespace experimental +{ + + +/* + clsLEAmDNS2_Host::clsBackbone::clsBackbone constructor + +*/ +clsLEAMDNSHost::clsBackbone::clsBackbone(void) + : m_pUDPContext(0), + m_bDelayUDPProcessing(false), + m_u32DelayedDatagrams(0), + m_uniqueHost(0) +{ +} + +/* + clsLEAmDNS2_Host::clsBackbone::clsBackbone destructor + +*/ +clsLEAMDNSHost::clsBackbone::~clsBackbone(void) +{ + _releaseUDPContext(); +} + +/* + clsLEAmDNS2_Host::clsBackbone::init + +*/ +bool clsLEAMDNSHost::clsBackbone::init(void) +{ + return _allocUDPContext(); +} + +/* + clsLEAmDNS2_Host::clsBackbone::addHost + +*/ +UdpContext* clsLEAMDNSHost::clsBackbone::addHost(clsLEAMDNSHost* p_pHost) +{ + UdpContext* pUDPContext = nullptr; + + if ((m_pUDPContext) && (p_pHost) && (m_uniqueHost == nullptr)) + { + m_uniqueHost = p_pHost; + pUDPContext = m_pUDPContext; + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s addHost: %s to add host!\n"), _DH(), (pUDPContext ? "Succeeded" : "FAILED"));); + return pUDPContext; +} + +/* + clsLEAmDNS2_Host::clsBackbone::removeHost + +*/ +bool clsLEAMDNSHost::clsBackbone::removeHost(clsLEAMDNSHost* p_pHost) +{ + bool bResult = false; + + if ((p_pHost) && (m_uniqueHost == p_pHost)) + { + m_uniqueHost = nullptr; + bResult = true; + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s removeHost: %s to remove host!\n"), _DH(), (bResult ? "Succeeded" : "FAILED"));); + return bResult; +} + + +/* + clsLEAmDNS2_Host::clsBackbone::hostCount + +*/ +size_t clsLEAMDNSHost::clsBackbone::hostCount(void) const +{ + return m_uniqueHost == nullptr ? 0 : 1; +} + +/* + clsLEAMDNSHost::clsBackbone::::setDelayUDPProcessing + + When executing _sendMessage, with multiple or larger messages, sometimes the ESP IP stack seems + to need a small delay to get the job done. To allow for this delay, a 'delay' was added after one + send operation. However, while 'taking' this delay, sometimes a UDP datagram is received and + processed (which might cause another send operation or change global states). + To avoid 're-entry-like' problems, UDP processing might be blocked for a short period of time. + +*/ +bool clsLEAMDNSHost::clsBackbone::setDelayUDPProcessing(bool p_bDelayUDPProcessing) +{ + if (m_bDelayUDPProcessing != p_bDelayUDPProcessing) + { + m_bDelayUDPProcessing = p_bDelayUDPProcessing; + + if ((!m_bDelayUDPProcessing) && + (m_u32DelayedDatagrams)) + { + DEBUG_EX_INFO2(if (6 <= m_u32DelayedDatagrams) DEBUG_OUTPUT.printf_P(PSTR("%s setDelayUDPProcessing: Processing %u delayed datagram(s)\n"), _DH(), m_u32DelayedDatagrams);); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s setDelayUDPProcessing: Processing %u delayed datagram(s)\n"), _DH(), m_u32DelayedDatagrams);); + _processUDPInput(); + } + m_u32DelayedDatagrams = 0; + } + return true; +} + +/* + clsLEAmDNS2_Host::clsBackbone::_allocUDPContext + +*/ +bool clsLEAMDNSHost::clsBackbone::_allocUDPContext(void) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext\n"), _DH());); + if (_releaseUDPContext()) + { + m_pUDPContext = new UdpContext; + if (m_pUDPContext) + { + m_pUDPContext->ref(); + + //ip_set_option(m_pUDPContext->pcb(), SOF_REUSEADDR); + //udp_bind_netif(m_pUDPContext->pcb(), m_pNetIf); + + if (m_pUDPContext->listen(IP_ANY_TYPE, DNS_MQUERY_PORT)) + { + // This is NOT the TTL (Time-To-Live) for MDNS records, but the subnet level distance MDNS records should travel. + // 1 sets the subnet distance to 'local', which is default for MDNS. + // (Btw.: 255 would set it to 'as far as possible' -> internet), however, RFC 3171 seems to force 255 instead + const uint8_t c_u8MulticastTTL = 255;//1;//255; + + m_pUDPContext->setMulticastTTL(c_u8MulticastTTL); + m_pUDPContext->onRx(std::bind(&clsLEAMDNSHost::clsBackbone::_processUDPInput, this)); + /* m_pUDPContext->onRx([&](void)->void + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext::onRx Received data!\n"), _DH());); + });*/ + m_pUDPContext->connect(IP_ANY_TYPE, DNS_MQUERY_PORT); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext: Succeeded to alloc UDPContext!\n"), _DH());); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext: FAILED to make UDPContext listening!\n"), _DH());); + _releaseUDPContext(); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext: FAILED to alloc UDPContext!\n"), _DH());); + } + } + DEBUG_EX_ERR(if (!m_pUDPContext) DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext: FAILED!\n"), _DH());); + return (0 != m_pUDPContext); +} + +/* + clsLEAmDNS2_Host::clsBackbone::_releaseUDPContext + +*/ +bool clsLEAMDNSHost::clsBackbone::_releaseUDPContext(void) +{ + if (m_pUDPContext) + { + m_pUDPContext->unref(); + m_pUDPContext = nullptr; + } + return true; +} + +/* + clsLEAmDNS2_Host::clsBackbone::_processUDPInput + + Called in SYS context! + +*/ +bool clsLEAMDNSHost::clsBackbone::_processUDPInput(void) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput\n"), _DH());); + + bool bResult = true; + + if (!m_bDelayUDPProcessing) + { + while ((m_pUDPContext) && + (m_pUDPContext->next())) + { + clsLEAMDNSHost* pHost = _findHost(); + + bResult = pHost->_processUDPInput(); + + DEBUG_EX_INFO2_IF(!bResult, + DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: FAILED to process UDP input!\n"), _DH())); + DEBUG_EX_ERR_IF((-1) != m_pUDPContext->peek(), + DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: !!!! CONTENT LEFT IN UDP BUFFER !!!!\n"), + _DH())); + m_pUDPContext->flush(); + } + } + else + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Delaying datagram!\n"), _DH());); + ++m_u32DelayedDatagrams; + } + return bResult; +} + +/* + MISC +*/ + +#if not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_PORT + +/* + clsLEAmDNS2_Host::clsBackbone::_DH +*/ +const char* clsLEAMDNSHost::clsBackbone::_DH(void) const +{ + static char acBuffer[20] = { 0, }; + if (!acBuffer[0]) + { + strcpy_P(acBuffer, PSTR("[mDNS::backbone]")); + } + return acBuffer; +} + +#endif + +} // namespace MDNSImplementation + + +} // namespace esp8266 diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h new file mode 100644 index 000000000..312a03d04 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h @@ -0,0 +1,115 @@ +/* + LEAmDNS_Priv.h + + 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. + +*/ + +#ifndef MDNS2_PRIV_H +#define MDNS2_PRIV_H + +/* + LWIP_OPEN_SRC +*/ +#ifndef LWIP_OPEN_SRC +#define LWIP_OPEN_SRC +#endif + +/* + Enable class debug functions +*/ +#define ESP_8266_MDNS_INCLUDE +//#define DEBUG_ESP_MDNS_RESPONDER // force debug, arduino IDE uses DEBUG_ESP_MDNS + +/* + Enable/disable debug trace macros +*/ + +#if defined(DEBUG_ESP_PORT) +#define DEBUG_ESP_MDNS_ERR +#endif + +#if defined(DEBUG_ESP_PORT) && (defined(DEBUG_ESP_MDNS) || defined(DEBUG_ESP_MDNS_RESPONDER)) +#define DEBUG_ESP_MDNS_INFO +#define DEBUG_ESP_MDNS_INFO2 +//#define DEBUG_ESP_MDNS_TX +//#define DEBUG_ESP_MDNS_RX +#endif + +#ifdef DEBUG_ESP_PORT +#define DEBUG_OUTPUT DEBUG_ESP_PORT +#else +#define DEBUG_OUTPUT Serialx +#endif + +#ifdef DEBUG_ESP_MDNS_INFO +#define DEBUG_EX_INFO(A) A +#define DEBUG_EX_INFO_IF(C,A...) do if (C) { A; } while (0) +#else +#define DEBUG_EX_INFO(A) +#define DEBUG_EX_INFO_IF(C,A...) +#endif + +#ifdef DEBUG_ESP_MDNS_INFO2 +#define DEBUG_EX_INFO2(A) A +#define DEBUG_EX_INFO2_IF(C,A...) do if (C) { A; } while (0) +#else +#define DEBUG_EX_INFO2(A) +#define DEBUG_EX_INFO2_IF(C,A...) +#endif + +#ifdef DEBUG_ESP_MDNS_ERR +#define DEBUG_EX_ERR(A) A +#define DEBUG_EX_ERR_IF(C,A...) do if (C) { A; } while (0) +#else +#define DEBUG_EX_ERR(A) +#define DEBUG_EX_ERR_IF(C,A...) +#endif + +#ifdef DEBUG_ESP_MDNS_TX +#define DEBUG_EX_TX(A) do { A; } while (0) +#else +#define DEBUG_EX_TX(A) +#endif + +#ifdef DEBUG_ESP_MDNS_RX +#define DEBUG_EX_RX(A) do { A; } while (0) +#else +#define DEBUG_EX_RX(A) +#endif + +/* + Enable/disable the usage of the F() macro in debug trace printf calls. + There needs to be an PGM comptible printf function to use this. + + USE_PGM_PRINTF and F +*/ +#define USE_PGM_PRINTF + +#ifdef USE_PGM_PRINTF +#else +#ifdef F +#undef F +#endif +#define F(A) A +#endif + + +#endif // MDNS2_PRIV_H diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h b/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h new file mode 100644 index 000000000..a24cf62ba --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h @@ -0,0 +1,31 @@ +/* + LEAmDNS2_lwIPdefs.h + + 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. + +*/ + +#ifndef LEAMDNS2_LWIPDEFS_H +#define LEAMDNS2_LWIPDEFS_H + +#include +#include // DNS_RRTYPE_xxx, DNS_MQUERY_PORT + +#endif // LEAMDNS2_LWIPDEFS_H diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp index 41e9524ab..03ade10c7 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp @@ -37,6 +37,7 @@ extern "C" { #include "user_interface.h" } +#include "ESP8266mDNS.h" #include "LEAmDNS_lwIPdefs.h" #include "LEAmDNS_Priv.h" @@ -1982,7 +1983,7 @@ bool MDNSResponder::_checkServiceQueryCache(void) /* MDNSResponder::_replyMaskForHost - Determines the relavant host answers for the given question. + Determines the relevant host answers for the given question. - A question for the hostname (eg. esp8266.local) will result in an A/AAAA (eg. 192.168.2.129) reply. - A question for the reverse IP address (eg. 192-168.2.120.inarpa.arpa) will result in an PTR_IP4 (eg. esp8266.local) reply. diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp b/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp index d23941ce5..7dc3773de 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp @@ -22,55 +22,13 @@ */ -#include "lwip/igmp.h" +#include +#include // strrstr() +#include "ESP8266mDNS.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 { diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Priv.h b/libraries/ESP8266mDNS/src/LEAmDNS_Priv.h index cc56b133a..4750669d3 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS_Priv.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Priv.h @@ -53,7 +53,7 @@ namespace MDNSImplementation //#define ENABLE_ESP_MDNS_RESPONDER_PASSIV_MODE // Enable/disable debug trace macros -#ifdef DEBUG_ESP_MDNS_RESPONDER +#if defined(DEBUG_ESP_PORT) && defined(DEBUG_ESP_MDNS_RESPONDER) #define DEBUG_ESP_MDNS_INFO #define DEBUG_ESP_MDNS_ERR #define DEBUG_ESP_MDNS_TX @@ -64,22 +64,22 @@ namespace MDNSImplementation #ifdef DEBUG_ESP_MDNS_INFO #define DEBUG_EX_INFO(A) A #else -#define DEBUG_EX_INFO(A) do { (void)0; } while (0) +#define DEBUG_EX_INFO(A) #endif #ifdef DEBUG_ESP_MDNS_ERR #define DEBUG_EX_ERR(A) A #else -#define DEBUG_EX_ERR(A) do { (void)0; } while (0) +#define DEBUG_EX_ERR(A) #endif #ifdef DEBUG_ESP_MDNS_TX #define DEBUG_EX_TX(A) A #else -#define DEBUG_EX_TX(A) do { (void)0; } while (0) +#define DEBUG_EX_TX(A) #endif #ifdef DEBUG_ESP_MDNS_RX #define DEBUG_EX_RX(A) A #else -#define DEBUG_EX_RX(A) do { (void)0; } while (0) +#define DEBUG_EX_RX(A) #endif #ifdef DEBUG_ESP_PORT diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp index ce475de3b..8f0d2f7cf 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp @@ -22,6 +22,7 @@ */ +#include "ESP8266mDNS.h" #include "LEAmDNS_Priv.h" #include "LEAmDNS_lwIPdefs.h" @@ -787,7 +788,7 @@ bool MDNSResponder::stcMDNS_RRDomain::compare(const stcMDNS_RRDomain& p_Other) c { if (*((unsigned char*)pT)) // Not 0 { - pT += (1 + * ((unsigned char*)pT)); // Shift by length byte and lenght + pT += (1 + * ((unsigned char*)pT)); // Shift by length byte and length pO += (1 + * ((unsigned char*)pO)); } else // Is 0 -> Successfully reached the end diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS_Transfer.cpp index 7400abec4..c5858690a 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Transfer.cpp @@ -26,6 +26,7 @@ extern "C" { #include "user_interface.h" } +#include "ESP8266mDNS.h" #include "LEAmDNS_lwIPdefs.h" #include "LEAmDNS_Priv.h" diff --git a/tests/host/Makefile b/tests/host/Makefile index eb2b9be36..dabab58e7 100644 --- a/tests/host/Makefile +++ b/tests/host/Makefile @@ -295,6 +295,12 @@ OPT_ARDUINO_LIBS ?= $(addprefix ../../libraries/,\ LEAmDNS_Structs.cpp \ LEAmDNS_Transfer.cpp \ ESP8266mDNS.cpp \ + LEAmDNS2Host.cpp \ + LEAmDNS2Host_Control.cpp \ + LEAmDNS2Host_Debug.cpp \ + LEAmDNS2Host_Structs.cpp \ + LEAmDNS2Host_Transfer.cpp \ + LEAmDNS2_Backbone.cpp \ ) \ ArduinoOTA/ArduinoOTA.cpp \ DNSServer/src/DNSServer.cpp \ diff --git a/tests/host/common/Arduino.h b/tests/host/common/Arduino.h index 4c0a8c676..8735ecba6 100644 --- a/tests/host/common/Arduino.h +++ b/tests/host/common/Arduino.h @@ -270,3 +270,10 @@ extern "C" void configTime(long timezone, int daylightOffset_sec, #include "pins_arduino.h" #endif /* Arduino_h */ + +#if __cplusplus +#include +#include +#define MOCKARGS 1 +extern std::map mockArgs; +#endif diff --git a/tests/host/common/ArduinoMain.cpp b/tests/host/common/ArduinoMain.cpp index a9d8d696f..637235968 100644 --- a/tests/host/common/ArduinoMain.cpp +++ b/tests/host/common/ArduinoMain.cpp @@ -56,6 +56,8 @@ const char* fspath = nullptr; static struct termios initial_settings; +std::map mockArgs; + int mockverbose (const char* fmt, ...) { va_list ap; @@ -135,6 +137,8 @@ void help (const char* argv0, int exitcode) "\t-S - spiffs size in KBytes (default: %zd)\n" "\t-L - littlefs size in KBytes (default: %zd)\n" "\t (spiffs, littlefs: negative value will force mismatched size)\n" + "\t-K - key\n" + "\t-V - value\n" "\tgeneral:\n" "\t-c - ignore CTRL-C (send it via Serial)\n" "\t-f - no throttle (possibly 100%%CPU)\n" @@ -158,6 +162,8 @@ static struct option options[] = { "spiffskb", required_argument, NULL, 'S' }, { "littlefskb", required_argument, NULL, 'L' }, { "portshifter", required_argument, NULL, 's' }, + { "key", required_argument, NULL, 'K' }, + { "value", required_argument, NULL, 'V' }, { "once", no_argument, NULL, '1' }, }; @@ -209,10 +215,11 @@ int main (int argc, char* const argv []) mock_port_shifter = 0; else mock_port_shifter = MOCK_PORT_SHIFTER; + String key; for (;;) { - int n = getopt_long(argc, argv, "hlcfbvTi:S:s:L:P:1", options, NULL); + int n = getopt_long(argc, argv, "hlcfbvTi:S:s:L:P:1K:V:", options, NULL); if (n < 0) break; switch (n) @@ -253,6 +260,12 @@ int main (int argc, char* const argv []) case 'T': serial_timestamp = true; break; + case 'K': + key = optarg; + break; + case 'V': + mockArgs[key] = optarg; + break; case '1': run_once = true; break; diff --git a/tests/host/common/ArduinoMainUdp.cpp b/tests/host/common/ArduinoMainUdp.cpp index 2962f4a48..6c4a034d2 100644 --- a/tests/host/common/ArduinoMainUdp.cpp +++ b/tests/host/common/ArduinoMainUdp.cpp @@ -29,11 +29,6 @@ DEALINGS WITH THE SOFTWARE. */ -#include "lwip/opt.h" -#include "lwip/udp.h" -#include "lwip/inet.h" -#include "lwip/igmp.h" -#include "lwip/mem.h" #include #include #include diff --git a/tests/host/common/MocklwIP.cpp b/tests/host/common/MocklwIP.cpp index 8628cc47b..1e19f20fc 100644 --- a/tests/host/common/MocklwIP.cpp +++ b/tests/host/common/MocklwIP.cpp @@ -1,6 +1,7 @@ #include -#include + +#include "MocklwIP.h" esp8266::AddressListImplementation::AddressList addrList; diff --git a/tests/host/common/MocklwIP.h b/tests/host/common/MocklwIP.h new file mode 100644 index 000000000..e7fead4cb --- /dev/null +++ b/tests/host/common/MocklwIP.h @@ -0,0 +1,15 @@ + +#ifndef __MOCKLWIP_H +#define __MOCKLWIP_H + +extern "C" +{ + +#include +#include + +extern netif netif0; + +} // extern "C" + +#endif // __MOCKLWIP_H diff --git a/tests/host/common/include/UdpContext.h b/tests/host/common/include/UdpContext.h index c4a87c19b..bf104bc40 100644 --- a/tests/host/common/include/UdpContext.h +++ b/tests/host/common/include/UdpContext.h @@ -1,28 +1,32 @@ /* - UdpContext.h - emulation of UDP connection handling on top of lwIP + UdpContext.h - emulation of UDP connection handling on top of lwIP - Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. + Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef UDPCONTEXT_H #define UDPCONTEXT_H #include +#include +#include +#include + class UdpContext; #define GET_IP_HDR(pb) reinterpret_cast(((uint8_t*)((pb)->payload)) - UDP_HLEN - IP_HLEN); @@ -52,12 +56,13 @@ public: void unref() { - if(--_refcnt == 0) { + if (--_refcnt == 0) + { delete this; } } - bool connect (const ip_addr_t* addr, uint16_t port) + bool connect(const ip_addr_t* addr, uint16_t port) { _dst = *addr; _dstport = port; @@ -108,7 +113,8 @@ public: // warning: handler is called from tcp stack context // esp_yield and non-reentrant functions which depend on it will fail - void onRx(rxhandler_t handler) { + void onRx(rxhandler_t handler) + { _on_rx = handler; } @@ -132,11 +138,12 @@ public: mockUDPSwallow(pos, _inbuf, _inbufsize); } - bool isValidOffset(const size_t pos) const { + bool isValidOffset(const size_t pos) const + { return pos <= _inbufsize; } - uint32_t getRemoteAddress() + IPAddress getRemoteAddress() { return _dst.addr; } @@ -146,7 +153,7 @@ public: return _dstport; } - uint32_t getDestAddress() + IPAddress getDestAddress() { mockverbose("TODO: implement UDP getDestAddress\n"); return 0; //ip_hdr* iphdr = GET_IP_HDR(_rx_buf); @@ -173,7 +180,7 @@ public: int read() { char c; - return read(&c, 1)? c: -1; + return read(&c, 1) ? c : -1; } size_t read(char* dst, size_t size) @@ -184,7 +191,7 @@ public: int peek() { char c; - return mockUDPPeekBytes(_sock, &c, 1, _timeout_ms, _inbuf, _inbufsize)?: -1; + return mockUDPPeekBytes(_sock, &c, 1, _timeout_ms, _inbuf, _inbufsize) ? : -1; } void flush() @@ -195,7 +202,7 @@ public: _inbufsize = 0; } - size_t append (const char* data, size_t size) + size_t append(const char* data, size_t size) { if (size + _outbufsize > sizeof _outbuf) { @@ -208,16 +215,40 @@ public: return size; } - bool send (ip_addr_t* addr = 0, uint16_t port = 0) + err_t trySend(ip_addr_t* addr = 0, uint16_t port = 0, bool keepBuffer = true) { - uint32_t dst = addr? addr->addr: _dst.addr; - uint16_t dstport = port?: _dstport; - size_t ret = mockUDPWrite(_sock, (const uint8_t*)_outbuf, _outbufsize, _timeout_ms, dst, dstport); - _outbufsize = 0; - return ret > 0; + uint32_t dst = addr ? addr->addr : _dst.addr; + uint16_t dstport = port ? : _dstport; + size_t wrt = mockUDPWrite(_sock, (const uint8_t*)_outbuf, _outbufsize, _timeout_ms, dst, dstport); + err_t ret = _outbufsize ? ERR_OK : ERR_ABRT; + if (!keepBuffer || wrt == _outbufsize) + cancelBuffer(); + return ret; } - void mock_cb (void) + void cancelBuffer() + { + _outbufsize = 0; + } + + bool send(ip_addr_t* addr = 0, uint16_t port = 0) + { + return trySend(addr, port, false) == ERR_OK; + } + + bool sendTimeout(ip_addr_t* addr, uint16_t port, + esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs) + { + err_t err; + esp8266::polledTimeout::oneShotFastMs timeout(timeoutMs); + while (((err = trySend(addr, port)) != ERR_OK) && !timeout) + delay(0); + if (err != ERR_OK) + cancelBuffer(); + return err == ERR_OK; + } + + void mock_cb(void) { if (_on_rx) _on_rx(); } @@ -228,7 +259,7 @@ public: private: - void translate_addr () + void translate_addr() { if (addrsize == 4) { @@ -260,7 +291,7 @@ private: uint8_t addr[16]; }; -extern "C" inline err_t igmp_joingroup (const ip4_addr_t *ifaddr, const ip4_addr_t *groupaddr) +extern "C" inline err_t igmp_joingroup(const ip4_addr_t *ifaddr, const ip4_addr_t *groupaddr) { (void)ifaddr; UdpContext::staticMCastAddr = groupaddr->addr; diff --git a/tests/host/common/user_interface.cpp b/tests/host/common/user_interface.cpp index 419707e99..f7061e38a 100644 --- a/tests/host/common/user_interface.cpp +++ b/tests/host/common/user_interface.cpp @@ -39,6 +39,7 @@ #include #include +#include "MocklwIP.h" extern "C" { @@ -138,6 +139,7 @@ extern "C" if (host_interface) mockverbose("host: looking for interface '%s':\n", host_interface); + for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) { mockverbose("host: interface: %s", ifa->ifa_name);