diff --git a/libraries/ESP8266WiFi/src/include/ClientContext.h b/libraries/ESP8266WiFi/src/include/ClientContext.h index 01ac0ff6a8..74fdd491c3 100644 --- a/libraries/ESP8266WiFi/src/include/ClientContext.h +++ b/libraries/ESP8266WiFi/src/include/ClientContext.h @@ -130,8 +130,21 @@ class ClientContext } _connect_pending = 1; _op_start_time = millis(); + /*LEA*/ + //Serial.print("\nClientContext::connect (A)"); + uint32_t nextPrint = (_op_start_time + 100); + while ((millis() < (_op_start_time + _timeout_ms)) && + (state() != ESTABLISHED)) { + yield(); + if (millis() > nextPrint) { + //Serial.printf("."); + nextPrint = (millis() + 100); + } + } + //Serial.println("\nClientContext::connect (B)"); // This delay will be interrupted by esp_schedule in the connect callback - delay(_timeout_ms); + //delay(_timeout_ms); + /*LEA*/ _connect_pending = 0; if (!_pcb) { DEBUGV(":cabrt\r\n"); @@ -142,6 +155,7 @@ class ClientContext abort(); return 0; } + //Serial.println("ClientContext::connect (C)"); return 1; } diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock/mDNS_Clock.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock/mDNS_Clock.ino new file mode 100755 index 0000000000..b07877f1dc --- /dev/null +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock/mDNS_Clock.ino @@ -0,0 +1,332 @@ +/* + ESP8266 mDNS responder clock + + This example demonstrates two features of the LEA MDNSResponder: + 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 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 negociated. Keep an + eye to 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 installed also 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 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 + +/* + * Global defines and vars + */ + +#define TIMEZONE_OFFSET 1 // CET +#define DST_OFFSET 1 // CEST +#define UPDATE_CYCLE (1 * 1000) // every second + +#define SERVICE_PORT 80 // HTTP port + + +const char* ssid = "............"; +const char* password = "............"; + +char* pcHostDomain = 0; // Negociated host domain +bool bHostDomainConfirmed = false; // Flags the confirmation of the host domain +LEAmDNS::MDNSResponder::hMDNSService hMDNSService = 0; // The handle of the clock service in the MDNS responder + +// TCP server at port 'SERVICE_PORT' will respond to HTTP requests +WiFiServer 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 = os_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 MDNS.announce(). + * + */ +bool MDNSDynamicServiceTxtCallback(LEAmDNS::MDNSResponder* p_pMDNSResponder, + const LEAmDNS::MDNSResponder::hMDNSService p_hService, + void* p_pUserdata) { + Serial.println("MDNSDynamicServiceTxtCallback"); + (void) p_pUserdata; + + if ((p_pMDNSResponder) && + (hMDNSService == p_hService)) { + Serial.printf("Updating curtime TXT item to: %s\n", getTimeString()); + p_pMDNSResponder->addDynamicServiceTxt(p_hService, "curtime", getTimeString()); + } + return true; +} + + +/* + * MDNSProbeResultCallback + * + * Probe result callback for the host domain. + * If the domain is free, the host domain is set and the clock service is + * added. + * If the domain is already used, a new name is created and the probing is + * restarted via p_pMDNSResponder->setHostname(). + * + */ +bool MDNSProbeResultCallback(LEAmDNS::MDNSResponder* p_pMDNSResponder, + const char* p_pcDomainName, + const LEAmDNS::MDNSResponder::hMDNSService p_hService, + bool p_bProbeResult, + void* p_pUserdata) { + Serial.println("MDNSProbeResultCallback"); + (void) p_pUserdata; + + if ((p_pMDNSResponder) && + (0 == p_hService)) { // Called for host domain + Serial.printf("MDNSProbeResultCallback: Host domain '%s.local' is %s\n", p_pcDomainName, (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 'clock.tcp' service to port 'SERVICE_PORT', using the host domain as instance domain + hMDNSService = p_pMDNSResponder->addService(0, "espclk", "tcp", SERVICE_PORT); + if (hMDNSService) { + // Add a simple static MDNS service TXT item + p_pMDNSResponder->addServiceTxt(hMDNSService, "port#", SERVICE_PORT); + // Set the callback function for dynamic service TXTs + p_pMDNSResponder->setDynamicServiceTxtCallback(hMDNSService, MDNSDynamicServiceTxtCallback, 0); + } + } + } + else { + // Change hostname, use '-' as divider between base name and index + if (LEAmDNS::MDNSResponder::indexDomain(pcHostDomain, "-", 0)) { + p_pMDNSResponder->setHostname(pcHostDomain); + } + else { + Serial.println("MDNSProbeResultCallback: FAILED to update hostname!"); + } + } + } + } + return true; +} + + +/* + * handleHTTPClient + */ +void handleHTTPClient(WiFiClient& client) { + Serial.println(""); + Serial.println("New client"); + + // Wait for data from client to become available + while (client.connected() && !client.available()) { + delay(1); + } + + // Read the first line of HTTP request + String req = client.readStringUntil('\r'); + + // First line of HTTP request looks like "GET /path HTTP/1.1" + // Retrieve the "/path" part by finding the spaces + int addr_start = req.indexOf(' '); + int addr_end = req.indexOf(' ', addr_start + 1); + if (addr_start == -1 || addr_end == -1) { + Serial.print("Invalid request: "); + Serial.println(req); + return; + } + req = req.substring(addr_start + 1, addr_end); + Serial.print("Request: "); + Serial.println(req); + client.flush(); + + // Get current time + time_t now = time(nullptr);; + struct tm timeinfo; + gmtime_r(&now, &timeinfo); + + String s; + if (req == "/") { + IPAddress ip = WiFi.localIP(); + String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]); + s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n\r\nHello from ESP8266 at "; + s += ipStr; + // Simple addition of the current time + s += "\r\nCurrent time is: "; + s += getTimeString(); + // done :-) + s += "\r\n\r\n"; + Serial.println("Sending 200"); + } else { + s = "HTTP/1.1 404 Not Found\r\n\r\n"; + Serial.println("Sending 404"); + } + client.print(s); + + Serial.println("Done with client"); +} + + +/* + * setup + */ +void setup(void) { + Serial.begin(115200); + + // Connect to WiFi network + 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 + LEAmDNS::MDNS.setProbeResultCallback(MDNSProbeResultCallback, 0); + // Init the (currently empty) host domain string with 'esp8266' + if ((!LEAmDNS::MDNSResponder::indexDomain(pcHostDomain, 0, "esp8266")) || + (!LEAmDNS::MDNS.begin(pcHostDomain))) { + Serial.println("Error setting up MDNS responder!"); + while (1) { // STOP + delay(1000); + } + } + Serial.println("MDNS responder started"); + + // Start TCP (HTTP) server + server.begin(); + Serial.println("TCP server started"); +} + + +/* + * loop + */ +void loop(void) { + // Check if a client has connected + WiFiClient client = server.available(); + if (client) { + handleHTTPClient(client); + } + + // Allow MDNS processing + LEAmDNS::MDNS.update(); + + // Update time (if needed) + //static unsigned long ulNextTimeUpdate = UPDATE_CYCLE; + static clsLEATimeFlag timeFlag(UPDATE_CYCLE); + if (timeFlag.flagged()/*ulNextTimeUpdate < millis()*/) { + + if (hMDNSService) { + // Just trigger a new MDNS announcement, this will lead to a call to + // 'MDNSDynamicServiceTxtCallback', which will update the time TXT item + LEAmDNS::MDNS.announce(); + } + //ulNextTimeUpdate = (millis() + UPDATE_CYCLE); // Set update 'timer' + timeFlag.restart(); + } +} + + diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor/mDNS_ServiceMonitor.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor/mDNS_ServiceMonitor.ino new file mode 100755 index 0000000000..55307d7eb8 --- /dev/null +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor/mDNS_ServiceMonitor.ino @@ -0,0 +1,362 @@ +/* + ESP8266 mDNS Responder Service Monitor + + This example demonstrates two features of the LEA MDNSResponder: + 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. + +*/ + + +#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 + +/* + * Global defines and vars + */ + +#define SERVICE_PORT 80 // HTTP port + + +const char* ssid = "............"; +const char* password = "............"; + +char* pcHostDomain = 0; // Negociated host domain +bool bHostDomainConfirmed = false; // Flags the confirmation of the host domain +LEAmDNS::MDNSResponder::hMDNSService hMDNSService = 0; // The handle of the http service in the MDNS responder +LEAmDNS::MDNSResponder::hMDNSServiceQuery 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; + +// TCP server at port 'SERVICE_PORT' will respond to HTTP requests +WiFiServer 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; +} + + +/* + * MDNSServiceQueryCallback + */ +bool MDNSServiceQueryCallback(LEAmDNS::MDNSResponder* p_pMDNSResponder, // The MDNS responder object + const LEAmDNS::MDNSResponder::hMDNSServiceQuery p_hServiceQuery, // Handle to the service query + uint32_t p_u32AnswerIndex, // Index of the updated answer + uint32_t p_u32ServiceQueryAnswerMask, // Mask for the updated component + bool p_bSetContent, // true: Component set, false: component deleted + void* p_pUserdata) { // pUserdata; here '0', as none set via 'installServiceQuery' + (void) p_pUserdata; + Serial.printf("MDNSServiceQueryCallback\n"); + + if ((p_pMDNSResponder) && + (hMDNSServiceQuery == p_hServiceQuery)) { + + if (LEAmDNS::MDNSResponder::ServiceQueryAnswerType_ServiceDomain & p_u32ServiceQueryAnswerMask) { + Serial.printf("MDNSServiceQueryCallback: Service domain '%s' %s index %u\n", + p_pMDNSResponder->answerServiceDomain(p_hServiceQuery, p_u32AnswerIndex), + (p_bSetContent ? "added at" : "removed from"), + p_u32AnswerIndex); + } + else if (LEAmDNS::MDNSResponder::ServiceQueryAnswerType_HostDomainAndPort & p_u32ServiceQueryAnswerMask) { + if (p_bSetContent) { + Serial.printf("MDNSServiceQueryCallback: Host domain and port added/updated for service '%s': %s:%u\n", + p_pMDNSResponder->answerServiceDomain(p_hServiceQuery, p_u32AnswerIndex), + p_pMDNSResponder->answerHostDomain(p_hServiceQuery, p_u32AnswerIndex), + p_pMDNSResponder->answerPort(p_hServiceQuery, p_u32AnswerIndex)); + } + else { + Serial.printf("MDNSServiceQueryCallback: Host domain and port removed from service '%s'\n", + p_pMDNSResponder->answerServiceDomain(p_hServiceQuery, p_u32AnswerIndex)); + } + } + else if (LEAmDNS::MDNSResponder::ServiceQueryAnswerType_IP4Address & p_u32ServiceQueryAnswerMask) { + if (p_bSetContent) { + Serial.printf("MDNSServiceQueryCallback: IP4 address added/updated for service '%s':\n", + p_pMDNSResponder->answerServiceDomain(p_hServiceQuery, p_u32AnswerIndex)); + for (uint32_t u=0; uanswerIP4AddressCount(p_hServiceQuery, p_u32AnswerIndex); ++u) { + Serial.printf("- %s\n", p_pMDNSResponder->answerIP4Address(p_hServiceQuery, p_u32AnswerIndex, u).toString().c_str()); + } + } + else { + Serial.printf("MDNSServiceQueryCallback: IP4 address removed from service '%s'\n", + p_pMDNSResponder->answerServiceDomain(p_hServiceQuery, p_u32AnswerIndex)); + } + } + else if (LEAmDNS::MDNSResponder::ServiceQueryAnswerType_Txts & p_u32ServiceQueryAnswerMask) { + if (p_bSetContent) { + Serial.printf("MDNSServiceQueryCallback: TXT items added/updated for service '%s': %s\n", + p_pMDNSResponder->answerServiceDomain(p_hServiceQuery, p_u32AnswerIndex), + p_pMDNSResponder->answerTxts(p_hServiceQuery, p_u32AnswerIndex)); + } + else { + Serial.printf("MDNSServiceQueryCallback: TXT items removed from service '%s'\n", + p_pMDNSResponder->answerServiceDomain(p_hServiceQuery, p_u32AnswerIndex)); + } + } + + // + // Create the current list of 'http.tcp' services + uint32_t u32Answers = p_pMDNSResponder->answerCount(p_hServiceQuery); + if (u32Answers) { + strHTTPServices = ""; + for (uint32_t u=0; uanswerServiceDomain(p_hServiceQuery, u); + // Host domain and port + if ((p_pMDNSResponder->hasAnswerHostDomain(p_hServiceQuery, u)) && + (p_pMDNSResponder->hasAnswerPort(p_hServiceQuery, u))) { + + strHTTPServices += " at "; + strHTTPServices += p_pMDNSResponder->answerHostDomain(p_hServiceQuery, u); + strHTTPServices += ":"; + strHTTPServices += p_pMDNSResponder->answerPort(p_hServiceQuery, u); + } + // IP4 address + if (p_pMDNSResponder->hasAnswerIP4Address(p_hServiceQuery, u)) { + strHTTPServices += " IP4: "; + for (uint32_t u2=0; u2answerIP4AddressCount(p_hServiceQuery, u); ++u2) { + if (0 != u2) { + strHTTPServices += ", "; + } + strHTTPServices += p_pMDNSResponder->answerIP4Address(p_hServiceQuery, u, u2).toString(); + } + } + // MDNS TXT items + if (p_pMDNSResponder->hasAnswerTxts(p_hServiceQuery, u)) { + strHTTPServices += " TXT: "; + strHTTPServices += p_pMDNSResponder->answerTxts(p_hServiceQuery, u); + } + strHTTPServices += "
"; + } + } + else { + strHTTPServices = cstrNoHTTPServices; + } + } + return true; +} + + +/* + * MDNSProbeResultCallback + * + * 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_pMDNSResponder->setHostname(). + * + */ +bool MDNSProbeResultCallback(LEAmDNS::MDNSResponder* p_pMDNSResponder, + const char* p_pcDomainName, + const LEAmDNS::MDNSResponder::hMDNSService p_hService, + bool p_bProbeResult, + void* p_pUserdata) { + (void) p_pUserdata; + + if ((p_pMDNSResponder) && + (0 == p_hService)) { // Called for host domain + + Serial.printf("MDNSProbeResultCallback: Host domain '%s.local' is %s\n", p_pcDomainName, (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 = p_pMDNSResponder->addService(0, "http", "tcp", SERVICE_PORT); + if (hMDNSService) { + // Add some '_http._tcp' protocol specific MDNS service TXT items + // See: http://www.dns-sd.org/txtrecords.html#http + p_pMDNSResponder->addServiceTxt(hMDNSService, "user", ""); + p_pMDNSResponder->addServiceTxt(hMDNSService, "password", ""); + p_pMDNSResponder->addServiceTxt(hMDNSService, "path", "/"); + } + + // Install dynamic 'http.tcp' service query + if (!hMDNSServiceQuery) { + hMDNSServiceQuery = p_pMDNSResponder->installServiceQuery("http", "tcp", MDNSServiceQueryCallback, 0); + 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 + if (LEAmDNS::MDNSResponder::indexDomain(pcHostDomain, "-", 0)) { + p_pMDNSResponder->setHostname(pcHostDomain); + } + else { + Serial.println("MDNSProbeResultCallback: FAILED to update hostname!"); + } + } + } + } + return true; +} + + +/* + * handleHTTPClient + */ +void handleHTTPClient(WiFiClient& client) { + Serial.println(""); + Serial.println("New client"); + + // Wait for data from client to become available + while (client.connected() && !client.available()) { + delay(1); + } + + // Read the first line of HTTP request + String req = client.readStringUntil('\r'); + + // First line of HTTP request looks like "GET /path HTTP/1.1" + // Retrieve the "/path" part by finding the spaces + int addr_start = req.indexOf(' '); + int addr_end = req.indexOf(' ', addr_start + 1); + if (addr_start == -1 || addr_end == -1) { + Serial.print("Invalid request: "); + Serial.println(req); + return; + } + req = req.substring(addr_start + 1, addr_end); + Serial.print("Request: "); + Serial.println(req); + client.flush(); + + String s; + if (req == "/") { + IPAddress ip = WiFi.localIP(); + String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]); + s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n\r\nHello from ESP8266 at "; + s += ipStr; + // Simple addition of the current time + s += "
Local HTTP services:
"; + s += strHTTPServices; + // done :-) + s += "\r\n\r\n"; + Serial.println("Sending 200"); + } else { + s = "HTTP/1.1 404 Not Found\r\n\r\n"; + Serial.println("Sending 404"); + } + client.print(s); + + Serial.println("Done with client"); +} + + +/* + * setup + */ +void setup(void) { + Serial.begin(115200); + Serial.setDebugOutput(false); + + // Connect to WiFi network + 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()); + + // Setup MDNS responder + LEAmDNS::MDNS.setProbeResultCallback(MDNSProbeResultCallback, 0); + // Init the (currently empty) host domain string with 'esp8266' + if ((!LEAmDNS::MDNSResponder::indexDomain(pcHostDomain, 0, "esp8266")) || + (!LEAmDNS::MDNS.begin(pcHostDomain))) { + Serial.println("Error setting up MDNS responder!"); + while (1) { // STOP + delay(1000); + } + } + Serial.println("MDNS responder started"); + + // Start TCP (HTTP) server + server.begin(); + Serial.println("TCP server started"); +} + + +/* + * loop + */ +void loop(void) { + // Check if a client has connected + WiFiClient client = server.available(); + if (client) { + handleHTTPClient(client); + } + + // Allow MDNS processing + LEAmDNS::MDNS.update(); +} + + diff --git a/libraries/ESP8266mDNS/library.properties b/libraries/ESP8266mDNS/library.properties index 7611a48504..4b0535d3ba 100644 --- a/libraries/ESP8266mDNS/library.properties +++ b/libraries/ESP8266mDNS/library.properties @@ -1,10 +1,10 @@ name=ESP8266mDNS -version=1.1 -author=tony@tonydicola.com -maintainer=ivan@esp8266.com -sentence=A port of CC3000 Multicast DNS library -paragraph=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. +version=1.2 +author=multiple, see files +maintainer=LaborEtArs +sentence=Creates a mDNS responder. +paragraph=Creates a mDNS responder to ensure host domain uniqueness in local networks and to allow for mDNS service discovery and announcment. category=Communication -url= +url=https://github.com/LaborEtArs/ESP8266mDNS architectures=esp8266 -dot_a_linkage=true +dot_a_linkage=true \ No newline at end of file diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS.h b/libraries/ESP8266mDNS/src/ESP8266mDNS.h new file mode 100644 index 0000000000..9b43ee78cb --- /dev/null +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS.h @@ -0,0 +1,49 @@ +/* + ESP8266mDNS.h - mDNSResponder for ESP8266 family + This file is part of the esp8266 core for Arduino environment. + + Legacy_ESP8266mDNS: + The well known, thouroughly tested (yet no flawless) default mDNS library for the ESP8266 family + + LEA_ESP8266mDNS: + An (currently) experimental mDNS implementation, that supports a lot more of mDNS features than Legacy_ESP8266mDNS, like: + - 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) + - 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 DNS-SD services (creating a fixed answer set after a certain timeout period) + - Dynamic queries for DNS-SD services with cached and updated answers and user notifications + - Support for multi-homed client host domains + + See 'LEA_ESP8266mDNS/EPS8266mDNS.h' for more implementation details and usage informations. + See 'examples/mDNS_Clock' and 'examples/mDNS_ServiceMonitor' for implementation examples of the advanced features. + + LEA_ESP8266mDNS is (mostly) client source code compatible to 'Legacy_ESP8266mDNS', so it could be + use as a 'drop-in' replacement in existing projects. + + + 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. + + 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 + +*/ + +#include "ESP8266MDNS_Legacy.h" +#include "LEAmDNS.h" + +using namespace Legacy_MDNSResponder; +//using namespace LEAmDNS; diff --git a/libraries/ESP8266mDNS/ESP8266mDNS.cpp b/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp similarity index 99% rename from libraries/ESP8266mDNS/ESP8266mDNS.cpp rename to libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp index c754a2b8cf..fabaee5779 100644 --- a/libraries/ESP8266mDNS/ESP8266mDNS.cpp +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp @@ -59,6 +59,9 @@ extern "C" { +namespace Legacy_MDNSResponder { + + #ifdef DEBUG_ESP_MDNS #define DEBUG_ESP_MDNS_ERR #define DEBUG_ESP_MDNS_TX @@ -431,11 +434,11 @@ MDNSTxt * MDNSResponder::_getServiceTxt(char *name, char *proto){ 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 nullptr; return servicePtr->_txts; } } - return 0; + return nullptr; } uint16_t MDNSResponder::_getServiceTxtLen(char *name, char *proto){ @@ -965,12 +968,24 @@ void MDNSResponder::_parsePacket(){ 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", ARDUINO_BOARD); + addServiceTxt("arduino", "tcp", "board", STRINGIZE_VALUE_OF(ARDUINO_BOARD)); addServiceTxt("arduino", "tcp", "auth_upload", (auth) ? "yes":"no"); } @@ -1272,3 +1287,9 @@ void MDNSResponder::_replyToInstanceRequest(uint8_t questionMask, uint8_t respon #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) MDNSResponder MDNS; #endif + +} // namespace Legacy_MDNSResponder + + + + diff --git a/libraries/ESP8266mDNS/ESP8266mDNS.h b/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.h similarity index 98% rename from libraries/ESP8266mDNS/ESP8266mDNS.h rename to libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.h index 505f90c46f..7efb325b70 100644 --- a/libraries/ESP8266mDNS/ESP8266mDNS.h +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.h @@ -53,6 +53,10 @@ License (MIT license): class UdpContext; + +namespace Legacy_MDNSResponder { + + struct MDNSService; struct MDNSTxt; struct MDNSAnswer; @@ -139,4 +143,9 @@ class MDNSResponder { extern MDNSResponder MDNS; #endif +} // namespace Legacy_MDNSResponder + #endif //ESP8266MDNS_H + + + diff --git a/libraries/ESP8266mDNS/src/LEATimeFlag.h b/libraries/ESP8266mDNS/src/LEATimeFlag.h new file mode 100644 index 0000000000..a9d233fefd --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEATimeFlag.h @@ -0,0 +1,62 @@ +/* + * LEATimeFlag.h + */ +#ifndef __LEATIMEFLAG_H +#define __LEATIMEFLAG_H + + +#include + + +/** + * clsLEATimeFlag + */ +class clsLEATimeFlag { +public: + // constructor + clsLEATimeFlag(unsigned long p_ulTimeout = (unsigned long)(-1)) + : m_ulStart(millis()), + m_ulTimeout(p_ulTimeout) { + } + // operator bool + operator bool(void) const { + return flagged(); + } + // flagged + bool flagged(void) const { + return ((millis() - m_ulStart) > m_ulTimeout); + } + // restart + void restart(unsigned long p_ulNewTimeout = (unsigned long)(-1)) { + if ((unsigned long)(-1) != p_ulNewTimeout) { + m_ulTimeout = p_ulNewTimeout; + } + m_ulStart = millis(); + } + void reset(void) { + m_ulTimeout = (unsigned long)(-1); + m_ulStart = millis(); + } + + unsigned long start(void) const { + return m_ulStart; + } + unsigned long timeout(void) const { + return m_ulTimeout; + } + bool hypotheticalTimeout(unsigned long p_ulHypotheticalTimeout) const { + return ((millis() - m_ulStart) > p_ulHypotheticalTimeout); + } + +protected: + unsigned long m_ulStart; + unsigned long m_ulTimeout; +}; + +#endif // __LEATIMEFLAG_H + + + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS.cpp b/libraries/ESP8266mDNS/src/LEAmDNS.cpp new file mode 100755 index 0000000000..037d84f484 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS.cpp @@ -0,0 +1,1120 @@ +/* + * LEAmDNS.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 "LEAmDNS_Priv.h" + +/* + * LEAmDNS + */ +namespace LEAmDNS { + +/** + * STRINGIZE + */ +#ifndef STRINGIZE + #define STRINGIZE(x) #x +#endif +#ifndef STRINGIZE_VALUE_OF + #define STRINGIZE_VALUE_OF(x) STRINGIZE(x) +#endif + + +/** + * INTERFACE + */ + +/** + * MDNSResponder::MDNSResponder + */ +MDNSResponder::MDNSResponder(void) +: m_pServices(0), + m_pUDPContext(0), + m_pcHostname(0), + m_pServiceQueries(0), + m_fnServiceTxtCallback(0), + m_pServiceTxtCallbackUserdata(0) { + +} + +/* + * MDNSResponder::~MDNSResponder + */ +MDNSResponder::~MDNSResponder(void) { + + _resetProbeStatus(false); + _releaseServiceQueries(); + _releaseHostname(); + _releaseUDPContext(); + _releaseServices(); +} + +/* + * MDNSResponder::begin + * + * Set the host domain (for probing) and install WiFi event handlers for + * IP assignment and disconnection management. In both cases, the MDNS responder + * is restarted (reset and restart probe status) + * Finally the responder is (re)started + * + */ +bool MDNSResponder::begin(const char* p_pcHostname) { + + bool bResult = false; + + if (_setHostname(p_pcHostname)) { + + m_GotIPHandler = WiFi.onStationModeGotIP([this](const WiFiEventStationModeGotIP& pEvent) { + (void) pEvent; + _restart(); + }); + + m_DisconnectedHandler = WiFi.onStationModeDisconnected([this](const WiFiEventStationModeDisconnected& pEvent) { + (void) pEvent; + _restart(); + }); + + bResult = _restart(); + } + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] begin: FAILED for '%s'!\n"), (p_pcHostname ?: "-")); } ); + return bResult; +} + +/* + * MDNSResponder::begin (LEGACY) + */ +bool MDNSResponder::begin(const char* p_pcHostname, + IPAddress p_IPAddress, + uint32_t p_u32TTL /*= 120*/) { + + (void) p_IPAddress; + (void) p_u32TTL; + return begin(p_pcHostname); +} + +/* + * MDNSResponder::close + * + * Ends the MDNS responder. + * Announced services are unannounced (by multicasting a goodbye message) + * + */ +bool MDNSResponder::close(void) { + + _announce(false); + _resetProbeStatus(false); // Stop probing + + _releaseServiceQueries(); + _releaseUDPContext(); + _releaseHostname(); + + return true; +} + +/* + * MDNSResponder::setHostname + * + * Replaces the current hostname and restarts probing. + * For services without own instance name (when the host name was used a instance + * name), the instance names are replaced also (and the probing is restarted). + * + */ +bool MDNSResponder::setHostname(const char* p_pcHostname) { + + bool bResult = false; + + if (_setHostname(p_pcHostname)) { + m_HostProbeInformation.m_ProbingStatus = ProbingStatus_ReadyToStart; + + // Replace 'auto-set' service names + bResult = true; + for (stcMDNSService* pService=m_pServices; ((bResult) && (pService)); pService=pService->m_pNext) { + if (pService->m_bAutoName) { + bResult = pService->setName(p_pcHostname); + pService->m_ProbeInformation.m_ProbingStatus = ProbingStatus_ReadyToStart; + } + } + } + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] setHostname: FAILED for '%s'!\n"), (p_pcHostname ?: "-")); } ); + return bResult; +} + +/* + * MDNSResponder::setHostname (LEGACY) + */ +bool MDNSResponder::setHostname(String p_strHostname) { + + return setHostname(p_strHostname.c_str()); +} + + +/* + * SERVICES + */ + +/* + * MDNSResponder::addService + * + * Add service; using hostname if no name is explicitly provided for the service + * The usual '_' underline, which is prepended to service and protocol, eg. _http, + * may be given. If not, it is added automatically. + * + */ +MDNSResponder::hMDNSService MDNSResponder::addService(const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol, + uint16_t p_u16Port) { + + hMDNSService hResult = 0; + + if (((!p_pcName) || // NO name OR + (MDNS_DOMAIN_LABEL_MAXLENGTH >= os_strlen(p_pcName))) && // Fitting name + (p_pcService) && + (MDNS_SERVICE_NAME_LENGTH >= os_strlen(p_pcService)) && + (p_pcProtocol) && + ((MDNS_SERVICE_PROTOCOL_LENGTH - 1) != os_strlen(p_pcProtocol)) && + (p_u16Port)) { + + if (!_findService((p_pcName ?: m_pcHostname), p_pcService, p_pcProtocol)) { // Not already used + if (0 != (hResult = (hMDNSService)_allocService(p_pcName, p_pcService, p_pcProtocol, p_u16Port))) { + + // Start probing + ((stcMDNSService*)hResult)->m_ProbeInformation.m_ProbingStatus = ProbingStatus_ReadyToStart; + } + } + } // else: bad arguments + DEBUG_EX_ERR(if (!hResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] addService: FAILED to add '%s.%s.%s'!\n"), (p_pcName ?: "-"), p_pcService, p_pcProtocol); } ); + return hResult; +} + +/* + * MDNSResponder::removeService + * + * Unanounce a service (by sending a goodbye message) and remove it + * from the MDNS responder + * + */ +bool MDNSResponder::removeService(const MDNSResponder::hMDNSService p_hService) { + + stcMDNSService* pService = 0; + bool bResult = (((pService = _findService(p_hService))) && + (_announceService(*pService, false)) && + (_releaseService(pService))); + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] removeService: FAILED!\n")); } ); + return bResult; +} + +/* + * MDNSResponder::removeService + */ +bool MDNSResponder::removeService(const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol) { + + return removeService((hMDNSService)_findService((p_pcName ?: m_pcHostname), p_pcService, p_pcProtocol)); +} + +/* + * MDNSResponder::addService (LEGACY) + */ +bool MDNSResponder::addService(String p_strService, + String p_strProtocol, + uint16_t p_u16Port) { + + return (0 != addService(m_pcHostname, p_strService.c_str(), p_strProtocol.c_str(), p_u16Port)); +} + +/* + * MDNSResponder::setServiceName + */ +bool MDNSResponder::setServiceName(const MDNSResponder::hMDNSService p_hService, + const char* p_pcInstanceName) { + + stcMDNSService* pService = 0; + bool bResult = (((!p_pcInstanceName) || + (MDNS_DOMAIN_LABEL_MAXLENGTH >= os_strlen(p_pcInstanceName))) && + ((pService = _findService(p_hService))) && + (pService->setName(p_pcInstanceName)) && + ((pService->m_ProbeInformation.m_ProbingStatus = ProbingStatus_ReadyToStart))); + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] setServiceName: FAILED for '%s'!\n"), (p_pcInstanceName ?: "-")); } ); + return bResult; +} + +/* + * SERVICE TXT + */ + +/* + * MDNSResponder::addServiceTxt + * + * Add a static service TXT item ('Key'='Value') to a service. + * + */ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hService, + const char* p_pcKey, + const char* p_pcValue) { + + hMDNSTxt hTxt = 0; + stcMDNSService* pService = _findService(p_hService); + if (pService) { + hTxt = (hMDNSTxt)_addServiceTxt(pService, p_pcKey, p_pcValue, false); + } + DEBUG_EX_ERR(if (!hTxt) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] addServiceTxt: FAILED for '%s=%s'!\n"), (p_pcKey ?: "-"), (p_pcValue ?: "-")); } ); + return hTxt; +} + +/* + * MDNSResponder::addServiceTxt (uint32_t) + * + * Formats: http://www.cplusplus.com/reference/cstdio/printf/ + */ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hService, + const char* p_pcKey, + uint32_t p_u32Value) { + char acBuffer[32]; *acBuffer = 0; + sprintf(acBuffer, "%u", p_u32Value); + + return addServiceTxt(p_hService, p_pcKey, acBuffer); +} + +/* + * MDNSResponder::addServiceTxt (uint16_t) + */ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hService, + const char* p_pcKey, + uint16_t p_u16Value) { + char acBuffer[16]; *acBuffer = 0; + sprintf(acBuffer, "%hu", p_u16Value); + + return addServiceTxt(p_hService, p_pcKey, acBuffer); +} + +/* + * MDNSResponder::addServiceTxt (uint8_t) + */ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hService, + const char* p_pcKey, + uint8_t p_u8Value) { + char acBuffer[8]; *acBuffer = 0; + sprintf(acBuffer, "%hhu", p_u8Value); + + return addServiceTxt(p_hService, p_pcKey, acBuffer); +} + +/* + * MDNSResponder::addServiceTxt (int32_t) + */ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hService, + const char* p_pcKey, + int32_t p_i32Value) { + char acBuffer[32]; *acBuffer = 0; + sprintf(acBuffer, "%i", p_i32Value); + + return addServiceTxt(p_hService, p_pcKey, acBuffer); +} + +/* + * MDNSResponder::addServiceTxt (int16_t) + */ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hService, + const char* p_pcKey, + int16_t p_i16Value) { + char acBuffer[16]; *acBuffer = 0; + sprintf(acBuffer, "%hi", p_i16Value); + + return addServiceTxt(p_hService, p_pcKey, acBuffer); +} + +/* + * MDNSResponder::addServiceTxt (int8_t) + */ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hService, + const char* p_pcKey, + int8_t p_i8Value) { + char acBuffer[8]; *acBuffer = 0; + sprintf(acBuffer, "%hhi", p_i8Value); + + return addServiceTxt(p_hService, p_pcKey, acBuffer); +} + +/* + * MDNSResponder::removeServiceTxt + * + * Remove a static service TXT item from a service. + */ +bool MDNSResponder::removeServiceTxt(const MDNSResponder::hMDNSService p_hService, + const MDNSResponder::hMDNSTxt p_hTxt) { + + bool bResult = false; + + stcMDNSService* pService = _findService(p_hService); + if (pService) { + stcMDNSServiceTxt* pTxt = _findServiceTxt(pService, p_hTxt); + if (pTxt) { + bResult = _releaseServiceTxt(pService, pTxt); + } + } + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] removeServiceTxt: FAILED!\n")); } ); + return bResult; +} + +/* + * MDNSResponder::removeServiceTxt + */ +bool MDNSResponder::removeServiceTxt(const MDNSResponder::hMDNSService p_hService, + const char* p_pcKey) { + + bool bResult = false; + + stcMDNSService* pService = _findService(p_hService); + if (pService) { + stcMDNSServiceTxt* pTxt = _findServiceTxt(pService, p_pcKey); + if (pTxt) { + bResult = _releaseServiceTxt(pService, pTxt); + } + } + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] removeServiceTxt: FAILED for '%s'!\n"), (p_pcKey ?: "-")); } ); + return bResult; +} + +/* + * MDNSResponder::removeServiceTxt + */ +bool MDNSResponder::removeServiceTxt(const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol, + const char* p_pcKey) { + + bool bResult = false; + + stcMDNSService* pService = _findService((p_pcName ?: m_pcHostname), p_pcService, p_pcProtocol); + if (pService) { + stcMDNSServiceTxt* pTxt = _findServiceTxt(pService, p_pcKey); + if (pTxt) { + bResult = _releaseServiceTxt(pService, pTxt); + } + } + return bResult; +} + +/* + * MDNSResponder::addServiceTxt (LEGACY) + */ +bool MDNSResponder::addServiceTxt(const char* p_pcService, + const char* p_pcProtocol, + const char* p_pcKey, + const char* p_pcValue) { + + return (0 != _addServiceTxt(_findService(m_pcHostname, p_pcService, p_pcProtocol), p_pcKey, p_pcValue, false)); +} + +/* + * MDNSResponder::addServiceTxt (LEGACY) + */ +bool MDNSResponder::addServiceTxt(String p_strService, + String p_strProtocol, + String p_strKey, + String p_strValue) { + + return (0 != _addServiceTxt(_findService(m_pcHostname, p_strService.c_str(), p_strProtocol.c_str()), p_strKey.c_str(), p_strValue.c_str(), false)); +} + +/* + * MDNSResponder::setDynamicServiceTxtCallback (global) + * + * Set a global callback for dynamic service TXT items. The callback is called, whenever + * service TXT items are needed. + * + */ +bool MDNSResponder::setDynamicServiceTxtCallback(MDNSResponder::MDNSDynamicServiceTxtCallbackFn p_fnCallback, + void* p_pUserdata) { + + m_fnServiceTxtCallback = p_fnCallback; + m_pServiceTxtCallbackUserdata = p_pUserdata; + + return true; +} + +/* + * MDNSResponder::setDynamicServiceTxtCallback (service specific) + * + * Set a service specific callback for dynamic service TXT items. The callback is called, whenever + * service TXT items are needed for the given service. + * + */ +bool MDNSResponder::setDynamicServiceTxtCallback(MDNSResponder::hMDNSService p_hService, + MDNSResponder::MDNSDynamicServiceTxtCallbackFn p_fnCallback, + void* p_pUserdata) { + + bool bResult = false; + + stcMDNSService* pService = _findService(p_hService); + if (pService) { + pService->m_fnTxtCallback = p_fnCallback; + pService->m_pTxtCallbackUserdata = p_pUserdata; + + bResult = true; + } + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] setDynamicServiceTxtCallback: FAILED!\n")); } ); + return bResult; +} + +/* + * MDNSResponder::addDynamicServiceTxt + */ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(MDNSResponder::hMDNSService p_hService, + const char* p_pcKey, + const char* p_pcValue) { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] addDynamicServiceTxt (%s=%s)\n"), p_pcKey, p_pcValue);); + + hMDNSTxt hTxt = 0; + + stcMDNSService* pService = _findService(p_hService); + if (pService) { + hTxt = _addServiceTxt(pService, p_pcKey, p_pcValue, true); + } + DEBUG_EX_ERR(if (!hTxt) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] addDynamicServiceTxt: FAILED for '%s=%s'!\n"), (p_pcKey ?: "-"), (p_pcValue ?: "-")); } ); + return hTxt; +} + +/* + * MDNSResponder::addDynamicServiceTxt (uint32_t) + */ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(MDNSResponder::hMDNSService p_hService, + const char* p_pcKey, + uint32_t p_u32Value) { + + char acBuffer[32]; *acBuffer = 0; + sprintf(acBuffer, "%u", p_u32Value); + + return addDynamicServiceTxt(p_hService, p_pcKey, acBuffer); +} + +/* + * MDNSResponder::addDynamicServiceTxt (uint16_t) + */ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(MDNSResponder::hMDNSService p_hService, + const char* p_pcKey, + uint16_t p_u16Value) { + + char acBuffer[16]; *acBuffer = 0; + sprintf(acBuffer, "%hu", p_u16Value); + + return addDynamicServiceTxt(p_hService, p_pcKey, acBuffer); +} + +/* + * MDNSResponder::addDynamicServiceTxt (uint8_t) + */ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(MDNSResponder::hMDNSService p_hService, + const char* p_pcKey, + uint8_t p_u8Value) { + + char acBuffer[8]; *acBuffer = 0; + sprintf(acBuffer, "%hhu", p_u8Value); + + return addDynamicServiceTxt(p_hService, p_pcKey, acBuffer); +} + +/* + * MDNSResponder::addDynamicServiceTxt (int32_t) + */ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(MDNSResponder::hMDNSService p_hService, + const char* p_pcKey, + int32_t p_i32Value) { + + char acBuffer[32]; *acBuffer = 0; + sprintf(acBuffer, "%i", p_i32Value); + + return addDynamicServiceTxt(p_hService, p_pcKey, acBuffer); +} + +/* + * MDNSResponder::addDynamicServiceTxt (int16_t) + */ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(MDNSResponder::hMDNSService p_hService, + const char* p_pcKey, + int16_t p_i16Value) { + + char acBuffer[16]; *acBuffer = 0; + sprintf(acBuffer, "%hi", p_i16Value); + + return addDynamicServiceTxt(p_hService, p_pcKey, acBuffer); +} + +/* + * MDNSResponder::addDynamicServiceTxt (int8_t) + */ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(MDNSResponder::hMDNSService p_hService, + const char* p_pcKey, + int8_t p_i8Value) { + + char acBuffer[8]; *acBuffer = 0; + sprintf(acBuffer, "%hhi", p_i8Value); + + return addDynamicServiceTxt(p_hService, p_pcKey, acBuffer); +} + + +/** + * STATIC SERVICE QUERY (LEGACY) + */ + +/* + * MDNSResponder::queryService + * + * Perform a (blocking) static service query. + * The arrived answers can be queried by calling: + * - answerHostname (or 'hostname') + * - answerIP (or 'IP') + * - answerPort (or 'port') + * + */ +uint32_t MDNSResponder::queryService(const char* p_pcService, + const char* p_pcProtocol, + const uint16_t p_u16Timeout /*= MDNS_QUERYSERVICES_WAIT_TIME*/) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] queryService '%s.%s'\n"), p_pcService, p_pcProtocol);); + + uint32_t u32Result = 0; + + stcMDNSServiceQuery* pServiceQuery = 0; + if ((p_pcService) && + (os_strlen(p_pcService)) && + (p_pcProtocol) && + (os_strlen(p_pcProtocol)) && + (p_u16Timeout) && + (_removeLegacyServiceQuery()) && + ((pServiceQuery = _allocServiceQuery())) && + (_buildDomainForService(p_pcService, p_pcProtocol, pServiceQuery->m_ServiceTypeDomain))) { + + pServiceQuery->m_bLegacyQuery = true; + + if (_sendMDNSServiceQuery(*pServiceQuery)) { + // Wait for answers to arrive + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] queryService: Waiting %u ms for answers...\n"), p_u16Timeout);); + delay(p_u16Timeout); + + // All answers should have arrived by now -> stop adding new answers + pServiceQuery->m_bAwaitingAnswers = false; + u32Result = pServiceQuery->answerCount(); + } + else { // FAILED to send query + _removeServiceQuery(pServiceQuery); + } + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] queryService: INVALID input data!\n"), p_pcService, p_pcProtocol);); + } + return u32Result; +} + +/* + * MDNSResponder::removeQuery + * + * Remove the last static service query (and all answers). + * + */ +bool MDNSResponder::removeQuery(void) { + + return _removeLegacyServiceQuery(); +} + +/* + * MDNSResponder::queryService (LEGACY) + */ +uint32_t MDNSResponder::queryService(String p_strService, + String p_strProtocol) { + + return queryService(p_strService.c_str(), p_strProtocol.c_str()); +} + +/* + * MDNSResponder::answerHostname + */ +const char* MDNSResponder::answerHostname(const uint32_t p_u32AnswerIndex) { + + stcMDNSServiceQuery* pServiceQuery = _findLegacyServiceQuery(); + stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + + if ((pSQAnswer) && + (pSQAnswer->m_HostDomain.m_u16NameLength) && + (!pSQAnswer->m_pcHostDomain)) { + + char* pcHostDomain = pSQAnswer->allocHostDomain(pSQAnswer->m_HostDomain.c_strLength()); + if (pcHostDomain) { + pSQAnswer->m_HostDomain.c_str(pcHostDomain); + } + } + return (pSQAnswer ? pSQAnswer->m_pcHostDomain : 0); +} + +#ifdef MDNS_IP4_SUPPORT + /* + * MDNSResponder::answerIP + */ + IPAddress MDNSResponder::answerIP(const uint32_t p_u32AnswerIndex) { + + const stcMDNSServiceQuery* pServiceQuery = _findLegacyServiceQuery(); + const stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + const stcMDNSServiceQuery::stcAnswer::stcIP4Address* pIP4Address = (((pSQAnswer) && (pSQAnswer->m_pIP4Addresses)) ? pSQAnswer->IP4AddressAtIndex(0) : 0); + return (pIP4Address ? pIP4Address->m_IPAddress : IPAddress()); + } +#endif + +#ifdef MDNS_IP6_SUPPORT + /* + * MDNSResponder::answerIP6 + */ + IPAddress MDNSResponder::answerIP6(const uint32_t p_u32AnswerIndex) { + + const stcMDNSServiceQuery* pServiceQuery = _findLegacyServiceQuery(); + const stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + const stcMDNSServiceQuery::stcAnswer::stcIP6Address* pIP6Address = (((pSQAnswer) && (pSQAnswer->m_pIP6Addresses)) ? pSQAnswer->IP6AddressAtIndex(0) : 0); + return (pIP6Address ? pIP6Address->m_IPAddress : IP6Address()); + } +#endif + +/* + * MDNSResponder::answerPort + */ +uint16_t MDNSResponder::answerPort(const uint32_t p_u32AnswerIndex) { + + const stcMDNSServiceQuery* pServiceQuery = _findLegacyServiceQuery(); + const stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return (pSQAnswer ? pSQAnswer->m_u16Port : 0); +} + +/* + * MDNSResponder::hostname (LEGACY) + */ +String MDNSResponder::hostname(const uint32_t p_u32AnswerIndex) { + + return String(answerHostname(p_u32AnswerIndex)); +} + +/* + * MDNSResponder::IP (LEGACY) + */ +IPAddress MDNSResponder::IP(const uint32_t p_u32AnswerIndex) { + + return answerIP(p_u32AnswerIndex); +} + +/* + * MDNSResponder::port (LEGACY) + */ +uint16_t MDNSResponder::port(const uint32_t p_u32AnswerIndex) { + + return answerPort(p_u32AnswerIndex); +} + + +/** + * DYNAMIC SERVICE QUERY + */ + +/* + * MDNSResponder::installServiceQuery + * + * Add a dynamic service query and a corresponding callback to the MDNS responder. + * The callback will be called for every answer update. + * The answers can also be queried by calling: + * - answerServiceDomain + * - answerHostDomain + * - answerIP4Address/answerIP6Address + * - answerPort + * - answerTxts + * + */ +MDNSResponder::hMDNSServiceQuery MDNSResponder::installServiceQuery(const char* p_pcService, + const char* p_pcProtocol, + MDNSResponder::MDNSServiceQueryCallbackFn p_fnCallback, + void* p_pUserdata) { + hMDNSServiceQuery hResult = 0; + + stcMDNSServiceQuery* pServiceQuery = 0; + if ((p_pcService) && + (os_strlen(p_pcService)) && + (p_pcProtocol) && + (os_strlen(p_pcProtocol)) && + (p_fnCallback) && + ((pServiceQuery = _allocServiceQuery())) && + (_buildDomainForService(p_pcService, p_pcProtocol, pServiceQuery->m_ServiceTypeDomain))) { + + pServiceQuery->m_fnCallback = p_fnCallback; + pServiceQuery->m_pUserdata = p_pUserdata; + pServiceQuery->m_bLegacyQuery = false; + + if (_sendMDNSServiceQuery(*pServiceQuery)) { + hResult = (hMDNSServiceQuery)pServiceQuery; + } + else { + _removeServiceQuery(pServiceQuery); + } + } + DEBUG_EX_ERR(if (!hResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] installServiceQuery: FAILED for '%s.%s'!\n"), (p_pcService ?: "-"), (p_pcProtocol ?: "-")); } ); + return hResult; +} + +/* + * MDNSResponder::removeServiceQuery + * + * Remove a dynamic service query (and all collected answers) from the MDNS responder + * + */ +bool MDNSResponder::removeServiceQuery(MDNSResponder::hMDNSServiceQuery p_hServiceQuery) { + + stcMDNSServiceQuery* pServiceQuery = 0; + bool bResult = (((pServiceQuery = _findServiceQuery(p_hServiceQuery))) && + (_removeServiceQuery(pServiceQuery))); + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] removeServiceQuery: FAILED!\n")); } ); + return bResult; +} + +/* + * MDNSResponder::answerCount + */ +uint32_t MDNSResponder::answerCount(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery) { + + stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery); + return (pServiceQuery ? pServiceQuery->answerCount() : 0); +} + +/* + * MDNSResponder::answerServiceDomain + * + * Returns the domain for the given service. + * If not already existing, the string is allocated, filled and attached to the answer. + * + */ +const char* MDNSResponder::answerServiceDomain(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) { + + stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery); + stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + // Fill m_pcServiceDomain (if not already done) + if ((pSQAnswer) && + (pSQAnswer->m_ServiceDomain.m_u16NameLength) && + (!pSQAnswer->m_pcServiceDomain)) { + + pSQAnswer->m_pcServiceDomain = pSQAnswer->allocServiceDomain(pSQAnswer->m_ServiceDomain.c_strLength()); + if (pSQAnswer->m_pcServiceDomain) { + pSQAnswer->m_ServiceDomain.c_str(pSQAnswer->m_pcServiceDomain); + } + } + return (pSQAnswer ? pSQAnswer->m_pcServiceDomain : 0); +} + +/* + * MDNSResponder::hasAnswerHostDomain + */ +bool MDNSResponder::hasAnswerHostDomain(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) { + + stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery); + stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return ((pSQAnswer) && + (pSQAnswer->m_u32ContentFlags & ServiceQueryAnswerType_HostDomainAndPort)); +} + +/* + * MDNSResponder::answerHostDomain + * + * Returns the host domain for the given service. + * If not already existing, the string is allocated, filled and attached to the answer. + * + */ +const char* MDNSResponder::answerHostDomain(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) { + + stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery); + stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + // Fill m_pcHostDomain (if not already done) + if ((pSQAnswer) && + (pSQAnswer->m_HostDomain.m_u16NameLength) && + (!pSQAnswer->m_pcHostDomain)) { + + pSQAnswer->m_pcHostDomain = pSQAnswer->allocHostDomain(pSQAnswer->m_HostDomain.c_strLength()); + if (pSQAnswer->m_pcHostDomain) { + pSQAnswer->m_HostDomain.c_str(pSQAnswer->m_pcHostDomain); + } + } + return (pSQAnswer ? pSQAnswer->m_pcHostDomain : 0); +} + +#ifdef MDNS_IP4_SUPPORT + /* + * MDNSResponder::hasAnswerIP4Address + */ + bool MDNSResponder::hasAnswerIP4Address(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) { + + stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery); + stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return ((pSQAnswer) && + (pSQAnswer->m_u32ContentFlags & ServiceQueryAnswerType_IP4Address)); + } + + /* + * MDNSResponder::answerIP4AddressCount + */ + uint32_t MDNSResponder::answerIP4AddressCount(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) { + + stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery); + stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return (pSQAnswer ? pSQAnswer->IP4AddressCount() : 0); + } + + /* + * MDNSResponder::answerIP4Address + */ + IPAddress MDNSResponder::answerIP4Address(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex) { + + stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery); + stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + stcMDNSServiceQuery::stcAnswer::stcIP4Address* pIP4Address = (pSQAnswer ? pSQAnswer->IP4AddressAtIndex(p_u32AddressIndex) : 0); + return (pIP4Address ? pIP4Address->m_IPAddress : IPAddress()); + } +#endif + +#ifdef MDNS_IP6_SUPPORT + /* + * MDNSResponder::hasAnswerIP6Address + */ + bool MDNSResponder::hasAnswerIP6Address(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) { + + stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery); + stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return ((pSQAnswer) && + (pSQAnswer->m_u32ContentFlags & ServiceQueryAnswerType_HostIP6Address)); + } + + /* + * MDNSResponder::answerIP6AddressCount + */ + uint32_t MDNSResponder::answerIP6AddressCount(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) { + + stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery); + stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return (pSQAnswer ? pSQAnswer->IP6AddressCount() : 0); + } + + /* + * MDNSResponder::answerIP6Address + */ + IPAddress MDNSResponder::answerIP6Address(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex) { + + stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery); + stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + stcMDNSServiceQuery::stcAnswer::stcIP6Address* pIP6Address = (pSQAnswer ? pSQAnswer->IP6AddressAtIndex(p_u32AddressIndex) : 0); + return (pIP6Address ? pIP6Address->m_IPAddress : IPAddress()); + } +#endif + +/* + * MDNSResponder::hasAnswerPort + */ +bool MDNSResponder::hasAnswerPort(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) { + + stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery); + stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return ((pSQAnswer) && + (pSQAnswer->m_u32ContentFlags & ServiceQueryAnswerType_HostDomainAndPort)); +} + +/* + * MDNSResponder::answerPort + */ +uint16_t MDNSResponder::answerPort(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) { + + stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery); + stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return (pSQAnswer ? pSQAnswer->m_u16Port : 0); +} + +/* + * MDNSResponder::hasAnswerTxts + */ +bool MDNSResponder::hasAnswerTxts(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) { + + stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery); + stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return ((pSQAnswer) && + (pSQAnswer->m_u32ContentFlags & ServiceQueryAnswerType_Txts)); +} + +/* + * MDNSResponder::answerTxts + * + * 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* MDNSResponder::answerTxts(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) { + + stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery); + stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + // Fill m_pcTxts (if not already done) + if ((pSQAnswer) && + (pSQAnswer->m_Txts.m_pTxts) && + (!pSQAnswer->m_pcTxts)) { + + pSQAnswer->m_pcTxts = pSQAnswer->allocTxts(pSQAnswer->m_Txts.c_strLength()); + if (pSQAnswer->m_pcTxts) { + pSQAnswer->m_Txts.c_str(pSQAnswer->m_pcTxts); + } + } + return (pSQAnswer ? pSQAnswer->m_pcTxts : 0); +} + + +/* + * PROBING + */ + +/* + * MDNSResponder::setProbeResultCallback + * + * Set a global callback for probe results. The callback is called, when probing + * for the host domain (or a service domain, without specific probe result callback) + * failes or succeedes. + * In the case of failure, the domain name should be changed via 'setHostname' or 'setServiceName'. + * When succeeded, the host or service domain will be announced by the MDNS responder. + * + */ +bool MDNSResponder::setProbeResultCallback(MDNSResponder::MDNSProbeResultCallbackFn p_fnCallback, + void* p_pUserdata) { + + m_HostProbeInformation.m_fnProbeResultCallback = p_fnCallback; + m_HostProbeInformation.m_pProbeResultCallbackUserdata = p_pUserdata; + + return true; +} + +/* + * MDNSResponder::setServiceProbeResultCallback + * + * Set a service specific callback for probe results. The callback is called, when probing + * for the service domain failes or succeedes. + * In the case of failure, the service name should be changed via 'setServiceName'. + * When succeeded, the service domain will be announced by the MDNS responder. + * + */ +bool MDNSResponder::setServiceProbeResultCallback(const MDNSResponder::hMDNSService p_hService, + MDNSResponder::MDNSProbeResultCallbackFn p_fnCallback, + void* p_pUserdata) { + bool bResult = false; + + stcMDNSService* pService = _findService(p_hService); + if (pService) { + pService->m_ProbeInformation.m_fnProbeResultCallback = p_fnCallback; + pService->m_ProbeInformation.m_pProbeResultCallbackUserdata = p_pUserdata; + + bResult = true; + } + return bResult; +} + + +/* + * MISC + */ + +/* + * MDNSResponder::notifyAPChange + * + * Should be called, whenever the AP for the MDNS responder changes. + * A bit of this is caught by the event callbacks installed in the constructor. + * + */ +bool MDNSResponder::notifyAPChange(void) { + + return _restart(); +} + +/* + * MDNSResponder::update + * + * Should be called in every 'loop'. + * + */ +bool MDNSResponder::update(void) { + + return _process(true); +} + +/* + * MDNSResponder::announce + * + * Should be called, if the 'configuration' changes. Mainly this will be changes in the TXT items... + */ +bool MDNSResponder::announce(void) { + + return (_announce()); +} + +/* + * MDNSResponder::enableArduino + * + * Enable the OTA update service. + * + */ +MDNSResponder::hMDNSService MDNSResponder::enableArduino(uint16_t p_u16Port, + bool p_bAuthUpload /*= false*/) { + + hMDNSService hService = addService(0, "arduino", "tcp", p_u16Port); + if (hService) { + if ((!addServiceTxt(hService, "tcp_check", "no")) || + (!addServiceTxt(hService, "ssh_upload", "no")) || + (!addServiceTxt(hService, "board", STRINGIZE_VALUE_OF(ARDUINO_BOARD))) || + (!addServiceTxt(hService, "auth_upload", (p_bAuthUpload) ? "yes" : "no"))) { + + removeService(hService); + hService = 0; + } + } + return hService; +} + +/* + * MDNS responder global instance + */ +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) +MDNSResponder MDNS; +#endif + +} // namespace LEAmDNS + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS.h b/libraries/ESP8266mDNS/src/LEAmDNS.h new file mode 100755 index 0000000000..bea1a44300 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS.h @@ -0,0 +1,1262 @@ +/* + * LEAmDNS.h + * (c) 2018, LaborEtArs + * + * Version 0.9 beta + * + * Some notes (from LaborEtArs, 2018): + * Essentially, this is an rewrite of the original EPS8266 Multicast DNS code (ESP8266mDNS). + * The target of this rewrite was to keep the existing interface as stable as possible while + * adding and extending the supported set of mDNS features. + * A lot of the additions were basicly taken from Erik Ekman's lwIP mdns app code. + * + * Supported mDNS features (in some cases somewhat limited): + * - 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) + * - 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 DNS-SD services (creating a fixed answer set after a certain timeout period) + * - Dynamic queries for DNS-SD services with cached and updated answers and user notifications + * + * + * Usage: + * In most cases, this implementation should work as a 'drop-in' replacement for the original + * ESP8266 Multicast DNS code. Adjustments to the existing code would only be needed, if some + * of the new features should be used. + * + * For presenting services: + * In 'setup()': + * Install a callback for the probing of host (and service) domains via 'MDNS.setProbeResultCallback(probeResultCallback, &userData);' + * Register DNS-SD services with 'MDNSResponder::hMDNSService hService = MDNS.addService("MyESP", "http", "tcp", 5000);' + * (Install additional callbacks for the probing of these service domains via 'MDNS.setServiceProbeResultCallback(hService, probeResultCallback, &userData);') + * Add service TXT items with 'MDNS.addServiceTxt(hService, "c#", "1");' or by installing a service TXT callback + * using 'MDNS.setDynamicServiceTxtCallback(dynamicServiceTxtCallback, &userData);' or service specific + * 'MDNS.setDynamicServiceTxtCallback(hService, dynamicServiceTxtCallback, &userData);' + * Call MDNS.begin("MyHostname"); + * + * In 'probeResultCallback(MDNSResponder* p_MDNSResponder, const char* p_pcDomain, MDNSResponder:hMDNSService p_hService, bool p_bProbeResult, void* p_pUserdata)': + * Check the probe result and update the host or service domain name if the probe failed + * + * In 'dynamicServiceTxtCallback(MDNSResponder* p_MDNSResponder, const hMDNSService p_hService, void* p_pUserdata)': + * Add dynamic TXT items by calling 'MDNS.addDynamicServiceTxt(p_hService, "c#", "1");' + * + * In loop(): + * Call 'MDNS.update();' + * + * + * For querying services: + * Static: + * Call 'uint32_t u32AnswerCount = MDNS.queryService("http", "tcp");' + * Iterate answers by: 'for (uint32_t u=0; u // for UdpContext.h +#include "WiFiUdp.h" +#include "lwip/udp.h" +#include "debug.h" +#include "include/UdpContext.h" +#include "LEATimeFlag.h" + +#include "ESP8266WiFi.h" + +/** + * LEAmDNS + */ +namespace LEAmDNS { + +//this should be defined at build time +#ifndef ARDUINO_BOARD +#define ARDUINO_BOARD "generic" +#endif + +#define MDNS_IP4_SUPPORT +//#define MDNS_IP6_SUPPORT + + +#ifdef MDNS_IP4_SUPPORT + #define MDNS_IP4_SIZE 4 +#endif +#ifdef MDNS_IP6_SUPPORT + #define MDNS_IP6_SIZE 16 +#endif +/* + * Maximum length for all service txts for one service + */ +#define MDNS_SERVICE_TXT_MAXLENGTH 1300 +/* + * Maximum length for a full domain name eg. MyESP._http._tcp.local + */ +#define MDNS_DOMAIN_MAXLENGTH 256 +/* + * Maximum length of on label in a domain name (length info fits into 6 bits) + */ +#define MDNS_DOMAIN_LABEL_MAXLENGTH 63 +/* + * Maximum length of a service name eg. http + */ +#define MDNS_SERVICE_NAME_LENGTH 15 +/* + * Maximum length of a service protocol name eg. tcp + */ +#define MDNS_SERVICE_PROTOCOL_LENGTH 3 +/* + * Default timeout for static service queries + */ +#define MDNS_QUERYSERVICES_WAIT_TIME 1000 + + +/** + * MDNSResponder + */ +class MDNSResponder { +public: + /* INTERFACE */ + MDNSResponder(void); + virtual ~MDNSResponder(void); + + // Start the MDNS responder by setting the default hostname + // Later call MDNS::update() in every 'loop' to run the process loop + // (probing, announcing, responding, ...) + bool begin(const char* p_pcHostname); + // for compatibility + bool begin(const char* p_pcHostname, + IPAddress p_IPAddress, // ignored + uint32_t p_u32TTL = 120); // ignored + // Finish MDNS processing + bool close(void); + + // Change hostname (probing is restarted) + bool setHostname(const char* p_pcHostname); + // for compatibility... + bool setHostname(String p_strHostname); + + /** + * hMDNSService (opaque handle to access the service) + */ + typedef const void* hMDNSService; + + // Add a new service to the MDNS responder. If no name (instance name) is given (p_pcName = 0) + // the current hostname is used. If the hostname is changed later, the instance names for + // these 'auto-named' services are changed to the new name also (and probing is restarted). + // The usual '_' before p_pcService (eg. http) and protocol (eg. tcp) may be given. + hMDNSService addService(const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol, + uint16_t p_u16Port); + // Removes a service from the MDNS responder + bool removeService(const hMDNSService p_hService); + bool removeService(const char* p_pcInstanceName, + const char* p_pcServiceName, + const char* p_pcProtocol); + // for compatibility... + bool addService(String p_strServiceName, + String p_strProtocol, + uint16_t p_u16Port); + // Change the services instance name (and restart probing). + bool setServiceName(const hMDNSService p_hService, + const char* p_pcInstanceName); + + /** + * hMDNSTxt (opaque handle to access the TXT items) + */ + typedef void* hMDNSTxt; + + // Add a (static) MDNS TXT item ('key' = 'value') to the service + hMDNSTxt addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + const char* p_pcValue); + hMDNSTxt addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint32_t p_u32Value); + hMDNSTxt addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint16_t p_u16Value); + hMDNSTxt addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint8_t p_u8Value); + hMDNSTxt addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int32_t p_i32Value); + hMDNSTxt addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int16_t p_i16Value); + hMDNSTxt addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int8_t p_i8Value); + // Remove an existing (static) MDNS TXT item from the service + bool removeServiceTxt(const hMDNSService p_hService, + const hMDNSTxt p_hTxt); + bool removeServiceTxt(const hMDNSService p_hService, + const char* p_pcKey); + bool removeServiceTxt(const char* p_pcinstanceName, + const char* p_pcServiceName, + const char* p_pcProtocol, + const char* p_pcKey); + // for compatibility... + bool addServiceTxt(const char* p_pcService, + const char* p_pcProtocol, + const char* p_pcKey, + const char* p_pcValue); + bool addServiceTxt(String p_strService, + String p_strProtocol, + String p_strKey, + String p_strValue); + + /** + * MDNSDynamicServiceTxtCallbackFn + * Callback function for dynamic MDNS TXT items + */ + typedef bool (*MDNSDynamicServiceTxtCallbackFn)(MDNSResponder* p_pMDNSResponder, + const hMDNSService p_hService, + void* p_pUserdata); + + // Set a global callback for dynamic MDNS TXT items. The callback function is called + // every time, a TXT item is needed for one of the installed services. + bool setDynamicServiceTxtCallback(MDNSDynamicServiceTxtCallbackFn p_fnCallback, + void* p_pUserdata); + // Set a service specific callback for dynamic MDNS TXT items. The callback function + // is called every time, a TXT item is needed for the given service. + bool setDynamicServiceTxtCallback(const hMDNSService p_hService, + MDNSDynamicServiceTxtCallbackFn p_fnCallback, + void* p_pUserdata); + + // 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). + hMDNSTxt addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + const char* p_pcValue); + hMDNSTxt addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + uint32_t p_u32Value); + hMDNSTxt addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + uint16_t p_u16Value); + hMDNSTxt addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + uint8_t p_u8Value); + hMDNSTxt addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + int32_t p_i32Value); + hMDNSTxt addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + int16_t p_i16Value); + hMDNSTxt addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + int8_t p_i8Value); + + // Perform a (static) service 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) + uint32_t queryService(const char* p_pcService, + const char* p_pcProtocol, + const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME); + bool removeQuery(void); + // for compatibility... + uint32_t queryService(String p_strService, + String p_strProtocol); + + const char* answerHostname(const uint32_t p_u32AnswerIndex); + IPAddress answerIP(const uint32_t p_u32AnswerIndex); + uint16_t answerPort(const uint32_t p_u32AnswerIndex); + // for compatibility... + String hostname(const uint32_t p_u32AnswerIndex); + IPAddress IP(const uint32_t p_u32AnswerIndex); + uint16_t port(const uint32_t p_u32AnswerIndex); + + /** + * hMDNSServiceQuery (opaque handle to access dynamic service queries) + */ + typedef const void* hMDNSServiceQuery; + + /** + * enuServiceQueryAnswerType + */ + typedef enum _enuServiceQueryAnswerType { + ServiceQueryAnswerType_ServiceDomain = (1 << 0), // Service instance name + ServiceQueryAnswerType_HostDomainAndPort = (1 << 1), // Host domain and service port + ServiceQueryAnswerType_Txts = (1 << 2), // TXT items +#ifdef MDNS_IP4_SUPPORT + ServiceQueryAnswerType_IP4Address = (1 << 3), // IP4 address +#endif +#ifdef MDNS_IP6_SUPPORT + ServiceQueryAnswerType_IP6Address = (1 << 4), // IP6 address +#endif + } enuServiceQueryAnswerType; + + /** + * MDNSServiceQueryCallbackFn + * Callback function for received answers for dynamic service queries + */ + typedef bool (*MDNSServiceQueryCallbackFn)(MDNSResponder* p_pMDNSResponder, + const hMDNSServiceQuery p_hServiceQuery, // dynamic service query handle + uint32_t p_u32AnswerIndex, // index of the updated answer + uint32_t p_u32ServiceQueryAnswerMask, // flag for the updated answer item + bool p_bSetContent, // true: Answer component set, false: component deleted + void* p_pUserdata); // pUserdata set via 'installServiceQuery' + + // Install a dynamic service 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 + // - answerServiceDomain + // - hasAnswerHostDomain/answerHostDomain + // - hasAnswerIP4Address/answerIP4Address + // - hasAnswerIP6Address/answerIP6Address + // - hasAnswerPort/answerPort + // - hasAnswerTxts/answerTxts + hMDNSServiceQuery installServiceQuery(const char* p_pcService, + const char* p_pcProtocol, + MDNSServiceQueryCallbackFn p_fnCallback, + void* p_pUserdata); + // Remove a dynamic service query + bool removeServiceQuery(hMDNSServiceQuery p_hServiceQuery); + + uint32_t answerCount(const hMDNSServiceQuery p_hServiceQuery); + const char* answerServiceDomain(const hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex); + bool hasAnswerHostDomain(const hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex); + const char* answerHostDomain(const hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex); +#ifdef MDNS_IP4_SUPPORT + bool hasAnswerIP4Address(const hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex); + uint32_t answerIP4AddressCount(const hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex); + IPAddress answerIP4Address(const hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex); +#endif +#ifdef MDNS_IP6_SUPPORT + bool hasAnswerIP6Address(const hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex); + uint32_t answerIP6AddressCount(const hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex); + IPAddress answerIP6Address(const hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex); +#endif + bool hasAnswerPort(const hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex); + uint16_t answerPort(const hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex); + bool hasAnswerTxts(const hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex); + // Get the TXT items as a ';'-separated string + const char* answerTxts(const hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex); + + /** + * MDNSProbeResultCallbackFn + * Callback function for (host and service domain) probe results + */ + typedef bool (*MDNSProbeResultCallbackFn)(MDNSResponder* p_pMDNSResponder, + const char* p_pcDomainName, + const hMDNSService p_hMDNSService, // 0 for host domain + bool p_bProbeResult, + void* p_pUserdata); + + // Set a global callback function for host and service probe results + // The callback function is called, when the probeing for the host domain + // (or a service domain, which hasn't got a service specific callback) + // Succeededs or fails. + // In case of failure, the failed domain name should be changed. + bool setProbeResultCallback(MDNSProbeResultCallbackFn p_fnCallback, + void* p_pUserdata); + // Set a service specific probe result callcack + bool setServiceProbeResultCallback(const hMDNSService p_hService, + MDNSProbeResultCallbackFn p_fnCallback, + void* p_pUserdata); + + // Application should call this whenever AP is configured/disabled + bool notifyAPChange(void); + + // 'update' should be called in every 'loop' to run the MDNS processing + bool update(void); + + // 'announce' can be called every time, the configuration of some service + // changes. Mainly, this would be changed content of TXT items. + bool announce(void); + + // Enable OTA update + hMDNSService enableArduino(uint16_t p_u16Port, + bool p_bAuthUpload = false); + + // Domain name helper + static bool indexDomain(char*& p_rpcDomain, + const char* p_pcDivider = "-", + const char* p_pcDefaultDomain = 0); + +protected: + /** STRUCTS **/ + /** + * stcMDNSServiceTxt + */ + typedef struct _stcMDNSServiceTxt { + _stcMDNSServiceTxt* m_pNext; + char* m_pcKey; + char* m_pcValue; + bool m_bTemp; + + _stcMDNSServiceTxt(const char* p_pcKey = 0, + const char* p_pcValue = 0, + bool p_bTemp = false); + _stcMDNSServiceTxt(const _stcMDNSServiceTxt& p_Other); + ~_stcMDNSServiceTxt(void); + + _stcMDNSServiceTxt& operator=(const _stcMDNSServiceTxt& 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; + } stcMDNSServiceTxt; + + /** + * stcMDNSTxts + */ + typedef struct _stcMDNSServiceTxts { + stcMDNSServiceTxt* m_pTxts; + + _stcMDNSServiceTxts(void); + _stcMDNSServiceTxts(const _stcMDNSServiceTxts& p_Other); + ~_stcMDNSServiceTxts(void); + + _stcMDNSServiceTxts& operator=(const _stcMDNSServiceTxts& p_Other); + + bool clear(void); + + bool add(stcMDNSServiceTxt* p_pTxt); + bool remove(stcMDNSServiceTxt* p_pTxt); + + bool removeTempTxts(void); + + stcMDNSServiceTxt* find(const char* p_pcKey); + const stcMDNSServiceTxt* find(const char* p_pcKey) const; + stcMDNSServiceTxt* find(const stcMDNSServiceTxt* p_pTxt); + + uint16_t length(void) const; + + size_t c_strLength(void) const; + bool c_str(char* p_pcBuffer); + + size_t bufferLength(void) const; + bool buffer(char* p_pcBuffer); + + bool compare(const _stcMDNSServiceTxts& p_Other) const; + bool operator==(const _stcMDNSServiceTxts& p_Other) const; + bool operator!=(const _stcMDNSServiceTxts& p_Other) const; + } stcMDNSServiceTxts; + + /** + * enuContentFlags + */ + typedef enum _enuContentFlags { + // Host + ContentFlag_A = 0x01, + ContentFlag_PTR_IP4 = 0x02, + ContentFlag_PTR_IP6 = 0x04, + ContentFlag_AAAA = 0x08, + // Service + ContentFlag_PTR_TYPE = 0x10, + ContentFlag_PTR_NAME = 0x20, + ContentFlag_TXT = 0x40, + ContentFlag_SRV = 0x80, + } enuContentFlags; + + /** + * stcMDNS_MsgHeader + */ + typedef struct _stcMDNS_MsgHeader { + uint16_t m_u16ID; // Identifier + bool m_1bQR : 1; // Query/Response flag + unsigned char 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 + unsigned char m_3bZ : 3; // Zero + unsigned char 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 + + _stcMDNS_MsgHeader(uint16_t p_u16ID = 0, + bool p_bQR = false, + unsigned char p_ucOpcode = 0, + bool p_bAA = false, + bool p_bTC = false, + bool p_bRD = false, + bool p_bRA = false, + unsigned char p_ucRCode = 0, + uint16_t p_u16QDCount = 0, + uint16_t p_u16ANCount = 0, + uint16_t p_u16NSCount = 0, + uint16_t p_u16ARCount = 0); + } stcMDNS_MsgHeader; + + /** + * stcMDNS_RRDomain + */ + typedef struct _stcMDNS_RRDomain { + char m_acName[MDNS_DOMAIN_MAXLENGTH]; // Encoded domain name + uint16_t m_u16NameLength; // Length (incl. '\0') + + _stcMDNS_RRDomain(void); + _stcMDNS_RRDomain(const _stcMDNS_RRDomain& p_Other); + + _stcMDNS_RRDomain& operator=(const _stcMDNS_RRDomain& p_Other); + + bool clear(void); + + bool addLabel(const char* p_pcLabel, + bool p_bPrependUnderline = false); + + bool compare(const _stcMDNS_RRDomain& p_Other) const; + bool operator==(const _stcMDNS_RRDomain& p_Other) const; + bool operator!=(const _stcMDNS_RRDomain& p_Other) const; + bool operator>(const _stcMDNS_RRDomain& p_Other) const; + + size_t c_strLength(void) const; + bool c_str(char* p_pcBuffer); + } stcMDNS_RRDomain; + + /** + * stcMDNS_RRAttributes + */ + typedef struct _stcMDNS_RRAttributes { + uint16_t m_u16Type; // Type + uint16_t m_u16Class; // Class, nearly always 'IN' + + _stcMDNS_RRAttributes(uint16_t p_u16Type = 0, + uint16_t p_u16Class = 1 /*DNS_RRCLASS_IN Internet*/); + _stcMDNS_RRAttributes(const _stcMDNS_RRAttributes& p_Other); + + _stcMDNS_RRAttributes& operator=(const _stcMDNS_RRAttributes& p_Other); + } stcMDNS_RRAttributes; + + /** + * stcMDNS_RRHeader + */ + typedef struct _stcMDNS_RRHeader { + stcMDNS_RRDomain m_Domain; + stcMDNS_RRAttributes m_Attributes; + + _stcMDNS_RRHeader(void); + _stcMDNS_RRHeader(const _stcMDNS_RRHeader& p_Other); + + _stcMDNS_RRHeader& operator=(const _stcMDNS_RRHeader& p_Other); + + bool clear(void); + } stcMDNS_RRHeader; + + /** + * stcMDNS_RRQuestion + */ + typedef struct _stcMDNS_RRQuestion { + _stcMDNS_RRQuestion* m_pNext; + _stcMDNS_RRHeader m_Header; + bool m_bUnicast; // Unicast reply requested + + _stcMDNS_RRQuestion(void); + } stcMDNS_RRQuestion; + + /** + * enuAnswerType + */ + typedef enum _enuAnswerType { + AnswerType_A, + AnswerType_PTR, + AnswerType_TXT, + AnswerType_AAAA, + AnswerType_SRV, + AnswerType_Generic + } enuAnswerType; + + /** + * stcMDNS_RRAnswer + */ + typedef struct _stcMDNS_RRAnswer { + _stcMDNS_RRAnswer* m_pNext; + const enuAnswerType m_AnswerType; + _stcMDNS_RRHeader m_Header; + bool m_bCacheFlush; // Cache flush command bit + uint32_t m_u32TTL; // Validity time in seconds + + virtual ~_stcMDNS_RRAnswer(void); + + enuAnswerType answerType(void) const; + + bool clear(void); + + protected: + _stcMDNS_RRAnswer(enuAnswerType p_AnswerType, + const stcMDNS_RRHeader& p_Header, + uint32_t p_u32TTL); + } stcMDNS_RRAnswer; + +#ifdef MDNS_IP4_SUPPORT + /** + * stcMDNS_RRAnswerA + */ + typedef struct _stcMDNS_RRAnswerA : public stcMDNS_RRAnswer { + IPAddress m_IPAddress; + + _stcMDNS_RRAnswerA(const stcMDNS_RRHeader& p_Header, + uint32_t p_u32TTL); + ~_stcMDNS_RRAnswerA(void); + + bool clear(void); + } stcMDNS_RRAnswerA; +#endif + + /** + * stcMDNS_RRAnswerPTR + */ + typedef struct _stcMDNS_RRAnswerPTR : public stcMDNS_RRAnswer { + stcMDNS_RRDomain m_PTRDomain; + + _stcMDNS_RRAnswerPTR(const stcMDNS_RRHeader& p_Header, + uint32_t p_u32TTL); + ~_stcMDNS_RRAnswerPTR(void); + + bool clear(void); + } stcMDNS_RRAnswerPTR; + + /** + * stcMDNS_RRAnswerTXT + */ + typedef struct _stcMDNS_RRAnswerTXT : public stcMDNS_RRAnswer { + stcMDNSServiceTxts m_Txts; + + _stcMDNS_RRAnswerTXT(const stcMDNS_RRHeader& p_Header, + uint32_t p_u32TTL); + ~_stcMDNS_RRAnswerTXT(void); + + bool clear(void); + } stcMDNS_RRAnswerTXT; + +#ifdef MDNS_IP6_SUPPORT + /** + * stcMDNS_RRAnswerAAAA + */ + typedef struct _stcMDNS_RRAnswerAAAA : public stcMDNS_RRAnswer { + //TODO: IP6Address m_IPAddress; + + _stcMDNS_RRAnswerAAAA(const stcMDNS_RRHeader& p_Header, + uint32_t p_u32TTL); + ~_stcMDNS_RRAnswerAAAA(void); + + bool clear(void); + } stcMDNS_RRAnswerAAAA; +#endif + + /** + * stcMDNS_RRAnswerSRV + */ + typedef struct _stcMDNS_RRAnswerSRV : public stcMDNS_RRAnswer { + uint16_t m_u16Priority; + uint16_t m_u16Weight; + uint16_t m_u16Port; + stcMDNS_RRDomain m_SRVDomain; + + _stcMDNS_RRAnswerSRV(const stcMDNS_RRHeader& p_Header, + uint32_t p_u32TTL); + ~_stcMDNS_RRAnswerSRV(void); + + bool clear(void); + } stcMDNS_RRAnswerSRV; + + /** + * stcMDNS_RRAnswerGeneric + */ + typedef struct _stcMDNS_RRAnswerGeneric : public stcMDNS_RRAnswer { + uint16_t m_u16RDLength; // Length of variable answer + uint8_t* m_pu8RDData; // Offset of start of variable answer in packet + + _stcMDNS_RRAnswerGeneric(const stcMDNS_RRHeader& p_Header, + uint32_t p_u32TTL); + ~_stcMDNS_RRAnswerGeneric(void); + + bool clear(void); + } stcMDNS_RRAnswerGeneric; + + + /** + * enuProbingStatus + */ + typedef enum _enuProbingStatus { + ProbingStatus_WaitingForData, + ProbingStatus_ReadyToStart, + ProbingStatus_InProgress, + ProbingStatus_Done + } enuProbingStatus; + + /** + * stcProbeInformation + */ + typedef struct _stcProbeInformation { + enuProbingStatus m_ProbingStatus; + uint8_t m_u8ProbesSent; + //unsigned long m_ulNextProbeTimeout; + clsLEATimeFlag m_NextProbeTimeFlag; + bool m_bConflict; + bool m_bTiebreakNeeded; + MDNSProbeResultCallbackFn m_fnProbeResultCallback; + void* m_pProbeResultCallbackUserdata; + + _stcProbeInformation(void); + + bool clear(bool p_bClearUserdata = false); + } stcProbeInformation; + + + /** + * stcMDNSService + */ + typedef struct _stcMDNSService { + _stcMDNSService* m_pNext; + char* m_pcName; + bool m_bAutoName; // Name was set automatically to hostname (if no name was supplied) + char* m_pcService; + char* m_pcProtocol; + uint16_t m_u16Port; + uint8_t m_u8ReplyMask; + stcMDNSServiceTxts m_Txts; + MDNSDynamicServiceTxtCallbackFn m_fnTxtCallback; + void* m_pTxtCallbackUserdata; + stcProbeInformation m_ProbeInformation; + + _stcMDNSService(const char* p_pcName = 0, + const char* p_pcService = 0, + const char* p_pcProtocol = 0); + ~_stcMDNSService(void); + + bool setName(const char* p_pcName); + bool releaseName(void); + + bool setService(const char* p_pcService); + bool releaseService(void); + + bool setProtocol(const char* p_pcProtocol); + bool releaseProtocol(void); + } stcMDNSService; + + /** + * stcMDNSServiceQuery + */ + typedef struct _stcMDNSServiceQuery { + /** + * stcAnswer + */ + typedef struct _stcAnswer { + /** + * stcTTL + */ + typedef struct _stcTTL { + //uint32_t m_u32TTL; + //uint32_t m_u32Millis; + clsLEATimeFlag m_TTLTimeFlag; + bool m_bUpdateScheduled; + + _stcTTL(uint32_t p_u32TTL = 0/*, + uint32_t p_u32Millis = 0*/); + bool set(uint32_t p_u32TTL/*, + uint32_t p_u32Millis*/); + + bool has80Percent(void/*uint32_t p_u32Now*/) const; + bool isOutdated(void/*uint32_t p_u32Now*/) const; + } stcTTL; +#ifdef MDNS_IP4_SUPPORT + /** + * stcIP4Address + */ + typedef struct _stcIP4Address { + _stcIP4Address* m_pNext; + IPAddress m_IPAddress; + stcTTL m_TTL; + + _stcIP4Address(IPAddress p_IPAddress, + uint32_t p_u32TTL = 0/*, + uint32_t p_u32Millis = 0*/); + } stcIP4Address; +#endif +#ifdef MDNS_IP6_SUPPORT + /** + * stcIP6Address + */ + typedef struct _stcIP6Address { + _stcIP6Address* m_pNext; + IP6Address m_IPAddress; + stcTTL m_TTL; + } stcIP6Address; +#endif + + _stcAnswer* m_pNext; + // 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. + stcMDNS_RRDomain m_ServiceDomain; // 1. level answer (PTR), eg. MyESP._http._tcp.local + char* m_pcServiceDomain; + stcTTL m_TTLServiceDomain; + stcMDNS_RRDomain m_HostDomain; // 2. level answer (SRV, using service domain), eg. esp8266.local + char* m_pcHostDomain; + uint16_t m_u16Port; // 2. level answer (SRV, using service domain), eg. 5000 + stcTTL m_TTLHostDomainAndPort; + stcMDNSServiceTxts m_Txts; // 2. level answer (TXT, using service domain), eg. c#=1 + char* m_pcTxts; + stcTTL m_TTLTxts; +#ifdef MDNS_IP4_SUPPORT + stcIP4Address* m_pIP4Addresses; // 3. level answer (A, using host domain), eg. 123.456.789.012 +#endif +#ifdef MDNS_IP6_SUPPORT + stcIP6Address* m_pIP6Addresses; // 3. level answer (AAAA, using host domain), eg. 1234::09 +#endif + uint32_t m_u32ContentFlags; + + _stcAnswer(void); + ~_stcAnswer(void); + + bool clear(void); + + char* allocServiceDomain(size_t p_stLength); + bool releaseServiceDomain(void); + + char* allocHostDomain(size_t p_stLength); + bool releaseHostDomain(void); + + char* allocTxts(size_t p_stLength); + bool releaseTxts(void); + +#ifdef MDNS_IP4_SUPPORT + bool releaseIP4Addresses(void); + bool addIP4Address(_stcIP4Address* p_pIP4Address); + bool removeIP4Address(_stcIP4Address* p_pIP4Address); + const stcIP4Address* findIP4Address(const IPAddress& p_IPAddress) const; + stcIP4Address* findIP4Address(const IPAddress& p_IPAddress); + uint32_t IP4AddressCount(void) const; + const stcIP4Address* IP4AddressAtIndex(uint32_t p_u32Index) const; + stcIP4Address* IP4AddressAtIndex(uint32_t p_u32Index); +#endif +#ifdef MDNS_IP6_SUPPORT + bool releaseIP6Addresses(void); + bool addIP6Address(_stcIP6Address* p_pIP6Address); + bool removeIP6Address(_stcIP6Address* p_pIP6Address); + const stcIP6Address* findIP6Address(const IPAddress& p_IPAddress) const; + stcIP6Address* findIP6Address(const IPAddress& p_IPAddress); + uint32_t IP6AddressCount(void) const; + const stcIP6Address* IP6AddressAtIndex(uint32_t p_u32Index) const; + stcIP6Address* IP6AddressAtIndex(uint32_t p_u32Index); +#endif + } stcAnswer; + + _stcMDNSServiceQuery* m_pNext; + stcMDNS_RRDomain m_ServiceTypeDomain; // eg. _http._tcp.local + MDNSServiceQueryCallbackFn m_fnCallback; + void* m_pUserdata; + bool m_bLegacyQuery; + bool m_bAwaitingAnswers; + stcAnswer* m_pAnswers; + + _stcMDNSServiceQuery(void); + ~_stcMDNSServiceQuery(void); + + bool clear(void); + + uint32_t answerCount(void) const; + const stcAnswer* answerAtIndex(uint32_t p_u32Index) const; + stcAnswer* answerAtIndex(uint32_t p_u32Index); + uint32_t indexOfAnswer(const stcAnswer* p_pAnswer) const; + + bool addAnswer(stcAnswer* p_pAnswer); + bool removeAnswer(stcAnswer* p_pAnswer); + + stcAnswer* findAnswerForServiceDomain(const stcMDNS_RRDomain& p_ServiceDomain); + stcAnswer* findAnswerForHostDomain(const stcMDNS_RRDomain& p_HostDomain); + } stcMDNSServiceQuery; + + /** + * stcMDNSSendParameter + */ + typedef struct _stcMDNSSendParameter { + protected: + /** + * stcDomainCacheItem + */ + typedef struct _stcDomainCacheItem { + _stcDomainCacheItem* m_pNext; + 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 + + _stcDomainCacheItem(const void* p_pHostnameOrService, + bool p_bAdditionalData, + uint32_t p_u16Offset); + } stcDomainCacheItem; + + public: + uint16_t m_u16ID; // Query ID (used only in lagacy queries) + stcMDNS_RRQuestion* m_pQuestions; // A list of queries + uint8_t m_u8HostReplyMask; // Flags for reply components/answers + bool m_bLegacyQuery; // Flag: Legacy query + bool m_bResponse; // Flag: 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 + uint16_t m_u16Offset; // Current offset in UDP write buffer (mainly for domain cache) + stcDomainCacheItem* m_pDomainCacheItems; // Cached host and service domains + + _stcMDNSSendParameter(void); + ~_stcMDNSSendParameter(void); + + bool clear(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; + } stcMDNSSendParameter; + + // Instance variables + stcMDNSService* m_pServices; + UdpContext* m_pUDPContext; + char* m_pcHostname; + stcMDNSServiceQuery* m_pServiceQueries; + WiFiEventHandler m_DisconnectedHandler; + WiFiEventHandler m_GotIPHandler; + MDNSDynamicServiceTxtCallbackFn m_fnServiceTxtCallback; + void* m_pServiceTxtCallbackUserdata; + stcProbeInformation m_HostProbeInformation; + + /** CONTROL **/ + /* MAINTENANCE */ + bool _process(bool p_bUserContext); + bool _restart(void); + + /* RECEIVING */ + bool _parseMessage(void); + bool _parseQuery(const stcMDNS_MsgHeader& p_Header); + + bool _parseResponse(const stcMDNS_MsgHeader& p_Header); + bool _processAnswers(const stcMDNS_RRAnswer* p_pPTRAnswers); + bool _processPTRAnswer(const stcMDNS_RRAnswerPTR* p_pPTRAnswer, + bool& p_rbFoundNewKeyAnswer); + bool _processSRVAnswer(const stcMDNS_RRAnswerSRV* p_pSRVAnswer, + bool& p_rbFoundNewKeyAnswer); + bool _processTXTAnswer(const stcMDNS_RRAnswerTXT* p_pTXTAnswer); +#ifdef MDNS_IP4_SUPPORT + bool _processAAnswer(const stcMDNS_RRAnswerA* p_pAAnswer); +#endif +#ifdef MDNS_IP6_SUPPORT + bool _processAAAAAnswer(const stcMDNS_RRAnswerAAAA* p_pAAAAAnswer); +#endif + + /* PROBING */ + bool _updateProbeStatus(void); + bool _resetProbeStatus(bool p_bRestart = true); + bool _hasProbesWaitingForAnswers(void) const; + bool _sendHostProbe(void); + bool _sendServiceProbe(stcMDNSService& p_rService); + bool _cancelProbingForHost(void); + bool _cancelProbingForService(stcMDNSService& p_rService); + + /* ANNOUNCE */ + bool _announce(bool p_bAnnounce = true); + bool _announceService(stcMDNSService& p_rService, + bool p_bAnnounce = true); + + /* SERVICE QUERY CACHE */ + bool _hasServiceQueriesWaitingForAnswers(void) const; + bool _checkServiceQueryCache(void); + + /** TRANSFER **/ + /* SENDING */ + bool _sendMDNSMessage(stcMDNSSendParameter& p_SendParameter); + bool _sendMDNSMessage_Multicast(MDNSResponder::stcMDNSSendParameter& p_rSendParameter, + int p_iWiFiOpMode); + bool _prepareMDNSMessage(stcMDNSSendParameter& p_SendParameter, + IPAddress p_IPAddress); + bool _sendMDNSServiceQuery(const stcMDNSServiceQuery& p_ServiceQuery); + bool _sendMDNSQuery(const stcMDNS_RRDomain& p_QueryDomain, + uint16_t p_u16QueryType); + + IPAddress _getResponseMulticastInterface(int p_iWiFiOpModes) const; + + uint8_t _replyMaskForHost(const stcMDNS_RRHeader& p_RRHeader, + bool* p_pbFullNameMatch = 0) const; + uint8_t _replyMaskForService(const stcMDNS_RRHeader& p_RRHeader, + const stcMDNSService& p_Service, + bool* p_pbFullNameMatch = 0) const; + + /* RESOURCE RECORD */ + bool _readRRQuestion(stcMDNS_RRQuestion& p_rQuestion); + bool _readRRAnswer(stcMDNS_RRAnswer*& p_rpAnswer); +#ifdef MDNS_IP4_SUPPORT + bool _readRRAnswerA(stcMDNS_RRAnswerA& p_rRRAnswerA, + uint16_t p_u16RDLength); +#endif + bool _readRRAnswerPTR(stcMDNS_RRAnswerPTR& p_rRRAnswerPTR, + uint16_t p_u16RDLength); + bool _readRRAnswerTXT(stcMDNS_RRAnswerTXT& p_rRRAnswerTXT, + uint16_t p_u16RDLength); +#ifdef MDNS_IP6_SUPPORT + bool _readRRAnswerAAAA(stcMDNS_RRAnswerAAAA& p_rRRAnswerAAAA, + uint16_t p_u16RDLength); +#endif + bool _readRRAnswerSRV(stcMDNS_RRAnswerSRV& p_rRRAnswerSRV, + uint16_t p_u16RDLength); + bool _readRRAnswerGeneric(stcMDNS_RRAnswerGeneric& p_rRRAnswerGeneric, + uint16_t p_u16RDLength); + + bool _readRRHeader(stcMDNS_RRHeader& p_rHeader); + bool _readRRDomain(stcMDNS_RRDomain& p_rRRDomain); + bool _readRRDomain_Loop(stcMDNS_RRDomain& p_rRRDomain, + uint8_t p_u8Depth); + bool _readRRAttributes(stcMDNS_RRAttributes& p_rAttributes); + + /* DOMAIN NAMES */ + bool _buildDomainForHost(const char* p_pcHostname, + stcMDNS_RRDomain& p_rHostDomain) const; + bool _buildDomainForDNSSD(stcMDNS_RRDomain& p_rDNSSDDomain) const; + bool _buildDomainForService(const stcMDNSService& p_Service, + bool p_bIncludeName, + stcMDNS_RRDomain& p_rServiceDomain) const; + bool _buildDomainForService(const char* p_pcService, + const char* p_pcProtocol, + stcMDNS_RRDomain& p_rServiceDomain) const; +#ifdef MDNS_IP4_SUPPORT + bool _buildDomainForReverseIP4(IPAddress p_IP4Address, + stcMDNS_RRDomain& p_rReverseIP4Domain) const; +#endif +#ifdef MDNS_IP6_SUPPORT + bool _buildDomainForReverseIP6(IPAddress p_IP4Address, + stcMDNS_RRDomain& p_rReverseIP6Domain) 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(stcMDNS_MsgHeader& p_rMsgHeader); + + bool _write8(uint8_t p_u8Value, + stcMDNSSendParameter& p_rSendParameter); + bool _write16(uint16_t p_u16Value, + stcMDNSSendParameter& p_rSendParameter); + bool _write32(uint32_t p_u32Value, + stcMDNSSendParameter& p_rSendParameter); + + bool _writeMDNSMsgHeader(const stcMDNS_MsgHeader& p_MsgHeader, + stcMDNSSendParameter& p_rSendParameter); + bool _writeMDNSRRAttributes(const stcMDNS_RRAttributes& p_Attributes, + stcMDNSSendParameter& p_rSendParameter); + bool _writeMDNSRRDomain(const stcMDNS_RRDomain& p_Domain, + stcMDNSSendParameter& p_rSendParameter); + bool _writeMDNSHostDomain(const char* m_pcHostname, + bool p_bPrependRDLength, + stcMDNSSendParameter& p_rSendParameter); + bool _writeMDNSServiceDomain(const stcMDNSService& p_Service, + bool p_bIncludeName, + bool p_bPrependRDLength, + stcMDNSSendParameter& p_rSendParameter); + + bool _writeMDNSQuestion(stcMDNS_RRQuestion& p_Question, + stcMDNSSendParameter& p_rSendParameter); + +#ifdef MDNS_IP4_SUPPORT + bool _writeMDNSAnswer_A(IPAddress p_IPAddress, + stcMDNSSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_PTR_IP4(IPAddress p_IPAddress, + stcMDNSSendParameter& p_rSendParameter); +#endif + bool _writeMDNSAnswer_PTR_TYPE(stcMDNSService& p_rService, + stcMDNSSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_PTR_NAME(stcMDNSService& p_rService, + stcMDNSSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_TXT(stcMDNSService& p_rService, + stcMDNSSendParameter& p_rSendParameter); +#ifdef MDNS_IP6_SUPPORT + bool _writeMDNSAnswer_AAAA(IPAddress p_IPAddress, + stcMDNSSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_PTR_IP6(IPAddress p_IPAddress, + stcMDNSSendParameter& p_rSendParameter); +#endif + bool _writeMDNSAnswer_SRV(stcMDNSService& p_rService, + stcMDNSSendParameter& p_rSendParameter); + + /** HELPERS **/ + /* UDP CONTEXT */ + bool _callProcess(void); + bool _allocUDPContext(void); + bool _releaseUDPContext(void); + + /* SERVICE QUERY */ + stcMDNSServiceQuery* _allocServiceQuery(void); + bool _removeServiceQuery(stcMDNSServiceQuery* p_pServiceQuery); + bool _removeLegacyServiceQuery(void); + stcMDNSServiceQuery* _findServiceQuery(hMDNSServiceQuery p_hServiceQuery); + stcMDNSServiceQuery* _findLegacyServiceQuery(void); + bool _releaseServiceQueries(void); + stcMDNSServiceQuery* _findNextServiceQueryByServiceType(const stcMDNS_RRDomain& p_ServiceDomain, + const stcMDNSServiceQuery* p_pPrevServiceQuery); + + /* HOSTNAME */ + bool _setHostname(const char* p_pcHostname); + bool _releaseHostname(void); + + /* SERVICE */ + stcMDNSService* _allocService(const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol, + uint16_t p_u16Port); + bool _releaseService(stcMDNSService* p_pService); + bool _releaseServices(void); + + stcMDNSService* _findService(const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol); + stcMDNSService* _findService(const hMDNSService p_hService); + + size_t _countServices(void) const; + + /* SERVICE TXT */ + stcMDNSServiceTxt* _allocServiceTxt(stcMDNSService* p_pService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp); + bool _releaseServiceTxt(stcMDNSService* p_pService, + stcMDNSServiceTxt* p_pTxt); + stcMDNSServiceTxt* _updateServiceTxt(stcMDNSService* p_pService, + stcMDNSServiceTxt* p_pTxt, + const char* p_pcValue, + bool p_bTemp); + + stcMDNSServiceTxt* _findServiceTxt(stcMDNSService* p_pService, + const char* p_pcKey); + stcMDNSServiceTxt* _findServiceTxt(stcMDNSService* p_pService, + const hMDNSTxt p_hTxt); + + stcMDNSServiceTxt* _addServiceTxt(stcMDNSService* p_pService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp); + + bool _collectServiceTxts(stcMDNSService& p_rService); + bool _releaseTempServiceTxts(stcMDNSService& p_rService); + const stcMDNSServiceTxt* _serviceTxts(const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol); + + /* MISC */ +#if not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_MDNS_RESPONDER + bool _printRRDomain(const stcMDNS_RRDomain& p_rRRDomain) const; + bool _printRRAnswer(const MDNSResponder::stcMDNS_RRAnswer& p_RRAnswer) const; +#endif +}; + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) + extern MDNSResponder MDNS; +#endif + +} // namespace LEAmDNS + +#endif // LEAMDNS_H + + + + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp new file mode 100755 index 0000000000..9a3f752cda --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp @@ -0,0 +1,1763 @@ +/* + * LEAmDNS_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 +#include +#include +#include +#include +#include + +/* + * ESP8266mDNS Control.cpp + */ + +extern "C" { + #include "user_interface.h" +} + +#include "lwip/prot/dns.h" // DNS_RRTYPE_xxx, DNS_MQUERY_PORT + +#include "LEAmDNS_Priv.h" + + +/* + * namespace LEAmDNS + */ +namespace LEAmDNS { + +/** + * CONTROL + */ + + +/** + * MAINTENANCE + */ + +/* + * MDNSResponder::_process + * + * Run the MDNS process. Should be called in every 'loop'. + * + */ +bool MDNSResponder::_process(bool p_bUserContext) { + + bool bResult = true; + + if ((m_pUDPContext) && // UDPContext available AND + (m_pUDPContext->next())) { // has content + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _update: Calling _parseMessage\n"));); + + bResult = _parseMessage(); + if (p_bUserContext) { + esp_yield(); + } + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parsePacket %s\n"), (bResult ? "succeeded" : "FAILED"));); + } + else if (p_bUserContext) { + if (bResult) { // Probing + bResult = _updateProbeStatus(); + } + if (bResult) { // Service query cache check + bResult = _checkServiceQueryCache(); + } + } + return bResult; +} + +/* + * MDNSResponder::_restart + */ +bool MDNSResponder::_restart(void) { + + return ((_resetProbeStatus(true)) && // Stop and restart probing + (_allocUDPContext())); // Restart UDP +} + + +/** + * RECEIVING + */ + +/* + * MDNSResponder::_parseMessage + */ +bool MDNSResponder::_parseMessage(void) { + DEBUG_EX_INFO( + unsigned long ulStartTime = millis(); + unsigned uStartMemory = ESP.getFreeHeap(); + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseMessage (Time: %lu ms, heap: %u bytes, from %s(%u), to %s(%u))\n"), ulStartTime, uStartMemory, + IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str(), m_pUDPContext->getRemotePort(), + IPAddress(m_pUDPContext->getDestAddress()).toString().c_str(), m_pUDPContext->getLocalPort()); + ); + //DEBUG_EX_INFO(_udpDump();); + + bool bResult = false; + + stcMDNS_MsgHeader header; + if (_readMDNSMsgHeader(header)) { + 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_LEA("[MDNSResponder] _parseMessage: Reading answers: ID:%u, Q:%u, A:%u, NS:%u, AR:%u\n"), header.m_u16ID, header.m_u16QDCount, header.m_u16ANCount, header.m_u16NSCount, header.m_u16ARCount);); + bResult = _parseResponse(header); + } + else { // Received a query (Questions) + //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseMessage: Reading query: ID:%u, Q:%u, A:%u, NS:%u, AR:%u\n"), header.m_u16ID, header.m_u16QDCount, header.m_u16ANCount, header.m_u16NSCount, header.m_u16ARCount);); + bResult = _parseQuery(header); + } + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseMessage: Received UNEXPECTED opcode:%u. Ignoring message!\n"), header.m_4bOpcode);); + m_pUDPContext->flush(); + } + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseMessage: FAILED to read header\n"));); + m_pUDPContext->flush(); + } + DEBUG_EX_INFO( + unsigned uFreeHeap = ESP.getFreeHeap(); + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseMessage: Done (%s after %lu ms, ate %i bytes, remaining %u)\n\n"), (bResult ? "Succeeded" : "FAILED"), (millis() - ulStartTime), (uStartMemory - uFreeHeap), uFreeHeap); + ); + return bResult; +} + +/* + * MDNSResponder::_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). + * + * 1. + */ +bool MDNSResponder::_parseQuery(const MDNSResponder::stcMDNS_MsgHeader& p_MsgHeader) { + + bool bResult = true; + + stcMDNSSendParameter sendParameter; + uint8_t u8HostOrServiceReplies = 0; + for (uint16_t qd=0; ((bResult) && (qdm_pNext) { + // Define service replies, BUT only answer queries after probing is done + uint8_t u8ReplyMaskForQuestion = ((ProbingStatus_Done == pService->m_ProbeInformation.m_ProbingStatus) + ? _replyMaskForService(questionRR.m_Header, *pService, 0) + : 0); + u8HostOrServiceReplies |= (pService->m_u8ReplyMask |= u8ReplyMaskForQuestion); + DEBUG_EX_INFO(if (u8ReplyMaskForQuestion) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Service reply needed for (%s.%s.%s): %u (%s)\n"), (pService->m_pcName ?: m_pcHostname), pService->m_pcService, pService->m_pcProtocol, u8ReplyMaskForQuestion, IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str()); } ); + /*if ((u8ReplyMaskForQuestion) && + (0 == os_strcmp("hap", pService->m_pcService))) { + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Service reply needed for (%s.%s.%s): %u (%s)\n"), (pService->m_pcName ?: m_pcHostname), pService->m_pcService, pService->m_pcProtocol, u8ReplyMaskForQuestion, IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str()); + }*/ + + // Check tiebreak need for service domain + if (ProbingStatus_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_LEA("[MDNSResponder] _parseQuery: Possible race-condition for service domain %s.%s.%s detected while probing.\n"), (pService->m_pcName ?: m_pcHostname), pService->m_pcService, pService->m_pcProtocol);); + Serial.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Possible race-condition for service domain %s.%s.%s detected while probing.\n"), (pService->m_pcName ?: m_pcHostname), pService->m_pcService, pService->m_pcProtocol); + + 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_LEA("[MDNSResponder] _parseQuery: Unicast response for %s!\n"), 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_u8HostReplyMask) || // Host replies OR + (u8HostOrServiceReplies))) { // Host or service replies available + // We're a match for this legacy query, BUT + // make sure, that the query comes from a local host + ip_info IPInfo_Local; + ip_info IPInfo_Remote; + if (((IPInfo_Remote.ip.addr = m_pUDPContext->getRemoteAddress())) && + (((wifi_get_ip_info(SOFTAP_IF, &IPInfo_Local)) && + (ip_addr_netcmp(&IPInfo_Remote.ip, &IPInfo_Local.ip, &IPInfo_Local.netmask))) || // Remote IP in SOFTAP's subnet OR + ((wifi_get_ip_info(STATION_IF, &IPInfo_Local)) && + (ip_addr_netcmp(&IPInfo_Remote.ip, &IPInfo_Local.ip, &IPInfo_Local.netmask))))) { // Remote IP in STATION's subnet + + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Legacy query from local host %s!\n"), IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str());); + + sendParameter.m_u16ID = p_MsgHeader.m_u16ID; + sendParameter.m_bLegacyQuery = true; + sendParameter.m_pQuestions = new stcMDNS_RRQuestion; + if ((bResult = (0 != sendParameter.m_pQuestions))) { + sendParameter.m_pQuestions->m_Header.m_Domain = questionRR.m_Header.m_Domain; + sendParameter.m_pQuestions->m_Header.m_Attributes.m_u16Type = questionRR.m_Header.m_Attributes.m_u16Type; + sendParameter.m_pQuestions->m_Header.m_Attributes.m_u16Class = questionRR.m_Header.m_Attributes.m_u16Class; + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: FAILED to add legacy question!\n"));); + } + } + else { + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Legacy query from NON-LOCAL host!\n"));); + bResult = false; + } + } + } + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: FAILED to read question!\n"));); + } + //*esp_yield(); + } // for questions + + //DEBUG_EX_INFO(if (u8HostOrServiceReplies) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Reply needed: %u (%s: %s->%s)\n"), u8HostOrServiceReplies, clsTimeSyncer::timestr(), IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str(), IPAddress(m_pUDPContext->getDestAddress()).toString().c_str()); } ); + + // Handle known answers + uint32_t u32Answers = (p_MsgHeader.m_u16ANCount + p_MsgHeader.m_u16NSCount + p_MsgHeader.m_u16ARCount); + DEBUG_EX_INFO(if ((u8HostOrServiceReplies) && (u32Answers)) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Known answers(%u):\n"), u32Answers); } ); + + for (uint32_t an=0; ((bResult) && (anm_Header.m_Attributes.m_u16Type) && // No ANY type answer + (DNS_RRCLASS_ANY != pKnownRRAnswer->m_Header.m_Attributes.m_u16Class)) { // No ANY class answer + + // Find match between planned answer (sendParameter.m_u8HostReplyMask) and this 'known answer' + uint8_t u8HostMatchMask = (sendParameter.m_u8HostReplyMask & _replyMaskForHost(pKnownRRAnswer->m_Header)); + if ((u8HostMatchMask) && // The RR in the known answer matches an RR we are planning to send, AND + ((MDNS_HOST_TTL / 2) <= pKnownRRAnswer->m_u32TTL)) { // The TTL of the known answer is longer than half of the new host TTL (120s) + + // Compare contents + if (AnswerType_PTR == pKnownRRAnswer->answerType()) { + stcMDNS_RRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostname, hostDomain)) && + (((stcMDNS_RRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain == hostDomain)) { + // Host domain match +#ifdef MDNS_IP4_SUPPORT + if (u8HostMatchMask & ContentFlag_PTR_IP4) { + // IP4 PTR was asked for, but is already known -> skipping + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: IP4 PTR already known... skipping!\n"));); + sendParameter.m_u8HostReplyMask &= ~ContentFlag_PTR_IP4; + } +#endif +#ifdef MDNS_IP6_SUPPORT + if (u8HostMatchMask & ContentFlag_PTR_IP6) { + // IP6 PTR was asked for, but is already known -> skipping + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: IP6 PTR already known... skipping!\n"));); + sendParameter.m_u8HostReplyMask &= ~ContentFlag_PTR_IP6; + } +#endif + } + } + else if (u8HostMatchMask & ContentFlag_A) { + // IP4 address was asked for +#ifdef MDNS_IP4_SUPPORT + if ((AnswerType_A == pKnownRRAnswer->answerType()) && + (((stcMDNS_RRAnswerA*)pKnownRRAnswer)->m_IPAddress == _getResponseMulticastInterface(SOFTAP_MODE | STATION_MODE))) { + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: IP4 address already known... skipping!\n"));); + sendParameter.m_u8HostReplyMask &= ~ContentFlag_A; + } // else: RData NOT IP4 length !! +#endif + } + else if (u8HostMatchMask & ContentFlag_AAAA) { + // IP6 address was asked for +#ifdef MDNS_IP6_SUPPORT + if ((AnswerType_AAAA == pAnswerRR->answerType()) && + (((stcMDNS_RRAnswerAAAA*)pAnswerRR)->m_IPAddress == _getResponseMulticastInterface(SOFTAP_MODE | STATION_MODE))) { + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: IP6 address already known... skipping!\n"));); + sendParameter.m_u8HostReplyMask &= ~ContentFlag_AAAA; + } // else: RData NOT IP6 length !! +#endif + } + } // Host match /*and TTL*/ + + // + // Check host tiebreak possibility + if (m_HostProbeInformation.m_bTiebreakNeeded) { + stcMDNS_RRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostname, hostDomain)) && + (pKnownRRAnswer->m_Header.m_Domain == hostDomain)) { + // Host domain match +#ifdef MDNS_IP4_SUPPORT + if (AnswerType_A == pKnownRRAnswer->answerType()) { + IPAddress localIPAddress(_getResponseMulticastInterface(SOFTAP_MODE | STATION_MODE)); + if (((stcMDNS_RRAnswerA*)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_LEA("[MDNSResponder] _parseQuery: Tiebreak (IP4) WON (was an old message)!\n"));); + m_HostProbeInformation.m_bTiebreakNeeded = false; + } + else { + if (((stcMDNS_RRAnswerA*)pKnownRRAnswer)->m_IPAddress > localIPAddress) { // The OTHER IP is 'higher' -> LOST + // LOST tiebreak + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Tiebreak (IP4) LOST (lower)!\n"));); + _cancelProbingForHost(); + m_HostProbeInformation.m_bTiebreakNeeded = false; + } + else { // WON tiebreak + //TiebreakState = TiebreakState_Won; // We received an 'old' message from ourselfs -> Just ignore + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Tiebreak (IP4) WON (higher IP)!\n"));); + m_HostProbeInformation.m_bTiebreakNeeded = false; + } + } + } +#endif +#ifdef MDNS_IP6_SUPPORT + if (AnswerType_AAAA == pAnswerRR->answerType()) { + // TODO + } +#endif + } + } // Host tiebreak possibility + + // Check service answers + for (stcMDNSService* pService=m_pServices; pService; pService=pService->m_pNext) { + + uint8_t u8ServiceMatchMask = (pService->m_u8ReplyMask & _replyMaskForService(pKnownRRAnswer->m_Header, *pService)); + + if ((u8ServiceMatchMask) && // The RR in the known answer matches an RR we are planning to send, AND + ((MDNS_SERVICE_TTL / 2) <= pKnownRRAnswer->m_u32TTL)) { // The TTL of the known answer is longer than half of the new service TTL (4500s) + + /*if ((0 == os_strcmp("hap", pService->m_pcService))) { + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Known answer for (%s.%s.%s): %u (%s) %u\n"), (pService->m_pcName ?: m_pcHostname), pService->m_pcService, pService->m_pcProtocol, pKnownRRAnswer->answerType(), IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str(), pKnownRRAnswer->m_u32TTL); + }*/ + + if (AnswerType_PTR == pKnownRRAnswer->answerType()) { + stcMDNS_RRDomain serviceDomain; + if ((u8ServiceMatchMask & ContentFlag_PTR_TYPE) && + (_buildDomainForService(*pService, false, serviceDomain)) && + (serviceDomain == ((stcMDNS_RRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain)) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Service type PTR already known... skipping!\n"));); + pService->m_u8ReplyMask &= ~ContentFlag_PTR_TYPE; + } + if ((u8ServiceMatchMask & ContentFlag_PTR_NAME) && + (_buildDomainForService(*pService, true, serviceDomain)) && + (serviceDomain == ((stcMDNS_RRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain)) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Service name PTR already known... skipping!\n"));); + pService->m_u8ReplyMask &= ~ContentFlag_PTR_NAME; + } + } + else if (u8ServiceMatchMask & ContentFlag_SRV) { + DEBUG_EX_ERR(if (AnswerType_SRV != pKnownRRAnswer->answerType()) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: ERROR! INVALID answer type (SRV)!\n"));); + stcMDNS_RRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostname, hostDomain)) && + (hostDomain == ((stcMDNS_RRAnswerSRV*)pKnownRRAnswer)->m_SRVDomain)) { // Host domain match + + if ((MDNS_SRV_PRIORITY == ((stcMDNS_RRAnswerSRV*)pKnownRRAnswer)->m_u16Priority) && + (MDNS_SRV_WEIGHT == ((stcMDNS_RRAnswerSRV*)pKnownRRAnswer)->m_u16Weight) && + (pService->m_u16Port == ((stcMDNS_RRAnswerSRV*)pKnownRRAnswer)->m_u16Port)) { + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Service SRV answer already known... skipping!\n"));); + pService->m_u8ReplyMask &= ~ContentFlag_SRV; + } // else: Small differences -> send update message + } + } + else if (u8ServiceMatchMask & ContentFlag_TXT) { + DEBUG_EX_ERR(if (AnswerType_TXT != pKnownRRAnswer->answerType()) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: ERROR! INVALID answer type (TXT)!\n"));); + _collectServiceTxts(*pService); + if (pService->m_Txts == ((stcMDNS_RRAnswerTXT*)pKnownRRAnswer)->m_Txts) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Service TXT answer already known... skipping!\n"));); + pService->m_u8ReplyMask &= ~ContentFlag_TXT; + } + _releaseTempServiceTxts(*pService); + } + } // Service match and enough TTL + + // + // Check service tiebreak possibility + if (pService->m_ProbeInformation.m_bTiebreakNeeded) { + stcMDNS_RRDomain serviceDomain; + if ((_buildDomainForService(*pService, true, serviceDomain)) && + (pKnownRRAnswer->m_Header.m_Domain == serviceDomain)) { + // Service domain match + if (AnswerType_SRV == pKnownRRAnswer->answerType()) { + stcMDNS_RRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostname, hostDomain)) && + (hostDomain == ((stcMDNS_RRAnswerSRV*)pKnownRRAnswer)->m_SRVDomain)) { // Host domain match + + // We've received an old message from ourselfs (same SRV) + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Tiebreak (SRV) won (was an old message)!\n"));); + pService->m_ProbeInformation.m_bTiebreakNeeded = false; + } + else { + if (((stcMDNS_RRAnswerSRV*)pKnownRRAnswer)->m_SRVDomain > hostDomain) { // The OTHER domain is 'higher' -> LOST + // LOST tiebreak + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Tiebreak (SRV) LOST (lower)!\n"));); + _cancelProbingForService(*pService); + pService->m_ProbeInformation.m_bTiebreakNeeded = false; + } + else { // WON tiebreak + //TiebreakState = TiebreakState_Won; // We received an 'old' message from ourselfs -> Just ignore + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Tiebreak (SRV) won (higher)!\n"));); + pService->m_ProbeInformation.m_bTiebreakNeeded = false; + } + } + } + } + } // service tiebreak possibility + } // for services + } // ANY answers + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: FAILED to read known answer!\n"));); + } + + if (pKnownRRAnswer) { + delete pKnownRRAnswer; + pKnownRRAnswer = 0; + } + //*esp_yield(); + } // for answers + + if (bResult) { + // Check, if a reply is needed + uint8_t u8ReplyNeeded = sendParameter.m_u8HostReplyMask; + for (stcMDNSService* pService=m_pServices; pService; pService=pService->m_pNext) { + u8ReplyNeeded |= pService->m_u8ReplyMask; + + if ((u8ReplyNeeded) && + (0 == os_strcmp("hap", pService->m_pcService))) { + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Sending service reply for (%s.%s.%s): %u (%s)\n"), (pService->m_pcName ?: m_pcHostname), pService->m_pcService, pService->m_pcProtocol, u8ReplyNeeded, IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str()); + } + } + + if (u8ReplyNeeded) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Sending answer(%u)...\n"), u8ReplyNeeded);); + sendParameter.m_bResponse = true; + sendParameter.m_bAuthorative = true; + bResult = _sendMDNSMessage(sendParameter); + } + else { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: No reply needed\n"));); + } + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: Something FAILED!\n"));); + m_pUDPContext->flush(); + } + + // + // Check and reset tiebreak-states + if (m_HostProbeInformation.m_bTiebreakNeeded) { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: UNSOLVED tiebreak-need for host domain!\n"));); + m_HostProbeInformation.m_bTiebreakNeeded = false; + } + for (stcMDNSService* pService=m_pServices; pService; pService=pService->m_pNext) { + if (pService->m_ProbeInformation.m_bTiebreakNeeded) { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: UNSOLVED tiebreak-need for service domain (%s.%s.%s)\n"), (pService->m_pcName ?: m_pcHostname), pService->m_pcService, pService->m_pcProtocol);); + pService->m_ProbeInformation.m_bTiebreakNeeded = false; + } + } + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseQuery: FAILED!\n")); }); + return bResult; +} + +/* + * MDNSResponder::_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 MDNSResponder::_parseResponse(const MDNSResponder::stcMDNS_MsgHeader& p_MsgHeader) { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseResponse\n"));); + //DEBUG_EX_INFO(_udpDump();); + + bool bResult = false; + + // A response should be the result of a query or a probe + if ((_hasServiceQueriesWaitingForAnswers()) || // Waiting for query answers OR + (_hasProbesWaitingForAnswers())) { // Probe responses + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseResponse: Received a response\n")); + //_udpDump(); + ); + + bResult = true; + // + // Ignore questions here + stcMDNS_RRQuestion dummyRRQ; + for (uint16_t qd=0; ((bResult) && (qdm_pNext = pCollectedRRAnswers; + pCollectedRRAnswers = pRRAnswer; + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseResponse: FAILED to read answer!\n"));); + if (pRRAnswer) { + delete pRRAnswer; + pRRAnswer = 0; + } + bResult = false; + } + //*esp_yield(); + } // for answers + + // + // Process answers + if (bResult) { + bResult = ((!pCollectedRRAnswers) || + (_processAnswers(pCollectedRRAnswers))); + } + else { // Some failure while reading answers + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseResponse: FAILED to read answers!\n"));); + m_pUDPContext->flush(); + } + + // Delete collected answers + while (pCollectedRRAnswers) { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseResponse: DELETING answer!\n"));); + stcMDNS_RRAnswer* pNextAnswer = pCollectedRRAnswers->m_pNext; + delete pCollectedRRAnswers; + pCollectedRRAnswers = pNextAnswer; + } + } + else { // Received an unexpected response -> ignore + /*DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseResponse: Received an unexpected response... ignoring!\nDUMP:\n")); + bool bDumpResult = true; + for (uint16_t qd=0; ((bDumpResult) && (qdflush(); + bResult = true; + } + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _parseResponse: FAILED!\n")); }); + return bResult; +} + +/* + * MDNSResponder::_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, IP4): eg. 012.789.456.123.in-addr.arpa PTR OP TTL esp8266.local + * PTR (0x0C, IP6): 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 MDNSResponder::_processAnswers(const MDNSResponder::stcMDNS_RRAnswer* p_pAnswers) { + + bool bResult = false; + + if (p_pAnswers) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processAnswers: Processing answers...\n"));); + 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 stcMDNS_RRAnswer* pRRAnswer = p_pAnswers; + while ((pRRAnswer) && + (bResult)) { + // 1. level answer (PTR) + if (AnswerType_PTR == pRRAnswer->answerType()) { + // eg. _http._tcp.local PTR xxxx xx MyESP._http._tcp.local + bResult = _processPTRAnswer((stcMDNS_RRAnswerPTR*)pRRAnswer, bFoundNewKeyAnswer); // May 'enable' new SRV or TXT answers to be linked to queries + } + // 2. level answers + // SRV -> host domain and port + else if (AnswerType_SRV == pRRAnswer->answerType()) { + // eg. MyESP_http._tcp.local SRV xxxx xx yy zz 5000 esp8266.local + bResult = _processSRVAnswer((stcMDNS_RRAnswerSRV*)pRRAnswer, bFoundNewKeyAnswer); // May 'enable' new A/AAAA answers to be linked to queries + } + // TXT -> Txts + else if (AnswerType_TXT == pRRAnswer->answerType()) { + // eg. MyESP_http._tcp.local TXT xxxx xx c#=1 + bResult = _processTXTAnswer((stcMDNS_RRAnswerTXT*)pRRAnswer); + } + // 3. level answers +#ifdef MDNS_IP4_SUPPORT + // A -> IP4Address + else if (AnswerType_A == pRRAnswer->answerType()) { + // eg. esp8266.local A xxxx xx 192.168.2.120 + bResult = _processAAnswer((stcMDNS_RRAnswerA*)pRRAnswer); + } +#endif +#ifdef MDNS_IP6_SUPPORT + // AAAA -> IP6Address + else if (AnswerType_AAAA == pRRAnswer->answerType()) { + // eg. esp8266.local AAAA xxxx xx 09cf::0c + bResult = _processAAAAAnswer((stcMDNS_RRAnswerAAAA*)pRRAnswer); + } +#endif + + // Finally check for probing conflicts + // Host domain + if ((ProbingStatus_InProgress == m_HostProbeInformation.m_ProbingStatus) && + ((AnswerType_A == pRRAnswer->answerType()) || + (AnswerType_AAAA == pRRAnswer->answerType()))) { + + stcMDNS_RRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostname, hostDomain)) && + (pRRAnswer->m_Header.m_Domain == hostDomain)) { + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processAnswers: Probing CONFLICT found with: %s.local\n"), m_pcHostname);); + _cancelProbingForHost(); + } + } + // Service domains + for (stcMDNSService* pService=m_pServices; pService; pService=pService->m_pNext) { + if ((ProbingStatus_InProgress == pService->m_ProbeInformation.m_ProbingStatus) && + ((AnswerType_TXT == pRRAnswer->answerType()) || + (AnswerType_SRV == pRRAnswer->answerType()))) { + + stcMDNS_RRDomain serviceDomain; + if ((_buildDomainForService(*pService, true, serviceDomain)) && + (pRRAnswer->m_Header.m_Domain == serviceDomain)) { + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processAnswers: Probing CONFLICT found with: %s.%s.%s\n"), (pService->m_pcName ?: m_pcHostname), pService->m_pcService, pService->m_pcProtocol);); + _cancelProbingForService(*pService); + } + } + } + + pRRAnswer = pRRAnswer->m_pNext; // Next collected answer + } // while (answers) + //*esp_yield(); + } while ((bFoundNewKeyAnswer) && + (bResult)); + } // else: No answers provided + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processAnswers: FAILED!\n")); }); + return bResult; +} + +/* + * MDNSResponder::_processPTRAnswer + */ +bool MDNSResponder::_processPTRAnswer(const MDNSResponder::stcMDNS_RRAnswerPTR* p_pPTRAnswer, + bool& p_rbFoundNewKeyAnswer) { + + bool bResult = false; + + if ((bResult = (0 != p_pPTRAnswer))) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processPTRAnswer: Processing PTR answers...\n"));); + // eg. _http._tcp.local PTR xxxx xx MyESP._http._tcp.local + // Check pending service queries for '_http._tcp' + + stcMDNSServiceQuery* pServiceQuery = _findNextServiceQueryByServiceType(p_pPTRAnswer->m_Header.m_Domain, 0); + while (pServiceQuery) { + if (pServiceQuery->m_bAwaitingAnswers) { + // Find answer for service domain (eg. MyESP._http._tcp.local) + stcMDNSServiceQuery::stcAnswer* pSQAnswer = pServiceQuery->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/*, millis()*/); // Update TTL tag + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processPTRAnswer: Updated TTL for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR_LEA("\n")); + ); + } + else { // received goodbye-message + pSQAnswer->m_TTLServiceDomain.set(1/*, millis()*/); // See RFC 6762, 10.1 + pSQAnswer->m_TTLServiceDomain.m_bUpdateScheduled = true; // Avoid 'cache update' query + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processPTRAnswer: 'Goodbye' received for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR_LEA("\n")); + ); + } + } + else if ((p_pPTRAnswer->m_u32TTL) && // Not just a goodbye-message + ((pSQAnswer = new stcMDNSServiceQuery::stcAnswer))) { // Not yet included -> add answer + pSQAnswer->m_ServiceDomain = p_pPTRAnswer->m_PTRDomain; + pSQAnswer->m_u32ContentFlags |= ServiceQueryAnswerType_ServiceDomain; + pSQAnswer->m_TTLServiceDomain.set(p_pPTRAnswer->m_u32TTL/*, millis()*/); + pSQAnswer->releaseServiceDomain(); + + bResult = pServiceQuery->addAnswer(pSQAnswer); + + p_rbFoundNewKeyAnswer = true; + if (pServiceQuery->m_fnCallback) { + pServiceQuery->m_fnCallback(this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer), ServiceQueryAnswerType_ServiceDomain, true, pServiceQuery->m_pUserdata); + } + } + } + pServiceQuery = _findNextServiceQueryByServiceType(p_pPTRAnswer->m_Header.m_Domain, pServiceQuery); + } + } // else: No p_pPTRAnswer + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processPTRAnswer: FAILED!\n")); }); + return bResult; +} + +/* + * MDNSResponder::_processSRVAnswer + */ +bool MDNSResponder::_processSRVAnswer(const MDNSResponder::stcMDNS_RRAnswerSRV* 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 + + stcMDNSServiceQuery* pServiceQuery = m_pServiceQueries; + while (pServiceQuery) { + stcMDNSServiceQuery::stcAnswer* pSQAnswer = pServiceQuery->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/*, millis()*/); // Update TTL tag + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processSRVAnswer: Updated TTL for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR_LEA(" 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_u32ContentFlags |= ServiceQueryAnswerType_HostDomainAndPort; + + p_rbFoundNewKeyAnswer = true; + if (pServiceQuery->m_fnCallback) { + pServiceQuery->m_fnCallback(this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer), ServiceQueryAnswerType_HostDomainAndPort, true, pServiceQuery->m_pUserdata); + } + } + } + else { // Goodby message + pSQAnswer->m_TTLHostDomainAndPort.set(1/*, millis()*/); // See RFC 6762, 10.1 + pSQAnswer->m_TTLHostDomainAndPort.m_bUpdateScheduled = true; // Avoid 'cache update' query + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processSRVAnswer: 'Goodbye' received for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR_LEA(" host domain and port\n")); + ); + } + } + pServiceQuery = pServiceQuery->m_pNext; + } // while(service query) + } // else: No p_pSRVAnswer + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processSRVAnswer: FAILED!\n")); }); + return bResult; +} + +/* + * MDNSResponder::_processTXTAnswer + */ +bool MDNSResponder::_processTXTAnswer(const MDNSResponder::stcMDNS_RRAnswerTXT* p_pTXTAnswer) { + + bool bResult = false; + + if ((bResult = (0 != p_pTXTAnswer))) { + // eg. MyESP._http._tcp.local TXT xxxx xx c#=1 + + stcMDNSServiceQuery* pServiceQuery = m_pServiceQueries; + while (pServiceQuery) { + stcMDNSServiceQuery::stcAnswer* pSQAnswer = pServiceQuery->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/*, millis()*/); // Update TTL tag + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processTXTAnswer: Updated TTL for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR_LEA(" TXTs\n")); + ); + if (!pSQAnswer->m_Txts.compare(p_pTXTAnswer->m_Txts)) { + pSQAnswer->m_Txts = p_pTXTAnswer->m_Txts; + pSQAnswer->m_u32ContentFlags |= ServiceQueryAnswerType_Txts; + pSQAnswer->releaseTxts(); + + if (pServiceQuery->m_fnCallback) { + pServiceQuery->m_fnCallback(this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer), ServiceQueryAnswerType_Txts, true, pServiceQuery->m_pUserdata); + } + } + } + else { // Goodby message + pSQAnswer->m_TTLTxts.set(1/*, millis()*/); // See RFC 6762, 10.1 + pSQAnswer->m_TTLTxts.m_bUpdateScheduled = true; // Avoid 'cache update' query + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processTXTAnswer: 'Goodbye' received for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR_LEA(" TXTs\n")); + ); + } + } + pServiceQuery = pServiceQuery->m_pNext; + } // while(service query) + } // else: No p_pTXTAnswer + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processTXTAnswer: FAILED!\n")); }); + return bResult; +} + +#ifdef MDNS_IP4_SUPPORT + /* + * MDNSResponder::_processAAnswer + */ + bool MDNSResponder::_processAAnswer(const MDNSResponder::stcMDNS_RRAnswerA* p_pAAnswer) { + + bool bResult = false; + + if ((bResult = (0 != p_pAAnswer))) { + // eg. esp8266.local A xxxx xx 192.168.2.120 + + stcMDNSServiceQuery* pServiceQuery = m_pServiceQueries; + while (pServiceQuery) { + stcMDNSServiceQuery::stcAnswer* pSQAnswer = pServiceQuery->findAnswerForHostDomain(p_pAAnswer->m_Header.m_Domain); + if (pSQAnswer) { // Answer for this host domain (eg. esp8266.local) available + stcMDNSServiceQuery::stcAnswer::stcIP4Address* pIP4Address = pSQAnswer->findIP4Address(p_pAAnswer->m_IPAddress); + if (pIP4Address) { + // Already known IP4 address + if (p_pAAnswer->m_u32TTL) { // Valid TTL -> Update answers TTL + pIP4Address->m_TTL.set(p_pAAnswer->m_u32TTL/*, millis()*/); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processAAnswer: Updated TTL for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR_LEA(" IP4Address (%s)\n"), pIP4Address->m_IPAddress.toString().c_str()); + ); + } + else { // 'Goodbye' message for known IP4 address + pIP4Address->m_TTL.set(1/*, millis()*/); // See RFC 6762, 10.1 + pIP4Address->m_TTL.m_bUpdateScheduled = true; // Avoid 'cache update' query + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processAAnswer: 'Goodbye' received for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR_LEA(" IP4 address (%s)\n"), pIP4Address->m_IPAddress.toString().c_str()); + ); + } + } + else { + // Until now unknown IP4 address -> Add (if the message isn't just a 'Goodbye' note) + if (p_pAAnswer->m_u32TTL) { // NOT just a 'Goodbye' message + pIP4Address = new stcMDNSServiceQuery::stcAnswer::stcIP4Address(p_pAAnswer->m_IPAddress, p_pAAnswer->m_u32TTL/*, millis()*/); + if ((pIP4Address) && + (pSQAnswer->addIP4Address(pIP4Address))) { + + pSQAnswer->m_u32ContentFlags |= ServiceQueryAnswerType_IP4Address; + + if (pServiceQuery->m_fnCallback) { + pServiceQuery->m_fnCallback(this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer), ServiceQueryAnswerType_IP4Address, true, pServiceQuery->m_pUserdata); + } + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processAAnswer: FAILED to add IP4 address (%s)!\n"), p_pAAnswer->m_IPAddress.toString().c_str());); + } + } + } + } + pServiceQuery = pServiceQuery->m_pNext; + } // while(service query) + } // else: No p_pAAnswer + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processAAnswer: FAILED!\n")); }); + return bResult; + } +#endif + +#ifdef MDNS_IP6_SUPPORT + /* + * MDNSResponder::_processAAAAAnswer + */ + bool MDNSResponder::_processAAAAAnswer(const MDNSResponder::stcMDNS_RRAnswerAAAA* p_pAAAAAnswer) { + + bool bResult = false; + + if ((bResult = (0 != p_pAAAAAnswer))) { + // eg. esp8266.local AAAA xxxx xx 0bf3::0c + + stcMDNSServiceQuery* pServiceQuery = m_pServiceQueries; + while (pServiceQuery) { + stcMDNSServiceQuery::stcAnswer* pSQAnswer = pServiceQuery->findAnswerForHostDomain(p_pAAAAAnswer->m_Header.m_Domain); + if (pSQAnswer) { // Answer for this host domain (eg. esp8266.local) available + stcIP6Address* pIP6Address = pSQAnswer->findIP6Address(p_pAAAAAnswer->m_IPAddress); + if (pIP6Address) { + // Already known IP6 address + if (p_pAAAAAnswer->m_u32TTL) { // Valid TTL -> Update answers TTL + pIP6Address->m_TTL.set(p_pAAAAAnswer->m_u32TTL/*, millis()*/); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processAAnswer: Updated TTL for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR_LEA(" IP6 address (%s)\n"), pIP6Address->m_IPAddress.toString().c_str()); + ); + } + else { // 'Goodbye' message for known IP6 address + pIP6Address->m_TTL.set(1/*, millis()*/); // See RFC 6762, 10.1 + pIP6Address->m_TTL.m_bUpdateScheduled = true; // Avoid 'cache update' query + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processAAnswer: 'Goodbye' received for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR_LEA(" IP6 address (%s)\n"), pIP6Address->m_IPAddress.toString().c_str()); + ); + } + } + else { + // Until now unknown IP6 address -> Add (if the message isn't just a 'Goodbye' note) + if (p_pAAAAAnswer->m_u32TTL) { // NOT just a 'Goodbye' message + pIP6Address = new stcIP6Address(p_pAAAAAnswer->m_IPAddress, p_pAAAAAnswer->m_u32TTL/*, millis()*/); + if ((pIP6Address) && + (pSQAnswer->addIP6Address(pIP6Address))) { + + pSQAnswer->m_u32ContentFlags |= ServiceQueryAnswerType_IP6Address; + + if (pServiceQuery->m_fnCallback) { + pServiceQuery->m_fnCallback(this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer), ServiceQueryAnswerType_IP6Address, true, pServiceQuery->m_pUserdata); + } + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _processAAnswer: FAILED to add IP6 address (%s)!\n"), p_pAAAAAnswer->m_IPAddress.toString().c_str());); + } + } + } + } + pServiceQuery = pServiceQuery->m_pNext; + } // while(service query) + } // else: No p_pAAAAAnswer + + return bResult; + } +#endif + + +/* + * PROBING + */ + +/* + * MDNSResponder::_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 MDNSResponder::_updateProbeStatus(void) { + + bool bResult = true; + + // + // Probe host domain + if ((ProbingStatus_ReadyToStart == m_HostProbeInformation.m_ProbingStatus) && // Ready to get started AND + (_getResponseMulticastInterface(SOFTAP_MODE | STATION_MODE) != IPAddress())) { // Has IP address + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _updateProbeStatus: Starting host probing...\n"));); + + // First probe delay SHOULD be random 0-250 ms + //m_HostProbeInformation.m_ulNextProbeTimeout = (millis() + (rand() % MDNS_PROBE_DELAY)); + m_HostProbeInformation.m_NextProbeTimeFlag.restart(rand() % MDNS_PROBE_DELAY); + m_HostProbeInformation.m_ProbingStatus = ProbingStatus_InProgress; + } + else if ((ProbingStatus_InProgress == m_HostProbeInformation.m_ProbingStatus) && // Probing AND + (m_HostProbeInformation.m_NextProbeTimeFlag.flagged())) { // Time for next probe + //(m_HostProbeInformation.m_ulNextProbeTimeout < millis())) { // Time for next probe + + if (MDNS_PROBE_COUNT > m_HostProbeInformation.m_u8ProbesSent) { // Send next probe + if ((bResult = _sendHostProbe())) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _updateProbeStatus: Did sent host probe\n"));); + //m_HostProbeInformation.m_ulNextProbeTimeout = (millis() + MDNS_PROBE_DELAY); + m_HostProbeInformation.m_NextProbeTimeFlag.restart(MDNS_PROBE_DELAY); + ++m_HostProbeInformation.m_u8ProbesSent; + } + } + else { // Probing finished + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _updateProbeStatus: Done host probing.\n"));); + m_HostProbeInformation.m_ProbingStatus = ProbingStatus_Done; + m_HostProbeInformation.m_NextProbeTimeFlag.reset(); + + if (m_HostProbeInformation.m_fnProbeResultCallback) { + m_HostProbeInformation.m_fnProbeResultCallback(this, m_pcHostname, 0, true, m_HostProbeInformation.m_pProbeResultCallbackUserdata); + } + + _announce(); + } + } // else: Probing already finished OR waiting for next time slot + + // + // Probe services + for (stcMDNSService* pService=m_pServices; ((bResult) && (pService)); pService=pService->m_pNext) { + if (ProbingStatus_ReadyToStart == pService->m_ProbeInformation.m_ProbingStatus) { // Ready to get started + + //pService->m_ProbeInformation.m_ulNextProbeTimeout = (millis() + MDNS_PROBE_DELAY); // More or equal than first probe for host domain + pService->m_ProbeInformation.m_NextProbeTimeFlag.restart(MDNS_PROBE_DELAY); // More or equal than first probe for host domain + pService->m_ProbeInformation.m_ProbingStatus = ProbingStatus_InProgress; + } + else if ((ProbingStatus_InProgress == pService->m_ProbeInformation.m_ProbingStatus) && // Probing AND + (pService->m_ProbeInformation.m_NextProbeTimeFlag.flagged())) { // Time for next probe + //(pService->m_ProbeInformation.m_ulNextProbeTimeout < millis())) { // Time for next probe + + if (MDNS_PROBE_COUNT > pService->m_ProbeInformation.m_u8ProbesSent) { // Send next probe + if ((bResult = _sendServiceProbe(*pService))) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _updateProbeStatus: Did sent service probe (%u)\n"), (pService->m_ProbeInformation.m_u8ProbesSent + 1));); + //pService->m_ProbeInformation.m_ulNextProbeTimeout = (millis() + MDNS_PROBE_DELAY); + pService->m_ProbeInformation.m_NextProbeTimeFlag.restart(MDNS_PROBE_DELAY); + ++pService->m_ProbeInformation.m_u8ProbesSent; + } + } + else { // Probing finished + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _updateProbeStatus: Done service probing %s.%s.%s\n"), (pService->m_pcName ?: m_pcHostname), pService->m_pcService, pService->m_pcProtocol);); + pService->m_ProbeInformation.m_ProbingStatus = ProbingStatus_Done; + pService->m_ProbeInformation.m_NextProbeTimeFlag.reset(); + + MDNSProbeResultCallbackFn fnProbeResultCallback = 0; + void* pProbeResultCallbackUserdata = 0; + if (pService->m_ProbeInformation.m_fnProbeResultCallback) { + fnProbeResultCallback = pService->m_ProbeInformation.m_fnProbeResultCallback; + pProbeResultCallbackUserdata = pService->m_ProbeInformation.m_pProbeResultCallbackUserdata; + } + else { + fnProbeResultCallback = m_HostProbeInformation.m_fnProbeResultCallback; + pProbeResultCallbackUserdata = m_HostProbeInformation.m_pProbeResultCallbackUserdata; + } + if (fnProbeResultCallback) { + fnProbeResultCallback(this, (pService->m_pcName ?: m_pcHostname), pService, true, pProbeResultCallbackUserdata); + } + + //_announceService(*pService); + } + } // else: Probing already finished OR waiting for next time slot + } + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _updateProbeStatus: FAILED!\n")); }); + return bResult; +} + +/* + * MDNSResponder::_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 MDNSResponder::_resetProbeStatus(bool p_bRestart /*= true*/) { + + m_HostProbeInformation.clear(false); + m_HostProbeInformation.m_ProbingStatus = (p_bRestart ? ProbingStatus_ReadyToStart : ProbingStatus_Done); + + for (stcMDNSService* pService=m_pServices; pService; pService=pService->m_pNext) { + pService->m_ProbeInformation.clear(false); + pService->m_ProbeInformation.m_ProbingStatus = (p_bRestart ? ProbingStatus_ReadyToStart : ProbingStatus_Done); + } + return true; +} + +/* + * MDNSResponder::_hasProbesWaitingForAnswers + */ +bool MDNSResponder::_hasProbesWaitingForAnswers(void) const { + + bool bResult = ((ProbingStatus_InProgress == m_HostProbeInformation.m_ProbingStatus) && // Probing + (0 < m_HostProbeInformation.m_u8ProbesSent)); // And really probing + + for (stcMDNSService* pService=m_pServices; ((!bResult) && (pService)); pService=pService->m_pNext) { + bResult = ((ProbingStatus_InProgress == pService->m_ProbeInformation.m_ProbingStatus) && // Probing + (0 < pService->m_ProbeInformation.m_u8ProbesSent)); // And really probing + } + return bResult; +} + +/* + * MDNSResponder::_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 'knwon answers' section of the query. + * Host domain: + * - A/AAAA (eg. esp8266.esp -> 192.168.2.120) + */ +bool MDNSResponder::_sendHostProbe(void) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _sendHostProbe (%s, %lu)\n"), m_pcHostname, millis());); + + bool bResult = true; + + // Requests for host domain + stcMDNSSendParameter sendParameter; + sendParameter.m_bCacheFlush = false; // RFC 6762 10.2 + + sendParameter.m_pQuestions = new stcMDNS_RRQuestion; + if (((bResult = (0 != sendParameter.m_pQuestions))) && + ((bResult = _buildDomainForHost(m_pcHostname, sendParameter.m_pQuestions->m_Header.m_Domain)))) { + + sendParameter.m_pQuestions->m_bUnicast = true; + sendParameter.m_pQuestions->m_Header.m_Attributes.m_u16Type = DNS_RRTYPE_ANY; + sendParameter.m_pQuestions->m_Header.m_Attributes.m_u16Class = (0x8000 | DNS_RRCLASS_IN); // Unicast & INternet + + // Add known answers +#ifdef MDNS_IP4_SUPPORT + sendParameter.m_u8HostReplyMask |= ContentFlag_A; // Add A answer +#endif +#ifdef MDNS_IP6_SUPPORT + sendParameter.m_u8HostReplyMask |= ContentFlag_AAAA; // Add AAAA answer +#endif + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _sendHostProbe: FAILED to create host question!\n"));); + if (sendParameter.m_pQuestions) { + delete sendParameter.m_pQuestions; + sendParameter.m_pQuestions = 0; + } + } + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _sendHostProbe: FAILED!\n")); }); + return ((bResult) && + (_sendMDNSMessage(sendParameter))); +} + +/* + * MDNSResponder::_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 'knwon 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 MDNSResponder::_sendServiceProbe(stcMDNSService& p_rService) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _sendServiceProbe (%s.%s.%s, %lu)\n"), (p_rService.m_pcName ?: m_pcHostname), p_rService.m_pcService, p_rService.m_pcProtocol, millis());); + + bool bResult = true; + + // Requests for service instance domain + stcMDNSSendParameter sendParameter; + sendParameter.m_bCacheFlush = false; // RFC 6762 10.2 + + sendParameter.m_pQuestions = new stcMDNS_RRQuestion; + if (((bResult = (0 != sendParameter.m_pQuestions))) && + ((bResult = _buildDomainForService(p_rService, true, sendParameter.m_pQuestions->m_Header.m_Domain)))) { + + sendParameter.m_pQuestions->m_bUnicast = true; + sendParameter.m_pQuestions->m_Header.m_Attributes.m_u16Type = DNS_RRTYPE_ANY; + sendParameter.m_pQuestions->m_Header.m_Attributes.m_u16Class = (0x8000 | DNS_RRCLASS_IN); // Unicast & INternet + + // Add known answers + p_rService.m_u8ReplyMask = (ContentFlag_SRV | ContentFlag_PTR_NAME); // Add SRV and PTR NAME answers + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _sendServiceProbe: FAILED to create service question!\n"));); + if (sendParameter.m_pQuestions) { + delete sendParameter.m_pQuestions; + sendParameter.m_pQuestions = 0; + } + } + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _sendServiceProbe: FAILED!\n")); }); + return ((bResult) && + (_sendMDNSMessage(sendParameter))); +} + +/* + * MDNSResponder::_cancelProbingForHost + */ +bool MDNSResponder::_cancelProbingForHost(void) { + + bool bResult = false; + + m_HostProbeInformation.clear(false); + + // Send host notification + if (m_HostProbeInformation.m_fnProbeResultCallback) { + m_HostProbeInformation.m_fnProbeResultCallback(this, m_pcHostname, 0, false, m_HostProbeInformation.m_pProbeResultCallbackUserdata); + + bResult = true; + } + + for (stcMDNSService* pService=m_pServices; ((!bResult) && (pService)); pService=pService->m_pNext) { + bResult = _cancelProbingForService(*pService); + } + return bResult; +} + +/* + * MDNSResponder::_cancelProbingForService + */ +bool MDNSResponder::_cancelProbingForService(stcMDNSService& p_rService) { + + bool bResult = false; + + p_rService.m_ProbeInformation.clear(false); + + // Send notification + MDNSProbeResultCallbackFn fnProbeResultCallback = 0; + void* pProbeResultCallbackUserdata = 0; + if (p_rService.m_ProbeInformation.m_fnProbeResultCallback) { + fnProbeResultCallback = p_rService.m_ProbeInformation.m_fnProbeResultCallback; + pProbeResultCallbackUserdata = p_rService.m_ProbeInformation.m_pProbeResultCallbackUserdata; + } + else { + fnProbeResultCallback = m_HostProbeInformation.m_fnProbeResultCallback; + pProbeResultCallbackUserdata = m_HostProbeInformation.m_pProbeResultCallbackUserdata; + } + if (fnProbeResultCallback) { + fnProbeResultCallback(this, (p_rService.m_pcName ?: m_pcHostname), &p_rService, false, pProbeResultCallbackUserdata); + bResult = true; + } + return bResult; +} + + + +/** + * ANNOUNCING + */ + +/* + * MDNSResponder::_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 MDNSResponder::_announce(bool p_bAnnounce /*= true*/) { + + bool bResult = false; + + stcMDNSSendParameter sendParameter; + if (ProbingStatus_Done == m_HostProbeInformation.m_ProbingStatus) { + + bResult = true; + + sendParameter.m_bResponse = true; // 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 + + // Announce host + sendParameter.m_u8HostReplyMask = 0; + #ifdef MDNS_IP4_SUPPORT + sendParameter.m_u8HostReplyMask |= ContentFlag_A; // A answer + sendParameter.m_u8HostReplyMask |= ContentFlag_PTR_IP4; // PTR_IP4 answer + #endif + #ifdef MDNS_IP6_SUPPORT + sendParameter.m_u8HostReplyMask |= ContentFlag_AAAA; // AAAA answer + sendParameter.m_u8HostReplyMask |= ContentFlag_PTR_IP6; // PTR_IP6 answer + #endif + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _announce: Announcing host %s (%u)\n"), m_pcHostname, sendParameter.m_u8HostReplyMask);); + //bResult = _sendMDNSMessage(sendParameter); + //DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _announceService: FAILED (A)!\n")); }); + + // Announce services (service type, name, SRV (location) and TXTs) + for (stcMDNSService* pService=m_pServices; ((bResult) && (pService)); pService=pService->m_pNext) { + //bResult = _announceService(*pService, p_bAnnounce); + //DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _announceService: FAILED (B)!\n")); }); + + + if (ProbingStatus_Done == pService->m_ProbeInformation.m_ProbingStatus) { + pService->m_u8ReplyMask = (ContentFlag_PTR_TYPE | ContentFlag_PTR_NAME | ContentFlag_SRV | ContentFlag_TXT); + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _announce: Announcing service %s.%s.%s (%u)\n"), (pService->m_pcName ?: m_pcHostname), pService->m_pcService, pService->m_pcProtocol, pService->m_u8ReplyMask);); + } + } + } + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _announceService: FAILED!\n")); }); + return ((bResult) && + (_sendMDNSMessage(sendParameter))); + //return bResult; +} + +/* + * MDNSResponder::_announceService + */ +bool MDNSResponder::_announceService(stcMDNSService& p_rService, + bool p_bAnnounce /*= true*/) { + + bool bResult = false; + + stcMDNSSendParameter sendParameter; + if (ProbingStatus_Done == p_rService.m_ProbeInformation.m_ProbingStatus) { + + sendParameter.m_bResponse = true; // 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_u8HostReplyMask = 0; + + // Announce services (service type, name, SRV (location) and TXTs) + p_rService.m_u8ReplyMask = (ContentFlag_PTR_TYPE | ContentFlag_PTR_NAME | ContentFlag_SRV | ContentFlag_TXT); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _announceService: Announcing service %s.%s.%s (%u)\n"), (p_rService.m_pcName ?: m_pcHostname), p_rService.m_pcService, p_rService.m_pcProtocol, p_rService.m_u8ReplyMask);); + + bResult = true; + } + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _announceService: FAILED!\n")); }); + return ((bResult) && + (_sendMDNSMessage(sendParameter))); +} + + +/** + * SERVICE QUERY CACHE + */ + +/* + * MDNSResponder::_hasServiceQueriesWaitingForAnswers + */ +bool MDNSResponder::_hasServiceQueriesWaitingForAnswers(void) const { + + bool bOpenQueries = false; + + for (stcMDNSServiceQuery* pServiceQuery=m_pServiceQueries; pServiceQuery; pServiceQuery=pServiceQuery->m_pNext) { + if (pServiceQuery->m_bAwaitingAnswers) { + bOpenQueries = true; + break; + } + } + return bOpenQueries; +} + +/* + * MDNSResponder::_checkServiceQueryCache + * + * For any 'living' service 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 MDNSResponder::_checkServiceQueryCache(void) { + + bool bResult = true; + + //uint32_t u32Now = millis(); + for (stcMDNSServiceQuery* pServiceQuery=m_pServiceQueries; ((bResult) && (pServiceQuery)); pServiceQuery=pServiceQuery->m_pNext) { + if (pServiceQuery->m_bAwaitingAnswers) { + stcMDNSServiceQuery::stcAnswer* pSQAnswer = pServiceQuery->m_pAnswers; + while ((bResult) && + (pSQAnswer)) { + stcMDNSServiceQuery::stcAnswer* pNextSQAnswer = pSQAnswer->m_pNext; + + // 1. level answer + if ((bResult) && + (pSQAnswer->m_TTLServiceDomain.has80Percent(/*u32Now*/))) { + + bResult = ((_sendMDNSServiceQuery(*pServiceQuery)) && + ((pSQAnswer->m_TTLServiceDomain.m_bUpdateScheduled = true))); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _checkServiceQueryCache: Update scheduled for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR_LEA(" %s\n"), (bResult ? "OK" : "FAILURE")); + ); + } + else if ((bResult) && + (pSQAnswer->m_TTLServiceDomain.isOutdated(/*u32Now*/))) { + + // Delete + if (pServiceQuery->m_fnCallback) { + pServiceQuery->m_fnCallback(this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)/*(uint32_t)(-1)*/, ServiceQueryAnswerType_ServiceDomain, false, pServiceQuery->m_pUserdata); + } + bResult = pServiceQuery->removeAnswer(pSQAnswer); + pSQAnswer = 0; + continue; // Don't use this answer anymore + } + + // 2. level answers + // HostDomain & Port (from SRV) + if ((bResult) && + (pSQAnswer->m_TTLHostDomainAndPort.has80Percent(/*u32Now*/))) { + + bResult = ((_sendMDNSQuery(pSQAnswer->m_ServiceDomain, DNS_RRTYPE_SRV)) && + ((pSQAnswer->m_TTLHostDomainAndPort.m_bUpdateScheduled = true))); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _checkServiceQueryCache: Update scheduled for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR_LEA(" host domain and port %s\n"), (bResult ? "OK" : "FAILURE")); + ); + } + else if ((bResult) && + (pSQAnswer->m_TTLHostDomainAndPort.isOutdated(/*u32Now*/))) { + + // Delete + pSQAnswer->m_HostDomain.clear(); + pSQAnswer->releaseHostDomain(); + pSQAnswer->m_u16Port = 0; + pSQAnswer->m_TTLHostDomainAndPort.set(0/*, 0*/); + uint32_t u32ContentFlags = ServiceQueryAnswerType_HostDomainAndPort; + // As the host domain is the base for the IP4- and IP6Address, remove these too +#ifdef MDNS_IP4_SUPPORT + pSQAnswer->releaseIP4Addresses(); + u32ContentFlags |= ServiceQueryAnswerType_IP4Address; +#endif +#ifdef MDNS_IP6_SUPPORT + pSQAnswer->releaseIP6Addresses(); + u32ContentFlags |= ServiceQueryAnswerType_IP6Address; +#endif + + // Remove content flags for deleted answer parts + pSQAnswer->m_u32ContentFlags &= ~u32ContentFlags; + + if (pServiceQuery->m_fnCallback) { + pServiceQuery->m_fnCallback(this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer), u32ContentFlags, false, pServiceQuery->m_pUserdata); + } + } + + // Txts (from TXT) + if ((bResult) && + (pSQAnswer->m_TTLTxts.has80Percent(/*u32Now*/))) { + + bResult = ((_sendMDNSQuery(pSQAnswer->m_ServiceDomain, DNS_RRTYPE_TXT)) && + ((pSQAnswer->m_TTLTxts.m_bUpdateScheduled = true))); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _checkServiceQueryCache: Update scheduled for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR_LEA(" TXTs %s\n"), (bResult ? "OK" : "FAILURE")); + ); + } + else if ((bResult) && + (pSQAnswer->m_TTLTxts.isOutdated(/*u32Now*/))) { + + // Delete + pSQAnswer->m_Txts.clear(); + pSQAnswer->m_TTLTxts.set(0/*, 0*/); + + // Remove content flags for deleted answer parts + pSQAnswer->m_u32ContentFlags &= ~ServiceQueryAnswerType_Txts; + + if (pServiceQuery->m_fnCallback) { + pServiceQuery->m_fnCallback(this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer), ServiceQueryAnswerType_Txts, false, pServiceQuery->m_pUserdata); + } + } + + // 3. level answers +#ifdef MDNS_IP4_SUPPORT + // IP4Address (from A) + stcMDNSServiceQuery::stcAnswer::stcIP4Address* pIP4Address = pSQAnswer->m_pIP4Addresses; + bool bAUpdateQuerySent = false; + while ((pIP4Address) && + (bResult)) { + + stcMDNSServiceQuery::stcAnswer::stcIP4Address* pNextIP4Address = pIP4Address->m_pNext; // Get 'next' early, as 'current' may be deleted at the end... + + if (pIP4Address->m_TTL.has80Percent(/*u32Now*/)) { // Needs update + if ((bAUpdateQuerySent) || + ((bResult = _sendMDNSQuery(pSQAnswer->m_HostDomain, DNS_RRTYPE_A)))) { + + pIP4Address->m_TTL.m_bUpdateScheduled = true; + bAUpdateQuerySent = true; + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _checkServiceQueryCache: Update scheduled for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR_LEA(" IP4 address (%s)\n"), (pIP4Address->m_IPAddress.toString().c_str())); + ); + } + } + else if (pIP4Address->m_TTL.isOutdated(/*u32Now*/)) { // Outdated: can be deleted + pSQAnswer->removeIP4Address(pIP4Address); + if (!pSQAnswer->m_pIP4Addresses) { // NO IP4 address left -> remove content flag + pSQAnswer->m_u32ContentFlags &= ~ServiceQueryAnswerType_IP4Address; + } + // Notify client + if (pServiceQuery->m_fnCallback) { + pServiceQuery->m_fnCallback(this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer), ServiceQueryAnswerType_IP4Address, false, pServiceQuery->m_pUserdata); + } + } + + pIP4Address = pNextIP4Address; // Next + } // while +#endif +#ifdef MDNS_IP6_SUPPORT + // IP6Address (from AAAA) + stcMDNSServiceQuery::stcAnswer::stcIP6Address* pIP6Address = pSQAnswer->m_pIP6Addresses; + bool bAAAAUpdateQuerySent = false; + while ((pIP6Address) && + (bResult)) { + + stcMDNSServiceQuery::stcAnswer::stcIP6Address* pNextIP6Address = pIP6Address->m_pNext; // Get 'next' early, as 'current' may be deleted at the end... + + if (pIP6Address->m_TTL.has80Percent(/*u32Now*/)) { // Needs update + if ((bAAAAUpdateQuerySent) || + ((bResult = _sendMDNSQuery(pSQAnswer->m_HostDomain, DNS_RRTYPE_AAAA)))) { + + pIP6Address->m_TTL.m_bUpdateScheduled = true; + bAAAAUpdateQuerySent = true; + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _checkServiceQueryCache: Update scheduled for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR_LEA(" IP6 address (%s)\n"), (pIP6Address->m_IPAddress.toString().c_str())); + ); + } + } + else if (pIP6Address->m_TTL.isOutdated(/*u32Now*/)) { // Outdated: can be deleted + pSQAnswer->removeIP6Address(pIP6Address); + if (!pSQAnswer->m_pIP6Addresses) { // NO IP6 address left -> remove content flag + pSQAnswer->m_u32ContentFlags &= ~ServiceQueryAnswerType_IP6Address; + } + // Notify client + if (pServiceQuery->m_fnCallback) { + pServiceQuery->m_fnCallback(this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer), ServiceQueryAnswerType_IP6Address, false, pServiceQuery->m_pUserdata); + } + } + + pIP6Address = pNextIP6Address; // Next + } // while +#endif + pSQAnswer = pNextSQAnswer; + } + } + } + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _checkServiceQueryCache: FAILED!\n")); }); + return bResult; +} + + +/* + * MDNSResponder::_replyMaskForHost + * + * Determines the relavant 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. + * + * In addition, a full name match (question domain == host domain) is marked. + */ +uint8_t MDNSResponder::_replyMaskForHost(const MDNSResponder::stcMDNS_RRHeader& p_RRHeader, + bool* p_pbFullNameMatch /*= 0*/) const { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _replyMaskForHost\n"));); + + uint8_t u8ReplyMask = 0; + (p_pbFullNameMatch ? *p_pbFullNameMatch = false : 0); + + if ((DNS_RRCLASS_IN == p_RRHeader.m_Attributes.m_u16Class) || + (DNS_RRCLASS_ANY == p_RRHeader.m_Attributes.m_u16Class)) { + + if ((DNS_RRTYPE_PTR == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) { + // PTR request +#ifdef MDNS_IP4_SUPPORT + stcMDNS_RRDomain reverseIP4Domain; + if ((_buildDomainForReverseIP4(_getResponseMulticastInterface(SOFTAP_MODE | STATION_MODE), reverseIP4Domain)) && + (p_RRHeader.m_Domain == reverseIP4Domain)) { + // Reverse domain match + u8ReplyMask |= ContentFlag_PTR_IP4; + } +#endif +#ifdef MDNS_IP6_SUPPORT + // TODO +#endif + } // Address qeuest + + stcMDNS_RRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostname, hostDomain)) && + (p_RRHeader.m_Domain == hostDomain)) { // Host domain match + + (p_pbFullNameMatch ? (*p_pbFullNameMatch = true) : (0)); + +#ifdef MDNS_IP4_SUPPORT + if ((DNS_RRTYPE_A == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) { + // IP4 address request + u8ReplyMask |= ContentFlag_A; + } +#endif +#ifdef MDNS_IP6_SUPPORT + if ((DNS_RRTYPE_AAAA == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) { + // IP6 address request + u8ReplyMask |= ContentFlag_AAAA; + } +#endif + } + } + else { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _replyMaskForHost: INVALID RR-class (0x%04X)!\n"), p_RRHeader.m_Attributes.m_u16Class);); + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _replyMaskForHost: %u\n"), u8ReplyMask);); + return u8ReplyMask; +} + +/* + * MDNSResponder::_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. + */ +uint8_t MDNSResponder::_replyMaskForService(const MDNSResponder::stcMDNS_RRHeader& p_RRHeader, + const MDNSResponder::stcMDNSService& p_Service, + bool* p_pbFullNameMatch /*= 0*/) const { + + uint8_t u8ReplyMask = 0; + (p_pbFullNameMatch ? *p_pbFullNameMatch = false : 0); + + if ((DNS_RRCLASS_IN == p_RRHeader.m_Attributes.m_u16Class) || + (DNS_RRCLASS_ANY == p_RRHeader.m_Attributes.m_u16Class)) { + + stcMDNS_RRDomain 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 + u8ReplyMask |= ContentFlag_PTR_TYPE; + } + + stcMDNS_RRDomain serviceDomain; + if ((_buildDomainForService(p_Service, 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 + u8ReplyMask |= ContentFlag_PTR_NAME; + } + + if ((_buildDomainForService(p_Service, 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 + u8ReplyMask |= ContentFlag_SRV; + } + if ((DNS_RRTYPE_TXT == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) { + // Instance info TXT requested + u8ReplyMask |= ContentFlag_TXT; + } + } + } + else { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _replyMaskForService: INVALID RR-class (0x%04X)!\n"), p_RRHeader.m_Attributes.m_u16Class);); + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _replyMaskForService(%s.%s.%s): %u\n"), p_Service.m_pcName, p_Service.m_pcService, p_Service.m_pcProtocol, u8ReplyMask);); + return u8ReplyMask; +} + +} // namespace LEAmDNS + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp b/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp new file mode 100755 index 0000000000..68e86b9315 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp @@ -0,0 +1,729 @@ +/* + * LEAmDNS_Helpers.cpp + * + * License (MIT license): + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "lwip/igmp.h" +#include "lwip/prot/dns.h" // DNS_RRTYPE_xxx, DNS_MQUERY_PORT + +#include "LEAmDNS_Priv.h" + +/* + * namespace LEAmDNS + */ +namespace LEAmDNS { + +/** + * HELPERS + */ + +/* + * strrstr (static) + * + * Backwards search for p_pcPattern in p_pcString + * Based on: https://stackoverflow.com/a/1634398/2778898 + * + */ +static 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; +} + +/* + * MDNSResponder::indexDomain (static) + * + * Updates the given domain 'p_rpcHostname' by appending a delimiter and an index number. + * + * If the given domain already hasa numeric index (after the given delimiter), this index + * incremented. If not, the delimiter and index '2' is added. + * + * If 'p_rpcHostname' is empty (==0), the given default name 'p_pcDefaultHostname' is used, + * if no default is given, 'esp8266' is used. + * + */ +/*static*/ bool MDNSResponder::indexDomain(char*& p_rpcDomain, + const char* p_pcDivider /*= "-"*/, + const char* p_pcDefaultDomain /*= 0*/) { + + bool bResult = false; + + // Ensure a divider exists; use '-' as default + const char* pcDivider = (p_pcDivider ?: "-"); + + if (p_rpcDomain) { + const char* pFoundDivider = strrstr(p_rpcDomain, pcDivider); + if (pFoundDivider) { // maybe already extended + char* pEnd = 0; + unsigned long ulIndex = strtoul((pFoundDivider + os_strlen(pcDivider)), &pEnd, 10); + if ((ulIndex) && + ((pEnd - p_rpcDomain) == os_strlen(p_rpcDomain)) && + (!*pEnd)) { // Valid (old) index found + + char acIndexBuffer[16]; + sprintf(acIndexBuffer, "%lu", (++ulIndex)); + size_t stLength = ((pFoundDivider - p_rpcDomain + os_strlen(pcDivider)) + os_strlen(acIndexBuffer) + 1); + char* pNewHostname = new char[stLength]; + if (pNewHostname) { + memcpy(pNewHostname, p_rpcDomain, (pFoundDivider - p_rpcDomain + os_strlen(pcDivider))); + pNewHostname[pFoundDivider - p_rpcDomain + os_strlen(pcDivider)] = 0; + os_strcat(pNewHostname, acIndexBuffer); + + delete[] p_rpcDomain; + p_rpcDomain = pNewHostname; + + bResult = true; + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.println(F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!"));); + } + } + else { + pFoundDivider = 0; // Flag the need to (base) extend the hostname + } + } + + if (!pFoundDivider) { // not yet extended (or failed to increment extension) -> start indexing + size_t stLength = os_strlen(p_rpcDomain) + (os_strlen(pcDivider) + 1 + 1); // Name + Divider + '2' + '\0' + char* pNewHostname = new char[stLength]; + if (pNewHostname) { + sprintf(pNewHostname, "%s%s2", p_rpcDomain, pcDivider); + + delete[] p_rpcDomain; + p_rpcDomain = pNewHostname; + + bResult = true; + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.println(F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!"));); + } + } + } + else { + // No given host domain, use base or default + const char* cpcDefaultName = (p_pcDefaultDomain ?: "esp8266"); + + size_t stLength = os_strlen(cpcDefaultName) + 1; // '\0' + p_rpcDomain = new char[stLength]; + if (p_rpcDomain) { + os_strncpy(p_rpcDomain, cpcDefaultName, stLength); + bResult = true; + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.println(F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!"));); + } + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] indexDomain: %s\n"), p_rpcDomain);); + return bResult; +} + + +/* + * UDP CONTEXT + */ + +bool MDNSResponder::_callProcess(void) { + + return _process(false); +} + +/* + * MDNSResponder::_allocUDPContext + * + * (Re-)Creates the one-and-only UDP context for the MDNS responder. + * The context is added to the 'multicast'-group and listens to the MDNS port (5353). + * The travel-distance for multicast messages is set to 1 (local, via MDNS_MULTICAST_TTL). + * Messages are received via the MDNSResponder '_update' function. CAUTION: This function + * is called from the WiFi stack side of the ESP stack system. + * + */ +bool MDNSResponder::_allocUDPContext(void) { + DEBUG_EX_INFO(DEBUG_OUTPUT.println("[MDNSResponder] _allocUDPContext");); + + bool bResult = false; + + _releaseUDPContext(); + + ip_addr_t multicast_addr; +#ifdef MDNS_IP4_SUPPORT + multicast_addr.addr = DNS_MQUERY_IPV4_GROUP_INIT; +#endif +#ifdef MDNS_IP6_SUPPORT + //TODO: set multicast address + multicast_addr.addr = DNS_MQUERY_IPV6_GROUP_INIT; +#endif + if (ERR_OK == igmp_joingroup(IP_ADDR_ANY, &multicast_addr)) { + m_pUDPContext = new UdpContext; + m_pUDPContext->ref(); + + if (m_pUDPContext->listen(*IP_ADDR_ANY, DNS_MQUERY_PORT)) { + m_pUDPContext->setMulticastTTL(MDNS_MULTICAST_TTL); + m_pUDPContext->onRx(std::bind(&MDNSResponder::_callProcess, this)); + + bResult = m_pUDPContext->connect(multicast_addr, DNS_MQUERY_PORT); + } + } + return bResult; +} + +/* + * MDNSResponder::_releaseUDPContext + */ +bool MDNSResponder::_releaseUDPContext(void) { + + if (m_pUDPContext) { + m_pUDPContext->unref(); + m_pUDPContext = 0; + } + return true; +} + + +/* + * SERVICE QUERY + */ + +/* + * MDNSResponder::_allocServiceQuery + */ +MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_allocServiceQuery(void) { + + stcMDNSServiceQuery* pServiceQuery = new stcMDNSServiceQuery; + if (pServiceQuery) { + // Link to query list + pServiceQuery->m_pNext = m_pServiceQueries; + m_pServiceQueries = pServiceQuery; + } + return m_pServiceQueries; +} + +/* + * MDNSResponder::_removeServiceQuery + */ +bool MDNSResponder::_removeServiceQuery(MDNSResponder::stcMDNSServiceQuery* p_pServiceQuery) { + + bool bResult = false; + + if (p_pServiceQuery) { + stcMDNSServiceQuery* pPred = m_pServiceQueries; + while ((pPred) && + (pPred->m_pNext != p_pServiceQuery)) { + pPred = pPred->m_pNext; + } + if (pPred) { + pPred->m_pNext = p_pServiceQuery->m_pNext; + delete p_pServiceQuery; + bResult = true; + } + else { // No predecesor + if (m_pServiceQueries == p_pServiceQuery) { + m_pServiceQueries = p_pServiceQuery->m_pNext; + delete p_pServiceQuery; + bResult = true; + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.println("[MDNSResponder] _releaseServiceQuery: INVALID service query!");); + } + } + } + return bResult; +} + +/* + * MDNSResponder::_removeLegacyServiceQuery + */ +bool MDNSResponder::_removeLegacyServiceQuery(void) { + + stcMDNSServiceQuery* pLegacyServiceQuery = _findLegacyServiceQuery(); + return (pLegacyServiceQuery ? _removeServiceQuery(pLegacyServiceQuery) : true); +} + +/* + * MDNSResponder::_findServiceQuery + * + * 'Convert' hMDNSServiceQuery to stcMDNSServiceQuery* (ensure existance) + * + */ +MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_findServiceQuery(MDNSResponder::hMDNSServiceQuery p_hServiceQuery) { + + stcMDNSServiceQuery* pServiceQuery = m_pServiceQueries; + while (pServiceQuery) { + if ((hMDNSServiceQuery)pServiceQuery == p_hServiceQuery) { + break; + } + pServiceQuery = pServiceQuery->m_pNext; + } + return pServiceQuery; +} + +/* + * MDNSResponder::_findLegacyServiceQuery + */ +MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_findLegacyServiceQuery(void) { + + stcMDNSServiceQuery* pServiceQuery = m_pServiceQueries; + while (pServiceQuery) { + if (pServiceQuery->m_bLegacyQuery) { + break; + } + pServiceQuery = pServiceQuery->m_pNext; + } + return pServiceQuery; +} + +/* + * MDNSResponder::_releaseServiceQueries + */ +bool MDNSResponder::_releaseServiceQueries(void) { + while (m_pServiceQueries) { + stcMDNSServiceQuery* pNext = m_pServiceQueries->m_pNext; + delete m_pServiceQueries; + m_pServiceQueries = pNext; + } + return true; +} + +/* + * MDNSResponder::_findNextServiceQueryByServiceType + */ +MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_findNextServiceQueryByServiceType(const stcMDNS_RRDomain& p_ServiceTypeDomain, + const stcMDNSServiceQuery* p_pPrevServiceQuery) { + stcMDNSServiceQuery* pMatchingServiceQuery = 0; + + stcMDNSServiceQuery* pServiceQuery = (p_pPrevServiceQuery ? p_pPrevServiceQuery->m_pNext : m_pServiceQueries); + while (pServiceQuery) { + if (p_ServiceTypeDomain == pServiceQuery->m_ServiceTypeDomain) { + pMatchingServiceQuery = pServiceQuery; + break; + } + pServiceQuery = pServiceQuery->m_pNext; + } + return pMatchingServiceQuery; +} + + +/* + * HOSTNAME + */ + +/* + * MDNSResponder::_setHostname + */ +bool MDNSResponder::_setHostname(const char* p_pcHostname) { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _allocHostname (%s)\n"), p_pcHostname);); + + bool bResult = false; + + _releaseHostname(); + + size_t stLength = 0; + if ((p_pcHostname) && + (MDNS_DOMAIN_LABEL_MAXLENGTH >= (stLength = os_strlen(p_pcHostname)))) { // char max size for a single label + // Copy in hostname characters as lowercase + if ((bResult = (0 != (m_pcHostname = new char[stLength + 1])))) { +#ifdef MDNS_FORCE_LOWERCASE_HOSTNAME + size_t i = 0; + for (; i= os_strlen(p_pcName))) && + (p_pcService) && + (MDNS_SERVICE_NAME_LENGTH >= os_strlen(p_pcService)) && + (p_pcProtocol) && + (MDNS_SERVICE_PROTOCOL_LENGTH >= os_strlen(p_pcProtocol)) && + (p_u16Port) && + (0 != (pService = new stcMDNSService)) && + (pService->setName(p_pcName ?: m_pcHostname)) && + (pService->setService(p_pcService)) && + (pService->setProtocol(p_pcProtocol))) { + + pService->m_bAutoName = (0 == p_pcName); + pService->m_u16Port = p_u16Port; + + // Add to list (or start list) + pService->m_pNext = m_pServices; + m_pServices = pService; + } + return pService; +} + +/* + * MDNSResponder::_releaseService + */ +bool MDNSResponder::_releaseService(MDNSResponder::stcMDNSService* p_pService) { + + bool bResult = false; + + if (p_pService) { + stcMDNSService* pPred = m_pServices; + while ((pPred) && + (pPred->m_pNext != p_pService)) { + pPred = pPred->m_pNext; + } + if (pPred) { + pPred->m_pNext = p_pService->m_pNext; + delete p_pService; + bResult = true; + } + else { // No predecesor + if (m_pServices == p_pService) { + m_pServices = p_pService->m_pNext; + delete p_pService; + bResult = true; + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.println("[MDNSResponder] _releaseService: INVALID service!");); + } + } + } + return bResult; +} + +/* + * MDNSResponder::_releaseServices + */ +bool MDNSResponder::_releaseServices(void) { + + stcMDNSService* pService = m_pServices; + while (pService) { + _releaseService(pService); + pService = m_pServices; + } + return true; +} + +/* + * MDNSResponder::_findService + */ +MDNSResponder::stcMDNSService* MDNSResponder::_findService(const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol) { + + stcMDNSService* pService = m_pServices; + while (pService) { + if ((0 == strcmp(pService->m_pcName, p_pcName)) && + (0 == strcmp(pService->m_pcService, p_pcService)) && + (0 == strcmp(pService->m_pcProtocol, p_pcProtocol))) { + + break; + } + pService = pService->m_pNext; + } + return pService; +} + +/* + * MDNSResponder::_findService + */ +MDNSResponder::stcMDNSService* MDNSResponder::_findService(const MDNSResponder::hMDNSService p_hService) { + + stcMDNSService* pService = m_pServices; + while (pService) { + if (p_hService == (hMDNSService)pService) { + break; + } + pService = pService->m_pNext; + } + return pService; +} + + +/* + * SERVICE TXT + */ + +/* + * MDNSResponder::_allocServiceTxt + */ +MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_allocServiceTxt(MDNSResponder::stcMDNSService* p_pService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp) { + + stcMDNSServiceTxt* pTxt = 0; + + if ((p_pService) && + (p_pcKey) && + (MDNS_SERVICE_TXT_MAXLENGTH > (p_pService->m_Txts.length() + + 1 + // Length byte + (p_pcKey ? os_strlen(p_pcKey) : 0) + + 1 + // '=' + (p_pcValue ? os_strlen(p_pcValue) : 0)))) { + + pTxt = new stcMDNSServiceTxt; + if (pTxt) { + size_t stLength = (p_pcKey ? os_strlen(p_pcKey) : 0); + pTxt->m_pcKey = new char[stLength + 1]; + if (pTxt->m_pcKey) { + os_strncpy(pTxt->m_pcKey, p_pcKey, stLength); pTxt->m_pcKey[stLength] = 0; + } + + if (p_pcValue) { + stLength = (p_pcValue ? os_strlen(p_pcValue) : 0); + pTxt->m_pcValue = new char[stLength + 1]; + if (pTxt->m_pcValue) { + os_strncpy(pTxt->m_pcValue, p_pcValue, stLength); pTxt->m_pcValue[stLength] = 0; + } + } + pTxt->m_bTemp = p_bTemp; + + // Add to list (or start list) + p_pService->m_Txts.add(pTxt); + } + } + return pTxt; +} + +/* + * MDNSResponder::_releaseServiceTxt + */ +bool MDNSResponder::_releaseServiceTxt(MDNSResponder::stcMDNSService* p_pService, + MDNSResponder::stcMDNSServiceTxt* p_pTxt) { + + return ((p_pService) && + (p_pTxt) && + (p_pService->m_Txts.remove(p_pTxt))); +} + +/* + * MDNSResponder::_updateServiceTxt + */ +MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_updateServiceTxt(MDNSResponder::stcMDNSService* p_pService, + MDNSResponder::stcMDNSServiceTxt* p_pTxt, + const char* p_pcValue, + bool p_bTemp) { + + if ((p_pService) && + (p_pTxt) && + (MDNS_SERVICE_TXT_MAXLENGTH > (p_pService->m_Txts.length() - + (p_pTxt->m_pcValue ? os_strlen(p_pTxt->m_pcValue) : 0) + + (p_pcValue ? os_strlen(p_pcValue) : 0)))) { + p_pTxt->update(p_pcValue); + p_pTxt->m_bTemp = p_bTemp; + } + return p_pTxt; +} + +/* + * MDNSResponder::_findServiceTxt + */ +MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_findServiceTxt(MDNSResponder::stcMDNSService* p_pService, + const char* p_pcKey) { + + return (p_pService ? p_pService->m_Txts.find(p_pcKey) : 0); +} + +/* + * MDNSResponder::_findServiceTxt + */ +MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_findServiceTxt(MDNSResponder::stcMDNSService* p_pService, + const hMDNSTxt p_hTxt) { + + return (((p_pService) && (p_hTxt)) ? p_pService->m_Txts.find((stcMDNSServiceTxt*)p_hTxt) : 0); +} + +/* + * MDNSResponder::_addServiceTxt + */ +MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_addServiceTxt(MDNSResponder::stcMDNSService* p_pService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp) { + stcMDNSServiceTxt* pResult = 0; + + if ((p_pService) && + (p_pcKey) && + (os_strlen(p_pcKey))) { + + stcMDNSServiceTxt* pTxt = p_pService->m_Txts.find(p_pcKey); + if (pTxt) { + pResult = _updateServiceTxt(p_pService, pTxt, p_pcValue, p_bTemp); + } + else { + pResult = _allocServiceTxt(p_pService, p_pcKey, p_pcValue, p_bTemp); + } + } + return pResult; +} + +/* + * MDNSResponder::_collectServiceTxts + */ +bool MDNSResponder::_collectServiceTxts(MDNSResponder::stcMDNSService& p_rService) { + + bool bResult = (m_fnServiceTxtCallback + ? m_fnServiceTxtCallback(this, (hMDNSService)&p_rService, m_pServiceTxtCallbackUserdata) + : true); + + if ((bResult) && + (p_rService.m_fnTxtCallback)) { + bResult = p_rService.m_fnTxtCallback(this, (hMDNSService)&p_rService, p_rService.m_pTxtCallbackUserdata); + } + return bResult; +} + +/* + * MDNSResponder::_releaseTempServiceTxts + */ +bool MDNSResponder::_releaseTempServiceTxts(MDNSResponder::stcMDNSService& p_rService) { + + return (p_rService.m_Txts.removeTempTxts()); +} + + +/* + * MISC + */ + +#ifdef DEBUG_ESP_MDNS_RESPONDER + /* + * MDNSResponder::_printRRDomain + */ + bool MDNSResponder::_printRRDomain(const MDNSResponder::stcMDNS_RRDomain& p_RRDomain) const { + + //DEBUG_OUTPUT.printf_P(PSTR_LEA("Domain: ")); + + const char* pCursor = p_RRDomain.m_acName; + uint8_t u8Length = *pCursor++; + if (u8Length) { + while (u8Length) { + for (uint8_t u=0; um_IPAddress.toString().c_str()); + break; +#endif + case DNS_RRTYPE_PTR: + DEBUG_OUTPUT.printf_P(PSTR_LEA("PTR ")); + _printRRDomain(((const stcMDNS_RRAnswerPTR*)&p_RRAnswer)->m_PTRDomain); + break; + case DNS_RRTYPE_TXT: { + size_t stTxtLength = ((const stcMDNS_RRAnswerTXT*)&p_RRAnswer)->m_Txts.c_strLength(); + char* pTxts = new char[stTxtLength]; + if (pTxts) { + ((/*const c_str()!!*/stcMDNS_RRAnswerTXT*)&p_RRAnswer)->m_Txts.c_str(pTxts); + DEBUG_OUTPUT.printf_P(PSTR_LEA("TXT(%u) %s"), stTxtLength, pTxts); + delete[] pTxts; + } + break; + } +#ifdef MDNS_IP6_SUPPORT + case DNS_RRTYPE_AAAA: + DEBUG_OUTPUT.printf_P(PSTR_LEA("AAAA IP:%s"), ((stcMDNS_RRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); + break; +#endif + case DNS_RRTYPE_SRV: + DEBUG_OUTPUT.printf_P(PSTR_LEA("SRV Port:%u "), ((const stcMDNS_RRAnswerSRV*)&p_RRAnswer)->m_u16Port); + _printRRDomain(((const stcMDNS_RRAnswerSRV*)&p_RRAnswer)->m_SRVDomain); + break; + default: + DEBUG_OUTPUT.printf_P(PSTR_LEA("generic ")); + break; + } + DEBUG_OUTPUT.printf_P(PSTR_LEA("\n")); + + return true; + } +#endif + +} // namespace LEAmDNS + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Priv.h b/libraries/ESP8266mDNS/src/LEAmDNS_Priv.h new file mode 100755 index 0000000000..a134c4c81e --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Priv.h @@ -0,0 +1,168 @@ +/* + * 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 LEAMDNS_PRIV_H +#define LEAMDNS_PRIV_H + +/* + * namespace LEAmDNS + */ +namespace LEAmDNS { + +// Enable class debug functions +#define ESP_8266_MDNS_INCLUDE +#define DEBUG_ESP_MDNS_RESPONDER + + +#ifndef LWIP_OPEN_SRC + #define LWIP_OPEN_SRC +#endif + +// Enable/disable debug trace macros +#ifdef DEBUG_ESP_MDNS_RESPONDER +//#define DEBUG_ESP_MDNS_INFO +#define DEBUG_ESP_MDNS_ERR +#define DEBUG_ESP_MDNS_TX +#define DEBUG_ESP_MDNS_RX +#endif + +#ifdef DEBUG_ESP_MDNS_RESPONDER + #ifdef DEBUG_ESP_MDNS_INFO + #define DEBUG_EX_INFO(A) A + #else + #define DEBUG_EX_INFO(A) + #endif + #ifdef DEBUG_ESP_MDNS_ERR + #define DEBUG_EX_ERR(A) A + #else + #define DEBUG_EX_ERR(A) + #endif + #ifdef DEBUG_ESP_MDNS_TX + #define DEBUG_EX_TX(A) A + #else + #define DEBUG_EX_TX(A) + #endif + #ifdef DEBUG_ESP_MDNS_RX + #define DEBUG_EX_RX(A) A + #else + #define DEBUG_EX_RX(A) + #endif + + #ifdef DEBUG_ESP_PORT + #define DEBUG_OUTPUT DEBUG_ESP_PORT + #else + #define DEBUG_OUTPUT Serial + #endif +#else + #define DEBUG_EX_INFO(A) + #define DEBUG_EX_ERR(A) + #define DEBUG_EX_TX(A) + #define DEBUG_EX_RX(A) +#endif + + +/* Replaced by 'lwip/prot/dns.h' definitions +#ifdef MDNS_IP4_SUPPORT + #define MDNS_MULTICAST_ADDR_IP4 (IPAddress(224, 0, 0, 251)) // ip_addr_t v4group = DNS_MQUERY_IPV4_GROUP_INIT +#endif +#ifdef MDNS_IP6_SUPPORT + #define MDNS_MULTICAST_ADDR_IP6 (IPAddress("FF02::FB")) // ip_addr_t v6group = DNS_MQUERY_IPV6_GROUP_INIT +#endif*/ +//#define MDNS_MULTICAST_PORT 5353 + +/* + * 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) + */ +#define MDNS_MULTICAST_TTL 1 + +/* + * This is the MDNS record TTL + * Host level records are set to 2min (120s) + * service level records are set to 75min (4500s) + */ +#define MDNS_HOST_TTL 120 +#define MDNS_SERVICE_TTL 4500 + +/* + * Compressed labels are flaged by the two topmost bits of the length byte being set + */ +#define MDNS_DOMAIN_COMPRESS_MARK 0xC0 +/* + * Avoid endless recursion because of malformed compressed labels + */ +#define MDNS_DOMAIN_MAX_REDIRCTION 6 + +/* + * Default service priority and weight in SRV answers + */ +#define MDNS_SRV_PRIORITY 0 +#define MDNS_SRV_WEIGHT 0 + +/* + * Delay between and number of probes for host and service domains + */ +#define MDNS_PROBE_DELAY 250 +#define MDNS_PROBE_COUNT 3 + + +/* + * Force host domain to use only lowercase letters + */ +//#define MDNS_FORCE_LOWERCASE_HOSTNAME + +/* + * 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 +// See: https://github.com/esp8266/Arduino/issues/3369 +#define PROGMEM_LEA __attribute__((section(".irom.text.LEA"))) +#define PSTR_LEA(s) (__extension__({static const char __c[] PROGMEM_LEA = (s); &__c[0];})) +#else + #ifdef F + #undef F + #endif + #define F(A) A +#endif + +} // namespace LEAmDNS + + +// Include the main header, so the submodlues only need to include this header +#include "LEAmDNS.h" + + +#endif // LEAMDNS_PRIV_H + + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp new file mode 100755 index 0000000000..802d35f590 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp @@ -0,0 +1,2119 @@ +/* + * LEAmDNS_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 "LEAmDNS_Priv.h" + + +/* + * namespace LEAmDNS + */ +namespace LEAmDNS { + +/** + * STRUCTS + */ + +/** + * MDNSResponder::stcMDNSServiceTxt + * + * One MDNS TXT item. + * m_pcValue may be '\0'. + * Objects can be chained together (list, m_pNext). + * A 'm_bTemp' flag differentiates between static and dynamic items. + * Output as byte array 'c#=1' is supported. + */ + +/* + * MDNSResponder::stcMDNSServiceTxt::_stcMDNSServiceTxt constructor + */ +MDNSResponder::_stcMDNSServiceTxt::_stcMDNSServiceTxt(const char* p_pcKey /*= 0*/, + const char* p_pcValue /*= 0*/, + bool p_bTemp /*= false*/) +: m_pNext(0), + m_pcKey(0), + m_pcValue(0), + m_bTemp(p_bTemp) { + + setKey(p_pcKey); + setValue(p_pcValue); +} + +/* + * MDNSResponder::_stcMDNSServiceTxt::_stcMDNSServiceTxt copy-constructor + */ +MDNSResponder::_stcMDNSServiceTxt::_stcMDNSServiceTxt(const MDNSResponder::_stcMDNSServiceTxt& p_Other) +: m_pNext(0), + m_pcKey(0), + m_pcValue(0), + m_bTemp(false) { + + operator=(p_Other); +} + +/* + * MDNSResponder::_stcMDNSServiceTxt::~_stcMDNSServiceTxt destructor + */ +MDNSResponder::_stcMDNSServiceTxt::~_stcMDNSServiceTxt(void) { + + clear(); +} + +/* + * MDNSResponder::_stcMDNSServiceTxt::operator= + */ +MDNSResponder::_stcMDNSServiceTxt& MDNSResponder::_stcMDNSServiceTxt::operator=(const MDNSResponder::_stcMDNSServiceTxt& p_Other) { + + if (&p_Other != this) { + clear(); + set(p_Other.m_pcKey, p_Other.m_pcValue, p_Other.m_bTemp); + } + return *this; +} + +/* + * MDNSResponder::_stcMDNSServiceTxt::clear + */ +bool MDNSResponder::_stcMDNSServiceTxt::clear(void) { + + releaseKey(); + releaseValue(); + return true; +} + +/* + * MDNSResponder::_stcMDNSServiceTxt::allocKey + */ +char* MDNSResponder::_stcMDNSServiceTxt::allocKey(size_t p_stLength) { + + releaseKey(); + if (p_stLength) { + m_pcKey = new char[p_stLength + 1]; + } + return m_pcKey; +} + +/* + * MDNSResponder::_stcMDNSServiceTxt::setKey + */ +bool MDNSResponder::_stcMDNSServiceTxt::setKey(const char* p_pcKey, + size_t p_stLength) { + + bool bResult = false; + + releaseKey(); + if (p_stLength) { + if (allocKey(p_stLength)) { + os_strncpy(m_pcKey, p_pcKey, p_stLength); + m_pcKey[p_stLength] = 0; + bResult = true; + } + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSServiceTxt::setKey + */ +bool MDNSResponder::_stcMDNSServiceTxt::setKey(const char* p_pcKey) { + + return setKey(p_pcKey, (p_pcKey ? os_strlen(p_pcKey) : 0)); +} + +/* + * MDNSResponder::_stcMDNSServiceTxt::releaseKey + */ +bool MDNSResponder::_stcMDNSServiceTxt::releaseKey(void) { + + if (m_pcKey) { + delete[] m_pcKey; + m_pcKey = 0; + } + return true; +} + +/* + * MDNSResponder::_stcMDNSServiceTxt::allocValue + */ +char* MDNSResponder::_stcMDNSServiceTxt::allocValue(size_t p_stLength) { + + releaseValue(); + if (p_stLength) { + m_pcValue = new char[p_stLength + 1]; + } + return m_pcValue; +} + +/* + * MDNSResponder::_stcMDNSServiceTxt::setValue + */ +bool MDNSResponder::_stcMDNSServiceTxt::setValue(const char* p_pcValue, + size_t p_stLength) { + + bool bResult = false; + + releaseValue(); + if (p_stLength) { + if (allocValue(p_stLength)) { + os_strncpy(m_pcValue, p_pcValue, p_stLength); + m_pcValue[p_stLength] = 0; + bResult = true; + } + } + else { // No value -> also OK + bResult = true; + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSServiceTxt::setValue + */ +bool MDNSResponder::_stcMDNSServiceTxt::setValue(const char* p_pcValue) { + + return setValue(p_pcValue, (p_pcValue ? os_strlen(p_pcValue) : 0)); +} + +/* + * MDNSResponder::_stcMDNSServiceTxt::releaseValue + */ +bool MDNSResponder::_stcMDNSServiceTxt::releaseValue(void) { + + if (m_pcValue) { + delete[] m_pcValue; + m_pcValue = 0; + } + return true; +} + +/* + * MDNSResponder::_stcMDNSServiceTxt::set + */ +bool MDNSResponder::_stcMDNSServiceTxt::set(const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp /*= false*/) { + + m_bTemp = p_bTemp; + return ((setKey(p_pcKey)) && + (setValue(p_pcValue))); +} + +/* + * MDNSResponder::_stcMDNSServiceTxt::update + */ +bool MDNSResponder::_stcMDNSServiceTxt::update(const char* p_pcValue) { + + return setValue(p_pcValue); +} + +/* + * MDNSResponder::_stcMDNSServiceTxt::length + * + * length of eg. 'c#=1' without any closing '\0' + */ +size_t MDNSResponder::_stcMDNSServiceTxt::length(void) const { + + size_t stLength = 0; + if (m_pcKey) { + stLength += os_strlen(m_pcKey); // Key + stLength += 1; // '=' + stLength += (m_pcValue ? os_strlen(m_pcValue) : 0); // Value + } + return stLength; +} + + +/** + * MDNSResponder::stcMDNSServiceTxts + * + * A list of zero or more MDNS TXT items. + * Dynamic TXT items can be removed by 'removeTempTxts'. + * A TXT item can be looke up by its 'key' member. + * Export as ';'-separated byte array is supported. + * Export as 'length byte coded' byte array is supported. + * Comparision ((all A TXT items in B and equal) AND (all B TXT items in A and equal)) is supported. + * + */ + +/* + * MDNSResponder::_stcMDNSServiceTxts::_stcMDNSServiceTxts contructor + */ +MDNSResponder::_stcMDNSServiceTxts::_stcMDNSServiceTxts(void) +: m_pTxts(0) { + +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::_stcMDNSServiceTxts copy-constructor + */ +MDNSResponder::_stcMDNSServiceTxts::_stcMDNSServiceTxts(const _stcMDNSServiceTxts& p_Other) +: m_pTxts(0) { + + operator=(p_Other); +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::~_stcMDNSServiceTxts destructor + */ +MDNSResponder::_stcMDNSServiceTxts::~_stcMDNSServiceTxts(void) { + + clear(); +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::operator= + */ +MDNSResponder::_stcMDNSServiceTxts& MDNSResponder::_stcMDNSServiceTxts::operator=(const _stcMDNSServiceTxts& p_Other) { + + if (this != &p_Other) { + clear(); + + for (stcMDNSServiceTxt* pOtherTxt=p_Other.m_pTxts; pOtherTxt; pOtherTxt=pOtherTxt->m_pNext) { + add(new stcMDNSServiceTxt(*pOtherTxt)); + } + } + return *this; +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::clear + */ +bool MDNSResponder::_stcMDNSServiceTxts::clear(void) { + + while (m_pTxts) { + stcMDNSServiceTxt* pNext = m_pTxts->m_pNext; + delete m_pTxts; + m_pTxts = pNext; + } + return true; +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::add + */ +bool MDNSResponder::_stcMDNSServiceTxts::add(MDNSResponder::stcMDNSServiceTxt* p_pTxt) { + + bool bResult = false; + + if (p_pTxt) { + p_pTxt->m_pNext = m_pTxts; + m_pTxts = p_pTxt; + bResult = true; + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::remove + */ +bool MDNSResponder::_stcMDNSServiceTxts::remove(stcMDNSServiceTxt* p_pTxt) { + + bool bResult = false; + + if (p_pTxt) { + stcMDNSServiceTxt* pPred = m_pTxts; + while ((pPred) && + (pPred->m_pNext != p_pTxt)) { + pPred = pPred->m_pNext; + } + if (pPred) { + pPred->m_pNext = p_pTxt->m_pNext; + delete p_pTxt; + bResult = true; + } + else if (m_pTxts == p_pTxt) { // No predecesor, but first item + m_pTxts = p_pTxt->m_pNext; + delete p_pTxt; + bResult = true; + } + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::removeTempTxts + */ +bool MDNSResponder::_stcMDNSServiceTxts::removeTempTxts(void) { + + bool bResult = true; + + stcMDNSServiceTxt* pTxt = m_pTxts; + while ((bResult) && + (pTxt)) { + stcMDNSServiceTxt* pNext = pTxt->m_pNext; + if (pTxt->m_bTemp) { + bResult = remove(pTxt); + } + pTxt = pNext; + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::find + */ +MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_stcMDNSServiceTxts::find(const char* p_pcKey) { + + _stcMDNSServiceTxt* pResult = 0; + + for (stcMDNSServiceTxt* pTxt=m_pTxts; pTxt; pTxt=pTxt->m_pNext) { + if ((p_pcKey) && + (0 == strcmp(pTxt->m_pcKey, p_pcKey))) { + pResult = pTxt; + break; + } + } + return pResult; +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::find + */ +const MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_stcMDNSServiceTxts::find(const char* p_pcKey) const { + + const _stcMDNSServiceTxt* pResult = 0; + + for (const stcMDNSServiceTxt* pTxt=m_pTxts; pTxt; pTxt=pTxt->m_pNext) { + if ((p_pcKey) && + (0 == strcmp(pTxt->m_pcKey, p_pcKey))) { + + pResult = pTxt; + break; + } + } + return pResult; +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::find + */ +MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_stcMDNSServiceTxts::find(const stcMDNSServiceTxt* p_pTxt) { + + _stcMDNSServiceTxt* pResult = 0; + + for (stcMDNSServiceTxt* pTxt=m_pTxts; pTxt; pTxt=pTxt->m_pNext) { + if (p_pTxt == pTxt) { + pResult = pTxt; + break; + } + } + return pResult; +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::length + */ +uint16_t MDNSResponder::_stcMDNSServiceTxts::length(void) const { + + uint16_t u16Length = 0; + + stcMDNSServiceTxt* pTxt = m_pTxts; + while (pTxt) { + u16Length += 1; // Length byte + u16Length += pTxt->length(); // Text + pTxt = pTxt->m_pNext; + } + return u16Length; +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::c_strLength + * + * (incl. closing '\0'). Length bytes place is used for delimiting ';' and closing '\0' + */ +size_t MDNSResponder::_stcMDNSServiceTxts::c_strLength(void) const { + + return length(); +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::c_str + */ +bool MDNSResponder::_stcMDNSServiceTxts::c_str(char* p_pcBuffer) { + + bool bResult = false; + + if (p_pcBuffer) { + bResult = true; + + *p_pcBuffer = 0; + for (stcMDNSServiceTxt* pTxt=m_pTxts; ((bResult) && (pTxt)); pTxt = pTxt->m_pNext) { + size_t stLength; + if ((bResult = (0 != (stLength = (pTxt->m_pcKey ? os_strlen(pTxt->m_pcKey) : 0))))) { + if (pTxt != m_pTxts) { + *p_pcBuffer++ = ';'; + } + os_strncpy(p_pcBuffer, pTxt->m_pcKey, stLength); p_pcBuffer[stLength] = 0; + p_pcBuffer += stLength; + *p_pcBuffer++ = '='; + if ((stLength = (pTxt->m_pcValue ? os_strlen(pTxt->m_pcValue) : 0))) { + os_strncpy(p_pcBuffer, pTxt->m_pcValue, stLength); p_pcBuffer[stLength] = 0; + p_pcBuffer += stLength; + } + } + } + *p_pcBuffer++ = 0; + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::bufferLength + * + * (incl. closing '\0'). + */ +size_t MDNSResponder::_stcMDNSServiceTxts::bufferLength(void) const { + + return (length() + 1); +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::toBuffer + */ +bool MDNSResponder::_stcMDNSServiceTxts::buffer(char* p_pcBuffer) { + + bool bResult = false; + + if (p_pcBuffer) { + bResult = true; + + *p_pcBuffer = 0; + for (stcMDNSServiceTxt* pTxt=m_pTxts; ((bResult) && (pTxt)); pTxt = pTxt->m_pNext) { + *(unsigned char*)p_pcBuffer++ = pTxt->length(); + size_t stLength; + if ((bResult = (0 != (stLength = (pTxt->m_pcKey ? os_strlen(pTxt->m_pcKey) : 0))))) { + os_memcpy(p_pcBuffer, pTxt->m_pcKey, stLength); + p_pcBuffer += stLength; + *p_pcBuffer++ = '='; + if ((stLength = (pTxt->m_pcValue ? os_strlen(pTxt->m_pcValue) : 0))) { + os_memcpy(p_pcBuffer, pTxt->m_pcValue, stLength); + p_pcBuffer += stLength; + } + } + } + *p_pcBuffer++ = 0; + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::compare + */ +bool MDNSResponder::_stcMDNSServiceTxts::compare(const MDNSResponder::_stcMDNSServiceTxts& p_Other) const { + + bool bResult = false; + + if ((bResult = (length() == p_Other.length()))) { + // Compare A->B + for (const stcMDNSServiceTxt* pTxt=m_pTxts; ((bResult) && (pTxt)); pTxt=pTxt->m_pNext) { + const stcMDNSServiceTxt* pOtherTxt = p_Other.find(pTxt->m_pcKey); + bResult = ((pOtherTxt) && + (pTxt->m_pcValue) && + (pOtherTxt->m_pcValue) && + (os_strlen(pTxt->m_pcValue) == os_strlen(pOtherTxt->m_pcValue)) && + (0 == strcmp(pTxt->m_pcValue, pOtherTxt->m_pcValue))); + } + // Compare B->A + for (const stcMDNSServiceTxt* pOtherTxt=p_Other.m_pTxts; ((bResult) && (pOtherTxt)); pOtherTxt=pOtherTxt->m_pNext) { + const stcMDNSServiceTxt* pTxt = find(pOtherTxt->m_pcKey); + bResult = ((pTxt) && + (pOtherTxt->m_pcValue) && + (pTxt->m_pcValue) && + (os_strlen(pOtherTxt->m_pcValue) == os_strlen(pTxt->m_pcValue)) && + (0 == strcmp(pOtherTxt->m_pcValue, pTxt->m_pcValue))); + } + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::operator== + */ +bool MDNSResponder::_stcMDNSServiceTxts::operator==(const _stcMDNSServiceTxts& p_Other) const { + + return compare(p_Other); +} + +/* + * MDNSResponder::_stcMDNSServiceTxts::operator!= + */ +bool MDNSResponder::_stcMDNSServiceTxts::operator!=(const _stcMDNSServiceTxts& p_Other) const { + + return !compare(p_Other); +} + + +/** + * MDNSResponder::stcMDNS_MsgHeader + * + * A MDNS message haeder. + * + */ + +/* + * MDNSResponder::_stcMDNS_MsgHeader::_stcMDNS_MsgHeader + */ +MDNSResponder::_stcMDNS_MsgHeader::_stcMDNS_MsgHeader(uint16_t p_u16ID /*= 0*/, + bool p_bQR /*= false*/, + unsigned char p_ucOpcode /*= 0*/, + bool p_bAA /*= false*/, + bool p_bTC /*= false*/, + bool p_bRD /*= false*/, + bool p_bRA /*= false*/, + unsigned char p_ucRCode /*= 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_ucOpcode), m_1bAA(p_bAA), m_1bTC(p_bTC), m_1bRD(p_bRD), + m_1bRA(p_bRA), m_3bZ(0), m_4bRCode(p_ucRCode), + m_u16QDCount(p_u16QDCount), + m_u16ANCount(p_u16ANCount), + m_u16NSCount(p_u16NSCount), + m_u16ARCount(p_u16ARCount) { + +} + + +/** + * MDNSResponder::stcMDNS_RRDomain + * + * 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. + * + */ + +/* + * MDNSResponder::_stcMDNS_RRDomain::_stcMDNS_RRDomain constructor + */ +MDNSResponder::_stcMDNS_RRDomain::_stcMDNS_RRDomain(void) +: m_u16NameLength(0) { + + clear(); +} + +/* + * MDNSResponder::_stcMDNS_RRDomain::_stcMDNS_RRDomain copy-constructor + */ +MDNSResponder::_stcMDNS_RRDomain::_stcMDNS_RRDomain(const _stcMDNS_RRDomain& p_Other) +: m_u16NameLength(0) { + + operator=(p_Other); +} + +/* + * MDNSResponder::_stcMDNS_RRDomain::operator = + */ +MDNSResponder::_stcMDNS_RRDomain& MDNSResponder::_stcMDNS_RRDomain::operator=(const _stcMDNS_RRDomain& p_Other) { + + if (&p_Other != this) { + memcpy(m_acName, p_Other.m_acName, sizeof(m_acName)); + m_u16NameLength = p_Other.m_u16NameLength; + } + return *this; +} + +/* + * MDNSResponder::_stcMDNS_RRDomain::clear + */ +bool MDNSResponder::_stcMDNS_RRDomain::clear(void) { + + memset(m_acName, 0, sizeof(m_acName)); + m_u16NameLength = 0; + return true; +} + +/* + * MDNSResponder::_stcMDNS_RRDomain::addLabel + */ +bool MDNSResponder::_stcMDNS_RRDomain::addLabel(const char* p_pcLabel, + bool p_bPrependUnderline /*= false*/) { + + bool bResult = false; + + size_t stLength = (p_pcLabel + ? (os_strlen(p_pcLabel) + (p_bPrependUnderline ? 1 : 0)) + : 0); + if ((MDNS_DOMAIN_LABEL_MAXLENGTH >= stLength) && + (MDNS_DOMAIN_MAXLENGTH >= (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; + } + os_strncpy(&(m_acName[m_u16NameLength]), p_pcLabel, stLength); m_acName[m_u16NameLength + stLength] = 0; + m_u16NameLength += stLength; + } + bResult = true; + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNS_RRDomain::compare + */ +bool MDNSResponder::_stcMDNS_RRDomain::compare(const _stcMDNS_RRDomain& 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 == lwip_strnicmp((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 lenght + pO += (1 + *((unsigned char*)pO)); + } + else { // Is 0 -> Successfully reached the end + bResult = true; + break; + } + } + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNS_RRDomain::operator == + */ +bool MDNSResponder::_stcMDNS_RRDomain::operator==(const _stcMDNS_RRDomain& p_Other) const { + + return compare(p_Other); +} + +/* + * MDNSResponder::_stcMDNS_RRDomain::operator != + */ +bool MDNSResponder::_stcMDNS_RRDomain::operator!=(const _stcMDNS_RRDomain& p_Other) const { + + return !compare(p_Other); +} + +/* + * MDNSResponder::_stcMDNS_RRDomain::operator > + */ +bool MDNSResponder::_stcMDNS_RRDomain::operator>(const _stcMDNS_RRDomain& p_Other) const { + + // TODO: Check, if this is a good idea... + return !compare(p_Other); +} + +/* + * MDNSResponder::_stcMDNS_RRDomain::c_strLength + */ +size_t MDNSResponder::_stcMDNS_RRDomain::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; +} + +/* + * MDNSResponder::_stcMDNS_RRDomain::c_str + */ +bool MDNSResponder::_stcMDNS_RRDomain::c_str(char* p_pcBuffer) { + + 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; +} + + +/** + * MDNSResponder::stcMDNS_RRAttributes + * + * A MDNS attributes object. + * + */ + +/* + * MDNSResponder::_stcMDNS_RRAttributes::_stcMDNS_RRAttributes constructor + */ +MDNSResponder::_stcMDNS_RRAttributes::_stcMDNS_RRAttributes(uint16_t p_u16Type /*= 0*/, + uint16_t p_u16Class /*= 1 DNS_RRCLASS_IN Internet*/) +: m_u16Type(p_u16Type), + m_u16Class(p_u16Class) { + +} + +/* + * MDNSResponder::_stcMDNS_RRAttributes::_stcMDNS_RRAttributes copy-constructor + */ +MDNSResponder::_stcMDNS_RRAttributes::_stcMDNS_RRAttributes(const MDNSResponder::_stcMDNS_RRAttributes& p_Other) { + + operator=(p_Other); +} + +/* + * MDNSResponder::_stcMDNS_RRAttributes::operator = + */ +MDNSResponder::_stcMDNS_RRAttributes& MDNSResponder::_stcMDNS_RRAttributes::operator=(const MDNSResponder::_stcMDNS_RRAttributes& p_Other) { + + if (&p_Other != this) { + m_u16Type = p_Other.m_u16Type; + m_u16Class = p_Other.m_u16Class; + } + return *this; +} + + +/** + * MDNSResponder::stcMDNS_RRHeader + * + * A MDNS record header (domain and attributes) object. + * + */ + +/* + * MDNSResponder::_stcMDNS_RRHeader::_stcMDNS_RRHeader constructor + */ +MDNSResponder::_stcMDNS_RRHeader::_stcMDNS_RRHeader(void) { + +} + +/* + * MDNSResponder::_stcMDNS_RRHeader::_stcMDNS_RRHeader copy-constructor + */ +MDNSResponder::_stcMDNS_RRHeader::_stcMDNS_RRHeader(const _stcMDNS_RRHeader& p_Other) { + + operator=(p_Other); +} + +/* + * MDNSResponder::_stcMDNS_RRHeader::operator = + */ +MDNSResponder::_stcMDNS_RRHeader& MDNSResponder::_stcMDNS_RRHeader::operator=(const MDNSResponder::_stcMDNS_RRHeader& p_Other) { + + if (&p_Other != this) { + m_Domain = p_Other.m_Domain; + m_Attributes = p_Other.m_Attributes; + } + return *this; +} + +/* + * MDNSResponder::_stcMDNS_RRHeader::clear + */ +bool MDNSResponder::_stcMDNS_RRHeader::clear(void) { + + m_Domain.clear(); + return true; +} + + +/** + * MDNSResponder::stcMDNS_RRQuestion + * + * A MDNS question record object (header + question flags) + * + */ + +/* + * MDNSResponder::_stcMDNS_RRQuestion::_stcMDNS_RRQuestion constructor + */ +MDNSResponder::_stcMDNS_RRQuestion::_stcMDNS_RRQuestion(void) +: m_pNext(0), + m_bUnicast(false) { + +} + + +/** + * MDNSResponder::stcMDNS_RRAnswer + * + * A MDNS answer record object (header + answer content). + * This is a 'virtual' base class for all other MDNS answer classes. + * + */ + +/* + * MDNSResponder::_stcMDNS_RRAnswer::_stcMDNS_RRAnswer constructor + */ +MDNSResponder::_stcMDNS_RRAnswer::_stcMDNS_RRAnswer(enuAnswerType p_AnswerType, + const MDNSResponder::stcMDNS_RRHeader& 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); +} + +/* + * MDNSResponder::_stcMDNS_RRAnswer::~_stcMDNS_RRAnswer destructor + */ +MDNSResponder::_stcMDNS_RRAnswer::~_stcMDNS_RRAnswer(void) { + +} + +/* + * MDNSResponder::_stcMDNS_RRAnswer::answerType + */ +MDNSResponder::enuAnswerType MDNSResponder::_stcMDNS_RRAnswer::answerType(void) const { + + return m_AnswerType; +} + +/* + * MDNSResponder::_stcMDNS_RRAnswer::clear + */ +bool MDNSResponder::_stcMDNS_RRAnswer::clear(void) { + + m_pNext = 0; + m_Header.clear(); + return true; +} + + +/** + * MDNSResponder::stcMDNS_RRAnswerA + * + * A MDNS A answer object. + * Extends the base class by an IP4 address member. + * + */ + +#ifdef MDNS_IP4_SUPPORT + /* + * MDNSResponder::_stcMDNS_RRAnswerA::stcMDNS_RRAnswerA constructor + */ + MDNSResponder::_stcMDNS_RRAnswerA::_stcMDNS_RRAnswerA(const MDNSResponder::stcMDNS_RRHeader& p_Header, + uint32_t p_u32TTL) + : stcMDNS_RRAnswer(AnswerType_A, p_Header, p_u32TTL), + m_IPAddress(0, 0, 0, 0) { + + } + + /* + * MDNSResponder::_stcMDNS_RRAnswerA::_stcMDNS_RRAnswerA destructor + */ + MDNSResponder::_stcMDNS_RRAnswerA::~_stcMDNS_RRAnswerA(void) { + + clear(); + } + + /* + * MDNSResponder::_stcMDNS_RRAnswerA::clear + */ + bool MDNSResponder::_stcMDNS_RRAnswerA::clear(void) { + + m_IPAddress = IPAddress(0, 0, 0, 0); + return true; + } +#endif + + +/** + * MDNSResponder::stcMDNS_RRAnswerPTR + * + * A MDNS PTR answer object. + * Extends the base class by a MDNS domain member. + * + */ + +/* + * MDNSResponder::_stcMDNS_RRAnswerPTR::_stcMDNS_RRAnswerPTR constructor + */ +MDNSResponder::_stcMDNS_RRAnswerPTR::_stcMDNS_RRAnswerPTR(const MDNSResponder::stcMDNS_RRHeader& p_Header, + uint32_t p_u32TTL) +: stcMDNS_RRAnswer(AnswerType_PTR, p_Header, p_u32TTL) { + +} + +/* + * MDNSResponder::_stcMDNS_RRAnswerPTR::~_stcMDNS_RRAnswerPTR destructor + */ +MDNSResponder::_stcMDNS_RRAnswerPTR::~_stcMDNS_RRAnswerPTR(void) { + + clear(); +} + +/* + * MDNSResponder::_stcMDNS_RRAnswerPTR::clear + */ +bool MDNSResponder::_stcMDNS_RRAnswerPTR::clear(void) { + + m_PTRDomain.clear(); + return true; +} + + +/** + * MDNSResponder::_stcMDNS_RRAnswerTXT + * + * A MDNS TXT answer object. + * Extends the base class by a MDNS TXT items list member. + * + */ + +/* + * MDNSResponder::_stcMDNS_RRAnswerTXT::_stcMDNS_RRAnswerTXT constructor + */ +MDNSResponder::_stcMDNS_RRAnswerTXT::_stcMDNS_RRAnswerTXT(const MDNSResponder::stcMDNS_RRHeader& p_Header, + uint32_t p_u32TTL) +: stcMDNS_RRAnswer(AnswerType_TXT, p_Header, p_u32TTL) { + +} + +/* + * MDNSResponder::_stcMDNS_RRAnswerTXT::~_stcMDNS_RRAnswerTXT destructor + */ +MDNSResponder::_stcMDNS_RRAnswerTXT::~_stcMDNS_RRAnswerTXT(void) { + + clear(); +} + +/* + * MDNSResponder::_stcMDNS_RRAnswerTXT::clear + */ +bool MDNSResponder::_stcMDNS_RRAnswerTXT::clear(void) { + + m_Txts.clear(); + return true; +} + + +/** + * MDNSResponder::stcMDNS_RRAnswerAAAA + * + * A MDNS AAAA answer object. + * (Should) extend the base class by an IP6 address member. + * + */ + +#ifdef MDNS_IP6_SUPPORT + /* + * MDNSResponder::_stcMDNS_RRAnswerAAAA::_stcMDNS_RRAnswerAAAA constructor + */ + MDNSResponder::_stcMDNS_RRAnswerAAAA::_stcMDNS_RRAnswerAAAA(const MDNSResponder::stcMDNS_RRHeader& p_Header, + uint32_t p_u32TTL) + : stcMDNS_RRAnswer(AnswerType_AAAA, p_Header, p_u32TTL) { + + } + + /* + * MDNSResponder::_stcMDNS_RRAnswerAAAA::~_stcMDNS_RRAnswerAAAA destructor + */ + MDNSResponder::_stcMDNS_RRAnswerAAAA::~_stcMDNS_RRAnswerAAAA(void) { + + clear(); + } + + /* + * MDNSResponder::_stcMDNS_RRAnswerAAAA::clear + */ + bool MDNSResponder::_stcMDNS_RRAnswerAAAA::clear(void) { + + return true; + } +#endif + + +/** + * MDNSResponder::stcMDNS_RRAnswerSRV + * + * A MDNS SRV answer object. + * Extends the base class by a port member. + * + */ + +/* + * MDNSResponder::_stcMDNS_RRAnswerSRV::_stcMDNS_RRAnswerSRV constructor + */ +MDNSResponder::_stcMDNS_RRAnswerSRV::_stcMDNS_RRAnswerSRV(const MDNSResponder::stcMDNS_RRHeader& p_Header, + uint32_t p_u32TTL) +: stcMDNS_RRAnswer(AnswerType_SRV, p_Header, p_u32TTL), + m_u16Priority(0), + m_u16Weight(0), + m_u16Port(0) { + +} + +/* + * MDNSResponder::_stcMDNS_RRAnswerSRV::~_stcMDNS_RRAnswerSRV destructor + */ +MDNSResponder::_stcMDNS_RRAnswerSRV::~_stcMDNS_RRAnswerSRV(void) { + + clear(); +} + +/* + * MDNSResponder::_stcMDNS_RRAnswerSRV::clear + */ +bool MDNSResponder::_stcMDNS_RRAnswerSRV::clear(void) { + + m_u16Priority = 0; + m_u16Weight = 0; + m_u16Port = 0; + m_SRVDomain.clear(); + return true; +} + + +/** + * MDNSResponder::stcMDNS_RRAnswerGeneric + * + * An unknown (generic) MDNS answer object. + * Extends the base class by a RDATA buffer member. + * + */ + +/* + * MDNSResponder::_stcMDNS_RRAnswerGeneric::_stcMDNS_RRAnswerGeneric constructor + */ +MDNSResponder::_stcMDNS_RRAnswerGeneric::_stcMDNS_RRAnswerGeneric(const stcMDNS_RRHeader& p_Header, + uint32_t p_u32TTL) +: stcMDNS_RRAnswer(AnswerType_Generic, p_Header, p_u32TTL), + m_u16RDLength(0), + m_pu8RDData(0) { + +} + +/* + * MDNSResponder::_stcMDNS_RRAnswerGeneric::~_stcMDNS_RRAnswerGeneric destructor + */ +MDNSResponder::_stcMDNS_RRAnswerGeneric::~_stcMDNS_RRAnswerGeneric(void) { + + clear(); +} + +/* + * MDNSResponder::_stcMDNS_RRAnswerGeneric::clear + */ +bool MDNSResponder::_stcMDNS_RRAnswerGeneric::clear(void) { + + if (m_pu8RDData) { + delete[] m_pu8RDData; + m_pu8RDData = 0; + } + m_u16RDLength = 0; + + return true; +} + + +/** + * MDNSResponder::stcProbeInformation + * + * Probing status information for a host or service domain + * + */ + +/* + * MDNSResponder::_stcProbeInformation::_stcProbeInformation constructor + */ +MDNSResponder::_stcProbeInformation::_stcProbeInformation(void) +: m_ProbingStatus(ProbingStatus_WaitingForData), + m_u8ProbesSent(0), + //m_ulNextProbeTimeout(0), + m_NextProbeTimeFlag(), + m_bConflict(false), + m_bTiebreakNeeded(false), + m_fnProbeResultCallback(0), + m_pProbeResultCallbackUserdata(0) { +} + +/* + * MDNSResponder::_stcProbeInformation::clear + */ +bool MDNSResponder::_stcProbeInformation::clear(bool p_bClearUserdata /*= false*/) { + + m_ProbingStatus = ProbingStatus_WaitingForData; + m_u8ProbesSent = 0; + //m_ulNextProbeTimeout = 0; + m_NextProbeTimeFlag.reset(); + m_bConflict = false; + m_bTiebreakNeeded = false; + if (p_bClearUserdata) { + m_fnProbeResultCallback = 0; + m_pProbeResultCallbackUserdata = 0; + } + return true; +} + +/** + * MDNSResponder::stcMDNSService + * + * 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. + */ + +/* + * MDNSResponder::_stcMDNSService::_stcMDNSService constructor + */ +MDNSResponder::_stcMDNSService::_stcMDNSService(const char* p_pcName /*= 0*/, + const char* p_pcService /*= 0*/, + const char* p_pcProtocol /*= 0*/) +: m_pNext(0), + m_pcName(0), + m_bAutoName(false), + m_pcService(0), + m_pcProtocol(0), + m_u16Port(0), + m_u8ReplyMask(0), + m_fnTxtCallback(0), + m_pTxtCallbackUserdata(0) { + + setName(p_pcName); + setService(p_pcService); + setProtocol(p_pcProtocol); +} + +/* + * MDNSResponder::_stcMDNSService::~_stcMDNSService destructor + */ +MDNSResponder::_stcMDNSService::~_stcMDNSService(void) { + + releaseName(); + releaseService(); + releaseProtocol(); +} + +/* + * MDNSResponder::_stcMDNSService::setName + */ +bool MDNSResponder::_stcMDNSService::setName(const char* p_pcName) { + + bool bResult = false; + + releaseName(); + size_t stLength = (p_pcName ? os_strlen(p_pcName) : 0); + if (stLength) { + if ((bResult = (0 != (m_pcName = new char[stLength + 1])))) { + os_strncpy(m_pcName, p_pcName, stLength); + m_pcName[stLength] = 0; + } + } + else { + bResult = true; + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSService::releaseName + */ +bool MDNSResponder::_stcMDNSService::releaseName(void) { + + if (m_pcName) { + delete[] m_pcName; + m_pcName = 0; + } + return true; +} + +/* + * MDNSResponder::_stcMDNSService::setService + */ +bool MDNSResponder::_stcMDNSService::setService(const char* p_pcService) { + + bool bResult = false; + + releaseService(); + size_t stLength = (p_pcService ? os_strlen(p_pcService) : 0); + if (stLength) { + if ((bResult = (0 != (m_pcService = new char[stLength + 1])))) { + os_strncpy(m_pcService, p_pcService, stLength); + m_pcService[stLength] = 0; + } + } + else { + bResult = true; + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSService::releaseService + */ +bool MDNSResponder::_stcMDNSService::releaseService(void) { + + if (m_pcService) { + delete[] m_pcService; + m_pcService = 0; + } + return true; +} + +/* + * MDNSResponder::_stcMDNSService::setProtocol + */ +bool MDNSResponder::_stcMDNSService::setProtocol(const char* p_pcProtocol) { + + bool bResult = false; + + releaseProtocol(); + size_t stLength = (p_pcProtocol ? os_strlen(p_pcProtocol) : 0); + if (stLength) { + if ((bResult = (0 != (m_pcProtocol = new char[stLength + 1])))) { + os_strncpy(m_pcProtocol, p_pcProtocol, stLength); + m_pcProtocol[stLength] = 0; + } + } + else { + bResult = true; + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSService::releaseProtocol + */ +bool MDNSResponder::_stcMDNSService::releaseProtocol(void) { + + if (m_pcProtocol) { + delete[] m_pcProtocol; + m_pcProtocol = 0; + } + return true; +} + + +/** + * MDNSResponder::stcMDNSServiceQuery + * + * 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)'). + * + */ + +/** + * MDNSResponder::stcMDNSServiceQuery::stcAnswer + * + * One answer for a service query. + * Every answer must contain + * - a service instance entry (pivot), + * and may contain + * - a host domain, + * - a port + * - an IP4 address + * (- an IP6 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). + * + */ + +/** + * MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL + * + * The TTL (Time-To-Live) for an specific answer content. + * The 80% and outdated states are calculated based on the current time (millis) + * and the 'set' time (also millis). + * If the answer is scheduled for an update, the corresponding flag should be set. + * + */ + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcTTL::_stcTTL constructor + */ +MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcTTL::_stcTTL(uint32_t p_u32TTL /*= 0*//*, + uint32_t p_u32Millis*/ /*= 0*/) +: m_bUpdateScheduled(false) { + + set(p_u32TTL * 1000/*, p_u32Millis*/); +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcTTL::set + */ +bool MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcTTL::set(uint32_t p_u32TTL/*, + uint32_t p_u32Millis*/) { + + /*m_u32TTL = p_u32TTL; + m_u32Millis = p_u32Millis;*/ + m_TTLTimeFlag.restart(p_u32TTL * 1000); + m_bUpdateScheduled = false; + + return true; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcTTL::has80Percent + */ +bool MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcTTL::has80Percent(void/*uint32_t p_u32Now*/) const { + + /*return ((m_u32Millis) && + (!m_bUpdateScheduled) && + ((p_u32Now - m_u32Millis) > (m_u32TTL * 800)));*/ // s * 1000ms/s * 80%(0.8, 8/10) = 1000 * 8 / 10 = 100 * 8 = 800 + return ((m_TTLTimeFlag.timeout()) && + (!m_bUpdateScheduled) && + (m_TTLTimeFlag.hypotheticalTimeout((m_TTLTimeFlag.timeout() * 800) / 1000))); +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcTTL::isOutdated + */ +bool MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcTTL::isOutdated(void/*uint32_t p_u32Now*/) const { + + /*return ((m_u32Millis) && + ((p_u32Now - m_u32Millis) > (m_u32TTL * 1000)));*/ + return ((m_TTLTimeFlag.timeout()) && + (m_TTLTimeFlag.flagged())); +} + + +#ifdef MDNS_IP4_SUPPORT +/** + * MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcIP4Address + * + */ + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcIP4Address::_stcIP4Address constructor + */ +MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcIP4Address::_stcIP4Address(IPAddress p_IPAddress, + uint32_t p_u32TTL /*= 0*//*, + uint32_t p_u32Millis*/ /*= 0*/) +: m_pNext(0), + m_IPAddress(p_IPAddress), + m_TTL(p_u32TTL/*, p_u32Millis*/) { +} +#endif + + +/** + * MDNSResponder::stcMDNSServiceQuery::stcAnswer + */ + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcAnswer constructor + */ +MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcAnswer(void) +: m_pNext(0), + m_pcServiceDomain(0), + m_pcHostDomain(0), + m_u16Port(0), + m_pcTxts(0), +#ifdef MDNS_IP4_SUPPORT + m_pIP4Addresses(0), +#endif +#ifdef MDNS_IP6_SUPPORT + m_pIP6Addresses(0), +#endif + m_u32ContentFlags(0) { +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::~_stcAnswer destructor + */ +MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::~_stcAnswer(void) { + + clear(); +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::clear + */ +bool MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::clear(void) { + + return ((releaseTxts()) && +#ifdef MDNS_IP4_SUPPORT + (releaseIP4Addresses()) && +#endif +#ifdef MDNS_IP6_SUPPORT + (releaseIP6Addresses()) +#endif + (releaseHostDomain()) && + (releaseServiceDomain())); +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::allocServiceDomain + * + * Alloc memory for the char array representation of the service domain. + * + */ +char* MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::allocServiceDomain(size_t p_stLength) { + + releaseServiceDomain(); + if (p_stLength) { + m_pcServiceDomain = new char[p_stLength]; + } + return m_pcServiceDomain; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::releaseServiceDomain + */ +bool MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::releaseServiceDomain(void) { + + if (m_pcServiceDomain) { + delete[] m_pcServiceDomain; + m_pcServiceDomain = 0; + } + return true; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::allocHostDomain + * + * Alloc memory for the char array representation of the host domain. + * + */ +char* MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::allocHostDomain(size_t p_stLength) { + + releaseHostDomain(); + if (p_stLength) { + m_pcHostDomain = new char[p_stLength]; + } + return m_pcHostDomain; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::releaseHostDomain + */ +bool MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::releaseHostDomain(void) { + + if (m_pcHostDomain) { + delete[] m_pcHostDomain; + m_pcHostDomain = 0; + } + return true; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::allocTxts + * + * Alloc memory for the char array representation of the TXT items. + * + */ +char* MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::allocTxts(size_t p_stLength) { + + releaseTxts(); + if (p_stLength) { + m_pcTxts = new char[p_stLength]; + } + return m_pcTxts; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::releaseTxts + */ +bool MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::releaseTxts(void) { + + if (m_pcTxts) { + delete[] m_pcTxts; + m_pcTxts = 0; + } + return true; +} + +#ifdef MDNS_IP4_SUPPORT +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::releaseIP4Addresses + */ +bool MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::releaseIP4Addresses(void) { + + while (m_pIP4Addresses) { + stcIP4Address* pNext = m_pIP4Addresses->m_pNext; + delete m_pIP4Addresses; + m_pIP4Addresses = pNext; + } + return true; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::addIP4Address + */ +bool MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::addIP4Address(MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcIP4Address* p_pIP4Address) { + + bool bResult = false; + + if (p_pIP4Address) { + p_pIP4Address->m_pNext = m_pIP4Addresses; + m_pIP4Addresses = p_pIP4Address; + bResult = true; + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::removeIP4Address + */ +bool MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::removeIP4Address(MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcIP4Address* p_pIP4Address) { + + bool bResult = false; + + if (p_pIP4Address) { + stcIP4Address* pPred = m_pIP4Addresses; + while ((pPred) && + (pPred->m_pNext != p_pIP4Address)) { + pPred = pPred->m_pNext; + } + if (pPred) { + pPred->m_pNext = p_pIP4Address->m_pNext; + delete p_pIP4Address; + bResult = true; + } + else if (m_pIP4Addresses == p_pIP4Address) { // No predecesor, but first item + m_pIP4Addresses = p_pIP4Address->m_pNext; + delete p_pIP4Address; + bResult = true; + } + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::findIP4Address (const) + */ +const MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcIP4Address* MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::findIP4Address(const IPAddress& p_IPAddress) const { + + return (stcIP4Address*)(((const _stcAnswer*)this)->findIP4Address(p_IPAddress)); +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::findIP4Address + */ +MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcIP4Address* MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::findIP4Address(const IPAddress& p_IPAddress) { + + stcIP4Address* pIP4Address = m_pIP4Addresses; + while (pIP4Address) { + if (pIP4Address->m_IPAddress == p_IPAddress) { + break; + } + pIP4Address = pIP4Address->m_pNext; + } + return pIP4Address; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::IP4AddressCount + */ +uint32_t MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::IP4AddressCount(void) const { + + uint32_t u32Count = 0; + + stcIP4Address* pIP4Address = m_pIP4Addresses; + while (pIP4Address) { + ++u32Count; + pIP4Address = pIP4Address->m_pNext; + } + return u32Count; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::IP4AddressAtIndex + */ +MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcIP4Address* MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::IP4AddressAtIndex(uint32_t p_u32Index) { + + return (stcIP4Address*)(((const _stcAnswer*)this)->IP4AddressAtIndex(p_u32Index)); +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::IP4AddressAtIndex (const) + */ +const MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcIP4Address* MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::IP4AddressAtIndex(uint32_t p_u32Index) const { + + const stcIP4Address* pIP4Address = 0; + + if (((uint32_t)(-1) != p_u32Index) && + (m_pIP4Addresses)) { + + uint32_t u32Index; + for (pIP4Address=m_pIP4Addresses, u32Index=0; ((pIP4Address) && (u32Indexm_pNext, ++u32Index); + } + return pIP4Address; +} +#endif + +#ifdef MDNS_IP6_SUPPORT +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::releaseIP6Addresses + */ +bool MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::releaseIP6Addresses(void) { + + while (m_pIP6Addresses) { + stcIP6Address* pNext = m_pIP6Addresses->m_pNext; + delete m_pIP6Addresses; + m_pIP6Addresses = pNext; + } + return true; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::addIP6Address + */ +bool MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::addIP6Address(MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcIP6Address* p_pIP6Address) { + + bool bResult = false; + + if (p_pIP6Address) { + p_pIP6Address->m_pNext = m_pIP6Addresses; + m_pIP6Addresses = p_pIP6Address; + bResult = true; + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::removeIP6Address + */ +bool MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::removeIP6Address(MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcIP6Address* p_pIP6Address) { + + bool bResult = false; + + if (p_pIP6Address) { + stcIP6Address* pPred = m_pIP6Addresses; + while ((pPred) && + (pPred->m_pNext != p_pIP6Address)) { + pPred = pPred->m_pNext; + } + if (pPred) { + pPred->m_pNext = p_pIP6Address->m_pNext; + delete p_pIP6Address; + bResult = true; + } + else if (m_pIP6Addresses == p_pIP6Address) { // No predecesor, but first item + m_pIP6Addresses = p_pIP6Address->m_pNext; + delete p_pIP6Address; + bResult = true; + } + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::findIP6Address + */ +MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcIP6Address* MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::findIP6Address(const IP6Address& p_IPAddress) { + + return (stcIP6Address*)(((const _stcAnswer*)this)->findIP6Address(p_IPAddress)); +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::findIP6Address (const) + */ +const MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcIP6Address* MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::findIP6Address(const IPAddress& p_IPAddress) const { + + const stcIP6Address* pIP6Address = m_pIP6Addresses; + while (pIP6Address) { + if (p_IP6Address->m_IPAddress == p_IPAddress) { + break; + } + pIP6Address = pIP6Address->m_pNext; + } + return pIP6Address; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::IP6AddressCount + */ +uint32_t MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::IP6AddressCount(void) const { + + uint32_t u32Count = 0; + + stcIP6Address* pIP6Address = m_pIP6Addresses; + while (pIP6Address) { + ++u32Count; + pIP6Address = pIP6Address->m_pNext; + } + return u32Count; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::IP6AddressAtIndex (const) + */ +const MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcIP6Address* MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::IP6AddressAtIndex(uint32_t p_u32Index) const { + + return (stcIP6Address*)(((const _stcAnswer*)this)->IP6AddressAtIndex(p_u32Index)); +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::IP6AddressAtIndex + */ +MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::_stcIP6Address* MDNSResponder::_stcMDNSServiceQuery::_stcAnswer::IP6AddressAtIndex(uint32_t p_u32Index) { + + stcIP6Address* pIP6Address = 0; + + if (((uint32_t)(-1) != p_u32Index) && + (m_pIP6Addresses)) { + + uint32_t u32Index; + for (pIP6Address=m_pIP6Addresses, u32Index=0; ((pIP6Address) && (u32Indexm_pNext, ++u32Index); + } + return pIP6Address; +} +#endif + + +/** + * MDNSResponder::stcMDNSServiceQuery + * + * A service 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 service query are stored in 'm_pAnswers' list. + * Individual answers may be addressed by index (in the list of answers). + * Every time a answer component is added (or changes) in a dynamic service query, + * the callback 'm_fnCallback' is called. + * The answer list may be searched by service and host domain. + * + * Service query object may be connected to a linked list. + */ + +/* + * MDNSResponder::_stcMDNSServiceQuery::_stcMDNSServiceQuery constructor + */ +MDNSResponder::_stcMDNSServiceQuery::_stcMDNSServiceQuery(void) +: m_pNext(0), + m_fnCallback(0), + m_pUserdata(0), + m_bLegacyQuery(false), + m_bAwaitingAnswers(true), + m_pAnswers(0) { + + clear(); +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::~_stcMDNSServiceQuery destructor + */ +MDNSResponder::_stcMDNSServiceQuery::~_stcMDNSServiceQuery(void) { + + clear(); +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::clear + */ +bool MDNSResponder::_stcMDNSServiceQuery::clear(void) { + + m_fnCallback = 0; + m_pUserdata = 0; + m_bLegacyQuery = false; + m_bAwaitingAnswers = true; + while (m_pAnswers) { + stcAnswer* pNext = m_pAnswers->m_pNext; + delete m_pAnswers; + m_pAnswers = pNext; + } + return true; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::answerCount + */ +uint32_t MDNSResponder::_stcMDNSServiceQuery::answerCount(void) const { + + uint32_t u32Count = 0; + + stcAnswer* pAnswer = m_pAnswers; + while (pAnswer) { + ++u32Count; + pAnswer = pAnswer->m_pNext; + } + return u32Count; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::answerAtIndex + */ +const MDNSResponder::_stcMDNSServiceQuery::_stcAnswer* MDNSResponder::_stcMDNSServiceQuery::answerAtIndex(uint32_t p_u32Index) const { + + const stcAnswer* pAnswer = 0; + + if (((uint32_t)(-1) != p_u32Index) && + (m_pAnswers)) { + + uint32_t u32Index; + for (pAnswer=m_pAnswers, u32Index=0; ((pAnswer) && (u32Indexm_pNext, ++u32Index); + } + return pAnswer; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::answerAtIndex + */ +MDNSResponder::_stcMDNSServiceQuery::_stcAnswer* MDNSResponder::_stcMDNSServiceQuery::answerAtIndex(uint32_t p_u32Index) { + + return (stcAnswer*)(((const _stcMDNSServiceQuery*)this)->answerAtIndex(p_u32Index)); +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::indexOfAnswer + */ +uint32_t MDNSResponder::_stcMDNSServiceQuery::indexOfAnswer(const MDNSResponder::_stcMDNSServiceQuery::_stcAnswer* p_pAnswer) const { + + uint32_t u32Index = 0; + + for (const stcAnswer* pAnswer=m_pAnswers; pAnswer; pAnswer=pAnswer->m_pNext, ++u32Index) { + if (pAnswer == p_pAnswer) { + return u32Index; + } + } + return ((uint32_t)(-1)); +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::addAnswer + */ +bool MDNSResponder::_stcMDNSServiceQuery::addAnswer(MDNSResponder::_stcMDNSServiceQuery::_stcAnswer* p_pAnswer) { + + bool bResult = false; + + if (p_pAnswer) { + p_pAnswer->m_pNext = m_pAnswers; + m_pAnswers = p_pAnswer; + bResult = true; + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::removeAnswer + */ +bool MDNSResponder::_stcMDNSServiceQuery::removeAnswer(MDNSResponder::_stcMDNSServiceQuery::_stcAnswer* p_pAnswer) { + + bool bResult = false; + + if (p_pAnswer) { + stcAnswer* pPred = m_pAnswers; + while ((pPred) && + (pPred->m_pNext != p_pAnswer)) { + pPred = pPred->m_pNext; + } + if (pPred) { + pPred->m_pNext = p_pAnswer->m_pNext; + delete p_pAnswer; + bResult = true; + } + else if (m_pAnswers == p_pAnswer) { // No predecesor, but first item + m_pAnswers = p_pAnswer->m_pNext; + delete p_pAnswer; + bResult = true; + } + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::findAnswerForServiceDomain + */ +MDNSResponder::_stcMDNSServiceQuery::_stcAnswer* MDNSResponder::_stcMDNSServiceQuery::findAnswerForServiceDomain(const MDNSResponder::stcMDNS_RRDomain& p_ServiceDomain) { + + stcAnswer* pAnswer = m_pAnswers; + while (pAnswer) { + if (pAnswer->m_ServiceDomain == p_ServiceDomain) { + break; + } + pAnswer = pAnswer->m_pNext; + } + return pAnswer; +} + +/* + * MDNSResponder::_stcMDNSServiceQuery::findAnswerForHostDomain + */ +MDNSResponder::_stcMDNSServiceQuery::_stcAnswer* MDNSResponder::_stcMDNSServiceQuery::findAnswerForHostDomain(const MDNSResponder::stcMDNS_RRDomain& p_HostDomain) { + + stcAnswer* pAnswer = m_pAnswers; + while (pAnswer) { + if (pAnswer->m_HostDomain == p_HostDomain) { + break; + } + pAnswer = pAnswer->m_pNext; + } + return pAnswer; +} + + +/** + * MDNSResponder::stcMDNSSendParameter + * + * 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. + * + */ + +/** + * MDNSResponder::stcMDNSSendParameter::stcDomainCacheItem + * + * A cached host or service domain, incl. the offset in the UDP output buffer. + * + */ + +/* + * MDNSResponder::stcMDNSSendParameter::_stcDomainCacheItem::_stcDomainCacheItem constructor + */ +MDNSResponder::stcMDNSSendParameter::_stcDomainCacheItem::_stcDomainCacheItem(const void* p_pHostnameOrService, + bool p_bAdditionalData, + uint32_t p_u16Offset) +: m_pNext(0), + m_pHostnameOrService(p_pHostnameOrService), + m_bAdditionalData(p_bAdditionalData), + m_u16Offset(p_u16Offset) { + +} + +/** + * MDNSResponder::stcMDNSSendParameter + */ + +/* + * MDNSResponder::_stcMDNSSendParameter::_stcMDNSSendParameter constructor + */ +MDNSResponder::_stcMDNSSendParameter::_stcMDNSSendParameter(void) +: m_pQuestions(0), + m_pDomainCacheItems(0) { + + clear(); +} + +/* + * MDNSResponder::_stcMDNSSendParameter::~_stcMDNSSendParameter destructor + */ +MDNSResponder::_stcMDNSSendParameter::~_stcMDNSSendParameter(void) { + + clear(); +} + +/* + * MDNSResponder::_stcMDNSSendParameter::clear + */ +bool MDNSResponder::_stcMDNSSendParameter::clear(void) { + + m_u16ID = 0; + m_u8HostReplyMask = 0; + m_u16Offset = 0; + + m_bLegacyQuery = false; + m_bResponse = false; + m_bAuthorative = false; + m_bUnicast = false; + m_bUnannounce = false; + + m_bCacheFlush = true; + + while (m_pQuestions) { + stcMDNS_RRQuestion* pNext = m_pQuestions->m_pNext; + delete m_pQuestions; + m_pQuestions = pNext; + } + while (m_pDomainCacheItems) { + stcDomainCacheItem* pNext = m_pDomainCacheItems->m_pNext; + delete m_pDomainCacheItems; + m_pDomainCacheItems = pNext; + } + return true; +} + +/* + * MDNSResponder::_stcMDNSSendParameter::shiftOffset + */ +bool MDNSResponder::_stcMDNSSendParameter::shiftOffset(uint16_t p_u16Shift) { + + m_u16Offset += p_u16Shift; + return true; +} + +/* + * MDNSResponder::_stcMDNSSendParameter::addDomainCacheItem + */ +bool MDNSResponder::_stcMDNSSendParameter::addDomainCacheItem(const void* p_pHostnameOrService, + bool p_bAdditionalData, + uint16_t p_u16Offset) { + + bool bResult = false; + + stcDomainCacheItem* pNewItem = 0; + if ((p_pHostnameOrService) && + (p_u16Offset) && + ((pNewItem = new stcDomainCacheItem(p_pHostnameOrService, p_bAdditionalData, p_u16Offset)))) { + + pNewItem->m_pNext = m_pDomainCacheItems; + bResult = ((m_pDomainCacheItems = pNewItem)); + } + return bResult; +} + +/* + * MDNSResponder::_stcMDNSSendParameter::findCachedDomainOffset + */ +uint16_t MDNSResponder::_stcMDNSSendParameter::findCachedDomainOffset(const void* p_pHostnameOrService, + bool p_bAdditionalData) const { + + const stcDomainCacheItem* pCacheItem = m_pDomainCacheItems; + + for (; pCacheItem; pCacheItem=pCacheItem->m_pNext) { + if ((pCacheItem->m_pHostnameOrService == p_pHostnameOrService) && + (pCacheItem->m_bAdditionalData == p_bAdditionalData)) { // Found cache item + break; + } + } + return (pCacheItem ? pCacheItem->m_u16Offset : 0); +} + +} // namespace LEAmDNS + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS_Transfer.cpp new file mode 100755 index 0000000000..a0e0128aa9 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Transfer.cpp @@ -0,0 +1,1633 @@ +/* + * LEAmDNS_Transfer.cpp + * + * License (MIT license): + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +extern "C" { + #include "user_interface.h" +} + +#include "lwip/prot/dns.h" // DNS_RRTYPE_xxx, DNS_MQUERY_PORT + +#include "LEAmDNS_Priv.h" + + +/* + * namespace LEAmDNS + */ +namespace LEAmDNS { + +/** + * CONST STRINGS + */ +static const char* scpcLocal = "local"; +static const char* scpcServices = "services"; +static const char* scpcDNSSD = "dns-sd"; +static const char* scpcUDP = "udp"; +//static const char* scpcTCP = "tcp"; + +#ifdef MDNS_IP4_SUPPORT + static const char* scpcReverseIP4Domain = "in-addr"; +#endif +#ifdef MDNS_IP6_SUPPORT + static const char* scpcReverseIP6Domain = "ip6"; +#endif +static const char* scpcReverseTopDomain = "arpa"; + +/** + * TRANSFER + */ + + +/** + * SENDING + */ + +/* + * MDNSResponder::_sendMDNSMessage + * + * Unicast responses are prepared and sent directly to the querier. + * Multicast responses or queries are transferred to _sendMDNSMessage_Multicast + * + * Any reply flags in installed services are removed at the end! + * + */ +bool MDNSResponder::_sendMDNSMessage(MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + + bool bResult = true; + + if (p_rSendParameter.m_bResponse) { + if (p_rSendParameter.m_bUnicast) { // Unicast response -> Send to querier + DEBUG_EX_ERR(if (!m_pUDPContext->getRemoteAddress()) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _sendMDNSMessage: MISSING remote address for response!\n")); }); + ip_addr_t ipRemote; + ipRemote.addr = m_pUDPContext->getRemoteAddress(); + bResult = ((_prepareMDNSMessage(p_rSendParameter, _getResponseMulticastInterface(SOFTAP_MODE | STATION_MODE))) && + (m_pUDPContext->send(&ipRemote, m_pUDPContext->getRemotePort()))); + } + else { // Multicast response -> Send via the same network interface, that received the query + bResult = _sendMDNSMessage_Multicast(p_rSendParameter, (SOFTAP_MODE | STATION_MODE)); + } + } + else { // Multicast query -> Send by all available network interfaces + const int caiWiFiOpModes[2] = { SOFTAP_MODE, STATION_MODE }; + for (int iInterfaceId=0; ((bResult) && (iInterfaceId<=1)); ++iInterfaceId) { + if (wifi_get_opmode() & caiWiFiOpModes[iInterfaceId]) { + bResult = _sendMDNSMessage_Multicast(p_rSendParameter, caiWiFiOpModes[iInterfaceId]); + } + } + } + + // Finally clear service reply masks + for (stcMDNSService* pService=m_pServices; pService; pService=pService->m_pNext) { + pService->m_u8ReplyMask = 0; + } + + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _sendMDNSMessage: FAILED!\n")); }); + return bResult; +} + +/* + * MDNSResponder::_sendMDNSMessage_Multicast + * + * Fills the UDP output buffer (via _prepareMDNSMessage) and sends the buffer + * via the selected WiFi interface (Station or AP) + */ +bool MDNSResponder::_sendMDNSMessage_Multicast(MDNSResponder::stcMDNSSendParameter& p_rSendParameter, + int p_iWiFiOpMode) { + bool bResult = false; + + ip_addr_t ifFromAddress; + ifFromAddress.addr = _getResponseMulticastInterface(p_iWiFiOpMode); + IPAddress fromIPAddress(ifFromAddress.addr); + m_pUDPContext->setMulticastInterface(ifFromAddress); + + ip_addr_t toMulticastAddress; +#ifdef MDNS_IP4_SUPPORT + toMulticastAddress.addr = DNS_MQUERY_IPV4_GROUP_INIT; +#endif +#ifdef MDNS_IP6_SUPPORT + //TODO: set multicast address + toMulticastAddress.addr = DNS_MQUERY_IPV6_GROUP_INIT; +#endif + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _sendMDNSMessage_Multicast: Will send to '%s'.\n"), IPAddress(toMulticastAddress.addr).toString().c_str());); + bResult = ((_prepareMDNSMessage(p_rSendParameter, fromIPAddress)) && + (m_pUDPContext->send(&toMulticastAddress, DNS_MQUERY_PORT))); + + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _sendMDNSMessage_Multicast: FAILED!\n")); }); + return bResult; +} + +/* + * MDNSResponder::_prepareMDNSMessage + * + * The MDNS message is composed in a two-step process. + * In the first loop 'only' the header informations (mainly number of answers) are collected, + * while in the seconds loop, the header and all queries and answers are written to the UDP + * output buffer. + * + */ +bool MDNSResponder::_prepareMDNSMessage(MDNSResponder::stcMDNSSendParameter& p_rSendParameter, + IPAddress p_IPAddress) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage\n"));); + bool bResult = true; + + // Prepare header; count answers + _stcMDNS_MsgHeader msgHeader(0, p_rSendParameter.m_bResponse, 0, p_rSendParameter.m_bAuthorative); + // If this is a response, the answers are anwers, + // else this is a query or probe and the answers go into auth section + uint16_t& ru16Answers = (p_rSendParameter.m_bResponse + ? msgHeader.m_u16ANCount + : msgHeader.m_u16NSCount); + + /** + * enuSequence + */ + enum enuSequence { + Sequence_Count = 0, + Sequence_Send = 1 + }; + + // Two step sequence: 'Count' and 'Send' + for (uint32_t sequence=Sequence_Count; ((bResult) && (sequence<=Sequence_Send)); ++sequence) { + DEBUG_EX_INFO( + if (Sequence_Send == sequence) { + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), + (unsigned)msgHeader.m_u16ID, + (unsigned)msgHeader.m_1bQR, (unsigned)msgHeader.m_4bOpcode, (unsigned)msgHeader.m_1bAA, (unsigned)msgHeader.m_1bTC, (unsigned)msgHeader.m_1bRD, + (unsigned)msgHeader.m_1bRA, (unsigned)msgHeader.m_4bRCode, + (unsigned)msgHeader.m_u16QDCount, + (unsigned)msgHeader.m_u16ANCount, + (unsigned)msgHeader.m_u16NSCount, + (unsigned)msgHeader.m_u16ARCount); + } + ); + // Count/send + // Header + bResult = ((Sequence_Count == sequence) + ? true + : _writeMDNSMsgHeader(msgHeader, p_rSendParameter)); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage: _writeMDNSMsgHeader FAILED!\n"));); + // Questions + for (stcMDNS_RRQuestion* pQuestion=p_rSendParameter.m_pQuestions; ((bResult) && (pQuestion)); pQuestion=pQuestion->m_pNext) { + ((Sequence_Count == sequence) + ? ++msgHeader.m_u16QDCount + : (bResult = _writeMDNSQuestion(*pQuestion, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage: _writeMDNSQuestion FAILED!\n"));); + } + + // Answers and authorative answers +#ifdef MDNS_IP4_SUPPORT + if ((bResult) && + (p_rSendParameter.m_u8HostReplyMask & ContentFlag_A)) { + ((Sequence_Count == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_A(p_IPAddress, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_A(A) FAILED!\n"));); + } + if ((bResult) && + (p_rSendParameter.m_u8HostReplyMask & ContentFlag_PTR_IP4)) { + ((Sequence_Count == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_PTR_IP4(p_IPAddress, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_IP4 FAILED!\n"));); + } +#endif +#ifdef MDNS_IP6_SUPPORT + if ((bResult) && + (p_rSendParameter.m_u8HostReplyMask & ContentFlag_AAAA)) { + ((Sequence_Count == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_AAAA(p_IPAddress, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_AAAA(A) FAILED!\n"));); + } + if ((bResult) && + (p_rSendParameter.m_u8HostReplyMask & ContentFlag_PTR_IP6)) { + ((Sequence_Count == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_PTR_IP6(p_IPAddress, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_IP6 FAILED!\n"));); + } +#endif + + for (stcMDNSService* pService=m_pServices; ((bResult) && (pService)); pService=pService->m_pNext) { + if ((bResult) && + (pService->m_u8ReplyMask & ContentFlag_PTR_TYPE)) { + ((Sequence_Count == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_PTR_TYPE(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_TYPE FAILED!\n"));); + } + if ((bResult) && + (pService->m_u8ReplyMask & ContentFlag_PTR_NAME)) { + ((Sequence_Count == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_PTR_NAME(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_NAME FAILED!\n"));); + } + if ((bResult) && + (pService->m_u8ReplyMask & ContentFlag_SRV)) { + ((Sequence_Count == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_SRV(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_SRV(A) FAILED!\n"));); + } + if ((bResult) && + (pService->m_u8ReplyMask & ContentFlag_TXT)) { + ((Sequence_Count == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_TXT(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_TXT(A) FAILED!\n"));); + } + } // for services + + // Additional answers +#ifdef MDNS_IP4_SUPPORT + bool bNeedsAdditionalAnswerA = false; +#endif +#ifdef MDNS_IP6_SUPPORT + bool bNeedsAdditionalAnswerAAAA = false; +#endif + for (stcMDNSService* pService=m_pServices; ((bResult) && (pService)); pService=pService->m_pNext) { + if ((bResult) && + (pService->m_u8ReplyMask & ContentFlag_PTR_NAME) && // If PTR_NAME is requested, AND + (!(pService->m_u8ReplyMask & ContentFlag_SRV))) { // NOT SRV -> add SRV as additional answer + ((Sequence_Count == sequence) + ? ++msgHeader.m_u16ARCount + : (bResult = _writeMDNSAnswer_SRV(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_SRV(B) FAILED!\n"));); + } + if ((bResult) && + (pService->m_u8ReplyMask & ContentFlag_PTR_NAME) && // If PTR_NAME is requested, AND + (!(pService->m_u8ReplyMask & ContentFlag_TXT))) { // NOT TXT -> add TXT as additional answer + ((Sequence_Count == sequence) + ? ++msgHeader.m_u16ARCount + : (bResult = _writeMDNSAnswer_TXT(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_TXT(B) FAILED!\n"));); + } + if ((pService->m_u8ReplyMask & (ContentFlag_PTR_NAME | ContentFlag_SRV)) || // If service instance name or SRV OR + (p_rSendParameter.m_u8HostReplyMask & (ContentFlag_A | ContentFlag_AAAA))) { // any host IP address is requested +#ifdef MDNS_IP4_SUPPORT + if ((bResult) && + (!(p_rSendParameter.m_u8HostReplyMask & ContentFlag_A))) { // Add IP4 address + bNeedsAdditionalAnswerA = true; + } +#endif +#ifdef MDNS_IP6_SUPPORT + if ((bResult) && + (!(p_rSendParameter.m_u8HostReplyMask & ContentFlag_AAAA))) { // Add IP6 address + bNeedsAdditionalAnswerAAAA = true; + } +#endif + } + } // for services + + // Answer A needed? +#ifdef MDNS_IP4_SUPPORT + if ((bResult) && + (bNeedsAdditionalAnswerA)) { + ((Sequence_Count == sequence) + ? ++msgHeader.m_u16ARCount + : (bResult = _writeMDNSAnswer_A(p_IPAddress, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_A(B) FAILED!\n"));); + } +#endif +#ifdef MDNS_IP6_SUPPORT + // Answer AAAA needed? + if ((bResult) && + (bNeedsAdditionalAnswerAAAA)) { + ((Sequence_Count == sequence) + ? ++msgHeader.m_u16ARCount + : (bResult = _writeMDNSAnswer_AAAA(p_IPAddress, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_AAAA(B) FAILED!\n"));); + } +#endif + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage: Loop %i FAILED!\n"), sequence);); + } // for sequence + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _prepareMDNSMessage: FAILED!\n"));); + return bResult; +} + +/* + * MDNSResponder::_sendMDNSServiceQuery + * + * Creates and sends a PTR query for the given service domain. + * + */ +bool MDNSResponder::_sendMDNSServiceQuery(const MDNSResponder::stcMDNSServiceQuery& p_ServiceQuery) { + + return _sendMDNSQuery(p_ServiceQuery.m_ServiceTypeDomain, DNS_RRTYPE_PTR); +} + +/* + * MDNSResponder::_sendMDNSQuery + * + * Creates and sends a query for the given domain and query type. + * + */ +bool MDNSResponder::_sendMDNSQuery(const MDNSResponder::stcMDNS_RRDomain& p_QueryDomain, + uint16_t p_u16QueryType) { + + bool bResult = false; + + stcMDNSSendParameter sendParameter; + if (0 != ((sendParameter.m_pQuestions = new stcMDNS_RRQuestion))) { + sendParameter.m_pQuestions->m_Header.m_Domain = p_QueryDomain; + + sendParameter.m_pQuestions->m_bUnicast = true; + sendParameter.m_pQuestions->m_Header.m_Attributes.m_u16Type = p_u16QueryType; + sendParameter.m_pQuestions->m_Header.m_Attributes.m_u16Class = (0x8000 | DNS_RRCLASS_IN); // Unicast & INternet + + bResult = _sendMDNSMessage(sendParameter); + } // else: FAILED to alloc question + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _sendMDNSQuery: FAILED to alloc question!\n"));); + return bResult; +} + +/* + * MDNSResponder::_getResponseMulticastInterface + * + * Selects the appropriate interface for responses. + * If AP mode is enabled and the remote contact is in the APs local net, then the + * AP interface is used to send the response. + * Otherwise the Station interface (if available) is used. + * + */ +IPAddress MDNSResponder::_getResponseMulticastInterface(int p_iWiFiOpModes) const { + + ip_info IPInfo_Local; + bool bFoundMatch = false; + + if ((p_iWiFiOpModes & SOFTAP_MODE) && + (wifi_get_opmode() & SOFTAP_MODE)) { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _getResponseMulticastInterface: SOFTAP_MODE\n"));); + // Get remote IP address + ip_info IPInfo_Remote; + IPInfo_Remote.ip.addr = m_pUDPContext->getRemoteAddress(); + // Get local (AP) IP address + wifi_get_ip_info(SOFTAP_IF, &IPInfo_Local); + + if ((IPInfo_Local.ip.addr) && // Has local AP IP address AND + (ip_addr_netcmp(&IPInfo_Remote.ip, &IPInfo_Local.ip, &IPInfo_Local.netmask))) { // Remote address is in the same subnet as the AP + bFoundMatch = true; + } + } + if ((!bFoundMatch) && + (p_iWiFiOpModes & STATION_MODE) && + (wifi_get_opmode() & STATION_MODE)) { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _getResponseMulticastInterface: STATION_MODE\n"));); + // Get local (STATION) IP address + wifi_get_ip_info(STATION_IF, &IPInfo_Local); + } + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _getResponseMulticastInterface(%i): %s\n"), p_iWiFiOpModes, IPAddress(IPInfo_Local.ip.addr).toString().c_str());); + return IPAddress(IPInfo_Local.ip.addr); +} + + +/** + * HELPERS + */ + +/** + * RESOURCE RECORDS + */ + +/* + * MDNSResponder::_readRRQuestion + * + * Reads a question (eg. MyESP._http._tcp.local ANY IN) from the UPD input buffer. + * + */ +bool MDNSResponder::_readRRQuestion(MDNSResponder::stcMDNS_RRQuestion& p_rRRQuestion) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRQuestion\n"));); + + bool bResult = false; + + if ((bResult = _readRRHeader(p_rRRQuestion.m_Header))) { + // Extract unicast flag from class field + p_rRRQuestion.m_bUnicast = (p_rRRQuestion.m_Header.m_Attributes.m_u16Class & 0x8000); + p_rRRQuestion.m_Header.m_Attributes.m_u16Class &= (~0x8000); + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRQuestion ")); + _printRRDomain(p_rRRQuestion.m_Header.m_Domain); + DEBUG_OUTPUT.printf_P(PSTR_LEA(" Type:0x%04X Class:0x%04X %s\n"), (unsigned)p_rRRQuestion.m_Header.m_Attributes.m_u16Type, (unsigned)p_rRRQuestion.m_Header.m_Attributes.m_u16Class, (p_rRRQuestion.m_bUnicast ? "Unicast" : "Multicast")); + ); + } + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRQuestion: FAILED!\n")); }); + return bResult; +} + +/* + * MDNSResponder::_readRRAnswer + * + * Reads an answer (eg. _http._tcp.local PTR OP TTL MyESP._http._tcp.local) + * from the UDP input buffer. + * After reading the domain and type info, the further processing of the answer + * is transferred the answer specific reading functions. + * Unknown answer types are processed by the generic answer reader (to remove them + * from the input buffer). + * + */ +bool MDNSResponder::_readRRAnswer(MDNSResponder::stcMDNS_RRAnswer*& p_rpRRAnswer) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswer\n"));); + + bool bResult = false; + + stcMDNS_RRHeader header; + uint32_t u32TTL; + uint16_t u16RDLength; + if ((_readRRHeader(header)) && + (_udpRead32(u32TTL)) && + (_udpRead16(u16RDLength))) { + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswer: Reading 0x%04X answer (class:0x%04X, TTL:%u, RDLength:%u) for "), header.m_Attributes.m_u16Type, header.m_Attributes.m_u16Class, u32TTL, u16RDLength); + _printRRDomain(header.m_Domain); + DEBUG_OUTPUT.printf_P(PSTR_LEA("\n")); + ); + + switch (header.m_Attributes.m_u16Type & (~0x8000)) { // Topmost bit might carry 'cache flush' flag +#ifdef MDNS_IP4_SUPPORT + case DNS_RRTYPE_A: + p_rpRRAnswer = new stcMDNS_RRAnswerA(header, u32TTL); + bResult = _readRRAnswerA(*(stcMDNS_RRAnswerA*&)p_rpRRAnswer, u16RDLength); + break; +#endif + case DNS_RRTYPE_PTR: + p_rpRRAnswer = new stcMDNS_RRAnswerPTR(header, u32TTL); + bResult = _readRRAnswerPTR(*(stcMDNS_RRAnswerPTR*&)p_rpRRAnswer, u16RDLength); + break; + case DNS_RRTYPE_TXT: + p_rpRRAnswer = new stcMDNS_RRAnswerTXT(header, u32TTL); + bResult = _readRRAnswerTXT(*(stcMDNS_RRAnswerTXT*&)p_rpRRAnswer, u16RDLength); + break; +#ifdef MDNS_IP6_SUPPORT + case DNS_RRTYPE_AAAA: + p_rpRRAnswer = new stcMDNS_RRAnswerAAAA(header, u32TTL); + bResult = _readRRAnswerAAAA(*(stcMDNS_RRAnswerAAAA*&)p_rpRRAnswer, u16RDLength); + break; +#endif + case DNS_RRTYPE_SRV: + p_rpRRAnswer = new stcMDNS_RRAnswerSRV(header, u32TTL); + bResult = _readRRAnswerSRV(*(stcMDNS_RRAnswerSRV*&)p_rpRRAnswer, u16RDLength); + break; + default: + p_rpRRAnswer = new stcMDNS_RRAnswerGeneric(header, u32TTL); + bResult = _readRRAnswerGeneric(*(stcMDNS_RRAnswerGeneric*&)p_rpRRAnswer, u16RDLength); + break; + } + DEBUG_EX_INFO( + if ((bResult) && + (p_rpRRAnswer)) { + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswer: ")); + _printRRDomain(p_rpRRAnswer->m_Header.m_Domain); + DEBUG_OUTPUT.printf_P(PSTR_LEA(" Type:0x%04X Class:0x%04X TTL:%u, RDLength:%u "), p_rpRRAnswer->m_Header.m_Attributes.m_u16Type, p_rpRRAnswer->m_Header.m_Attributes.m_u16Class, p_rpRRAnswer->m_u32TTL, u16RDLength); + switch (header.m_Attributes.m_u16Type & (~0x8000)) { // Topmost bit might carry 'cache flush' flag +#ifdef MDNS_IP4_SUPPORT + case DNS_RRTYPE_A: + DEBUG_OUTPUT.printf_P(PSTR_LEA("A IP:%s"), ((stcMDNS_RRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); + break; +#endif + case DNS_RRTYPE_PTR: + DEBUG_OUTPUT.printf_P(PSTR_LEA("PTR ")); + _printRRDomain(((stcMDNS_RRAnswerPTR*&)p_rpRRAnswer)->m_PTRDomain); + break; + case DNS_RRTYPE_TXT: { + size_t stTxtLength = ((stcMDNS_RRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_strLength(); + char* pTxts = new char[stTxtLength]; + if (pTxts) { + ((stcMDNS_RRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_str(pTxts); + DEBUG_OUTPUT.printf_P(PSTR_LEA("TXT(%u) %s"), stTxtLength, pTxts); + delete[] pTxts; + } + break; + } +#ifdef MDNS_IP6_SUPPORT + case DNS_RRTYPE_AAAA: + DEBUG_OUTPUT.printf_P(PSTR_LEA("AAAA IP:%s"), ((stcMDNS_RRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); + break; +#endif + case DNS_RRTYPE_SRV: + DEBUG_OUTPUT.printf_P(PSTR_LEA("SRV Port:%u "), ((stcMDNS_RRAnswerSRV*&)p_rpRRAnswer)->m_u16Port); + _printRRDomain(((stcMDNS_RRAnswerSRV*&)p_rpRRAnswer)->m_SRVDomain); + break; + default: + DEBUG_OUTPUT.printf_P(PSTR_LEA("generic ")); + break; + } + DEBUG_OUTPUT.printf_P(PSTR_LEA("\n")); + } + else { + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswer: FAILED to read specific answer of type 0x%04X!\n"), p_rpRRAnswer->m_Header.m_Attributes.m_u16Type); + } + ); // DEBUG_EX_INFO + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswer: FAILED!\n"));); + return bResult; +} + +#ifdef MDNS_IP4_SUPPORT + /* + * MDNSResponder::_readRRAnswerA + */ + bool MDNSResponder::_readRRAnswerA(MDNSResponder::stcMDNS_RRAnswerA& p_rRRAnswerA, + uint16_t p_u16RDLength) { + + uint32_t u32IP4Address; + bool bResult = ((MDNS_IP4_SIZE == p_u16RDLength) && + (_udpReadBuffer((unsigned char*)&u32IP4Address, MDNS_IP4_SIZE)) && + ((p_rRRAnswerA.m_IPAddress = IPAddress(u32IP4Address)))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswerA: FAILED!\n"));); + return bResult; + } +#endif + +/* + * MDNSResponder::_readRRAnswerPTR + */ +bool MDNSResponder::_readRRAnswerPTR(MDNSResponder::stcMDNS_RRAnswerPTR& p_rRRAnswerPTR, + uint16_t p_u16RDLength) { + + bool bResult = ((p_u16RDLength) && + (_readRRDomain(p_rRRAnswerPTR.m_PTRDomain))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswerPTR: FAILED!\n"));); + return bResult; +} + +/* + * MDNSResponder::_readRRAnswerTXT + * + * Read TXT items from a buffer like 4c#=15ff=20 + */ +bool MDNSResponder::_readRRAnswerTXT(MDNSResponder::stcMDNS_RRAnswerTXT& p_rRRAnswerTXT, + uint16_t p_u16RDLength) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswerTXT: RDLength:%u\n"), p_u16RDLength);); + bool bResult = true; + + p_rRRAnswerTXT.clear(); + if (p_u16RDLength) { + bResult = false; + + unsigned char* pucBuffer = new unsigned char[p_u16RDLength]; + if (pucBuffer) { + if (_udpReadBuffer(pucBuffer, p_u16RDLength)) { + bResult = true; + + const unsigned char* pucCursor = pucBuffer; + while ((pucCursor < (pucBuffer + p_u16RDLength)) && + (bResult)) { + bResult = false; + + stcMDNSServiceTxt* pTxt = 0; + unsigned char ucLength = *pucCursor++; // Length of the next txt item + if (ucLength) { + DEBUG_EX_INFO( + static char sacBuffer[64]; *sacBuffer = 0; + uint8_t u8MaxLength = ((ucLength > (sizeof(sacBuffer) - 1)) ? (sizeof(sacBuffer) - 1) : ucLength); + os_strncpy(sacBuffer, (const char*)pucCursor, u8MaxLength); sacBuffer[u8MaxLength] = 0; + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswerTXT: Item(%u): %s\n"), ucLength, sacBuffer); + ); + + unsigned char* pucEqualSign = (unsigned char*)os_strchr((const char*)pucCursor, '='); // Position of the '=' sign + unsigned char ucKeyLength; + if ((pucEqualSign) && + ((ucKeyLength = (pucEqualSign - pucCursor)))) { + unsigned char ucValueLength = (ucLength - (pucEqualSign - pucCursor + 1)); + bResult = (((pTxt = new stcMDNSServiceTxt)) && + (pTxt->setKey((const char*)pucCursor, ucKeyLength)) && + (pTxt->setValue((const char*)(pucEqualSign + 1), ucValueLength))); + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswerTXT: INVALID TXT format (No '=')!\n"));); + } + pucCursor += ucLength; + } + else { // no/zero length TXT + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswerTXT: TXT answer contains no items.\n"));); + bResult = true; + } + + if ((bResult) && + (pTxt)) { // Everythings fine so far + // Link TXT item to answer TXTs + pTxt->m_pNext = p_rRRAnswerTXT.m_Txts.m_pTxts; + p_rRRAnswerTXT.m_Txts.m_pTxts = pTxt; + } + else { // At least no TXT (migth be OK, if length was 0) OR an error + if (!bResult) { + DEBUG_EX_ERR( + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswerTXT: FAILED to read TXT item!\n")); + DEBUG_OUTPUT.printf_P(PSTR_LEA("RData dump:\n")); + _udpDump((m_pUDPContext->tell() - p_u16RDLength), p_u16RDLength); + DEBUG_OUTPUT.printf_P(PSTR_LEA("\n")); + ); + } + if (pTxt) { + delete pTxt; + pTxt = 0; + } + p_rRRAnswerTXT.clear(); + } + } // while + + DEBUG_EX_ERR( + if (!bResult) { // Some failure + DEBUG_OUTPUT.printf_P(PSTR_LEA("RData dump:\n")); + _udpDump((m_pUDPContext->tell() - p_u16RDLength), p_u16RDLength); + DEBUG_OUTPUT.printf_P(PSTR_LEA("\n")); + } + ); + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswerTXT: FAILED to read TXT content!\n"));); + } + // Clean up + delete[] pucBuffer; + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswerTXT: FAILED to alloc buffer for TXT content!\n"));); + } + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswerTXT: WARNING! No content!\n"));); + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswerTXT: FAILED!\n"));); + return bResult; +} + +#ifdef MDNS_IP6_SUPPORT + bool MDNSResponder::_readRRAnswerAAAA(MDNSResponder::stcMDNS_RRAnswerAAAA& p_rRRAnswerAAAA, + uint16_t p_u16RDLength) { + bool bResult = false; + // TODO: Implement + return bResult; + } +#endif + +/* + * MDNSResponder::_readRRAnswerSRV + */ +bool MDNSResponder::_readRRAnswerSRV(MDNSResponder::stcMDNS_RRAnswerSRV& p_rRRAnswerSRV, + uint16_t p_u16RDLength) { + + bool bResult = (((3 * sizeof(uint16_t)) < p_u16RDLength) && + (_udpRead16(p_rRRAnswerSRV.m_u16Priority)) && + (_udpRead16(p_rRRAnswerSRV.m_u16Weight)) && + (_udpRead16(p_rRRAnswerSRV.m_u16Port)) && + (_readRRDomain(p_rRRAnswerSRV.m_SRVDomain))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswerSRV: FAILED!\n"));); + return bResult; +} + +/* + * MDNSResponder::_readRRAnswerGeneric + */ +bool MDNSResponder::_readRRAnswerGeneric(MDNSResponder::stcMDNS_RRAnswerGeneric& p_rRRAnswerGeneric, + uint16_t p_u16RDLength) { + bool bResult = (0 == p_u16RDLength); + + p_rRRAnswerGeneric.clear(); + if (((p_rRRAnswerGeneric.m_u16RDLength = p_u16RDLength)) && + ((p_rRRAnswerGeneric.m_pu8RDData = new unsigned char[p_rRRAnswerGeneric.m_u16RDLength]))) { + + bResult = _udpReadBuffer(p_rRRAnswerGeneric.m_pu8RDData, p_rRRAnswerGeneric.m_u16RDLength); + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAnswerGeneric: FAILED!\n"));); + return bResult; +} + +/* + * MDNSResponder::_readRRHeader + */ +bool MDNSResponder::_readRRHeader(MDNSResponder::stcMDNS_RRHeader& p_rRRHeader) { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRHeader\n"));); + + bool bResult = ((_readRRDomain(p_rRRHeader.m_Domain)) && + (_readRRAttributes(p_rRRHeader.m_Attributes))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRHeader: FAILED!\n"));); + return bResult; +} + +/* + * MDNSResponder::_readRRDomain + * + * Reads a (maybe multilevel compressed) domain from the UDP input buffer. + * + */ +bool MDNSResponder::_readRRDomain(MDNSResponder::stcMDNS_RRDomain& p_rRRDomain) { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRDomain\n"));); + + bool bResult = ((p_rRRDomain.clear()) && + (_readRRDomain_Loop(p_rRRDomain, 0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRDomain: FAILED!\n"));); + return bResult; +} + +/* + * MDNSResponder::_readRRDomain_Loop + * + * Reads a domain from the UDP input buffer. For every compression level, the functions + * calls itself recursively. To avoid endless recursion because of malformed MDNS records, + * the maximum recursion depth is set by MDNS_DOMAIN_MAX_REDIRCTION. + * + */ +bool MDNSResponder::_readRRDomain_Loop(MDNSResponder::stcMDNS_RRDomain& p_rRRDomain, + uint8_t p_u8Depth) { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRDomain_Loop(%u)\n"), p_u8Depth);); + + bool bResult = false; + + if (MDNS_DOMAIN_MAX_REDIRCTION >= p_u8Depth) { + bResult = true; + + uint8_t u8Len = 0; + do { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRDomain_Loop(%u): Offset:%u p0:%02x\n"), p_u8Depth, m_pUDPContext->tell(), m_pUDPContext->peek());); + _udpRead8(u8Len); + + if (u8Len & MDNS_DOMAIN_COMPRESS_MARK) { + // Compressed label(s) + uint16_t u16Offset = ((u8Len & ~MDNS_DOMAIN_COMPRESS_MARK) << 8); // Implicit BE to LE conversion! + _udpRead8(u8Len); + u16Offset |= u8Len; + + if (m_pUDPContext->isValidOffset(u16Offset)) { + size_t stCurrentPosition = m_pUDPContext->tell(); // Prepare return from recursion + + //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRDomain_Loop(%u): Redirecting from %u to %u!\n"), p_u8Depth, stCurrentPosition, u16Offset);); + m_pUDPContext->seek(u16Offset); + if (_readRRDomain_Loop(p_rRRDomain, p_u8Depth + 1)) { // Do recursion + //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRDomain_Loop(%u): Succeeded to read redirected label! Returning to %u\n"), p_u8Depth, stCurrentPosition);); + m_pUDPContext->seek(stCurrentPosition); // Restore after recursion + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRDomain_Loop(%u): FAILED to read redirected label!\n"), p_u8Depth);); + bResult = false; + } + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRDomain_Loop(%u): INVALID offset in redirection!\n"), p_u8Depth);); + bResult = false; + } + break; + } + else { + // Normal (uncompressed) label (maybe '\0' only) + if (MDNS_DOMAIN_MAXLENGTH > (p_rRRDomain.m_u16NameLength + u8Len)) { + // Add length byte + p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength] = u8Len; + ++(p_rRRDomain.m_u16NameLength); + if (u8Len) { // Add name + if ((bResult = _udpReadBuffer((unsigned char*)&(p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength]), u8Len))) { + /*DEBUG_EX_INFO( + p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength + u8Len] = 0; // Closing '\0' for printing + DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRDomain_Loop(%u): Domain label (%u): %s\n"), p_u8Depth, (unsigned)(p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength - 1]), &(p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength])); + );*/ + + p_rRRDomain.m_u16NameLength += u8Len; + } + } + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRDomain_Loop(2) offset:%u p0:%x\n"), m_pUDPContext->tell(), m_pUDPContext->peek());); + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRDomain_Loop(%u): ERROR! Domain name too long (%u + %u)!\n"), p_u8Depth, p_rRRDomain.m_u16NameLength, u8Len);); + bResult = false; + break; + } + } + } while ((bResult) && + (0 != u8Len)); + } + else { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRDomain_Loop(%u): ERROR! Too many redirections!\n"), p_u8Depth);); + } + return bResult; +} + +/* + * MDNSResponder::_readRRAttributes + */ +bool MDNSResponder::_readRRAttributes(MDNSResponder::stcMDNS_RRAttributes& p_rRRAttributes) { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAttributes\n"));); + + bool bResult = ((_udpRead16(p_rRRAttributes.m_u16Type)) && + (_udpRead16(p_rRRAttributes.m_u16Class))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readRRAttributes: FAILED!\n"));); + return bResult; +} + + +/* + * DOMAIN NAMES + */ + +/* + * MDNSResponder::_buildDomainForHost + * + * Builds a MDNS host domain (eg. esp8266.local) for the given hostname. + * + */ +bool MDNSResponder::_buildDomainForHost(const char* p_pcHostname, + MDNSResponder::stcMDNS_RRDomain& p_rHostDomain) const { + + p_rHostDomain.clear(); + bool bResult = ((p_pcHostname) && + (*p_pcHostname) && + (p_rHostDomain.addLabel(p_pcHostname)) && + (p_rHostDomain.addLabel(scpcLocal)) && + (p_rHostDomain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _buildDomainForHost: FAILED!\n"));); + return bResult; +} + +/* + * MDNSResponder::_buildDomainForDNSSD + * + * Builds the '_services._dns-sd._udp.local' domain. + * Used while detecting generic service enum question (DNS-SD) and answering these questions. + * + */ +bool MDNSResponder::_buildDomainForDNSSD(MDNSResponder::stcMDNS_RRDomain& p_rDNSSDDomain) const { + + p_rDNSSDDomain.clear(); + bool bResult = ((p_rDNSSDDomain.addLabel(scpcServices, true)) && + (p_rDNSSDDomain.addLabel(scpcDNSSD, true)) && + (p_rDNSSDDomain.addLabel(scpcUDP, true)) && + (p_rDNSSDDomain.addLabel(scpcLocal)) && + (p_rDNSSDDomain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _buildDomainForDNSSD: FAILED!\n"));); + return bResult; +} + +/* + * MDNSResponder::_buildDomainForService + * + * Builds the domain for the given service (eg. _http._tcp.local or + * MyESP._http._tcp.local (if p_bIncludeName is set)). + * + */ +bool MDNSResponder::_buildDomainForService(const MDNSResponder::stcMDNSService& p_Service, + bool p_bIncludeName, + MDNSResponder::stcMDNS_RRDomain& p_rServiceDomain) const { + + p_rServiceDomain.clear(); + bool bResult = (((!p_bIncludeName) || + (p_rServiceDomain.addLabel(p_Service.m_pcName))) && + (p_rServiceDomain.addLabel(p_Service.m_pcService, true)) && + (p_rServiceDomain.addLabel(p_Service.m_pcProtocol, true)) && + (p_rServiceDomain.addLabel(scpcLocal)) && + (p_rServiceDomain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _buildDomainForService: FAILED!\n"));); + return bResult; +} + +/* + * MDNSResponder::_buildDomainForService + * + * Builds the domain for the given service properties (eg. _http._tcp.local). + * The usual prepended '_' are added, if missing in the input strings. + * + */ +bool MDNSResponder::_buildDomainForService(const char* p_pcService, + const char* p_pcProtocol, + MDNSResponder::stcMDNS_RRDomain& p_rServiceDomain) const { + + p_rServiceDomain.clear(); + bool bResult = ((p_pcService) && + (p_pcProtocol) && + (p_rServiceDomain.addLabel(p_pcService, ('_' != *p_pcService))) && + (p_rServiceDomain.addLabel(p_pcProtocol, ('_' != *p_pcProtocol))) && + (p_rServiceDomain.addLabel(scpcLocal)) && + (p_rServiceDomain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _buildDomainForService: FAILED for (%s.%s)!\n"), (p_pcService ?: "-"), (p_pcProtocol ?: "-"));); + return bResult; +} + +#ifdef MDNS_IP4_SUPPORT + /* + * MDNSResponder::_buildDomainForReverseIP4 + * + * The IP4 address is stringized by printing the four address bytes into a char buffer in reverse order + * and adding 'in-addr.arpa' (eg. 012.789.456.123.in-addr.arpa). + * Used while detecting reverse IP4 questions and answering these + */ + bool MDNSResponder::_buildDomainForReverseIP4(IPAddress p_IP4Address, + MDNSResponder::stcMDNS_RRDomain& p_rReverseIP4Domain) const { + + bool bResult = true; + + p_rReverseIP4Domain.clear(); + + char acBuffer[32]; + for (int i=MDNS_IP4_SIZE; ((bResult) && (i>=1)); --i) { + itoa(p_IP4Address[i - 1], acBuffer, 10); + bResult = p_rReverseIP4Domain.addLabel(acBuffer); + } + bResult = ((bResult) && + (p_rReverseIP4Domain.addLabel(scpcReverseIP4Domain)) && + (p_rReverseIP4Domain.addLabel(scpcReverseTopDomain)) && + (p_rReverseIP4Domain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _buildDomainForReverseIP4: FAILED!\n"));); + return bResult; + } +#endif + +#ifdef MDNS_IP6_SUPPORT + /* + * MDNSResponder::_buildDomainForReverseIP6 + * + * Used while detecting reverse IP6 questions and answering these + */ + bool MDNSResponder::_buildDomainForReverseIP6(IPAddress p_IP4Address, + MDNSResponder::stcMDNS_RRDomain& p_rReverseIP6Domain) const { + // TODO: Implement + return false; + } +#endif + + +/* + * UDP + */ + +/* + * MDNSResponder::_udpReadBuffer + */ +bool MDNSResponder::_udpReadBuffer(unsigned char* p_pBuffer, + size_t p_stLength) { + + bool bResult = ((m_pUDPContext) && + (true/*m_pUDPContext->getSize() > p_stLength*/) && + (p_pBuffer) && + (p_stLength) && + ((p_stLength == m_pUDPContext->read((char*)p_pBuffer, p_stLength)))); + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _udpReadBuffer: FAILED!\n")); }); + return bResult; +} + +/* + * MDNSResponder::_udpRead8 + */ +bool MDNSResponder::_udpRead8(uint8_t& p_ru8Value) { + + return _udpReadBuffer((unsigned char*)&p_ru8Value, sizeof(p_ru8Value)); +} + +/* + * MDNSResponder::_udpRead16 + */ +bool MDNSResponder::_udpRead16(uint16_t& p_ru16Value) { + + bool bResult = false; + + if (_udpReadBuffer((unsigned char*)&p_ru16Value, sizeof(p_ru16Value))) { + p_ru16Value = ntohs(p_ru16Value); + bResult = true; + } + return bResult; +} + +/* + * MDNSResponder::_udpRead32 + */ +bool MDNSResponder::_udpRead32(uint32_t& p_ru32Value) { + + bool bResult = false; + + if (_udpReadBuffer((unsigned char*)&p_ru32Value, sizeof(p_ru32Value))) { + p_ru32Value = ntohl(p_ru32Value); + bResult = true; + } + return bResult; +} + +/* + * MDNSResponder::_udpAppendBuffer + */ +bool MDNSResponder::_udpAppendBuffer(const unsigned char* p_pcBuffer, + size_t p_stLength) { + + bool bResult = ((m_pUDPContext) && + (p_pcBuffer) && + (p_stLength) && + (p_stLength == m_pUDPContext->append((const char*)p_pcBuffer, p_stLength))); + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _udpAppendBuffer: FAILED!\n")); }); + return bResult; +} + +/* + * MDNSResponder::_udpAppend8 + */ +bool MDNSResponder::_udpAppend8(uint8_t p_u8Value) { + + return (_udpAppendBuffer((unsigned char*)&p_u8Value, sizeof(p_u8Value))); +} + +/* + * MDNSResponder::_udpAppend16 + */ +bool MDNSResponder::_udpAppend16(uint16_t p_u16Value) { + + p_u16Value = htons(p_u16Value); + return (_udpAppendBuffer((unsigned char*)&p_u16Value, sizeof(p_u16Value))); +} + +/* + * MDNSResponder::_udpAppend32 + */ +bool MDNSResponder::_udpAppend32(uint32_t p_u32Value) { + + p_u32Value = htonl(p_u32Value); + return (_udpAppendBuffer((unsigned char*)&p_u32Value, sizeof(p_u32Value))); +} + +#ifdef DEBUG_ESP_MDNS_RESPONDER + /* + * MDNSResponder::_udpDump + */ + bool MDNSResponder::_udpDump(bool p_bMovePointer /*= false*/) { + + const uint8_t cu8BytesPerLine = 16; + + uint32_t u32StartPosition = m_pUDPContext->tell(); + DEBUG_OUTPUT.println("UDP Context Dump:"); + uint32_t u32Counter = 0; + uint8_t u8Byte = 0; + + while (_udpRead8(u8Byte)) { + DEBUG_OUTPUT.printf_P(PSTR_LEA("%02x %s"), u8Byte, ((++u32Counter % cu8BytesPerLine) ? "" : "\n")); + } + DEBUG_OUTPUT.printf_P(PSTR_LEA("%sDone: %u bytes\n"), (((u32Counter) && (u32Counter % cu8BytesPerLine)) ? "\n" : ""), u32Counter); + + if (!p_bMovePointer) { // Restore + m_pUDPContext->seek(u32StartPosition); + } + return true; + } + + /* + * MDNSResponder::_udpDump + */ + bool MDNSResponder::_udpDump(unsigned p_uOffset, + unsigned p_uLength) { + + if ((m_pUDPContext) && + (m_pUDPContext->isValidOffset(p_uOffset))) { + unsigned uCurrentPosition = m_pUDPContext->tell(); // Remember start position + + m_pUDPContext->seek(p_uOffset); + uint8_t u8Byte; + for (unsigned u=0; ((useek(uCurrentPosition); + } + return true; + } +#endif + + +/** + * READ/WRITE MDNS STRUCTS + */ + +/* + * MDNSResponder::_readMDNSMsgHeader + * + * Read a MDNS header from the UDP input buffer. + * | 8 | 8 | 8 | 8 | + * 00| Identifier | Flags & Codes | + * 01| Question count | Answer count | + * 02| NS answer count | Ad answer count | + * + * All 16-bit and 32-bit elements need to be translated form network coding to host coding (done in _udpRead16 and _udpRead32) + * In addition, bitfield memory order is undefined in C standard (GCC doesn't order them in the coded direction...), so they + * need some mapping here + */ +bool MDNSResponder::_readMDNSMsgHeader(MDNSResponder::stcMDNS_MsgHeader& p_rMsgHeader) { + + bool bResult = false; + + uint8_t u8B1; + uint8_t u8B2; + if ((_udpRead16(p_rMsgHeader.m_u16ID)) && + (_udpRead8(u8B1))&& + (_udpRead8(u8B2)) && + (_udpRead16(p_rMsgHeader.m_u16QDCount)) && + (_udpRead16(p_rMsgHeader.m_u16ANCount)) && + (_udpRead16(p_rMsgHeader.m_u16NSCount)) && + (_udpRead16(p_rMsgHeader.m_u16ARCount))) { + + p_rMsgHeader.m_1bQR = (u8B1 & 0x80); // Query/Responde flag + p_rMsgHeader.m_4bOpcode = (u8B1 & 0x78); // Operation code (0: Standard query, others ignored) + p_rMsgHeader.m_1bAA = (u8B1 & 0x04); // Authorative answer + p_rMsgHeader.m_1bTC = (u8B1 & 0x02); // Truncation flag + p_rMsgHeader.m_1bRD = (u8B1 & 0x01); // Recursion desired + + p_rMsgHeader.m_1bRA = (u8B2 & 0x80); // Recursion available + p_rMsgHeader.m_3bZ = (u8B2 & 0x70); // Zero + p_rMsgHeader.m_4bRCode = (u8B2 & 0x0F); // Response code + + /*DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readMDNSMsgHeader: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), + (unsigned)p_rMsgHeader.m_u16ID, + (unsigned)p_rMsgHeader.m_1bQR, (unsigned)p_rMsgHeader.m_4bOpcode, (unsigned)p_rMsgHeader.m_1bAA, (unsigned)p_rMsgHeader.m_1bTC, (unsigned)p_rMsgHeader.m_1bRD, + (unsigned)p_rMsgHeader.m_1bRA, (unsigned)p_rMsgHeader.m_4bRCode, + (unsigned)p_rMsgHeader.m_u16QDCount, + (unsigned)p_rMsgHeader.m_u16ANCount, + (unsigned)p_rMsgHeader.m_u16NSCount, + (unsigned)p_rMsgHeader.m_u16ARCount););*/ + bResult = true; + } + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _readMDNSMsgHeader: FAILED!\n")); } ); + return bResult; +} + +/* + * MDNSResponder::_write8 + */ +bool MDNSResponder::_write8(uint8_t p_u8Value, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + + return ((_udpAppend8(p_u8Value)) && + (p_rSendParameter.shiftOffset(sizeof(p_u8Value)))); +} + +/* + * MDNSResponder::_write16 + */ +bool MDNSResponder::_write16(uint16_t p_u16Value, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + + return ((_udpAppend16(p_u16Value)) && + (p_rSendParameter.shiftOffset(sizeof(p_u16Value)))); +} + +/* + * MDNSResponder::_write32 + */ +bool MDNSResponder::_write32(uint32_t p_u32Value, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + + return ((_udpAppend32(p_u32Value)) && + (p_rSendParameter.shiftOffset(sizeof(p_u32Value)))); +} + +/* + * MDNSResponder::_writeMDNSMsgHeader + * + * Write MDNS header to the UDP output buffer. + * + * All 16-bit and 32-bit elements need to be translated form host coding to network coding (done in _udpAppend16 and _udpAppend32) + * In addition, bitfield memory order is undefined in C standard (GCC doesn't order them in the coded direction...), so they + * need some mapping here + */ +bool MDNSResponder::_writeMDNSMsgHeader(const MDNSResponder::stcMDNS_MsgHeader& p_MsgHeader, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + /*DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSMsgHeader: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), + (unsigned)p_MsgHeader.m_u16ID, + (unsigned)p_MsgHeader.m_1bQR, (unsigned)p_MsgHeader.m_4bOpcode, (unsigned)p_MsgHeader.m_1bAA, (unsigned)p_MsgHeader.m_1bTC, (unsigned)p_MsgHeader.m_1bRD, + (unsigned)p_MsgHeader.m_1bRA, (unsigned)p_MsgHeader.m_4bRCode, + (unsigned)p_MsgHeader.m_u16QDCount, + (unsigned)p_MsgHeader.m_u16ANCount, + (unsigned)p_MsgHeader.m_u16NSCount, + (unsigned)p_MsgHeader.m_u16ARCount););*/ + + uint8_t u8B1((p_MsgHeader.m_1bQR << 7) | (p_MsgHeader.m_4bOpcode << 3) | (p_MsgHeader.m_1bAA << 2) | (p_MsgHeader.m_1bTC << 1) | (p_MsgHeader.m_1bRD)); + uint8_t u8B2((p_MsgHeader.m_1bRA << 7) | (p_MsgHeader.m_3bZ << 4) | (p_MsgHeader.m_4bRCode)); + bool bResult = ((_write16(p_MsgHeader.m_u16ID, p_rSendParameter)) && + (_write8(u8B1, p_rSendParameter)) && + (_write8(u8B2, p_rSendParameter)) && + (_write16(p_MsgHeader.m_u16QDCount, p_rSendParameter)) && + (_write16(p_MsgHeader.m_u16ANCount, p_rSendParameter)) && + (_write16(p_MsgHeader.m_u16NSCount, p_rSendParameter)) && + (_write16(p_MsgHeader.m_u16ARCount, p_rSendParameter))); + + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSMsgHeader: FAILED!\n")); }); + return bResult; +} + +/* + * MDNSResponder::_writeRRAttributes + */ +bool MDNSResponder::_writeMDNSRRAttributes(const MDNSResponder::stcMDNS_RRAttributes& p_Attributes, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + + bool bResult = ((_write16(p_Attributes.m_u16Type, p_rSendParameter)) && + (_write16(p_Attributes.m_u16Class, p_rSendParameter))); + + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSRRAttributes: FAILED!\n")); }); + return bResult; +} + +/* + * MDNSResponder::_writeMDNSRRDomain + */ +bool MDNSResponder::_writeMDNSRRDomain(const MDNSResponder::stcMDNS_RRDomain& p_Domain, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + + bool bResult = ((_udpAppendBuffer((const unsigned char*)p_Domain.m_acName, p_Domain.m_u16NameLength)) && + (p_rSendParameter.shiftOffset(p_Domain.m_u16NameLength))); + + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSRRDomain: FAILED!\n")); }); + return bResult; +} + +/* + * MDNSResponder::_writeMDNSHostDomain + * + * Write a host domain to the UDP output buffer. + * If the domain record is part of the answer, the records length is + * prepended (p_bPrependRDLength is set). + * + * A very simple form of name compression is applied here: + * If the domain is written to the UDP output buffer, the write offset is stored + * together with a domain id (the pointer) in a p_rSendParameter substructure (cache). + * If the same domain (pointer) should be written to the UDP output later again, + * the old offset is retrieved from the cache, marked as a compressed domain offset + * and written to the output buffer. + * + */ +bool MDNSResponder::_writeMDNSHostDomain(const char* p_pcHostname, + bool p_bPrependRDLength, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + + // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV' + uint16_t u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)p_pcHostname, false); + + stcMDNS_RRDomain hostDomain; + bool bResult = (u16CachedDomainOffset + // Found cached domain -> mark as compressed domain + ? ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset + ((!p_bPrependRDLength) || + (_write16(2, p_rSendParameter))) && // Length of 'Cxxx' + (_write8(((u16CachedDomainOffset >> 8) | MDNS_DOMAIN_COMPRESS_MARK), p_rSendParameter)) && // Compression mark (and offset) + (_write8((uint8_t)(u16CachedDomainOffset & 0xFF), p_rSendParameter))) + // No cached domain -> add this domain to cache and write full domain name + : ((_buildDomainForHost(p_pcHostname, hostDomain)) && // eg. esp8266.local + ((!p_bPrependRDLength) || + (_write16(hostDomain.m_u16NameLength, p_rSendParameter))) && // RDLength (if needed) + (p_rSendParameter.addDomainCacheItem((const void*)p_pcHostname, false, p_rSendParameter.m_u16Offset)) && + (_writeMDNSRRDomain(hostDomain, p_rSendParameter)))); + + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSHostDomain: FAILED!\n")); }); + return bResult; + +} + +/* + * MDNSResponder::_writeMDNSServiceDomain + * + * Write a service domain to the UDP output buffer. + * If the domain record is part of the answer, the records length is + * prepended (p_bPrependRDLength is set). + * + * A very simple form of name compression is applied here: see '_writeMDNSHostDomain' + * The cache differentiates of course between service domains which includes + * the instance name (p_bIncludeName is set) and thoose who don't. + * + */ +bool MDNSResponder::_writeMDNSServiceDomain(const MDNSResponder::stcMDNSService& p_Service, + bool p_bIncludeName, + bool p_bPrependRDLength, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + + // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV' + uint16_t u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)&p_Service, p_bIncludeName); + + stcMDNS_RRDomain serviceDomain; + bool bResult = (u16CachedDomainOffset + // Found cached domain -> mark as compressed domain + ? ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset + ((!p_bPrependRDLength) || + (_write16(2, p_rSendParameter))) && // Lenght of 'Cxxx' + (_write8(((u16CachedDomainOffset >> 8) | MDNS_DOMAIN_COMPRESS_MARK), p_rSendParameter)) && // Compression mark (and offset) + (_write8((uint8_t)(u16CachedDomainOffset & 0xFF), p_rSendParameter))) + // No cached domain -> add this domain to cache and write full domain name + : ((_buildDomainForService(p_Service, p_bIncludeName, serviceDomain)) && // eg. MyESP._http._tcp.local + ((!p_bPrependRDLength) || + (_write16(serviceDomain.m_u16NameLength, p_rSendParameter))) && // RDLength (if needed) + (p_rSendParameter.addDomainCacheItem((const void*)&p_Service, p_bIncludeName, p_rSendParameter.m_u16Offset)) && + (_writeMDNSRRDomain(serviceDomain, p_rSendParameter)))); + + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSServiceDomain: FAILED!\n")); }); + return bResult; + +} + +/* + * MDNSResponder::_writeMDNSQuestion + * + * Write a MDNS question to the UDP output buffer + * + * QNAME (host/service domain, eg. esp8266.local) + * QTYPE (16bit, eg. ANY) + * QCLASS (16bit, eg. IN) + * + */ +bool MDNSResponder::_writeMDNSQuestion(MDNSResponder::stcMDNS_RRQuestion& p_Question, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSQuestion\n"));); + + bool bResult = ((_writeMDNSRRDomain(p_Question.m_Header.m_Domain, p_rSendParameter)) && + (_writeMDNSRRAttributes(p_Question.m_Header.m_Attributes, p_rSendParameter))); + + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSQuestion: FAILED!\n")); }); + return bResult; + +} + + +#ifdef MDNS_IP4_SUPPORT + /* + * MDNSResponder::_writeMDNSAnswer_A + * + * Write a MDNS A answer to the UDP output buffer. + * + * NAME (var, host/service domain, eg. esp8266.local + * TYPE (16bit, eg. A) + * CLASS (16bit, eg. IN) + * TTL (32bit, eg. 120) + * RDLENGTH (16bit, eg 4) + * RDATA (var, eg. 123.456.789.012) + * + * eg. esp8266.local A 0x8001 120 4 123.456.789.012 + * Ref: http://www.zytrax.com/books/dns/ch8/a.html + */ + bool MDNSResponder::_writeMDNSAnswer_A(IPAddress p_IPAddress, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSAnswer_A (%s)\n"), p_IPAddress.toString().c_str());); + + stcMDNS_RRAttributes attributes(DNS_RRTYPE_A, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + const unsigned char aucIPAddress[MDNS_IP4_SIZE] = { p_IPAddress[0], p_IPAddress[1], p_IPAddress[2], p_IPAddress[3] }; + bool bResult = ((_writeMDNSHostDomain(m_pcHostname, false, p_rSendParameter)) && + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) && // TTL + (_write16(MDNS_IP4_SIZE, p_rSendParameter)) && // RDLength + (_udpAppendBuffer(aucIPAddress, MDNS_IP4_SIZE)) && // RData + (p_rSendParameter.shiftOffset(MDNS_IP4_SIZE))); + + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSAnswer_A: FAILED!\n")); }); + return bResult; + + } + + /* + * MDNSResponder::_writeMDNSAnswer_PTR_IP4 + * + * Write a MDNS reverse IP4 PTR answer to the UDP output buffer. + * See: '_writeMDNSAnswer_A' + * + * eg. 012.789.456.123.in-addr.arpa PTR 0x8001 120 15 esp8266.local + * Used while answering reverse IP4 questions + */ + bool MDNSResponder::_writeMDNSAnswer_PTR_IP4(IPAddress p_IPAddress, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSAnswer_PTR_IP4 (%s)\n"), p_IPAddress.toString().c_str());); + + stcMDNS_RRDomain reverseIP4Domain; + stcMDNS_RRAttributes attributes(DNS_RRTYPE_PTR, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + stcMDNS_RRDomain hostDomain; + bool bResult = ((_buildDomainForReverseIP4(p_IPAddress, reverseIP4Domain)) && // 012.789.456.123.in-addr.arpa + (_writeMDNSRRDomain(reverseIP4Domain, p_rSendParameter)) && + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) && // TTL + (_writeMDNSHostDomain(m_pcHostname, true, p_rSendParameter))); // RDLength & RData (host domain, eg. esp8266.local) + + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSAnswer_PTR_IP4: FAILED!\n")); }); + return bResult; + } +#endif + +/* + * MDNSResponder::_writeMDNSAnswer_PTR_TYPE + * + * Write a MDNS PTR answer to the UDP output buffer. + * See: '_writeMDNSAnswer_A' + * + * PTR all-services -> service type + * eg. _services._dns-sd._udp.local PTR 0x8001 5400 xx _http._tcp.local + * http://www.zytrax.com/books/dns/ch8/ptr.html + */ +bool MDNSResponder::_writeMDNSAnswer_PTR_TYPE(MDNSResponder::stcMDNSService& p_rService, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSAnswer_PTR_TYPE\n"));); + + stcMDNS_RRDomain dnssdDomain; + stcMDNS_RRDomain serviceDomain; + stcMDNS_RRAttributes attributes(DNS_RRTYPE_PTR, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + bool bResult = ((_buildDomainForDNSSD(dnssdDomain)) && // _services._dns-sd._udp.local + (_writeMDNSRRDomain(dnssdDomain, p_rSendParameter)) && + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL + (_writeMDNSServiceDomain(p_rService, false, true, p_rSendParameter))); // RDLength & RData (service domain, eg. _http._tcp.local) + + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSAnswer_PTR_TYPE: FAILED!\n")); }); + return bResult; +} + +/* + * MDNSResponder::_writeMDNSAnswer_PTR_NAME + * + * Write a MDNS PTR answer to the UDP output buffer. + * See: '_writeMDNSAnswer_A' + * + * PTR service type -> service name + * eg. _http.tcp.local PTR 0x8001 120 xx myESP._http._tcp.local + * http://www.zytrax.com/books/dns/ch8/ptr.html + */ +bool MDNSResponder::_writeMDNSAnswer_PTR_NAME(MDNSResponder::stcMDNSService& p_rService, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSAnswer_PTR_NAME\n"));); + + stcMDNS_RRAttributes attributes(DNS_RRTYPE_PTR, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + bool bResult = ((_writeMDNSServiceDomain(p_rService, false, false, p_rSendParameter)) && // _http._tcp.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL + (_writeMDNSServiceDomain(p_rService, true, true, p_rSendParameter))); // RDLength & RData (service domain, eg. MyESP._http._tcp.local) + + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSAnswer_PTR_NAME: FAILED!\n")); }); + return bResult; +} + + +/* + * MDNSResponder::_writeMDNSAnswer_TXT + * + * Write a MDNS TXT answer to the UDP output buffer. + * See: '_writeMDNSAnswer_A' + * + * The TXT items in the RDATA block are 'length byte encoded': [len]vardata + * + * eg. myESP._http._tcp.local TXT 0x8001 120 4 c#=1 + * http://www.zytrax.com/books/dns/ch8/txt.html + */ +bool MDNSResponder::_writeMDNSAnswer_TXT(MDNSResponder::stcMDNSService& p_rService, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSAnswer_TXT\n"));); + + bool bResult = false; + + stcMDNS_RRAttributes attributes(DNS_RRTYPE_TXT, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + + if ((_collectServiceTxts(p_rService)) && + (_writeMDNSServiceDomain(p_rService, true, false, p_rSendParameter)) && // MyESP._http._tcp.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL + (_write16(p_rService.m_Txts.length(), p_rSendParameter))) { // RDLength + + bResult = true; + // RData Txts + for (stcMDNSServiceTxt* pTxt=p_rService.m_Txts.m_pTxts; ((bResult) && (pTxt)); pTxt = pTxt->m_pNext) { + unsigned char ucLengthByte = pTxt->length(); + bResult = ((_udpAppendBuffer((unsigned char*)&ucLengthByte, sizeof(ucLengthByte))) && // Length + (p_rSendParameter.shiftOffset(sizeof(ucLengthByte))) && + ((size_t)os_strlen(pTxt->m_pcKey) == m_pUDPContext->append(pTxt->m_pcKey, os_strlen(pTxt->m_pcKey))) && // Key + (p_rSendParameter.shiftOffset((size_t)os_strlen(pTxt->m_pcKey))) && + (1 == m_pUDPContext->append("=", 1)) && // = + (p_rSendParameter.shiftOffset(1)) && + ((!pTxt->m_pcValue) || + (((size_t)os_strlen(pTxt->m_pcValue) == m_pUDPContext->append(pTxt->m_pcValue, os_strlen(pTxt->m_pcValue))) && // Value + (p_rSendParameter.shiftOffset((size_t)os_strlen(pTxt->m_pcValue)))))); + + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSAnswer_TXT: FAILED to write %sTxt %s=%s!\n"), (pTxt->m_bTemp ? "temp. " : ""), (pTxt->m_pcKey ?: "?"), (pTxt->m_pcValue ?: "?")); }); + } + } + _releaseTempServiceTxts(p_rService); + + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSAnswer_TXT: FAILED!\n")); }); + return bResult; +} + +#ifdef MDNS_IP6_SUPPORT + /* + * MDNSResponder::_writeMDNSAnswer_AAAA + * + * Write a MDNS AAAA answer to the UDP output buffer. + * See: '_writeMDNSAnswer_A' + * + * eg. esp8266.local AAAA 0x8001 120 16 xxxx::xx + * http://www.zytrax.com/books/dns/ch8/aaaa.html + */ + bool MDNSResponder::_writeMDNSAnswer_AAAA(IPAddress p_IPAddress, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSAnswer_AAAA\n"));); + + stcMDNS_RRAttributes attributes(DNS_RRTYPE_AAAA, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + bool bResult = ((_writeMDNSHostDomain(m_pcHostname, false, p_rSendParameter)) && // esp8266.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) && // TTL + (_write16(MDNS_IP6_SIZE, p_rSendParameter)) && // RDLength + (false /*TODO: IP6 version of: _udpAppendBuffer((uint32_t)p_IPAddress, MDNS_IP4_SIZE)*/)); // RData + + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSAnswer_AAAA: FAILED!\n")); }); + return bResult; + } + + /* + * MDNSResponder::_writeMDNSAnswer_PTR_IP6 + * + * Write a MDNS reverse IP6 PTR answer to the UDP output buffer. + * See: '_writeMDNSAnswer_A' + * + * eg. xxxx::xx.in6.arpa PTR 0x8001 120 15 esp8266.local + * Used while answering reverse IP6 questions + */ + bool MDNSResponder::_writeMDNSAnswer_PTR_IP6(IPAddress p_IPAddress, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSAnswer_PTR_IP6\n"));); + + stcMDNS_RRDomain reverseIP6Domain; + stcMDNS_RRAttributes attributes(DNS_RRTYPE_PTR, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + bool bResult = ((_buildDomainForReverseIP6(p_IPAddress, reverseIP6Domain)) && // xxxx::xx.ip6.arpa + (_writeMDNSRRDomain(reverseIP6Domain, p_rSendParameter)) && + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) && // TTL + (_writeMDNSHostDomain(m_pcHostname, true, p_rSendParameter))); // RDLength & RData (host domain, eg. esp8266.local) + + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSAnswer_PTR_IP6: FAILED!\n")); }); + return bResult; + } +#endif + +/* + * MDNSResponder::_writeMDNSAnswer_SRV + * + * eg. MyESP._http.tcp.local SRV 0x8001 120 0 0 60068 esp8266.local + * http://www.zytrax.com/books/dns/ch8/srv.html ???? Include instance name ???? + */ +bool MDNSResponder::_writeMDNSAnswer_SRV(MDNSResponder::stcMDNSService& p_rService, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSAnswer_SRV\n"));); + + uint16_t u16CachedDomainOffset = (p_rSendParameter.m_bLegacyQuery + ? 0 + : p_rSendParameter.findCachedDomainOffset((const void*)m_pcHostname, false)); + + stcMDNS_RRAttributes attributes(DNS_RRTYPE_SRV, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + stcMDNS_RRDomain hostDomain; + bool bResult = ((_writeMDNSServiceDomain(p_rService, true, false, p_rSendParameter)) && // MyESP._http._tcp.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL + (!u16CachedDomainOffset + // No cache for domain name (or no compression allowed) + ? ((_buildDomainForHost(m_pcHostname, hostDomain)) && + (_write16((sizeof(uint16_t /*Prio*/) + // RDLength + sizeof(uint16_t /*Weight*/) + + sizeof(uint16_t /*Port*/) + + hostDomain.m_u16NameLength), p_rSendParameter)) && // Domain length + (_write16(MDNS_SRV_PRIORITY, p_rSendParameter)) && // Priority + (_write16(MDNS_SRV_WEIGHT, p_rSendParameter)) && // Weight + (_write16(p_rService.m_u16Port, p_rSendParameter)) && // Port + (p_rSendParameter.addDomainCacheItem((const void*)m_pcHostname, false, p_rSendParameter.m_u16Offset)) && + (_writeMDNSRRDomain(hostDomain, p_rSendParameter))) // Host, eg. esp8266.local + // Cache available for domain + : ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset + (_write16((sizeof(uint16_t /*Prio*/) + // RDLength + sizeof(uint16_t /*Weight*/) + + sizeof(uint16_t /*Port*/) + + 2), p_rSendParameter)) && // Length of 'C0xx' + (_write16(MDNS_SRV_PRIORITY, p_rSendParameter)) && // Priority + (_write16(MDNS_SRV_WEIGHT, p_rSendParameter)) && // Weight + (_write16(p_rService.m_u16Port, p_rSendParameter)) && // Port + (_write8(((u16CachedDomainOffset >> 8) | MDNS_DOMAIN_COMPRESS_MARK), p_rSendParameter)) && // Compression mark (and offset) + (_write8((uint8_t)u16CachedDomainOffset, p_rSendParameter))))); // Offset + + DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR_LEA("[MDNSResponder] _writeMDNSAnswer_SRV: FAILED!\n")); }); + return bResult; +} + +} // namespace LEAmDNS + + + + + + diff --git a/libraries/ESP8266mDNS/README.rst b/libraries/ESP8266mDNS/src/README.rst similarity index 100% rename from libraries/ESP8266mDNS/README.rst rename to libraries/ESP8266mDNS/src/README.rst diff --git a/libraries/ESP8266mDNS/keywords.txt b/libraries/ESP8266mDNS/src/keywords.txt similarity index 100% rename from libraries/ESP8266mDNS/keywords.txt rename to libraries/ESP8266mDNS/src/keywords.txt