From 66e79bf81a51d479b8e5ff92a461d4a58dcba5a8 Mon Sep 17 00:00:00 2001 From: LaborEtArs Date: Mon, 2 Dec 2019 19:19:27 +0100 Subject: [PATCH 001/152] LEAmDNS2 (Host Version) --- .../ESP8266WebServer/src/detail/RequestHandler.h | 3 ++- libraries/ESP8266WiFi/src/include/UdpContext.h | 16 ++++++++++++++++ libraries/ESP8266mDNS/src/ESP8266mDNS.h | 2 ++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/libraries/ESP8266WebServer/src/detail/RequestHandler.h b/libraries/ESP8266WebServer/src/detail/RequestHandler.h index db840af2a1..7da7f4be9c 100644 --- a/libraries/ESP8266WebServer/src/detail/RequestHandler.h +++ b/libraries/ESP8266WebServer/src/detail/RequestHandler.h @@ -5,8 +5,9 @@ template class RequestHandler { - using WebServerType = ESP8266WebServerTemplate; public: + using WebServerType = ESP8266WebServerTemplate; + virtual ~RequestHandler() { } virtual bool canHandle(HTTPMethod method, String uri) { (void) method; (void) uri; return false; } virtual bool canUpload(String uri) { (void) uri; return false; } diff --git a/libraries/ESP8266WiFi/src/include/UdpContext.h b/libraries/ESP8266WiFi/src/include/UdpContext.h index 8ad074eeec..9ac77ce4da 100644 --- a/libraries/ESP8266WiFi/src/include/UdpContext.h +++ b/libraries/ESP8266WiFi/src/include/UdpContext.h @@ -167,6 +167,22 @@ class UdpContext #endif // !LWIP_IPV6 + /* + * Add a netif (by its index) as the multicast interface + */ + void setMulticastInterface(netif* p_pNetIf) + { + udp_set_multicast_netif_index(_pcb, netif_get_index(p_pNetIf)); + } + + /* + * Allow access to pcb to change eg. options + */ + udp_pcb* pcb(void) + { + return _pcb; + } + void setMulticastTTL(int ttl) { #ifdef LWIP_MAYBE_XCC diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS.h b/libraries/ESP8266mDNS/src/ESP8266mDNS.h index 66d40b1b2e..585ddfbbec 100644 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS.h +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS.h @@ -44,12 +44,14 @@ #include "ESP8266mDNS_Legacy.h" #include "LEAmDNS.h" +#include "LEAmDNS2.h" #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) // Maps the implementation to use to the global namespace type //using MDNSResponder = Legacy_MDNSResponder::MDNSResponder; //legacy using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; //new +//using MDNSResponder = esp8266::experimental::MDNSResponder; //new^2 not compatible extern MDNSResponder MDNS; #endif From af3a877e56103cd5fa01543dc971f5b06eb54150 Mon Sep 17 00:00:00 2001 From: LaborEtArs Date: Tue, 3 Dec 2019 18:18:53 +0100 Subject: [PATCH 002/152] Second try :-) --- libraries/ESP8266mDNS/src/LEAmDNS2.h | 1303 ++++++ libraries/ESP8266mDNS/src/LEAmDNS2_API.cpp | 4164 +++++++++++++++++ .../ESP8266mDNS/src/LEAmDNS2_APIHelpers.cpp | 354 ++ libraries/ESP8266mDNS/src/LEAmDNS2_Host.cpp | 1336 ++++++ libraries/ESP8266mDNS/src/LEAmDNS2_Host.hpp | 1177 +++++ .../ESP8266mDNS/src/LEAmDNS2_Host_Control.cpp | 2207 +++++++++ .../ESP8266mDNS/src/LEAmDNS2_Host_Debug.cpp | 327 ++ .../ESP8266mDNS/src/LEAmDNS2_Host_Structs.cpp | 2435 ++++++++++ .../src/LEAmDNS2_Host_Transfer.cpp | 2390 ++++++++++ libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h | 169 + libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h | 44 + 11 files changed, 15906 insertions(+) create mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2.h create mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2_API.cpp create mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2_APIHelpers.cpp create mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2_Host.cpp create mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2_Host.hpp create mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2_Host_Control.cpp create mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2_Host_Debug.cpp create mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2_Host_Structs.cpp create mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2_Host_Transfer.cpp create mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h create mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2.h b/libraries/ESP8266mDNS/src/LEAmDNS2.h new file mode 100755 index 0000000000..7aa3bb0160 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2.h @@ -0,0 +1,1303 @@ +/* + LEAmDNS2.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_hMDNSService, 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_hMDNSService, void* p_pUserdata)': + Add dynamic TXT items by calling 'MDNS.addDynamicServiceTxt(p_hMDNSService, "c#", "1");' + + In loop(): + Call 'MDNS.update();' + + + For querying services/hosts: + Static: + Call 'uint32_t u32AnswerCount = MDNS.queryService("http", "tcp");' or 'MDNS.queryHost("esp8266")'; + Iterate answers by: 'for (uint32_t u=0; u // for UdpContext.h +#include +#include +#include + +#include "lwip/netif.h" +#include "WiFiUdp.h" +#include "lwip/udp.h" +#include "debug.h" +#include "include/UdpContext.h" +#include + +#include "ESP8266WiFi.h" + + +namespace esp8266 +{ + +/** + LEAmDNS +*/ +namespace experimental +{ + +//this should be user-defined at build time +#ifndef ARDUINO_BOARD +#define ARDUINO_BOARD "generic" +#endif + +#define MDNS_IPV4_SUPPORT +#if LWIP_IPV6 +#define MDNS_IPV6_SUPPORT // If we've got IPv6 support, then we need IPv6 support :-) +#endif + + +#ifdef MDNS_IPV4_SUPPORT +#define MDNS_IPV4_SIZE 4 +#endif +#ifdef MDNS_IPV6_SUPPORT +#define MDNS_IPV6_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 5000 + +/* + DNS_RRTYPE_NSEC +*/ +#ifndef DNS_RRTYPE_NSEC +#define DNS_RRTYPE_NSEC 0x2F +#endif + + +/** + MDNSResponder +*/ +class MDNSResponder +{ +protected: +#include "LEAmDNS2_Host.hpp" + +public: + // MISC HELPERS + // Domain name helper + static bool indexDomain(char*& p_rpcDomain, + const char* p_pcDivider = "-", + const char* p_pcDefaultDomain = 0); + // Host name helper + static bool setNetIfHostName(netif* p_pNetIf, + const char* p_pcHostName); + + // INTERFACE + MDNSResponder(void); + virtual ~MDNSResponder(void); + + // HANDLEs for opaque access to responder objects + /** + hMDNSHost + */ + using hMDNSHost = const void*; + /** + hMDNSService + */ + using hMDNSService = const void*; + /** + hMDNSTxt + */ + using hMDNSTxt = const void*; + /** + hMDNSQuery + */ + using hMDNSQuery = const void*; + + // CALLBACKS + /** + MDNSHostProbeResultCallbackFn + Callback function for host domain probe results + */ + using MDNSHostProbeResultCallbackFn = std::function; + + // Create a MDNS netif responder netif by setting the default hostname + // Later call 'update()' in every 'loop' to run the process loop + // (probing, announcing, responding, ...) + // If no callback is given, the (maybe) already installed callback stays set + hMDNSHost begin(const char* p_pcHostName, + netif* p_pNetIf, + MDNSHostProbeResultCallbackFn p_fnCallback = 0); + bool begin(const char* p_pcHostName, + WiFiMode_t p_WiFiMode, + MDNSHostProbeResultCallbackFn p_fnCallback = 0); + bool begin(const char* p_pcHostName, + MDNSHostProbeResultCallbackFn p_fnCallback = 0); + + // Finish MDNS processing + bool close(const hMDNSHost p_hMDNSHost); + bool close(void); + + hMDNSHost getMDNSHost(netif* p_pNetIf) const; + hMDNSHost getMDNSHost(WiFiMode_t p_WiFiMode) const; + + // Change hostname (probing is restarted) + // If no callback is given, the (maybe) already installed callback stays set + bool setHostName(const hMDNSHost p_hMDNSHost, + const char* p_pcHostName, + MDNSHostProbeResultCallbackFn p_fnCallback = 0); + + const char* hostName(const hMDNSHost p_hMDNSHost) const; + + // Set a callback function for host probe results + // The callback function is called, when the probeing for the host domain + // succeededs or fails. + // In case of failure, the failed domain name should be changed. + bool setHostProbeResultCallback(const hMDNSHost p_hMDNSHost, + MDNSHostProbeResultCallbackFn p_fnCallback); + + // Returns 'true' is host domain probing is done + bool status(const hMDNSHost p_hMDNSHost) const; + + // Add a 'global' default' instance name for new services + bool setInstanceName(const hMDNSHost p_hMDNSHost, + const char* p_pcInstanceName); + const char* instanceName(const hMDNSHost p_hMDNSHost) const; + + /** + MDNSServiceProbeResultCallbackFn + Callback function for service domain probe results + */ + using MDNSServiceProbeResultCallbackFn = std::function; + // 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.# + // If no callback is given, the (maybe) already installed callback stays set + hMDNSService addService(const hMDNSHost p_hMDNSHost, + const char* p_pcName, + const char* p_pcServiceType, + const char* p_pcProtocol, + uint16_t p_u16Port, + MDNSServiceProbeResultCallbackFn p_fnCallback = 0); + // Removes a service from the MDNS responder + bool removeService(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService); + bool removeService(const hMDNSHost p_hMDNSHost, + const char* p_pcInstanceName, + const char* p_pcServiceType, + const char* p_pcProtocol); + hMDNSService findService(const hMDNSHost p_hMDNSHost, + const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol); + + // Change the services instance name (and restart probing). + // If no callback is given, the (maybe) already installed callback stays set + bool setServiceName(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcInstanceName, + MDNSServiceProbeResultCallbackFn p_fnCallback = 0); + const char* serviceName(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const; + const char* serviceType(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const; + const char* serviceProtocol(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const; + uint16_t servicePort(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const; + + // Set a service specific probe result callcack + bool setServiceProbeResultCallback(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + MDNSServiceProbeResultCallbackFn p_fnCallback); + + bool serviceStatus(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const; + + // Add a (static) MDNS TXT item ('key' = 'value') to the service + hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + const char* p_pcValue); + hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint32_t p_u32Value); + hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint16_t p_u16Value); + hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint8_t p_u8Value); + hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + int32_t p_i32Value); + hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + int16_t p_i16Value); + hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + int8_t p_i8Value); + + // Remove an existing (static) MDNS TXT item from the service + bool removeServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const hMDNSTxt p_hTxt); + bool removeServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey); + + /** + MDNSDynamicServiceTxtCallbackFn + Callback function for dynamic MDNS TXT items + */ + using MDNSDynamicServiceTxtCallbackFn = std::function; + bool setDynamicServiceTxtCallback(const hMDNSHost p_hMDNSHost, + MDNSDynamicServiceTxtCallbackFn p_fnCallback); + bool setDynamicServiceTxtCallback(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + MDNSDynamicServiceTxtCallbackFn p_fnCallback); + + // 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(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + const char* p_pcValue); + hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint32_t p_u32Value); + hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint16_t p_u16Value); + hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint8_t p_u8Value); + hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + int32_t p_i32Value); + hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + int16_t p_i16Value); + hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + int8_t p_i8Value); + + // QUERIES & ANSWERS + /** + clsMDNSAnswerAccessor & clsAnswerAccessorVector + */ + struct clsMDNSAnswerAccessor + { + protected: + /** + stcCompareTxtKey + */ + struct stcCompareTxtKey + { + bool operator()(char const* p_pA, char const* p_pB) const; + }; + public: + clsMDNSAnswerAccessor(const clsHost::stcQuery::stcAnswer* p_pAnswer); + ~clsMDNSAnswerAccessor(void); + + /** + clsTxtKeyValueMap + */ + using clsTxtKeyValueMap = std::map; + + bool serviceDomainAvailable(void) const; + const char* serviceDomain(void) const; + bool hostDomainAvailable(void) const; + const char* hostDomain(void) const; + bool hostPortAvailable(void) const; + uint16_t hostPort(void) const; +#ifdef MDNS_IPV4_SUPPORT + bool IPv4AddressAvailable(void) const; + std::vector IPv4Addresses(void) const; +#endif +#ifdef MDNS_IPV6_SUPPORT + bool IPv6AddressAvailable(void) const; + std::vector IPv6Addresses(void) const; +#endif + bool txtsAvailable(void) const; + const char* txts(void) const; + const clsTxtKeyValueMap& txtKeyValues(void) const; + const char* txtValue(const char* p_pcKey) const; + + size_t printTo(Print& p_Print) const; + + protected: + const clsHost::stcQuery::stcAnswer* m_pAnswer; + clsTxtKeyValueMap m_TxtKeyValueMap; + }; + using clsMDNSAnswerAccessorVector = std::vector; + using typeQueryAnswerType = clsHost::typeQueryAnswerType; + using enuQueryAnswerType = clsHost::enuQueryAnswerType; + + // STATIC QUERY + // Perform a (static) service/host query. The function returns after p_u16Timeout milliseconds + // The answers (the number of received answers is returned) can be retrieved by calling + // - answerHostName (or hostname) + // - answerIP (or IP) + // - answerPort (or port) + uint32_t queryService(const hMDNSHost p_hMDNSHost, + const char* p_pcService, + const char* p_pcProtocol, + const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME); + uint32_t queryHost(const hMDNSHost p_hMDNSHost, + const char* p_pcHostName, + const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME); + bool removeQuery(const hMDNSHost p_hMDNSHost); + bool hasQuery(const hMDNSHost p_hMDNSHost); + hMDNSQuery getQuery(const hMDNSHost p_hMDNSHost); + + clsMDNSAnswerAccessorVector answerAccessors(const hMDNSHost p_hMDNSHost); + uint32_t answerCount(const hMDNSHost p_hMDNSHost); + clsMDNSAnswerAccessor answerAccessor(const hMDNSHost p_hMDNSHost, + uint32_t p_u32AnswerIndex); + + // DYNAMIC QUERIES + /** + MDNSQueryCallbackFn + + Callback function for received answers for dynamic queries + */ + using MDNSQueryCallbackFn = std::function; // true: Answer component set, false: component deleted + + // Install a dynamic service/host query. For every received answer (part) the given callback + // function is called. The query will be updated every time, the TTL for an answer + // has timed-out. + // The answers can also be retrieved by calling + // - answerCount service/host (for host queries, this should never be >1) + // - answerServiceDomain service + // - hasAnswerHostDomain/answerHostDomain service/host + // - hasAnswerIPv4Address/answerIPv4Address service/host + // - hasAnswerIPv6Address/answerIPv6Address service/host + // - hasAnswerPort/answerPort service + // - hasAnswerTxts/answerTxts service + hMDNSQuery installServiceQuery(const hMDNSHost p_hMDNSHost, + const char* p_pcServiceType, + const char* p_pcProtocol, + MDNSQueryCallbackFn p_fnCallback); + hMDNSQuery installHostQuery(const hMDNSHost p_hMDNSHost, + const char* p_pcHostName, + MDNSQueryCallbackFn p_fnCallback); + // Remove a dynamic service query + bool removeQuery(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hMDNSQuery); + + + uint32_t answerCount(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hMDNSQuery); + clsMDNSAnswerAccessorVector answerAccessors(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hMDNSQuery); + clsMDNSAnswerAccessor answerAccessor(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hMDNSQuery, + uint32 p_u32AnswerIndex); + + /* bool hasAnswerServiceDomain(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + const char* answerServiceDomain(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + bool hasAnswerHostDomain(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + const char* answerHostDomain(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + #ifdef MDNS_IPV4_SUPPORT + bool hasAnswerIPv4Address(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + uint32_t answerIPv4AddressCount(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + IPAddress answerIPv4Address(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex); + #endif + #ifdef MDNS_IPV6_SUPPORT + bool hasAnswerIPv6Address(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + uint32_t answerIPv6AddressCount(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + IPAddress answerIPv6Address(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex); + #endif + bool hasAnswerPort(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + uint16_t answerPort(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + bool hasAnswerTxts(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + // Get the TXT items as a ';'-separated string + const char* answerTxts(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex);*/ + + // GENERAL MANAGEMENT + // Application should call this whenever AP is configured/disabled + bool notifyNetIfChange(netif* p_pNetIf); + + // 'update' should be called in every 'loop' to run the MDNS processing + bool update(const hMDNSHost p_hMDNSHost); + bool update(void); // Convenience + + // 'announce' can be called every time, the configuration of some service + // changes. Mainly, this would be changed content of TXT items. + bool announce(const hMDNSHost p_hMDNSHost); + bool announce(void); // Convenience + + // MISC + // Enable OTA update + hMDNSService enableArduino(const hMDNSHost p_hMDNSHost, + uint16_t p_u16Port, + bool p_bAuthUpload = false); + +protected: + /** Internal CLASSES & STRUCTS **/ + + // InstanceData + UdpContext* m_pUDPContext; + clsHostList m_HostList; + + // UDP CONTEXT + bool _allocUDPContext(void); + bool _releaseUDPContext(void); + bool _processUDPInput(void); + + // NETIF + clsHost* _createHost(netif* p_pNetIf); + bool _releaseHost(clsHost* p_pHost); + + const clsHost* _findHost(netif* p_pNetIf) const; + clsHost* _findHost(netif* p_pNetIf); + const clsHost* _findHost(const hMDNSHost p_hMDNSHost) const; + clsHost* _findHost(const hMDNSHost p_hMDNSHost); + + + // HANDLE HELPERS + bool _validateMDNSHostHandle(const hMDNSHost p_hMDNSHost) const; + bool _validateMDNSHostHandle(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const; + + clsHost* _NRH2Ptr(const hMDNSHost p_hMDNSHost); + const clsHost* _NRH2Ptr(const hMDNSHost p_hMDNSHost) const; + clsHost::stcService* _SH2Ptr(const hMDNSService p_hMDNSService); + const clsHost::stcService* _SH2Ptr(const hMDNSService p_hMDNSService) const; + + // INIT + clsHost* _begin(const char* p_pcHostName, + netif* p_pNetIf, + MDNSHostProbeResultCallbackFn p_fnCallback); + bool _close(clsHost& p_rHost); + + +#if not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_MDNS_RESPONDER + + const char* _DH(hMDNSHost p_hMDNSResponder = 0) const; + +#endif // not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_MDNS_RESPONDER + +}; + +#ifdef __MDNS_USE_LEGACY +/** + MDNSResponder_Legacy +*/ +class MDNSResponder_Legacy //: public MDNSResponder +{ +public: + /* INTERFACE */ + MDNSResponder_Legacy(void); + virtual ~MDNSResponder_Legacy(void); + + /** + hMDNSHost (opaque handle to access a netif binding) + */ + using hMDNSHost = const void*; + /** + hMDNSService (opaque handle to access the service) + */ + using hMDNSService = const void*; + /** + MDNSHostProbeResultCallbackFn + Callback function for host domain probe results + */ + using MDNSHostProbeResultCallbackFn = std::function; + /* LEGACY 2 */ + using MDNSServiceProbeResultCallbackFn1 = std::function; + using MDNSServiceProbeResultCallbackFn2 = std::function; + /** + hMDNSTxt (opaque handle to access the TXT items) + */ + using hMDNSTxt = const void*; + /** + MDNSDynamicServiceTxtCallbackFn + Callback function for dynamic MDNS TXT items + */ + using MDNSDynamicServiceTxtCallbackFn = std::function; + // LEGACY + using MDNSDynamicServiceTxtCallbackFn1 = std::function; + using MDNSDynamicServiceTxtCallbackFn2 = std::function; + + + hMDNSHost getNetIfBinding(netif* p_pNetIf) const; + hMDNSHost getNetIfBinding(WiFiMode_t p_WiFiMode) const; + + // Create a MDNS responder netif binding by setting the default hostname + // Later call 'update()' in every 'loop' to run the process loop + // (probing, announcing, responding, ...) + + hMDNSHost begin(const char* p_pcHostName, + netif* p_pNetIf, + MDNSHostProbeResultCallbackFn p_fnCallback = 0); + bool begin(const char* p_pcHostName, + WiFiMode_t p_WiFiMode, + MDNSHostProbeResultCallbackFn p_fnCallback = 0); + bool begin(const char* p_pcHostName, + MDNSHostProbeResultCallbackFn p_fnCallback = 0); + + /* bool begin(const String& p_strHostName) {return begin(p_strHostName.c_str());} + // for compatibility + bool begin(const char* p_pcHostName, + IPAddress p_IPAddress, // ignored + uint32_t p_u32TTL = 120); // ignored + bool begin(const String& p_strHostName, + IPAddress p_IPAddress, // ignored + uint32_t p_u32TTL = 120) { // ignored + return begin(p_strHostName.c_str(), p_IPAddress, p_u32TTL); + }*/ + // Finish MDNS processing + bool close(const hMDNSHost p_hMDNSHost); + bool close(void); + // for ESP32 compatibility + bool end(void); + + // Change hostname (probing is restarted) + bool setHostName(const hMDNSHost p_hMDNSHost, + const char* p_pcHostName); + // for compatibility... + bool setHostname(const char* p_pcHostName); + bool setHostname(String p_strHostName); + + const char* hostName(const hMDNSHost p_hMDNSHost) const; + const char* hostname(void) const; + + // Returns 'true' is host domain probing is done + bool status(const hMDNSHost p_hMDNSHost) const; + bool status(void) const; + + bool setInstanceName(const hMDNSHost p_hMDNSHost, + const char* p_pcInstanceName); + bool setInstanceName(const char* p_pcInstanceName); + // for ESP32 compatibility + bool setInstanceName(const String& p_strHostName); + + // 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 hMDNSHost p_hMDNSHost, + const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol, + uint16_t p_u16Port); + 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 hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService); + bool removeService(const hMDNSHost p_hMDNSHost, + const char* p_pcInstanceName, + const char* p_pcServiceName, + const char* p_pcProtocol); + bool removeService(const hMDNSService p_hMDNSService); + 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); + hMDNSService findService(const hMDNSHost p_hMDNSHost, + const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol); + hMDNSService findService(const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol); + + // Change the services instance name (and restart probing). + bool setServiceName(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcInstanceName); + bool setServiceName(const hMDNSService p_hMDNSService, + const char* p_pcInstanceName); + + const char* serviceName(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const; + const char* service(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const; + const char* serviceProtocol(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const; + /* LEGACY */ + const char* serviceName(const hMDNSService p_hMDNSService) const; + const char* service(const hMDNSService p_hMDNSService) const; + const char* serviceProtocol(const hMDNSService p_hMDNSService) const; + + bool serviceStatus(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const; + bool serviceStatus(const hMDNSService p_hMDNSService) const; + + // Add a (static) MDNS TXT item ('key' = 'value') to the service + hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + const char* p_pcValue); + hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint32_t p_u32Value); + hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint16_t p_u16Value); + hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint8_t p_u8Value); + hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + int32_t p_i32Value); + hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + int16_t p_i16Value); + hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + int8_t p_i8Value); + // LEGACY + hMDNSTxt addServiceTxt(const hMDNSService p_hMDNSService, + const char* p_pcKey, + const char* p_pcValue); + hMDNSTxt addServiceTxt(const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint32_t p_u32Value); + hMDNSTxt addServiceTxt(const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint16_t p_u16Value); + hMDNSTxt addServiceTxt(const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint8_t p_u8Value); + hMDNSTxt addServiceTxt(const hMDNSService p_hMDNSService, + const char* p_pcKey, + int32_t p_i32Value); + hMDNSTxt addServiceTxt(const hMDNSService p_hMDNSService, + const char* p_pcKey, + int16_t p_i16Value); + hMDNSTxt addServiceTxt(const hMDNSService p_hMDNSService, + const char* p_pcKey, + int8_t p_i8Value); + + // Remove an existing (static) MDNS TXT item from the service + bool removeServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const hMDNSTxt p_hTxt); + bool removeServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey); + bool removeServiceTxt(const hMDNSHost p_hMDNSHost, + const char* p_pcinstanceName, + const char* p_pcServiceName, + const char* p_pcProtocol, + const char* p_pcKey); + bool removeServiceTxt(const hMDNSService p_hMDNSService, + const hMDNSTxt p_hTxt); + bool removeServiceTxt(const hMDNSService p_hMDNSService, + 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); + + bool setDynamicServiceTxtCallback(const hMDNSHost p_hMDNSHost, + MDNSDynamicServiceTxtCallbackFn p_fnCallback); + bool setDynamicServiceTxtCallback(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + MDNSDynamicServiceTxtCallbackFn p_fnCallback); + + // 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(MDNSDynamicServiceTxtCallbackFn1 p_fnCallback); + bool setDynamicServiceTxtCallback(MDNSDynamicServiceTxtCallbackFn2 p_fnCallback); + + // 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_hMDNSService, + MDNSDynamicServiceTxtCallbackFn1 p_fnCallback); + bool setDynamicServiceTxtCallback(const hMDNSService p_hMDNSService, + MDNSDynamicServiceTxtCallbackFn2 p_fnCallback); + + // 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(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + const char* p_pcValue); + hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint32_t p_u32Value); + hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint16_t p_u16Value); + hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint8_t p_u8Value); + hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + int32_t p_i32Value); + hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + int16_t p_i16Value); + hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcKey, + int8_t p_i8Value); + /* LEGACY */ + hMDNSTxt addDynamicServiceTxt(const hMDNSService p_hMDNSService, + const char* p_pcKey, + const char* p_pcValue); + hMDNSTxt addDynamicServiceTxt(const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint32_t p_u32Value); + hMDNSTxt addDynamicServiceTxt(const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint16_t p_u16Value); + hMDNSTxt addDynamicServiceTxt(const hMDNSService p_hMDNSService, + const char* p_pcKey, + uint8_t p_u8Value); + hMDNSTxt addDynamicServiceTxt(const hMDNSService p_hMDNSService, + const char* p_pcKey, + int32_t p_i32Value); + hMDNSTxt addDynamicServiceTxt(const hMDNSService p_hMDNSService, + const char* p_pcKey, + int16_t p_i16Value); + hMDNSTxt addDynamicServiceTxt(const hMDNSService p_hMDNSService, + const char* p_pcKey, + int8_t p_i8Value); + + /** + hMDNSQuery (opaque handle to access dynamic service queries) + */ + using hMDNSQuery = const void*; + //using hMDNSServiceQuery = hMDNSQuery; // for compatibility with V1 + + // Perform a (static) service/host query. The function returns after p_u16Timeout milliseconds + // The answers (the number of received answers is returned) can be retrieved by calling + // - answerHostName (or hostname) + // - answerIP (or IP) + // - answerPort (or port) + uint32_t queryService(const hMDNSHost p_hMDNSHost, + const char* p_pcService, + const char* p_pcProtocol, + const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME); + uint32_t queryService(const char* p_pcService, + const char* p_pcProtocol, + const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME); + // for compatibility... + uint32_t queryService(const String& p_strService, + const String& p_strProtocol); + uint32_t queryHost(const hMDNSHost p_hMDNSHost, + const char* p_pcHostName, + const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME); + uint32_t queryHost(const char* p_pcHostName, + const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME); + bool removeQuery(const hMDNSHost p_hMDNSHost); + bool removeQuery(void); + bool hasQuery(const hMDNSHost p_hMDNSHost); + bool hasQuery(void); + hMDNSQuery getQuery(const hMDNSHost p_hMDNSHost); + hMDNSQuery getQuery(void); + + const char* answerHostName(const hMDNSHost p_hMDNSHost, + const uint32_t p_u32AnswerIndex); + const char* answerHostName(const uint32_t p_u32AnswerIndex); + // for compatibility... + String hostname(const uint32_t p_u32AnswerIndex); +#ifdef MDNS_IPV4_SUPPORT + IPAddress answerIPv4(const hMDNSHost p_hMDNSHost, + const uint32_t p_u32AnswerIndex); + IPAddress answerIPv4(const uint32_t p_u32AnswerIndex); + // for compatibility + IPAddress answerIP(const uint32_t p_u32AnswerIndex); + IPAddress IP(const uint32_t p_u32AnswerIndex); +#endif +#ifdef MDNS_IPV6_SUPPORT + IPAddress answerIPv6(const hMDNSHost p_hMDNSHost, + const uint32_t p_u32AnswerIndex); + IPAddress answerIPv6(const uint32_t p_u32AnswerIndex); +#endif + uint16_t answerPort(const hMDNSHost p_hMDNSHost, + const uint32_t p_u32AnswerIndex); + uint16_t answerPort(const uint32_t p_u32AnswerIndex); + // for compatibility + uint16_t port(const uint32_t p_u32AnswerIndex); + + /** + typeQueryAnswerType & enuQueryAnswerType + */ + using typeQueryAnswerType = uint8_t; + enum class enuQueryAnswerType : typeQueryAnswerType + { + Unknown = 0x00, + ServiceDomain = 0x01, // Service domain + HostDomain = 0x02, // Host domain + Port = 0x04, // Port + Txts = 0x08, // TXT items +#ifdef MDNS_IPV4_SUPPORT + IPv4Address = 0x10, // IPv4 address +#endif +#ifdef MDNS_IPV6_SUPPORT + IPv6Address = 0x20, // IPv6 address +#endif + }; + //using AnswerType = enuQueryAnswerType; // for compatibility with V1 + + /** + stcAnswerAccessor + */ + struct stcAnswerAccessor + { + protected: + /** + stcCompareTxtKey + */ + struct stcCompareTxtKey + { + bool operator()(char const* p_pA, char const* p_pB) const; + }; + public: + stcAnswerAccessor(MDNSResponder& p_rMDNSResponder, + hMDNSQuery p_hQuery, + uint32_t p_u32AnswerIndex); + /** + clsTxtKeyValueMap + */ + using clsTxtKeyValueMap = std::map; + + bool serviceDomainAvailable(void) const; + const char* serviceDomain(void) const; + bool hostDomainAvailable(void) const; + const char* hostDomain(void) const; + bool hostPortAvailable(void) const; + uint16_t hostPort(void) const; +#ifdef MDNS_IPV4_SUPPORT + bool IPv4AddressAvailable(void) const; + std::vector IPv4Addresses(void) const; +#endif +#ifdef MDNS_IPV6_SUPPORT + bool IPv6AddressAvailable(void) const; + std::vector IPv6Addresses(void) const; +#endif + bool txtsAvailable(void) const; + const char* txts(void) const; + const clsTxtKeyValueMap& txtKeyValues(void) const; + const char* txtValue(const char* p_pcKey) const; + + size_t printTo(Print& p_Print) const; + + protected: + MDNSResponder& m_rMDNSResponder; + hMDNSQuery m_hQuery; + uint32_t m_u32AnswerIndex; + clsTxtKeyValueMap m_TxtKeyValueMap; + }; + + /** + MDNSQueryCallbackFn + + Callback function for received answers for dynamic queries + */ + using MDNSQueryCallbackFn = std::function; // true: Answer component set, false: component deleted + // LEGACY + using MDNSQueryCallbackFn1 = std::function; // true: Answer component set, false: component deleted + using MDNSQueryCallbackFn2 = std::function; // true: Answer component set, false: component deleted + //using MDNSServiceInfo = stcAnswerAccessor; // for compatibility with V1 + + // Install a dynamic service/host query. For every received answer (part) the given callback + // function is called. The query will be updated every time, the TTL for an answer + // has timed-out. + // The answers can also be retrieved by calling + // - answerCount service/host (for host queries, this should never be >1) + // - answerServiceDomain service + // - hasAnswerHostDomain/answerHostDomain service/host + // - hasAnswerIPv4Address/answerIPv4Address service/host + // - hasAnswerIPv6Address/answerIPv6Address service/host + // - hasAnswerPort/answerPort service + // - hasAnswerTxts/answerTxts service + hMDNSQuery installServiceQuery(const hMDNSHost p_hMDNSHost, + const char* p_pcService, + const char* p_pcProtocol, + MDNSQueryCallbackFn p_fnCallback); + hMDNSQuery installHostQuery(const hMDNSHost p_hMDNSHost, + const char* p_pcHostName, + MDNSQueryCallbackFn p_fnCallback); + + hMDNSQuery installServiceQuery(const char* p_pcService, + const char* p_pcProtocol, + MDNSQueryCallbackFn1 p_fnCallback); + hMDNSQuery installServiceQuery(const char* p_pcService, + const char* p_pcProtocol, + MDNSQueryCallbackFn2 p_fnCallback); + + hMDNSQuery installHostQuery(const char* p_pcHostName, + MDNSQueryCallbackFn1 p_fnCallback); + hMDNSQuery installHostQuery(const char* p_pcHostName, + MDNSQueryCallbackFn2 p_fnCallback); + // Remove a dynamic service query + bool removeDynamicQuery(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hMDNSQuery); + bool removeDynamicQuery(const hMDNSQuery p_hQuery); + + /** + clsMDNSAnswerAccessorVector + */ + using clsMDNSAnswerAccessorVector = std::vector; + + clsMDNSAnswerAccessorVector answerAccessors(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hMDNSQuery); + clsMDNSAnswerAccessorVector answerAccessors(const hMDNSQuery p_hQuery); + + uint32_t answerCount(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hMDNSQuery); + uint32_t answerCount(const hMDNSQuery p_hQuery); + + bool hasAnswerServiceDomain(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + bool hasAnswerServiceDomain(const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + const char* answerServiceDomain(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + const char* answerServiceDomain(const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + bool hasAnswerHostDomain(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + bool hasAnswerHostDomain(const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + const char* answerHostDomain(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + const char* answerHostDomain(const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); +#ifdef MDNS_IPV4_SUPPORT + bool hasAnswerIPv4Address(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + uint32_t answerIPv4AddressCount(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + IPAddress answerIPv4Address(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex); + bool hasAnswerIPv4Address(const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + uint32_t answerIPv4AddressCount(const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + IPAddress answerIPv4Address(const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex); +#endif +#ifdef MDNS_IPV6_SUPPORT + bool hasAnswerIPv6Address(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + uint32_t answerIPv6AddressCount(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + IPAddress answerIPv6Address(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex); + bool hasAnswerIPv6Address(const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + uint32_t answerIPv6AddressCount(const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + IPAddress answerIPv6Address(const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex); +#endif + bool hasAnswerPort(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + bool hasAnswerPort(const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + uint16_t answerPort(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + /* uint16_t answerPort(const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex);*/ + bool hasAnswerTxts(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + bool hasAnswerTxts(const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + // Get the TXT items as a ';'-separated string + const char* answerTxts(const hMDNSHost p_hMDNSHost, + const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + const char* answerTxts(const hMDNSQuery p_hQuery, + const uint32_t p_u32AnswerIndex); + + // Set a callback function for host probe results + // The callback function is called, when the probeing for the host domain + // succeededs or fails. + // In case of failure, the failed domain name should be changed. + bool setHostProbeResultCallback(MDNSHostProbeResultCallbackFn p_fnCallback); + /* LEGACY 2 */ + using MDNSHostProbeResultCallbackFn1 = std::function; + using MDNSHostProbeResultCallbackFn2 = std::function; + + bool setHostProbeResultCallback(MDNSHostProbeResultCallbackFn1 p_fnCallback); + bool setHostProbeResultCallback(MDNSHostProbeResultCallbackFn2 p_fnCallback); + + /** + MDNSServiceProbeResultCallbackFn + Callback function for service domain probe results + */ + using MDNSServiceProbeResultCallbackFn = std::function; + // Set a service specific probe result callcack + bool setServiceProbeResultCallback(const hMDNSService p_hMDNSService, + MDNSServiceProbeResultCallbackFn p_fnCallback); + + bool setServiceProbeResultCallback(const hMDNSService p_hMDNSService, + MDNSServiceProbeResultCallbackFn1 p_fnCallback); + bool setServiceProbeResultCallback(const hMDNSService p_hMDNSService, + MDNSServiceProbeResultCallbackFn2 p_fnCallback); + + // Application should call this whenever AP is configured/disabled + bool notifyNetIfChange(netif* p_pNetIf); + + // 'update' should be called in every 'loop' to run the MDNS processing + bool update(const hMDNSHost p_hMDNSHost); + 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(const hMDNSHost p_hMDNSHost); + bool announce(void); + + // Enable OTA update + hMDNSService enableArduino(const hMDNSHost p_hMDNSHost, + uint16_t p_u16Port, + bool p_bAuthUpload = false); + 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); + // Host name helper + static bool setNetIfHostName(netif* p_pNetIf, + const char* p_pcHostName); +}; +#endif + +} // namespace MDNSImplementation + +} // namespace esp8266 + +#endif // LEAMDNS2_H + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_API.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_API.cpp new file mode 100755 index 0000000000..4b3d22902e --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_API.cpp @@ -0,0 +1,4164 @@ +/* + LEAmDNS2_API.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 "LEAmDNS2_Priv.h" + + +namespace +{ + +/* + strrstr (static) + + Backwards search for p_pcPattern in p_pcString + Based on: https://stackoverflow.com/a/1634398/2778898 + +*/ +const char* strrstr(const char*__restrict p_pcString, const char*__restrict p_pcPattern) +{ + const char* pcResult = 0; + + size_t stStringLength = (p_pcString ? strlen(p_pcString) : 0); + size_t stPatternLength = (p_pcPattern ? strlen(p_pcPattern) : 0); + + if ((stStringLength) && + (stPatternLength) && + (stPatternLength <= stStringLength)) + { + // Pattern is shorter or has the same length tham the string + + for (const char* s = (p_pcString + stStringLength - stPatternLength); s >= p_pcString; --s) + { + if (0 == strncmp(s, p_pcPattern, stPatternLength)) + { + pcResult = s; + break; + } + } + } + return pcResult; +} + +} // anonymous + + +namespace esp8266 +{ + +/* + LEAmDNS +*/ +namespace experimental +{ + +/** + STRINGIZE +*/ +#ifndef STRINGIZE +#define STRINGIZE(x) #x +#endif +#ifndef STRINGIZE_VALUE_OF +#define STRINGIZE_VALUE_OF(x) STRINGIZE(x) +#endif + +/** + HELPERS +*/ + +/* + MDNSResponder::indexDomain (static) + + Updates the given domain 'p_rpcHostName' by appending a delimiter and an index number. + + If the given domain already hasa numeric index (after the given delimiter), this index + incremented. If not, the delimiter and index '2' is added. + + If 'p_rpcHostName' is empty (==0), the given default name 'p_pcDefaultHostName' is used, + if no default is given, 'esp8266' is used. + +*/ +/*static*/ bool MDNSResponder::indexDomain(char*& p_rpcDomain, + const char* p_pcDivider /*= "-"*/, + const char* p_pcDefaultDomain /*= 0*/) +{ + bool bResult = false; + + // Ensure a divider exists; use '-' as default + const char* pcDivider = (p_pcDivider ? : "-"); + + if (p_rpcDomain) + { + const char* pFoundDivider = strrstr(p_rpcDomain, pcDivider); + if (pFoundDivider) // maybe already extended + { + char* pEnd = 0; + unsigned long ulIndex = strtoul((pFoundDivider + strlen(pcDivider)), &pEnd, 10); + if ((ulIndex) && + ((pEnd - p_rpcDomain) == (ptrdiff_t)strlen(p_rpcDomain)) && + (!*pEnd)) // Valid (old) index found + { + + char acIndexBuffer[16]; + sprintf(acIndexBuffer, "%lu", (++ulIndex)); + size_t stLength = ((pFoundDivider - p_rpcDomain + strlen(pcDivider)) + strlen(acIndexBuffer) + 1); + char* pNewHostName = new char[stLength]; + if (pNewHostName) + { + memcpy(pNewHostName, p_rpcDomain, (pFoundDivider - p_rpcDomain + strlen(pcDivider))); + pNewHostName[pFoundDivider - p_rpcDomain + strlen(pcDivider)] = 0; + strcat(pNewHostName, acIndexBuffer); + + delete[] p_rpcDomain; + p_rpcDomain = pNewHostName; + + bResult = true; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] indexDomain: FAILED to alloc new hostname!\n"));); + } + } + else + { + pFoundDivider = 0; // Flag the need to (base) extend the hostname + } + } + + if (!pFoundDivider) // not yet extended (or failed to increment extension) -> start indexing + { + size_t stLength = strlen(p_rpcDomain) + (strlen(pcDivider) + 1 + 1); // Name + Divider + '2' + '\0' + char* pNewHostName = new char[stLength]; + if (pNewHostName) + { + sprintf(pNewHostName, "%s%s2", p_rpcDomain, pcDivider); + + delete[] p_rpcDomain; + p_rpcDomain = pNewHostName; + + bResult = true; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] indexDomain: FAILED to alloc new hostname!\n"));); + } + } + } + else + { + // No given host domain, use base or default + const char* cpcDefaultName = (p_pcDefaultDomain ? : "esp8266"); + + size_t stLength = strlen(cpcDefaultName) + 1; // '\0' + p_rpcDomain = new char[stLength]; + if (p_rpcDomain) + { + strncpy(p_rpcDomain, cpcDefaultName, stLength); + bResult = true; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] indexDomain: FAILED to alloc new hostname!\n"));); + } + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] indexDomain: %s\n"), p_rpcDomain);); + return bResult; +} + + +/* + MDNSResponder::setStationHostName (static) + + Sets the staion hostname + +*/ +/*static*/ bool MDNSResponder::setNetIfHostName(netif* p_pNetIf, + const char* p_pcHostName) +{ + if ((p_pNetIf) && + (p_pcHostName)) + { + netif_set_hostname(p_pNetIf, p_pcHostName); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] setNetIfHostName host name: %s!\n"), p_pcHostName);); + } + return true; +} + + +/** + INTERFACE +*/ + +/** + MDNSResponder::MDNSResponder +*/ +MDNSResponder::MDNSResponder(void) + : m_pUDPContext(0) +{ + _allocUDPContext(); +} + +/* + MDNSResponder::~MDNSResponder +*/ +MDNSResponder::~MDNSResponder(void) +{ + close(); +} + +/* + MDNSResponder::begin (hostname, netif, probe_callback) +*/ +MDNSResponder::hMDNSHost MDNSResponder::begin(const char* p_pcHostName, + netif* p_pNetIf, + MDNSHostProbeResultCallbackFn p_fnCallback /*= 0*/) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s, netif: %u)\n"), _DH(), (p_pcHostName ? : "-"), (p_pNetIf ? netif_get_index(p_pNetIf) : 0));); + + return (hMDNSHost)_begin(p_pcHostName, p_pNetIf, p_fnCallback); +} + +/* + MDNSResponder::begin (hostname, probe_callback) +*/ +bool MDNSResponder::begin(const char* p_pcHostName, + MDNSHostProbeResultCallbackFn p_fnCallback /*= 0*/) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s)\n"), _DH(), (p_pcHostName ? : "_"));); + + return begin(p_pcHostName, (WiFiMode_t)wifi_get_opmode(), p_fnCallback); +} + +/* + MDNSResponder::begin (hostname, WiFiMode, probe_callback) +*/ +bool MDNSResponder::begin(const char* p_pcHostName, + WiFiMode_t p_WiFiMode, + MDNSHostProbeResultCallbackFn p_fnCallback /*= 0*/) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s, opmode: %u)\n"), _DH(), (p_pcHostName ? : "_"), (uint32_t)p_WiFiMode);); + + bool bResult = true; + + if ((bResult) && + (p_WiFiMode & WIFI_STA)) + { + bResult = (0 != _begin(p_pcHostName, netif_get_by_index(WIFI_STA), p_fnCallback)); + } + if ((bResult) && + (p_WiFiMode & WIFI_AP)) + { + bResult = (0 != _begin(p_pcHostName, netif_get_by_index(WIFI_AP), p_fnCallback)); + } + return bResult; +} + +/* + MDNSResponder::close +*/ +bool MDNSResponder::close(const MDNSResponder::hMDNSHost p_hMDNSHost) +{ + return ((_validateMDNSHostHandle(p_hMDNSHost)) && + (_close(*(clsHost*)p_hMDNSHost))); +} + +/* + MDNSResponder::close (convenience) +*/ +bool MDNSResponder::close(void) +{ + clsHostList::iterator it(m_HostList.begin()); + while (m_HostList.end() != it) + { + _close(**it++); + } + + return _releaseUDPContext(); +} + + +/* + MDNSResponder::getMDNSHost (netif) +*/ +MDNSResponder::hMDNSHost MDNSResponder::getMDNSHost(netif* p_pNetIf) const +{ + return (hMDNSHost)(p_pNetIf ? _findHost(p_pNetIf) : 0); +} + +/* + MDNSResponder::getMDNSHost (WiFiMode) +*/ +MDNSResponder::hMDNSHost MDNSResponder::getMDNSHost(WiFiMode_t p_WiFiMode) const +{ + hMDNSHost hResult = 0; + + if (WIFI_STA == p_WiFiMode) + { + hResult = getMDNSHost(netif_get_by_index(WIFI_STA)); + } + else if (WIFI_AP == p_WiFiMode) + { + hResult = getMDNSHost(netif_get_by_index(WIFI_AP)); + } + return hResult; +} + +/* + MDNSResponder::setHostName +*/ +bool MDNSResponder::setHostName(const MDNSResponder::hMDNSHost p_hMDNSHost, + const char* p_pcHostName, + MDNSHostProbeResultCallbackFn p_fnCallback /*= 0*/) +{ + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && + (_NRH2Ptr(p_hMDNSHost)->setHostName(p_pcHostName)) && + (p_fnCallback ? setHostProbeResultCallback(p_hMDNSHost, p_fnCallback) : true)); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setHostName: FAILED for '%s'!\n"), _DH(p_hMDNSHost), (p_pcHostName ? : "-"));); + return bResult; +} + +/* + MDNSResponder::hostName +*/ +const char* MDNSResponder::hostName(const MDNSResponder::hMDNSHost p_hMDNSHost) const +{ + return (_validateMDNSHostHandle(p_hMDNSHost) + ? (_NRH2Ptr(p_hMDNSHost)->hostName()) + : 0); +} + +/* + MDNSResponder::setHostProbeResultCallback +*/ +bool MDNSResponder::setHostProbeResultCallback(const hMDNSHost p_hMDNSHost, + MDNSHostProbeResultCallbackFn p_fnCallback) +{ + bool bResult = false; + if ((bResult = _validateMDNSHostHandle(p_hMDNSHost))) + { + if (p_fnCallback) + { + _NRH2Ptr(p_hMDNSHost)->m_HostProbeInformation.m_fnProbeResultCallback = [this, p_fnCallback](clsHost & p_rHost, + const char* p_pcDomainName, + bool p_bProbeResult)->void + { + p_fnCallback(*this, (hMDNSHost)&p_rHost, p_pcDomainName, p_bProbeResult); + }; + } + else + { + _NRH2Ptr(p_hMDNSHost)->m_HostProbeInformation.m_fnProbeResultCallback = 0; + } + } + return bResult; +} + +/* + MDNSResponder::status +*/ +bool MDNSResponder::status(const MDNSResponder::hMDNSHost p_hMDNSHost) const +{ + return ((_validateMDNSHostHandle(p_hMDNSHost)) && + (_NRH2Ptr(p_hMDNSHost)->probeStatus())); +} + + +/* + SERVICES +*/ + +/* + MDNSResponder::setInstanceName +*/ +bool MDNSResponder::setInstanceName(const MDNSResponder::hMDNSHost p_hMDNSHost, + const char* p_pcInstanceName) +{ + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && + (_NRH2Ptr(p_hMDNSHost)->setInstanceName(p_pcInstanceName))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setInstanceName: FAILED for '%s'!\n"), _DH(p_hMDNSHost), (p_pcInstanceName ? : "-"));); + return bResult; +} + +/* + MDNSResponder::instanceName +*/ +const char* MDNSResponder::instanceName(const MDNSResponder::hMDNSHost p_hMDNSHost) const +{ + return (_validateMDNSHostHandle(p_hMDNSHost) + ? (_NRH2Ptr(p_hMDNSHost)->instanceName()) + : 0); +} + +/* + 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 MDNSResponder::hMDNSHost p_hMDNSHost, + const char* p_pcName, + const char* p_pcServiceType, + const char* p_pcProtocol, + uint16_t p_u16Port, + MDNSServiceProbeResultCallbackFn p_fnCallback /*= 0*/) +{ + hMDNSService hService = (_validateMDNSHostHandle(p_hMDNSHost) + ? (hMDNSService)_NRH2Ptr(p_hMDNSHost)->addService(p_pcName, p_pcServiceType, p_pcProtocol, p_u16Port) + : 0); + if ((p_fnCallback) && + (hService)) + { + setServiceProbeResultCallback(p_hMDNSHost, hService, p_fnCallback); + } + DEBUG_EX_ERR(if (!hService) DEBUG_OUTPUT.printf_P(PSTR("%s addService: FAILED for '%s._%s._%s.local'!\n"), _DH(p_hMDNSHost), (p_pcName ? : "-"), (p_pcServiceType ? : "-"), (p_pcProtocol ? : "-"));); + return hService; +} + +/* + MDNSResponder::removeService + + Unanounce a service (by sending a goodbye message) and remove it + from the MDNS responder + +*/ +bool MDNSResponder::removeService(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService) +{ + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && + (_NRH2Ptr(p_hMDNSHost)->removeService(_SH2Ptr(p_hMDNSService)))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeService: FAILED!\n"), _DH(p_hMDNSHost));); + return bResult; +} + +/* + MDNSResponder::removeService +*/ +bool MDNSResponder::removeService(const MDNSResponder::hMDNSHost p_hMDNSHost, + const char* p_pcInstanceName, + const char* p_pcServiceType, + const char* p_pcProtocol) +{ + clsHost* pMDNSHost; + clsHost::stcService* pMDNSService; + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && + (pMDNSHost = (clsHost*)p_hMDNSHost) && + ((pMDNSService = _NRH2Ptr(p_hMDNSHost)->findService(p_pcInstanceName, p_pcServiceType, p_pcProtocol))) && + (_NRH2Ptr(p_hMDNSHost)->removeService(pMDNSService))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeService: FAILED for '%s._%s._%s.local'!\n"), _DH(p_hMDNSHost), (p_pcInstanceName ? : "-"), (p_pcServiceType ? : "-"), (p_pcProtocol ? : "-"));); + return bResult; +} + +/* + MDNSResponder::findService + + Find an existing service. + +*/ +MDNSResponder::hMDNSService MDNSResponder::findService(const MDNSResponder::hMDNSHost p_hMDNSHost, + const char* p_pcInstanceName, + const char* p_pcServiceType, + const char* p_pcProtocol) +{ + clsHost* pMDNSHost; + return (((_validateMDNSHostHandle(p_hMDNSHost)) && + (pMDNSHost = (clsHost*)p_hMDNSHost)) + ? _NRH2Ptr(p_hMDNSHost)->findService(p_pcInstanceName, p_pcServiceType, p_pcProtocol) + : 0); +} + +/* + MDNSResponder::setServiceName +*/ +bool MDNSResponder::setServiceName(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcInstanceName, + MDNSServiceProbeResultCallbackFn p_fnCallback /*= 0*/) +{ + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && + (_NRH2Ptr(p_hMDNSHost)->setServiceName(_SH2Ptr(p_hMDNSService), p_pcInstanceName)) && + (p_fnCallback ? setServiceProbeResultCallback(p_hMDNSHost, p_hMDNSService, p_fnCallback) : true)); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setServiceName: FAILED for '%s'!\n"), _DH(p_hMDNSHost), (p_pcInstanceName ? : "-"));); + return bResult; +} + +/* + MDNSResponder::serviceName +*/ +const char* MDNSResponder::serviceName(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const +{ + return (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) + ? (_SH2Ptr(p_hMDNSService)->m_pcName) + : 0); +} + +/* + MDNSResponder::serviceType +*/ +const char* MDNSResponder::serviceType(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const +{ + return (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) + ? (_SH2Ptr(p_hMDNSService)->m_pcServiceType) + : 0); +} + +/* + MDNSResponder::serviceProtocol +*/ +const char* MDNSResponder::serviceProtocol(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const +{ + return (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) + ? (_SH2Ptr(p_hMDNSService)->m_pcProtocol) + : 0); +} + +/* + MDNSResponder::serviceProtocol +*/ +uint16_t MDNSResponder::servicePort(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const +{ + return (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) + ? (_SH2Ptr(p_hMDNSService)->m_u16Port) + : 0); +} + +/* + 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::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + MDNSResponder::MDNSServiceProbeResultCallbackFn p_fnCallback) +{ + bool bResult = false; + if ((bResult = _validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService))) + { + if (p_fnCallback) + { + _SH2Ptr(p_hMDNSService)->m_ProbeInformation.m_fnProbeResultCallback = [this, p_fnCallback](clsHost & p_rHost, + clsHost::stcService & p_rMDNSService, + const char* p_pcDomainName, + bool p_bProbeResult)->void + { + p_fnCallback(*this, (hMDNSHost)&p_rHost, (hMDNSService)&p_rMDNSService, p_pcDomainName, p_bProbeResult); + }; + } + else + { + _SH2Ptr(p_hMDNSService)->m_ProbeInformation.m_fnProbeResultCallback = 0; + } + } + return bResult; +} + +/* + MDNSResponder::serviceStatus +*/ +bool MDNSResponder::serviceStatus(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const +{ + return ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && + (_SH2Ptr(p_hMDNSService)->probeStatus())); +} + + +/* + SERVICE TXT +*/ + +/* + MDNSResponder::addServiceTxt + + Add a static service TXT item ('Key'='Value') to a service. + +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + const char* p_pcValue) +{ + hMDNSTxt hTxt = (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) + ? (hMDNSTxt)_NRH2Ptr(p_hMDNSHost)->addServiceTxt(_SH2Ptr(p_hMDNSService), p_pcKey, p_pcValue) + : 0); + DEBUG_EX_ERR(if (!hTxt) DEBUG_OUTPUT.printf_P(PSTR("%s addServiceTxt: FAILED for '%s=%s'!\n"), _DH(p_hMDNSHost), (p_pcKey ? : "-"), (p_pcValue ? : "-"));); + return hTxt; +} + +/* + MDNSRESPONDER_xxx_TO_CHAR + Formats: http://www.cplusplus.com/reference/cstdio/printf/ +*/ +#define MDNSRESPONDER_U32_TO_CHAR(BUFFERNAME, U32VALUE) \ + char BUFFERNAME[16]; /* 32-bit max 10 digits */ \ + *BUFFERNAME = 0; \ + sprintf(BUFFERNAME, "%u", U32VALUE); +#define MDNSRESPONDER_U16_TO_CHAR(BUFFERNAME, U16VALUE) \ + char BUFFERNAME[8]; /* 16-bit max 5 digits */ \ + *BUFFERNAME = 0; \ + sprintf(BUFFERNAME, "%hu", U16VALUE); +#define MDNSRESPONDER_U8_TO_CHAR(BUFFERNAME, U8VALUE) \ + char BUFFERNAME[8]; /* 8-bit max 3 digits */ \ + *BUFFERNAME = 0; \ + sprintf(BUFFERNAME, "%hhu", U8VALUE); +#define MDNSRESPONDER_I32_TO_CHAR(BUFFERNAME, I32VALUE) \ + char BUFFERNAME[16]; /* 32-bit max 10 digits */ \ + *BUFFERNAME = 0; \ + sprintf(BUFFERNAME, "%i", I32VALUE); +#define MDNSRESPONDER_I16_TO_CHAR(BUFFERNAME, I16VALUE) \ + char BUFFERNAME[8]; /* 16-bit max 5 digits */ \ + *BUFFERNAME = 0; \ + sprintf(BUFFERNAME, "%hi", I16VALUE); +#define MDNSRESPONDER_I8_TO_CHAR(BUFFERNAME, I8VALUE) \ + char BUFFERNAME[8]; /* 8-bit max 3 digits */ \ + *BUFFERNAME = 0; \ + sprintf(BUFFERNAME, "%hhi", I8VALUE); + +/* + MDNSResponder::addServiceTxt (uint32_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint32_t p_u32Value) +{ + MDNSRESPONDER_U32_TO_CHAR(acBuffer, p_u32Value); + return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); +} + +/* + MDNSResponder::addServiceTxt (uint16_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint16_t p_u16Value) +{ + MDNSRESPONDER_U16_TO_CHAR(acBuffer, p_u16Value); + return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); +} + +/* + MDNSResponder::addServiceTxt (uint8_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint8_t p_u8Value) +{ + MDNSRESPONDER_U8_TO_CHAR(acBuffer, p_u8Value); + return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); +} + +/* + MDNSResponder::addServiceTxt (int32_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + int32_t p_i32Value) +{ + MDNSRESPONDER_I32_TO_CHAR(acBuffer, p_i32Value); + return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); +} + +/* + MDNSResponder::addServiceTxt (int16_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + int16_t p_i16Value) +{ + MDNSRESPONDER_I16_TO_CHAR(acBuffer, p_i16Value); + return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); +} + +/* + MDNSResponder::addServiceTxt (int8_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + int8_t p_i8Value) +{ + MDNSRESPONDER_I8_TO_CHAR(acBuffer, p_i8Value); + return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); +} + +/* + MDNSResponder::removeServiceTxt + + Remove a static service TXT item from a service. +*/ +bool MDNSResponder::removeServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const MDNSResponder::hMDNSTxt p_hTxt) +{ + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && + (p_hTxt) && + (_NRH2Ptr(p_hMDNSHost)->removeServiceTxt(_SH2Ptr(p_hMDNSService), (clsHost::stcServiceTxt*)p_hTxt))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeServiceTxt: FAILED!\n"), _DH(p_hMDNSHost));); + return bResult; +} + +/* + MDNSResponder::removeServiceTxt +*/ +bool MDNSResponder::removeServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey) +{ + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && + (p_pcKey) && + (_NRH2Ptr(p_hMDNSHost)->removeServiceTxt(_SH2Ptr(p_hMDNSService), _NRH2Ptr(p_hMDNSHost)->findServiceTxt(_SH2Ptr(p_hMDNSService), p_pcKey)))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeServiceTxt: FAILED!\n"), _DH(p_hMDNSHost));); + return bResult; +} + +/* + MDNSResponder::setDynamicServiceTxtCallback (binding) + + Set a netif binding specific callback for dynamic service TXT items. The callback is called, whenever + service TXT items are needed for any service on the netif binding. + +*/ +bool MDNSResponder::setDynamicServiceTxtCallback(const MDNSResponder::hMDNSHost p_hMDNSHost, + MDNSResponder::MDNSDynamicServiceTxtCallbackFn p_fnCallback) +{ + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && + ((!p_fnCallback) || + (_NRH2Ptr(p_hMDNSHost)->setDynamicServiceTxtCallback([this, p_fnCallback](clsHost & p_rHost, + clsHost::stcService & p_rMDNSService)->void + { + if (p_fnCallback) + { + p_fnCallback(*this, (hMDNSHost)&p_rHost, (hMDNSService)&p_rMDNSService); + } + })))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setDynamicServiceTxtCallback: FAILED!\n"), _DH(p_hMDNSHost));); + return bResult; +} + +/* + MDNSResponder::setDynamicServiceTxtCallback (service) + + 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(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + MDNSResponder::MDNSDynamicServiceTxtCallbackFn p_fnCallback) +{ + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && + ((!p_fnCallback) || + (_NRH2Ptr(p_hMDNSHost)->setDynamicServiceTxtCallback(_SH2Ptr(p_hMDNSService), [this, p_fnCallback](clsHost & p_rHost, + clsHost::stcService & p_rMDNSService)->void + { + if (p_fnCallback) + { + p_fnCallback(*this, (hMDNSHost)&p_rHost, (hMDNSService)&p_rMDNSService); + } + })))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setDynamicServiceTxtCallback: FAILED!\n"), _DH(p_hMDNSHost));); + return bResult; +} + +/* + MDNSResponder::addDynamicServiceTxt +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + const char* p_pcValue) +{ + hMDNSTxt hTxt = (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) + ? (hMDNSTxt)_NRH2Ptr(p_hMDNSHost)->addDynamicServiceTxt(_SH2Ptr(p_hMDNSService), p_pcKey, p_pcValue) + : 0); + DEBUG_EX_ERR(if (!hTxt) DEBUG_OUTPUT.printf_P(PSTR("%s addServiceTxt: FAILED for '%s=%s'!\n"), _DH(p_hMDNSHost), (p_pcKey ? : "-"), (p_pcValue ? : "-"));); + return hTxt; +} + +/* + MDNSResponder::addDynamicServiceTxt (uint32_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint32_t p_u32Value) +{ + MDNSRESPONDER_U32_TO_CHAR(acBuffer, p_u32Value); + return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); +} + +/* + MDNSResponder::addDynamicServiceTxt (uint16_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint16_t p_u16Value) +{ + MDNSRESPONDER_U16_TO_CHAR(acBuffer, p_u16Value); + return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); +} + +/* + MDNSResponder::addDynamicServiceTxt (uint8_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint8_t p_u8Value) +{ + MDNSRESPONDER_U8_TO_CHAR(acBuffer, p_u8Value); + return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); +} + +/* + MDNSResponder::addDynamicServiceTxt (int32_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + int32_t p_i32Value) +{ + MDNSRESPONDER_I32_TO_CHAR(acBuffer, p_i32Value); + return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); +} + +/* + MDNSResponder::addDynamicServiceTxt (int16_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + int16_t p_i16Value) +{ + MDNSRESPONDER_I16_TO_CHAR(acBuffer, p_i16Value); + return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); +} + +/* + MDNSResponder::addDynamicServiceTxt (int8_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + int8_t p_i8Value) +{ + MDNSRESPONDER_I8_TO_CHAR(acBuffer, p_i8Value); + return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); +} + + +/** + QUERIES +*/ + + +/** + MDNSResponder::clsMDNSAnswerAccessor + +*/ + +/* + MDNSResponder::clsMDNSAnswerAccessor::clsMDNSAnswerAccessor constructor +*/ +MDNSResponder::clsMDNSAnswerAccessor::clsMDNSAnswerAccessor(const MDNSResponder::clsHost::stcQuery::stcAnswer* p_pAnswer) + : m_pAnswer(p_pAnswer) +{ + if ((m_pAnswer) && + (txtsAvailable())) + { + // Prepare m_TxtKeyValueMap + for (const clsHost::stcServiceTxt* pTxt = m_pAnswer->m_Txts.m_pTxts; pTxt; pTxt = pTxt->m_pNext) + { + m_TxtKeyValueMap.emplace(std::pair(pTxt->m_pcKey, pTxt->m_pcValue)); + } + } +} + +/* + MDNSResponder::clsMDNSAnswerAccessor::~clsMDNSAnswerAccessor destructor +*/ +MDNSResponder::clsMDNSAnswerAccessor::~clsMDNSAnswerAccessor(void) +{ +} + +/** + MDNSResponder::clsMDNSAnswerAccessor::stcCompareTxtKey +*/ + +/* + MDNSResponder::clsMDNSAnswerAccessor::stcCompareTxtKey::operator() +*/ +bool MDNSResponder::clsMDNSAnswerAccessor::stcCompareTxtKey::operator()(char const* p_pA, + char const* p_pB) const +{ + return (0 > strcasecmp(p_pA, p_pB)); +} + +/* + MDNSResponder::clsMDNSAnswerAccessor::serviceDomainAvailable +*/ +bool MDNSResponder::clsMDNSAnswerAccessor::serviceDomainAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::ServiceDomain))); +} + +/* + MDNSResponder::clsMDNSAnswerAccessor::serviceDomain +*/ +const char* MDNSResponder::clsMDNSAnswerAccessor::serviceDomain(void) const +{ + return ((m_pAnswer) + ? m_pAnswer->m_ServiceDomain.c_str() + : 0); +} + +/* + MDNSResponder::clsMDNSAnswerAccessor::hostDomainAvailable +*/ +bool MDNSResponder::clsMDNSAnswerAccessor::hostDomainAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::HostDomain))); +} + +/* + MDNSResponder::clsMDNSAnswerAccessor::hostDomain +*/ +const char* MDNSResponder::clsMDNSAnswerAccessor::hostDomain(void) const +{ + return ((m_pAnswer) + ? m_pAnswer->m_HostDomain.c_str() + : 0); +} + +/* + MDNSResponder::clsMDNSAnswerAccessor::hostPortAvailable +*/ +bool MDNSResponder::clsMDNSAnswerAccessor::hostPortAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::Port))); +} + +/* + MDNSResponder::clsMDNSAnswerAccessor::hostPort +*/ +uint16_t MDNSResponder::clsMDNSAnswerAccessor::hostPort(void) const +{ + return ((m_pAnswer) + ? (m_pAnswer->m_u16Port) + : 0); +} + +#ifdef MDNS_IPV4_SUPPORT +/* + MDNSResponder::clsMDNSAnswerAccessor::IPv4AddressAvailable +*/ +bool MDNSResponder::clsMDNSAnswerAccessor::IPv4AddressAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::IPv4Address))); +} + +/* + MDNSResponder::clsMDNSAnswerAccessor::IPv4Addresses +*/ +std::vector MDNSResponder::clsMDNSAnswerAccessor::IPv4Addresses(void) const +{ + std::vector internalIP; + if ((m_pAnswer) && + (IPv4AddressAvailable())) + { + for (uint32_t u = 0; u < m_pAnswer->IPv4AddressCount(); ++u) + { + const clsHost::stcQuery::stcAnswer::stcIPAddress* pIPAddr = m_pAnswer->IPv4AddressAtIndex(u); + if (pIPAddr) + { + internalIP.emplace_back(pIPAddr->m_IPAddress); + } + } + } + return internalIP; +} +#endif + +#ifdef MDNS_IPV6_SUPPORT +/* + MDNSResponder::clsMDNSAnswerAccessor::IPv6AddressAvailable +*/ +bool MDNSResponder::clsMDNSAnswerAccessor::IPv6AddressAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::IPv6Address))); +} + +/* + MDNSResponder::clsMDNSAnswerAccessor::IPv6Addresses +*/ +std::vector MDNSResponder::clsMDNSAnswerAccessor::IPv6Addresses(void) const +{ + std::vector internalIP; + if ((m_pAnswer) && + (IPv6AddressAvailable())) + { + for (uint32_t u = 0; u < m_pAnswer->IPv6AddressCount(); ++u) + { + const clsHost::stcQuery::stcAnswer::stcIPAddress* pIPAddr = m_pAnswer->IPv6AddressAtIndex(u); + if (pIPAddr) + { + internalIP.emplace_back(pIPAddr->m_IPAddress); + } + } + } + return internalIP; +} +#endif + +/* + MDNSResponder::clsMDNSAnswerAccessor::txtsAvailable +*/ +bool MDNSResponder::clsMDNSAnswerAccessor::txtsAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::Txts))); +} + +/* + MDNSResponder::clsMDNSAnswerAccessor::txts + + Returns all TXT items for the given service as a ';'-separated string. + If not already existing; the string is alloced, filled and attached to the answer. +*/ +const char* MDNSResponder::clsMDNSAnswerAccessor::txts(void) const +{ + return ((m_pAnswer) + ? m_pAnswer->m_Txts.c_str() + : 0); +} + +/* + MDNSResponder::clsMDNSAnswerAccessor::txtKeyValues +*/ +const MDNSResponder::clsMDNSAnswerAccessor::clsTxtKeyValueMap& MDNSResponder::clsMDNSAnswerAccessor::txtKeyValues(void) const +{ + return m_TxtKeyValueMap; +} + +/* + MDNSResponder::clsMDNSAnswerAccessor::txtValue +*/ +const char* MDNSResponder::clsMDNSAnswerAccessor::txtValue(const char* p_pcKey) const +{ + char* pcResult = 0; + + if (m_pAnswer) + { + for (const clsHost::stcServiceTxt* pTxt = m_pAnswer->m_Txts.m_pTxts; pTxt; pTxt = pTxt->m_pNext) + { + if ((p_pcKey) && + (0 == strcasecmp(pTxt->m_pcKey, p_pcKey))) + { + pcResult = pTxt->m_pcValue; + break; + } + } + } + return pcResult; +} + +/* + MDNSResponder::clsMDNSAnswerAccessor::printTo + **/ +size_t MDNSResponder::clsMDNSAnswerAccessor::printTo(Print& p_Print) const +{ + size_t stLen = 0; + const char* cpcI = " * "; + const char* cpcS = " "; + + stLen += p_Print.println(" * * * * *"); + if (hostDomainAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print("Host domain: "); + stLen += p_Print.println(hostDomain()); + } +#ifdef MDNS_IPV4_SUPPORT + if (IPv4AddressAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.println("IPv4 address(es):"); + for (const IPAddress& addr : IPv4Addresses()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print(cpcS); + stLen += p_Print.println(addr); + } + } +#endif +#ifdef MDNS_IPV6_SUPPORT + if (IPv6AddressAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.println("IPv6 address(es):"); + for (const IPAddress& addr : IPv6Addresses()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print(cpcS); + stLen += p_Print.println(addr); + } + } +#endif + if (serviceDomainAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print("Service domain: "); + stLen += p_Print.println(serviceDomain()); + } + if (hostPortAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print("Host port: "); + stLen += p_Print.println(hostPort()); + } + if (txtsAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print("TXTs:"); + for (auto const& x : txtKeyValues()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print(cpcS); + stLen += p_Print.print(x.first); + stLen += p_Print.print("="); + stLen += p_Print.println(x.second); + } + } + stLen += p_Print.println(" * * * * *"); + + return stLen; +} + +/** + STATIC QUERIES +*/ + +/* + 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 MDNSResponder::hMDNSHost p_hMDNSHost, + 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("%s queryService '_%s._%s.local'\n"), _DH(p_hMDNSHost), p_pcService, p_pcProtocol);); + + uint32_t u32Result = ((_validateMDNSHostHandle(p_hMDNSHost)) + ? (_NRH2Ptr(p_hMDNSHost)->queryService(p_pcService, p_pcProtocol, p_u16Timeout)) + : 0); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryService '_%s._%s.local' returned %u hits!\n"), _DH(p_hMDNSHost), p_pcService, p_pcProtocol, u32Result);); + return u32Result; +} + +/* + MDNSResponder::queryHost + + Perform a (blocking) static host query. + The arrived answers can be queried by calling: + - answerHostName (or 'hostname') + - answerIP (or 'IP') + - answerPort (or 'port') + +*/ +uint32_t MDNSResponder::queryHost(const MDNSResponder::hMDNSHost p_hMDNSHost, + const char* p_pcHostName, + const uint16_t p_u16Timeout /*= MDNS_QUERYSERVICES_WAIT_TIME*/) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost '%s.local'\n"), _DH(p_hMDNSHost), p_pcHostName);); + + uint32_t u32Result = ((_validateMDNSHostHandle(p_hMDNSHost)) + ? (_NRH2Ptr(p_hMDNSHost)->queryHost(p_pcHostName, p_u16Timeout)) + : 0); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost '%s.local' returned %u hits!\n"), _DH(p_hMDNSHost), p_pcHostName, u32Result);); + return u32Result; +} + +/* + MDNSResponder::removeQuery + + Remove the last static query (and all answers). + +*/ +bool MDNSResponder::removeQuery(const MDNSResponder::hMDNSHost p_hMDNSHost) +{ + return ((_validateMDNSHostHandle(p_hMDNSHost)) && + (_NRH2Ptr(p_hMDNSHost)->removeQuery())); +} + +/* + MDNSResponder::hasQuery + + Return 'true', if a static query is currently installed + +*/ +bool MDNSResponder::hasQuery(const MDNSResponder::hMDNSHost p_hMDNSHost) +{ + return ((_validateMDNSHostHandle(p_hMDNSHost)) && + (0 != _NRH2Ptr(p_hMDNSHost)->hasQuery())); +} + +/* + MDNSResponder::getQuery + + Return handle to the last static query + +*/ +MDNSResponder::hMDNSQuery MDNSResponder::getQuery(const MDNSResponder::hMDNSHost p_hMDNSHost) +{ + return (_validateMDNSHostHandle(p_hMDNSHost) + ? (hMDNSQuery)_NRH2Ptr(p_hMDNSHost)->getQuery() + : 0); +} + + +/* + MDNSResponder::answerAccessors +*/ +MDNSResponder::clsMDNSAnswerAccessorVector MDNSResponder::answerAccessors(const MDNSResponder::hMDNSHost p_hMDNSHost) +{ + hMDNSQuery hLegacyQuery = getQuery(p_hMDNSHost); + return ((hLegacyQuery) + ? answerAccessors(p_hMDNSHost, hLegacyQuery) + : clsMDNSAnswerAccessorVector()); +} + +/* + MDNSResponder::answerCount +*/ +uint32_t MDNSResponder::answerCount(const MDNSResponder::hMDNSHost p_hMDNSHost) +{ + hMDNSQuery hLegacyQuery = getQuery(p_hMDNSHost); + return ((hLegacyQuery) + ? answerCount(p_hMDNSHost, hLegacyQuery) + : 0); +} + +/* + MDNSResponder::answerAccessor +*/ +MDNSResponder::clsMDNSAnswerAccessor MDNSResponder::answerAccessor(const MDNSResponder::hMDNSHost p_hMDNSHost, + uint32_t p_u32AnswerIndex) +{ + hMDNSQuery hLegacyQuery = getQuery(p_hMDNSHost); + return ((hLegacyQuery) + ? answerAccessor(p_hMDNSHost, hLegacyQuery, p_u32AnswerIndex) + : clsMDNSAnswerAccessor(0)); +} + + + +#ifdef NOTUSED + +/* + MDNSResponder::answerHostName +*/ +const char* MDNSResponder::answerHostName(const MDNSResponder::hMDNSHost p_hMDNSHost, + const uint32_t p_u32AnswerIndex) +{ + const stcQuery* pQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findLegacyQuery(*(clsHost*)p_hMDNSHost) : 0); + stcQuery::stcAnswer* pSQAnswer = (pQuery ? pQuery->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_IPV4_SUPPORT +/* + MDNSResponder::answerIPv4 +*/ +IPAddress MDNSResponder::answerIPv4(const MDNSResponder::hMDNSHost p_hMDNSHost, + const uint32_t p_u32AnswerIndex) +{ + const stcQuery* pQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findLegacyQuery(*(clsHost*)p_hMDNSHost) : 0); + const stcQuery::stcAnswer* pSQAnswer = (pQuery ? pQuery->answerAtIndex(p_u32AnswerIndex) : 0); + const stcQuery::stcAnswer::stcIPv4Address* pIPv4Address = (((pSQAnswer) && (pSQAnswer->m_pIPv4Addresses)) ? pSQAnswer->IPv4AddressAtIndex(0) : 0); + return (pIPv4Address ? pIPv4Address->m_IPAddress : IPAddress()); +} +#endif + +#ifdef MDNS_IPV6_SUPPORT +/* + MDNSResponder::answerIPv6 +*/ +IPAddress MDNSResponder::answerIPv6(const MDNSResponder::hMDNSHost p_hMDNSHost, + const uint32_t p_u32AnswerIndex) +{ + const stcQuery* pQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findLegacyQuery(*(clsHost*)p_hMDNSHost) : 0); + const stcQuery::stcAnswer* pSQAnswer = (pQuery ? pQuery->answerAtIndex(p_u32AnswerIndex) : 0); + const stcQuery::stcAnswer::stcIPv6Address* pIPv6Address = (((pSQAnswer) && (pSQAnswer->m_pIPv6Addresses)) ? pSQAnswer->IPv6AddressAtIndex(0) : 0); + return (pIPv6Address ? pIPv6Address->m_IPAddress : IPAddress()); +} +#endif + +/* + MDNSResponder::answerPort +*/ +uint16_t MDNSResponder::answerPort(const MDNSResponder::hMDNSHost p_hMDNSHost, + const uint32_t p_u32AnswerIndex) +{ + const stcQuery* pQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findLegacyQuery(*(clsHost*)p_hMDNSHost) : 0); + const stcQuery::stcAnswer* pSQAnswer = (pQuery ? pQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return (pSQAnswer ? pSQAnswer->m_u16Port : 0); +} + +#endif + + +/** + 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 + - answerIPv4Address/answerIPv6Address + - answerPort + - answerTxts + +*/ +MDNSResponder::hMDNSQuery MDNSResponder::installServiceQuery(const MDNSResponder::hMDNSHost p_hMDNSHost, + const char* p_pcService, + const char* p_pcProtocol, + MDNSResponder::MDNSQueryCallbackFn p_fnCallback) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryService '_%s._%s.local'\n"), _DH(p_hMDNSHost), p_pcService, p_pcProtocol);); + + hMDNSQuery hResult = ((_validateMDNSHostHandle(p_hMDNSHost)) + ? (_NRH2Ptr(p_hMDNSHost)->installServiceQuery(p_pcService, p_pcProtocol, [this, p_fnCallback](clsHost & p_rHost, + const clsHost::stcQuery & p_Query, + const clsHost::stcQuery::stcAnswer & p_Answer, + clsHost::typeQueryAnswerType p_QueryAnswerTypeFlags, // flags for the updated answer item + bool p_bSetContent)->void + { + if (p_fnCallback) + { + p_fnCallback(*this, (hMDNSHost)&p_rHost, (hMDNSQuery)&p_Query, clsMDNSAnswerAccessor(&p_Answer), p_QueryAnswerTypeFlags, p_bSetContent); + } + })) + : 0); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s installServiceQuery: %s for '_%s._%s.local'!\n\n"), _DH(p_hMDNSHost), (hResult ? "Succeeded" : "FAILED"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); + DEBUG_EX_ERR(if (!hResult) DEBUG_OUTPUT.printf_P(PSTR("%s installServiceQuery: FAILED for '_%s._%s.local'!\n\n"), _DH(p_hMDNSHost), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); + return hResult; +} + +/* + MDNSResponder::installHostQuery +*/ +MDNSResponder::hMDNSQuery MDNSResponder::installHostQuery(const MDNSResponder::hMDNSHost p_hMDNSHost, + const char* p_pcHostName, + MDNSResponder::MDNSQueryCallbackFn p_fnCallback) +{ + hMDNSQuery hResult = ((_validateMDNSHostHandle(p_hMDNSHost)) + ? (_NRH2Ptr(p_hMDNSHost)->installHostQuery(p_pcHostName, [this, p_fnCallback](clsHost & p_rHost, + const clsHost::stcQuery & p_Query, + const clsHost::stcQuery::stcAnswer & p_Answer, + clsHost::typeQueryAnswerType p_QueryAnswerTypeFlags, // flags for the updated answer item + bool p_bSetContent)->void + { + if (p_fnCallback) + { + p_fnCallback(*this, (hMDNSHost)&p_rHost, (hMDNSQuery)&p_Query, clsMDNSAnswerAccessor(&p_Answer), p_QueryAnswerTypeFlags, p_bSetContent); + } + })) + : 0); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s installHostQuery: %s for '%s.local'!\n\n"), _DH(p_hMDNSHost), (hResult ? "Succeeded" : "FAILED"), (p_pcHostName ? : "-"));); + DEBUG_EX_ERR(if (!hResult) DEBUG_OUTPUT.printf_P(PSTR("%s installHostQuery: FAILED for '%s.local'!\n\n"), _DH(p_hMDNSHost), (p_pcHostName ? : "-"));); + return hResult; +} + +/* + MDNSResponder::removeQuery + + Remove a dynamic query (and all collected answers) from the MDNS responder + +*/ +bool MDNSResponder::removeQuery(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery) +{ + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && + (p_hMDNSQuery) && + (_NRH2Ptr(p_hMDNSHost)->removeQuery((clsHost::stcQuery*)p_hMDNSQuery))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeQuery: FAILED!\n"), _DH(p_hMDNSHost));); + return bResult; +} + +/* + MDNSResponder::answerAccessors +*/ +MDNSResponder::clsMDNSAnswerAccessorVector MDNSResponder::answerAccessors(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery) +{ + clsMDNSAnswerAccessorVector tempVector; + for (uint32_t u = 0; u < answerCount(p_hMDNSHost, p_hMDNSQuery); ++u) + { + clsHost::stcQuery::stcAnswer* pAnswer = 0; + if ((_validateMDNSHostHandle(p_hMDNSHost)) && + (p_hMDNSQuery) && + ((pAnswer = ((clsHost::stcQuery*)p_hMDNSQuery)->answerAtIndex(u)))) + { + tempVector.emplace_back(clsMDNSAnswerAccessor(pAnswer)); + //tempVector.emplace_back(*pAnswer); + } + } + return tempVector; +} + +/* + MDNSResponder::answerCount +*/ +uint32_t MDNSResponder::answerCount(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery) +{ + _validateMDNSHostHandle(p_hMDNSHost); + return ((clsHost::stcQuery*)p_hMDNSQuery)->answerCount(); +} + +/* + MDNSResponder::answerAccessor +*/ +MDNSResponder::clsMDNSAnswerAccessor MDNSResponder::answerAccessor(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + uint32_t p_u32AnswerIndex) +{ + clsHost::stcQuery::stcAnswer* pAnswer = (((_validateMDNSHostHandle(p_hMDNSHost)) && + (p_hMDNSQuery)) + ? ((clsHost::stcQuery*)p_hMDNSQuery)->answerAtIndex(p_u32AnswerIndex) + : 0); + return MDNSResponder::clsMDNSAnswerAccessor(pAnswer); +} + +#ifdef LATER +/* + MDNSResponder::hasAnswerServiceDomain +*/ +bool MDNSResponder::hasAnswerServiceDomain(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return ((pSQAnswer) && + (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::ServiceDomain))); +} + + /* + 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::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->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::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return ((pSQAnswer) && + (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::HostDomain))); +} + + /* + 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::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->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_IPV4_SUPPORT +/* + MDNSResponder::hasAnswerIPv4Address +*/ +bool MDNSResponder::hasAnswerIPv4Address(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return ((pSQAnswer) && + (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::IPv4Address))); +} + + /* + MDNSResponder::answerIPv4AddressCount + */ + uint32_t MDNSResponder::answerIPv4AddressCount(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return (pSQAnswer ? pSQAnswer->IPv4AddressCount() : 0); +} + + /* + MDNSResponder::answerIPv4Address + */ + IPAddress MDNSResponder::answerIPv4Address(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + stcQuery::stcAnswer::stcIPv4Address* pIPv4Address = (pSQAnswer ? pSQAnswer->IPv4AddressAtIndex(p_u32AddressIndex) : 0); + return (pIPv4Address ? pIPv4Address->m_IPAddress : IPAddress()); +} +#endif + +#ifdef MDNS_IPV6_SUPPORT + /* + MDNSResponder::hasAnswerIPv6Address + */ + bool MDNSResponder::hasAnswerIPv6Address(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return ((pSQAnswer) && + (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::IPv6Address))); +} + + /* + MDNSResponder::answerIPv6AddressCount + */ + uint32_t MDNSResponder::answerIPv6AddressCount(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return (pSQAnswer ? pSQAnswer->IPv6AddressCount() : 0); +} + + /* + MDNSResponder::answerIPv6Address + */ + IPAddress MDNSResponder::answerIPv6Address(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + stcQuery::stcAnswer::stcIPv6Address* pIPv6Address = (pSQAnswer ? pSQAnswer->IPv6AddressAtIndex(p_u32AddressIndex) : 0); + return (pIPv6Address ? pIPv6Address->m_IPAddress : IPAddress()); +} +#endif + + /* + MDNSResponder::hasAnswerPort + */ + bool MDNSResponder::hasAnswerPort(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return ((pSQAnswer) && + (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::Port))); +} + + /* + MDNSResponder::answerPort + */ + uint16_t MDNSResponder::answerPort(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return (pSQAnswer ? pSQAnswer->m_u16Port : 0); +} + + /* + MDNSResponder::hasAnswerTxts + */ + bool MDNSResponder::hasAnswerTxts(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return ((pSQAnswer) && + (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::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::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->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::setHostProbeResultCallback + + Set a callback for probe results. The callback is called, when probing + for the host domain failes or succeedes. + In the case of failure, the domain name should be changed via 'setHostName' + When succeeded, the host domain will be announced by the MDNS responder. + +*/ +bool MDNSResponder::setHostProbeResultCallback(const MDNSResponder::hMDNSHost p_hMDNSHost, + MDNSResponder::MDNSHostProbeResultCallbackFn p_fnCallback) +{ + return ((_validateMDNSHostHandle(p_hMDNSHost)) && + (((clsHost*)p_hMDNSHost)->m_HostProbeInformation.m_fnProbeResultCallback = p_fnCallback, true)); +} + + +/* + MISC +*/ + +/* + MDNSResponder::notifyNetIfChange + + 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::notifyNetIfChange(netif* p_pNetIf) +{ + clsHost* pMDNSHost; + return (((pMDNSHost = _findHost(p_pNetIf))) && + (_restart(*pMDNSHost))); +} + +/* + MDNSResponder::update + + Should be called in every 'loop'. + +*/ +bool MDNSResponder::update(const MDNSResponder::hMDNSHost p_hMDNSHost) +{ + return ((_validateMDNSHostHandle(p_hMDNSHost)) && + (_process(*(clsHost*)p_hMDNSHost, true))); +} + +/* + MDNSResponder::update (convenience) +*/ +bool MDNSResponder::update(void) +{ + bool bResult = true; + for (clsHost*& pMDNSHost : m_HostList) + { + if (!_process(*it, true)) + { + bResult = false; + } + } + return bResult; +} + +/* + MDNSResponder::announce (convenience) + + Should be called, if the 'configuration' changes. Mainly this will be changes in the TXT items... +*/ +bool MDNSResponder::announce(const MDNSResponder::hMDNSHost p_hMDNSHost) +{ + return ((_validateMDNSHostHandle(p_hMDNSHost)) && + (_announce(*(clsHost*)p_hMDNSHost, true, true))); +} + +/* + MDNSResponder::announce (convenience) +*/ +bool MDNSResponder::announce(void) +{ + bool bResult = true; + for (clsHost*& pMDNSHost : m_HostList) + { + if (!_announce(*it, true, true)) + { + bResult = false; + } + } + return bResult; +} + +/* + MDNSResponder::enableArduino + + Enable the OTA update service. + +*/ +MDNSResponder::hMDNSService MDNSResponder::enableArduino(const MDNSResponder::hMDNSHost p_hMDNSHost, + uint16_t p_u16Port, + bool p_bAuthUpload /*= false*/) +{ + hMDNSService hService = addService(p_hMDNSHost, 0, "arduino", "tcp", p_u16Port); + if (hService) + { + if ((!addServiceTxt(p_hMDNSHost, hService, "tcp_check", "no")) || + (!addServiceTxt(p_hMDNSHost, hService, "ssh_upload", "no")) || + (!addServiceTxt(p_hMDNSHost, hService, "board", STRINGIZE_VALUE_OF(ARDUINO_BOARD))) || + (!addServiceTxt(p_hMDNSHost, hService, "auth_upload", (p_bAuthUpload) ? "yes" : "no"))) + { + + removeService(p_hMDNSHost, hService); + hService = 0; + } + } + return hService; +} +#endif // LATER + +#ifdef __MDNS_USE_LEGACY + +/** + INTERFACE +*/ + +/** + MDNSResponder::MDNSResponder +*/ +MDNSResponder::MDNSResponder(void) + : m_pUDPContext(0) +{ +} + +/* + MDNSResponder::~MDNSResponder +*/ +MDNSResponder::~MDNSResponder(void) +{ + close(); +} + + +/* + MDNSResponder::getHost (netif) +*/ +MDNSResponder::hMDNSHost MDNSResponder::getHost(netif* p_pNetIf) const +{ + return (hMDNSHost)(p_pNetIf ? _findHost(p_pNetIf) : 0); +} + +/* + MDNSResponder::getHost (WiFiMode) +*/ +MDNSResponder::hMDNSHost MDNSResponder::getHost(WiFiMode_t p_WiFiMode) const +{ + hMDNSHost hResult = 0; + + if (WIFI_STA == p_WiFiMode) + { + hResult = getHost(netif_get_by_index(WIFI_STA)); + } + else if (WIFI_AP == p_WiFiMode) + { + hResult = getHost(netif_get_by_index(WIFI_AP)); + } + return hResult; +} + +/* + MDNSResponder::begin (hostname, netif, probe_callback) +*/ +MDNSResponder::hMDNSHost MDNSResponder::begin(const char* p_pcHostName, + netif* p_pNetIf, + MDNSHostProbeResultCallbackFn p_fnCallback /*= 0*/) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s, netif: %u)\n"), _DH(), (p_pcHostName ? : "_"), (p_pNetIf ? netif_get_index(p_pNetIf) : 0));); + + return (hMDNSHost)_begin(p_pcHostName, p_pNetIf, p_fnCallback); +} + +/* + MDNSResponder::begin (hostname, probe_callback) +*/ +bool MDNSResponder::begin(const char* p_pcHostName, + MDNSHostProbeResultCallbackFn p_fnCallback /*= 0*/) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s)\n"), _DH(), (p_pcHostName ? : "_"));); + + return begin(p_pcHostName, (WiFiMode_t)wifi_get_opmode(), p_fnCallback); +} + +/* + MDNSResponder::begin (hostname, WiFiMode, probe_callback) +*/ +bool MDNSResponder::begin(const char* p_pcHostName, + WiFiMode_t p_WiFiMode, + MDNSHostProbeResultCallbackFn p_fnCallback /*= 0*/) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s, opmode: %u)\n"), _DH(), (p_pcHostName ? : "_"), (uint32_t)p_WiFiMode);); + + bool bResult = true; + + if ((bResult) && + (p_WiFiMode & WIFI_STA)) + { + bResult = (0 != _begin(p_pcHostName, netif_get_by_index(WIFI_STA), p_fnCallback)); + } + if ((bResult) && + (p_WiFiMode & WIFI_AP)) + { + bResult = (0 != _begin(p_pcHostName, netif_get_by_index(WIFI_AP), p_fnCallback)); + } + return bResult; +} + +/* + MDNSResponder::close +*/ +bool MDNSResponder::close(const MDNSResponder::hMDNSHost p_hMDNSHost) +{ + return ((_validateMDNSHostHandle(p_hMDNSHost)) && + (_close(*(clsHost*)p_hMDNSHost))); +} + +/* + MDNSResponder::close (convenience) +*/ +bool MDNSResponder::close(void) +{ + clsHostList::iterator it(m_HostList.begin()); + while (m_HostList.end() != it) + { + _close(**it++); + } + + _releaseUDPContext(); + + return true; +} + +/* + MDNSResponder::end (ESP32) +*/ +bool MDNSResponder::end(void) +{ + return close(); +} + +/* + MDNSResponder::setHostName +*/ +bool MDNSResponder::setHostName(const MDNSResponder::hMDNSHost p_hMDNSHost, + const char* p_pcHostName) +{ + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && + (_setHostName(*(clsHost*)p_hMDNSHost, p_pcHostName))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setHostName: FAILED for '%s'!\n"), _DH(), (p_pcHostName ? : "-"));); + return bResult; +} + +/* + MDNSResponder::setHostname (LEGACY 2) + + Set the host name in all netif bindings + +*/ +bool MDNSResponder::setHostname(const char* p_pcHostName) +{ + bool bResult = true; + for (clsHost*& pMDNSHost : m_HostList) + { + if (!setHostName((hMDNSHost)pMDNSHost, p_pcHostName)) + { + bResult = false; + } + } + return bResult; +} + +/* + MDNSResponder::setHostname (LEGACY) +*/ +bool MDNSResponder::setHostname(String p_strHostName) +{ + return setHostname(p_strHostName.c_str()); +} + +/* + MDNSResponder::hostName +*/ +const char* MDNSResponder::hostName(const MDNSResponder::hMDNSHost p_hMDNSHost) const +{ + return (_validateMDNSHostHandle(p_hMDNSHost) + ? (((clsHost*)p_hMDNSHost)->m_pcHostName) + : 0); +} + +/* + MDNSResponder::hostname (LEGACY 2) +*/ +const char* MDNSResponder::hostname(void) const +{ + return ((!m_HostList.empty()) + ? hostName((hMDNSHost)m_HostList.front()) + : 0); +} + +/* + MDNSResponder::status +*/ +bool MDNSResponder::status(const MDNSResponder::hMDNSHost p_hMDNSHost) const +{ + return ((_validateMDNSHostHandle(p_hMDNSHost)) && + (enuProbingStatus::Done == ((clsHost*)p_hMDNSHost)->m_HostProbeInformation.m_ProbingStatus)); +} + +/* + MDNSResponder::status (LEGACY 2) +*/ +bool MDNSResponder::status(void) const +{ + bool bResult = true; + for (clsHost * const& pMDNSHost : m_HostList) + { + if (!((bResult = status((hMDNSHost)pMDNSHost)))) + { + break; + } + } + return bResult; +} + + +/* + SERVICES +*/ + +/* + MDNSResponder::setInstanceName +*/ +bool MDNSResponder::setInstanceName(const MDNSResponder::hMDNSHost p_hMDNSHost, + const char* p_pcInstanceName) +{ + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && + (_setInstanceName(*(clsHost*)p_hMDNSHost, p_pcInstanceName))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setInstanceName: FAILED for '%s'!\n"), _DH(), (p_pcInstanceName ? : "-"));); + return bResult; +} + +/* + MDNSResponder::setInstanceName (LEGACY 2) + + Set the instance name in all netif bindings + +*/ +bool MDNSResponder::setInstanceName(const char* p_pcInstanceName) +{ + bool bResult = true; + for (clsHost*& pMDNSHost : m_HostList) + { + if (!setInstanceName((hMDNSHost)pMDNSHost, p_pcInstanceName)) + { + bResult = false; + } + } + return bResult; +} + +/* + MDNSResponder::setInstanceName (LEGACY 2) +*/ +bool MDNSResponder::setInstanceName(const String& p_strInstanceName) +{ + return setInstanceName(p_strInstanceName.c_str()); +} + +/* + 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 MDNSResponder::hMDNSHost p_hMDNSHost, + const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol, + uint16_t p_u16Port) +{ + hMDNSService hService = (_validateMDNSHostHandle(p_hMDNSHost) + ? (hMDNSService)_addService(*(clsHost*)p_hMDNSHost, p_pcName, p_pcService, p_pcProtocol, p_u16Port) + : 0); + DEBUG_EX_ERR(if (!hService) DEBUG_OUTPUT.printf_P(PSTR("%s addService: FAILED for '%s._%s._%s.local'!\n"), _DH((clsHost*)p_hMDNSHost), (p_pcName ? : "-"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); + return hService; +} + +/* + MDNSResponder::addService (LEGACY 2) + + Add a service to all netif bindings. + (Only) the first service (handle) is returned. + +*/ +MDNSResponder::hMDNSService MDNSResponder::addService(const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol, + uint16_t p_u16Port) +{ + hMDNSService hResult = 0; + for (clsHost*& pMDNSHost : m_HostList) + { + hMDNSService hNewService = addService((hMDNSHost)pMDNSHost, p_pcName, p_pcService, p_pcProtocol, p_u16Port); + if (!hResult) + { + hResult = hNewService; + } + } + return hResult; +} + +/* + MDNSResponder::removeService + + Unanounce a service (by sending a goodbye message) and remove it + from the MDNS responder + +*/ +bool MDNSResponder::removeService(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService) +{ + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && + (_removeService(*(clsHost*)p_hMDNSHost, *(stcService*)p_hMDNSService))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeService: FAILED!\n"), _DH((clsHost*)p_hMDNSHost));); + return bResult; +} + +/* + MDNSResponder::removeService (LEGACY 2) + + Find and remove the service from one netif binding +*/ +bool MDNSResponder::removeService(const MDNSResponder::hMDNSService p_hMDNSService) +{ + clsHost* pHost = 0; + return ((_validateMDNSHostHandle(p_hMDNSService, &pHost)) && + (removeService((hMDNSHost)pHost, p_hMDNSService))); +} + +/* + MDNSResponder::removeService +*/ +bool MDNSResponder::removeService(const MDNSResponder::hMDNSHost p_hMDNSHost, + const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol) +{ + clsHost* pMDNSHost; + stcService* pMDNSService; + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && + (pMDNSHost = (clsHost*)p_hMDNSHost) && + ((pMDNSService = _findService(*pMDNSHost, (p_pcName ? : (pMDNSHost->m_pcInstanceName ? : pMDNSHost->m_pcHostName)), p_pcService, p_pcProtocol))) && + (_removeService(*(clsHost*)p_hMDNSHost, *pMDNSService))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeService: FAILED for '%s._%s._%s.local'!\n"), _DH((clsHost*)p_hMDNSHost), (p_pcName ? : "-"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); + return bResult; +} + +/* + MDNSResponder::removeService (LEGACY 2) +*/ +bool MDNSResponder::removeService(const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol) +{ + bool bResult = true; + + for (clsHost*& pMDNSHost : m_HostList) + { + if (!removeService((hMDNSHost)pMDNSHost, p_pcName, p_pcService, p_pcProtocol)) + { + bResult = false; + } + } + return bResult; +} + +/* + MDNSResponder::addService (LEGACY) +*/ +bool MDNSResponder::addService(String p_strService, + String p_strProtocol, + uint16_t p_u16Port) +{ + return (0 != addService(0, p_strService.c_str(), p_strProtocol.c_str(), p_u16Port)); +} + +/* + MDNSResponder::findService + + Find an existing service. + +*/ +MDNSResponder::hMDNSService MDNSResponder::findService(const MDNSResponder::hMDNSHost p_hMDNSHost, + const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol) +{ + clsHost* pMDNSHost; + return (((_validateMDNSHostHandle(p_hMDNSHost)) && + (pMDNSHost = (clsHost*)p_hMDNSHost)) + ? _findService(*pMDNSHost, (p_pcName ? : (pMDNSHost->m_pcInstanceName ? : pMDNSHost->m_pcHostName)), p_pcService, p_pcProtocol) + : 0); +} + +/* + MDNSResponder::findService (LEGACY 2) + + (Only) the first service handle is returned. + +*/ +MDNSResponder::hMDNSService MDNSResponder::findService(const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol) +{ + hMDNSService hResult = 0; + for (clsHost*& pMDNSHost : m_HostList) + { + if ((hResult = findService((hMDNSHost)pMDNSHost, p_pcName, p_pcService, p_pcProtocol))) + { + break; + } + } + return hResult; +} + +/* + MDNSResponder::setServiceName +*/ +bool MDNSResponder::setServiceName(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcInstanceName) +{ + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && + (_setServiceName(*(clsHost*)p_hMDNSHost, *(stcService*)p_hMDNSService, p_pcInstanceName))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setServiceName: FAILED for '%s'!\n"), _DH((clsHost*)p_hMDNSHost), (p_pcInstanceName ? : "-"));); + return bResult; +} + +/* + MDNSResponder::setServiceName (LEGACY 2) +*/ +bool MDNSResponder::setServiceName(const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcInstanceName) +{ + clsHost* pHost = 0; + return ((_validateMDNSServiceHandle(p_hMDNSService, &pHost)) && + (setServiceName((hMDNSHost)pHost, p_hMDNSService, p_pcInstanceName))); +} + +/* + MDNSResponder::serviceName +*/ +const char* MDNSResponder::serviceName(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const +{ + return (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) + ? ((stcService*)p_hMDNSService)->m_pcName + : 0); +} + +/* + MDNSResponder::serviceName (LEGACY 2) +*/ +const char* MDNSResponder::serviceName(const hMDNSService p_hMDNSService) const +{ + clsHost* pHost = 0; + return (_validateMDNSServiceHandle(p_hMDNSService, &pHost) + ? serviceName((hMDNSHost)pHost, p_hMDNSService) + : 0); +} + +/* + MDNSResponder::service +*/ +const char* MDNSResponder::service(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const +{ + return (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) + ? ((stcService*)p_hMDNSService)->m_pcService + : 0); +} + +/* + MDNSResponder::service (LEGACY 2) +*/ +const char* MDNSResponder::service(const hMDNSService p_hMDNSService) const +{ + clsHost* pHost = 0; + return (_validateMDNSServiceHandle(p_hMDNSService, &pHost) + ? service((hMDNSHost)pHost, p_hMDNSService) + : 0); +} + +/* + MDNSResponder::serviceProtocol +*/ +const char* MDNSResponder::serviceProtocol(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const +{ + return (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) + ? ((stcService*)p_hMDNSService)->m_pcProtocol + : 0); +} + +/* + MDNSResponder::serviceProtocol (LEGACY) +*/ +const char* MDNSResponder::serviceProtocol(const hMDNSService p_hMDNSService) const +{ + clsHost* pHost = 0; + return (_validateMDNSServiceHandle(p_hMDNSService, &pHost) + ? serviceProtocol((hMDNSHost)pHost, p_hMDNSService) + : 0); +} + +/* + MDNSResponder::serviceStatus +*/ +bool MDNSResponder::serviceStatus(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const +{ + return ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && + (enuProbingStatus::Done == ((stcService*)p_hMDNSService)->m_ProbeInformation.m_ProbingStatus)); +} + +/* + MDNSResponder::serviceStatus (LEGACY 2) + + Returns 'true' if probing for the service 'hMDNSService' is done + +*/ +bool MDNSResponder::serviceStatus(const hMDNSService p_hMDNSService) const +{ + clsHost* pHost = 0; + return (_validateMDNSServiceHandle(p_hMDNSService, &pHost) + ? serviceStatus((hMDNSHost)pHost, p_hMDNSService) + : false); +} + + +/* + SERVICE TXT +*/ + +/* + MDNSResponder::addServiceTxt + + Add a static service TXT item ('Key'='Value') to a service. + +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + const char* p_pcValue) +{ + hMDNSTxt hTxt = (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) + ? (hMDNSTxt)_addServiceTxt((clsHost*)p_hMDNSHost, (stcService*)p_hMDNSService, p_pcKey, p_pcValue, false) + : 0); + DEBUG_EX_ERR(if (!hTxt) DEBUG_OUTPUT.printf_P(PSTR("%s addServiceTxt: FAILED for '%s=%s'!\n"), _DH(), (p_pcKey ? : "-"), (p_pcValue ? : "-"));); + return hTxt; +} + +/* + MDNSResponder::addServiceTxt (LEGACY 2) + + Add a static service TXT item ('Key'='Value') to a service. + +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + const char* p_pcValue) +{ + clsHost* pHost = 0; + return (_validateMDNSHostHandle(p_hMDNSService, &pHost) + ? addServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_pcValue) + : 0); +} + +/* + MDNSRESPONDER_xxx_TO_CHAR + Formats: http://www.cplusplus.com/reference/cstdio/printf/ +*/ +#define MDNSRESPONDER_U32_TO_CHAR(BUFFERNAME, U32VALUE) \ + char BUFFERNAME[16]; /* 32-bit max 10 digits */ \ + *BUFFERNAME = 0; \ + sprintf(BUFFERNAME, "%u", U32VALUE); +#define MDNSRESPONDER_U16_TO_CHAR(BUFFERNAME, U16VALUE) \ + char BUFFERNAME[8]; /* 16-bit max 5 digits */ \ + *BUFFERNAME = 0; \ + sprintf(BUFFERNAME, "%hu", U16VALUE); +#define MDNSRESPONDER_U8_TO_CHAR(BUFFERNAME, U8VALUE) \ + char BUFFERNAME[8]; /* 8-bit max 3 digits */ \ + *BUFFERNAME = 0; \ + sprintf(BUFFERNAME, "%hhu", U8VALUE); +#define MDNSRESPONDER_I32_TO_CHAR(BUFFERNAME, I32VALUE) \ + char BUFFERNAME[16]; /* 32-bit max 10 digits */ \ + *BUFFERNAME = 0; \ + sprintf(BUFFERNAME, "%i", I32VALUE); +#define MDNSRESPONDER_I16_TO_CHAR(BUFFERNAME, I16VALUE) \ + char BUFFERNAME[8]; /* 16-bit max 5 digits */ \ + *BUFFERNAME = 0; \ + sprintf(BUFFERNAME, "%hi", I16VALUE); +#define MDNSRESPONDER_I8_TO_CHAR(BUFFERNAME, I8VALUE) \ + char BUFFERNAME[8]; /* 8-bit max 3 digits */ \ + *BUFFERNAME = 0; \ + sprintf(BUFFERNAME, "%hhi", I8VALUE); + + +/* + MDNSResponder::addServiceTxt (uint32_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint32_t p_u32Value) +{ + MDNSRESPONDER_U32_TO_CHAR(acBuffer, p_u32Value); + return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): + } + + /* + MDNSResponder::addServiceTxt (uint32_t) (LEGACY 2) + */ + MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint32_t p_u32Value) +{ + clsHost* pHost = 0; + return (_validateMDNSHostHandle(p_hMDNSService, &pHost) + ? addServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_u32Value) + : 0); +} + +/* + MDNSResponder::addServiceTxt (uint16_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint16_t p_u16Value) +{ + MDNSRESPONDER_U16_TO_CHAR(acBuffer, p_u16Value); + return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): + } + + /* + MDNSResponder::addServiceTxt (uint16_t) (LEGACY 2) + */ + MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint16_t p_u16Value) +{ + clsHost* pHost = 0; + return (_validateMDNSHostHandle(p_hMDNSService, &pHost) + ? addServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_u16Value) + : 0); +} + +/* + MDNSResponder::addServiceTxt (uint8_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint8_t p_u8Value) +{ + MDNSRESPONDER_U8_TO_CHAR(acBuffer, p_u8Value); + return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): + } + + /* + MDNSResponder::addServiceTxt (uint8_t) (LEGACY 2) + */ + MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint8_t p_u8Value) +{ + clsHost* pHost = 0; + return (_validateMDNSHostHandle(p_hMDNSService, &pHost) + ? addServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_u8Value) + : 0); +} + +/* + MDNSResponder::addServiceTxt (int32_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + int32_t p_i32Value) +{ + MDNSRESPONDER_I32_TO_CHAR(acBuffer, p_i32Value); + return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): + } + + /* + MDNSResponder::addServiceTxt (int32_t) (LEGACY 2) + */ + MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + int32_t p_i32Value) +{ + clsHost* pHost = 0; + return (_validateMDNSHostHandle(p_hMDNSService, &pHost) + ? addServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_i32Value) + : 0); +} + +/* + MDNSResponder::addServiceTxt (int16_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + int16_t p_i16Value) +{ + MDNSRESPONDER_I16_TO_CHAR(acBuffer, p_i16Value); + return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): + } + + /* + MDNSResponder::addServiceTxt (int16_t) (LEGACY 2) + */ + MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + int16_t p_i16Value) +{ + clsHost* pHost = 0; + return (_validateMDNSHostHandle(p_hMDNSService, &pHost) + ? addServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_i16Value) + : 0); +} + +/* + MDNSResponder::addServiceTxt (int8_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + int8_t p_i8Value) +{ + MDNSRESPONDER_I8_TO_CHAR(acBuffer, p_i8Value); + return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): + } + + /* + MDNSResponder::addServiceTxt (int8_t) (LEGACY 2) + */ + MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + int8_t p_i8Value) +{ + clsHost* pHost = 0; + return (_validateMDNSHostHandle(p_hMDNSService, &pHost) + ? addServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_i8Value) + : 0); +} + +/* + MDNSResponder::removeServiceTxt + + Remove a static service TXT item from a service. +*/ +bool MDNSResponder::removeServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const MDNSResponder::hMDNSTxt p_hTxt) +{ + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && + (p_hTxt) && + (_findServiceTxt(*(clsHost*)p_hMDNSHost, *(stcService*)p_hMDNSService, p_hTxt)) && + (_releaseServiceTxt(*(clsHost*)p_hMDNSHost, *(stcService*)p_hMDNSService, (stcServiceTxt*)p_hTxt))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeServiceTxt: FAILED!\n"), _DH((clsHost*)p_hMDNSHost));); + return bResult; +} + +/* + MDNSResponder::removeServiceTxt (LEGACY 2) +*/ +bool MDNSResponder::removeServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, + const MDNSResponder::hMDNSTxt p_hTxt) +{ + clsHost* pHost = 0; + return (_validateMDNSHostHandle(p_hMDNSService, &pHost) + ? removeServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_hTxt) + : false); +} + +/* + MDNSResponder::removeServiceTxt +*/ +bool MDNSResponder::removeServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey) +{ + stcServiceTxt* pTxt; + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && + (p_pcKey) && + ((pTxt = _findServiceTxt(*(clsHost*)p_hMDNSHost, *(stcService*)p_hMDNSService, p_pcKey))) && + (_releaseServiceTxt(*(clsHost*)p_hMDNSHost, *(stcService*)p_hMDNSService, pTxt))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeServiceTxt: FAILED!\n"), _DH((clsHost*)p_hMDNSHost));); + return bResult; +} + +/* + MDNSResponder::removeServiceTxt (LEGACY 2) +*/ +bool MDNSResponder::removeServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey) +{ + clsHost* pHost = 0; + return (_validateMDNSHostHandle(p_hMDNSService, &pHost) + ? removeServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey) + : false); +} + +/* + MDNSResponder::removeServiceTxt (LEGACY) +*/ +bool MDNSResponder::removeServiceTxt(const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol, + const char* p_pcKey) +{ + hMDNSService hService; + return (((hService = findService(p_pcName, p_pcService, p_pcProtocol))) + ? removeServiceTxt(hService, p_pcKey) + : false); +} + +/* + MDNSResponder::addServiceTxt (LEGACY) +*/ +bool MDNSResponder::addServiceTxt(const char* p_pcService, + const char* p_pcProtocol, + const char* p_pcKey, + const char* p_pcValue) +{ + hMDNSService hService; + return (((hService = findService(p_pcName, p_pcService, p_pcProtocol))) + ? addServiceTxt(hService, p_pcKey, p_pcValue) + : false); +} + +/* + MDNSResponder::addServiceTxt (LEGACY) +*/ +bool MDNSResponder::addServiceTxt(String p_strService, + String p_strProtocol, + String p_strKey, + String p_strValue) +{ + return addServiceTxt(p_strService.c_str(), p_strProtocol.c_str(), p_strKey.c_str(), p_strValue.c_str()); +} + +/* + MDNSResponder::setDynamicServiceTxtCallback (binding) + + Set a netif binding specific callback for dynamic service TXT items. The callback is called, whenever + service TXT items are needed for any service on the netif binding. + +*/ +bool MDNSResponder::setDynamicServiceTxtCallback(const MDNSResponder::hMDNSHost p_hMDNSHost, + MDNSResponder::MDNSDynamicServiceTxtCallbackFn p_fnCallback) +{ + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && + ((!p_fnCallback) || + ((((clsHost*)p_hMDNSHost)->m_fnServiceTxtCallback = p_fnCallback)))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setDynamicServiceTxtCallback: FAILED!\n"), _DH((clsHost*)p_hMDNSHost));); + return bResult; +} + +/* + MDNSResponder::setDynamicServiceTxtCallback (service) + + 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(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + MDNSResponder::MDNSDynamicServiceTxtCallbackFn p_fnCallback) +{ + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && + ((!p_fnCallback) || + ((((stcService*)p_hMDNSService)->m_fnServiceTxtCallback = p_fnCallback)))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setDynamicServiceTxtCallback: FAILED!\n"), _DH((clsHost*)p_hMDNSHost));); + return bResult; +} + +/* + MDNSResponder::setDynamicServiceTxtCallback (global) (LEGACY 2) + + Set a global callback for dynamic service TXT items. The callback is called, whenever + service TXT items are needed. + +*/ +bool MDNSResponder::setDynamicServiceTxtCallback(MDNSResponder::MDNSDynamicServiceTxtCallbackFn1 p_fnCallback) +{ + for (clsHostList : .iterator it : m_HostList) + { + setDynamicServiceTxtCallback((hMDNSHost)it, p_fnCallback); + } + return true; +} + +/* + MDNSResponder::setDynamicServiceTxtCallback (global) (LEGACY 2 (Fn2)) +*/ +bool MDNSResponder::setDynamicServiceTxtCallback(MDNSResponder::MDNSDynamicServiceTxtCallbackFn2 p_fnCallback) +{ + return setDynamicServiceTxtCallback([p_fnCallback](MDNSResponder*, const hMDNSService p_hMDNSService) + { + if (p_fnCallback) + { + p_fnCallback(p_hMDNSService); + } + }); +} + +/* + MDNSResponder::setDynamicServiceTxtCallback (service) (LEGACY 2) +*/ +bool MDNSResponder::setDynamicServiceTxtCallback(MDNSResponder::hMDNSService p_hMDNSService, + MDNSResponder::MDNSDynamicServiceTxtCallbackFn1 p_fnCallback) +{ + clsHost* pHost; + return ((_validateMDNSHostHandle(p_hMDNSService, &pHost)) && + (setDynamicServiceTxtCallback((hMDNSHost)pHost, hMDNSService, p_fnCallback))); +} + +/* + MDNSResponder::setDynamicServiceTxtCallback (service) (LEGACY 2 (Fn2)) +*/ +bool MDNSResponder::setDynamicServiceTxtCallback(MDNSResponder::hMDNSService p_hMDNSService, + MDNSResponder::MDNSDynamicServiceTxtCallbackFn2 p_fnCallback) +{ + return setDynamicServiceTxtCallback(p_hMDNSService, [p_fnCallback](MDNSResponder*, const hMDNSService p_hMDNSService) + { + if (p_fnCallback) + { + p_fnCallback(p_hMDNSService); + } + }); +} + +/* + MDNSResponder::addDynamicServiceTxt +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + const char* p_pcValue) +{ + hMDNSTxt hTxt = (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) + ? _addServiceTxt(*(clsHost*)p_hMDNSHost, *(stcService*)p_hMDNSService, p_pcKey, p_pcValue, true) + : 0); + DEBUG_EX_ERR(if (!hTxt) DEBUG_OUTPUT.printf_P(PSTR("%s addDynamicServiceTxt: FAILED for '%s=%s'!\n"), _DH(), (p_pcKey ? : "-"), (p_pcValue ? : "-"));); + return hTxt; +} + +/* + MDNSResponder::addDynamicServiceTxt (LEGACY 2) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + const char* p_pcValue) +{ + clsHost* pHost = 0; + return (_validateMDNSHostHandle(p_hMDNSService, &pHost) + ? addDynamicServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_pcValue) + : 0); +} + +/* + MDNSResponder::addDynamicServiceTxt (uint32_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint32_t p_u32Value) +{ + MDNSRESPONDER_U32_TO_CHAR(acBuffer, p_u32Value); + return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): + } + + /* + MDNSResponder::addDynamicServiceTxt (uint32_t) (LEGACY 2) + */ + MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint32_t p_u32Value) +{ + clsHost* pHost = 0; + return (_validateMDNSHostHandle(p_hMDNSService, &pHost) + ? addDynamicServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_u32Value) + : 0); +} + +/* + MDNSResponder::addDynamicServiceTxt (uint16_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint16_t p_u16Value) +{ + MDNSRESPONDER_U16_TO_CHAR(acBuffer, p_u16Value); + return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): + } + + /* + MDNSResponder::addDynamicServiceTxt (uint16_t) (LEGACY 2) + */ + MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint16_t p_u16Value) +{ + clsHost* pHost = 0; + return (_validateMDNSHostHandle(p_hMDNSService, &pHost) + ? addDynamicServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_u16Value) + : 0); +} + +/* + MDNSResponder::addDynamicServiceTxt (uint8_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint8_t p_u8Value) +{ + MDNSRESPONDER_U8_TO_CHAR(acBuffer, p_u8Value); + return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): + } + + /* + MDNSResponder::addDynamicServiceTxt (uint8_t) (LEGACY 2) + */ + MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint8_t p_u8Value) +{ + clsHost* pHost = 0; + return (_validateMDNSHostHandle(p_hMDNSService, &pHost) + ? addDynamicServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_u8Value) + : 0); +} + +/* + MDNSResponder::addDynamicServiceTxt (int32_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + int32_t p_i32Value) +{ + MDNSRESPONDER_I32_TO_CHAR(acBuffer, p_i32Value); + return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): + } + + /* + MDNSResponder::addDynamicServiceTxt (int32_t) (LEGACY 2) + */ + MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint32_t p_i32Value) +{ + clsHost* pHost = 0; + return (_validateMDNSHostHandle(p_hMDNSService, &pHost) + ? addDynamicServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_i32Value) + : 0); +} + +/* + MDNSResponder::addDynamicServiceTxt (int16_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + int16_t p_i16Value) +{ + MDNSRESPONDER_I16_TO_CHAR(acBuffer, p_i16Value); + return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): + } + + /* + MDNSResponder::addDynamicServiceTxt (int16_t) (LEGACY 2) + */ + MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint16_t p_i16Value) +{ + clsHost* pHost = 0; + return (_validateMDNSHostHandle(p_hMDNSService, &pHost) + ? addDynamicServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_i16Value) + : 0); +} + +/* + MDNSResponder::addDynamicServiceTxt (int8_t) +*/ +MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + int8_t p_i8Value) +{ + MDNSRESPONDER_I8_TO_CHAR(acBuffer, p_i8Value); + return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): + } + + /* + MDNSResponder::addDynamicServiceTxt (int8_t) (LEGACY 2) + */ + MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, + const char* p_pcKey, + uint8_t p_i8Value) +{ + clsHost* pHost = 0; + return (_validateMDNSHostHandle(p_hMDNSService, &pHost) + ? addDynamicServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_i8Value) + : 0); +} + + +/** + STATIC QUERIES +*/ + +/* + 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 MDNSResponder::hMDNSHost p_hMDNSHost, + 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("%s queryService '%s.%s'\n"), _DH(), p_pcService, p_pcProtocol);); + + uint32_t u32Result = 0; + + stcQuery* pMDNSQuery = 0; + if ((_validateMDNSHostHandle(p_hMDNSHost)) && + (p_pcService) && + (os_strlen(p_pcService)) && + (p_pcProtocol) && + (os_strlen(p_pcProtocol)) && + (p_u16Timeout) && + (_removeLegacyQuery()) && + ((pMDNSQuery = _allocQuery(*(clsHost*)p_hMDNSHost, stcQuery::enuQueryType::Service))) && + (_buildDomainForService(p_pcService, p_pcProtocol, pMDNSQuery->m_Domain))) + { + pMDNSQuery->m_bLegacyQuery = true; + + if (_sendMDNSQuery(*(clsHost*)p_hMDNSHost, *pMDNSQuery)) + { + // Wait for answers to arrive + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryService: Waiting %u ms for answers...\n"), _DH(), p_u16Timeout);); + delay(p_u16Timeout); + + // All answers should have arrived by now -> stop adding new answers + pMDNSQuery->m_bAwaitingAnswers = false; + u32Result = pMDNSQuery->answerCount(); + } + else // FAILED to send query + { + _removeQuery(*(clsHost*)p_hMDNSHost, pMDNSQuery); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s queryService: INVALID input data!\n"), _DH());); + } + return u32Result; +} + +/* + MDNSResponder::queryService (LEGACY 2) +*/ +uint32_t MDNSResponder::queryService(const char* p_pcService, + const char* p_pcProtocol, + const uint16_t p_u16Timeout /*= MDNS_QUERYSERVICES_WAIT_TIME*/) +{ + return ((!m_HostList.empty()) + ? queryService((hMDNSHost)m_HostList.front(), p_pcService, p_pcProtocol, p_u16Timeout) + : 0); +} + +/* + MDNSResponder::queryService (LEGACY) +*/ +uint32_t MDNSResponder::queryService(const String& p_strService, + const String& p_strProtocol) +{ + return queryService(p_strService.c_str(), p_strProtocol.c_str()); +} + +/* + MDNSResponder::queryHost + + Perform a (blocking) static host query. + The arrived answers can be queried by calling: + - answerHostName (or 'hostname') + - answerIP (or 'IP') + - answerPort (or 'port') + +*/ +uint32_t MDNSResponder::queryHost(const MDNSResponder::hMDNSHost p_hMDNSHost, + const char* p_pcHostName, + const uint16_t p_u16Timeout /*= MDNS_QUERYSERVICES_WAIT_TIME*/) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost '%s.local'\n"), _DH(), p_pcHostName);); + + uint32_t u32Result = 0; + + stcQuery* pHostQuery = 0; + if ((_validateMDNSHostHandle(p_hMDNSHost)) && + (p_pcHostName) && + (os_strlen(p_pcHostName)) && + (p_u16Timeout) && + (_removeLegacyQuery()) && + ((pHostQuery = _allocQuery(*(clsHost*)p_hMDNSHost, stcQuery::enuQueryType::Host))) && + (_buildDomainForHost(p_pcHostName, pHostQuery->m_Domain))) + { + + pHostQuery->m_bLegacyQuery = true; + + if (_sendMDNSQuery(*(clsHost*)p_hMDNSHost, *pHostQuery)) + { + // Wait for answers to arrive + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost: Waiting %u ms for answers...\n"), _DH(), p_u16Timeout);); + delay(p_u16Timeout); + + // All answers should have arrived by now -> stop adding new answers + pHostQuery->m_bAwaitingAnswers = false; + u32Result = pHostQuery->answerCount(); + } + else // FAILED to send query + { + _removeQuery(*(clsHost*)p_hMDNSHost, pHostQuery); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost: INVALID input data!\n"), _DH());); + } + return u32Result; +} + +/* + queryHost (LEGACY 2) +*/ +uint32_t MDNSResponder::queryHost(const char* p_pcHostName, + const uint16_t p_u16Timeout /*= MDNS_QUERYSERVICES_WAIT_TIME*/) +{ + return ((!m_HostList.empty()) + ? queryHost((hMDNSHost)m_HostList.front(), p_pcService, p_pcProtocol, p_u16Timeout) + : 0); +} + +/* + MDNSResponder::removeQuery + + Remove the last static query (and all answers). + +*/ +bool MDNSResponder::removeQuery(const MDNSResponder::hMDNSHost p_hMDNSHost) +{ + return ((_validateMDNSHostHandle(p_hMDNSHost)) && + (_removeLegacyQuery(*(clsHost*)p_hMDNSHost))); +} + +/* + MDNSResponder::removeQuery (LEGACY 2) +*/ +bool MDNSResponder::removeQuery(void) +{ + return ((!m_HostList.empty()) + ? removeQuery((hMDNSHost)m_HostList.front()) + : false); +} + +/* + MDNSResponder::hasQuery + + Return 'true', if a static query is currently installed + +*/ +bool MDNSResponder::hasQuery(const MDNSResponder::hMDNSHost p_hMDNSHost) +{ + return ((_validateMDNSHostHandle(p_hMDNSHost)) && + (0 != _findLegacyQuery(*(clsHost*)p_hMDNSHost))); +} + +/* + MDNSResponder::hasQuery (LEGACY 2) +*/ +bool MDNSResponder::hasQuery(void) +{ + return ((!m_HostList.empty()) + ? hasQuery((hMDNSHost)m_HostList.front()) + : false); +} + +/* + MDNSResponder::getQuery + + Return handle to the last static query + +*/ +MDNSResponder::hMDNSQuery MDNSResponder::getQuery(const MDNSResponder::hMDNSHost p_hMDNSHost) +{ + return (_validateMDNSHostHandle(p_hMDNSHost) + ? (hMDNSQuery)_findLegacyQuery() + : 0); +} + +/* + MDNSResponder::getQuery (LEGACY 2) +*/ +MDNSResponder::hMDNSQuery MDNSResponder::getQuery(void) +{ + return ((!m_HostList.empty()) + ? getQuery((hMDNSHost)m_HostList.front()) + : false); +} + +/* + MDNSResponder::answerHostName +*/ +const char* MDNSResponder::answerHostName(const MDNSResponder::hMDNSHost p_hMDNSHost, + const uint32_t p_u32AnswerIndex) +{ + const stcQuery* pQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findLegacyQuery(*(clsHost*)p_hMDNSHost) : 0); + stcQuery::stcAnswer* pSQAnswer = (pQuery ? pQuery->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); +} + +/* + MDNSResponder::answerHostName (LEGACY 2) +*/ +const char* MDNSResponder::answerHostName(const uint32_t p_u32AnswerIndex) +{ + return ((!m_HostList.empty()) + ? answerHostName((hMDNSHost)m_HostList.front(), p_u32AnswerIndex) + : 0); +} + +/* + MDNSResponder::hostname (LEGACY) +*/ +String MDNSResponder::hostname(const uint32_t p_u32AnswerIndex) +{ + return String(answerHostName(p_u32AnswerIndex)); +} + +#ifdef MDNS_IPV4_SUPPORT +/* + MDNSResponder::answerIPv4 +*/ +IPAddress MDNSResponder::answerIPv4(const MDNSResponder::hMDNSHost p_hMDNSHost, + const uint32_t p_u32AnswerIndex) +{ + const stcQuery* pQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findLegacyQuery(*(clsHost*)p_hMDNSHost) : 0); + const stcQuery::stcAnswer* pSQAnswer = (pQuery ? pQuery->answerAtIndex(p_u32AnswerIndex) : 0); + const stcQuery::stcAnswer::stcIPv4Address* pIPv4Address = (((pSQAnswer) && (pSQAnswer->m_pIPv4Addresses)) ? pSQAnswer->IPv4AddressAtIndex(0) : 0); + return (pIPv4Address ? pIPv4Address->m_IPAddress : IPAddress()); +} + +/* + MDNSResponder::answerIPv4 (LEGACY 2) +*/ +IPAddress MDNSResponder::answerIPv4(const uint32_t p_u32AnswerIndex) +{ + return ((!m_HostList.empty()) + ? answerIPv4((hMDNSHost)m_HostList.front(), p_u32AnswerIndex) + : IPAddress()); +} + +/* + MDNSResponder::answerIP (LEGACY 2) +*/ +IPAddress MDNSResponder::answerIP(const uint32_t p_u32AnswerIndex) +{ + return answerIPv4(p_u32AnswerIndex); +} + +/* + MDNSResponder::IP (LEGACY) +*/ +IPAddress MDNSResponder::IP(const uint32_t p_u32AnswerIndex) +{ + return answerIPv4(p_u32AnswerIndex); +} +#endif + +#ifdef MDNS_IPV6_SUPPORT +/* + MDNSResponder::answerIPv6 +*/ +IPAddress MDNSResponder::answerIPv6(const MDNSResponder::hMDNSHost p_hMDNSHost, + const uint32_t p_u32AnswerIndex) +{ + const stcQuery* pQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findLegacyQuery(*(clsHost*)p_hMDNSHost) : 0); + const stcQuery::stcAnswer* pSQAnswer = (pQuery ? pQuery->answerAtIndex(p_u32AnswerIndex) : 0); + const stcQuery::stcAnswer::stcIPv6Address* pIPv6Address = (((pSQAnswer) && (pSQAnswer->m_pIPv6Addresses)) ? pSQAnswer->IPv6AddressAtIndex(0) : 0); + return (pIPv6Address ? pIPv6Address->m_IPAddress : IPAddress()); +} + +/* + MDNSResponder::answerIPv6 (LEGACY 2) +*/ +IPAddress MDNSResponder::answerIPv6(const uint32_t p_u32AnswerIndex) +{ + return ((!m_HostList.empty()) + ? answerIPv6((hMDNSHost)m_HostList.front(), p_u32AnswerIndex) + : IPAddress()); +} +#endif + +/* + MDNSResponder::answerPort +*/ +uint16_t MDNSResponder::answerPort(const MDNSResponder::hMDNSHost p_hMDNSHost, + const uint32_t p_u32AnswerIndex) +{ + const stcQuery* pQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findLegacyQuery(*(clsHost*)p_hMDNSHost) : 0); + const stcQuery::stcAnswer* pSQAnswer = (pQuery ? pQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return (pSQAnswer ? pSQAnswer->m_u16Port : 0); +} + +/* + MDNSResponder::answerPort (LEGACY 2) +*/ +uint16_t MDNSResponder::answerPort(const uint32_t p_u32AnswerIndex) +{ + return ((!m_HostList.empty()) + ? answerPort((hMDNSHost)m_HostList.front(), p_u32AnswerIndex) + : 0); +} + +/* + 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 + - answerIPv4Address/answerIPv6Address + - answerPort + - answerTxts + +*/ +MDNSResponder::hMDNSQuery MDNSResponder::installServiceQuery(const MDNSResponder::hMDNSHost p_hMDNSHost, + const char* p_pcService, + const char* p_pcProtocol, + MDNSResponder::MDNSQueryCallbackFn p_fnCallback) +{ + hMDNSQuery hResult = 0; + + stcQuery* pMDNSQuery = 0; + if ((_validateMDNSHostHandle(p_hMDNSHost)) && + (p_pcService) && + (os_strlen(p_pcService)) && + (p_pcProtocol) && + (os_strlen(p_pcProtocol)) && + (p_fnCallback) && + ((pMDNSQuery = _allocQuery(*(clsHost*)p_hMDNSHost, stcQuery::enuQueryType::Service))) && + (_buildDomainForService(p_pcService, p_pcProtocol, pMDNSQuery->m_Domain))) + { + + pMDNSQuery->m_fnCallback = p_fnCallback; + pMDNSQuery->m_bLegacyQuery = false; + + if (_sendMDNSQuery(*(clsHost*)p_hMDNSHost, *pMDNSQuery)) + { + pMDNSQuery->m_u8SentCount = 1; + pMDNSQuery->m_ResendTimeout.reset(MDNS_DYNAMIC_QUERY_RESEND_DELAY); + + hResult = (hMDNSQuery)pMDNSQuery; + } + else + { + _removeQuery(*(clsHost*)p_hMDNSHost, pMDNSQuery); + } + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s installServiceQuery: %s for '%s.%s'!\n\n"), _DH(), (hResult ? "Succeeded" : "FAILED"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); + DEBUG_EX_ERR(if (!hResult) DEBUG_OUTPUT.printf_P(PSTR("%s installServiceQuery: FAILED for '%s.%s'!\n\n"), _DH(), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); + return hResult; +} + +/* + MDNSResponder::installServiceQuery (LEGACY 2) +*/ +MDNSResponder::hMDNSQuery MDNSResponder::installServiceQuery(const char* p_pcService, + const char* p_pcProtocol, + MDNSResponder::MDNSQueryCallbackFn1 p_fnCallback) +{ + return ((!m_HostList.empty()) + ? installServiceQuery((hMDNSHost)m_HostList.front(), p_pcService, p_pcProtocol, [p_fnCallback])(MDNSResponder * p_pMDNSResponder, + MDNSResponder::hMDNSHost, + const stcAnswerAccessor & p_MDNSAnswerAccessor, + typeQueryAnswerType p_QueryAnswerTypeFlags, + bool p_bSetContent) + { + if (p_fnCallback) + { + p_fnCallback(p_pMDNSResponder, p_MDNSAnswerAccessor, p_QueryAnswerTypeFlags, p_bSetContent); + } + }) + : 0); + } + + /* + MDNSResponder::installServiceQuery (LEGACY 2) + */ + MDNSResponder::hMDNSQuery MDNSResponder::installServiceQuery(const char* p_pcService, + const char* p_pcProtocol, + MDNSResponder::MDNSQueryCallbackFn2 p_fnCallback) +{ + return ((!m_HostList.empty()) + ? installServiceQuery((hMDNSHost)m_HostList.front(), p_pcService, p_pcProtocol, [p_fnCallback])(MDNSResponder*, + MDNSResponder::hMDNSHost, + const stcAnswerAccessor & p_MDNSAnswerAccessor, + typeQueryAnswerType p_QueryAnswerTypeFlags, + bool p_bSetContent) + { + if (p_fnCallback) + { + p_fnCallback(p_MDNSAnswerAccessor, p_QueryAnswerTypeFlags, p_bSetContent); + } + }) + : 0); + } + + /* + MDNSResponder::installHostQuery + */ + MDNSResponder::hMDNSQuery MDNSResponder::installHostQuery(const MDNSResponder::hMDNSHost p_hMDNSHost, + const char* p_pcHostName, + MDNSResponder::MDNSQueryCallbackFn p_fnCallback) +{ + hMDNSQuery hResult = 0; + + if ((_validateMDNSHostHandle(p_hMDNSHost)) && + (p_pcHostName) && + (os_strlen(p_pcHostName))) + { + stcRRDomain domain; + hResult = ((_buildDomainForHost(p_pcHostName, domain)) + ? _installDomainQuery(*(clsHost*)p_hMDNSHost, domain, stcQuery::enuQueryType::Host, p_fnCallback) + : 0); + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s installHostQuery: %s for '%s.local'!\n\n"), _DH(), (hResult ? "Succeeded" : "FAILED"), (p_pcHostName ? : "-"));); + DEBUG_EX_ERR(if (!hResult) DEBUG_OUTPUT.printf_P(PSTR("%s installHostQuery: FAILED for '%s.local'!\n\n"), _DH(), (p_pcHostName ? : "-"));); + return hResult; +} + +/* + MDNSResponder::installHostQuery (LEGACY 2) +*/ +MDNSResponder::hMDNSQuery MDNSResponder::installHostQuery(const char* p_pcHostName, + MDNSResponder::MDNSQueryCallbackFn1 p_fnCallback) +{ + return installHostQuery(p_pcHostName, [p_fnCallback](MDNSResponder * p_pMDNSResponder, + hMDNSHost, + const stcAnswerAccessor & p_MDNSAnswerAccessor, + typeQueryAnswerType p_QueryAnswerTypeFlags, + bool p_bSetContent) + { + if (p_fnCallback) + { + p_fnCallback(p_pMDNSResponder, p_MDNSAnswerAccessor, p_QueryAnswerTypeFlags, p_bSetContent); + } + }); +} + +/* + MDNSResponder::installHostQuery (LEGACY 2) +*/ +MDNSResponder::hMDNSQuery MDNSResponder::installHostQuery(const char* p_pcHostName, + MDNSResponder::MDNSQueryCallbackFn2 p_fnCallback) +{ + return installHostQuery(p_pcHostName, [p_fnCallback](MDNSResponder*, + hMDNSHost, + const stcAnswerAccessor & p_MDNSAnswerAccessor, + typeQueryAnswerType p_QueryAnswerTypeFlags, + bool p_bSetContent) + { + if (p_fnCallback) + { + p_fnCallback(p_MDNSAnswerAccessor, p_QueryAnswerTypeFlags, p_bSetContent); + } + }); +} + +/* + MDNSResponder::removeQuery + + Remove a dynamic query (and all collected answers) from the MDNS responder + +*/ +bool MDNSResponder::removeQuery(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery) +{ + stcQuery* pMDNSQuery = 0; + bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && + ((pMDNSQuery = _findQuery(*(clsHost*)p_hMDNSHost, p_hQuery))) && + (_removeQuery(*(clsHost*)p_hMDNSHost, pMDNSQuery))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeQuery: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::removeQuery (LEGACY 2) +*/ +bool MDNSResponder::removeQuery(const MDNSResponder::hMDNSQuery p_hMDNSQuery) +{ + return ((!m_HostList.empty()) + ? removeQuery((hMDNSHost)m_HostList.front(), p_hMDNSQuery) + : false); +} + +/* + MDNSResponder::answerAccessors +*/ +MDNSResponder::clsMDNSAnswerAccessorVector MDNSResponder::answerAccessors(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery) +{ + MDNSResponder::clsMDNSAnswerAccessorVector tempVector; + for (uint32_t u = 0; u < answerCount(p_hMDNSHost, p_hMDNSQuery); ++u) + { + tempVector.emplace_back(*this, p_hMDNSQuery, u); + } + return tempVector; +} + +/* + MDNSResponder::answerAccessors (LEGACY 2) +*/ +MDNSResponder::clsMDNSAnswerAccessorVector MDNSResponder::answerAccessors(const MDNSResponder::hMDNSQuery p_hMDNSQuery) +{ + return ((!m_HostList.empty()) + ? answerAccessors((hMDNSHost)m_HostList.front(), p_hMDNSQuery) + : MDNSResponder::clsMDNSAnswerAccessorVector()); +} + +/* + MDNSResponder::answerCount +*/ +uint32_t MDNSResponder::answerCount(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery) : 0); + return (pMDNSQuery ? pMDNSQuery->answerCount() : 0); +} + +/* + MDNSResponder::answerCount (LEGACY 2) +*/ +uint32_t MDNSResponder::answerCount(const MDNSResponder::hMDNSQuery p_hMDNSQuery) +{ + return ((!m_HostList.empty()) + ? answerCount((hMDNSHost)m_HostList.front(), p_hMDNSQuery) + : 0); +} + +/* + MDNSResponder::hasAnswerServiceDomain +*/ +bool MDNSResponder::hasAnswerServiceDomain(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return ((pSQAnswer) && + (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::ServiceDomain))); +} + + /* + MDNSResponder::hasAnswerServiceDomain (LEGACY 2) + */ + bool MDNSResponder::hasAnswerServiceDomain(const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + return ((!m_HostList.empty()) + ? hasAnswerServiceDomain((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) + : false); +} + +/* + 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::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->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::answerServiceDomain (LEGACY 2) +*/ +const char* MDNSResponder::answerServiceDomain(const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + return ((!m_HostList.empty()) + ? answerServiceDomain((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) + : 0); +} + +/* + MDNSResponder::hasAnswerHostDomain +*/ +bool MDNSResponder::hasAnswerHostDomain(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return ((pSQAnswer) && + (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::HostDomain))); +} + + /* + MDNSResponder::hasAnswerHostDomain (LEGACY 2) + */ + bool MDNSResponder::hasAnswerHostDomain(const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + return ((!m_HostList.empty()) + ? hasAnswerHostDomain((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) + : false); +} + +/* + 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::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->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); +} + +/* + MDNSResponder::answerHostDomain (LEGACY 2) +*/ +const char* MDNSResponder::answerHostDomain(const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + return ((!m_HostList.empty()) + ? answerHostDomain((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) + : 0); +} + +#ifdef MDNS_IPV4_SUPPORT +/* + MDNSResponder::hasAnswerIPv4Address +*/ +bool MDNSResponder::hasAnswerIPv4Address(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return ((pSQAnswer) && + (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::IPv4Address))); +} + + /* + MDNSResponder::hasAnswerIPv4Address (LEGACY 2) + */ + bool MDNSResponder::hasAnswerIPv4Address(const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + return ((!m_HostList.empty()) + ? hasAnswerIPv4Address((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) + : false); +} + +/* + MDNSResponder::answerIPv4AddressCount +*/ +uint32_t MDNSResponder::answerIPv4AddressCount(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return (pSQAnswer ? pSQAnswer->IPv4AddressCount() : 0); +} + + /* + MDNSResponder::answerIPv4AddressCount (LEGACY 2) + */ + uint32_t MDNSResponder::answerIPv4AddressCount(const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + return ((!m_HostList.empty()) + ? answerIPv4AddressCount((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) + : 0); +} + +/* + MDNSResponder::answerIPv4Address +*/ +IPAddress MDNSResponder::answerIPv4Address(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + stcQuery::stcAnswer::stcIPv4Address* pIPv4Address = (pSQAnswer ? pSQAnswer->IPv4AddressAtIndex(p_u32AddressIndex) : 0); + return (pIPv4Address ? pIPv4Address->m_IPAddress : IPAddress()); +} + + /* + MDNSResponder::answerIPv4Address (LEGACY 2) + */ + IPAddress MDNSResponder::answerIPv4Address(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex) +{ + return ((!m_HostList.empty()) + ? answerIPv4Address((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex, p_u32AddressIndex) + : IPAddress()); +} +#endif + +#ifdef MDNS_IPV6_SUPPORT +/* + MDNSResponder::hasAnswerIPv6Address +*/ +bool MDNSResponder::hasAnswerIPv6Address(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return ((pSQAnswer) && + (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::IPv6Address))); +} + + /* + MDNSResponder::hasAnswerIPv6Address (LEGACY 2) + */ + bool MDNSResponder::hasAnswerIPv6Address(const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + return ((!m_HostList.empty()) + ? hasAnswerIPv6Address((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) + : false); +} + +/* + MDNSResponder::answerIPv6AddressCount +*/ +uint32_t MDNSResponder::answerIPv6AddressCount(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return (pSQAnswer ? pSQAnswer->IPv6AddressCount() : 0); +} + + /* + MDNSResponder::answerIPv6AddressCount (LEGACY 2) + */ + uint32_t MDNSResponder::answerIPv6AddressCount(const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + return ((!m_HostList.empty()) + ? answerIPv6AddressCount((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) + : 0); +} + +/* + MDNSResponder::answerIPv6Address +*/ +IPAddress MDNSResponder::answerIPv6Address(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + stcQuery::stcAnswer::stcIPv6Address* pIPv6Address = (pSQAnswer ? pSQAnswer->IPv6AddressAtIndex(p_u32AddressIndex) : 0); + return (pIPv6Address ? pIPv6Address->m_IPAddress : IPAddress()); +} + + /* + MDNSResponder::answerIPv6Address (LEGACY 2) + */ + IPAddress MDNSResponder::answerIPv6Address(const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex) +{ + return ((!m_HostList.empty()) + ? answerIPv6Address((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) + : IPAddress()); +} +#endif + +/* + MDNSResponder::hasAnswerPort +*/ +bool MDNSResponder::hasAnswerPort(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return ((pSQAnswer) && + (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::Port))); +} + + /* + MDNSResponder::hasAnswerPort (LEGACY 2) + */ + bool MDNSResponder::hasAnswerPort(const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + return ((!m_HostList.empty()) + ? hasAnswerPort((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) + : false); +} + +/* + MDNSResponder::answerPort +*/ +uint16_t MDNSResponder::answerPort(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return (pSQAnswer ? pSQAnswer->m_u16Port : 0); +} + + /* + MDNSResponder::answerPort (LEGACY 2) + */ + uint16_t MDNSResponder::answerPort(const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + return ((!m_HostList.empty()) + ? answerPort((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) + : 0); +} + +/* + MDNSResponder::hasAnswerTxts +*/ +bool MDNSResponder::hasAnswerTxts(const MDNSResponder::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); + return ((pSQAnswer) && + (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::Txts))); +} + + /* + MDNSResponder::hasAnswerTxts (LEGACY 2) + */ + bool MDNSResponder::hasAnswerTxts(const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + return ((!m_HostList.empty()) + ? hasAnswerTxts((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) + : false); +} + +/* + 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::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); + stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->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); +} + +/* + MDNSResponder::answerTxts (LEGACY 2) +*/ +const char* MDNSResponder::answerTxts(const MDNSResponder::hMDNSQuery p_hMDNSQuery, + const uint32_t p_u32AnswerIndex) +{ + return ((!m_HostList.empty()) + ? answerTxts((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) + : 0); +} + + +/* + PROBING +*/ + +/* + MDNSResponder::setHostProbeResultCallback + + Set a callback for probe results. The callback is called, when probing + for the host domain failes or succeedes. + In the case of failure, the domain name should be changed via 'setHostName' + When succeeded, the host domain will be announced by the MDNS responder. + +*/ +bool MDNSResponder::setHostProbeResultCallback(const MDNSResponder::hMDNSHost p_hMDNSHost, + MDNSResponder::MDNSHostProbeResultCallbackFn p_fnCallback) +{ + return ((_validateMDNSHostHandle(p_hMDNSHost)) && + (((clsHost*)p_hMDNSHost)->m_HostProbeInformation.m_fnProbeResultCallback = p_fnCallback, true)); +} + +/* + MDNSResponder::setHostProbeResultCallback (LEGACY 2) +*/ +bool MDNSResponder::setHostProbeResultCallback(MDNSResponder::MDNSHostProbeResultCallbackFn1 p_fnCallback) +{ + return setHostProbeResultCallback([p_fnCallback](MDNSResponder * p_pMDNSResponder, + hMDNSHost, + const char* p_pcDomainName, + bool p_bProbeResult) + { + if (p_fnCallback) + { + p_fnCallback(p_pMDNSResponder, p_pcDomainName, p_bProbeResult); + } + }); +} + +/* + MDNSResponder::setHostProbeResultCallback (LEGACY 2) +*/ +bool MDNSResponder::setHostProbeResultCallback(MDNSResponder::MDNSHostProbeResultCallbackFn2 p_fnCallback) +{ + return setHostProbeResultCallback([p_fnCallback](MDNSResponder*, + hMDNSHost, + const char* p_pcDomainName, + bool p_bProbeResult) + { + if (p_fnCallback) + { + p_fnCallback(p_pcDomainName, p_bProbeResult); + } + }); +} + +/* + 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::hMDNSHost p_hMDNSHost, + const MDNSResponder::hMDNSService p_hMDNSService, + MDNSResponder::MDNSServiceProbeResultCallbackFn p_fnCallback) +{ + return ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && + (((stcService*)p_hMDNSService)->m_ProbeInformation.m_fnProbeResultCallback = p_fnCallback, true)); +} + +/* + MDNSResponder::setServiceProbeResultCallback (LEGACY 2) +*/ +bool MDNSResponder::setServiceProbeResultCallback(const MDNSResponder::hMDNSService p_hMDNSService, + MDNSResponder::MDNSServiceProbeResultCallbackFn1 p_fnCallback) +{ + return setServiceProbeResultCallback(p_hMDNSService, [p_fnCallback](MDNSResponder * p_pMDNSResponder, + hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcServiceName, + bool p_bProbeResult) + { + if (p_fnCallback) + { + p_fnCallback(p_pMDNSResponder, p_hMDNSService, p_pcServiceName, p_bProbeResult); + } + }); +} + +/* + MDNSResponder::setServiceProbeResultCallback (LEGACY 2) +*/ +bool MDNSResponder::setServiceProbeResultCallback(const MDNSResponder::hMDNSService p_hMDNSService, + MDNSResponder::MDNSServiceProbeResultCallbackFn2 p_fnCallback) +{ + return setServiceProbeResultCallback(p_hMDNSService, [p_fnCallback](MDNSResponder*, + hMDNSHost, + const hMDNSService p_hMDNSService, + const char* p_pcServiceName, + bool p_bProbeResult) + { + if (p_fnCallback) + { + p_fnCallback(p_hMDNSService, p_pcServiceName, p_bProbeResult); + } + }); +} +#endif + +/* + MISC +*/ + +/* + MDNSResponder::notifyNetIfChange + + 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::notifyNetIfChange(netif* p_pNetIf) +{ + clsHost* pMDNSHost; + return (((pMDNSHost = _findHost(p_pNetIf))) && + (pMDNSHost->restart())); +} + +/* + MDNSResponder::update + + Should be called in every 'loop'. + +*/ +bool MDNSResponder::update(const MDNSResponder::hMDNSHost p_hMDNSHost) +{ + return ((_validateMDNSHostHandle(p_hMDNSHost)) && + (_NRH2Ptr(p_hMDNSHost)->update())); +} + +/* + MDNSResponder::update (convenience) +*/ +bool MDNSResponder::update(void) +{ + bool bResult = true; + for (clsHost* pMDNSHost : m_HostList) + { + if (!pMDNSHost->update()) + { + bResult = false; + } + } + return bResult; +} + +/* + MDNSResponder::announce + + Should be called, if the 'configuration' changes. Mainly this will be changes in the TXT items... +*/ +bool MDNSResponder::announce(const MDNSResponder::hMDNSHost p_hMDNSHost) +{ + return ((_validateMDNSHostHandle(p_hMDNSHost)) && + (_NRH2Ptr(p_hMDNSHost)->announce(true, true))); +} + +/* + MDNSResponder::announce (convenience) +*/ +bool MDNSResponder::announce(void) +{ + bool bResult = true; + for (clsHost* pMDNSHost : m_HostList) + { + if (!pMDNSHost->announce(true, true)) + { + bResult = false; + } + } + return bResult; +} + +/* + MDNSResponder::enableArduino + + Enable the OTA update service. + +*/ +MDNSResponder::hMDNSService MDNSResponder::enableArduino(const MDNSResponder::hMDNSHost p_hMDNSHost, + uint16_t p_u16Port, + bool p_bAuthUpload /*= false*/) +{ + hMDNSService hService = addService(p_hMDNSHost, 0, "arduino", "tcp", p_u16Port); + if (hService) + { + if ((!addServiceTxt(p_hMDNSHost, hService, "tcp_check", "no")) || + (!addServiceTxt(p_hMDNSHost, hService, "ssh_upload", "no")) || + (!addServiceTxt(p_hMDNSHost, hService, "board", STRINGIZE_VALUE_OF(ARDUINO_BOARD))) || + (!addServiceTxt(p_hMDNSHost, hService, "auth_upload", (p_bAuthUpload) ? "yes" : "no"))) + { + + removeService(p_hMDNSHost, hService); + hService = 0; + } + } + return hService; +} + +#ifdef LATER + +/* + MDNSResponder::enableArduino (LEGACY 2) +*/ +MDNSResponder::hMDNSService MDNSResponder::enableArduino(uint16_t p_u16Port, + bool p_bAuthUpload /*= false*/) +{ + hMDNSService hMDNSService = 0; + for (clsHost*& pMDNSHost : m_HostList) + { + hMDNSService hLastMDNSService = enableArduino((hMDNSHost)it, p_u16Port, p_bAuthUpload); + if ((hLastMDNSService) && + (!hMDNSService)) + { + hMDNSService = hLastMDNSService; + } + } + return hMDNSService; +} + +#endif + +} //namespace MDNSImplementation + +} //namespace esp8266 + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_APIHelpers.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_APIHelpers.cpp new file mode 100755 index 0000000000..7c144499e4 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_APIHelpers.cpp @@ -0,0 +1,354 @@ +/* + LEAmDNS2_APIHelpers.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 +#include + +/* + ESP8266mDNS Control.cpp +*/ + +extern "C" { +#include "user_interface.h" +} + +#include "LEAmDNS2_lwIPdefs.h" +#include "LEAmDNS2_Priv.h" + +namespace esp8266 +{ +/* + LEAmDNS +*/ +namespace experimental +{ + +/* + MDNSResponder::_allocUDPContext +*/ +bool MDNSResponder::_allocUDPContext(void) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext\n"), _DH());); + if (_releaseUDPContext()) + { + m_pUDPContext = new UdpContext; + if (m_pUDPContext) + { + m_pUDPContext->ref(); + + ip_set_option(m_pUDPContext->pcb(), SOF_REUSEADDR); + //udp_bind_netif(m_pUDPContext->pcb(), m_pNetIf); + + if (m_pUDPContext->listen(IP_ANY_TYPE, DNS_MQUERY_PORT)) + { + //m_pUDPContext->setMulticastInterface(m_pNetIf); + m_pUDPContext->setMulticastTTL(MDNS_MULTICAST_TTL); + m_pUDPContext->onRx(std::bind(&MDNSResponder::_processUDPInput, this)); + m_pUDPContext->connect(IP_ANY_TYPE, DNS_MQUERY_PORT); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext: Succeeded to alloc UDPContext!\n"), _DH());); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext: FAILED to make UDPContext listening!\n"), _DH());); + _releaseUDPContext(); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext: FAILED to alloc UDPContext!\n"), _DH());); + } + } + DEBUG_EX_ERR(if (!m_pUDPContext) DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext: FAILED!\n"), _DH());); + return (0 != m_pUDPContext); +} + +/* + MDNSResponder::_releaseUDPContext +*/ +bool MDNSResponder::_releaseUDPContext(void) +{ + if (m_pUDPContext) + { + m_pUDPContext->unref(); + m_pUDPContext = 0; + } + return true; +} + +/* + MDNSResponder::_processUDPInput + + Called in SYS context! + +*/ +bool MDNSResponder::_processUDPInput(void) +{ + //DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput\n"), _DH());); + + if (m_pUDPContext->next()) + { + netif* pNetIf = ip_current_input_netif(); + MDNSResponder::clsHost* pHost = 0; + if ((pNetIf) && + ((pHost = _findHost(pNetIf)))) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: netif:%u,src:%s,dest:%s\n"), _DH(), netif_get_index(pNetIf), IPAddress(ip_current_src_addr()).toString().c_str(), IPAddress(ip_current_dest_addr()).toString().c_str());); + pHost->processUDPInput(/*IPAddress(ip_current_src_addr()), IPAddress(ip_current_dest_addr())*/); + } + else + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Received UDP datagramm for unused netif at index: %u\n"), _DH(), (pNetIf ? netif_get_index(pNetIf) : (-1)));); + } + m_pUDPContext->flush(); + } + return true; +} + +/* + MDNSResponder::_createHost +*/ +MDNSResponder::clsHost* MDNSResponder::_createHost(netif* p_pNetIf) +{ + clsHost* pHost = 0; + + if ((p_pNetIf) && + (!((pHost = _findHost(p_pNetIf)))) && + (m_pUDPContext) && + ((pHost = new clsHost(*p_pNetIf, *m_pUDPContext)))) + { + if (pHost->init()) + { + //pHost->setHostProbeResultCallback(_defaultHostProbeResultCallback); + m_HostList.push_back(pHost); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: FAILED!\n"), _DH());); + _releaseHost(pHost); + pHost = 0; + } + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: Attaching to netif %s!\n"), _DH(), (pHost ? "succeeded" : "FAILED"));); + return pHost; +} + +/* + MDNSResponder::_releaseHost +*/ +bool MDNSResponder::_releaseHost(MDNSResponder::clsHost* p_pHost) +{ + bool bResult = false; + + if ((p_pHost) && + (m_HostList.end() != std::find(m_HostList.begin(), m_HostList.end(), p_pHost))) + { + // Delete and remove Responder object + delete p_pHost; + m_HostList.remove(p_pHost); + bResult = true; + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _releaseHost: %s to release netif Responder!\n"), _DH(), (bResult ? "Succeeded" : "FAILED"));); + return bResult; +} + +/* + MDNSResponder::_findHost +*/ +const MDNSResponder::clsHost* MDNSResponder::_findHost(netif* p_pNetIf) const +{ + const clsHost* pResult = 0; + for (const clsHost* pHost : m_HostList) + { + if ((p_pNetIf) && + (&(pHost->m_rNetIf) == p_pNetIf)) + { + pResult = pHost; + break; + } + } + return pResult; +} + +/* + MDNSResponder::_findHost +*/ +MDNSResponder::clsHost* MDNSResponder::_findHost(netif* p_pNetIf) +{ + return (clsHost*)(((const MDNSResponder*)this)->_findHost(p_pNetIf)); +} + +/* + MDNSResponder::_findHost +*/ +const MDNSResponder::clsHost* MDNSResponder::_findHost(const MDNSResponder::hMDNSHost p_hMDNSHost) const +{ + clsHostList::const_iterator it(std::find(m_HostList.begin(), m_HostList.end(), _NRH2Ptr(p_hMDNSHost))); + return ((m_HostList.end() != it) ? *it : 0); +} + +/* + MDNSResponder::_findHost +*/ +MDNSResponder::clsHost* MDNSResponder::_findHost(const MDNSResponder::hMDNSHost p_hMDNSHost) +{ + return (clsHost*)(((const MDNSResponder*)this)->_findHost(p_hMDNSHost)); +} + + +/* + HANDLE HELPERS +*/ + +/* + MDNSResponder::_validateMDNSHostHandle +*/ +bool MDNSResponder::_validateMDNSHostHandle(const hMDNSHost p_hMDNSHost) const +{ + return (0 != _findHost(_NRH2Ptr(p_hMDNSHost))); +} + +/* + MDNSResponder::_validateMDNSHostHandle +*/ +bool MDNSResponder::_validateMDNSHostHandle(const hMDNSHost p_hMDNSHost, + const hMDNSService p_hMDNSService) const +{ + return ((_validateMDNSHostHandle(p_hMDNSHost)) && + (_NRH2Ptr(p_hMDNSHost)->validateService(_SH2Ptr(p_hMDNSService)))); +} + +/* + MDNSResponder::_NRH2Ptr +*/ +MDNSResponder::clsHost* MDNSResponder::_NRH2Ptr(const hMDNSHost p_hMDNSHost) +{ + return (clsHost*)p_hMDNSHost; +} + +/* + MDNSResponder::_NRH2Ptr +*/ +const MDNSResponder::clsHost* MDNSResponder::_NRH2Ptr(const hMDNSHost p_hMDNSHost) const +{ + return (const clsHost*)p_hMDNSHost; +} + +/* + MDNSResponder::_SH2Ptr +*/ +MDNSResponder::clsHost::stcService* MDNSResponder::_SH2Ptr(const hMDNSService p_hMDNSService) +{ + return (clsHost::stcService*)p_hMDNSService; +} + +/* + MDNSResponder::_SH2Ptr +*/ +const MDNSResponder::clsHost::stcService* MDNSResponder::_SH2Ptr(const hMDNSService p_hMDNSService) const +{ + return (const clsHost::stcService*)p_hMDNSService; +} + +/* + MDNSResponder::_begin + + Creates a new netif responder (adding the netif to the multicast groups), + sets up the instance data (hostname, ...) and starts the probing process + +*/ +MDNSResponder::clsHost* MDNSResponder::_begin(const char* p_pcHostName, + netif* p_pNetIf, + MDNSHostProbeResultCallbackFn p_fnCallback) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _begin(%s, netif: %u)\n"), _DH(), (p_pcHostName ?: "_"), (p_pNetIf ? netif_get_index(p_pNetIf) : 0));); + + clsHost* pHost = 0; + if ((!m_pUDPContext) || + (!p_pNetIf) || + (!((pHost = _createHost(p_pNetIf)))) || + (p_fnCallback ? !setHostProbeResultCallback((hMDNSHost)pHost, p_fnCallback) : false) || + (!pHost->setHostName(p_pcHostName)) || + (!pHost->restart())) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _begin: FAILED for '%s'!\n"), _DH(), (p_pcHostName ? : "-"));); + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _begin: %s to init netif with hostname %s!\n"), _DH(), (pHost ? "Succeeded" : "FAILED"), (p_pcHostName ? : "-"));); + return pHost; +} + +/* + MDNSResponder::_close + + The announced host and services are unannounced (by multicasting a goodbye message) + All connected objects and finally the netif Responder is removed. + +*/ +bool MDNSResponder::_close(MDNSResponder::clsHost& p_rHost) +{ + _releaseHost(&p_rHost); // Will call 'delete' on the pHost object! + + return true; +} + + +/* + MISC +*/ + +#if not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_MDNS_RESPONDER + +/* + MDNSResponder::_DH +*/ +const char* MDNSResponder::_DH(const hMDNSHost p_hMDNSHost /*= 0*/) const +{ + static char acBuffer[64]; + + *acBuffer = 0; + if (p_hMDNSHost) + { + sprintf_P(acBuffer, PSTR("[MDNSResponder %s]"), ((WIFI_STA == netif_get_index(&((clsHost*)p_hMDNSHost)->m_rNetIf)) + ? "STA" + : ((WIFI_AP == netif_get_index(&((clsHost*)p_hMDNSHost)->m_rNetIf)) + ? "AP" + : "??"))); + } + else + { + sprintf_P(acBuffer, PSTR("[MDNSResponder]")); + } + return acBuffer; +} + +#endif + + +} // namespace MDNSImplementation + +} // namespace esp8266 diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Host.cpp new file mode 100755 index 0000000000..b9df26ce8f --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Host.cpp @@ -0,0 +1,1336 @@ +/* + LEAmDNS2_Host.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 +#include +#include + +/* + ESP8266mDNS Control.cpp +*/ + +extern "C" { +#include "user_interface.h" +} + +#include "LEAmDNS2_lwIPdefs.h" +#include "LEAmDNS2_Priv.h" + +#ifdef MDNS_IPV4_SUPPORT +#include +#endif +#ifdef MDNS_IPV6_SUPPORT +#include +#endif + + +namespace esp8266 +{ + +/* + LEAmDNS +*/ +namespace experimental +{ + +/* + MDNSResponder::clsHost::clsHost constructor +*/ +MDNSResponder::clsHost::clsHost(netif& p_rNetIf, + UdpContext& p_rUDPContext) + : m_rNetIf(p_rNetIf), + m_NetIfState(static_cast(enuNetIfState::None)), + m_rUDPContext(p_rUDPContext), + m_pcHostName(0), + m_pcInstanceName(0), + m_pServices(0), + m_pQueries(0), + m_fnServiceTxtCallback(0), + m_HostProbeInformation() +{ +} + +/* + MDNSResponder::clsHost::~clsHost destructor +*/ +MDNSResponder::clsHost::~clsHost(void) +{ + _close(); +} + +/* + MDNSResponder::clsHost::init +*/ +bool MDNSResponder::clsHost::init(void) +{ + bool bResult = true; + + // Join multicast group(s) +#ifdef MDNS_IPV4_SUPPORT + ip_addr_t multicast_addr_V4 = DNS_MQUERY_IPV4_GROUP_INIT; + if (!(m_rNetIf.flags & NETIF_FLAG_IGMP)) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: Setting flag: flags & NETIF_FLAG_IGMP\n"), _DH());); + m_rNetIf.flags |= NETIF_FLAG_IGMP; + + if (ERR_OK != igmp_start(&m_rNetIf)) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: igmp_start FAILED!\n"), _DH());); + } + } + + bResult = ((bResult) && + (ERR_OK == igmp_joingroup_netif(&m_rNetIf, ip_2_ip4(&multicast_addr_V4)))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: igmp_joingroup_netif(%s) FAILED!\n"), _DH(), IPAddress(multicast_addr_V4).toString().c_str());); +#endif + +#ifdef MDNS_IPV6_SUPPORT + ip_addr_t multicast_addr_V6 = DNS_MQUERY_IPV6_GROUP_INIT; + bResult = ((bResult) && + (ERR_OK == mld6_joingroup_netif(&m_rNetIf, ip_2_ip6(&multicast_addr_V6)))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: mld6_joingroup_netif FAILED!\n"), _DH());); +#endif + return bResult; +} + +/* + MDNSResponder::clsHost::setHostName +*/ +bool MDNSResponder::clsHost::setHostName(const char* p_pcHostName) +{ + bool bResult; + if ((bResult = _allocHostName(p_pcHostName))) + { + m_HostProbeInformation.m_ProbingStatus = enuProbingStatus::ReadyToStart; + + // Replace 'auto-set' service names + for (stcService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) + { + if ((pService->m_bAutoName) && + (!m_pcInstanceName)) + { + bResult = pService->setName(p_pcHostName); + pService->m_ProbeInformation.m_ProbingStatus = enuProbingStatus::ReadyToStart; + } + } + } + return bResult; +} + +/* + MDNSResponder::clsHost::setHostName +*/ +const char* MDNSResponder::clsHost::hostName(void) const +{ + return m_pcHostName; +} + +/* + MDNSResponder::clsHost::setHostProbeResultCallback +*/ +bool MDNSResponder::clsHost::setHostProbeResultCallback(HostProbeResultCallbackFn p_fnCallback) +{ + m_HostProbeInformation.m_fnProbeResultCallback = p_fnCallback; + return true; +} + +/* + MDNSResponder::clsHost::probeStatus +*/ +bool MDNSResponder::clsHost::probeStatus(void) const +{ + return (enuProbingStatus::Done == m_HostProbeInformation.m_ProbingStatus); +} + + +/* + SERVICE +*/ + +/* + MDNSResponder::clsHost::setInstanceName +*/ +bool MDNSResponder::clsHost::setInstanceName(const char* p_pcInstanceName) +{ + bool bResult; + if ((bResult = _allocInstanceName(p_pcInstanceName))) + { + // Replace 'auto-set' service names + for (stcService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) + { + if (pService->m_bAutoName) + { + bResult = pService->setName(p_pcInstanceName); + pService->m_ProbeInformation.m_ProbingStatus = enuProbingStatus::ReadyToStart; + } + } + } + return bResult; +} + +/* + MDNSResponder::clsHost::instanceName +*/ +const char* MDNSResponder::clsHost::instanceName(void) const +{ + return m_pcInstanceName; +} + +/* + MDNSResponder::clsHost::addService +*/ +MDNSResponder::clsHost::stcService* MDNSResponder::clsHost::addService(const char* p_pcInstanceName, + const char* p_pcServiceType, + const char* p_pcProtocol, + uint16_t p_u16Port) +{ + stcService* pService = 0; + + if (((!p_pcInstanceName) || // NO name OR + (MDNS_DOMAIN_LABEL_MAXLENGTH >= os_strlen(p_pcInstanceName))) && // Fitting name + (p_pcServiceType) && + (MDNS_SERVICE_NAME_LENGTH >= os_strlen(p_pcServiceType)) && + (p_pcProtocol) && + ((MDNS_SERVICE_PROTOCOL_LENGTH - 1) != os_strlen(p_pcProtocol)) && + (p_u16Port)) + { + if (!((pService = findService((p_pcInstanceName ? : (m_pcInstanceName ? : m_pcHostName)), p_pcServiceType, p_pcProtocol, p_u16Port)))) // Not already used + { + if (0 != (pService = _allocService(p_pcInstanceName, p_pcServiceType, p_pcProtocol, p_u16Port))) + { + // Init probing + pService->m_ProbeInformation.m_ProbingStatus = enuProbingStatus::ReadyToStart; + } + } + } // else: bad arguments + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s addService: %s to add service '%s.%s.%s.local'!\n"), _DH(pService), (pService ? "Succeeded" : "FAILED"), (p_pcInstanceName ? : (m_pcInstanceName ? : (m_pcHostName ? : "-"))), (p_pcServiceType ? : ""), (p_pcProtocol ? : ""));); + DEBUG_EX_ERR(if (!pService) DEBUG_OUTPUT.printf_P(PSTR("%s addService: FAILED to add service '%s.%s.%s.local'!\n"), _DH(pService), (p_pcInstanceName ? : (m_pcInstanceName ? : (m_pcHostName ? : "-"))), (p_pcServiceType ? : ""), (p_pcProtocol ? : ""));); + return pService; +} + +/* + MDNSResponder::clsHost::removeService +*/ +bool MDNSResponder::clsHost::removeService(MDNSResponder::clsHost::stcService* p_pMDNSService) +{ + bool bResult = ((p_pMDNSService) && + (_announceService(*p_pMDNSService, false)) && + (_releaseService(p_pMDNSService))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _removeService: FAILED!\n"), _DH(p_pMDNSService));); + return bResult; +} + +/* + MDNSResponder::clsHost::findService (const) +*/ +const MDNSResponder::clsHost::stcService* MDNSResponder::clsHost::findService(const char* p_pcInstanceName, + const char* p_pcServiceType, + const char* p_pcProtocol, + uint16_t p_u16Port /*= 0*/) const +{ + stcService* pService = m_pServices; + while (pService) + { + if ((0 == strcmp(pService->m_pcName, p_pcInstanceName)) && + (0 == strcmp(pService->m_pcServiceType, p_pcServiceType)) && + (0 == strcmp(pService->m_pcProtocol, p_pcProtocol)) && + ((!p_u16Port) || + (p_u16Port == pService->m_u16Port))) + { + + break; + } + pService = pService->m_pNext; + } + return pService; +} + +/* + MDNSResponder::clsHost::findService +*/ +MDNSResponder::clsHost::stcService* MDNSResponder::clsHost::findService(const char* p_pcInstanceName, + const char* p_pcServiceType, + const char* p_pcProtocol, + uint16_t p_u16Port /*= 0*/) +{ + return (stcService*)((const clsHost*)this)->findService(p_pcInstanceName, p_pcServiceType, p_pcProtocol, p_u16Port); +} + +/* + MDNSResponder::clsHost::validateService +*/ +bool MDNSResponder::clsHost::validateService(const MDNSResponder::clsHost::stcService* p_pService) const +{ + const stcService* pService = m_pServices; + while (pService) + { + if (pService == p_pService) + { + break; + } + pService = pService->m_pNext; + } + return (0 != pService); +} + +/* + MDNSResponder::clsHost::setServiceName +*/ +bool MDNSResponder::clsHost::setServiceName(MDNSResponder::clsHost::stcService* p_pMDNSService, + const char* p_pcInstanceName) +{ + p_pcInstanceName = p_pcInstanceName ? : m_pcInstanceName; + + bool bResult = ((p_pMDNSService) && + ((!p_pcInstanceName) || + (MDNS_DOMAIN_LABEL_MAXLENGTH >= os_strlen(p_pcInstanceName))) && + (p_pMDNSService->setName(p_pcInstanceName)) && + ((p_pMDNSService->m_ProbeInformation.m_ProbingStatus = enuProbingStatus::ReadyToStart), true)); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _setServiceName: FAILED for '%s'!\n"), _DH(p_pMDNSService), (p_pcInstanceName ? : "-"));); + return bResult; +} + +/* + MDNSResponder::clsHost::setServiceName +*/ +const char* MDNSResponder::clsHost::serviceName(const stcService* p_pMDNSService) const +{ + return ((p_pMDNSService) + ? (p_pMDNSService->m_pcName) + : 0); +} + +/* + MDNSResponder::clsHost::serviceType +*/ +const char* MDNSResponder::clsHost::serviceType(const stcService* p_pMDNSService) const +{ + return ((p_pMDNSService) + ? (p_pMDNSService->m_pcServiceType) + : 0); +} + +/* + MDNSResponder::clsHost::serviceProtocol +*/ +const char* MDNSResponder::clsHost::serviceProtocol(const stcService* p_pMDNSService) const +{ + return ((p_pMDNSService) + ? (p_pMDNSService->m_pcProtocol) + : 0); +} + +/* + MDNSResponder::clsHost::servicePort +*/ +uint16_t MDNSResponder::clsHost::servicePort(const stcService* p_pMDNSService) const +{ + return ((p_pMDNSService) + ? (p_pMDNSService->m_u16Port) + : 0); +} + + +/* + MDNSResponder::clsHost::setServiceProbeResultCallback +*/ +bool MDNSResponder::clsHost::setServiceProbeResultCallback(stcService* p_pMDNSService, + ServiceProbeResultCallbackFn p_fnCallback) +{ + return ((p_pMDNSService) + ? ((p_pMDNSService->m_ProbeInformation.m_fnProbeResultCallback = p_fnCallback), true) + : false); +} + +/* + MDNSResponder::clsHost::setServiceName +*/ +bool MDNSResponder::clsHost::serviceProbeStatus(const stcService* p_pMDNSService) const +{ + return ((p_pMDNSService) && + (enuProbingStatus::Done == p_pMDNSService->m_ProbeInformation.m_ProbingStatus)); +} + + +/* + SERVICE TXT +*/ + +/* + MDNSResponder::clsHost::addServiceTxt +*/ +MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::addServiceTxt(stcService* p_pMDNSService, + const char* p_pcKey, + const char* p_pcValue) +{ + return _addServiceTxt(p_pMDNSService, p_pcKey, p_pcValue, false); +} + +/* + MDNSResponder::clsHost::removeServiceTxt +*/ +bool MDNSResponder::clsHost::removeServiceTxt(stcService* p_pMDNSService, + stcServiceTxt* p_pTxt) +{ + bool bResult = ((p_pMDNSService) && + (p_pTxt) && + (_releaseServiceTxt(p_pMDNSService, p_pTxt))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeServiceTxt: FAILED!\n"), _DH(p_pMDNSService));); + return bResult; +} + +/* + MDNSResponder::clsHost::findServiceTxt (const) +*/ +const MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::findServiceTxt(MDNSResponder::clsHost::stcService* p_pMDNSService, + const char* p_pcKey) const +{ + return (const stcServiceTxt*)((const clsHost*)this)->findServiceTxt(p_pMDNSService, p_pcKey); +} + +/* + MDNSResponder::clsHost::findServiceTxt +*/ +MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::findServiceTxt(MDNSResponder::clsHost::stcService* p_pMDNSService, + const char* p_pcKey) +{ + return _findServiceTxt(p_pMDNSService, p_pcKey); +} + +/* + MDNSResponder::clsHost::setDynamicServiceTxtCallback +*/ +bool MDNSResponder::clsHost::setDynamicServiceTxtCallback(DynamicServiceTxtCallbackFn p_fnCallback) +{ + m_fnServiceTxtCallback = p_fnCallback; + return true; +} + +/* + MDNSResponder::clsHost::setDynamicServiceTxtCallback +*/ +bool MDNSResponder::clsHost::setDynamicServiceTxtCallback(stcService* p_pMDNSService, + DynamicServiceTxtCallbackFn p_fnCallback) +{ + return ((p_pMDNSService) + ? ((p_pMDNSService->m_fnTxtCallback = p_fnCallback), true) + : false); +} + +/* + MDNSResponder::clsHost::addDynamicServiceTxt + + 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). +*/ +MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::addDynamicServiceTxt(stcService* p_pMDNSService, + const char* p_pcKey, + const char* p_pcValue) +{ + return _addServiceTxt(p_pMDNSService, p_pcKey, p_pcValue, true); +} + + +/* + QUERIES +*/ +/* + MDNSResponder::clsHost::queryService +*/ +uint32_t MDNSResponder::clsHost::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("%s queryService '_%s._%s.local'\n"), _DH(), p_pcService, p_pcProtocol);); + + stcQuery* pMDNSQuery = 0; + if ((p_pcService) && (*p_pcService) && + (p_pcProtocol) && (*p_pcProtocol) && + (p_u16Timeout) && + ((pMDNSQuery = _allocQuery(stcQuery::enuQueryType::Service))) && + (_buildDomainForService(p_pcService, p_pcProtocol, pMDNSQuery->m_Domain))) + { + if ((_removeLegacyQuery()) && + ((pMDNSQuery->m_bLegacyQuery = true)) && + (_sendMDNSQuery(*pMDNSQuery))) + { + // Wait for answers to arrive + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryService: Waiting %u ms for answers...\n"), _DH(), p_u16Timeout);); + delay(p_u16Timeout); + + // All answers should have arrived by now -> stop adding new answers + pMDNSQuery->m_bAwaitingAnswers = false; + } + else // FAILED to send query + { + _removeQuery(pMDNSQuery); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s queryService: INVALID input data!\n"), _DH());); + } + return ((pMDNSQuery) + ? pMDNSQuery->answerCount() + : 0); +} + +/* + MDNSResponder::clsHost::queryHost +*/ +uint32_t MDNSResponder::clsHost::queryHost(const char* p_pcHostName, + const uint16_t p_u16Timeout /*= MDNS_QUERYSERVICES_WAIT_TIME*/) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost '%s.local'\n"), _DH(), p_pcHostName);); + + stcQuery* pMDNSQuery = 0; + if ((p_pcHostName) && (*p_pcHostName) && + (p_u16Timeout) && + ((pMDNSQuery = _allocQuery(stcQuery::enuQueryType::Host))) && + (_buildDomainForHost(p_pcHostName, pMDNSQuery->m_Domain))) + { + if ((_removeLegacyQuery()) && + ((pMDNSQuery->m_bLegacyQuery = true)) && + (_sendMDNSQuery(*pMDNSQuery))) + { + // Wait for answers to arrive + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost: Waiting %u ms for answers...\n"), _DH(), p_u16Timeout);); + delay(p_u16Timeout); + + // All answers should have arrived by now -> stop adding new answers + pMDNSQuery->m_bAwaitingAnswers = false; + } + else // FAILED to send query + { + _removeQuery(pMDNSQuery); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost: INVALID input data!\n"), _DH());); + } + return ((pMDNSQuery) + ? pMDNSQuery->answerCount() + : 0); +} + +/* + MDNSResponder::clsHost::removeQuery +*/ +bool MDNSResponder::clsHost::removeQuery(void) +{ + return _removeLegacyQuery(); +} + +/* + MDNSResponder::clsHost::hasQuery +*/ +bool MDNSResponder::clsHost::hasQuery(void) +{ + return (0 != _findLegacyQuery()); +} + +/* + MDNSResponder::clsHost::getQuery +*/ +MDNSResponder::clsHost::stcQuery* MDNSResponder::clsHost::getQuery(void) +{ + return _findLegacyQuery(); +} + +/* + MDNSResponder::clsHost::installServiceQuery +*/ +MDNSResponder::clsHost::stcQuery* MDNSResponder::clsHost::installServiceQuery(const char* p_pcService, + const char* p_pcProtocol, + MDNSResponder::clsHost::QueryCallbackFn p_fnCallback) +{ + stcQuery* pMDNSQuery = 0; + if ((p_pcService) && (*p_pcService) && + (p_pcProtocol) && (*p_pcProtocol) && + (p_fnCallback) && + ((pMDNSQuery = _allocQuery(stcQuery::enuQueryType::Service))) && + (_buildDomainForService(p_pcService, p_pcProtocol, pMDNSQuery->m_Domain))) + { + + pMDNSQuery->m_fnCallback = p_fnCallback; + pMDNSQuery->m_bLegacyQuery = false; + + if (_sendMDNSQuery(*pMDNSQuery)) + { + pMDNSQuery->m_u8SentCount = 1; + pMDNSQuery->m_ResendTimeout.reset(MDNS_DYNAMIC_QUERY_RESEND_DELAY); + } + else + { + _removeQuery(pMDNSQuery); + } + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s installServiceQuery: %s for '_%s._%s.local'!\n\n"), _DH(), (pMDNSQuery ? "Succeeded" : "FAILED"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); + DEBUG_EX_ERR(if (!pMDNSQuery) DEBUG_OUTPUT.printf_P(PSTR("%s installServiceQuery: FAILED for '_%s._%s.local'!\n\n"), _DH(), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); + return pMDNSQuery; +} + +/* + MDNSResponder::clsHost::installHostQuery +*/ +MDNSResponder::clsHost::stcQuery* MDNSResponder::clsHost::installHostQuery(const char* p_pcHostName, + MDNSResponder::clsHost::QueryCallbackFn p_fnCallback) +{ + stcQuery* pMDNSQuery = 0; + if ((p_pcHostName) && (*p_pcHostName)) + { + stcRRDomain domain; + pMDNSQuery = ((_buildDomainForHost(p_pcHostName, domain)) + ? _installDomainQuery(domain, stcQuery::enuQueryType::Host, p_fnCallback) + : 0); + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s installHostQuery: %s for '%s.local'!\n\n"), _DH(), (pMDNSQuery ? "Succeeded" : "FAILED"), (p_pcHostName ? : "-"));); + DEBUG_EX_ERR(if (!pMDNSQuery) DEBUG_OUTPUT.printf_P(PSTR("%s installHostQuery: FAILED for '%s.local'!\n\n"), _DH(), (p_pcHostName ? : "-"));); + return pMDNSQuery; +} + +/* + MDNSResponder::clsHost::removeQuery +*/ +bool MDNSResponder::clsHost::removeQuery(MDNSResponder::clsHost::stcQuery* p_pMDNSQuery) +{ + bool bResult = ((p_pMDNSQuery) && + (_removeQuery(p_pMDNSQuery))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeQuery: FAILED!\n"), _DH());); + return bResult; +} + + +/* + PROCESSING +*/ + +/* + MDNSResponder::clsHost::processUDPInput +*/ +bool MDNSResponder::clsHost::processUDPInput() +{ + bool bResult = false; + + bResult = ((_checkNetIfState()) && // Any changes in the netif state? + (_parseMessage())); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR(""));); + + return bResult; +} + +/* + MDNSResponder::clsHost::update +*/ +bool MDNSResponder::clsHost::update(void) +{ + return ((_checkNetIfState()) && // Any changes in the netif state? + (_updateProbeStatus()) && // Probing + (_checkQueryCache())); +} + +/* + MDNSResponder::clsHost::restart +*/ +bool MDNSResponder::clsHost::restart(void) +{ + return (_resetProbeStatus(true)); // Stop and restart probing +} + + + + + +/* + P R O T E C T E D +*/ + +/* + MDNSResponder::clsHost::_close +*/ +bool MDNSResponder::clsHost::_close(void) +{ + /* _resetProbeStatus(false); // Stop probing + + _releaseQueries(); + _releaseServices(); + _releaseHostName();*/ + + // Leave multicast group(s) +#ifdef MDNS_IPV4_SUPPORT + ip_addr_t multicast_addr_V4 = DNS_MQUERY_IPV4_GROUP_INIT; + if (ERR_OK != igmp_leavegroup_netif(&m_rNetIf, ip_2_ip4(&multicast_addr_V4)/*(const struct ip4_addr *)&multicast_addr_V4.u_addr.ip4*/)) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("\n"));); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + ip_addr_t multicast_addr_V6 = DNS_MQUERY_IPV6_GROUP_INIT; + if (ERR_OK != mld6_leavegroup_netif(&m_rNetIf, ip_2_ip6(&multicast_addr_V6)/*&(multicast_addr_V6.u_addr.ip6)*/)) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("\n"));); + } +#endif + return true; +} + + +/* + NETIF +*/ + +/* + MDNSResponder::clsHost::_getNetIfState + + Returns the current netif state. + +*/ +MDNSResponder::clsHost::typeNetIfState MDNSResponder::clsHost::_getNetIfState(void) const +{ + typeNetIfState curNetIfState = static_cast(enuNetIfState::None); + + if (netif_is_up(&m_rNetIf)) + { + curNetIfState |= static_cast(enuNetIfState::IsUp); + + // Check if netif link is up + if ((netif_is_link_up(&m_rNetIf)) && + ((&m_rNetIf != netif_get_by_index(WIFI_STA)) || + (STATION_GOT_IP == wifi_station_get_connect_status()))) + { + curNetIfState |= static_cast(enuNetIfState::LinkIsUp); + } + + // Check for IPv4 address + if (_getResponderIPAddress(enuIPProtocolType::V4).isSet()) + { + curNetIfState |= static_cast(enuNetIfState::IPv4); + } + // Check for IPv6 address + if (_getResponderIPAddress(enuIPProtocolType::V6).isSet()) + { + curNetIfState |= static_cast(enuNetIfState::IPv6); + } + } + return curNetIfState; +} + +/* + MDNSResponder::clsHost::_checkNetIfState + + Checks the netif state. + If eg. a new address appears, the announcing is restarted. + +*/ +bool MDNSResponder::clsHost::_checkNetIfState(void) +{ + typeNetIfState curNetIfState; + if (m_NetIfState != ((curNetIfState = _getNetIfState()))) + { + // Some state change happened + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: DID CHANGE NETIF STATE\n\n"), _DH());); + DEBUG_EX_INFO( + if ((curNetIfState & static_cast(enuNetIfState::IsUp)) != (m_NetIfState & static_cast(enuNetIfState::IsUp))) + { + DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Netif is up: %s\n"), _DH(), ((curNetIfState & static_cast(enuNetIfState::IsUp)) ? "YES" : "NO")); + } + if ((curNetIfState & static_cast(enuNetIfState::LinkIsUp)) != (m_NetIfState & static_cast(enuNetIfState::LinkIsUp))) + { + DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Netif link is up: %s\n"), _DH(), ((curNetIfState & static_cast(enuNetIfState::LinkIsUp)) ? "YES" : "NO")); + } + if ((curNetIfState & static_cast(enuNetIfState::IPv4)) != (m_NetIfState & static_cast(enuNetIfState::IPv4))) + { + DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IPv4 address is set: %s\n"), _DH(), ((curNetIfState & static_cast(enuNetIfState::IPv4)) ? "YES" : "NO")); + if (curNetIfState & static_cast(enuNetIfState::IPv4)) + { + DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IPv4 address: %s\n"), _DH(), _getResponderIPAddress(enuIPProtocolType::V4).toString().c_str()); + } + } + if ((curNetIfState & static_cast(enuNetIfState::IPv6)) != (m_NetIfState & static_cast(enuNetIfState::IPv6))) + { + DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IPv6 address is set: %s\n"), _DH(), ((curNetIfState & static_cast(enuNetIfState::IPv6)) ? "YES" : "NO")); + if (curNetIfState & static_cast(enuNetIfState::IPv6)) + { + DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IPv6 address: %s\n"), _DH(), _getResponderIPAddress(enuIPProtocolType::V6).toString().c_str()); + } + } + ); + + if ((curNetIfState & static_cast(enuNetIfState::LinkMask)) != (m_NetIfState & static_cast(enuNetIfState::LinkMask))) + { + // Link came up (restart() will alloc a m_pUDPContext, ...) or down (_restart() will remove an existing m_pUDPContext, ...) + restart(); + } + else if (curNetIfState & static_cast(enuNetIfState::LinkIsUp)) + { + // Link is up (unchanged) + if ((curNetIfState & static_cast(enuNetIfState::IPMask)) != (m_NetIfState & static_cast(enuNetIfState::IPMask))) + { + // IP state changed + // TODO: If just a new IP address was added, a simple re-announcement should be enough + restart(); + } + } + /* if (enuProbingStatus::Done == m_HostProbeInformation.m_ProbingStatus) { + // Probing is done, prepare to (re)announce host + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Preparing to (re)announce host.\n"));); + //m_HostProbeInformation.m_ProbingStatus = enuProbingStatus::Done; + m_HostProbeInformation.m_u8SentCount = 0; + m_HostProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); + }*/ + m_NetIfState = curNetIfState; + } + + bool bResult = ((curNetIfState & static_cast(enuNetIfState::LinkMask)) && // Continue if Link is UP + (curNetIfState & static_cast(enuNetIfState::IPMask))); // AND has any IP + //DEBUG_EX_INFO(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Link is DOWN or NO IP address!\n"), _DH());); + return bResult; +} + + +/* + DOMAIN NAMES +*/ + +/* + MDNSResponder::clsHost::_allocDomainName +*/ +bool MDNSResponder::clsHost::_allocDomainName(const char* p_pcNewDomainName, + char*& p_rpcDomainName) +{ + bool bResult = false; + + _releaseDomainName(p_rpcDomainName); + + size_t stLength = 0; + if ((p_pcNewDomainName) && + (MDNS_DOMAIN_LABEL_MAXLENGTH >= (stLength = strlen(p_pcNewDomainName)))) // char max size for a single label + { + // Copy in hostname characters as lowercase + if ((bResult = (0 != (p_rpcDomainName = new char[stLength + 1])))) + { +#ifdef MDNS_FORCE_LOWERCASE_HOSTNAME + size_t i = 0; + for (; i < stLength; ++i) + { + p_rpcDomainName[i] = (isupper(p_pcNewDomainName[i]) ? tolower(p_pcNewDomainName[i]) : p_pcNewDomainName[i]); + } + p_rpcDomainName[i] = 0; +#else + strncpy(p_rpcDomainName, p_pcNewDomainName, (stLength + 1)); +#endif + } + } + return bResult; +} + +/* + MDNSResponder::clsHost::_releaseDomainName +*/ +bool MDNSResponder::clsHost::_releaseDomainName(char*& p_rpcDomainName) +{ + bool bResult; + if ((bResult = (0 != p_rpcDomainName))) + { + delete[] p_rpcDomainName; + p_rpcDomainName = 0; + } + return bResult; +} + +/* + MDNSResponder::clsHost::_allocHostName +*/ +bool MDNSResponder::clsHost::_allocHostName(const char* p_pcHostName) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocHostName (%s)\n"), _DH(), p_pcHostName);); + return _allocDomainName(p_pcHostName, m_pcHostName); +} + +/* + MDNSResponder::clsHost::_releaseHostName +*/ +bool MDNSResponder::clsHost::_releaseHostName(void) +{ + return _releaseDomainName(m_pcHostName); +} + +/* + MDNSResponder::clsHost::_allocInstanceName +*/ +bool MDNSResponder::clsHost::_allocInstanceName(const char* p_pcInstanceName) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocInstanceName (%s)\n"), _DH(), p_pcHostName);); + return _allocDomainName(p_pcInstanceName, m_pcInstanceName); +} + +/* + MDNSResponder::clsHost::_releaseInstanceName +*/ +bool MDNSResponder::clsHost::_releaseInstanceName(void) +{ + return _releaseDomainName(m_pcInstanceName); +} + + +/* + SERVICE +*/ + +/* + MDNSResponder::clsHost::_allocService +*/ +MDNSResponder::clsHost::stcService* MDNSResponder::clsHost::_allocService(const char* p_pcName, + const char* p_pcServiceType, + const char* p_pcProtocol, + uint16_t p_u16Port) +{ + stcService* pService = 0; + if (((!p_pcName) || + (MDNS_DOMAIN_LABEL_MAXLENGTH >= strlen(p_pcName))) && + (p_pcServiceType) && + (MDNS_SERVICE_NAME_LENGTH >= strlen(p_pcServiceType)) && + (p_pcProtocol) && + (MDNS_SERVICE_PROTOCOL_LENGTH >= strlen(p_pcProtocol)) && + (p_u16Port) && + (0 != ((pService = new stcService))) && + (pService->setName(p_pcName ? : (m_pcInstanceName ? : m_pcHostName))) && + (pService->setServiceType(p_pcServiceType)) && + (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::clsHost::_releaseService +*/ +bool MDNSResponder::clsHost::_releaseService(MDNSResponder::clsHost::stcService* p_pService) +{ + bool bResult = false; + + if (p_pService) + { + stcService* 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.printf_P(PSTR("%s _releaseService: INVALID service!"), _DH(p_pService));); + } + } + } + return bResult; +} + + +/* + SERVICE TXT +*/ + +/* + MDNSResponder::clsHost::_allocServiceTxt +*/ +MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::_allocServiceTxt(MDNSResponder::clsHost::stcService* p_pService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp) +{ + stcServiceTxt* pTxt = 0; + + if ((p_pService) && + (p_pcKey) && + (MDNS_SERVICE_TXT_MAXLENGTH > (p_pService->m_Txts.length() + + 1 + // Length byte + (p_pcKey ? strlen(p_pcKey) : 0) + + 1 + // '=' + (p_pcValue ? strlen(p_pcValue) : 0)))) + { + + pTxt = new stcServiceTxt; + if (pTxt) + { + size_t stLength = (p_pcKey ? strlen(p_pcKey) : 0); + pTxt->m_pcKey = new char[stLength + 1]; + if (pTxt->m_pcKey) + { + strncpy(pTxt->m_pcKey, p_pcKey, stLength); pTxt->m_pcKey[stLength] = 0; + } + + if (p_pcValue) + { + stLength = (p_pcValue ? strlen(p_pcValue) : 0); + pTxt->m_pcValue = new char[stLength + 1]; + if (pTxt->m_pcValue) + { + strncpy(pTxt->m_pcValue, p_pcValue, stLength); pTxt->m_pcValue[stLength] = 0; + } + } + pTxt->m_bTemp = p_bTemp; + + // Add to list (or start list) + p_pService->m_Txts.add(pTxt); + } + } + return pTxt; +} + +/* + MDNSResponder::clsHost::_releaseServiceTxt +*/ +bool MDNSResponder::clsHost::_releaseServiceTxt(MDNSResponder::clsHost::stcService* p_pService, + MDNSResponder::clsHost::stcServiceTxt* p_pTxt) +{ + return ((p_pService) && + (p_pTxt) && + (p_pService->m_Txts.remove(p_pTxt))); +} + +/* + MDNSResponder::clsHost::_updateServiceTxt +*/ +MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::_updateServiceTxt(MDNSResponder::clsHost::stcService* p_pService, + MDNSResponder::clsHost::stcServiceTxt* p_pTxt, + const char* p_pcValue, + bool p_bTemp) +{ + if ((p_pService) && + (p_pTxt) && + (MDNS_SERVICE_TXT_MAXLENGTH > (p_pService->m_Txts.length() - + (p_pTxt->m_pcValue ? strlen(p_pTxt->m_pcValue) : 0) + + (p_pcValue ? strlen(p_pcValue) : 0)))) + { + p_pTxt->update(p_pcValue); + p_pTxt->m_bTemp = p_bTemp; + } + return p_pTxt; +} + +/* + MDNSResponder::clsHost::_findServiceTxt +*/ +MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::_findServiceTxt(MDNSResponder::clsHost::stcService* p_pService, + const char* p_pcKey) +{ + return (p_pService ? p_pService->m_Txts.find(p_pcKey) : 0); +} + +/* + MDNSResponder::clsHost::_addServiceTxt +*/ +MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::_addServiceTxt(MDNSResponder::clsHost::stcService* p_pService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp) +{ + stcServiceTxt* pResult = 0; + + if ((p_pService) && + (p_pcKey) && + (strlen(p_pcKey))) + { + + stcServiceTxt* 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::clsHost::_answerKeyValue + / + MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::_answerKeyValue(const MDNSResponder::clsHost::stcQuery p_pQuery, + const uint32_t p_u32AnswerIndex) + { + stcQuery::stcAnswer* pSQAnswer = (p_pQuery ? p_pQuery->answerAtIndex(p_u32AnswerIndex) : 0); + // Fill m_pcTxts (if not already done) + return (pSQAnswer) ? pSQAnswer->m_Txts.m_pTxts : 0; + }*/ + +/* + MDNSResponder::clsHost::_collectServiceTxts +*/ +bool MDNSResponder::clsHost::_collectServiceTxts(MDNSResponder::clsHost::stcService& p_rService) +{ + if (m_fnServiceTxtCallback) + { + //m_fnServiceTxtCallback(*this, p_pService); + } + if (p_rService.m_fnTxtCallback) + { + //p_pService->m_fnTxtCallback(*this, p_pService); + } + return true; +} + +/* + MDNSResponder::clsHost::_releaseTempServiceTxts +*/ +bool MDNSResponder::clsHost::_releaseTempServiceTxts(MDNSResponder::clsHost::stcService& p_rService) +{ + return (p_rService.m_Txts.removeTempTxts()); +} + + +/* + QUERIES +*/ + +/* + MDNSResponder::_allocQuery +*/ +MDNSResponder::clsHost::stcQuery* MDNSResponder::clsHost::_allocQuery(MDNSResponder::clsHost::stcQuery::enuQueryType p_QueryType) +{ + stcQuery* pQuery = new stcQuery(p_QueryType); + if (pQuery) + { + // Link to query list + pQuery->m_pNext = m_pQueries; + m_pQueries = pQuery; + } + return m_pQueries; +} + +/* + MDNSResponder:clsHost:::_removeQuery +*/ +bool MDNSResponder::clsHost::_removeQuery(MDNSResponder::clsHost::stcQuery* p_pQuery) +{ + bool bResult = false; + + if (p_pQuery) + { + stcQuery* pPred = m_pQueries; + while ((pPred) && + (pPred->m_pNext != p_pQuery)) + { + pPred = pPred->m_pNext; + } + if (pPred) + { + pPred->m_pNext = p_pQuery->m_pNext; + delete p_pQuery; + bResult = true; + } + else // No predecesor + { + if (m_pQueries == p_pQuery) + { + m_pQueries = p_pQuery->m_pNext; + delete p_pQuery; + bResult = true; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _releaseQuery: INVALID query!"), _DH());); + } + } + } + return bResult; +} + +/* + MDNSResponder::clsHost::_removeLegacyQuery +*/ +bool MDNSResponder::clsHost::_removeLegacyQuery(void) +{ + stcQuery* pLegacyQuery = 0; + return (((pLegacyQuery = _findLegacyQuery())) + ? _removeQuery(pLegacyQuery) + : false); +} + +/* + MDNSResponder::clsHost::_findLegacyQuery +*/ +MDNSResponder::clsHost::stcQuery* MDNSResponder::clsHost::_findLegacyQuery(void) +{ + stcQuery* pLegacyQuery = m_pQueries; + while (pLegacyQuery) + { + if (pLegacyQuery->m_bLegacyQuery) + { + break; + } + pLegacyQuery = pLegacyQuery->m_pNext; + } + return pLegacyQuery; +} + +/* + MDNSResponder::clsHost::_releaseQueries +*/ +bool MDNSResponder::clsHost::_releaseQueries(void) +{ + while (m_pQueries) + { + stcQuery* pNext = m_pQueries->m_pNext; + delete m_pQueries; + m_pQueries = pNext; + } + return true; +} + +/* + MDNSResponder::clsHost::_findNextQueryByDomain +*/ +MDNSResponder::clsHost::stcQuery* MDNSResponder::clsHost::_findNextQueryByDomain(const MDNSResponder::clsHost::stcRRDomain& p_Domain, + const MDNSResponder::clsHost::stcQuery::enuQueryType p_QueryType, + const stcQuery* p_pPrevQuery) +{ + stcQuery* pMatchingQuery = 0; + + stcQuery* pQuery = (p_pPrevQuery ? p_pPrevQuery->m_pNext : m_pQueries); + while (pQuery) + { + if (((stcQuery::enuQueryType::None == p_QueryType) || + (pQuery->m_QueryType == p_QueryType)) && + (p_Domain == pQuery->m_Domain)) + { + + pMatchingQuery = pQuery; + break; + } + pQuery = pQuery->m_pNext; + } + return pMatchingQuery; +} + +/* + MDNSResponder::clsHost::_installDomainQuery +*/ +MDNSResponder::clsHost::stcQuery* MDNSResponder::clsHost::_installDomainQuery(MDNSResponder::clsHost::stcRRDomain& p_Domain, + MDNSResponder::clsHost::stcQuery::enuQueryType p_QueryType, + MDNSResponder::clsHost::QueryCallbackFn p_fnCallback) +{ + stcQuery* pQuery = 0; + + if ((p_fnCallback) && + ((pQuery = _allocQuery(p_QueryType)))) + { + pQuery->m_Domain = p_Domain; + pQuery->m_fnCallback = p_fnCallback; + pQuery->m_bLegacyQuery = false; + + if (_sendMDNSQuery(*pQuery)) + { + pQuery->m_u8SentCount = 1; + pQuery->m_ResendTimeout.reset(MDNS_DYNAMIC_QUERY_RESEND_DELAY); + } + else + { + _removeQuery(pQuery); + } + } + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _installDomainQuery: %s for "), (pQuery ? "Succeeded" : "FAILED"), _DH()); + _printRRDomain(p_Domain); + DEBUG_OUTPUT.println(); + ); + DEBUG_EX_ERR( + if (!pQuery) +{ + DEBUG_OUTPUT.printf_P(PSTR("%s _installDomainQuery: FAILED for "), _DH()); + _printRRDomain(p_Domain); + DEBUG_OUTPUT.println(); + } + ); + return pQuery; +} + +/* + MDNSResponder::clsHost::_hasQueriesWaitingForAnswers +*/ +bool MDNSResponder::clsHost::_hasQueriesWaitingForAnswers(void) const +{ + bool bOpenQueries = false; + + for (stcQuery* pQuery = m_pQueries; pQuery; pQuery = pQuery->m_pNext) + { + if (pQuery->m_bAwaitingAnswers) + { + bOpenQueries = true; + break; + } + } + return bOpenQueries; +} + +/* + MDNSResponder::clsHost::_executeQueryCallback +*/ +bool MDNSResponder::clsHost::_executeQueryCallback(const stcQuery& p_Query, + const stcQuery::stcAnswer& p_Answer, + typeQueryAnswerType p_QueryAnswerTypeFlags, + bool p_bSetContent) +{ + if (p_Query.m_fnCallback) + { + p_Query.m_fnCallback(*this, p_Query, p_Answer, p_QueryAnswerTypeFlags, p_bSetContent); + } + return true; +} + + + +} // namespace MDNSImplementation + +} // namespace esp8266 diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Host.hpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Host.hpp new file mode 100755 index 0000000000..4a5ff6f39d --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Host.hpp @@ -0,0 +1,1177 @@ +/* + LEAmDNS2_Host.hpp + + 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. + +*/ + +/** + clsHost & clsHostList +*/ +class clsHost +{ +public: + + // File: ..._Host_Structs + /** + typeIPProtocolType & enuIPProtocolType + */ + using typeIPProtocolType = uint8_t; + enum class enuIPProtocolType : typeIPProtocolType + { +#ifdef MDNS_IPV4_SUPPORT + V4 = 0x01, +#endif +#ifdef MDNS_IPV6_SUPPORT + V6 = 0x02, +#endif + }; + + /** + typeNetIfState & enuNetIfState + */ + using typeNetIfState = uint8_t; + enum class enuNetIfState : typeNetIfState + { + None = 0x00, + + IsUp = 0x01, + UpMask = (IsUp), + + LinkIsUp = 0x02, + LinkMask = (LinkIsUp), + + IPv4 = 0x04, + IPv6 = 0x08, + IPMask = (IPv4 | IPv6), + }; + + /** + stcServiceTxt + */ + struct stcServiceTxt + { + stcServiceTxt* m_pNext; + char* m_pcKey; + char* m_pcValue; + bool m_bTemp; + + stcServiceTxt(const char* p_pcKey = 0, + const char* p_pcValue = 0, + bool p_bTemp = false); + stcServiceTxt(const stcServiceTxt& p_Other); + ~stcServiceTxt(void); + + stcServiceTxt& operator=(const stcServiceTxt& 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; + }; + + /** + stcServiceTxts + */ + struct stcServiceTxts + { + stcServiceTxt* m_pTxts; + char* m_pcCache; + + stcServiceTxts(void); + stcServiceTxts(const stcServiceTxts& p_Other); + ~stcServiceTxts(void); + + stcServiceTxts& operator=(const stcServiceTxts& p_Other); + + bool clear(void); + bool clearCache(void); + + bool add(stcServiceTxt* p_pTxt); + bool remove(stcServiceTxt* p_pTxt); + + bool removeTempTxts(void); + + stcServiceTxt* find(const char* p_pcKey); + const stcServiceTxt* find(const char* p_pcKey) const; + stcServiceTxt* find(const stcServiceTxt* p_pTxt); + + uint16_t length(void) const; + + size_t c_strLength(void) const; + bool c_str(char* p_pcBuffer); + const char* c_str(void) const; + + size_t bufferLength(void) const; + bool buffer(char* p_pcBuffer); + + bool compare(const stcServiceTxts& p_Other) const; + bool operator==(const stcServiceTxts& p_Other) const; + bool operator!=(const stcServiceTxts& p_Other) const; + }; + + /** + typeProbingStatus & enuProbingStatus + */ + using typeProbingStatus = uint8_t; + enum class enuProbingStatus : typeProbingStatus + { + WaitingForData, + ReadyToStart, + InProgress, + Done + }; + + /** + stcProbeInformation_Base + */ + struct stcProbeInformation_Base + { + enuProbingStatus m_ProbingStatus; + uint8_t m_u8SentCount; // Used for probes and announcements + esp8266::polledTimeout::oneShot m_Timeout; // Used for probes and announcements + bool m_bConflict; + bool m_bTiebreakNeeded; + + stcProbeInformation_Base(void); + + bool clear(void); // No 'virtual' needed, no polymorphic use (save 4 bytes) + }; + + /** + HostProbeResultCallbackFn + Callback function for host domain probe results + */ + using HostProbeResultCallbackFn = std::function; + /** + MDNSServiceProbeResultCallbackFn + Callback function for service domain probe results + */ + struct stcService; + using ServiceProbeResultCallbackFn = std::function; + + /** + stcProbeInformation_Host + */ + struct stcProbeInformation_Host : public stcProbeInformation_Base + { + HostProbeResultCallbackFn m_fnProbeResultCallback; + + stcProbeInformation_Host(void); + + bool clear(bool p_bClearUserdata = false); + }; + + /** + stcProbeInformation_Service + */ + struct stcProbeInformation_Service : public stcProbeInformation_Base + { + ServiceProbeResultCallbackFn m_fnProbeResultCallback; + + stcProbeInformation_Service(void); + + bool clear(bool p_bClearUserdata = false); + }; + + /** + DynamicServiceTxtCallbackFn + Callback function for dynamic MDNS TXT items + */ + struct stcService; + using DynamicServiceTxtCallbackFn = std::function; + + /** + stcService + */ + struct stcService + { + stcService* m_pNext; + char* m_pcName; + bool m_bAutoName; // Name was set automatically to hostname (if no name was supplied) + char* m_pcServiceType; + char* m_pcProtocol; + uint16_t m_u16Port; + uint32_t m_u32ReplyMask; + stcServiceTxts m_Txts; + DynamicServiceTxtCallbackFn m_fnTxtCallback; + stcProbeInformation_Service m_ProbeInformation; + + stcService(const char* p_pcName = 0, + const char* p_pcServiceType = 0, + const char* p_pcProtocol = 0); + ~stcService(void); + + bool setName(const char* p_pcName); + bool releaseName(void); + + bool setServiceType(const char* p_pcService); + bool releaseServiceType(void); + + bool setProtocol(const char* p_pcProtocol); + bool releaseProtocol(void); + + bool probeStatus(void) const; + }; + + /** + typeContentFlag & enuContentFlag + */ + using typeContentFlag = uint16_t; + enum class enuContentFlag : typeContentFlag + { + // Host + A = 0x0001, + PTR_IPv4 = 0x0002, + PTR_IPv6 = 0x0004, + AAAA = 0x0008, + // Service + PTR_TYPE = 0x0010, + PTR_NAME = 0x0020, + TXT = 0x0040, + SRV = 0x0080, + // DNSSEC + NSEC = 0x0100, + + PTR = (PTR_IPv4 | PTR_IPv6 | PTR_TYPE | PTR_NAME) + }; + + /** + stcMsgHeader + */ + struct stcMsgHeader + { + uint16_t m_u16ID; // Identifier + bool m_1bQR : 1; // Query/Response flag + uint8_t m_4bOpcode : 4; // Operation code + bool m_1bAA : 1; // Authoritative Answer flag + bool m_1bTC : 1; // Truncation flag + bool m_1bRD : 1; // Recursion desired + bool m_1bRA : 1; // Recursion available + uint8_t m_3bZ : 3; // Zero + uint8_t m_4bRCode : 4; // Response code + uint16_t m_u16QDCount; // Question count + uint16_t m_u16ANCount; // Answer count + uint16_t m_u16NSCount; // Authority Record count + uint16_t m_u16ARCount; // Additional Record count + + stcMsgHeader(uint16_t p_u16ID = 0, + bool p_bQR = false, + uint8_t p_u8Opcode = 0, + bool p_bAA = false, + bool p_bTC = false, + bool p_bRD = false, + bool p_bRA = false, + uint8_t p_u8RCode = 0, + uint16_t p_u16QDCount = 0, + uint16_t p_u16ANCount = 0, + uint16_t p_u16NSCount = 0, + uint16_t p_u16ARCount = 0); + }; + + /** + stcRRDomain + */ + struct stcRRDomain + { + char m_acName[MDNS_DOMAIN_MAXLENGTH]; // Encoded domain name + uint16_t m_u16NameLength; // Length (incl. '\0') + char* m_pcDecodedName; + + stcRRDomain(void); + stcRRDomain(const stcRRDomain& p_Other); + ~stcRRDomain(void); + + stcRRDomain& operator=(const stcRRDomain& p_Other); + + bool clear(void); + bool clearNameCache(void); + + bool addLabel(const char* p_pcLabel, + bool p_bPrependUnderline = false); + + bool compare(const stcRRDomain& p_Other) const; + bool operator==(const stcRRDomain& p_Other) const; + bool operator!=(const stcRRDomain& p_Other) const; + bool operator>(const stcRRDomain& p_Other) const; + + size_t c_strLength(void) const; + bool c_str(char* p_pcBuffer) const; + const char* c_str(void) const; + }; + + /** + stcRRAttributes + */ + struct stcRRAttributes + { + uint16_t m_u16Type; // Type + uint16_t m_u16Class; // Class, nearly always 'IN' + + stcRRAttributes(uint16_t p_u16Type = 0, + uint16_t p_u16Class = 1 /*DNS_RRCLASS_IN Internet*/); + stcRRAttributes(const stcRRAttributes& p_Other); + + stcRRAttributes& operator=(const stcRRAttributes& p_Other); + }; + + /** + stcRRHeader + */ + struct stcRRHeader + { + stcRRDomain m_Domain; + stcRRAttributes m_Attributes; + + stcRRHeader(void); + stcRRHeader(const stcRRHeader& p_Other); + + stcRRHeader& operator=(const stcRRHeader& p_Other); + + bool clear(void); + }; + + /** + stcRRQuestion + */ + struct stcRRQuestion + { + stcRRQuestion* m_pNext; + stcRRHeader m_Header; + bool m_bUnicast; // Unicast reply requested + + stcRRQuestion(void); + }; + + /** + stcNSECBitmap + */ + struct stcNSECBitmap + { + uint8_t m_au8BitmapData[6]; // 6 bytes data + + stcNSECBitmap(void); + + bool clear(void); + uint16_t length(void) const; + bool setBit(uint16_t p_u16Bit); + bool getBit(uint16_t p_u16Bit) const; + }; + + /** + typeAnswerType & enuAnswerType + */ + using typeAnswerType = uint8_t; + enum class enuAnswerType : typeAnswerType + { + A, + PTR, + TXT, + AAAA, + SRV, + //NSEC, + Generic + }; + + /** + stcRRAnswer + */ + struct stcRRAnswer + { + stcRRAnswer* m_pNext; + const enuAnswerType m_AnswerType; + stcRRHeader m_Header; + bool m_bCacheFlush; // Cache flush command bit + uint32_t m_u32TTL; // Validity time in seconds + + virtual ~stcRRAnswer(void); + + enuAnswerType answerType(void) const; + + bool clear(void); + + protected: + stcRRAnswer(enuAnswerType p_AnswerType, + const stcRRHeader& p_Header, + uint32_t p_u32TTL); + }; + +#ifdef MDNS_IPV4_SUPPORT + /** + stcRRAnswerA + */ + struct stcRRAnswerA : public stcRRAnswer + { + IPAddress m_IPAddress; + + stcRRAnswerA(const stcRRHeader& p_Header, + uint32_t p_u32TTL); + ~stcRRAnswerA(void); + + bool clear(void); + }; +#endif + + /** + stcRRAnswerPTR + */ + struct stcRRAnswerPTR : public stcRRAnswer + { + stcRRDomain m_PTRDomain; + + stcRRAnswerPTR(const stcRRHeader& p_Header, + uint32_t p_u32TTL); + ~stcRRAnswerPTR(void); + + bool clear(void); + }; + + /** + stcRRAnswerTXT + */ + struct stcRRAnswerTXT : public stcRRAnswer + { + stcServiceTxts m_Txts; + + stcRRAnswerTXT(const stcRRHeader& p_Header, + uint32_t p_u32TTL); + ~stcRRAnswerTXT(void); + + bool clear(void); + }; + +#ifdef MDNS_IPV6_SUPPORT + /** + stcRRAnswerAAAA + */ + struct stcRRAnswerAAAA : public stcRRAnswer + { + IPAddress m_IPAddress; + + stcRRAnswerAAAA(const stcRRHeader& p_Header, + uint32_t p_u32TTL); + ~stcRRAnswerAAAA(void); + + bool clear(void); + }; +#endif + + /** + stcRRAnswerSRV + */ + struct stcRRAnswerSRV : public stcRRAnswer + { + uint16_t m_u16Priority; + uint16_t m_u16Weight; + uint16_t m_u16Port; + stcRRDomain m_SRVDomain; + + stcRRAnswerSRV(const stcRRHeader& p_Header, + uint32_t p_u32TTL); + ~stcRRAnswerSRV(void); + + bool clear(void); + }; + + /** + stcRRAnswerGeneric + */ + struct stcRRAnswerGeneric : public stcRRAnswer + { + uint16_t m_u16RDLength; // Length of variable answer + uint8_t* m_pu8RDData; // Offset of start of variable answer in packet + + stcRRAnswerGeneric(const stcRRHeader& p_Header, + uint32_t p_u32TTL); + ~stcRRAnswerGeneric(void); + + bool clear(void); + }; + + + /** + stcSendParameter + */ + struct stcSendParameter + { + protected: + /** + stcDomainCacheItem + */ + 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); + }; + + public: + /** + typeResponseType & enuResponseType + */ + using typeResponseType = uint8_t; + enum class enuResponseType : typeResponseType + { + None, + Response, + Unsolicited + }; + + uint16_t m_u16ID; // Query ID (used only in lagacy queries) + stcRRQuestion* m_pQuestions; // A list of queries + uint32_t m_u32HostReplyMask; // Flags for reply components/answers + bool m_bLegacyQuery; // Flag: Legacy query + enuResponseType m_Response; // Enum: Response to a query + bool m_bAuthorative; // Flag: Authorative (owner) response + bool m_bCacheFlush; // Flag: Clients should flush their caches + bool m_bUnicast; // Flag: Unicast response + bool m_bUnannounce; // Flag: Unannounce service + + // Temp content; created while processing _prepareMessage + uint16_t m_u16Offset; // Current offset in UDP write buffer (mainly for domain cache) + stcDomainCacheItem* m_pDomainCacheItems; // Cached host and service domains + + stcSendParameter(void); + ~stcSendParameter(void); + + bool clear(void); + bool flushQuestions(void); + bool flushDomainCache(void); + bool flushTempContent(void); + + bool shiftOffset(uint16_t p_u16Shift); + + bool addDomainCacheItem(const void* p_pHostNameOrService, + bool p_bAdditionalData, + uint16_t p_u16Offset); + uint16_t findCachedDomainOffset(const void* p_pHostNameOrService, + bool p_bAdditionalData) const; + }; + + + // QUERIES & ANSWERS + /** + typeQueryAnswerType & enuQueryAnswerType + */ + using typeQueryAnswerType = uint8_t; + enum class enuQueryAnswerType : typeQueryAnswerType + { + Unknown = 0x00, + ServiceDomain = 0x01, // Service domain + HostDomain = 0x02, // Host domain + Port = 0x04, // Port + Txts = 0x08, // TXT items +#ifdef MDNS_IPV4_SUPPORT + IPv4Address = 0x10, // IPv4 address +#endif +#ifdef MDNS_IPV6_SUPPORT + IPv6Address = 0x20, // IPv6 address +#endif + }; + + /** + stcQuery + */ + struct stcQuery + { + /** + stcAnswer + */ + struct stcAnswer + { + /** + stcTTL + */ + struct stcTTL + { + /** + typeTimeoutLevel & enuTimeoutLevel + */ + using typeTimeoutLevel = uint8_t; + enum class enuTimeoutLevel : typeTimeoutLevel + { + None = 0, + Base = 80, + Interval = 5, + Final = 100 + }; + + uint32_t m_u32TTL; + esp8266::polledTimeout::oneShot m_TTLTimeout; + typeTimeoutLevel m_TimeoutLevel; + + stcTTL(void); + bool set(uint32_t p_u32TTL); + + bool flagged(void) const; + bool restart(void); + + bool prepareDeletion(void); + bool finalTimeoutLevel(void) const; + + unsigned long timeout(void) const; + }; + /** + stcIPAddress + */ + struct stcIPAddress + { + stcIPAddress* m_pNext; + IPAddress m_IPAddress; + stcTTL m_TTL; + + stcIPAddress(IPAddress p_IPAddress, + uint32_t p_u32TTL = 0); + }; + + 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. + stcRRDomain m_ServiceDomain; // 1. level answer (PTR), eg. MyESP._http._tcp.local + stcTTL m_TTLServiceDomain; + stcRRDomain m_HostDomain; // 2. level answer (SRV, using service domain), eg. esp8266.local + uint16_t m_u16Port; // 2. level answer (SRV, using service domain), eg. 5000 + stcTTL m_TTLHostDomainAndPort; + stcServiceTxts m_Txts; // 2. level answer (TXT, using service domain), eg. c#=1 + stcTTL m_TTLTxts; +#ifdef MDNS_IPV4_SUPPORT + stcIPAddress* m_pIPv4Addresses; // 3. level answer (A, using host domain), eg. 123.456.789.012 +#endif +#ifdef MDNS_IPV6_SUPPORT + stcIPAddress* m_pIPv6Addresses; // 3. level answer (AAAA, using host domain), eg. 1234::09 +#endif + typeQueryAnswerType m_QueryAnswerFlags; // enuQueryAnswerType + + stcAnswer(void); + ~stcAnswer(void); + + bool clear(void); + +#ifdef MDNS_IPV4_SUPPORT + bool releaseIPv4Addresses(void); + bool addIPv4Address(stcIPAddress* p_pIPAddress); + bool removeIPv4Address(stcIPAddress* p_pIPAddress); + const stcIPAddress* findIPv4Address(const IPAddress& p_IPAddress) const; + stcIPAddress* findIPv4Address(const IPAddress& p_IPAddress); + uint32_t IPv4AddressCount(void) const; + const stcIPAddress* IPv4AddressAtIndex(uint32_t p_u32Index) const; + stcIPAddress* IPv4AddressAtIndex(uint32_t p_u32Index); +#endif +#ifdef MDNS_IPV6_SUPPORT + bool releaseIPv6Addresses(void); + bool addIPv6Address(stcIPAddress* p_pIPAddress); + bool removeIPv6Address(stcIPAddress* p_pIPAddress); + const stcIPAddress* findIPv6Address(const IPAddress& p_IPAddress) const; + stcIPAddress* findIPv6Address(const IPAddress& p_IPAddress); + uint32_t IPv6AddressCount(void) const; + const stcIPAddress* IPv6AddressAtIndex(uint32_t p_u32Index) const; + stcIPAddress* IPv6AddressAtIndex(uint32_t p_u32Index); +#endif + }; //stcAnswer + + /** + typeQueryType & enuQueryType + */ + using typeQueryType = uint8_t; + enum class enuQueryType : typeQueryType + { + None, + Service, + Host + }; + using _QueryCallbackFn = std::function; // true: Answer component set, false: component deleted + + stcQuery* m_pNext; + enuQueryType m_QueryType; + stcRRDomain m_Domain; // Type:Service -> _http._tcp.local; Type:Host -> esp8266.local + _QueryCallbackFn m_fnCallback; + bool m_bLegacyQuery; + uint8_t m_u8SentCount; + esp8266::polledTimeout::oneShot m_ResendTimeout; + bool m_bAwaitingAnswers; + stcAnswer* m_pAnswers; + + stcQuery(const enuQueryType p_QueryType); + ~stcQuery(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 stcRRDomain& p_ServiceDomain); + stcAnswer* findAnswerForHostDomain(const stcRRDomain& p_HostDomain); + }; + /** + QueryCallbackFn + + Callback function for received answers for dynamic queries + */ + using QueryCallbackFn = stcQuery::_QueryCallbackFn; + +public: + clsHost(netif& p_rNetIf, + UdpContext& p_rUDPContext); + ~clsHost(void); + + bool init(void); + + // HOST + bool setHostName(const char* p_pcHostName); + const char* hostName(void) const; + + bool setHostProbeResultCallback(HostProbeResultCallbackFn p_fnCallback); + + // Returns 'true' is host domain probing is done + bool probeStatus(void) const; + + // SERVICE + bool setInstanceName(const char* p_pcInstanceName); + const char* instanceName(void) const; + + stcService* addService(const char* p_pcInstanceName, + const char* p_pcServiceType, + const char* p_pcProtocol, + uint16_t p_u16Port); + bool removeService(stcService* p_pMDNSService); + + const stcService* findService(const char* p_pcInstanceName, + const char* p_pcServiceType, + const char* p_pcProtocol, + uint16_t p_u16Port = 0) const; + stcService* findService(const char* p_pcInstanceName, + const char* p_pcServiceType, + const char* p_pcProtocol, + uint16_t p_u16Port = 0); + bool validateService(const stcService* p_pService) const; + + bool setServiceName(stcService* p_pMDNSService, + const char* p_pcInstanceName); + const char* serviceName(const stcService* p_pMDNSService) const; + const char* serviceType(const stcService* p_pMDNSService) const; + const char* serviceProtocol(const stcService* p_pMDNSService) const; + uint16_t servicePort(const stcService* p_pMDNSService) const; + + // Set a service specific probe result callcack + bool setServiceProbeResultCallback(stcService* p_pMDNSService, + ServiceProbeResultCallbackFn p_fnCallback); + + bool serviceProbeStatus(const stcService* p_pMDNSService) const; + + // SERVICE TXT + // Add a (static) MDNS TXT item ('key' = 'value') to the service + stcServiceTxt* addServiceTxt(stcService* p_pMDNSService, + const char* p_pcKey, + const char* p_pcValue); + bool removeServiceTxt(stcService* p_pMDNSService, + stcServiceTxt* p_pTxt); + const stcServiceTxt* findServiceTxt(stcService* p_pMDNSService, + const char* p_pcKey) const; + stcServiceTxt* findServiceTxt(stcService* p_pMDNSService, + const char* p_pcKey); + + bool setDynamicServiceTxtCallback(DynamicServiceTxtCallbackFn p_fnCallback); + bool setDynamicServiceTxtCallback(stcService* p_pMDNSService, + DynamicServiceTxtCallbackFn p_fnCallback); + + // 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). + stcServiceTxt* addDynamicServiceTxt(stcService* p_pMDNSService, + const char* p_pcKey, + const char* p_pcValue); + + // QUERIES + + // - STATIC + // Perform a (static) service/host query. The function returns after p_u16Timeout milliseconds + // The answers (the number of received answers is returned) can be retrieved by calling + // - answerHostName (or hostname) + // - answerIP (or IP) + // - answerPort (or port) + uint32_t queryService(const char* p_pcService, + const char* p_pcProtocol, + const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME); + uint32_t queryHost(const char* p_pcHostName, + const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME); + bool removeQuery(void); + bool hasQuery(void); + stcQuery* getQuery(void); + + // - DYNAMIC + // Install a dynamic service/host query. For every received answer (part) the given callback + // function is called. The query will be updated every time, the TTL for an answer + // has timed-out. + // The answers can also be retrieved by calling + // - answerCount service/host (for host queries, this should never be >1) + // - answerServiceDomain service + // - hasAnswerHostDomain/answerHostDomain service/host + // - hasAnswerIPv4Address/answerIPv4Address service/host + // - hasAnswerIPv6Address/answerIPv6Address service/host + // - hasAnswerPort/answerPort service + // - hasAnswerTxts/answerTxts service + stcQuery* installServiceQuery(const char* p_pcServiceType, + const char* p_pcProtocol, + QueryCallbackFn p_fnCallback); + stcQuery* installHostQuery(const char* p_pcHostName, + QueryCallbackFn p_fnCallback); + // Remove a dynamic service query + bool removeQuery(stcQuery* p_pMDNSQuery); + + + + + + // PROCESSING + bool processUDPInput(void); + bool update(void); + + bool announce(bool p_bAnnounce, + bool p_bIncludeServices); + bool announceService(stcService* p_pService, + bool p_bAnnounce = true); + + bool restart(void); + +protected: + // File: ..._Host + bool _close(void); + + // NETIF + typeNetIfState _getNetIfState(void) const; + bool _checkNetIfState(void); + + // DOMAIN NAMES + bool _allocDomainName(const char* p_pcNewDomainName, + char*& p_rpcDomainName); + bool _releaseDomainName(char*& p_rpcDomainName); + bool _allocHostName(const char* p_pcHostName); + bool _releaseHostName(void); + bool _allocInstanceName(const char* p_pcInstanceName); + bool _releaseInstanceName(void); + + // SERVICE + stcService* _allocService(const char* p_pcName, + const char* p_pcServiceType, + const char* p_pcProtocol, + uint16_t p_u16Port); + bool _releaseService(stcService* p_pService); + + // SERVICE TXT + stcServiceTxt* _allocServiceTxt(stcService* p_pService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp); + bool _releaseServiceTxt(stcService* p_pService, + stcServiceTxt* p_pTxt); + stcServiceTxt* _updateServiceTxt(stcService* p_pService, + stcServiceTxt* p_pTxt, + const char* p_pcValue, + bool p_bTemp); + stcServiceTxt* _findServiceTxt(stcService* p_pService, + const char* p_pcKey); + stcServiceTxt* _addServiceTxt(stcService* p_pService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp); + stcServiceTxt* _answerKeyValue(const stcQuery p_pQuery, + const uint32_t p_u32AnswerIndex); + bool _collectServiceTxts(stcService& p_rService); + bool _releaseTempServiceTxts(stcService& p_rService); + + // QUERIES + stcQuery* _allocQuery(stcQuery::enuQueryType p_QueryType); + bool _removeQuery(stcQuery* p_pQuery); + bool _removeLegacyQuery(void); + stcQuery* _findLegacyQuery(void); + bool _releaseQueries(void); + stcQuery* _findNextQueryByDomain(const stcRRDomain& p_Domain, + const stcQuery::enuQueryType p_QueryType, + const stcQuery* p_pPrevQuery); + stcQuery* _installDomainQuery(stcRRDomain& p_Domain, + stcQuery::enuQueryType p_QueryType, + QueryCallbackFn p_fnCallback); + bool _hasQueriesWaitingForAnswers(void) const; + bool _executeQueryCallback(const stcQuery& p_Query, + const stcQuery::stcAnswer& p_Answer, + typeQueryAnswerType p_QueryAnswerTypeFlags, + bool p_SetContent); + + + // File: ..._Host_Control + // RECEIVING + bool _parseMessage(void); + bool _parseQuery(const stcMsgHeader& p_Header); + + bool _parseResponse(const stcMsgHeader& p_Header); + bool _processAnswers(const stcRRAnswer* p_pPTRAnswers); + bool _processPTRAnswer(const stcRRAnswerPTR* p_pPTRAnswer, + bool& p_rbFoundNewKeyAnswer); + bool _processSRVAnswer(const stcRRAnswerSRV* p_pSRVAnswer, + bool& p_rbFoundNewKeyAnswer); + bool _processTXTAnswer(const stcRRAnswerTXT* p_pTXTAnswer); +#ifdef MDNS_IPV4_SUPPORT + bool _processAAnswer(const stcRRAnswerA* p_pAAnswer); +#endif +#ifdef MDNS_IPV6_SUPPORT + bool _processAAAAAnswer(const stcRRAnswerAAAA* p_pAAAAAnswer); +#endif + + // PROBING + bool _updateProbeStatus(void); + bool _resetProbeStatus(bool p_bRestart = true); + bool _hasProbesWaitingForAnswers(void) const; + bool _sendHostProbe(void); + bool _sendServiceProbe(stcService& p_rService); + bool _cancelProbingForHost(void); + bool _cancelProbingForService(stcService& p_rService); + bool _callHostProbeResultCallback(bool p_bResult); + bool _callServiceProbeResultCallback(stcService& p_rService, + bool p_bResult); + + // ANNOUNCE + bool _announce(bool p_bAnnounce, + bool p_bIncludeServices); + bool _announceService(stcService& p_pService, + bool p_bAnnounce = true); + + // QUERY CACHE + bool _checkQueryCache(void); + + uint32_t _replyMaskForHost(const stcRRHeader& p_RRHeader, + bool* p_pbFullNameMatch = 0) const; + uint32_t _replyMaskForService(const stcRRHeader& p_RRHeader, + const stcService& p_Service, + bool* p_pbFullNameMatch = 0) const; + + + // File: ..._Host_Transfer + // SENDING + bool _sendMDNSMessage(stcSendParameter& p_SendParameter); + bool _sendMDNSMessage_Multicast(stcSendParameter& p_rSendParameter, + uint8_t p_IPProtocolTypes); + bool _prepareMDNSMessage(stcSendParameter& p_SendParameter); + bool _addMDNSQueryRecord(stcSendParameter& p_rSendParameter, + const stcRRDomain& p_QueryDomain, + uint16_t p_u16QueryType); + bool _sendMDNSQuery(const stcQuery& p_Query, + stcQuery::stcAnswer* p_pKnownAnswers = 0); + bool _sendMDNSQuery(const stcRRDomain& p_QueryDomain, + uint16_t p_u16RecordType, + stcQuery::stcAnswer* p_pKnownAnswers = 0); + + IPAddress _getResponderIPAddress(enuIPProtocolType p_IPProtocolType) const; + + // RESOURCE RECORD + bool _readRRQuestion(stcRRQuestion& p_rQuestion); + bool _readRRAnswer(stcRRAnswer*& p_rpAnswer); +#ifdef MDNS_IPV4_SUPPORT + bool _readRRAnswerA(stcRRAnswerA& p_rRRAnswerA, + uint16_t p_u16RDLength); +#endif + bool _readRRAnswerPTR(stcRRAnswerPTR& p_rRRAnswerPTR, + uint16_t p_u16RDLength); + bool _readRRAnswerTXT(stcRRAnswerTXT& p_rRRAnswerTXT, + uint16_t p_u16RDLength); +#ifdef MDNS_IPV6_SUPPORT + bool _readRRAnswerAAAA(stcRRAnswerAAAA& p_rRRAnswerAAAA, + uint16_t p_u16RDLength); +#endif + bool _readRRAnswerSRV(stcRRAnswerSRV& p_rRRAnswerSRV, + uint16_t p_u16RDLength); + bool _readRRAnswerGeneric(stcRRAnswerGeneric& p_rRRAnswerGeneric, + uint16_t p_u16RDLength); + + bool _readRRHeader(stcRRHeader& p_rHeader); + bool _readRRDomain(stcRRDomain& p_rRRDomain); + bool _readRRDomain_Loop(stcRRDomain& p_rRRDomain, + uint8_t p_u8Depth); + bool _readRRAttributes(stcRRAttributes& p_rAttributes); + + // DOMAIN NAMES + bool _buildDomainForHost(const char* p_pcHostName, + stcRRDomain& p_rHostDomain) const; + bool _buildDomainForDNSSD(stcRRDomain& p_rDNSSDDomain) const; + bool _buildDomainForService(const stcService& p_Service, + bool p_bIncludeName, + stcRRDomain& p_rServiceDomain) const; + bool _buildDomainForService(const char* p_pcService, + const char* p_pcProtocol, + stcRRDomain& p_rServiceDomain) const; +#ifdef MDNS_IPV4_SUPPORT + bool _buildDomainForReverseIPv4(IPAddress p_IPv4Address, + stcRRDomain& p_rReverseIPv4Domain) const; +#endif +#ifdef MDNS_IPV6_SUPPORT + bool _buildDomainForReverseIPv6(IPAddress p_IPv4Address, + stcRRDomain& p_rReverseIPv6Domain) const; +#endif + + // UDP + bool _udpReadBuffer(unsigned char* p_pBuffer, + size_t p_stLength); + bool _udpRead8(uint8_t& p_ru8Value); + bool _udpRead16(uint16_t& p_ru16Value); + bool _udpRead32(uint32_t& p_ru32Value); + + bool _udpAppendBuffer(const unsigned char* p_pcBuffer, + size_t p_stLength); + bool _udpAppend8(uint8_t p_u8Value); + bool _udpAppend16(uint16_t p_u16Value); + bool _udpAppend32(uint32_t p_u32Value); + +#if not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_MDNS_RESPONDER + bool _udpDump(bool p_bMovePointer = false); + bool _udpDump(unsigned p_uOffset, + unsigned p_uLength); +#endif + + // READ/WRITE MDNS STRUCTS + bool _readMDNSMsgHeader(stcMsgHeader& p_rMsgHeader); + + bool _write8(uint8_t p_u8Value, + stcSendParameter& p_rSendParameter); + bool _write16(uint16_t p_u16Value, + stcSendParameter& p_rSendParameter); + bool _write32(uint32_t p_u32Value, + stcSendParameter& p_rSendParameter); + + bool _writeMDNSMsgHeader(const stcMsgHeader& p_MsgHeader, + stcSendParameter& p_rSendParameter); + bool _writeMDNSRRAttributes(const stcRRAttributes& p_Attributes, + stcSendParameter& p_rSendParameter); + bool _writeMDNSRRDomain(const stcRRDomain& p_Domain, + stcSendParameter& p_rSendParameter); + bool _writeMDNSHostDomain(const char* m_pcHostName, + bool p_bPrependRDLength, + uint16_t p_u16AdditionalLength, + stcSendParameter& p_rSendParameter); + bool _writeMDNSServiceDomain(const stcService& p_Service, + bool p_bIncludeName, + bool p_bPrependRDLength, + uint16_t p_u16AdditionalLength, + stcSendParameter& p_rSendParameter); + + bool _writeMDNSQuestion(stcRRQuestion& p_Question, + stcSendParameter& p_rSendParameter); + +#ifdef MDNS_IPV4_SUPPORT + bool _writeMDNSAnswer_A(IPAddress p_IPAddress, + stcSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_PTR_IPv4(IPAddress p_IPAddress, + stcSendParameter& p_rSendParameter); +#endif + bool _writeMDNSAnswer_PTR_TYPE(stcService& p_rService, + stcSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_PTR_NAME(stcService& p_rService, + stcSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_TXT(stcService& p_rService, + stcSendParameter& p_rSendParameter); +#ifdef MDNS_IPV6_SUPPORT + bool _writeMDNSAnswer_AAAA(IPAddress p_IPAddress, + stcSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_PTR_IPv6(IPAddress p_IPAddress, + stcSendParameter& p_rSendParameter); +#endif + bool _writeMDNSAnswer_SRV(stcService& p_rService, + stcSendParameter& p_rSendParameter); + stcNSECBitmap* _createNSECBitmap(uint32_t p_u32NSECContent); + bool _writeMDNSNSECBitmap(const stcNSECBitmap& p_NSECBitmap, + stcSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_NSEC(uint32_t p_u32NSECContent, + stcSendParameter& p_rSendParameter); +#ifdef MDNS_IPV4_SUPPORT + bool _writeMDNSAnswer_NSEC_PTR_IPv4(IPAddress p_IPAddress, + stcSendParameter& p_rSendParameter); +#endif +#ifdef MDNS_IPV6_SUPPORT + bool _writeMDNSAnswer_NSEC_PTR_IPv6(IPAddress p_IPAddress, + stcSendParameter& p_rSendParameter); +#endif + bool _writeMDNSAnswer_NSEC(stcService& p_rService, + uint32_t p_u32NSECContent, + stcSendParameter& p_rSendParameter); + + + // File: ..._Host_Debug +#if not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_MDNS_RESPONDER + const char* _DH(const stcService* p_pMDNSService = 0) const; + const char* _service2String(const stcService* p_pMDNSService) const; + + bool _printRRDomain(const stcRRDomain& p_rRRDomain) const; + bool _printRRAnswer(const stcRRAnswer& p_RRAnswer) const; + const char* _RRType2Name(uint16_t p_u16RRType) const; + const char* _RRClass2String(uint16_t p_u16RRClass, + bool p_bIsQuery) const; + const char* _replyFlags2String(uint32_t p_u32ReplyFlags) const; + const char* _NSECBitmap2String(const stcNSECBitmap* p_pNSECBitmap) const; +#endif + + +public: + netif& m_rNetIf; + typeNetIfState m_NetIfState; + UdpContext& m_rUDPContext; + + char* m_pcHostName; + char* m_pcInstanceName; + stcService* m_pServices; + stcQuery* m_pQueries; + DynamicServiceTxtCallbackFn m_fnServiceTxtCallback; + stcProbeInformation_Host m_HostProbeInformation; +}; +using clsHostList = std::list; + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Control.cpp new file mode 100755 index 0000000000..7b4402c587 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Control.cpp @@ -0,0 +1,2207 @@ +/* + LEAmDNS2_Host_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 +#include + +/* + ESP8266mDNS Control.cpp +*/ + +extern "C" { +#include "user_interface.h" +} + +#include "LEAmDNS2_lwIPdefs.h" +#include "LEAmDNS2_Priv.h" + +namespace esp8266 +{ +/* + LEAmDNS +*/ +namespace experimental +{ + +/** + RECEIVING +*/ + +/* + MDNSResponder::clsHost::_parseMessage +*/ +bool MDNSResponder::clsHost::_parseMessage(void) +{ + DEBUG_EX_INFO( + unsigned long ulStartTime = millis(); + unsigned uStartMemory = ESP.getFreeHeap(); + DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage (Time: %lu ms, heap: %u bytes, from %s, to %s)\n"), _DH(), ulStartTime, uStartMemory, + m_rUDPContext.getRemoteAddress().toString().c_str(), + m_rUDPContext.getDestAddress().toString().c_str()); + ); + //DEBUG_EX_INFO(_udpDump();); + + bool bResult = false; + + stcMsgHeader header; + if (_readMDNSMsgHeader(header)) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), + _DH(), + (unsigned)header.m_u16ID, + (unsigned)header.m_1bQR, (unsigned)header.m_4bOpcode, (unsigned)header.m_1bAA, (unsigned)header.m_1bTC, (unsigned)header.m_1bRD, + (unsigned)header.m_1bRA, (unsigned)header.m_4bRCode, + (unsigned)header.m_u16QDCount, + (unsigned)header.m_u16ANCount, + (unsigned)header.m_u16NSCount, + (unsigned)header.m_u16ARCount)); + if (0 == header.m_4bOpcode) // A standard query + { + if (header.m_1bQR) // Received a response -> answers to a query + { + //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: Reading answers: ID:%u, Q:%u, A:%u, NS:%u, AR:%u\n"), _DH(), header.m_u16ID, header.m_u16QDCount, header.m_u16ANCount, header.m_u16NSCount, header.m_u16ARCount);); + bResult = _parseResponse(header); + } + else // Received a query (Questions) + { + //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: Reading query: ID:%u, Q:%u, A:%u, NS:%u, AR:%u\n"), _DH(), header.m_u16ID, header.m_u16QDCount, header.m_u16ANCount, header.m_u16NSCount, header.m_u16ARCount);); + bResult = _parseQuery(header); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: Received UNEXPECTED opcode:%u. Ignoring message!\n"), _DH(), header.m_4bOpcode);); + m_rUDPContext.flush(); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: FAILED to read header\n"), _DH());); + m_rUDPContext.flush(); + } + DEBUG_EX_INFO( + unsigned uFreeHeap = ESP.getFreeHeap(); + DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: Done (%s after %lu ms, ate %i bytes, remaining %u)\n\n"), _DH(), (bResult ? "Succeeded" : "FAILED"), (millis() - ulStartTime), (uStartMemory - uFreeHeap), uFreeHeap); + ); + return bResult; +} + +/* + MDNSResponder::clsHost::_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::clsHost::_parseQuery(const MDNSResponder::clsHost::stcMsgHeader& p_MsgHeader) +{ + bool bResult = true; + + stcSendParameter sendParameter; + uint32_t u32HostOrServiceReplies = 0; + for (uint16_t qd = 0; ((bResult) && (qd < p_MsgHeader.m_u16QDCount)); ++qd) + { + stcRRQuestion questionRR; + if ((bResult = _readRRQuestion(questionRR))) + { + // Define host replies, BUT only answer queries after probing is done + u32HostOrServiceReplies = + sendParameter.m_u32HostReplyMask |= ((probeStatus()) + ? _replyMaskForHost(questionRR.m_Header, 0) + : 0); + DEBUG_EX_INFO(if (u32HostOrServiceReplies) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Host reply needed %s\n"), _DH(), _replyFlags2String(u32HostOrServiceReplies));); + + // Check tiebreak need for host domain + if (enuProbingStatus::InProgress == m_HostProbeInformation.m_ProbingStatus) + { + bool bFullNameMatch = false; + if ((_replyMaskForHost(questionRR.m_Header, &bFullNameMatch)) && + (bFullNameMatch)) + { + // We're in 'probing' state and someone is asking for our host domain: this might be + // a race-condition: Two host with the same domain names try simutanously to probe their domains + // See: RFC 6762, 8.2 (Tiebraking) + // However, we're using a max. reduced approach for tiebreaking here: The higher IP-address wins! + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Possible race-condition for host domain detected while probing.\n"), _DH());); + Serial.printf_P(PSTR("%s _parseQuery: Possible race-condition for host domain detected while probing.\n"), _DH()); + + m_HostProbeInformation.m_bTiebreakNeeded = true; + } + } + + // Define service replies + for (stcService* pService = m_pServices; pService; pService = pService->m_pNext) + { + // Define service replies, BUT only answer queries after probing is done + uint32_t u32ReplyMaskForQuestion = ((pService->probeStatus()) + ? _replyMaskForService(questionRR.m_Header, *pService, 0) + : 0); + u32HostOrServiceReplies |= (pService->m_u32ReplyMask |= u32ReplyMaskForQuestion); + DEBUG_EX_INFO(if (u32ReplyMaskForQuestion) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service reply needed: %s\n"), _DH(pService), _replyFlags2String(u32ReplyMaskForQuestion));); + + // Check tiebreak need for service domain + if (enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) + { + bool bFullNameMatch = false; + if ((_replyMaskForService(questionRR.m_Header, *pService, &bFullNameMatch)) && + (bFullNameMatch)) + { + // We're in 'probing' state and someone is asking for this service domain: this might be + // a race-condition: Two services with the same domain names try simutanously to probe their domains + // See: RFC 6762, 8.2 (Tiebraking) + // However, we're using a max. reduced approach for tiebreaking here: The 'higher' SRV host wins! + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Possible race-condition for service domain detected while probing.\n"), _DH(pService));); + Serial.printf_P(PSTR("%s _parseQuery: Possible race-condition for service domain detected while probing.\n"), _DH(pService)); + + 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_rUDPContext.getRemotePort()) || // Unicast (maybe legacy) query OR + (questionRR.m_bUnicast)) && // Expressivly unicast query + (!sendParameter.m_bUnicast)) + { + + sendParameter.m_bUnicast = true; + //sendParameter.m_bCacheFlush = false; + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Unicast response asked for %s!\n"), _DH(), m_rUDPContext.getRemoteAddress().toString().c_str());); + //Serial.printf_P(PSTR("%s _parseQuery: Ignored Unicast response asked for by %s!\n"), _DH(), IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str()); + + if ((DNS_MQUERY_PORT != m_rUDPContext.getRemotePort()) && // Unicast (maybe legacy) query AND + (1 == p_MsgHeader.m_u16QDCount) && // Only one question AND + ((sendParameter.m_u32HostReplyMask) || // Host replies OR + (u32HostOrServiceReplies))) // Host or service replies available + { + // Local host check + // We're a match for this legacy query, BUT + // make sure, that the query comes from a local host +#ifdef MDNS_IPV4_SUPPORT + ip_info IPInfo_Local; +#endif + if ( +#ifdef MDNS_IPV4_SUPPORT + (m_rUDPContext.getRemoteAddress().isV4()) && + ((wifi_get_ip_info(netif_get_index(&m_rNetIf), &IPInfo_Local))) && + (ip4_addr_netcmp(ip_2_ip4((const ip_addr_t*)m_rUDPContext.getRemoteAddress()), &IPInfo_Local.ip, &IPInfo_Local.netmask)) +#else + (true) +#endif + && +#ifdef MDNS_IPV6_SUPPORT + (m_rUDPContext.getRemoteAddress().isV6()) && + (ip6_addr_islinklocal(ip_2_ip6((const ip_addr_t*)m_rUDPContext.getRemoteAddress()))) +#else + (true) +#endif + ) + { + /* ip_info IPInfo_Local; + ip_info IPInfo_Remote; + if (((IPInfo_Remote.ip.addr = m_pUDPContext->getRemoteAddress())) && + (((wifi_get_ip_info(SOFTAP_IF, &IPInfo_Local)) && + (ip4_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)) && + (ip4_addr_netcmp(&IPInfo_Remote.ip, &IPInfo_Local.ip, &IPInfo_Local.netmask))))) // Remote IP in STATION's subnet + {*/ + Serial.println("\n\n\nUNICAST QUERY\n\n"); + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Legacy query from local host %s!\n"), _DH(), m_rUDPContext.getRemoteAddress().toString().c_str());); + + sendParameter.m_u16ID = p_MsgHeader.m_u16ID; + sendParameter.m_bLegacyQuery = true; + sendParameter.m_bCacheFlush = false; + sendParameter.m_pQuestions = new stcRRQuestion; + 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("%s _parseQuery: FAILED to add legacy question!\n"), _DH());); + } + } + else + { + Serial.println("\n\n\nINVALID UNICAST QUERY\n\n"); + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Legacy query from NON-LOCAL host!\n"), _DH());); + bResult = false; + } + } + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: FAILED to read question!\n"), _DH());); + } + } // for questions + + //DEBUG_EX_INFO(if (u8HostOrServiceReplies) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Reply needed: %u (%s: %s->%s)\n"), _DH(), u8HostOrServiceReplies, clsTimeSyncer::timestr(), IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str(), IPAddress(m_pUDPContext->getDestAddress()).toString().c_str());); + + // Handle known answers + uint32_t u32Answers = (p_MsgHeader.m_u16ANCount + p_MsgHeader.m_u16NSCount + p_MsgHeader.m_u16ARCount); + if ((u32HostOrServiceReplies) && + (u32Answers)) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Reading known answers(%u):\n"), _DH(), u32Answers);); + + for (uint32_t an = 0; ((bResult) && (an < u32Answers)); ++an) + { + stcRRAnswer* pKnownRRAnswer = 0; + if (((bResult = _readRRAnswer(pKnownRRAnswer))) && + (pKnownRRAnswer)) + { + + if ((DNS_RRTYPE_ANY != pKnownRRAnswer->m_Header.m_Attributes.m_u16Type) && // No ANY type answer + (DNS_RRCLASS_ANY != (pKnownRRAnswer->m_Header.m_Attributes.m_u16Class & (~0x8000)))) // No ANY class answer + { + + // Find match between planned answer (sendParameter.m_u8HostReplyMask) and this 'known answer' + uint32_t u32HostMatchMask = (sendParameter.m_u32HostReplyMask & _replyMaskForHost(pKnownRRAnswer->m_Header)); + if ((u32HostMatchMask) && // 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 (enuAnswerType::PTR == pKnownRRAnswer->answerType()) + { + stcRRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostName, hostDomain)) && + (((stcRRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain == hostDomain)) + { + // Host domain match +#ifdef MDNS_IPV4_SUPPORT + if (u32HostMatchMask & static_cast(enuContentFlag::PTR_IPv4)) + { + // IPv4 PTR was asked for, but is already known -> skipping + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: IPv4 PTR already known... skipping!\n"), _DH());); + sendParameter.m_u32HostReplyMask &= ~static_cast(enuContentFlag::PTR_IPv4); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + if (u32HostMatchMask & static_cast(enuContentFlag::PTR_IPv6)) + { + // IPv6 PTR was asked for, but is already known -> skipping + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: IPv6 PTR already known... skipping!\n"), _DH());); + sendParameter.m_u32HostReplyMask &= ~static_cast(enuContentFlag::PTR_IPv6); + } +#endif + } + } + else if (u32HostMatchMask & static_cast(enuContentFlag::A)) + { + // IPv4 address was asked for +#ifdef MDNS_IPV4_SUPPORT + if ((enuAnswerType::A == pKnownRRAnswer->answerType()) && + (((stcRRAnswerA*)pKnownRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V4))) + { + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: IPv4 address already known... skipping!\n"), _DH());); + sendParameter.m_u32HostReplyMask &= ~static_cast(enuContentFlag::A); + } // else: RData NOT IPv4 length !! +#endif + } + else if (u32HostMatchMask & static_cast(enuContentFlag::AAAA)) + { + // IPv6 address was asked for +#ifdef MDNS_IPV6_SUPPORT + if ((enuAnswerType::AAAA == pKnownRRAnswer->answerType()) && + (((stcRRAnswerAAAA*)pKnownRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V6))) + { + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: IPv6 address already known... skipping!\n"), _DH());); + sendParameter.m_u32HostReplyMask &= ~static_cast(enuContentFlag::AAAA); + } // else: RData NOT IPv6 length !! +#endif + } + } // Host match /*and TTL*/ + + // + // Check host tiebreak possibility + if (m_HostProbeInformation.m_bTiebreakNeeded) + { + stcRRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostName, hostDomain)) && + (pKnownRRAnswer->m_Header.m_Domain == hostDomain)) + { + // Host domain match +#ifdef MDNS_IPV4_SUPPORT + if (enuAnswerType::A == pKnownRRAnswer->answerType()) + { + // CHECK + IPAddress localIPAddress(_getResponderIPAddress(enuIPProtocolType::V4)); + if (((stcRRAnswerA*)pKnownRRAnswer)->m_IPAddress == localIPAddress) + { + // SAME IP address -> We've received an old message from ourselfs (same IP) + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv4) WON (was an old message)!\n"), _DH());); + m_HostProbeInformation.m_bTiebreakNeeded = false; + } + else + { + if ((uint32_t)(((stcRRAnswerA*)pKnownRRAnswer)->m_IPAddress) > (uint32_t)localIPAddress) // The OTHER IP is 'higher' -> LOST + { + // LOST tiebreak + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv4) LOST (lower)!\n"), _DH());); + _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("%s _parseQuery: Tiebreak (IPv4) WON (higher IP)!\n"), _DH());); + m_HostProbeInformation.m_bTiebreakNeeded = false; + } + } + } +#endif +#ifdef MDNS_IPV6_SUPPORT + if (enuAnswerType::AAAA == pKnownRRAnswer->answerType()) + { + // TODO / CHECK + IPAddress localIPAddress(_getResponderIPAddress(enuIPProtocolType::V6)); + if (((stcRRAnswerAAAA*)pKnownRRAnswer)->m_IPAddress == localIPAddress) + { + // SAME IP address -> We've received an old message from ourselfs (same IP) + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv6) WON (was an old message)!\n"), _DH());); + m_HostProbeInformation.m_bTiebreakNeeded = false; + } + else + { + if ((uint32_t)(((stcRRAnswerAAAA*)pKnownRRAnswer)->m_IPAddress) > (uint32_t)localIPAddress) // The OTHER IP is 'higher' -> LOST + { + // LOST tiebreak + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv6) LOST (lower)!\n"), _DH());); + _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("%s _parseQuery: Tiebreak (IPv6) WON (higher IP)!\n"), _DH());); + m_HostProbeInformation.m_bTiebreakNeeded = false; + } + } + } +#endif + } + } // Host tiebreak possibility + + // Check service answers + for (stcService* pService = m_pServices; pService; pService = pService->m_pNext) + { + + uint32_t u32ServiceMatchMask = (pService->m_u32ReplyMask & _replyMaskForService(pKnownRRAnswer->m_Header, *pService)); + + if ((u32ServiceMatchMask) && // The RR in the known answer matches an RR we are planning to send, AND + ((MDNS_SERVICE_TTL / 2) <= pKnownRRAnswer->m_u32TTL)) // The TTL of the known answer is longer than half of the new service TTL (4500s) + { + + if (enuAnswerType::PTR == pKnownRRAnswer->answerType()) + { + stcRRDomain serviceDomain; + if ((u32ServiceMatchMask & static_cast(enuContentFlag::PTR_TYPE)) && + (_buildDomainForService(*pService, false, serviceDomain)) && + (serviceDomain == ((stcRRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain)) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service type PTR already known... skipping!\n"), _DH());); + pService->m_u32ReplyMask &= ~static_cast(enuContentFlag::PTR_TYPE); + } + if ((u32ServiceMatchMask & static_cast(enuContentFlag::PTR_NAME)) && + (_buildDomainForService(*pService, true, serviceDomain)) && + (serviceDomain == ((stcRRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain)) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service name PTR already known... skipping!\n"), _DH());); + pService->m_u32ReplyMask &= ~static_cast(enuContentFlag::PTR_NAME); + } + } + else if (u32ServiceMatchMask & static_cast(enuContentFlag::SRV)) + { + DEBUG_EX_ERR(if (enuAnswerType::SRV != pKnownRRAnswer->answerType()) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: ERROR! INVALID answer type (SRV)!\n"), _DH());); + stcRRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostName, hostDomain)) && + (hostDomain == ((stcRRAnswerSRV*)pKnownRRAnswer)->m_SRVDomain)) // Host domain match + { + + if ((MDNS_SRV_PRIORITY == ((stcRRAnswerSRV*)pKnownRRAnswer)->m_u16Priority) && + (MDNS_SRV_WEIGHT == ((stcRRAnswerSRV*)pKnownRRAnswer)->m_u16Weight) && + (pService->m_u16Port == ((stcRRAnswerSRV*)pKnownRRAnswer)->m_u16Port)) + { + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service SRV answer already known... skipping!\n"), _DH());); + pService->m_u32ReplyMask &= ~static_cast(enuContentFlag::SRV); + } // else: Small differences -> send update message + } + } + else if (u32ServiceMatchMask & static_cast(enuContentFlag::TXT)) + { + DEBUG_EX_ERR(if (enuAnswerType::TXT != pKnownRRAnswer->answerType()) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: ERROR! INVALID answer type (TXT)!\n"), _DH());); + _collectServiceTxts(*pService); + if (pService->m_Txts == ((stcRRAnswerTXT*)pKnownRRAnswer)->m_Txts) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service TXT answer already known... skipping!\n"), _DH());); + pService->m_u32ReplyMask &= ~static_cast(enuContentFlag::TXT); + } + _releaseTempServiceTxts(*pService); + } + } // Service match and enough TTL + + // + // Check service tiebreak possibility + if (pService->m_ProbeInformation.m_bTiebreakNeeded) + { + stcRRDomain serviceDomain; + if ((_buildDomainForService(*pService, true, serviceDomain)) && + (pKnownRRAnswer->m_Header.m_Domain == serviceDomain)) + { + // Service domain match + if (enuAnswerType::SRV == pKnownRRAnswer->answerType()) + { + stcRRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostName, hostDomain)) && + (hostDomain == ((stcRRAnswerSRV*)pKnownRRAnswer)->m_SRVDomain)) // Host domain match + { + + // We've received an old message from ourselfs (same SRV) + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (SRV) won (was an old message)!\n"), _DH());); + pService->m_ProbeInformation.m_bTiebreakNeeded = false; + } + else + { + if (((stcRRAnswerSRV*)pKnownRRAnswer)->m_SRVDomain > hostDomain) // The OTHER domain is 'higher' -> LOST + { + // LOST tiebreak + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (SRV) LOST (lower)!\n"), _DH());); + _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("%s _parseQuery: Tiebreak (SRV) won (higher)!\n"), _DH());); + pService->m_ProbeInformation.m_bTiebreakNeeded = false; + } + } + } + } + } // service tiebreak possibility + } // for services + } // ANY answers + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: FAILED to read known answer!\n"), _DH());); + } + + if (pKnownRRAnswer) + { + delete pKnownRRAnswer; + pKnownRRAnswer = 0; + } + } // for answers + } + else + { + DEBUG_EX_INFO(if (u32Answers) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Skipped %u known answers!\n"), _DH(), u32Answers);); + m_rUDPContext.flush(); + } + + if (bResult) + { + // Check, if a reply is needed + uint32_t u32ReplyNeeded = sendParameter.m_u32HostReplyMask; + for (stcService* pService = m_pServices; pService; pService = pService->m_pNext) + { + u32ReplyNeeded |= pService->m_u32ReplyMask; + } + + if (u32ReplyNeeded) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Sending answer(%s)...\n"), _DH(), _replyFlags2String(u32ReplyNeeded));); + + sendParameter.m_Response = stcSendParameter::enuResponseType::Response; + sendParameter.m_bAuthorative = true; + + bResult = _sendMDNSMessage(sendParameter); + } + DEBUG_EX_INFO( + else + { + DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: No reply needed\n"), _DH()); + } + ); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Something FAILED!\n"), _DH());); + m_rUDPContext.flush(); + } + + // + // Check and reset tiebreak-states + if (m_HostProbeInformation.m_bTiebreakNeeded) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: UNSOLVED tiebreak-need for host domain!\n"), _DH());); + m_HostProbeInformation.m_bTiebreakNeeded = false; + } + for (stcService* pService = m_pServices; pService; pService = pService->m_pNext) + { + if (pService->m_ProbeInformation.m_bTiebreakNeeded) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: UNSOLVED tiebreak-need for service domain '%s')\n"), _DH(), _service2String(pService));); + pService->m_ProbeInformation.m_bTiebreakNeeded = false; + } + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::clsHost::_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::clsHost::_parseResponse(const MDNSResponder::clsHost::stcMsgHeader& p_MsgHeader) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse\n"));); + //DEBUG_EX_INFO(_udpDump();); + + bool bResult = false; + + // A response should be the result of a query or a probe + if ((_hasQueriesWaitingForAnswers()) || // Waiting for query answers OR + (_hasProbesWaitingForAnswers())) // Probe responses + { + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: Received a response\n"), _DH()); + //_udpDump(); + ); + + bResult = true; + // + // Ignore questions here + stcRRQuestion dummyRRQ; + for (uint16_t qd = 0; ((bResult) && (qd < p_MsgHeader.m_u16QDCount)); ++qd) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: Received a response containing a question... ignoring!\n"), _DH());); + bResult = _readRRQuestion(dummyRRQ); + } // for queries + + // + // Read and collect answers + stcRRAnswer* pCollectedRRAnswers = 0; + uint32_t u32NumberOfAnswerRRs = (p_MsgHeader.m_u16ANCount + p_MsgHeader.m_u16NSCount + p_MsgHeader.m_u16ARCount); + for (uint32_t an = 0; ((bResult) && (an < u32NumberOfAnswerRRs)); ++an) + { + stcRRAnswer* pRRAnswer = 0; + if (((bResult = _readRRAnswer(pRRAnswer))) && + (pRRAnswer)) + { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: ADDING answer!\n"));); + pRRAnswer->m_pNext = pCollectedRRAnswers; + pCollectedRRAnswers = pRRAnswer; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: FAILED to read answer!\n"), _DH());); + if (pRRAnswer) + { + delete pRRAnswer; + pRRAnswer = 0; + } + bResult = false; + } + } // for answers + + // + // Process answers + if (bResult) + { + bResult = ((!pCollectedRRAnswers) || + (_processAnswers(pCollectedRRAnswers))); + } + else // Some failure while reading answers + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: FAILED to read answers!\n"), _DH());); + m_rUDPContext.flush(); + } + + // Delete collected answers + while (pCollectedRRAnswers) + { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: DELETING answer!\n"), _DH());); + stcRRAnswer* pNextAnswer = pCollectedRRAnswers->m_pNext; + delete pCollectedRRAnswers; + pCollectedRRAnswers = pNextAnswer; + } + } + else // Received an unexpected response -> ignore + { + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: Received an unexpected response... ignoring!\n"), _DH()); + /* + DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: Received an unexpected response... ignoring!\nDUMP:\n"), _DH()); + bool bDumpResult = true; + for (uint16_t qd=0; ((bDumpResult) && (qdanswerType()) + { + // eg. _http._tcp.local PTR xxxx xx MyESP._http._tcp.local + bResult = _processPTRAnswer((stcRRAnswerPTR*)pRRAnswer, bFoundNewKeyAnswer); // May 'enable' new SRV or TXT answers to be linked to queries + } + // 2. level answers + // SRV -> host domain and port + else if (enuAnswerType::SRV == pRRAnswer->answerType()) + { + // eg. MyESP._http._tcp.local SRV xxxx xx yy zz 5000 esp8266.local + bResult = _processSRVAnswer((stcRRAnswerSRV*)pRRAnswer, bFoundNewKeyAnswer); // May 'enable' new A/AAAA answers to be linked to queries + } + // TXT -> Txts + else if (enuAnswerType::TXT == pRRAnswer->answerType()) + { + // eg. MyESP_http._tcp.local TXT xxxx xx c#=1 + bResult = _processTXTAnswer((stcRRAnswerTXT*)pRRAnswer); + } + // 3. level answers +#ifdef MDNS_IPV4_SUPPORT + // A -> IPv4Address + else if (enuAnswerType::A == pRRAnswer->answerType()) + { + // eg. esp8266.local A xxxx xx 192.168.2.120 + bResult = _processAAnswer((stcRRAnswerA*)pRRAnswer); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + // AAAA -> IPv6Address + else if (enuAnswerType::AAAA == pRRAnswer->answerType()) + { + // eg. esp8266.local AAAA xxxx xx 09cf::0c + bResult = _processAAAAAnswer((stcRRAnswerAAAA*)pRRAnswer); + } +#endif + + // Finally check for probing conflicts + // Host domain + if ((enuProbingStatus::InProgress == m_HostProbeInformation.m_ProbingStatus) && + ((enuAnswerType::A == pRRAnswer->answerType()) || + (enuAnswerType::AAAA == pRRAnswer->answerType()))) + { + + stcRRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostName, hostDomain)) && + (pRRAnswer->m_Header.m_Domain == hostDomain)) + { + + bool bPossibleEcho = false; +#ifdef MDNS_IPV4_SUPPORT + if ((enuAnswerType::A == pRRAnswer->answerType()) && + (((stcRRAnswerA*)pRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V4))) + { + + bPossibleEcho = true; + } +#endif +#ifdef MDNS_IPV6_SUPPORT + if ((enuAnswerType::AAAA == pRRAnswer->answerType()) && + (((stcRRAnswerAAAA*)pRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V6))) + { + + bPossibleEcho = true; + } +#endif + if (!bPossibleEcho) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processAnswers: Probing CONFLICT found with '%s.local'\n"), _DH(), m_pcHostName);); + _cancelProbingForHost(); + } + else + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processAnswers: Ignoring CONFLICT found with '%s.local' as echo!\n"), _DH(), m_pcHostName);); + } + } + } + // Service domains + for (stcService* pService = m_pServices; pService; pService = pService->m_pNext) + { + if ((enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) && + ((enuAnswerType::TXT == pRRAnswer->answerType()) || + (enuAnswerType::SRV == pRRAnswer->answerType()))) + { + + stcRRDomain serviceDomain; + if ((_buildDomainForService(*pService, true, serviceDomain)) && + (pRRAnswer->m_Header.m_Domain == serviceDomain)) + { + + // TODO: Echo management needed? + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processAnswers: Probing CONFLICT found with '%s'\n"), _DH(), _service2String(pService));); + _cancelProbingForService(*pService); + } + } + } + + pRRAnswer = pRRAnswer->m_pNext; // Next collected answer + } // while (answers) + } while ((bFoundNewKeyAnswer) && + (bResult)); + } // else: No answers provided + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _processAnswers: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::clsHost::_processPTRAnswer (level 1) +*/ +bool MDNSResponder::clsHost::_processPTRAnswer(const MDNSResponder::clsHost::stcRRAnswerPTR* p_pPTRAnswer, + bool& p_rbFoundNewKeyAnswer) +{ + bool bResult = false; + + if ((bResult = (0 != p_pPTRAnswer))) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processPTRAnswer: Processing PTR answers...\n"), _DH());); + // eg. _http._tcp.local PTR xxxx xx MyESP._http._tcp.local + // Check pending service queries for eg. '_http._tcp' + + stcQuery* pQuery = _findNextQueryByDomain(p_pPTRAnswer->m_Header.m_Domain, stcQuery::enuQueryType::Service, 0); + while (pQuery) + { + if (pQuery->m_bAwaitingAnswers) + { + // Find answer for service domain (eg. MyESP._http._tcp.local) + stcQuery::stcAnswer* pSQAnswer = pQuery->findAnswerForServiceDomain(p_pPTRAnswer->m_PTRDomain); + if (pSQAnswer) // existing answer + { + if (p_pPTRAnswer->m_u32TTL) // Received update message + { + pSQAnswer->m_TTLServiceDomain.set(p_pPTRAnswer->m_u32TTL); // Update TTL tag + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processPTRAnswer: Updated TTL(%lu) for "), _DH(), p_pPTRAnswer->m_u32TTL); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR("\n")); + ); + } + else // received goodbye-message + { + pSQAnswer->m_TTLServiceDomain.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processPTRAnswer: 'Goodbye' received for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR("\n")); + ); + } + } + else if ((p_pPTRAnswer->m_u32TTL) && // Not just a goodbye-message + ((pSQAnswer = new stcQuery::stcAnswer))) // Not yet included -> add answer + { + pSQAnswer->m_ServiceDomain = p_pPTRAnswer->m_PTRDomain; + pSQAnswer->m_QueryAnswerFlags |= static_cast(enuQueryAnswerType::ServiceDomain); + pSQAnswer->m_TTLServiceDomain.set(p_pPTRAnswer->m_u32TTL); + //pSQAnswer->releaseServiceDomain(); + + bResult = pQuery->addAnswer(pSQAnswer); + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processPTRAnswer: Added service domain to answer: "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.println(); + ); + + p_rbFoundNewKeyAnswer = true; + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::ServiceDomain), true); + } + } + pQuery = _findNextQueryByDomain(p_pPTRAnswer->m_Header.m_Domain, stcQuery::enuQueryType::Service, pQuery); + } + } // else: No p_pPTRAnswer + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _processPTRAnswer: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::clsHost::_processSRVAnswer (level 2) +*/ +bool MDNSResponder::clsHost::_processSRVAnswer(const MDNSResponder::clsHost::stcRRAnswerSRV* 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 + + stcQuery* pQuery = m_pQueries; + while (pQuery) + { + if (pQuery->m_bAwaitingAnswers) + { + stcQuery::stcAnswer* pSQAnswer = pQuery->findAnswerForServiceDomain(p_pSRVAnswer->m_Header.m_Domain); + if (pSQAnswer) // Answer for this service domain (eg. MyESP._http._tcp.local) available + { + if (p_pSRVAnswer->m_u32TTL) // First or update message (TTL != 0) + { + pSQAnswer->m_TTLHostDomainAndPort.set(p_pSRVAnswer->m_u32TTL); // Update TTL tag + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processSRVAnswer: Updated TTL(%lu) for "), _DH(), p_pSRVAnswer->m_u32TTL); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" host domain and port\n")); + ); + // Host domain & Port + if ((pSQAnswer->m_HostDomain != p_pSRVAnswer->m_SRVDomain) || + (pSQAnswer->m_u16Port != p_pSRVAnswer->m_u16Port)) + { + + pSQAnswer->m_HostDomain = p_pSRVAnswer->m_SRVDomain; + //pSQAnswer->releaseHostDomain(); + pSQAnswer->m_u16Port = p_pSRVAnswer->m_u16Port; + pSQAnswer->m_QueryAnswerFlags |= (static_cast(enuQueryAnswerType::HostDomain) | static_cast(enuQueryAnswerType::Port)); + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processSVRAnswer: Added host domain and port to "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(": ")); + _printRRDomain(pSQAnswer->m_HostDomain); + DEBUG_OUTPUT.printf_P(PSTR(": %u\n"), pSQAnswer->m_u16Port); + ); + + p_rbFoundNewKeyAnswer = true; + _executeQueryCallback(*pQuery, *pSQAnswer, (static_cast(enuQueryAnswerType::HostDomain) | static_cast(enuQueryAnswerType::Port)), true); + } + } + else // Goodby message + { + pSQAnswer->m_TTLHostDomainAndPort.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processSRVAnswer: 'Goodbye' received for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" host domain and port\n")); + ); + } + } + } // m_bAwaitingAnswers + pQuery = pQuery->m_pNext; + } // while(service query) + } // else: No p_pSRVAnswer + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _processSRVAnswer: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::clsHost::_processTXTAnswer (level 2) +*/ +bool MDNSResponder::clsHost::_processTXTAnswer(const MDNSResponder::clsHost::stcRRAnswerTXT* p_pTXTAnswer) +{ + bool bResult = false; + + if ((bResult = (0 != p_pTXTAnswer))) + { + // eg. MyESP._http._tcp.local TXT xxxx xx c#=1 + + stcQuery* pQuery = m_pQueries; + while (pQuery) + { + if (pQuery->m_bAwaitingAnswers) + { + stcQuery::stcAnswer* pSQAnswer = pQuery->findAnswerForServiceDomain(p_pTXTAnswer->m_Header.m_Domain); + if (pSQAnswer) // Answer for this service domain (eg. MyESP._http._tcp.local) available + { + if (p_pTXTAnswer->m_u32TTL) // First or update message + { + pSQAnswer->m_TTLTxts.set(p_pTXTAnswer->m_u32TTL); // Update TTL tag + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processTXTAnswer: Updated TTL(%lu) for "), _DH(), p_pTXTAnswer->m_u32TTL); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" TXTs\n")); + ); + if (!pSQAnswer->m_Txts.compare(p_pTXTAnswer->m_Txts)) + { + pSQAnswer->m_Txts = p_pTXTAnswer->m_Txts; + pSQAnswer->m_QueryAnswerFlags |= static_cast(enuQueryAnswerType::Txts); + //pSQAnswer->releaseTxts(); + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processTXTAnswer: Added TXT to "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.println(); + ); + + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::Txts), true); + } + } + else // Goodby message + { + pSQAnswer->m_TTLTxts.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processTXTAnswer: 'Goodbye' received for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" TXTs\n")); + ); + } + } + } // m_bAwaitingAnswers + pQuery = pQuery->m_pNext; + } // while(service query) + } // else: No p_pTXTAnswer + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _processTXTAnswer: FAILED!\n"), _DH());); + return bResult; +} + +#ifdef MDNS_IPV4_SUPPORT +/* + MDNSResponder::clsHost::_processAAnswer (level 3) +*/ +bool MDNSResponder::clsHost::_processAAnswer(const MDNSResponder::clsHost::stcRRAnswerA* p_pAAnswer) +{ + bool bResult = false; + + if ((bResult = (0 != p_pAAnswer))) + { + // eg. esp8266.local A xxxx xx 192.168.2.120 + + stcQuery* pQuery = m_pQueries; + while (pQuery) + { + if (pQuery->m_bAwaitingAnswers) + { + // Look for answers to host queries + if ((p_pAAnswer->m_u32TTL) && // NOT just a goodbye message + (stcQuery::enuQueryType::Host == pQuery->m_QueryType) && // AND a host query + (pQuery->m_Domain == p_pAAnswer->m_Header.m_Domain)) // AND a matching host domain + { + + stcQuery::stcAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAnswer->m_Header.m_Domain); + if ((!pSQAnswer) && + ((pSQAnswer = new stcQuery::stcAnswer))) + { + // Add not yet included answer + pSQAnswer->m_HostDomain = p_pAAnswer->m_Header.m_Domain; + //pSQAnswer->releaseHostDomain(); + + bResult = pQuery->addAnswer(pSQAnswer); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: Added host query answer for "), _DH()); + _printRRDomain(pQuery->m_Domain); + DEBUG_OUTPUT.println(); + ); + + pSQAnswer->m_QueryAnswerFlags |= static_cast(enuQueryAnswerType::HostDomain); + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::HostDomain), true); + } + } + + // Look for answers to service queries + stcQuery::stcAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAnswer->m_Header.m_Domain); + if (pSQAnswer) // Answer for this host domain (eg. esp8266.local) available + { + stcQuery::stcAnswer::stcIPAddress* pIPAddress = pSQAnswer->findIPv4Address(p_pAAnswer->m_IPAddress); + if (pIPAddress) + { + // Already known IPv4 address + if (p_pAAnswer->m_u32TTL) // Valid TTL -> Update answers TTL + { + pIPAddress->m_TTL.set(p_pAAnswer->m_u32TTL); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: Updated TTL(%lu) for "), _DH(), p_pAAnswer->m_u32TTL); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IPv4 address (%s)\n"), pIPAddress->m_IPAddress.toString().c_str()); + ); + } + else // 'Goodbye' message for known IPv4 address + { + pIPAddress->m_TTL.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: 'Goodbye' received for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IPv4 address (%s)\n"), pIPAddress->m_IPAddress.toString().c_str()); + ); + } + } + else + { + // Until now unknown IPv4 address -> Add (if the message isn't just a 'Goodbye' note) + if (p_pAAnswer->m_u32TTL) // NOT just a 'Goodbye' message + { + pIPAddress = new stcQuery::stcAnswer::stcIPAddress(p_pAAnswer->m_IPAddress, p_pAAnswer->m_u32TTL); + if ((pIPAddress) && + (pSQAnswer->addIPv4Address(pIPAddress))) + { + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: Added IPv4 address to "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(": %s\n"), pIPAddress->m_IPAddress.toString().c_str()); + ); + + pSQAnswer->m_QueryAnswerFlags |= static_cast(enuQueryAnswerType::IPv4Address); + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::IPv4Address), true); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: FAILED to add IPv4 address (%s)!\n"), _DH(), p_pAAnswer->m_IPAddress.toString().c_str());); + } + } + } + } + } // m_bAwaitingAnswers + pQuery = pQuery->m_pNext; + } // while(service query) + } // else: No p_pAAnswer + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: FAILED!\n"), _DH());); + return bResult; +} +#endif + +#ifdef MDNS_IPV6_SUPPORT +/* + MDNSResponder::clsHost::_processAAAAAnswer (level 3) +*/ +bool MDNSResponder::clsHost::_processAAAAAnswer(const MDNSResponder::clsHost::stcRRAnswerAAAA* p_pAAAAAnswer) +{ + bool bResult = false; + + if ((bResult = (0 != p_pAAAAAnswer))) + { + // eg. esp8266.local AAAA xxxx xx 0bf3::0c + + stcQuery* pQuery = m_pQueries; + while (pQuery) + { + if (pQuery->m_bAwaitingAnswers) + { + // Look for answers to host queries + if ((p_pAAAAAnswer->m_u32TTL) && // NOT just a goodbye message + (stcQuery::enuQueryType::Host == pQuery->m_QueryType) && // AND a host query + (pQuery->m_Domain == p_pAAAAAnswer->m_Header.m_Domain)) // AND a matching host domain + { + + stcQuery::stcAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAAAAnswer->m_Header.m_Domain); + if ((!pSQAnswer) && + ((pSQAnswer = new stcQuery::stcAnswer))) + { + // Add not yet included answer + pSQAnswer->m_HostDomain = p_pAAAAAnswer->m_Header.m_Domain; + //pSQAnswer->releaseHostDomain(); + + bResult = pQuery->addAnswer(pSQAnswer); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processAAAAAnswer: Added host query answer for "), _DH()); + _printRRDomain(pQuery->m_Domain); + DEBUG_OUTPUT.println(); + ); + + pSQAnswer->m_QueryAnswerFlags |= static_cast(enuQueryAnswerType::HostDomain); + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::HostDomain), true); + } + } + + // Look for answers to service queries + stcQuery::stcAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAAAAnswer->m_Header.m_Domain); + if (pSQAnswer) // Answer for this host domain (eg. esp8266.local) available + { + stcQuery::stcAnswer::stcIPAddress* pIPAddress = pSQAnswer->findIPv6Address(p_pAAAAAnswer->m_IPAddress); + if (pIPAddress) + { + // Already known IPv6 address + if (p_pAAAAAnswer->m_u32TTL) // Valid TTL -> Update answers TTL + { + pIPAddress->m_TTL.set(p_pAAAAAnswer->m_u32TTL); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processAAAAAnswer: Updated TTL(%lu) for "), _DH(), p_pAAAAAnswer->m_u32TTL); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IPv6 address (%s)\n"), pIPAddress->m_IPAddress.toString().c_str()); + ); + } + else // 'Goodbye' message for known IPv6 address + { + pIPAddress->m_TTL.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processAAAAAnswer: 'Goodbye' received for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IPv6 address (%s)\n"), pIPAddress->m_IPAddress.toString().c_str()); + ); + } + } + else + { + // Until now unknown IPv6 address -> Add (if the message isn't just a 'Goodbye' note) + if (p_pAAAAAnswer->m_u32TTL) // NOT just a 'Goodbye' message + { + pIPAddress = new stcQuery::stcAnswer::stcIPAddress(p_pAAAAAnswer->m_IPAddress, p_pAAAAAnswer->m_u32TTL); + if ((pIPAddress) && + (pSQAnswer->addIPv6Address(pIPAddress))) + { + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _processAAAAAnswer: Added IPv6 address to "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(": %s\n"), pIPAddress->m_IPAddress.toString().c_str()); + ); + + pSQAnswer->m_QueryAnswerFlags |= static_cast(enuQueryAnswerType::IPv6Address); + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::IPv6Address), true); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _processAAAAAnswer: FAILED to add IPv6 address (%s)!\n"), _DH(), p_pAAAAAnswer->m_IPAddress.toString().c_str());); + } + } + } + } + } // m_bAwaitingAnswers + pQuery = pQuery->m_pNext; + } // while(service query) + } // else: No p_pAAAAAnswer + + return bResult; +} +#endif + + +/* + PROBING +*/ + +/* + MDNSResponder::clsHost::_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::clsHost::_updateProbeStatus(void) +{ + bool bResult = true; + + // + // Probe host domain + if ((enuProbingStatus::ReadyToStart == m_HostProbeInformation.m_ProbingStatus) && // Ready to get started AND + (( +#ifdef MDNS_IPV4_SUPPORT + _getResponderIPAddress(enuIPProtocolType::V4).isSet() // AND has IPv4 address +#else + true +#endif + ) || ( +#ifdef MDNS_IPV6_SUPPORT + _getResponderIPAddress(enuIPProtocolType::V6).isSet() // OR has IPv6 address +#else + true +#endif + ))) // Has IP address + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Starting host probing...\n"), _DH());); + + // First probe delay SHOULD be random 0-250 ms + m_HostProbeInformation.m_Timeout.reset(rand() % MDNS_PROBE_DELAY); + m_HostProbeInformation.m_ProbingStatus = enuProbingStatus::InProgress; + } + else if ((enuProbingStatus::InProgress == m_HostProbeInformation.m_ProbingStatus) && // Probing AND + (m_HostProbeInformation.m_Timeout.expired())) // Time for next probe + { + + if (MDNS_PROBE_COUNT > m_HostProbeInformation.m_u8SentCount) // Send next probe + { + if ((bResult = _sendHostProbe())) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Did sent host probe for '%s.local'\n\n"), _DH(), (m_pcHostName ? : ""));); + m_HostProbeInformation.m_Timeout.reset(MDNS_PROBE_DELAY); + ++m_HostProbeInformation.m_u8SentCount; + } + } + else // Probing finished + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("\n%s _updateProbeStatus: Done host probing for '%s.local'.\n\n\n"), _DH(), (m_pcHostName ? : ""));); + m_HostProbeInformation.m_ProbingStatus = enuProbingStatus::Done; + m_HostProbeInformation.m_Timeout.reset(std::numeric_limits::max()); + + _callHostProbeResultCallback(true); + + // Prepare to announce host + m_HostProbeInformation.m_u8SentCount = 0; + m_HostProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Prepared host announcing.\n\n"), _DH());); + } + } // else: Probing already finished OR waiting for next time slot + else if ((enuProbingStatus::Done == m_HostProbeInformation.m_ProbingStatus) && + (m_HostProbeInformation.m_Timeout.expired())) + { + + if ((bResult = _announce(true, false))) // Don't announce services here + { + ++m_HostProbeInformation.m_u8SentCount; // 1.. + + if (MDNS_ANNOUNCE_COUNT > m_HostProbeInformation.m_u8SentCount) + { + m_HostProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY * pow(2, (m_HostProbeInformation.m_u8SentCount - 1))); // 2^(0..) -> 1, 2, 4, ... + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Announcing host '%s.local' (%lu).\n\n"), _DH(), (m_pcHostName ? : ""), m_HostProbeInformation.m_u8SentCount);); + } + else + { + m_HostProbeInformation.m_Timeout.reset(std::numeric_limits::max()); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Done host announcing for '%s.local'.\n\n"), _DH(), (m_pcHostName ? : ""));); + } + } + } + + // + // Probe services + for (stcService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) + { + if (enuProbingStatus::ReadyToStart == pService->m_ProbeInformation.m_ProbingStatus) // Ready to get started + { + + pService->m_ProbeInformation.m_Timeout.reset(MDNS_PROBE_DELAY); // More or equal than first probe for host domain + pService->m_ProbeInformation.m_ProbingStatus = enuProbingStatus::InProgress; + } + else if ((enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) && // Probing AND + (pService->m_ProbeInformation.m_Timeout.expired())) // Time for next probe + { + + if (MDNS_PROBE_COUNT > pService->m_ProbeInformation.m_u8SentCount) // Send next probe + { + if ((bResult = _sendServiceProbe(*pService))) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Did sent service probe for '%s' (%u)\n\n"), _DH(), _service2String(pService), (pService->m_ProbeInformation.m_u8SentCount + 1));); + pService->m_ProbeInformation.m_Timeout.reset(MDNS_PROBE_DELAY); + ++pService->m_ProbeInformation.m_u8SentCount; + } + } + else // Probing finished + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("\n%s _updateProbeStatus: Done service probing '%s'\n\n\n"), _DH(), _service2String(pService));); + pService->m_ProbeInformation.m_ProbingStatus = enuProbingStatus::Done; + pService->m_ProbeInformation.m_Timeout.reset(std::numeric_limits::max()); + + _callServiceProbeResultCallback(*pService, true); + + // Prepare to announce service + pService->m_ProbeInformation.m_u8SentCount = 0; + pService->m_ProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Prepared service announcing.\n\n"), _DH());); + } + } // else: Probing already finished OR waiting for next time slot + else if ((enuProbingStatus::Done == pService->m_ProbeInformation.m_ProbingStatus) && + (pService->m_ProbeInformation.m_Timeout.expired())) + { + + if ((bResult = _announceService(*pService))) // Announce service + { + ++pService->m_ProbeInformation.m_u8SentCount; // 1.. + + if (MDNS_ANNOUNCE_COUNT > pService->m_ProbeInformation.m_u8SentCount) + { + pService->m_ProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY * pow(2, (pService->m_ProbeInformation.m_u8SentCount - 1))); // 2^(0..) -> 1, 2, 4, ... + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Announcing service '%s' (%lu)\n\n"), _DH(), _service2String(pService), pService->m_ProbeInformation.m_u8SentCount);); + } + else + { + pService->m_ProbeInformation.m_Timeout.reset(std::numeric_limits::max()); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Done service announcing for '%s'\n\n"), _DH(), _service2String(pService));); + } + } + } + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: FAILED!\n\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::clsHost::_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::clsHost::_resetProbeStatus(bool p_bRestart /*= true*/) +{ + m_HostProbeInformation.clear(false); + m_HostProbeInformation.m_ProbingStatus = (p_bRestart ? enuProbingStatus::ReadyToStart : enuProbingStatus::Done); + + for (stcService* pService = m_pServices; pService; pService = pService->m_pNext) + { + pService->m_ProbeInformation.clear(false); + pService->m_ProbeInformation.m_ProbingStatus = (p_bRestart ? enuProbingStatus::ReadyToStart : enuProbingStatus::Done); + } + return true; +} + +/* + MDNSResponder::clsHost::_hasProbesWaitingForAnswers +*/ +bool MDNSResponder::clsHost::_hasProbesWaitingForAnswers(void) const +{ + bool bResult = ((enuProbingStatus::InProgress == m_HostProbeInformation.m_ProbingStatus) && // Probing + (0 < m_HostProbeInformation.m_u8SentCount)); // And really probing + + for (stcService* pService = m_pServices; ((!bResult) && (pService)); pService = pService->m_pNext) + { + bResult = ((enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) && // Probing + (0 < pService->m_ProbeInformation.m_u8SentCount)); // And really probing + } + return bResult; +} + +/* + MDNSResponder::clsHost::_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::clsHost::_sendHostProbe(void) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendHostProbe (%s.local, %lu)\n"), _DH(), m_pcHostName, millis());); + + bool bResult = true; + + // Requests for host domain + stcSendParameter sendParameter; + sendParameter.m_bCacheFlush = false; // RFC 6762 10.2 + + sendParameter.m_pQuestions = new stcRRQuestion; + 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_IPV4_SUPPORT + sendParameter.m_u32HostReplyMask |= static_cast(enuContentFlag::A); // Add A answer +#endif +#ifdef MDNS_IPV6_SUPPORT + sendParameter.m_u32HostReplyMask |= static_cast(enuContentFlag::AAAA); // Add AAAA answer +#endif + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _sendHostProbe: FAILED to create host question!\n"), _DH());); + if (sendParameter.m_pQuestions) + { + delete sendParameter.m_pQuestions; + sendParameter.m_pQuestions = 0; + } + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendHostProbe: FAILED!\n"), _DH());); + return ((bResult) && + (_sendMDNSMessage(sendParameter))); +} + +/* + MDNSResponder::clsHost::_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::clsHost::_sendServiceProbe(stcService& p_rService) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendServiceProbe (%s, %lu)\n"), _DH(), _service2String(&p_rService), millis());); + + bool bResult = true; + + // Requests for service instance domain + stcSendParameter sendParameter; + sendParameter.m_bCacheFlush = false; // RFC 6762 10.2 + + sendParameter.m_pQuestions = new stcRRQuestion; + 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_u32ReplyMask = (static_cast(enuContentFlag::SRV) | static_cast(enuContentFlag::PTR_NAME)); // Add SRV and PTR NAME answers + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _sendServiceProbe: FAILED to create service question!\n"), _DH());); + if (sendParameter.m_pQuestions) + { + delete sendParameter.m_pQuestions; + sendParameter.m_pQuestions = 0; + } + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendServiceProbe: FAILED!\n"), _DH());); + return ((bResult) && + (_sendMDNSMessage(sendParameter))); +} + +/* + MDNSResponder::clsHost::_cancelProbingForHost +*/ +bool MDNSResponder::clsHost::_cancelProbingForHost(void) +{ + bool bResult = false; + + m_HostProbeInformation.clear(false); + + // Send host notification + bResult = _callHostProbeResultCallback(false); + + for (stcService* pService = m_pServices; ((!bResult) && (pService)); pService = pService->m_pNext) + { + bResult = _cancelProbingForService(*pService); + } + return bResult; +} + +/* + MDNSResponder::clsHost::_cancelProbingForService +*/ +bool MDNSResponder::clsHost::_cancelProbingForService(stcService& p_rService) +{ + p_rService.m_ProbeInformation.clear(false); + + // Send notification + return _callServiceProbeResultCallback(p_rService, false); +} + +/* + MDNSResponder::clsHost::_callHostProbeResultCallback + +*/ +bool MDNSResponder::clsHost::_callHostProbeResultCallback(bool p_bResult) +{ + if (m_HostProbeInformation.m_fnProbeResultCallback) + { + m_HostProbeInformation.m_fnProbeResultCallback(*this, m_pcHostName, p_bResult); + } + else if (!p_bResult) + { + // Auto-Handle failure by changing the host name, use '-' as divider between base name and index + char* pcHostDomainTemp = strdup(m_pcHostName); + if (pcHostDomainTemp) + { + if (MDNSResponder::indexDomain(pcHostDomainTemp, "-", 0)) + { + setHostName(pcHostDomainTemp); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _callHostProbeResultCallback: FAILED to update host domain '%s'!\n"), _DH(), (m_pcHostName ? : ""));); + } + free(pcHostDomainTemp); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _callHostProbeResultCallback: FAILED to copy host domain '%s'!\n"), _DH(), (m_pcHostName ? : ""));); + } + } + return true; +} + +/* + MDNSResponder::clsHost::_callServiceProbeResultCallback + +*/ +bool MDNSResponder::clsHost::_callServiceProbeResultCallback(MDNSResponder::clsHost::stcService& p_rService, + bool p_bResult) +{ + if (p_rService.m_ProbeInformation.m_fnProbeResultCallback) + { + p_rService.m_ProbeInformation.m_fnProbeResultCallback(*this, p_rService, p_rService.m_pcName, p_bResult); + } + else if (!p_bResult) + { + // Auto-Handle failure by changing the service name, use ' #' as divider between base name and index + char* pcServiceNameTemp = strdup(p_rService.m_pcName); + if (pcServiceNameTemp) + { + if (MDNSResponder::indexDomain(pcServiceNameTemp, " #", 0)) + { + setServiceName(&p_rService, pcServiceNameTemp); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _callServiceProbeResultCallback: FAILED to update service name for '%s'!\n"), _DH(), _service2String(&p_rService));); + } + free(pcServiceNameTemp); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _callServiceProbeResultCallback: FAILED to copy service name for '%s'!\n"), _DH(), _service2String(&p_rService));); + } + } + return true; +} + + +/** + ANNOUNCING +*/ + +/* + MDNSResponder::clsHost::_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::clsHost::_announce(bool p_bAnnounce, + bool p_bIncludeServices) +{ + bool bResult = false; + + stcSendParameter sendParameter; + if (enuProbingStatus::Done == m_HostProbeInformation.m_ProbingStatus) + { + bResult = true; + + sendParameter.m_Response = stcSendParameter::enuResponseType::Unsolicited; // Announces are 'Unsolicited authorative responses' + sendParameter.m_bAuthorative = true; + sendParameter.m_bCacheFlush = true; // RFC 6762 8.3 + sendParameter.m_bUnannounce = !p_bAnnounce; // When unannouncing, the TTL is set to '0' while creating the answers + + // Announce host + sendParameter.m_u32HostReplyMask = 0; +#ifdef MDNS_IPV4_SUPPORT + sendParameter.m_u32HostReplyMask |= static_cast(enuContentFlag::A); // A answer + sendParameter.m_u32HostReplyMask |= static_cast(enuContentFlag::PTR_IPv4); // PTR_IPv4 answer +#endif +#ifdef MDNS_IPV6_SUPPORT + sendParameter.m_u32HostReplyMask |= static_cast(enuContentFlag::AAAA); // AAAA answer + sendParameter.m_u32HostReplyMask |= static_cast(enuContentFlag::PTR_IPv6); // PTR_IPv6 answer +#endif + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _announce: Announcing host %s (content: %s)\n"), _DH(), m_pcHostName, _replyFlags2String(sendParameter.m_u32HostReplyMask));); + + if (p_bIncludeServices) + { + // Announce services (service type, name, SRV (location) and TXTs) + for (stcService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) + { + if (enuProbingStatus::Done == pService->m_ProbeInformation.m_ProbingStatus) + { + pService->m_u32ReplyMask = (static_cast(enuContentFlag::PTR_TYPE) | static_cast(enuContentFlag::PTR_NAME) | static_cast(enuContentFlag::SRV) | static_cast(enuContentFlag::TXT)); + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _announce: Announcing service '%s' (content %s)\n"), _DH(), _service2String(pService), _replyFlags2String(pService->m_u32ReplyMask));); + } + } + } + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _announce: FAILED!\n"), _DH());); + return ((bResult) && + (_sendMDNSMessage(sendParameter))); +} + +/* + MDNSResponder::clsHost::_announceService +*/ +bool MDNSResponder::clsHost::_announceService(MDNSResponder::clsHost::stcService& p_rService, + bool p_bAnnounce /*= true*/) +{ + bool bResult = false; + + stcSendParameter sendParameter; + if (enuProbingStatus::Done == p_rService.m_ProbeInformation.m_ProbingStatus) + { + + sendParameter.m_Response = stcSendParameter::enuResponseType::Unsolicited; // Announces are 'Unsolicited authorative responses' + sendParameter.m_bAuthorative = true; + sendParameter.m_bUnannounce = !p_bAnnounce; // When unannouncing, the TTL is set to '0' while creating the answers + + // DON'T announce host + sendParameter.m_u32HostReplyMask = 0; + + // Announce services (service type, name, SRV (location) and TXTs) + p_rService.m_u32ReplyMask = (static_cast(enuContentFlag::PTR_TYPE) | static_cast(enuContentFlag::PTR_NAME) | static_cast(enuContentFlag::SRV) | static_cast(enuContentFlag::TXT)); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _announceService: Announcing service '%s' (content: %s)\n"), _DH(), _service2String(&p_rService), _replyFlags2String(p_rService.m_u32ReplyMask));); + + bResult = true; + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _announceService: FAILED!\n"), _DH());); + return ((bResult) && + (_sendMDNSMessage(sendParameter))); +} + + +/** + QUERY CACHE +*/ + +/* + MDNSResponder::clsHost::_checkQueryCache + + For any 'living' query (m_bAwaitingAnswers == true) all available answers (their components) + are checked for topicality based on the stored reception time and the answers TTL. + When the components TTL is outlasted by more than 80%, a new question is generated, to get updated information. + When no update arrived (in time), the component is removed from the answer (cache). + +*/ +bool MDNSResponder::clsHost::_checkQueryCache(void) +{ + bool bResult = true; + + DEBUG_EX_INFO( + bool printedInfo = false; + ); + for (stcQuery* pQuery = m_pQueries; ((bResult) && (pQuery)); pQuery = pQuery->m_pNext) + { + // + // Resend dynamic queries, if not already done often enough + if ((!pQuery->m_bLegacyQuery) && + (pQuery->m_ResendTimeout.expired())) + { + + if ((bResult = _sendMDNSQuery(*pQuery))) + { + // The re-query rate is increased to more than one hour (RFC 6762 5.2) + ++pQuery->m_u8SentCount; + uint32_t u32NewDelay = (MDNS_DYNAMIC_QUERY_RESEND_DELAY * pow(2, std::min((pQuery->m_u8SentCount - 1), 12))); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Next query in %u seconds!\n"), _DH(), (u32NewDelay));); + pQuery->m_ResendTimeout.reset(u32NewDelay); + } + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: %s to resend query!\n"), _DH(), (bResult ? "Succeeded" : "FAILED")); + printedInfo = true; + ); + } + + // + // Schedule updates for cached answers + if (pQuery->m_bAwaitingAnswers) + { + stcQuery::stcAnswer* pSQAnswer = pQuery->m_pAnswers; + while ((bResult) && + (pSQAnswer)) + { + stcQuery::stcAnswer* pNextSQAnswer = pSQAnswer->m_pNext; + + // 1. level answer + if ((bResult) && + (pSQAnswer->m_TTLServiceDomain.flagged())) + { + + if (!pSQAnswer->m_TTLServiceDomain.finalTimeoutLevel()) + { + + bResult = ((_sendMDNSQuery(*pQuery)) && + (pSQAnswer->m_TTLServiceDomain.restart())); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: PTR update scheduled for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" %s\n"), (bResult ? "OK" : "FAILURE")); + printedInfo = true; + ); + } + else + { + // Timed out! -> Delete + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::ServiceDomain), false); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Will remove PTR answer for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR("\n")); + printedInfo = true; + ); + + bResult = pQuery->removeAnswer(pSQAnswer); + pSQAnswer = 0; + continue; // Don't use this answer anymore + } + } // ServiceDomain flagged + + // 2. level answers + // HostDomain & Port (from SRV) + if ((bResult) && + (pSQAnswer->m_TTLHostDomainAndPort.flagged())) + { + + if (!pSQAnswer->m_TTLHostDomainAndPort.finalTimeoutLevel()) + { + + bResult = ((_sendMDNSQuery(pSQAnswer->m_ServiceDomain, DNS_RRTYPE_SRV)) && + (pSQAnswer->m_TTLHostDomainAndPort.restart())); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: SRV update scheduled for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" host domain and port %s\n"), (bResult ? "OK" : "FAILURE")); + printedInfo = true; + ); + } + else + { + // Timed out! -> Delete + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Will remove SRV answer for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" host domain and port\n")); + printedInfo = true; + ); + // Delete + pSQAnswer->m_HostDomain.clear(); + //pSQAnswer->releaseHostDomain(); + pSQAnswer->m_u16Port = 0; + pSQAnswer->m_TTLHostDomainAndPort.set(0); + typeQueryAnswerType queryAnswerContentFlags = (static_cast(enuQueryAnswerType::HostDomain) | static_cast(enuQueryAnswerType::Port)); + // As the host domain is the base for the IPv4- and IPv6Address, remove these too +#ifdef MDNS_IPV4_SUPPORT + pSQAnswer->releaseIPv4Addresses(); + queryAnswerContentFlags |= static_cast(enuQueryAnswerType::IPv4Address); +#endif +#ifdef MDNS_IPV6_SUPPORT + pSQAnswer->releaseIPv6Addresses(); + queryAnswerContentFlags |= static_cast(enuQueryAnswerType::IPv6Address); +#endif + + // Remove content flags for deleted answer parts + pSQAnswer->m_QueryAnswerFlags &= ~queryAnswerContentFlags; + _executeQueryCallback(*pQuery, *pSQAnswer, queryAnswerContentFlags, false); + } + } // HostDomainAndPort flagged + + // Txts (from TXT) + if ((bResult) && + (pSQAnswer->m_TTLTxts.flagged())) + { + + if (!pSQAnswer->m_TTLTxts.finalTimeoutLevel()) + { + + bResult = ((_sendMDNSQuery(pSQAnswer->m_ServiceDomain, DNS_RRTYPE_TXT)) && + (pSQAnswer->m_TTLTxts.restart())); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: TXT update scheduled for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" TXTs %s\n"), (bResult ? "OK" : "FAILURE")); + printedInfo = true; + ); + } + else + { + // Timed out! -> Delete + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Will remove TXT answer for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" TXTs\n")); + printedInfo = true; + ); + // Delete + pSQAnswer->m_Txts.clear(); + pSQAnswer->m_TTLTxts.set(0); + + // Remove content flags for deleted answer parts + pSQAnswer->m_QueryAnswerFlags &= ~static_cast(enuQueryAnswerType::Txts); + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::Txts), false); + } + } // TXTs flagged + + // 3. level answers +#ifdef MDNS_IPV4_SUPPORT + // IPv4Address (from A) + stcQuery::stcAnswer::stcIPAddress* pIPv4Address = pSQAnswer->m_pIPv4Addresses; + bool bAUpdateQuerySent = false; + while ((pIPv4Address) && + (bResult)) + { + + stcQuery::stcAnswer::stcIPAddress* pNextIPv4Address = pIPv4Address->m_pNext; // Get 'next' early, as 'current' may be deleted at the end... + + if (pIPv4Address->m_TTL.flagged()) + { + + if (!pIPv4Address->m_TTL.finalTimeoutLevel()) // Needs update + { + + if ((bAUpdateQuerySent) || + ((bResult = _sendMDNSQuery(pSQAnswer->m_HostDomain, DNS_RRTYPE_A)))) + { + + pIPv4Address->m_TTL.restart(); + bAUpdateQuerySent = true; + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: IPv4 update scheduled for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IPv4 address (%s)\n"), (pIPv4Address->m_IPAddress.toString().c_str())); + printedInfo = true; + ); + } + } + else + { + // Timed out! -> Delete + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Will remove IPv4 answer for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IPv4 address\n")); + printedInfo = true; + ); + pSQAnswer->removeIPv4Address(pIPv4Address); + if (!pSQAnswer->m_pIPv4Addresses) // NO IPv4 address left -> remove content flag + { + pSQAnswer->m_QueryAnswerFlags &= ~static_cast(enuQueryAnswerType::IPv4Address); + } + // Notify client + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::IPv4Address), false); + } + } // IPv4 flagged + + pIPv4Address = pNextIPv4Address; // Next + } // while +#endif +#ifdef MDNS_IPV6_SUPPORT + // IPv6Address (from AAAA) + stcQuery::stcAnswer::stcIPAddress* pIPv6Address = pSQAnswer->m_pIPv6Addresses; + bool bAAAAUpdateQuerySent = false; + while ((pIPv6Address) && + (bResult)) + { + + stcQuery::stcAnswer::stcIPAddress* pNextIPv6Address = pIPv6Address->m_pNext; // Get 'next' early, as 'current' may be deleted at the end... + + if (pIPv6Address->m_TTL.flagged()) + { + + if (!pIPv6Address->m_TTL.finalTimeoutLevel()) // Needs update + { + + if ((bAAAAUpdateQuerySent) || + ((bResult = _sendMDNSQuery(pSQAnswer->m_HostDomain, DNS_RRTYPE_AAAA)))) + { + + pIPv6Address->m_TTL.restart(); + bAAAAUpdateQuerySent = true; + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: IPv6 update scheduled for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IPv6 address (%s)\n"), (pIPv6Address->m_IPAddress.toString().c_str())); + printedInfo = true; + ); + } + } + else + { + // Timed out! -> Delete + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Will remove answer for "), _DH()); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IPv6 address\n")); + printedInfo = true; + ); + pSQAnswer->removeIPv6Address(pIPv6Address); + if (!pSQAnswer->m_pIPv6Addresses) // NO IPv6 address left -> remove content flag + { + pSQAnswer->m_QueryAnswerFlags &= ~static_cast(enuQueryAnswerType::IPv6Address); + } + // Notify client + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::IPv6Address), false); + } + } // IPv6 flagged + + pIPv6Address = pNextIPv6Address; // Next + } // while +#endif + pSQAnswer = pNextSQAnswer; + } + } + } + DEBUG_EX_INFO(if (printedInfo) DEBUG_OUTPUT.printf_P(PSTR("\n"));); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: FAILED!\n"), _DH());); + return bResult; +} + + +/* + MDNSResponder::clsHost::_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_IPv4 (eg. esp8266.local) reply. + + In addition, a full name match (question domain == host domain) is marked. +*/ +uint32_t MDNSResponder::clsHost::_replyMaskForHost(const MDNSResponder::clsHost::stcRRHeader& p_RRHeader, + bool* p_pbFullNameMatch /*= 0*/) const +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _replyMaskForHost\n"));); + + uint32_t u32ReplyMask = 0; + (p_pbFullNameMatch ? *p_pbFullNameMatch = false : 0); + + if ((DNS_RRCLASS_IN == (p_RRHeader.m_Attributes.m_u16Class & (~0x8000))) || + (DNS_RRCLASS_ANY == (p_RRHeader.m_Attributes.m_u16Class & (~0x8000)))) + { + + if ((DNS_RRTYPE_PTR == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) + { + // PTR request +#ifdef MDNS_IPV4_SUPPORT + stcRRDomain reverseIPv4Domain; + if ((_getResponderIPAddress(enuIPProtocolType::V4).isSet()) && + (_buildDomainForReverseIPv4(_getResponderIPAddress(enuIPProtocolType::V4), reverseIPv4Domain)) && + (p_RRHeader.m_Domain == reverseIPv4Domain)) + { + // Reverse domain match + u32ReplyMask |= static_cast(enuContentFlag::PTR_IPv4); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + stcRRDomain reverseIPv6Domain; + if ((_getResponderIPAddress(enuIPProtocolType::V6).isSet()) && + (_buildDomainForReverseIPv6(_getResponderIPAddress(enuIPProtocolType::V6), reverseIPv6Domain)) && + (p_RRHeader.m_Domain == reverseIPv6Domain)) + { + // Reverse domain match + u32ReplyMask |= static_cast(enuContentFlag::PTR_IPv6); + } +#endif + } // Address qeuest + + stcRRDomain hostDomain; + if ((_buildDomainForHost(m_pcHostName, hostDomain)) && + (p_RRHeader.m_Domain == hostDomain)) // Host domain match + { + + (p_pbFullNameMatch ? (*p_pbFullNameMatch = true) : (0)); + +#ifdef MDNS_IPV4_SUPPORT + if ((DNS_RRTYPE_A == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) + { + // IPv4 address request + u32ReplyMask |= static_cast(enuContentFlag::A); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + if ((DNS_RRTYPE_AAAA == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) + { + // IPv6 address request + u32ReplyMask |= static_cast(enuContentFlag::AAAA); + } +#endif + } + } + else + { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _replyMaskForHost: INVALID RR-class (0x%04X)!\n"), p_RRHeader.m_Attributes.m_u16Class);); + } + DEBUG_EX_INFO(if (u32ReplyMask) DEBUG_OUTPUT.printf_P(PSTR("%s _replyMaskForHost: %s\n"), _DH(), _replyFlags2String(u32ReplyMask));); + return u32ReplyMask; +} + +/* + MDNSResponder::clsHost::_replyMaskForService + + Determines the relevant service answers for the given question + - A PTR dns-sd service enum question (_services.dns-sd._udp.local) will result into an PTR_TYPE (eg. _http._tcp.local) answer + - A PTR service type question (eg. _http._tcp.local) will result into an PTR_NAME (eg. MyESP._http._tcp.local) answer + - A PTR service name question (eg. MyESP._http._tcp.local) will result into an PTR_NAME (eg. MyESP._http._tcp.local) answer + - A SRV service name question (eg. MyESP._http._tcp.local) will result into an SRV (eg. 5000 MyESP.local) answer + - A TXT service name question (eg. MyESP._http._tcp.local) will result into an TXT (eg. c#=1) answer + + In addition, a full name match (question domain == service instance domain) is marked. +*/ +uint32_t MDNSResponder::clsHost::_replyMaskForService(const MDNSResponder::clsHost::stcRRHeader& p_RRHeader, + const MDNSResponder::clsHost::stcService& p_Service, + bool* p_pbFullNameMatch /*= 0*/) const +{ + uint32_t u32ReplyMask = 0; + (p_pbFullNameMatch ? *p_pbFullNameMatch = false : 0); + + if ((DNS_RRCLASS_IN == (p_RRHeader.m_Attributes.m_u16Class & (~0x8000))) || + (DNS_RRCLASS_ANY == (p_RRHeader.m_Attributes.m_u16Class & (~0x8000)))) + { + + stcRRDomain DNSSDDomain; + if ((_buildDomainForDNSSD(DNSSDDomain)) && // _services._dns-sd._udp.local + (p_RRHeader.m_Domain == DNSSDDomain) && + ((DNS_RRTYPE_PTR == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type))) + { + // Common service info requested + u32ReplyMask |= static_cast(enuContentFlag::PTR_TYPE); + } + + stcRRDomain 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 + u32ReplyMask |= static_cast(enuContentFlag::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 + u32ReplyMask |= static_cast(enuContentFlag::SRV); + } + if ((DNS_RRTYPE_TXT == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) + { + // Instance info TXT requested + u32ReplyMask |= static_cast(enuContentFlag::TXT); + } + } + } + else + { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _replyMaskForService: INVALID RR-class (0x%04X)!\n"), p_RRHeader.m_Attributes.m_u16Class);); + } + DEBUG_EX_INFO(if (u32ReplyMask) DEBUG_OUTPUT.printf_P(PSTR("%s _replyMaskForService(%s.%s.%s): %s\n"), _DH(), p_Service.m_pcName, p_Service.m_pcServiceType, p_Service.m_pcProtocol, _replyFlags2String(u32ReplyMask));); + return u32ReplyMask; +} + +} // namespace MDNSImplementation + +} // namespace esp8266 diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Debug.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Debug.cpp new file mode 100755 index 0000000000..3f43a54089 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Debug.cpp @@ -0,0 +1,327 @@ +/* + LEAmDNS2_Host_Debug.h + + License (MIT license): + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include + +/* + ESP8266mDNS Control.cpp +*/ + +extern "C" { + //#include "user_interface.h" +} + +#include "LEAmDNS2_lwIPdefs.h" +#include "LEAmDNS2_Priv.h" + +#ifdef MDNS_IPV4_SUPPORT +//#include +#endif +#ifdef MDNS_IPV6_SUPPORT +//#include +#endif + + +namespace esp8266 +{ + +/* + LEAmDNS +*/ +namespace experimental +{ + +#ifdef DEBUG_ESP_MDNS_RESPONDER + +/* + MDNSResponder::clsHost::_DH +*/ +const char* MDNSResponder::clsHost::_DH(const MDNSResponder::clsHost::stcService* p_pMDNSService /*= 0*/) const +{ + static char acBuffer[16 + 64]; + + *acBuffer = 0; + if (p_pMDNSService) + { + sprintf_P(acBuffer, PSTR("[MDNSResponder (%c%c%u)->%s]"), m_rNetIf.name[0], m_rNetIf.name[1], m_rNetIf.num, _service2String(p_pMDNSService)); + } + else + { + sprintf_P(acBuffer, PSTR("[MDNSResponder (%c%c%u)]"), m_rNetIf.name[0], m_rNetIf.name[1], m_rNetIf.num); + } + return acBuffer; +} + +/* + MDNSResponder::clsHost::_service2String +*/ +const char* MDNSResponder::clsHost::_service2String(const MDNSResponder::clsHost::stcService* p_pMDNSService) const +{ + static char acBuffer[64]; + + *acBuffer = 0; + if (p_pMDNSService) + { + sprintf_P(acBuffer, PSTR("%s.%s%s.%s%s.local"), + (p_pMDNSService->m_pcName ? : "-"), + (p_pMDNSService->m_pcServiceType ? ('_' == *(p_pMDNSService->m_pcServiceType) ? "" : "_") : "-"), + (p_pMDNSService->m_pcServiceType ? : "-"), + (p_pMDNSService->m_pcProtocol ? ('_' == *(p_pMDNSService->m_pcProtocol) ? "" : "_") : "-"), + (p_pMDNSService->m_pcProtocol ? : "-")); + } + return acBuffer; +} + +/* + MDNSResponder::clsHost::_printRRDomain +*/ +bool MDNSResponder::clsHost::_printRRDomain(const MDNSResponder::clsHost::stcRRDomain& p_RRDomain) const +{ + //DEBUG_OUTPUT.printf_P(PSTR("Domain: ")); + + const char* pCursor = p_RRDomain.m_acName; + uint8_t u8Length = *pCursor++; + if (u8Length) + { + while (u8Length) + { + for (uint8_t u = 0; u < u8Length; ++u) + { + DEBUG_OUTPUT.printf_P(PSTR("%c"), *(pCursor++)); + } + u8Length = *pCursor++; + if (u8Length) + { + DEBUG_OUTPUT.printf_P(PSTR(".")); + } + } + } + else // empty domain + { + DEBUG_OUTPUT.printf_P(PSTR("-empty-")); + } + //DEBUG_OUTPUT.printf_P(PSTR("\n")); + + return true; +} + +/* + MDNSResponder::clsHost::_printRRAnswer +*/ +bool MDNSResponder::clsHost::_printRRAnswer(const MDNSResponder::clsHost::stcRRAnswer& p_RRAnswer) const +{ + DEBUG_OUTPUT.printf_P(PSTR("%s RRAnswer: "), _DH()); + _printRRDomain(p_RRAnswer.m_Header.m_Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:0x%04X Class:0x%04X TTL:%u, "), p_RRAnswer.m_Header.m_Attributes.m_u16Type, p_RRAnswer.m_Header.m_Attributes.m_u16Class, p_RRAnswer.m_u32TTL); + switch (p_RRAnswer.m_Header.m_Attributes.m_u16Type & (~0x8000)) // Topmost bit might carry 'cache flush' flag + { +#ifdef MDNS_IPV4_SUPPORT + case DNS_RRTYPE_A: + DEBUG_OUTPUT.printf_P(PSTR("A IP:%s"), ((const stcRRAnswerA*)&p_RRAnswer)->m_IPAddress.toString().c_str()); + break; +#endif + case DNS_RRTYPE_PTR: + DEBUG_OUTPUT.printf_P(PSTR("PTR ")); + _printRRDomain(((const stcRRAnswerPTR*)&p_RRAnswer)->m_PTRDomain); + break; + case DNS_RRTYPE_TXT: + { + size_t stTxtLength = ((const stcRRAnswerTXT*)&p_RRAnswer)->m_Txts.c_strLength(); + char* pTxts = new char[stTxtLength]; + if (pTxts) + { + ((/*const c_str()!!*/stcRRAnswerTXT*)&p_RRAnswer)->m_Txts.c_str(pTxts); + DEBUG_OUTPUT.printf_P(PSTR("TXT(%u) %s"), stTxtLength, pTxts); + delete[] pTxts; + } + break; + } +#ifdef MDNS_IPV6_SUPPORT + case DNS_RRTYPE_AAAA: + DEBUG_OUTPUT.printf_P(PSTR("AAAA IP:%s"), ((stcRRAnswerAAAA*&)p_RRAnswer)->m_IPAddress.toString().c_str()); + break; +#endif + case DNS_RRTYPE_SRV: + DEBUG_OUTPUT.printf_P(PSTR("SRV Port:%u "), ((const stcRRAnswerSRV*)&p_RRAnswer)->m_u16Port); + _printRRDomain(((const stcRRAnswerSRV*)&p_RRAnswer)->m_SRVDomain); + break; + default: + DEBUG_OUTPUT.printf_P(PSTR("generic ")); + break; + } + DEBUG_OUTPUT.printf_P(PSTR("\n")); + + return true; +} + +/* + MDNSResponder::clsHost::_RRType2Name +*/ +const char* MDNSResponder::clsHost::_RRType2Name(uint16_t p_u16RRType) const +{ + static char acRRName[16]; + *acRRName = 0; + + switch (p_u16RRType & (~0x8000)) // Topmost bit might carry 'cache flush' flag + { + case DNS_RRTYPE_A: strcpy(acRRName, "A"); break; + case DNS_RRTYPE_PTR: strcpy(acRRName, "PTR"); break; + case DNS_RRTYPE_TXT: strcpy(acRRName, "TXT"); break; + case DNS_RRTYPE_AAAA: strcpy(acRRName, "AAAA"); break; + case DNS_RRTYPE_SRV: strcpy(acRRName, "SRV"); break; + case DNS_RRTYPE_NSEC: strcpy(acRRName, "NSEC"); break; + case DNS_RRTYPE_ANY: strcpy(acRRName, "ANY"); break; + default: + sprintf(acRRName, "Unknown(0x%04X)", p_u16RRType); // MAX 15! + } + return acRRName; +} + +/* + MDNSResponder::clsHost::_RRClass2String +*/ +const char* MDNSResponder::clsHost::_RRClass2String(uint16_t p_u16RRClass, + bool p_bIsQuery) const +{ + static char acClassString[16]; + *acClassString = 0; + + if (p_u16RRClass & 0x0001) + { + strcat(acClassString, "IN "); // 3 + } + if (p_u16RRClass & 0x8000) + { + strcat(acClassString, (p_bIsQuery ? "UNICAST " : "FLUSH ")); // 8/6 + } + + return acClassString; // 11 +} + +/* + MDNSResponder::clsHost::_replyFlags2String +*/ +const char* MDNSResponder::clsHost::_replyFlags2String(uint32_t p_u32ReplyFlags) const +{ + static char acFlagsString[64]; + + *acFlagsString = 0; + if (p_u32ReplyFlags & static_cast(enuContentFlag::A)) + { + strcat(acFlagsString, "A "); // 2 + } + if (p_u32ReplyFlags & static_cast(enuContentFlag::PTR_IPv4)) + { + strcat(acFlagsString, "PTR_IPv4 "); // 7 + } + if (p_u32ReplyFlags & static_cast(enuContentFlag::PTR_IPv6)) + { + strcat(acFlagsString, "PTR_IPv6 "); // 7 + } + if (p_u32ReplyFlags & static_cast(enuContentFlag::AAAA)) + { + strcat(acFlagsString, "AAAA "); // 5 + } + if (p_u32ReplyFlags & static_cast(enuContentFlag::PTR_TYPE)) + { + strcat(acFlagsString, "PTR_TYPE "); // 9 + } + if (p_u32ReplyFlags & static_cast(enuContentFlag::PTR_NAME)) + { + strcat(acFlagsString, "PTR_NAME "); // 9 + } + if (p_u32ReplyFlags & static_cast(enuContentFlag::TXT)) + { + strcat(acFlagsString, "TXT "); // 4 + } + if (p_u32ReplyFlags & static_cast(enuContentFlag::SRV)) + { + strcat(acFlagsString, "SRV "); // 4 + } + if (p_u32ReplyFlags & static_cast(enuContentFlag::NSEC)) + { + strcat(acFlagsString, "NSEC "); // 5 + } + + if (0 == p_u32ReplyFlags) + { + strcpy(acFlagsString, "none"); + } + + return acFlagsString; // 63 +} + +/* + MDNSResponder::clsHost::_NSECBitmap2String +*/ +const char* MDNSResponder::clsHost::_NSECBitmap2String(const stcNSECBitmap* p_pNSECBitmap) const +{ + static char acFlagsString[32]; + + *acFlagsString = 0; + if (p_pNSECBitmap->getBit(DNS_RRTYPE_A)) + { + strcat(acFlagsString, "A "); // 2 + } + if (p_pNSECBitmap->getBit(DNS_RRTYPE_PTR)) + { + strcat(acFlagsString, "PTR "); // 4 + } + if (p_pNSECBitmap->getBit(DNS_RRTYPE_AAAA)) + { + strcat(acFlagsString, "AAAA "); // 5 + } + if (p_pNSECBitmap->getBit(DNS_RRTYPE_TXT)) + { + strcat(acFlagsString, "TXT "); // 4 + } + if (p_pNSECBitmap->getBit(DNS_RRTYPE_SRV)) + { + strcat(acFlagsString, "SRV "); // 4 + } + if (p_pNSECBitmap->getBit(DNS_RRTYPE_NSEC)) + { + strcat(acFlagsString, "NSEC "); // 5 + } + + if (!*acFlagsString) + { + strcpy(acFlagsString, "none"); + } + + return acFlagsString; // 31 +} + +#endif // DEBUG_ESP_MDNS_RESPONDER + +} // namespace MDNSImplementation + +} // namespace esp8266 diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Structs.cpp new file mode 100755 index 0000000000..7121abcfca --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Structs.cpp @@ -0,0 +1,2435 @@ +/* + LEAmDNS2_Host_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 "LEAmDNS2_Priv.h" +#include "LEAmDNS2_lwIPdefs.h" + +namespace esp8266 +{ + +/* + LEAmDNS +*/ +namespace experimental +{ + +/** + Internal CLASSES & STRUCTS +*/ + +/** + MDNSResponder::clsHost::stcServiceTxt + + 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::clsHost::stcServiceTxt::stcServiceTxt constructor +*/ +MDNSResponder::clsHost::stcServiceTxt::stcServiceTxt(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::clsHost::stcServiceTxt::stcServiceTxt copy-constructor +*/ +MDNSResponder::clsHost::stcServiceTxt::stcServiceTxt(const MDNSResponder::clsHost::stcServiceTxt& p_Other) + : m_pNext(0), + m_pcKey(0), + m_pcValue(0), + m_bTemp(false) +{ + operator=(p_Other); +} + +/* + MDNSResponder::clsHost::stcServiceTxt::~stcServiceTxt destructor +*/ +MDNSResponder::clsHost::stcServiceTxt::~stcServiceTxt(void) +{ + clear(); +} + +/* + MDNSResponder::clsHost::stcServiceTxt::operator= +*/ +MDNSResponder::clsHost::stcServiceTxt& MDNSResponder::clsHost::stcServiceTxt::operator=(const MDNSResponder::clsHost::stcServiceTxt& p_Other) +{ + if (&p_Other != this) + { + clear(); + set(p_Other.m_pcKey, p_Other.m_pcValue, p_Other.m_bTemp); + } + return *this; +} + +/* + MDNSResponder::clsHost::stcServiceTxt::clear +*/ +bool MDNSResponder::clsHost::stcServiceTxt::clear(void) +{ + releaseKey(); + releaseValue(); + return true; +} + +/* + MDNSResponder::clsHost::stcServiceTxt::allocKey +*/ +char* MDNSResponder::clsHost::stcServiceTxt::allocKey(size_t p_stLength) +{ + releaseKey(); + if (p_stLength) + { + m_pcKey = new char[p_stLength + 1]; + } + return m_pcKey; +} + +/* + MDNSResponder::clsHost::stcServiceTxt::setKey +*/ +bool MDNSResponder::clsHost::stcServiceTxt::setKey(const char* p_pcKey, + size_t p_stLength) +{ + bool bResult = false; + + releaseKey(); + if (p_stLength) + { + if (allocKey(p_stLength)) + { + strncpy(m_pcKey, p_pcKey, p_stLength); + m_pcKey[p_stLength] = 0; + bResult = true; + } + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcServiceTxt::setKey +*/ +bool MDNSResponder::clsHost::stcServiceTxt::setKey(const char* p_pcKey) +{ + return setKey(p_pcKey, (p_pcKey ? strlen(p_pcKey) : 0)); +} + +/* + MDNSResponder::clsHost::stcServiceTxt::releaseKey +*/ +bool MDNSResponder::clsHost::stcServiceTxt::releaseKey(void) +{ + if (m_pcKey) + { + delete[] m_pcKey; + m_pcKey = 0; + } + return true; +} + +/* + MDNSResponder::clsHost::stcServiceTxt::allocValue +*/ +char* MDNSResponder::clsHost::stcServiceTxt::allocValue(size_t p_stLength) +{ + releaseValue(); + if (p_stLength) + { + m_pcValue = new char[p_stLength + 1]; + } + return m_pcValue; +} + +/* + MDNSResponder::clsHost::stcServiceTxt::setValue +*/ +bool MDNSResponder::clsHost::stcServiceTxt::setValue(const char* p_pcValue, + size_t p_stLength) +{ + bool bResult = false; + + releaseValue(); + if (p_stLength) + { + if (allocValue(p_stLength)) + { + strncpy(m_pcValue, p_pcValue, p_stLength); + m_pcValue[p_stLength] = 0; + bResult = true; + } + } + else // No value -> also OK + { + bResult = true; + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcServiceTxt::setValue +*/ +bool MDNSResponder::clsHost::stcServiceTxt::setValue(const char* p_pcValue) +{ + return setValue(p_pcValue, (p_pcValue ? strlen(p_pcValue) : 0)); +} + +/* + MDNSResponder::clsHost::stcServiceTxt::releaseValue +*/ +bool MDNSResponder::clsHost::stcServiceTxt::releaseValue(void) +{ + if (m_pcValue) + { + delete[] m_pcValue; + m_pcValue = 0; + } + return true; +} + +/* + MDNSResponder::clsHost::stcServiceTxt::set +*/ +bool MDNSResponder::clsHost::stcServiceTxt::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::clsHost::stcServiceTxt::update +*/ +bool MDNSResponder::clsHost::stcServiceTxt::update(const char* p_pcValue) +{ + return setValue(p_pcValue); +} + +/* + MDNSResponder::clsHost::stcServiceTxt::length + + length of eg. 'c#=1' without any closing '\0' +*/ +size_t MDNSResponder::clsHost::stcServiceTxt::length(void) const +{ + size_t stLength = 0; + if (m_pcKey) + { + stLength += strlen(m_pcKey); // Key + stLength += 1; // '=' + stLength += (m_pcValue ? strlen(m_pcValue) : 0); // Value + } + return stLength; +} + + +/** + MDNSResponder::clsHost::stcServiceTxts + + 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::clsHost::stcServiceTxts::stcServiceTxts contructor +*/ +MDNSResponder::clsHost::stcServiceTxts::stcServiceTxts(void) + : m_pTxts(0), + m_pcCache(0) +{ +} + +/* + MDNSResponder::clsHost::stcServiceTxts::stcServiceTxts copy-constructor +*/ +MDNSResponder::clsHost::stcServiceTxts::stcServiceTxts(const stcServiceTxts& p_Other) + : m_pTxts(0), + m_pcCache(0) +{ + operator=(p_Other); +} + +/* + MDNSResponder::clsHost::stcServiceTxts::~stcServiceTxts destructor +*/ +MDNSResponder::clsHost::stcServiceTxts::~stcServiceTxts(void) +{ + clear(); +} + +/* + MDNSResponder::clsHost::stcServiceTxts::operator= +*/ +MDNSResponder::clsHost::stcServiceTxts& MDNSResponder::clsHost::stcServiceTxts::operator=(const stcServiceTxts& p_Other) +{ + if (this != &p_Other) + { + clear(); + + for (stcServiceTxt* pOtherTxt = p_Other.m_pTxts; pOtherTxt; pOtherTxt = pOtherTxt->m_pNext) + { + add(new stcServiceTxt(*pOtherTxt)); + } + } + return *this; +} + +/* + MDNSResponder::clsHost::stcServiceTxts::clear +*/ +bool MDNSResponder::clsHost::stcServiceTxts::clear(void) +{ + while (m_pTxts) + { + stcServiceTxt* pNext = m_pTxts->m_pNext; + delete m_pTxts; + m_pTxts = pNext; + } + return clearCache(); +} + +/* + MDNSResponder::clsHost::stcServiceTxts::clearCache +*/ +bool MDNSResponder::clsHost::stcServiceTxts::clearCache(void) +{ + if (m_pcCache) + { + delete[] m_pcCache; + m_pcCache = 0; + } + return true; +} + +/* + MDNSResponder::clsHost::stcServiceTxts::add +*/ +bool MDNSResponder::clsHost::stcServiceTxts::add(MDNSResponder::clsHost::stcServiceTxt* p_pTxt) +{ + bool bResult = false; + + if (p_pTxt) + { + p_pTxt->m_pNext = m_pTxts; + m_pTxts = p_pTxt; + bResult = true; + } + return ((clearCache()) && + (bResult)); +} + +/* + MDNSResponder::clsHost::stcServiceTxts::remove +*/ +bool MDNSResponder::clsHost::stcServiceTxts::remove(stcServiceTxt* p_pTxt) +{ + bool bResult = false; + + if (p_pTxt) + { + stcServiceTxt* 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 ((clearCache()) && + (bResult)); +} + +/* + MDNSResponder::clsHost::stcServiceTxts::removeTempTxts +*/ +bool MDNSResponder::clsHost::stcServiceTxts::removeTempTxts(void) +{ + bool bResult = true; + + stcServiceTxt* pTxt = m_pTxts; + while ((bResult) && + (pTxt)) + { + stcServiceTxt* pNext = pTxt->m_pNext; + if (pTxt->m_bTemp) + { + bResult = remove(pTxt); + } + pTxt = pNext; + } + return ((clearCache()) && + (bResult)); +} + +/* + MDNSResponder::clsHost::stcServiceTxts::find +*/ +MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::stcServiceTxts::find(const char* p_pcKey) +{ + stcServiceTxt* pResult = 0; + + for (stcServiceTxt* pTxt = m_pTxts; pTxt; pTxt = pTxt->m_pNext) + { + if ((p_pcKey) && + (0 == strcmp(pTxt->m_pcKey, p_pcKey))) + { + pResult = pTxt; + break; + } + } + return pResult; +} + +/* + MDNSResponder::clsHost::stcServiceTxts::find +*/ +const MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::stcServiceTxts::find(const char* p_pcKey) const +{ + const stcServiceTxt* pResult = 0; + + for (const stcServiceTxt* pTxt = m_pTxts; pTxt; pTxt = pTxt->m_pNext) + { + if ((p_pcKey) && + (0 == strcmp(pTxt->m_pcKey, p_pcKey))) + { + + pResult = pTxt; + break; + } + } + return pResult; +} + +/* + MDNSResponder::clsHost::stcServiceTxts::find +*/ +MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::stcServiceTxts::find(const stcServiceTxt* p_pTxt) +{ + stcServiceTxt* pResult = 0; + + for (stcServiceTxt* pTxt = m_pTxts; pTxt; pTxt = pTxt->m_pNext) + { + if (p_pTxt == pTxt) + { + pResult = pTxt; + break; + } + } + return pResult; +} + +/* + MDNSResponder::clsHost::stcServiceTxts::length +*/ +uint16_t MDNSResponder::clsHost::stcServiceTxts::length(void) const +{ + uint16_t u16Length = 0; + + stcServiceTxt* pTxt = m_pTxts; + while (pTxt) + { + u16Length += 1; // Length byte + u16Length += pTxt->length(); // Text + pTxt = pTxt->m_pNext; + } + return u16Length; +} + +/* + MDNSResponder::clsHost::stcServiceTxts::c_strLength + + (incl. closing '\0'). Length bytes place is used for delimiting ';' and closing '\0' +*/ +size_t MDNSResponder::clsHost::stcServiceTxts::c_strLength(void) const +{ + return length(); +} + +/* + MDNSResponder::clsHost::stcServiceTxts::c_str +*/ +bool MDNSResponder::clsHost::stcServiceTxts::c_str(char* p_pcBuffer) +{ + bool bResult = false; + + if (p_pcBuffer) + { + bResult = true; + + *p_pcBuffer = 0; + for (stcServiceTxt* pTxt = m_pTxts; ((bResult) && (pTxt)); pTxt = pTxt->m_pNext) + { + size_t stLength; + if ((bResult = (0 != (stLength = (pTxt->m_pcKey ? strlen(pTxt->m_pcKey) : 0))))) + { + if (pTxt != m_pTxts) + { + *p_pcBuffer++ = ';'; + } + strncpy(p_pcBuffer, pTxt->m_pcKey, stLength); p_pcBuffer[stLength] = 0; + p_pcBuffer += stLength; + *p_pcBuffer++ = '='; + if ((stLength = (pTxt->m_pcValue ? strlen(pTxt->m_pcValue) : 0))) + { + strncpy(p_pcBuffer, pTxt->m_pcValue, stLength); p_pcBuffer[stLength] = 0; + p_pcBuffer += stLength; + } + } + } + *p_pcBuffer++ = 0; + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcServiceTxts::c_str +*/ +const char* MDNSResponder::clsHost::stcServiceTxts::c_str(void) const +{ + + if ((!m_pcCache) && + (m_pTxts) && + ((((stcServiceTxts*)this)->m_pcCache = new char[c_strLength()]))) // TRANSPARENT caching + { + ((stcServiceTxts*)this)->c_str(m_pcCache); + } + return m_pcCache; +} + +/* + MDNSResponder::clsHost::stcServiceTxts::bufferLength + + (incl. closing '\0'). +*/ +size_t MDNSResponder::clsHost::stcServiceTxts::bufferLength(void) const +{ + return (length() + 1); +} + +/* + MDNSResponder::clsHost::stcServiceTxts::toBuffer +*/ +bool MDNSResponder::clsHost::stcServiceTxts::buffer(char* p_pcBuffer) +{ + bool bResult = false; + + if (p_pcBuffer) + { + bResult = true; + + *p_pcBuffer = 0; + for (stcServiceTxt* 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 ? strlen(pTxt->m_pcKey) : 0))))) + { + memcpy(p_pcBuffer, pTxt->m_pcKey, stLength); + p_pcBuffer += stLength; + *p_pcBuffer++ = '='; + if ((stLength = (pTxt->m_pcValue ? strlen(pTxt->m_pcValue) : 0))) + { + memcpy(p_pcBuffer, pTxt->m_pcValue, stLength); + p_pcBuffer += stLength; + } + } + } + *p_pcBuffer++ = 0; + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcServiceTxts::compare +*/ +bool MDNSResponder::clsHost::stcServiceTxts::compare(const MDNSResponder::clsHost::stcServiceTxts& p_Other) const +{ + bool bResult = false; + + if ((bResult = (length() == p_Other.length()))) + { + // Compare A->B + for (const stcServiceTxt* pTxt = m_pTxts; ((bResult) && (pTxt)); pTxt = pTxt->m_pNext) + { + const stcServiceTxt* pOtherTxt = p_Other.find(pTxt->m_pcKey); + bResult = ((pOtherTxt) && + (pTxt->m_pcValue) && + (pOtherTxt->m_pcValue) && + (strlen(pTxt->m_pcValue) == strlen(pOtherTxt->m_pcValue)) && + (0 == strcmp(pTxt->m_pcValue, pOtherTxt->m_pcValue))); + } + // Compare B->A + for (const stcServiceTxt* pOtherTxt = p_Other.m_pTxts; ((bResult) && (pOtherTxt)); pOtherTxt = pOtherTxt->m_pNext) + { + const stcServiceTxt* pTxt = find(pOtherTxt->m_pcKey); + bResult = ((pTxt) && + (pOtherTxt->m_pcValue) && + (pTxt->m_pcValue) && + (strlen(pOtherTxt->m_pcValue) == strlen(pTxt->m_pcValue)) && + (0 == strcmp(pOtherTxt->m_pcValue, pTxt->m_pcValue))); + } + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcServiceTxts::operator== +*/ +bool MDNSResponder::clsHost::stcServiceTxts::operator==(const stcServiceTxts& p_Other) const +{ + return compare(p_Other); +} + +/* + MDNSResponder::clsHost::stcServiceTxts::operator!= +*/ +bool MDNSResponder::clsHost::stcServiceTxts::operator!=(const stcServiceTxts& p_Other) const +{ + return !compare(p_Other); +} + + +/** + MDNSResponder::clsHost::stcProbeInformation_Base + + Probing status information for a host or service domain + +*/ + +/* + MDNSResponder::clsHost::stcProbeInformation_Base::stcProbeInformation_Base constructor +*/ +MDNSResponder::clsHost::stcProbeInformation_Base::stcProbeInformation_Base(void) + : m_ProbingStatus(enuProbingStatus::WaitingForData), + m_u8SentCount(0), + m_Timeout(std::numeric_limits::max()), + m_bConflict(false), + m_bTiebreakNeeded(false) +{ +} + +/* + MDNSResponder::clsHost::stcProbeInformation_Base::clear +*/ +bool MDNSResponder::clsHost::stcProbeInformation_Base::clear(void) +{ + m_ProbingStatus = enuProbingStatus::WaitingForData; + m_u8SentCount = 0; + m_Timeout.reset(std::numeric_limits::max()); + m_bConflict = false; + m_bTiebreakNeeded = false; + + return true; +} + + +/** + MDNSResponder::clsHost::stcProbeInformation_Host + + Probing status information for a host or service domain + +*/ + +/* + MDNSResponder::clsHost::stcProbeInformation_Host::stcProbeInformation_Host constructor +*/ +MDNSResponder::clsHost::stcProbeInformation_Host::stcProbeInformation_Host(void) + : m_fnProbeResultCallback(0) +{ +} + +/* + MDNSResponder::clsHost::stcProbeInformation_Host::clear +*/ +bool MDNSResponder::clsHost::stcProbeInformation_Host::clear(bool p_bClearUserdata /*= false*/) +{ + if (p_bClearUserdata) + { + m_fnProbeResultCallback = 0; + } + return stcProbeInformation_Base::clear(); +} + + +/** + MDNSResponder::clsHost::stcProbeInformation_Service + + Probing status information for a host or service domain + +*/ + +/* + MDNSResponder::clsHost::stcProbeInformation_Service::stcProbeInformation_Service constructor +*/ +MDNSResponder::clsHost::stcProbeInformation_Service::stcProbeInformation_Service(void) + : m_fnProbeResultCallback(0) +{ +} + +/* + MDNSResponder::clsHost::stcProbeInformation_Service::clear +*/ +bool MDNSResponder::clsHost::stcProbeInformation_Service::clear(bool p_bClearUserdata /*= false*/) +{ + if (p_bClearUserdata) + { + m_fnProbeResultCallback = 0; + } + return stcProbeInformation_Base::clear(); +} + + +/** + MDNSResponder::clsHost::stcService + + 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::clsHost::stcService::stcService constructor +*/ +MDNSResponder::clsHost::stcService::stcService(const char* p_pcName /*= 0*/, + const char* p_pcServiceType /*= 0*/, + const char* p_pcProtocol /*= 0*/) + : m_pNext(0), + m_pcName(0), + m_bAutoName(false), + m_pcServiceType(0), + m_pcProtocol(0), + m_u16Port(0), + m_u32ReplyMask(0), + m_fnTxtCallback(0) +{ + setName(p_pcName); + setServiceType(p_pcServiceType); + setProtocol(p_pcProtocol); +} + +/* + MDNSResponder::clsHost::stcService::~stcService destructor +*/ +MDNSResponder::clsHost::stcService::~stcService(void) +{ + releaseName(); + releaseServiceType(); + releaseProtocol(); +} + +/* + MDNSResponder::clsHost::stcService::setName +*/ +bool MDNSResponder::clsHost::stcService::setName(const char* p_pcName) +{ + bool bResult = false; + + releaseName(); + size_t stLength = (p_pcName ? strlen(p_pcName) : 0); + if (stLength) + { + if ((bResult = (0 != (m_pcName = new char[stLength + 1])))) + { + strncpy(m_pcName, p_pcName, stLength); + m_pcName[stLength] = 0; + } + } + else + { + bResult = true; + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcService::releaseName +*/ +bool MDNSResponder::clsHost::stcService::releaseName(void) +{ + if (m_pcName) + { + delete[] m_pcName; + m_pcName = 0; + } + return true; +} + +/* + MDNSResponder::clsHost::stcService::setServiceType +*/ +bool MDNSResponder::clsHost::stcService::setServiceType(const char* p_pcServiceType) +{ + bool bResult = false; + + releaseServiceType(); + size_t stLength = (p_pcServiceType ? strlen(p_pcServiceType) : 0); + if (stLength) + { + if ((bResult = (0 != (m_pcServiceType = new char[stLength + 1])))) + { + strncpy(m_pcServiceType, p_pcServiceType, stLength); + m_pcServiceType[stLength] = 0; + } + } + else + { + bResult = true; + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcService::releaseServiceType +*/ +bool MDNSResponder::clsHost::stcService::releaseServiceType(void) +{ + if (m_pcServiceType) + { + delete[] m_pcServiceType; + m_pcServiceType = 0; + } + return true; +} + +/* + MDNSResponder::clsHost::stcService::setProtocol +*/ +bool MDNSResponder::clsHost::stcService::setProtocol(const char* p_pcProtocol) +{ + bool bResult = false; + + releaseProtocol(); + size_t stLength = (p_pcProtocol ? strlen(p_pcProtocol) : 0); + if (stLength) + { + if ((bResult = (0 != (m_pcProtocol = new char[stLength + 1])))) + { + strncpy(m_pcProtocol, p_pcProtocol, stLength); + m_pcProtocol[stLength] = 0; + } + } + else + { + bResult = true; + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcService::releaseProtocol +*/ +bool MDNSResponder::clsHost::stcService::releaseProtocol(void) +{ + if (m_pcProtocol) + { + delete[] m_pcProtocol; + m_pcProtocol = 0; + } + return true; +} + +/* + MDNSResponder::clsHost::stcService::probeStatus +*/ +bool MDNSResponder::clsHost::stcService::probeStatus(void) const +{ + return (enuProbingStatus::Done == m_ProbeInformation.m_ProbingStatus); +} + + +/** + MDNSResponder::clsHost::stcMsgHeader + + A MDNS message haeder. + +*/ + +/* + MDNSResponder::clsHost::stcMsgHeader::stcMsgHeader +*/ +MDNSResponder::clsHost::stcMsgHeader::stcMsgHeader(uint16_t p_u16ID /*= 0*/, + bool p_bQR /*= false*/, + uint8_t p_u8Opcode /*= 0*/, + bool p_bAA /*= false*/, + bool p_bTC /*= false*/, + bool p_bRD /*= false*/, + bool p_bRA /*= false*/, + uint8_t p_u8RCode /*= 0*/, + uint16_t p_u16QDCount /*= 0*/, + uint16_t p_u16ANCount /*= 0*/, + uint16_t p_u16NSCount /*= 0*/, + uint16_t p_u16ARCount /*= 0*/) + : m_u16ID(p_u16ID), + m_1bQR(p_bQR), m_4bOpcode(p_u8Opcode), m_1bAA(p_bAA), m_1bTC(p_bTC), m_1bRD(p_bRD), + m_1bRA(p_bRA), m_3bZ(0), m_4bRCode(p_u8RCode), + m_u16QDCount(p_u16QDCount), + m_u16ANCount(p_u16ANCount), + m_u16NSCount(p_u16NSCount), + m_u16ARCount(p_u16ARCount) +{ +} + + +/** + MDNSResponder::clsHost::stcRRDomain + + 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::clsHost::stcRRDomain::stcRRDomain constructor +*/ +MDNSResponder::clsHost::stcRRDomain::stcRRDomain(void) + : m_u16NameLength(0), + m_pcDecodedName(0) +{ + clear(); +} + +/* + MDNSResponder::clsHost::stcRRDomain::stcRRDomain copy-constructor +*/ +MDNSResponder::clsHost::stcRRDomain::stcRRDomain(const stcRRDomain& p_Other) + : m_u16NameLength(0), + m_pcDecodedName(0) +{ + operator=(p_Other); +} + +/* + MDNSResponder::clsHost::stcRRDomain::stcRRDomain destructor +*/ +MDNSResponder::clsHost::stcRRDomain::~stcRRDomain(void) +{ + clear(); +} + +/* + MDNSResponder::clsHost::stcRRDomain::operator = +*/ +MDNSResponder::clsHost::stcRRDomain& MDNSResponder::clsHost::stcRRDomain::operator=(const stcRRDomain& p_Other) +{ + if (&p_Other != this) + { + clear(); + memcpy(m_acName, p_Other.m_acName, sizeof(m_acName)); + m_u16NameLength = p_Other.m_u16NameLength; + } + return *this; +} + +/* + MDNSResponder::clsHost::stcRRDomain::clear +*/ +bool MDNSResponder::clsHost::stcRRDomain::clear(void) +{ + memset(m_acName, 0, sizeof(m_acName)); + m_u16NameLength = 0; + return clearNameCache(); +} + +/* + MDNSResponder::clsHost::stcRRDomain::clearNameCache +*/ +bool MDNSResponder::clsHost::stcRRDomain::clearNameCache(void) +{ + if (m_pcDecodedName) + { + delete[] m_pcDecodedName; + m_pcDecodedName = 0; + } + return true; +} + +/* + MDNSResponder::clsHost::stcRRDomain::addLabel +*/ +bool MDNSResponder::clsHost::stcRRDomain::addLabel(const char* p_pcLabel, + bool p_bPrependUnderline /*= false*/) +{ + bool bResult = false; + + size_t stLength = (p_pcLabel + ? (strlen(p_pcLabel) + (p_bPrependUnderline ? 1 : 0)) + : 0); + if ((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; + } + strncpy(&(m_acName[m_u16NameLength]), p_pcLabel, stLength); m_acName[m_u16NameLength + stLength] = 0; + m_u16NameLength += stLength; + } + bResult = clearNameCache(); + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcRRDomain::compare +*/ +bool MDNSResponder::clsHost::stcRRDomain::compare(const stcRRDomain& p_Other) const +{ + bool bResult = false; + + if (m_u16NameLength == p_Other.m_u16NameLength) + { + const char* pT = m_acName; + const char* pO = p_Other.m_acName; + while ((pT) && + (pO) && + (*((unsigned char*)pT) == *((unsigned char*)pO)) && // Same length AND + (0 == strncasecmp((pT + 1), (pO + 1), *((unsigned char*)pT)))) // Same content + { + if (*((unsigned char*)pT)) // Not 0 + { + pT += (1 + * ((unsigned char*)pT)); // Shift by length byte and lenght + pO += (1 + * ((unsigned char*)pO)); + } + else // Is 0 -> Successfully reached the end + { + bResult = true; + break; + } + } + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcRRDomain::operator == +*/ +bool MDNSResponder::clsHost::stcRRDomain::operator==(const stcRRDomain& p_Other) const +{ + return compare(p_Other); +} + +/* + MDNSResponder::clsHost::stcRRDomain::operator != +*/ +bool MDNSResponder::clsHost::stcRRDomain::operator!=(const stcRRDomain& p_Other) const +{ + return !compare(p_Other); +} + +/* + MDNSResponder::clsHost::stcRRDomain::operator > +*/ +bool MDNSResponder::clsHost::stcRRDomain::operator>(const stcRRDomain& p_Other) const +{ + // TODO: Check, if this is a good idea... + return !compare(p_Other); +} + +/* + MDNSResponder::clsHost::stcRRDomain::c_strLength +*/ +size_t MDNSResponder::clsHost::stcRRDomain::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::clsHost::stcRRDomain::c_str (const) +*/ +bool MDNSResponder::clsHost::stcRRDomain::c_str(char* p_pcBuffer) const +{ + bool bResult = false; + + if (p_pcBuffer) + { + *p_pcBuffer = 0; + unsigned char* pucLabelLength = (unsigned char*)m_acName; + while (*pucLabelLength) + { + memcpy(p_pcBuffer, (const char*)(pucLabelLength + 1), *pucLabelLength); + p_pcBuffer += *pucLabelLength; + pucLabelLength += (*pucLabelLength + 1); + *p_pcBuffer++ = (*pucLabelLength ? '.' : '\0'); + } + bResult = true; + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcRRDomain::c_str +*/ +const char* MDNSResponder::clsHost::stcRRDomain::c_str(void) const +{ + if ((!m_pcDecodedName) && + (m_u16NameLength) && + ((((stcRRDomain*)this)->m_pcDecodedName = new char[c_strLength()]))) // TRANSPARENT caching + { + ((stcRRDomain*)this)->c_str(m_pcDecodedName); + } + return m_pcDecodedName; +} + + +/** + MDNSResponder::clsHost::stcRRAttributes + + A MDNS attributes object. + +*/ + +/* + MDNSResponder::clsHost::stcRRAttributes::stcRRAttributes constructor +*/ +MDNSResponder::clsHost::stcRRAttributes::stcRRAttributes(uint16_t p_u16Type /*= 0*/, + uint16_t p_u16Class /*= 1 DNS_RRCLASS_IN Internet*/) + : m_u16Type(p_u16Type), + m_u16Class(p_u16Class) +{ +} + +/* + MDNSResponder::clsHost::stcRRAttributes::stcRRAttributes copy-constructor +*/ +MDNSResponder::clsHost::stcRRAttributes::stcRRAttributes(const MDNSResponder::clsHost::stcRRAttributes& p_Other) +{ + operator=(p_Other); +} + +/* + MDNSResponder::clsHost::stcRRAttributes::operator = +*/ +MDNSResponder::clsHost::stcRRAttributes& MDNSResponder::clsHost::stcRRAttributes::operator=(const MDNSResponder::clsHost::stcRRAttributes& p_Other) +{ + if (&p_Other != this) + { + m_u16Type = p_Other.m_u16Type; + m_u16Class = p_Other.m_u16Class; + } + return *this; +} + + +/** + MDNSResponder::clsHost::stcRRHeader + + A MDNS record header (domain and attributes) object. + +*/ + +/* + MDNSResponder::clsHost::stcRRHeader::stcRRHeader constructor +*/ +MDNSResponder::clsHost::stcRRHeader::stcRRHeader(void) +{ +} + +/* + MDNSResponder::clsHost::stcRRHeader::stcRRHeader copy-constructor +*/ +MDNSResponder::clsHost::stcRRHeader::stcRRHeader(const stcRRHeader& p_Other) +{ + operator=(p_Other); +} + +/* + MDNSResponder::clsHost::stcRRHeader::operator = +*/ +MDNSResponder::clsHost::stcRRHeader& MDNSResponder::clsHost::stcRRHeader::operator=(const MDNSResponder::clsHost::stcRRHeader& p_Other) +{ + if (&p_Other != this) + { + m_Domain = p_Other.m_Domain; + m_Attributes = p_Other.m_Attributes; + } + return *this; +} + +/* + MDNSResponder::clsHost::stcRRHeader::clear +*/ +bool MDNSResponder::clsHost::stcRRHeader::clear(void) +{ + m_Domain.clear(); + return true; +} + + +/** + MDNSResponder::clsHost::stcRRQuestion + + A MDNS question record object (header + question flags) + +*/ + +/* + MDNSResponder::clsHost::stcRRQuestion::stcRRQuestion constructor +*/ +MDNSResponder::clsHost::stcRRQuestion::stcRRQuestion(void) + : m_pNext(0), + m_bUnicast(false) +{ +} + + +/** + MDNSResponder::clsHost::stcNSECBitmap + + A MDNS question record object (header + question flags) + +*/ + +/* + MDNSResponder::clsHost::stcNSECBitmap::stcNSECBitmap constructor +*/ +MDNSResponder::clsHost::stcNSECBitmap::stcNSECBitmap(void) +{ + clear(); +} + +/* + MDNSResponder::clsHost::stcNSECBitmap::stcNSECBitmap destructor +*/ +bool MDNSResponder::clsHost::stcNSECBitmap::clear(void) +{ + memset(m_au8BitmapData, 0, sizeof(m_au8BitmapData)); + return true; +} + +/* + MDNSResponder::clsHost::stcNSECBitmap::length +*/ +uint16_t MDNSResponder::clsHost::stcNSECBitmap::length(void) const +{ + return sizeof(m_au8BitmapData); // 6 +} + +/* + MDNSResponder::clsHost::stcNSECBitmap::setBit +*/ +bool MDNSResponder::clsHost::stcNSECBitmap::setBit(uint16_t p_u16Bit) +{ + bool bResult = false; + + if ((p_u16Bit) && + (length() > (p_u16Bit / 8))) // bit between 0..47(2F) + { + + uint8_t& ru8Byte = m_au8BitmapData[p_u16Bit / 8]; + uint8_t u8Flag = 1 << (7 - (p_u16Bit % 8)); // (7 - (0..7)) = 7..0 + + ru8Byte |= u8Flag; + + bResult = true; + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcNSECBitmap::getBit +*/ +bool MDNSResponder::clsHost::stcNSECBitmap::getBit(uint16_t p_u16Bit) const +{ + bool bResult = false; + + if ((p_u16Bit) && + (length() > (p_u16Bit / 8))) // bit between 0..47(2F) + { + + uint8_t u8Byte = m_au8BitmapData[p_u16Bit / 8]; + uint8_t u8Flag = 1 << (7 - (p_u16Bit % 8)); // (7 - (0..7)) = 7..0 + + bResult = (u8Byte & u8Flag); + } + return bResult; +} + + +/** + MDNSResponder::clsHost::stcRRAnswer + + A MDNS answer record object (header + answer content). + This is a 'virtual' base class for all other MDNS answer classes. + +*/ + +/* + MDNSResponder::clsHost::stcRRAnswer::stcRRAnswer constructor +*/ +MDNSResponder::clsHost::stcRRAnswer::stcRRAnswer(enuAnswerType p_AnswerType, + const MDNSResponder::clsHost::stcRRHeader& 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::clsHost::stcRRAnswer::~stcRRAnswer destructor +*/ +MDNSResponder::clsHost::stcRRAnswer::~stcRRAnswer(void) +{ +} + +/* + MDNSResponder::clsHost::stcRRAnswer::answerType +*/ +MDNSResponder::clsHost::enuAnswerType MDNSResponder::clsHost::stcRRAnswer::answerType(void) const +{ + return m_AnswerType; +} + +/* + MDNSResponder::clsHost::stcRRAnswer::clear +*/ +bool MDNSResponder::clsHost::stcRRAnswer::clear(void) +{ + m_pNext = 0; + m_Header.clear(); + return true; +} + + +/** + MDNSResponder::clsHost::stcRRAnswerA + + A MDNS A answer object. + Extends the base class by an IPv4 address member. + +*/ + +#ifdef MDNS_IPV4_SUPPORT +/* + MDNSResponder::clsHost::stcRRAnswerA::stcRRAnswerA constructor +*/ +MDNSResponder::clsHost::stcRRAnswerA::stcRRAnswerA(const MDNSResponder::clsHost::stcRRHeader& p_Header, + uint32_t p_u32TTL) + : stcRRAnswer(enuAnswerType::A, p_Header, p_u32TTL), + m_IPAddress() +{ +} + +/* + MDNSResponder::clsHost::stcRRAnswerA::stcRRAnswerA destructor +*/ +MDNSResponder::clsHost::stcRRAnswerA::~stcRRAnswerA(void) +{ + clear(); +} + +/* + MDNSResponder::clsHost::stcRRAnswerA::clear +*/ +bool MDNSResponder::clsHost::stcRRAnswerA::clear(void) +{ + m_IPAddress = IPAddress(); + return true; +} +#endif + + +/** + MDNSResponder::clsHost::stcRRAnswerPTR + + A MDNS PTR answer object. + Extends the base class by a MDNS domain member. + +*/ + +/* + MDNSResponder::clsHost::stcRRAnswerPTR::stcRRAnswerPTR constructor +*/ +MDNSResponder::clsHost::stcRRAnswerPTR::stcRRAnswerPTR(const MDNSResponder::clsHost::stcRRHeader& p_Header, + uint32_t p_u32TTL) + : stcRRAnswer(enuAnswerType::PTR, p_Header, p_u32TTL) +{ +} + +/* + MDNSResponder::clsHost::stcRRAnswerPTR::~stcRRAnswerPTR destructor +*/ +MDNSResponder::clsHost::stcRRAnswerPTR::~stcRRAnswerPTR(void) +{ + clear(); +} + +/* + MDNSResponder::clsHost::stcRRAnswerPTR::clear +*/ +bool MDNSResponder::clsHost::stcRRAnswerPTR::clear(void) +{ + m_PTRDomain.clear(); + return true; +} + + +/** + MDNSResponder::clsHost::stcRRAnswerTXT + + A MDNS TXT answer object. + Extends the base class by a MDNS TXT items list member. + +*/ + +/* + MDNSResponder::clsHost::stcRRAnswerTXT::stcRRAnswerTXT constructor +*/ +MDNSResponder::clsHost::stcRRAnswerTXT::stcRRAnswerTXT(const MDNSResponder::clsHost::stcRRHeader& p_Header, + uint32_t p_u32TTL) + : stcRRAnswer(enuAnswerType::TXT, p_Header, p_u32TTL) +{ +} + +/* + MDNSResponder::clsHost::stcRRAnswerTXT::~stcRRAnswerTXT destructor +*/ +MDNSResponder::clsHost::stcRRAnswerTXT::~stcRRAnswerTXT(void) +{ + clear(); +} + +/* + MDNSResponder::clsHost::stcRRAnswerTXT::clear +*/ +bool MDNSResponder::clsHost::stcRRAnswerTXT::clear(void) +{ + m_Txts.clear(); + return true; +} + + +/** + MDNSResponder::clsHost::stcRRAnswerAAAA + + A MDNS AAAA answer object. + Extends the base class by an IPv6 address member. + +*/ + +#ifdef MDNS_IPV6_SUPPORT +/* + MDNSResponder::clsHost::stcRRAnswerAAAA::stcRRAnswerAAAA constructor +*/ +MDNSResponder::clsHost::stcRRAnswerAAAA::stcRRAnswerAAAA(const MDNSResponder::clsHost::stcRRHeader& p_Header, + uint32_t p_u32TTL) + : stcRRAnswer(enuAnswerType::AAAA, p_Header, p_u32TTL), + m_IPAddress() +{ +} + +/* + MDNSResponder::clsHost::stcRRAnswerAAAA::~stcRRAnswerAAAA destructor +*/ +MDNSResponder::clsHost::stcRRAnswerAAAA::~stcRRAnswerAAAA(void) +{ + clear(); +} + +/* + MDNSResponder::clsHost::stcRRAnswerAAAA::clear +*/ +bool MDNSResponder::clsHost::stcRRAnswerAAAA::clear(void) +{ + m_IPAddress = IPAddress(); + return true; +} +#endif + + +/** + MDNSResponder::clsHost::stcRRAnswerSRV + + A MDNS SRV answer object. + Extends the base class by a port member. + +*/ + +/* + MDNSResponder::clsHost::stcRRAnswerSRV::stcRRAnswerSRV constructor +*/ +MDNSResponder::clsHost::stcRRAnswerSRV::stcRRAnswerSRV(const MDNSResponder::clsHost::stcRRHeader& p_Header, + uint32_t p_u32TTL) + : stcRRAnswer(enuAnswerType::SRV, p_Header, p_u32TTL), + m_u16Priority(0), + m_u16Weight(0), + m_u16Port(0) +{ +} + +/* + MDNSResponder::clsHost::stcRRAnswerSRV::~stcRRAnswerSRV destructor +*/ +MDNSResponder::clsHost::stcRRAnswerSRV::~stcRRAnswerSRV(void) +{ + clear(); +} + +/* + MDNSResponder::clsHost::stcRRAnswerSRV::clear +*/ +bool MDNSResponder::clsHost::stcRRAnswerSRV::clear(void) +{ + m_u16Priority = 0; + m_u16Weight = 0; + m_u16Port = 0; + m_SRVDomain.clear(); + return true; +} + + +/** + MDNSResponder::clsHost::stcRRAnswerGeneric + + An unknown (generic) MDNS answer object. + Extends the base class by a RDATA buffer member. + +*/ + +/* + MDNSResponder::clsHost::stcRRAnswerGeneric::stcRRAnswerGeneric constructor +*/ +MDNSResponder::clsHost::stcRRAnswerGeneric::stcRRAnswerGeneric(const stcRRHeader& p_Header, + uint32_t p_u32TTL) + : stcRRAnswer(enuAnswerType::Generic, p_Header, p_u32TTL), + m_u16RDLength(0), + m_pu8RDData(0) +{ +} + +/* + MDNSResponder::clsHost::stcRRAnswerGeneric::~stcRRAnswerGeneric destructor +*/ +MDNSResponder::clsHost::stcRRAnswerGeneric::~stcRRAnswerGeneric(void) +{ + clear(); +} + +/* + MDNSResponder::clsHost::stcRRAnswerGeneric::clear +*/ +bool MDNSResponder::clsHost::stcRRAnswerGeneric::clear(void) +{ + if (m_pu8RDData) + { + delete[] m_pu8RDData; + m_pu8RDData = 0; + } + m_u16RDLength = 0; + + return true; +} + + +/** + MDNSResponder::clsHost::stcSendParameter + + 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::clsHost::stcSendParameter::stcDomainCacheItem + + A cached host or service domain, incl. the offset in the UDP output buffer. + +*/ + +/* + MDNSResponder::clsHost::stcSendParameter::stcDomainCacheItem::stcDomainCacheItem constructor +*/ +MDNSResponder::clsHost::stcSendParameter::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::clsHost::stcSendParameter +*/ + +/* + MDNSResponder::clsHost::stcSendParameter::stcSendParameter constructor +*/ +MDNSResponder::clsHost::stcSendParameter::stcSendParameter(void) + : m_pQuestions(0), + m_Response(enuResponseType::None), + m_pDomainCacheItems(0) +{ + clear(); +} + +/* + MDNSResponder::clsHost::stcSendParameter::~stcSendParameter destructor +*/ +MDNSResponder::clsHost::stcSendParameter::~stcSendParameter(void) +{ + clear(); +} + +/* + MDNSResponder::clsHost::stcSendParameter::clear +*/ +bool MDNSResponder::clsHost::stcSendParameter::clear(void) +{ + m_u16ID = 0; + flushQuestions(); + m_u32HostReplyMask = 0; + + m_bLegacyQuery = false; + m_Response = enuResponseType::None; + m_bAuthorative = false; + m_bCacheFlush = true; + m_bUnicast = false; + m_bUnannounce = false; + + m_u16Offset = 0; + flushDomainCache(); + return true; +} + +/* + MDNSResponder::clsHost::stcSendParameter::flushQuestions +*/ +bool MDNSResponder::clsHost::stcSendParameter::flushQuestions(void) +{ + while (m_pQuestions) + { + stcRRQuestion* pNext = m_pQuestions->m_pNext; + delete m_pQuestions; + m_pQuestions = pNext; + } + return true; +} + +/* + MDNSResponder::clsHost::stcSendParameter::flushDomainCache +*/ +bool MDNSResponder::clsHost::stcSendParameter::flushDomainCache(void) +{ + while (m_pDomainCacheItems) + { + stcDomainCacheItem* pNext = m_pDomainCacheItems->m_pNext; + delete m_pDomainCacheItems; + m_pDomainCacheItems = pNext; + } + return true; +} + +/* + MDNSResponder::clsHost::stcSendParameter::flushTempContent +*/ +bool MDNSResponder::clsHost::stcSendParameter::flushTempContent(void) +{ + m_u16Offset = 0; + flushDomainCache(); + return true; +} + +/* + MDNSResponder::clsHost::stcSendParameter::shiftOffset +*/ +bool MDNSResponder::clsHost::stcSendParameter::shiftOffset(uint16_t p_u16Shift) +{ + m_u16Offset += p_u16Shift; + return true; +} + +/* + MDNSResponder::clsHost::stcSendParameter::addDomainCacheItem +*/ +bool MDNSResponder::clsHost::stcSendParameter::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::clsHost::stcSendParameter::findCachedDomainOffset +*/ +uint16_t MDNSResponder::clsHost::stcSendParameter::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); +} + + +/** + MDNSResponder::clsHost::stcQuery + + 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::clsHost::stcQuery::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 IPv4 address + (- an IPv6 address) + - a MDNS TXTs + The existance of a component is flaged in 'm_u32ContentFlags'. + For every answer component a TTL value is maintained. + Answer objects can be connected to a linked list. + + For the host domain, service domain and TXTs components, a char array + representation can be retrieved (which is created on demand). + +*/ + +/** + MDNSResponder::clsHost::stcQuery::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::clsHost::stcQuery::stcAnswer::stcTTL::stcTTL constructor +*/ +MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::stcTTL(void) + : m_u32TTL(0), + m_TTLTimeout(std::numeric_limits::max()), + m_TimeoutLevel(static_cast(enuTimeoutLevel::None)) +{ +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::set +*/ +bool MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::set(uint32_t p_u32TTL) +{ + m_u32TTL = p_u32TTL; + if (m_u32TTL) + { + m_TimeoutLevel = static_cast(enuTimeoutLevel::Base); // Set to 80% + m_TTLTimeout.reset(timeout()); + } + else + { + m_TimeoutLevel = static_cast(enuTimeoutLevel::None); // undef + m_TTLTimeout.reset(std::numeric_limits::max()); + } + return true; +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::flagged +*/ +bool MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::flagged(void) const +{ + return ((m_u32TTL) && + (static_cast(enuTimeoutLevel::None) != m_TimeoutLevel) && + (((esp8266::polledTimeout::timeoutTemplate*)&m_TTLTimeout)->expired())); // Cast-away the const; in case of oneShot-timer OK (but ugly...) +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::restart +*/ +bool MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::restart(void) +{ + bool bResult = true; + + if ((static_cast(enuTimeoutLevel::Base) <= m_TimeoutLevel) && // >= 80% AND + (static_cast(enuTimeoutLevel::Final) > m_TimeoutLevel)) // < 100% + { + + m_TimeoutLevel += static_cast(enuTimeoutLevel::Interval); // increment by 5% + m_TTLTimeout.reset(timeout()); + } + else + { + bResult = false; + m_TTLTimeout.reset(std::numeric_limits::max()); + m_TimeoutLevel = static_cast(enuTimeoutLevel::None); + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::prepareDeletion +*/ +bool MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::prepareDeletion(void) +{ + m_TimeoutLevel = static_cast(enuTimeoutLevel::Final); + m_TTLTimeout.reset(1 * 1000); // See RFC 6762, 10.1 + + return true; +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::finalTimeoutLevel +*/ +bool MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::finalTimeoutLevel(void) const +{ + return (static_cast(enuTimeoutLevel::Final) == m_TimeoutLevel); +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::timeout +*/ +unsigned long MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::timeout(void) const +{ + uint32_t u32Timeout = std::numeric_limits::max(); + + if (static_cast(enuTimeoutLevel::Base) == m_TimeoutLevel) // 80% + { + u32Timeout = (m_u32TTL * 800); // to milliseconds + } + else if ((static_cast(enuTimeoutLevel::Base) < m_TimeoutLevel) && // >80% AND + (static_cast(enuTimeoutLevel::Final) >= m_TimeoutLevel)) // <= 100% + { + + u32Timeout = (m_u32TTL * 50); + } // else: invalid + return u32Timeout; +} + + +/** + MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress + +*/ + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress::stcIPAddress constructor +*/ +MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress::stcIPAddress(IPAddress p_IPAddress, + uint32_t p_u32TTL /*= 0*/) + : m_pNext(0), + m_IPAddress(p_IPAddress) +{ + m_TTL.set(p_u32TTL); +} + + +/** + MDNSResponder::clsHost::stcQuery::stcAnswer +*/ + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::stcAnswer constructor +*/ +MDNSResponder::clsHost::stcQuery::stcAnswer::stcAnswer(void) + : m_pNext(0), + m_u16Port(0), +#ifdef MDNS_IPV4_SUPPORT + m_pIPv4Addresses(0), +#endif +#ifdef MDNS_IPV6_SUPPORT + m_pIPv6Addresses(0), +#endif + m_QueryAnswerFlags(0) +{ +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::~stcAnswer destructor +*/ +MDNSResponder::clsHost::stcQuery::stcAnswer::~stcAnswer(void) +{ + clear(); +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::clear +*/ +bool MDNSResponder::clsHost::stcQuery::stcAnswer::clear(void) +{ + return ( +#ifdef MDNS_IPV4_SUPPORT + (releaseIPv4Addresses()) && +#endif +#ifdef MDNS_IPV6_SUPPORT + (releaseIPv6Addresses()) +#endif + ); +} + +#ifdef MDNS_IPV4_SUPPORT +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::releaseIPv4Addresses +*/ +bool MDNSResponder::clsHost::stcQuery::stcAnswer::releaseIPv4Addresses(void) +{ + while (m_pIPv4Addresses) + { + stcIPAddress* pNext = m_pIPv4Addresses->m_pNext; + delete m_pIPv4Addresses; + m_pIPv4Addresses = pNext; + } + return true; +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::addIPv4Address +*/ +bool MDNSResponder::clsHost::stcQuery::stcAnswer::addIPv4Address(MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* p_pIPv4Address) +{ + bool bResult = false; + + if (p_pIPv4Address) + { + p_pIPv4Address->m_pNext = m_pIPv4Addresses; + m_pIPv4Addresses = p_pIPv4Address; + bResult = true; + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::removeIPv4Address +*/ +bool MDNSResponder::clsHost::stcQuery::stcAnswer::removeIPv4Address(MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* p_pIPv4Address) +{ + bool bResult = false; + + if (p_pIPv4Address) + { + stcIPAddress* pPred = m_pIPv4Addresses; + while ((pPred) && + (pPred->m_pNext != p_pIPv4Address)) + { + pPred = pPred->m_pNext; + } + if (pPred) + { + pPred->m_pNext = p_pIPv4Address->m_pNext; + delete p_pIPv4Address; + bResult = true; + } + else if (m_pIPv4Addresses == p_pIPv4Address) // No predecesor, but first item + { + m_pIPv4Addresses = p_pIPv4Address->m_pNext; + delete p_pIPv4Address; + bResult = true; + } + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::findIPv4Address (const) +*/ +const MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* MDNSResponder::clsHost::stcQuery::stcAnswer::findIPv4Address(const IPAddress& p_IPAddress) const +{ + return (stcIPAddress*)(((const stcAnswer*)this)->findIPv4Address(p_IPAddress)); +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::findIPv4Address +*/ +MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* MDNSResponder::clsHost::stcQuery::stcAnswer::findIPv4Address(const IPAddress& p_IPAddress) +{ + stcIPAddress* pIPv4Address = m_pIPv4Addresses; + while (pIPv4Address) + { + if (pIPv4Address->m_IPAddress == p_IPAddress) + { + break; + } + pIPv4Address = pIPv4Address->m_pNext; + } + return pIPv4Address; +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::IPv4AddressCount +*/ +uint32_t MDNSResponder::clsHost::stcQuery::stcAnswer::IPv4AddressCount(void) const +{ + uint32_t u32Count = 0; + + stcIPAddress* pIPv4Address = m_pIPv4Addresses; + while (pIPv4Address) + { + ++u32Count; + pIPv4Address = pIPv4Address->m_pNext; + } + return u32Count; +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::IPv4AddressAtIndex +*/ +MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* MDNSResponder::clsHost::stcQuery::stcAnswer::IPv4AddressAtIndex(uint32_t p_u32Index) +{ + return (stcIPAddress*)(((const stcAnswer*)this)->IPv4AddressAtIndex(p_u32Index)); +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::IPv4AddressAtIndex (const) +*/ +const MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* MDNSResponder::clsHost::stcQuery::stcAnswer::IPv4AddressAtIndex(uint32_t p_u32Index) const +{ + const stcIPAddress* pIPv4Address = 0; + + if (((uint32_t)(-1) != p_u32Index) && + (m_pIPv4Addresses)) + { + + uint32_t u32Index; + for (pIPv4Address = m_pIPv4Addresses, u32Index = 0; ((pIPv4Address) && (u32Index < p_u32Index)); pIPv4Address = pIPv4Address->m_pNext, ++u32Index); + } + return pIPv4Address; +} +#endif + +#ifdef MDNS_IPV6_SUPPORT +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::releaseIPv6Addresses +*/ +bool MDNSResponder::clsHost::stcQuery::stcAnswer::releaseIPv6Addresses(void) +{ + while (m_pIPv6Addresses) + { + stcIPAddress* pNext = m_pIPv6Addresses->m_pNext; + delete m_pIPv6Addresses; + m_pIPv6Addresses = pNext; + } + return true; +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::addIPv6Address +*/ +bool MDNSResponder::clsHost::stcQuery::stcAnswer::addIPv6Address(MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* p_pIPv6Address) +{ + bool bResult = false; + + if (p_pIPv6Address) + { + p_pIPv6Address->m_pNext = m_pIPv6Addresses; + m_pIPv6Addresses = p_pIPv6Address; + bResult = true; + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::removeIPv6Address +*/ +bool MDNSResponder::clsHost::stcQuery::stcAnswer::removeIPv6Address(MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* p_pIPv6Address) +{ + bool bResult = false; + + if (p_pIPv6Address) + { + stcIPAddress* pPred = m_pIPv6Addresses; + while ((pPred) && + (pPred->m_pNext != p_pIPv6Address)) + { + pPred = pPred->m_pNext; + } + if (pPred) + { + pPred->m_pNext = p_pIPv6Address->m_pNext; + delete p_pIPv6Address; + bResult = true; + } + else if (m_pIPv6Addresses == p_pIPv6Address) // No predecesor, but first item + { + m_pIPv6Addresses = p_pIPv6Address->m_pNext; + delete p_pIPv6Address; + bResult = true; + } + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::findIPv6Address +*/ +MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* MDNSResponder::clsHost::stcQuery::stcAnswer::findIPv6Address(const IPAddress& p_IPAddress) +{ + return (stcIPAddress*)(((const stcAnswer*)this)->findIPv6Address(p_IPAddress)); +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::findIPv6Address (const) +*/ +const MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* MDNSResponder::clsHost::stcQuery::stcAnswer::findIPv6Address(const IPAddress& p_IPAddress) const +{ + const stcIPAddress* pIPv6Address = m_pIPv6Addresses; + while (pIPv6Address) + { + if (pIPv6Address->m_IPAddress == p_IPAddress) + { + break; + } + pIPv6Address = pIPv6Address->m_pNext; + } + return pIPv6Address; +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::IPv6AddressCount +*/ +uint32_t MDNSResponder::clsHost::stcQuery::stcAnswer::IPv6AddressCount(void) const +{ + uint32_t u32Count = 0; + + stcIPAddress* pIPv6Address = m_pIPv6Addresses; + while (pIPv6Address) + { + ++u32Count; + pIPv6Address = pIPv6Address->m_pNext; + } + return u32Count; +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::IPv6AddressAtIndex (const) +*/ +const MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* MDNSResponder::clsHost::stcQuery::stcAnswer::IPv6AddressAtIndex(uint32_t p_u32Index) const +{ + return (stcIPAddress*)(((const stcAnswer*)this)->IPv6AddressAtIndex(p_u32Index)); +} + +/* + MDNSResponder::clsHost::stcQuery::stcAnswer::IPv6AddressAtIndex +*/ +MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* MDNSResponder::clsHost::stcQuery::stcAnswer::IPv6AddressAtIndex(uint32_t p_u32Index) +{ + stcIPAddress* pIPv6Address = 0; + + if (((uint32_t)(-1) != p_u32Index) && + (m_pIPv6Addresses)) + { + + uint32_t u32Index; + for (pIPv6Address = m_pIPv6Addresses, u32Index = 0; ((pIPv6Address) && (u32Index < p_u32Index)); pIPv6Address = pIPv6Address->m_pNext, ++u32Index); + } + return pIPv6Address; +} +#endif + + +/** + MDNSResponder::clsHost::stcQuery + + 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::clsHost::stcQuery::stcQuery constructor +*/ +MDNSResponder::clsHost::stcQuery::stcQuery(const enuQueryType p_QueryType) + : m_pNext(0), + m_QueryType(p_QueryType), + m_fnCallback(0), + m_bLegacyQuery(false), + m_u8SentCount(0), + m_ResendTimeout(std::numeric_limits::max()), + m_bAwaitingAnswers(true), + m_pAnswers(0) +{ + clear(); + m_QueryType = p_QueryType; +} + +/* + MDNSResponder::clsHost::stcQuery::~stcQuery destructor +*/ +MDNSResponder::clsHost::stcQuery::~stcQuery(void) +{ + clear(); +} + +/* + MDNSResponder::clsHost::stcQuery::clear +*/ +bool MDNSResponder::clsHost::stcQuery::clear(void) +{ + m_pNext = 0; + m_QueryType = enuQueryType::None; + m_fnCallback = 0; + m_bLegacyQuery = false; + m_u8SentCount = 0; + m_ResendTimeout.reset(std::numeric_limits::max()); + m_bAwaitingAnswers = true; + while (m_pAnswers) + { + stcAnswer* pNext = m_pAnswers->m_pNext; + delete m_pAnswers; + m_pAnswers = pNext; + } + return true; +} + +/* + MDNSResponder::clsHost::stcQuery::answerCount +*/ +uint32_t MDNSResponder::clsHost::stcQuery::answerCount(void) const +{ + uint32_t u32Count = 0; + + stcAnswer* pAnswer = m_pAnswers; + while (pAnswer) + { + ++u32Count; + pAnswer = pAnswer->m_pNext; + } + return u32Count; +} + +/* + MDNSResponder::clsHost::stcQuery::answerAtIndex +*/ +const MDNSResponder::clsHost::stcQuery::stcAnswer* MDNSResponder::clsHost::stcQuery::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) && (u32Index < p_u32Index)); pAnswer = pAnswer->m_pNext, ++u32Index); + } + return pAnswer; +} + +/* + MDNSResponder::clsHost::stcQuery::answerAtIndex +*/ +MDNSResponder::clsHost::stcQuery::stcAnswer* MDNSResponder::clsHost::stcQuery::answerAtIndex(uint32_t p_u32Index) +{ + return (stcAnswer*)(((const stcQuery*)this)->answerAtIndex(p_u32Index)); +} + +/* + MDNSResponder::clsHost::stcQuery::indexOfAnswer +*/ +uint32_t MDNSResponder::clsHost::stcQuery::indexOfAnswer(const MDNSResponder::clsHost::stcQuery::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::clsHost::stcQuery::addAnswer +*/ +bool MDNSResponder::clsHost::stcQuery::addAnswer(MDNSResponder::clsHost::stcQuery::stcAnswer* p_pAnswer) +{ + bool bResult = false; + + if (p_pAnswer) + { + p_pAnswer->m_pNext = m_pAnswers; + m_pAnswers = p_pAnswer; + bResult = true; + } + return bResult; +} + +/* + MDNSResponder::clsHost::stcQuery::removeAnswer +*/ +bool MDNSResponder::clsHost::stcQuery::removeAnswer(MDNSResponder::clsHost::stcQuery::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::clsHost::stcQuery::findAnswerForServiceDomain +*/ +MDNSResponder::clsHost::stcQuery::stcAnswer* MDNSResponder::clsHost::stcQuery::findAnswerForServiceDomain(const MDNSResponder::clsHost::stcRRDomain& p_ServiceDomain) +{ + stcAnswer* pAnswer = m_pAnswers; + while (pAnswer) + { + if (pAnswer->m_ServiceDomain == p_ServiceDomain) + { + break; + } + pAnswer = pAnswer->m_pNext; + } + return pAnswer; +} + +/* + MDNSResponder::clsHost::stcQuery::findAnswerForHostDomain +*/ +MDNSResponder::clsHost::stcQuery::stcAnswer* MDNSResponder::clsHost::stcQuery::findAnswerForHostDomain(const MDNSResponder::clsHost::stcRRDomain& p_HostDomain) +{ + stcAnswer* pAnswer = m_pAnswers; + while (pAnswer) + { + if (pAnswer->m_HostDomain == p_HostDomain) + { + break; + } + pAnswer = pAnswer->m_pNext; + } + return pAnswer; +} + + +} // namespace MDNSImplementation + +} // namespace esp8266 + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Transfer.cpp new file mode 100755 index 0000000000..5da81255ab --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Transfer.cpp @@ -0,0 +1,2390 @@ +/* + LEAmDNS2_Host_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/netif.h" + +#include "LEAmDNS2_lwIPdefs.h" +#include "LEAmDNS2_Priv.h" + + +namespace esp8266 +{ + +/* + LEAmDNS +*/ +namespace experimental +{ + +/** + 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_IPV4_SUPPORT +static const char* scpcReverseIPv4Domain = "in-addr"; +#endif +#ifdef MDNS_IPV6_SUPPORT +static const char* scpcReverseIPv6Domain = "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::clsHost::_sendMDNSMessage(MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + bool bResult = false; + + uint8_t u8AvailableProtocols = 0; +#ifdef MDNS_IPV4_SUPPORT + // Only send out IPv4 messages, if we've got an IPv4 address + if (_getResponderIPAddress(enuIPProtocolType::V4).isSet()) + { + u8AvailableProtocols |= static_cast(enuIPProtocolType::V4); + } + DEBUG_EX_INFO(else + { + DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage: No IPv4 address available!\n"), _DH()); + }); +#endif +#ifdef MDNS_IPV6_SUPPORT + // Only send out IPv6 messages, if we've got an IPv6 address + if (_getResponderIPAddress(enuIPProtocolType::V6).isSet()) + { + u8AvailableProtocols |= static_cast(enuIPProtocolType::V6); + } + DEBUG_EX_INFO(else + { + DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage: No IPv6 address available!\n"), _DH()); + }); +#endif + + if (stcSendParameter::enuResponseType::None != p_rSendParameter.m_Response) + { + IPAddress ipRemote = ((stcSendParameter::enuResponseType::Response == p_rSendParameter.m_Response) + ? m_rUDPContext.getRemoteAddress() + : IPAddress()); + + if (p_rSendParameter.m_bUnicast) // Unicast response -> Send to querier + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage: Will send unicast to '%s'.\n"), _DH(), ipRemote.toString().c_str());); + DEBUG_EX_ERR(if (!ipRemote.isSet()) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage: MISSING remote address for unicast response!\n"), _DH());); + + bResult = ((ipRemote.isSet()) && + (_prepareMDNSMessage(p_rSendParameter)) && + (m_rUDPContext.send(ipRemote, m_rUDPContext.getRemotePort()))); + } + else // Multicast response -> Send via the same network interface, that received the query + { +#ifdef MDNS_IPV4_SUPPORT + if (((!ipRemote.isSet()) || // NO remote IP + (ipRemote.isV4())) && // OR IPv4 + (u8AvailableProtocols & static_cast(enuIPProtocolType::V4))) // AND IPv4 protocol available + { + + bResult = _sendMDNSMessage_Multicast(p_rSendParameter, static_cast(enuIPProtocolType::V4)); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + if (((!ipRemote.isSet()) || // NO remote IP + (ipRemote.isV6())) && // OR IPv6 + (u8AvailableProtocols & static_cast(enuIPProtocolType::V6))) // AND IPv6 protocol available + { + + bResult = _sendMDNSMessage_Multicast(p_rSendParameter, static_cast(enuIPProtocolType::V6)); + } +#endif + } + } + else // Multicast query -> Send by all available protocols + { + bResult = ((u8AvailableProtocols) && + (_sendMDNSMessage_Multicast(p_rSendParameter, u8AvailableProtocols))); + } + + // Finally clear service reply masks + for (stcService* pService = m_pServices; pService; pService = pService->m_pNext) + { + pService->m_u32ReplyMask = 0; + } + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage: FAILED!\n"), _DH());); + return bResult; +} + +#include "cont.h" +/* + MDNSResponder::_sendMDNSMessage_Multicast + + Fills the UDP output buffer (via _prepareMDNSMessage) and sends the buffer + via the selected WiFi protocols +*/ +bool MDNSResponder::clsHost::_sendMDNSMessage_Multicast(MDNSResponder::clsHost::stcSendParameter& p_rSendParameter, + uint8_t p_IPProtocolTypes) +{ + bool bIPv4Result = true; + bool bIPv6Result = true; + +#ifdef MDNS_IPV4_SUPPORT + if (p_IPProtocolTypes & static_cast(enuIPProtocolType::V4)) + { + IPAddress ip4MulticastAddress(DNS_MQUERY_IPV4_GROUP_INIT); + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage_Multicast v4: Will send to '%s'.\n"), _DH(), ip4MulticastAddress.toString().c_str());); + DEBUG_EX_INFO(if (!_getResponderIPAddress(enuIPProtocolType::V4)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage_Multicast v4: NO IPv4 address!.\n"), _DH());); + bIPv4Result = ((_prepareMDNSMessage(p_rSendParameter)) && + (m_rUDPContext.setMulticastInterface(&m_rNetIf), true) && + (m_rUDPContext.send(ip4MulticastAddress, DNS_MQUERY_PORT)) && + (m_rUDPContext.setMulticastInterface(0), true)); + DEBUG_EX_ERR(if (!bIPv4Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage_Multicast (V4): FAILED!\n"), _DH());); + } +#endif + +#ifdef MDNS_IPV6_SUPPORT + if (p_IPProtocolTypes & static_cast(enuIPProtocolType::V6)) + { + IPAddress ip6MulticastAddress(DNS_MQUERY_IPV6_GROUP_INIT); + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage_Multicast v6: Will send to '%s'.\n"), _DH(), ip6MulticastAddress.toString().c_str());); + DEBUG_EX_INFO(if (!_getResponderIPAddress(enuIPProtocolType::V6)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage_Multicast v6: NO IPv6 address!.\n"), _DH());); + DEBUG_EX_ERR( + bool bPrepareMessage = false; + bool bUDPContextSend = false; + ); + bIPv6Result = ((DEBUG_EX_ERR(bPrepareMessage =)_prepareMDNSMessage(p_rSendParameter)) && + (m_rUDPContext.setMulticastInterface(&m_rNetIf), true) && + (DEBUG_EX_ERR(bUDPContextSend =)m_rUDPContext.send(ip6MulticastAddress, DNS_MQUERY_PORT)) && + (m_rUDPContext.setMulticastInterface(0), true)); + DEBUG_EX_ERR(if (!bIPv6Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage_Multicast (V6): FAILED! (%s, %s, %s)\n"), _DH(), (_getResponderIPAddress(enuIPProtocolType::V6).isSet() ? "1" : "0"), (bPrepareMessage ? "1" : "0"), (bUDPContextSend ? "1" : "0"));); + } +#endif + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage_Multicast: %s!\n\n"), _DH(), ((bIPv4Result && bIPv6Result) ? "Succeeded" : "FAILED"));); + DEBUG_EX_ERR(if (!(bIPv4Result && bIPv6Result)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage_Multicast: FAILED!\n"), _DH());); + return (bIPv4Result && bIPv6Result); +} + +/* + 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::clsHost::_prepareMDNSMessage(MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage\n"));); + bool bResult = true; + + // Prepare output buffer for potential reuse + p_rSendParameter.flushTempContent(); + + // Prepare header; count answers + stcMsgHeader msgHeader(p_rSendParameter.m_u16ID, + (static_cast(stcSendParameter::enuResponseType::None) != p_rSendParameter.m_Response), + 0, + p_rSendParameter.m_bAuthorative); + // If this is a response, the answers are anwers, + // else this is a query or probe and the answers go into auth section + uint16_t& ru16Answers = ((stcSendParameter::enuResponseType::None != p_rSendParameter.m_Response) + ? msgHeader.m_u16ANCount // Usual answers + : msgHeader.m_u16NSCount); // Authorative answers + + /** + enuSequence + */ + using typeSequence = uint8_t; + enum class enuSequence : typeSequence + { + Count = 0, + Send = 1 + }; + + // Two step sequence: 'Count' and 'Send' + for (typeSequence sequence = static_cast(enuSequence::Count); ((bResult) && (sequence <= static_cast(enuSequence::Send))); ++sequence) + { + DEBUG_EX_INFO( + if (static_cast(enuSequence::Send) == sequence) + DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), + _DH(), + (unsigned)msgHeader.m_u16ID, + (unsigned)msgHeader.m_1bQR, (unsigned)msgHeader.m_4bOpcode, (unsigned)msgHeader.m_1bAA, (unsigned)msgHeader.m_1bTC, (unsigned)msgHeader.m_1bRD, + (unsigned)msgHeader.m_1bRA, (unsigned)msgHeader.m_4bRCode, + (unsigned)msgHeader.m_u16QDCount, + (unsigned)msgHeader.m_u16ANCount, + (unsigned)msgHeader.m_u16NSCount, + (unsigned)msgHeader.m_u16ARCount); + ); + // Count/send + // Header + bResult = ((static_cast(enuSequence::Count) == sequence) + ? true + : _writeMDNSMsgHeader(msgHeader, p_rSendParameter)); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSMsgHeader FAILED!\n"), _DH());); + // Questions + for (stcRRQuestion* pQuestion = p_rSendParameter.m_pQuestions; ((bResult) && (pQuestion)); pQuestion = pQuestion->m_pNext) + { + ((static_cast(enuSequence::Count) == sequence) + ? ++msgHeader.m_u16QDCount + : (bResult = _writeMDNSQuestion(*pQuestion, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSQuestion FAILED!\n"), _DH());); + } + + // Answers and authorative answers + // NSEC host (part 1) + uint32_t u32NSECContent = 0; +#ifdef MDNS_IPV4_SUPPORT + // A + if ((bResult) && + (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::A)) && + (_getResponderIPAddress(enuIPProtocolType::V4).isSet())) + { + + u32NSECContent |= static_cast(enuContentFlag::A); + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_A(_getResponderIPAddress(enuIPProtocolType::V4), p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_A(A) FAILED!\n"), _DH());); + } + // PTR_IPv4 + if ((bResult) && + (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::PTR_IPv4)) && + (_getResponderIPAddress(enuIPProtocolType::V4).isSet())) + { + + u32NSECContent |= static_cast(enuContentFlag::PTR_IPv4); + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_PTR_IPv4(_getResponderIPAddress(enuIPProtocolType::V4), p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_PTR_IPv4 FAILED!\n"), _DH());); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + // AAAA + if ((bResult) && + (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::AAAA)) && + (_getResponderIPAddress(enuIPProtocolType::V6).isSet())) + { + + u32NSECContent |= static_cast(enuContentFlag::AAAA); + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_AAAA(_getResponderIPAddress(enuIPProtocolType::V6), p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_AAAA(A) FAILED!\n"), _DH());); + } + // PTR_IPv6 + if ((bResult) && + (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::PTR_IPv6)) && + (_getResponderIPAddress(enuIPProtocolType::V6).isSet())) + { + + u32NSECContent |= static_cast(enuContentFlag::PTR_IPv6); + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_PTR_IPv6(_getResponderIPAddress(enuIPProtocolType::V6), p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_PTR_IPv6 FAILED!\n"), _DH());); + } +#endif + + for (stcService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) + { + // PTR_TYPE + if ((bResult) && + (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_TYPE))) + { + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_PTR_TYPE(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_PTR_TYPE FAILED!\n"), _DH());); + } + // PTR_NAME + if ((bResult) && + (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_NAME))) + { + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_PTR_NAME(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_PTR_NAME FAILED!\n"), _DH());); + } + // SRV + if ((bResult) && + (pService->m_u32ReplyMask & static_cast(enuContentFlag::SRV))) + { + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_SRV(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_SRV(A) FAILED!\n"), _DH());); + } + // TXT + if ((bResult) && + (pService->m_u32ReplyMask & static_cast(enuContentFlag::TXT))) + { + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_TXT(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_TXT(A) FAILED!\n"), _DH());); + } + } // for services + + // Additional answers + uint16_t& ru16AdditionalAnswers = msgHeader.m_u16ARCount; + +#ifdef MDNS_IPV4_SUPPORT + bool bNeedsAdditionalAnswerA = false; +#endif +#ifdef MDNS_IPV6_SUPPORT + bool bNeedsAdditionalAnswerAAAA = false; +#endif + for (stcService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) + { + if ((bResult) && + (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_NAME)) && // If PTR_NAME is requested, AND + (!(pService->m_u32ReplyMask & static_cast(enuContentFlag::SRV)))) // NOT SRV -> add SRV as additional answer + { + + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16AdditionalAnswers + : (bResult = _writeMDNSAnswer_SRV(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_SRV(B) FAILED!\n"), _DH());); + } + /* AppleTV doesn't add TXT + if ((bResult) && + (pService->m_u32ReplyMask & ContentFlag_PTR_NAME) && // If PTR_NAME is requested, AND + (!(pService->m_u32ReplyMask & ContentFlag_TXT))) { // NOT TXT -> add TXT as additional answer + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16AdditionalAnswers + : (bResult = _writeMDNSAnswer_TXT(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_TXT(B) FAILED!\n"));); + } + */ + if ((pService->m_u32ReplyMask & (static_cast(enuContentFlag::PTR_NAME) | static_cast(enuContentFlag::SRV))) || // If service instance name or SRV OR + (p_rSendParameter.m_u32HostReplyMask & (static_cast(enuContentFlag::A) | static_cast(enuContentFlag::AAAA)))) // any host IP address is requested + { +#ifdef MDNS_IPV4_SUPPORT + if ((bResult) && + (!(p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::A)))) // Add IPv4 address + { + bNeedsAdditionalAnswerA = true; + } +#endif +#ifdef MDNS_IPV6_SUPPORT + if ((bResult) && + (!(p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::AAAA)))) // Add IPv6 address + { + bNeedsAdditionalAnswerAAAA = true; + } +#endif + } + // NSEC record for service + if ((bResult) && + (pService->m_u32ReplyMask) && + ((stcSendParameter::enuResponseType::None != p_rSendParameter.m_Response))) + { + + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16AdditionalAnswers + : (bResult = _writeMDNSAnswer_NSEC(*pService, (static_cast(enuContentFlag::TXT) | static_cast(enuContentFlag::SRV)), p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_NSEC(Service) FAILED!\n"), _DH());); + } + } // for services + +#ifdef MDNS_IPV4_SUPPORT + // Answer A needed? + if ((bResult) && + (bNeedsAdditionalAnswerA) && + (_getResponderIPAddress(enuIPProtocolType::V4).isSet())) + { + // Additional A + u32NSECContent |= static_cast(enuContentFlag::A); + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16AdditionalAnswers + : (bResult = _writeMDNSAnswer_A(_getResponderIPAddress(enuIPProtocolType::V4), p_rSendParameter))); + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_A(B) FAILED!\n"), _DH());); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + // Answer AAAA needed? + if ((bResult) && + (bNeedsAdditionalAnswerAAAA) && + (_getResponderIPAddress(enuIPProtocolType::V6).isSet())) + { + // Additional AAAA + u32NSECContent |= static_cast(enuContentFlag::AAAA); + ((static_cast(enuSequence::Count) == sequence) + ? ++ru16AdditionalAnswers + : (bResult = _writeMDNSAnswer_AAAA(_getResponderIPAddress(enuIPProtocolType::V6), p_rSendParameter))); + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_AAAA(B) FAILED!\n"), _DH());); + } +#endif + + // NSEC host (part 2) + if ((bResult) && + ((stcSendParameter::enuResponseType::None != p_rSendParameter.m_Response)) && + (u32NSECContent)) + { + + // NSEC PTR IPv4/IPv6 are separate answers; make sure, that this is counted for +#ifdef MDNS_IPV4_SUPPORT + uint32_t u32NSECContent_PTR_IPv4 = (u32NSECContent & static_cast(enuContentFlag::PTR_IPv4)); + u32NSECContent &= ~static_cast(enuContentFlag::PTR_IPv4); +#endif +#ifdef MDNS_IPV6_SUPPORT + uint32_t u32NSECContent_PTR_IPv6 = (u32NSECContent & static_cast(enuContentFlag::PTR_IPv6)); + u32NSECContent &= ~static_cast(enuContentFlag::PTR_IPv6); +#endif + + ((static_cast(enuSequence::Count) == sequence) + ? (ru16AdditionalAnswers += ((u32NSECContent ? 1 : 0) +#ifdef MDNS_IPV4_SUPPORT + + (u32NSECContent_PTR_IPv4 ? 1 : 0) +#endif +#ifdef MDNS_IPV6_SUPPORT + + (u32NSECContent_PTR_IPv6 ? 1 : 0) +#endif + )) + : (bResult = (((!u32NSECContent) || + // Write host domain NSEC answer + (_writeMDNSAnswer_NSEC(u32NSECContent, p_rSendParameter))) +#ifdef MDNS_IPV4_SUPPORT + // Write separate answer for host PTR IPv4 + && ((!u32NSECContent_PTR_IPv4) || + ((!_getResponderIPAddress(enuIPProtocolType::V4).isSet()) || + (_writeMDNSAnswer_NSEC_PTR_IPv4(_getResponderIPAddress(enuIPProtocolType::V4), p_rSendParameter)))) +#endif +#ifdef MDNS_IPV6_SUPPORT + // Write separate answer for host PTR IPv6 + && ((!u32NSECContent_PTR_IPv6) || + ((!_getResponderIPAddress(enuIPProtocolType::V6).isSet()) || + (_writeMDNSAnswer_NSEC_PTR_IPv6(_getResponderIPAddress(enuIPProtocolType::V6), p_rSendParameter)))) +#endif + ))); + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_NSEC(Host) FAILED!\n"), _DH());); + } + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: Loop %i FAILED!\n"), _DH(), sequence);); + } // for sequence + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_addMDNSQueryRecord + + Adds a query for the given domain and query type. + +*/ +bool MDNSResponder::clsHost::_addMDNSQueryRecord(MDNSResponder::clsHost::stcSendParameter& p_rSendParameter, + const MDNSResponder::clsHost::stcRRDomain& p_QueryDomain, + uint16_t p_u16RecordType) +{ + bool bResult = false; + + stcRRQuestion* pQuestion = new stcRRQuestion; + if ((bResult = (0 != pQuestion))) + { + // Link to list of questions + pQuestion->m_pNext = p_rSendParameter.m_pQuestions; + p_rSendParameter.m_pQuestions = pQuestion; + + pQuestion->m_Header.m_Domain = p_QueryDomain; + + pQuestion->m_Header.m_Attributes.m_u16Type = p_u16RecordType; + // It seems, that some mDNS implementations don't support 'unicast response' questions... + pQuestion->m_Header.m_Attributes.m_u16Class = (/*0x8000 |*/ DNS_RRCLASS_IN); // /*Unicast &*/ INternet + } + return bResult; +} + +/* + MDNSResponder::_sendMDNSQuery + + Creates and sends a query for the given domain and query type. + +*/ +bool MDNSResponder::clsHost::_sendMDNSQuery(const MDNSResponder::clsHost::stcQuery& p_Query, + MDNSResponder::clsHost::stcQuery::stcAnswer* p_pKnownAnswers /*= 0*/) +{ + bool bResult = false; + + stcSendParameter sendParameter; + switch (p_Query.m_QueryType) + { + case stcQuery::enuQueryType::Host: +#ifdef MDNS_IPV4_SUPPORT + bResult = _addMDNSQueryRecord(sendParameter, p_Query.m_Domain, DNS_RRTYPE_A); +#endif +#ifdef MDNS_IPV6_SUPPORT + bResult = _addMDNSQueryRecord(sendParameter, p_Query.m_Domain, DNS_RRTYPE_AAAA); +#endif + break; + + case stcQuery::enuQueryType::Service: + bResult = _addMDNSQueryRecord(sendParameter, p_Query.m_Domain, DNS_RRTYPE_PTR); + break; + + case stcQuery::enuQueryType::None: + default: + break; + } + + // TODO: Add known answers to query + (void)p_pKnownAnswers; + + bResult = ((bResult) && + (_sendMDNSMessage(sendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSQuery: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_sendMDNSQuery + + Creates and sends a query for the given domain and record type. + +*/ +bool MDNSResponder::clsHost::_sendMDNSQuery(const MDNSResponder::clsHost::stcRRDomain& p_QueryDomain, + uint16_t p_u16RecordType, + MDNSResponder::clsHost::stcQuery::stcAnswer* p_pKnownAnswers /*= 0*/) +{ + bool bResult = false; + + stcSendParameter sendParameter; + bResult = ((_addMDNSQueryRecord(sendParameter, p_QueryDomain, p_u16RecordType)) && + (_sendMDNSMessage(sendParameter))); + + // TODO: Add known answer records + (void) p_pKnownAnswers; + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSQuery: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_getResponderIPAddress +*/ +IPAddress MDNSResponder::clsHost::_getResponderIPAddress(enuIPProtocolType p_IPProtocolType) const +{ + IPAddress ipResponder; +#ifdef MDNS_IPV4_SUPPORT + if (enuIPProtocolType::V4 == p_IPProtocolType) + { + ipResponder = netif_ip_addr4(&m_rNetIf); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + if (enuIPProtocolType::V6 == p_IPProtocolType) + { + bool bCheckLinkLocal = true; + for (int i = 0; ((!ipResponder.isSet()) && (i < 2)); ++i) // Two loops: First with link-local check, second without + { + for (int idx = 0; idx < LWIP_IPV6_NUM_ADDRESSES; ++idx) + { + //DEBUG_EX_INFO(if ip6_addr_isvalid(netif_ip6_addr_state(&m_rNetIf, idx)) DEBUG_OUTPUT.printf_P(PSTR("%s _getResponderIPAddress: Checking IPv6 address %s (LL: %s)\n"), _DH(), IPAddress(netif_ip_addr6(&m_rNetIf, idx)).toString().c_str(), (bCheckLinkLocal ? "YES" : "NO"));); + if ((ip6_addr_isvalid(netif_ip6_addr_state(&m_rNetIf, idx))) && + (((!bCheckLinkLocal) || + (ip6_addr_islinklocal(netif_ip6_addr(&m_rNetIf, idx)))))) + { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _getResponderIPAddress: Selected IPv6 address %s (LL: %s)\n"), _DH(), IPAddress(netif_ip_addr6(&m_rNetIf, idx)).toString().c_str(), (bCheckLinkLocal ? "YES" : "NO"));); + ipResponder = netif_ip_addr6(&m_rNetIf, idx); + break; + } + } + bCheckLinkLocal = false; + } + } +#endif + return ipResponder; +} + + +/** + HELPERS +*/ + +/** + RESOURCE RECORDS +*/ + +/* + MDNSResponder::_readRRQuestion + + Reads a question (eg. MyESP._http._tcp.local ANY IN) from the UPD input buffer. + +*/ +bool MDNSResponder::clsHost::_readRRQuestion(MDNSResponder::clsHost::stcRRQuestion& p_rRRQuestion) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRQuestion\n"));); + + bool bResult = false; + + if ((bResult = _readRRHeader(p_rRRQuestion.m_Header))) + { + // Extract unicast flag from class field + p_rRRQuestion.m_bUnicast = (p_rRRQuestion.m_Header.m_Attributes.m_u16Class & 0x8000); + //p_rRRQuestion.m_Header.m_Attributes.m_u16Class &= (~0x8000); + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _readRRQuestion "), _DH()); + _printRRDomain(p_rRRQuestion.m_Header.m_Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:%s Class:%s\n"), _RRType2Name(p_rRRQuestion.m_Header.m_Attributes.m_u16Type), _RRClass2String(p_rRRQuestion.m_Header.m_Attributes.m_u16Class, true)); + ); + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRQuestion: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_readRRAnswer + + Reads an answer (eg. _http._tcp.local PTR OP TTL MyESP._http._tcp.local) + from the UDP input buffer. + After reading the domain and type info, the further processing of the answer + is transferred the answer specific reading functions. + Unknown answer types are processed by the generic answer reader (to remove them + from the input buffer). + +*/ +bool MDNSResponder::clsHost::_readRRAnswer(MDNSResponder::clsHost::stcRRAnswer*& p_rpRRAnswer) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswer\n"));); + + bool bResult = false; + + stcRRHeader header; + uint32_t u32TTL; + uint16_t u16RDLength; + if ((_readRRHeader(header)) && + (_udpRead32(u32TTL)) && + (_udpRead16(u16RDLength))) + { + + /* DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswer: Reading 0x%04X answer (class:0x%04X, TTL:%u, RDLength:%u) for "), header.m_Attributes.m_u16Type, header.m_Attributes.m_u16Class, u32TTL, u16RDLength); + _printRRDomain(header.m_Domain); + DEBUG_OUTPUT.printf_P(PSTR("\n")); + );*/ + + switch (header.m_Attributes.m_u16Type /*& (~0x8000)*/) // Topmost bit might carry 'cache flush' flag + { +#ifdef MDNS_IPV4_SUPPORT + case DNS_RRTYPE_A: + p_rpRRAnswer = new stcRRAnswerA(header, u32TTL); + bResult = _readRRAnswerA(*(stcRRAnswerA*&)p_rpRRAnswer, u16RDLength); + break; +#endif + case DNS_RRTYPE_PTR: + p_rpRRAnswer = new stcRRAnswerPTR(header, u32TTL); + bResult = _readRRAnswerPTR(*(stcRRAnswerPTR*&)p_rpRRAnswer, u16RDLength); + break; + case DNS_RRTYPE_TXT: + p_rpRRAnswer = new stcRRAnswerTXT(header, u32TTL); + bResult = _readRRAnswerTXT(*(stcRRAnswerTXT*&)p_rpRRAnswer, u16RDLength); + break; +#ifdef MDNS_IPV6_SUPPORT + case DNS_RRTYPE_AAAA: + p_rpRRAnswer = new stcRRAnswerAAAA(header, u32TTL); + bResult = _readRRAnswerAAAA(*(stcRRAnswerAAAA*&)p_rpRRAnswer, u16RDLength); + break; +#endif + case DNS_RRTYPE_SRV: + p_rpRRAnswer = new stcRRAnswerSRV(header, u32TTL); + bResult = _readRRAnswerSRV(*(stcRRAnswerSRV*&)p_rpRRAnswer, u16RDLength); + break; + default: + p_rpRRAnswer = new stcRRAnswerGeneric(header, u32TTL); + bResult = _readRRAnswerGeneric(*(stcRRAnswerGeneric*&)p_rpRRAnswer, u16RDLength); + break; + } + DEBUG_EX_INFO( + if ((bResult) && + (p_rpRRAnswer)) + { + DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswer: "), _DH()); + _printRRDomain(p_rpRRAnswer->m_Header.m_Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:%s Class:0x%04X TTL:%u, RDLength:%u "), + _RRType2Name(p_rpRRAnswer->m_Header.m_Attributes.m_u16Type), + (p_rpRRAnswer->m_Header.m_Attributes.m_u16Class | (p_rpRRAnswer->m_bCacheFlush ? 0x8000 : 0)), + p_rpRRAnswer->m_u32TTL, + u16RDLength); + switch (header.m_Attributes.m_u16Type /*& (~0x8000)*/) // Topmost bit might carry 'cache flush' flag + { +#ifdef MDNS_IPV4_SUPPORT + case DNS_RRTYPE_A: + DEBUG_OUTPUT.printf_P(PSTR("A IP:%s"), ((stcRRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); + break; +#endif + case DNS_RRTYPE_PTR: + DEBUG_OUTPUT.printf_P(PSTR("PTR ")); + _printRRDomain(((stcRRAnswerPTR*&)p_rpRRAnswer)->m_PTRDomain); + break; + case DNS_RRTYPE_TXT: + { + size_t stTxtLength = ((stcRRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_strLength(); + char* pTxts = new char[stTxtLength]; + if (pTxts) + { + ((stcRRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_str(pTxts); + DEBUG_OUTPUT.printf_P(PSTR("TXT(%u) %s"), stTxtLength, pTxts); + delete[] pTxts; + } + break; + } +#ifdef MDNS_IPV6_SUPPORT + case DNS_RRTYPE_AAAA: + DEBUG_OUTPUT.printf_P(PSTR("AAAA IP:%s"), ((stcRRAnswerAAAA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); + break; +#endif + case DNS_RRTYPE_SRV: + DEBUG_OUTPUT.printf_P(PSTR("SRV Port:%u "), ((stcRRAnswerSRV*&)p_rpRRAnswer)->m_u16Port); + _printRRDomain(((stcRRAnswerSRV*&)p_rpRRAnswer)->m_SRVDomain); + break; + /* case DNS_RRTYPE_NSEC: + DEBUG_OUTPUT.printf_P(PSTR("NSEC ")); + _printRRDomain(((stcRRAnswerNSEC*&)p_rpRRAnswer)->m_NSECDomain); + for (uint32_t u=0; u<(((stcRRAnswerNSEC*&)p_rpRRAnswer)->m_pNSECBitmap->m_u16BitmapLength * 8); ++u) { + uint8_t byte = ((stcRRAnswerNSEC*&)p_rpRRAnswer)->m_pNSECBitmap->m_pu8BitmapData[u / 8]; + uint8_t flag = 1 << (7 - (u % 8)); // (7 - (0..7)) = 7..0 + if (byte & flag) { + DEBUG_OUTPUT.printf_P(PSTR(" %s"), _RRType2Name(u)); + } + } + break;*/ + default: + DEBUG_OUTPUT.printf_P(PSTR("generic ")); + break; + } + DEBUG_OUTPUT.printf_P(PSTR("\n")); + } + else + { + DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswer: FAILED to read specific answer of type 0x%04X!\n"), _DH(), p_rpRRAnswer->m_Header.m_Attributes.m_u16Type); + } + ); // DEBUG_EX_INFO + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswer: FAILED!\n"), _DH());); + return bResult; +} + +#ifdef MDNS_IPV4_SUPPORT +/* + MDNSResponder::_readRRAnswerA +*/ +bool MDNSResponder::clsHost::_readRRAnswerA(MDNSResponder::clsHost::stcRRAnswerA& p_rRRAnswerA, + uint16_t p_u16RDLength) +{ + + uint32_t u32IPv4Address; + bool bResult = ((MDNS_IPV4_SIZE == p_u16RDLength) && + (_udpReadBuffer((unsigned char*)&u32IPv4Address, MDNS_IPV4_SIZE)) && + ((p_rRRAnswerA.m_IPAddress = IPAddress(u32IPv4Address)))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerA: FAILED!\n"), _DH());); + return bResult; +} +#endif + +/* + MDNSResponder::_readRRAnswerPTR +*/ +bool MDNSResponder::clsHost::_readRRAnswerPTR(MDNSResponder::clsHost::stcRRAnswerPTR& p_rRRAnswerPTR, + uint16_t p_u16RDLength) +{ + bool bResult = ((p_u16RDLength) && + (_readRRDomain(p_rRRAnswerPTR.m_PTRDomain))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerPTR: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_readRRAnswerTXT + + Read TXT items from a buffer like 4c#=15ff=20 +*/ +bool MDNSResponder::clsHost::_readRRAnswerTXT(MDNSResponder::clsHost::stcRRAnswerTXT& p_rRRAnswerTXT, + uint16_t p_u16RDLength) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: RDLength:%u\n"), _DH(), p_u16RDLength);); + bool bResult = true; + + p_rRRAnswerTXT.clear(); + if (p_u16RDLength) + { + bResult = false; + + unsigned char* pucBuffer = new unsigned char[p_u16RDLength]; + if (pucBuffer) + { + if (_udpReadBuffer(pucBuffer, p_u16RDLength)) + { + bResult = true; + + const unsigned char* pucCursor = pucBuffer; + while ((pucCursor < (pucBuffer + p_u16RDLength)) && + (bResult)) + { + bResult = false; + + stcServiceTxt* 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("%s _readRRAnswerTXT: Item(%u): %s\n"), _DH(), ucLength, sacBuffer); + ); + + unsigned char* pucEqualSign = (unsigned char*)os_strchr((const char*)pucCursor, '='); // Position of the '=' sign + unsigned char ucKeyLength; + if ((pucEqualSign) && + ((ucKeyLength = (pucEqualSign - pucCursor)))) + { + unsigned char ucValueLength = (ucLength - (pucEqualSign - pucCursor + 1)); + bResult = (((pTxt = new stcServiceTxt)) && + (pTxt->setKey((const char*)pucCursor, ucKeyLength)) && + (pTxt->setValue((const char*)(pucEqualSign + 1), ucValueLength))); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: INVALID TXT format (No '=')!\n"), _DH());); + } + pucCursor += ucLength; + } + else // no/zero length TXT + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: TXT answer contains no items.\n"), _DH());); + 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("%s _readRRAnswerTXT: FAILED to read TXT item!\n"), _DH()); + DEBUG_OUTPUT.printf_P(PSTR("RData dump:\n")); + _udpDump((m_rUDPContext.tell() - p_u16RDLength), p_u16RDLength); + DEBUG_OUTPUT.printf_P(PSTR("\n")); + ); + } + if (pTxt) + { + delete pTxt; + pTxt = 0; + } + p_rRRAnswerTXT.clear(); + } + } // while + + DEBUG_EX_ERR( + if (!bResult) // Some failure + { + DEBUG_OUTPUT.printf_P(PSTR("RData dump:\n")); + _udpDump((m_rUDPContext.tell() - p_u16RDLength), p_u16RDLength); + DEBUG_OUTPUT.printf_P(PSTR("\n")); + } + ); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: FAILED to read TXT content!\n"), _DH());); + } + // Clean up + delete[] pucBuffer; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: FAILED to alloc buffer for TXT content!\n"), _DH());); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: WARNING! No content!\n"), _DH());); + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: FAILED!\n"), _DH());); + return bResult; +} + +#ifdef MDNS_IPV6_SUPPORT +bool MDNSResponder::clsHost::_readRRAnswerAAAA(MDNSResponder::clsHost::stcRRAnswerAAAA& p_rRRAnswerAAAA, + uint16_t p_u16RDLength) +{ + bool bResult = false; + + uint32_t au32IPv6Address[4]; // 16 bytes + if ((bResult = ((MDNS_IPV6_SIZE == p_u16RDLength) && + (_udpReadBuffer((uint8_t*)&au32IPv6Address[0], MDNS_IPV6_SIZE))))) + { + + // ?? IPADDR6_INIT_HOST ?? + ip_addr_t addr = IPADDR6_INIT(au32IPv6Address[0], au32IPv6Address[1], au32IPv6Address[2], au32IPv6Address[3]); + p_rRRAnswerAAAA.m_IPAddress = IPAddress(addr); + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerAAAA: FAILED!\n"), _DH());); + return bResult; +} +#endif + +/* + MDNSResponder::_readRRAnswerSRV +*/ +bool MDNSResponder::clsHost::_readRRAnswerSRV(MDNSResponder::clsHost::stcRRAnswerSRV& p_rRRAnswerSRV, + uint16_t p_u16RDLength) +{ + bool bResult = (((3 * sizeof(uint16_t)) < p_u16RDLength) && + (_udpRead16(p_rRRAnswerSRV.m_u16Priority)) && + (_udpRead16(p_rRRAnswerSRV.m_u16Weight)) && + (_udpRead16(p_rRRAnswerSRV.m_u16Port)) && + (_readRRDomain(p_rRRAnswerSRV.m_SRVDomain))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerSRV: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_readRRAnswerGeneric +*/ +bool MDNSResponder::clsHost::_readRRAnswerGeneric(MDNSResponder::clsHost::stcRRAnswerGeneric& p_rRRAnswerGeneric, + uint16_t p_u16RDLength) +{ + bool bResult = (0 == p_u16RDLength); + + p_rRRAnswerGeneric.clear(); + if (((p_rRRAnswerGeneric.m_u16RDLength = p_u16RDLength)) && + ((p_rRRAnswerGeneric.m_pu8RDData = new unsigned char[p_rRRAnswerGeneric.m_u16RDLength]))) + { + + bResult = _udpReadBuffer(p_rRRAnswerGeneric.m_pu8RDData, p_rRRAnswerGeneric.m_u16RDLength); + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerGeneric: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_readRRHeader +*/ +bool MDNSResponder::clsHost::_readRRHeader(MDNSResponder::clsHost::stcRRHeader& p_rRRHeader) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRHeader\n"));); + + bool bResult = ((_readRRDomain(p_rRRHeader.m_Domain)) && + (_readRRAttributes(p_rRRHeader.m_Attributes))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRHeader: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_readRRDomain + + Reads a (maybe multilevel compressed) domain from the UDP input buffer. + +*/ +bool MDNSResponder::clsHost::_readRRDomain(MDNSResponder::clsHost::stcRRDomain& p_rRRDomain) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain\n"));); + + bool bResult = ((p_rRRDomain.clear()) && + (_readRRDomain_Loop(p_rRRDomain, 0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_readRRDomain_Loop + + Reads a domain from the UDP input buffer. For every compression level, the functions + calls itself recursively. To avoid endless recursion because of malformed MDNS records, + the maximum recursion depth is set by MDNS_DOMAIN_MAX_REDIRCTION. + +*/ +bool MDNSResponder::clsHost::_readRRDomain_Loop(MDNSResponder::clsHost::stcRRDomain& p_rRRDomain, + uint8_t p_u8Depth) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u)\n"), _DH(), p_u8Depth);); + + bool bResult = false; + + if (MDNS_DOMAIN_MAX_REDIRCTION >= p_u8Depth) + { + bResult = true; + + uint8_t u8Len = 0; + do + { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u): Offset:%u p0:%02x\n"), _DH(), p_u8Depth, m_rUDPContext.tell(), m_rUDPContext.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_rUDPContext.isValidOffset(u16Offset)) + { + size_t stCurrentPosition = m_rUDPContext.tell(); // Prepare return from recursion + + //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u): Redirecting from %u to %u!\n"), _DH(), p_u8Depth, stCurrentPosition, u16Offset);); + m_rUDPContext.seek(u16Offset); + if (_readRRDomain_Loop(p_rRRDomain, p_u8Depth + 1)) // Do recursion + { + //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u): Succeeded to read redirected label! Returning to %u\n"), _DH(), p_u8Depth, stCurrentPosition);); + m_rUDPContext.seek(stCurrentPosition); // Restore after recursion + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u): FAILED to read redirected label!\n"), _DH(), p_u8Depth);); + bResult = false; + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u): INVALID offset in redirection!\n"), _DH(), p_u8Depth);); + bResult = false; + } + break; + } + else + { + // Normal (uncompressed) label (maybe '\0' only) + if (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("%s _readRRDomain_Loop(%u): Domain label (%u): %s\n"), _DH(), p_u8Depth, (unsigned)(p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength - 1]), &(p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength])); + );*/ + + p_rRRDomain.m_u16NameLength += u8Len; + } + } + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(2) offset:%u p0:%x\n"), _DH(), m_rUDPContext.tell(), m_rUDPContext.peek());); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u): ERROR! Domain name too long (%u + %u)!\n"), _DH(), p_u8Depth, p_rRRDomain.m_u16NameLength, u8Len);); + bResult = false; + break; + } + } + } while ((bResult) && + (0 != u8Len)); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u): ERROR! Too many redirections!\n"), _DH(), p_u8Depth);); + } + return bResult; +} + +/* + MDNSResponder::_readRRAttributes +*/ +bool MDNSResponder::clsHost::_readRRAttributes(MDNSResponder::clsHost::stcRRAttributes& p_rRRAttributes) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAttributes\n"));); + + bool bResult = ((_udpRead16(p_rRRAttributes.m_u16Type)) && + (_udpRead16(p_rRRAttributes.m_u16Class))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAttributes: FAILED!\n"), _DH());); + return bResult; +} + + +/* + DOMAIN NAMES +*/ + +/* + MDNSResponder::_buildDomainForHost + + Builds a MDNS host domain (eg. esp8266.local) for the given hostname. + +*/ +bool MDNSResponder::clsHost::_buildDomainForHost(const char* p_pcHostName, + MDNSResponder::clsHost::stcRRDomain& 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("%s _buildDomainForHost: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_buildDomainForDNSSD + + Builds the '_services._dns-sd._udp.local' domain. + Used while detecting generic service enum question (DNS-SD) and answering these questions. + +*/ +bool MDNSResponder::clsHost::_buildDomainForDNSSD(MDNSResponder::clsHost::stcRRDomain& 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("%s _buildDomainForDNSSD: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_buildDomainForService + + Builds the domain for the given service (eg. _http._tcp.local or + MyESP._http._tcp.local (if p_bIncludeName is set)). + +*/ +bool MDNSResponder::clsHost::_buildDomainForService(const MDNSResponder::clsHost::stcService& p_Service, + bool p_bIncludeName, + MDNSResponder::clsHost::stcRRDomain& p_rServiceDomain) const +{ + p_rServiceDomain.clear(); + bool bResult = (((!p_bIncludeName) || + (p_rServiceDomain.addLabel(p_Service.m_pcName))) && + (p_rServiceDomain.addLabel(p_Service.m_pcServiceType, ('_' != *p_Service.m_pcServiceType))) && + (p_rServiceDomain.addLabel(p_Service.m_pcProtocol, ('_' != *p_Service.m_pcProtocol))) && + (p_rServiceDomain.addLabel(scpcLocal)) && + (p_rServiceDomain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _buildDomainForService: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_buildDomainForService + + Builds the domain for the given service properties (eg. _http._tcp.local). + The usual prepended '_' are added, if missing in the input strings. + +*/ +bool MDNSResponder::clsHost::_buildDomainForService(const char* p_pcServiceType, + const char* p_pcProtocol, + MDNSResponder::clsHost::stcRRDomain& p_rServiceDomain) const +{ + p_rServiceDomain.clear(); + bool bResult = ((p_pcServiceType) && + (p_pcProtocol) && + (p_rServiceDomain.addLabel(p_pcServiceType, ('_' != *p_pcServiceType))) && + (p_rServiceDomain.addLabel(p_pcProtocol, ('_' != *p_pcProtocol))) && + (p_rServiceDomain.addLabel(scpcLocal)) && + (p_rServiceDomain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _buildDomainForService: FAILED for (%s.%s)!\n"), _DH(), (p_pcServiceType ? : "-"), (p_pcProtocol ? : "-"));); + return bResult; +} + +#ifdef MDNS_IPV4_SUPPORT +/* + MDNSResponder::_buildDomainForReverseIPv4 + + The IPv4 address is stringized by printing the four address bytes into a char buffer in reverse order + and adding 'in-addr.arpa' (eg. 012.789.456.123.in-addr.arpa). + Used while detecting reverse IPv4 questions and answering these +*/ +bool MDNSResponder::clsHost::_buildDomainForReverseIPv4(IPAddress p_IPv4Address, + MDNSResponder::clsHost::stcRRDomain& p_rReverseIPv4Domain) const +{ + bool bResult = ((p_IPv4Address.isSet()) && + (p_IPv4Address.isV4())); + + p_rReverseIPv4Domain.clear(); + + char acBuffer[32]; + for (int i = MDNS_IPV4_SIZE; ((bResult) && (i >= 1)); --i) + { + itoa(p_IPv4Address[i - 1], acBuffer, 10); + bResult = p_rReverseIPv4Domain.addLabel(acBuffer); + } + bResult = ((bResult) && + (p_rReverseIPv4Domain.addLabel(scpcReverseIPv4Domain)) && + (p_rReverseIPv4Domain.addLabel(scpcReverseTopDomain)) && + (p_rReverseIPv4Domain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _buildDomainForReverseIPv4: FAILED!\n"), _DH());); + return bResult; +} +#endif + +#ifdef MDNS_IPV6_SUPPORT +/* + MDNSResponder::_buildDomainForReverseIPv6 + + The IPv6 address is stringized by printing the 16 address bytes (32 nibbles) into a char buffer in reverse order + and adding 'ip6.arpa' (eg. 3.B.6.E.A.1.B.B.A.B.F.7.F.8.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.8.E.F.ip6.arpa). + Used while detecting reverse IPv6 questions and answering these +*/ +bool MDNSResponder::clsHost::_buildDomainForReverseIPv6(IPAddress p_IPv6Address, + MDNSResponder::clsHost::stcRRDomain& p_rReverseIPv6Domain) const +{ + bool bResult = ((p_IPv6Address.isSet()) && + (p_IPv6Address.isV6())); + + p_rReverseIPv6Domain.clear(); + + const uint16_t* pRaw = p_IPv6Address.raw6(); + for (int8_t i8 = (MDNS_IPV6_SIZE / 2); ((bResult) && (i8 > 0)); --i8) // 8..1 + { + uint16_t u16Part = ntohs(pRaw[i8 - 1] & 0xFFFF); + char acBuffer[2]; + for (uint8_t u8 = 0; ((bResult) && (u8 < 4)); ++u8) // 0..3 + { + itoa((u16Part & 0xF), acBuffer, 16); + bResult = p_rReverseIPv6Domain.addLabel(acBuffer); + u16Part >>= 4; + } + } + bResult = ((bResult) && + (p_rReverseIPv6Domain.addLabel(scpcReverseIPv6Domain)) && // .ip6.arpa + (p_rReverseIPv6Domain.addLabel(scpcReverseTopDomain)) && // .local + (p_rReverseIPv6Domain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _buildDomainForReverseIPv6: FAILED!\n"), _DH());); + return bResult; +} +#endif + + +/* + UDP +*/ + +/* + MDNSResponder::_udpReadBuffer +*/ +bool MDNSResponder::clsHost::_udpReadBuffer(unsigned char* p_pBuffer, + size_t p_stLength) +{ + bool bResult = ((true/*m_rUDPContext.getSize() > p_stLength*/) && + (p_pBuffer) && + (p_stLength) && + ((p_stLength == m_rUDPContext.read((char*)p_pBuffer, p_stLength)))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _udpReadBuffer: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_udpRead8 +*/ +bool MDNSResponder::clsHost::_udpRead8(uint8_t& p_ru8Value) +{ + return _udpReadBuffer((unsigned char*)&p_ru8Value, sizeof(p_ru8Value)); +} + +/* + MDNSResponder::_udpRead16 +*/ +bool MDNSResponder::clsHost::_udpRead16(uint16_t& p_ru16Value) +{ + bool bResult = false; + + if (_udpReadBuffer((unsigned char*)&p_ru16Value, sizeof(p_ru16Value))) + { + p_ru16Value = lwip_ntohs(p_ru16Value); + bResult = true; + } + return bResult; +} + +/* + MDNSResponder::_udpRead32 +*/ +bool MDNSResponder::clsHost::_udpRead32(uint32_t& p_ru32Value) +{ + bool bResult = false; + + if (_udpReadBuffer((unsigned char*)&p_ru32Value, sizeof(p_ru32Value))) + { + p_ru32Value = lwip_ntohl(p_ru32Value); + bResult = true; + } + return bResult; +} + +/* + MDNSResponder::_udpAppendBuffer +*/ +bool MDNSResponder::clsHost::_udpAppendBuffer(const unsigned char* p_pcBuffer, + size_t p_stLength) +{ + bool bResult = ((p_pcBuffer) && + (p_stLength) && + (p_stLength == m_rUDPContext.append((const char*)p_pcBuffer, p_stLength))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _udpAppendBuffer: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_udpAppend8 +*/ +bool MDNSResponder::clsHost::_udpAppend8(uint8_t p_u8Value) +{ + return (_udpAppendBuffer((unsigned char*)&p_u8Value, sizeof(p_u8Value))); +} + +/* + MDNSResponder::_udpAppend16 +*/ +bool MDNSResponder::clsHost::_udpAppend16(uint16_t p_u16Value) +{ + p_u16Value = lwip_htons(p_u16Value); + return (_udpAppendBuffer((unsigned char*)&p_u16Value, sizeof(p_u16Value))); +} + +/* + MDNSResponder::_udpAppend32 +*/ +bool MDNSResponder::clsHost::_udpAppend32(uint32_t p_u32Value) +{ + p_u32Value = lwip_htonl(p_u32Value); + return (_udpAppendBuffer((unsigned char*)&p_u32Value, sizeof(p_u32Value))); +} + +#ifdef DEBUG_ESP_MDNS_RESPONDER +/* + MDNSResponder::_udpDump +*/ +bool MDNSResponder::clsHost::_udpDump(bool p_bMovePointer /*= false*/) +{ + const uint8_t cu8BytesPerLine = 16; + + uint32_t u32StartPosition = m_rUDPContext.tell(); + DEBUG_OUTPUT.println("UDP Context Dump:"); + uint32_t u32Counter = 0; + uint8_t u8Byte = 0; + + while (_udpRead8(u8Byte)) + { + DEBUG_OUTPUT.printf_P(PSTR("%02x %s"), u8Byte, ((++u32Counter % cu8BytesPerLine) ? "" : "\n")); + } + DEBUG_OUTPUT.printf_P(PSTR("%sDone: %u bytes\n"), (((u32Counter) && (u32Counter % cu8BytesPerLine)) ? "\n" : ""), u32Counter); + + if (!p_bMovePointer) // Restore + { + m_rUDPContext.seek(u32StartPosition); + } + return true; +} + +/* + MDNSResponder::_udpDump +*/ +bool MDNSResponder::clsHost::_udpDump(unsigned p_uOffset, + unsigned p_uLength) +{ + if (m_rUDPContext.isValidOffset(p_uOffset)) + { + unsigned uCurrentPosition = m_rUDPContext.tell(); // Remember start position + + m_rUDPContext.seek(p_uOffset); + uint8_t u8Byte; + for (unsigned u = 0; ((u < p_uLength) && (_udpRead8(u8Byte))); ++u) + { + DEBUG_OUTPUT.printf_P(PSTR("%02x "), u8Byte); + } + // Return to start position + m_rUDPContext.seek(uCurrentPosition); + } + return true; +} +#endif + + +/** + READ/WRITE MDNS STRUCTS +*/ + +/* + MDNSResponder::_readMDNSMsgHeader + + Read a MDNS header from the UDP input buffer. + | 8 | 8 | 8 | 8 | + 00| Identifier | Flags & Codes | + 01| Question count | Answer count | + 02| NS answer count | Ad answer count | + + All 16-bit and 32-bit elements need to be translated form network coding to host coding (done in _udpRead16 and _udpRead32) + In addition, bitfield memory order is undefined in C standard (GCC doesn't order them in the coded direction...), so they + need some mapping here +*/ +bool MDNSResponder::clsHost::_readMDNSMsgHeader(MDNSResponder::clsHost::stcMsgHeader& 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("%s _readMDNSMsgHeader: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), + _DH(), + (unsigned)p_rMsgHeader.m_u16ID, + (unsigned)p_rMsgHeader.m_1bQR, (unsigned)p_rMsgHeader.m_4bOpcode, (unsigned)p_rMsgHeader.m_1bAA, (unsigned)p_rMsgHeader.m_1bTC, (unsigned)p_rMsgHeader.m_1bRD, + (unsigned)p_rMsgHeader.m_1bRA, (unsigned)p_rMsgHeader.m_4bRCode, + (unsigned)p_rMsgHeader.m_u16QDCount, + (unsigned)p_rMsgHeader.m_u16ANCount, + (unsigned)p_rMsgHeader.m_u16NSCount, + (unsigned)p_rMsgHeader.m_u16ARCount););*/ + bResult = true; + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readMDNSMsgHeader: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_write8 +*/ +bool MDNSResponder::clsHost::_write8(uint8_t p_u8Value, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + return ((_udpAppend8(p_u8Value)) && + (p_rSendParameter.shiftOffset(sizeof(p_u8Value)))); +} + +/* + MDNSResponder::_write16 +*/ +bool MDNSResponder::clsHost::_write16(uint16_t p_u16Value, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + return ((_udpAppend16(p_u16Value)) && + (p_rSendParameter.shiftOffset(sizeof(p_u16Value)))); +} + +/* + MDNSResponder::_write32 +*/ +bool MDNSResponder::clsHost::_write32(uint32_t p_u32Value, + MDNSResponder::clsHost::stcSendParameter& 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::clsHost::_writeMDNSMsgHeader(const MDNSResponder::clsHost::stcMsgHeader& p_MsgHeader, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + /* DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSMsgHeader: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), + _DH(), + (unsigned)p_MsgHeader.m_u16ID, + (unsigned)p_MsgHeader.m_1bQR, (unsigned)p_MsgHeader.m_4bOpcode, (unsigned)p_MsgHeader.m_1bAA, (unsigned)p_MsgHeader.m_1bTC, (unsigned)p_MsgHeader.m_1bRD, + (unsigned)p_MsgHeader.m_1bRA, (unsigned)p_MsgHeader.m_4bRCode, + (unsigned)p_MsgHeader.m_u16QDCount, + (unsigned)p_MsgHeader.m_u16ANCount, + (unsigned)p_MsgHeader.m_u16NSCount, + (unsigned)p_MsgHeader.m_u16ARCount););*/ + + uint8_t u8B1((p_MsgHeader.m_1bQR << 7) | (p_MsgHeader.m_4bOpcode << 3) | (p_MsgHeader.m_1bAA << 2) | (p_MsgHeader.m_1bTC << 1) | (p_MsgHeader.m_1bRD)); + uint8_t u8B2((p_MsgHeader.m_1bRA << 7) | (p_MsgHeader.m_3bZ << 4) | (p_MsgHeader.m_4bRCode)); + bool bResult = ((_write16(p_MsgHeader.m_u16ID, p_rSendParameter)) && + (_write8(u8B1, p_rSendParameter)) && + (_write8(u8B2, p_rSendParameter)) && + (_write16(p_MsgHeader.m_u16QDCount, p_rSendParameter)) && + (_write16(p_MsgHeader.m_u16ANCount, p_rSendParameter)) && + (_write16(p_MsgHeader.m_u16NSCount, p_rSendParameter)) && + (_write16(p_MsgHeader.m_u16ARCount, p_rSendParameter))); + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSMsgHeader: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_writeRRAttributes +*/ +bool MDNSResponder::clsHost::_writeMDNSRRAttributes(const MDNSResponder::clsHost::stcRRAttributes& p_Attributes, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + bool bResult = ((_write16(p_Attributes.m_u16Type, p_rSendParameter)) && + (_write16(p_Attributes.m_u16Class, p_rSendParameter))); + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSRRAttributes: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_writeMDNSRRDomain +*/ +bool MDNSResponder::clsHost::_writeMDNSRRDomain(const MDNSResponder::clsHost::stcRRDomain& p_Domain, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + bool bResult = ((_udpAppendBuffer((const unsigned char*)p_Domain.m_acName, p_Domain.m_u16NameLength)) && + (p_rSendParameter.shiftOffset(p_Domain.m_u16NameLength))); + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSRRDomain: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_writeMDNSHostDomain + + Write a host domain to the UDP output buffer. + If the domain record is part of the answer, the records length is + prepended (p_bPrependRDLength is set). + + A very simple form of name compression is applied here: + If the domain is written to the UDP output buffer, the write offset is stored + together with a domain id (the pointer) in a p_rSendParameter substructure (cache). + If the same domain (pointer) should be written to the UDP output later again, + the old offset is retrieved from the cache, marked as a compressed domain offset + and written to the output buffer. + +*/ +bool MDNSResponder::clsHost::_writeMDNSHostDomain(const char* p_pcHostName, + bool p_bPrependRDLength, + uint16_t p_u16AdditionalLength, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV' + uint16_t u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)p_pcHostName, false); + + stcRRDomain 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_u16AdditionalLength), 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_u16AdditionalLength), p_rSendParameter))) && // RDLength (if needed) + (p_rSendParameter.addDomainCacheItem((const void*)p_pcHostName, false, p_rSendParameter.m_u16Offset)) && + (_writeMDNSRRDomain(hostDomain, p_rSendParameter)))); + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSHostDomain: FAILED!\n"), _DH());); + return bResult; + +} + +/* + MDNSResponder::_writeMDNSServiceDomain + + Write a service domain to the UDP output buffer. + If the domain record is part of the answer, the records length is + prepended (p_bPrependRDLength is set). + + A very simple form of name compression is applied here: see '_writeMDNSHostDomain' + The cache differentiates of course between service domains which includes + the instance name (p_bIncludeName is set) and thoose who don't. + +*/ +bool MDNSResponder::clsHost::_writeMDNSServiceDomain(const MDNSResponder::clsHost::stcService& p_Service, + bool p_bIncludeName, + bool p_bPrependRDLength, + uint16_t p_u16AdditionalLength, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV' + uint16_t u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)&p_Service, p_bIncludeName); + + stcRRDomain 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_u16AdditionalLength), 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_u16AdditionalLength), p_rSendParameter))) && // RDLength (if needed) + (p_rSendParameter.addDomainCacheItem((const void*)&p_Service, p_bIncludeName, p_rSendParameter.m_u16Offset)) && + (_writeMDNSRRDomain(serviceDomain, p_rSendParameter)))); + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSServiceDomain: FAILED!\n"), _DH());); + return bResult; + +} + +/* + MDNSResponder::_writeMDNSQuestion + + Write a MDNS question to the UDP output buffer + + QNAME (host/service domain, eg. esp8266.local) + QTYPE (16bit, eg. ANY) + QCLASS (16bit, eg. IN) + +*/ +bool MDNSResponder::clsHost::_writeMDNSQuestion(MDNSResponder::clsHost::stcRRQuestion& p_Question, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSQuestion\n"));); + + bool bResult = ((_writeMDNSRRDomain(p_Question.m_Header.m_Domain, p_rSendParameter)) && + (_writeMDNSRRAttributes(p_Question.m_Header.m_Attributes, p_rSendParameter))); + + DEBUG_EX_INFO(if (bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSQuestion "), _DH()); + _printRRDomain(p_Question.m_Header.m_Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:%s Class:0x%04X\n"), + _RRType2Name(p_Question.m_Header.m_Attributes.m_u16Type), + p_Question.m_Header.m_Attributes.m_u16Class); + }); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSQuestion: FAILED!\n"), _DH());); + return bResult; + +} + + +#ifdef MDNS_IPV4_SUPPORT +/* + MDNSResponder::_writeMDNSAnswer_A + + Write a MDNS A answer to the UDP output buffer. + + NAME (var, host/service domain, eg. esp8266.local + TYPE (16bit, eg. A) + CLASS (16bit, eg. IN) + TTL (32bit, eg. 120) + RDLENGTH (16bit, eg 4) + RDATA (var, eg. 123.456.789.012) + + eg. esp8266.local A 0x8001 120 4 123.456.789.012 + Ref: http://www.zytrax.com/books/dns/ch8/a.html +*/ +bool MDNSResponder::clsHost::_writeMDNSAnswer_A(IPAddress p_IPAddress, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_A (%s)%s\n"), p_IPAddress.toString().c_str(), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); + + stcRRAttributes attributes(DNS_RRTYPE_A, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + const unsigned char aucIPAddress[MDNS_IPV4_SIZE] = { p_IPAddress[0], p_IPAddress[1], p_IPAddress[2], p_IPAddress[3] }; + bool bResult = ((p_IPAddress.isV4()) && + (_writeMDNSHostDomain(m_pcHostName, false, 0, p_rSendParameter)) && + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), p_rSendParameter)) && // TTL + (_write16(MDNS_IPV4_SIZE, p_rSendParameter)) && // RDLength + (_udpAppendBuffer(aucIPAddress, MDNS_IPV4_SIZE)) && // RData + (p_rSendParameter.shiftOffset(MDNS_IPV4_SIZE))); + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_A %s.local Type:%s Class:0x%04X TTL:%u %s\n"), + _DH(), + m_pcHostName, + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), + p_IPAddress.toString().c_str()); + ); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_A: FAILED!\n"), _DH());); + return bResult; + +} + +/* + MDNSResponder::_writeMDNSAnswer_PTR_IPv4 + + Write a MDNS reverse IPv4 PTR answer to the UDP output buffer. + See: '_writeMDNSAnswer_A' + + eg. 012.789.456.123.in-addr.arpa PTR 0x8001 120 15 esp8266.local + Used while answering reverse IPv4 questions +*/ +bool MDNSResponder::clsHost::_writeMDNSAnswer_PTR_IPv4(IPAddress p_IPAddress, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv4 (%s)%s\n"), p_IPAddress.toString().c_str(), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); + + stcRRDomain reverseIPv4Domain; + stcRRAttributes attributes(DNS_RRTYPE_PTR, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + stcRRDomain hostDomain; + bool bResult = ((p_IPAddress.isV4()) && + (_buildDomainForReverseIPv4(p_IPAddress, reverseIPv4Domain)) && // 012.789.456.123.in-addr.arpa + (_writeMDNSRRDomain(reverseIPv4Domain, p_rSendParameter)) && + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), p_rSendParameter)) && // TTL + (_writeMDNSHostDomain(m_pcHostName, true, 0, p_rSendParameter))); // RDLength & RData (host domain, eg. esp8266.local) + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv4 "), _DH()); + _printRRDomain(reverseIPv4Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:%s Class:0x%04X TTL:%u %s.local\n"), + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), + m_pcHostName); + ); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv4: FAILED!\n"), _DH());); + return bResult; +} +#endif + +/* + MDNSResponder::_writeMDNSAnswer_PTR_TYPE + + Write a MDNS PTR answer to the UDP output buffer. + See: '_writeMDNSAnswer_A' + + PTR all-services -> service type + eg. _services._dns-sd._udp.local PTR 0x8001 5400 xx _http._tcp.local + http://www.zytrax.com/books/dns/ch8/ptr.html +*/ +bool MDNSResponder::clsHost::_writeMDNSAnswer_PTR_TYPE(MDNSResponder::clsHost::stcService& p_rService, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_TYPE\n"));); + + stcRRDomain dnssdDomain; + stcRRDomain serviceDomain; + stcRRAttributes attributes(DNS_RRTYPE_PTR, DNS_RRCLASS_IN); // No cache flush for shared records! only INternet + bool bResult = ((_buildDomainForDNSSD(dnssdDomain)) && // _services._dns-sd._udp.local + (_writeMDNSRRDomain(dnssdDomain, p_rSendParameter)) && + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL)), p_rSendParameter)) && // TTL + (_writeMDNSServiceDomain(p_rService, false, true, 0, p_rSendParameter))); // RDLength & RData (service domain, eg. _http._tcp.local) + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_TYPE "), _DH()); + _printRRDomain(dnssdDomain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:%s Class:0x%04X TTL:%u _%s._%s.local\n"), + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL)), + p_rService.m_pcServiceType, + p_rService.m_pcProtocol); + ); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_TYPE: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_writeMDNSAnswer_PTR_NAME + + Write a MDNS PTR answer to the UDP output buffer. + See: '_writeMDNSAnswer_A' + + PTR service type -> service name + eg. _http.tcp.local PTR 0x8001 120 xx myESP._http._tcp.local + http://www.zytrax.com/books/dns/ch8/ptr.html +*/ +bool MDNSResponder::clsHost::_writeMDNSAnswer_PTR_NAME(MDNSResponder::clsHost::stcService& p_rService, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_NAME\n"), _DH());); + + stcRRAttributes attributes(DNS_RRTYPE_PTR, DNS_RRCLASS_IN); // No cache flush for shared records! only INternet + bool bResult = ((_writeMDNSServiceDomain(p_rService, false, false, 0, p_rSendParameter)) && // _http._tcp.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL)), p_rSendParameter)) && // TTL + (_writeMDNSServiceDomain(p_rService, true, true, 0, p_rSendParameter))); // RDLength & RData (service domain, eg. MyESP._http._tcp.local) + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_NAME _%s._%s.local Type:%s Class:0x%04X TTL:%u %s._%s._%s.local\n"), + _DH(), + p_rService.m_pcServiceType, + p_rService.m_pcProtocol, + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL)), + p_rService.m_pcName, + p_rService.m_pcServiceType, + p_rService.m_pcProtocol); + ); + DEBUG_EX_ERR(if (!bResult)DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_NAME: FAILED!\n"), _DH());); + return bResult; +} + + +/* + MDNSResponder::_writeMDNSAnswer_TXT + + Write a MDNS TXT answer to the UDP output buffer. + See: '_writeMDNSAnswer_A' + + The TXT items in the RDATA block are 'length byte encoded': [len]vardata + + eg. myESP._http._tcp.local TXT 0x8001 120 4 c#=1 + http://www.zytrax.com/books/dns/ch8/txt.html +*/ +bool MDNSResponder::clsHost::_writeMDNSAnswer_TXT(MDNSResponder::clsHost::stcService& p_rService, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_TXT%s\n"), (p_rSendParameter.m_bCacheFlush ? "" : " nF"), _DH());); + + bool bResult = false; + + stcRRAttributes attributes(DNS_RRTYPE_TXT, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + + if ((_collectServiceTxts(p_rService)) && + (_writeMDNSServiceDomain(p_rService, true, false, 0, p_rSendParameter)) && // MyESP._http._tcp.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL)), p_rSendParameter)) && // TTL + (_write16(p_rService.m_Txts.length(), p_rSendParameter))) // RDLength + { + + bResult = true; + // RData Txts + for (stcServiceTxt* 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_rUDPContext.append(pTxt->m_pcKey, os_strlen(pTxt->m_pcKey))) && // Key + (p_rSendParameter.shiftOffset((size_t)os_strlen(pTxt->m_pcKey))) && + (1 == m_rUDPContext.append("=", 1)) && // = + (p_rSendParameter.shiftOffset(1)) && + ((!pTxt->m_pcValue) || + (((size_t)os_strlen(pTxt->m_pcValue) == m_rUDPContext.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("%s _writeMDNSAnswer_TXT: FAILED to write %sTxt %s=%s!\n"), _DH(), (pTxt->m_bTemp ? "temp. " : ""), (pTxt->m_pcKey ? : "?"), (pTxt->m_pcValue ? : "?"));); + } + } + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_TXT %s._%s._%s.local Type:%s Class:0x%04X TTL:%u \n"), + _DH(), + p_rService.m_pcName, + p_rService.m_pcServiceType, + p_rService.m_pcProtocol, + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL)), + p_rService.m_pcName, + p_rService.m_pcServiceType, + p_rService.m_pcProtocol); + ); + + _releaseTempServiceTxts(p_rService); + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_TXT: FAILED!\n"), _DH());); + return bResult; +} + +#ifdef MDNS_IPV6_SUPPORT +/* + MDNSResponder::_writeMDNSAnswer_AAAA + + Write a MDNS AAAA answer to the UDP output buffer. + See: '_writeMDNSAnswer_AAAA' + + eg. esp8266.local AAAA 0x8001 120 16 xxxx::xx + http://www.zytrax.com/books/dns/ch8/aaaa.html +*/ +bool MDNSResponder::clsHost::_writeMDNSAnswer_AAAA(IPAddress p_IPAddress, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_AAAA (%s)%s\n"), p_IPAddress.toString().c_str(), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); + + stcRRAttributes attributes(DNS_RRTYPE_AAAA, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + bool bResult = ((p_IPAddress.isV6()) && + (_writeMDNSHostDomain(m_pcHostName, false, 0, p_rSendParameter)) && // esp8266.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), p_rSendParameter)) && // TTL + (_write16(MDNS_IPV6_SIZE, p_rSendParameter)) && // RDLength + (_udpAppendBuffer((uint8_t*)p_IPAddress.raw6(), MDNS_IPV6_SIZE)) && // RData + (p_rSendParameter.shiftOffset(MDNS_IPV6_SIZE))); + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_AAAA %s.local Type:%s Class:0x%04X TTL:%u %s\n"), + _DH(), + m_pcHostName, + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), + p_IPAddress.toString().c_str()); + ); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_AAAA: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_writeMDNSAnswer_PTR_IPv6 + + Write a MDNS reverse IPv6 PTR answer to the UDP output buffer. + See: '_writeMDNSAnswer_AAAA' + + eg. xxxx::xx.ip6.arpa PTR 0x8001 120 15 esp8266.local + Used while answering reverse IPv6 questions +*/ +bool MDNSResponder::clsHost::_writeMDNSAnswer_PTR_IPv6(IPAddress p_IPAddress, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv6%s\n"), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); + + stcRRDomain reverseIPv6Domain; + stcRRAttributes attributes(DNS_RRTYPE_PTR, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + bool bResult = ((p_IPAddress.isV6()) && + (_buildDomainForReverseIPv6(p_IPAddress, reverseIPv6Domain)) && // xxxx::xx.ip6.arpa + (_writeMDNSRRDomain(reverseIPv6Domain, p_rSendParameter)) && + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), p_rSendParameter)) && // TTL + (_writeMDNSHostDomain(m_pcHostName, true, 0, p_rSendParameter))); // RDLength & RData (host domain, eg. esp8266.local) + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv6 "), _DH()); + _printRRDomain(reverseIPv6Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:%s Class:0x%04X TTL:%u %s.local\n"), + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), + m_pcHostName); + ); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv6: FAILED!\n"), _DH());); + return bResult; +} +#endif + +/* + MDNSResponder::_writeMDNSAnswer_SRV + + eg. MyESP._http.tcp.local SRV 0x8001 120 0 0 60068 esp8266.local + http://www.zytrax.com/books/dns/ch8/srv.html ???? Include instance name ???? +*/ +bool MDNSResponder::clsHost::_writeMDNSAnswer_SRV(MDNSResponder::clsHost::stcService& p_rService, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_SRV%s\n"), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); + + uint16_t u16CachedDomainOffset = (p_rSendParameter.m_bLegacyQuery + ? 0 + : p_rSendParameter.findCachedDomainOffset((const void*)m_pcHostName, false)); + + stcRRAttributes attributes(DNS_RRTYPE_SRV, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + stcRRDomain hostDomain; + bool bResult = ((_writeMDNSServiceDomain(p_rService, true, false, 0, p_rSendParameter)) && // MyESP._http._tcp.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL/*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_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_SRV %s._%s._%s.local Type:%s Class:0x%04X TTL:%u %u %u %u %s.local\n"), + _DH(), + p_rService.m_pcName, + p_rService.m_pcServiceType, + p_rService.m_pcProtocol, + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), + MDNS_SRV_PRIORITY, + MDNS_SRV_WEIGHT, + p_rService.m_u16Port, + m_pcHostName); + ); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_SRV: FAILED!\n"), _DH());); + return bResult; +} + +/* + MDNSResponder::_createNSECBitmap +*/ +MDNSResponder::clsHost::stcNSECBitmap* MDNSResponder::clsHost::_createNSECBitmap(uint32_t p_u32NSECContent) +{ + // Currently 6 bytes (6*8 -> 0..47) are long enough, and only this is implemented + stcNSECBitmap* pNSECBitmap = new stcNSECBitmap; + if (pNSECBitmap) + { + if (p_u32NSECContent & static_cast(enuContentFlag::A)) + { + pNSECBitmap->setBit(DNS_RRTYPE_A); // 01/0x01 + } + if ((p_u32NSECContent & static_cast(enuContentFlag::PTR_IPv4)) || + (p_u32NSECContent & static_cast(enuContentFlag::PTR_IPv6))) + { + pNSECBitmap->setBit(DNS_RRTYPE_PTR); // 12/0x0C + } + if (p_u32NSECContent & static_cast(enuContentFlag::AAAA)) + { + pNSECBitmap->setBit(DNS_RRTYPE_AAAA); // 28/0x1C + } + if (p_u32NSECContent & static_cast(enuContentFlag::TXT)) + { + pNSECBitmap->setBit(DNS_RRTYPE_TXT); // 16/0x10 + } + if (p_u32NSECContent & static_cast(enuContentFlag::SRV)) + { + pNSECBitmap->setBit(DNS_RRTYPE_SRV); // 33/0x21 + } + if (p_u32NSECContent & static_cast(enuContentFlag::NSEC)) + { + pNSECBitmap->setBit(DNS_RRTYPE_NSEC); // 47/0x2F + } + } + return pNSECBitmap; +} + +/* + MDNSResponder::_writeMDNSNSECBitmap +*/ +bool MDNSResponder::clsHost::_writeMDNSNSECBitmap(const MDNSResponder::clsHost::stcNSECBitmap& p_NSECBitmap, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + /* DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("_writeMDNSNSECBitmap: ")); + for (uint16_t u=0; ulength()), p_rSendParameter)) && // XX esp8266.local + (_writeMDNSNSECBitmap(*pNSECBitmap, p_rSendParameter))); // NSEC bitmap + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC %s.local Type:%s Class:0x%04X TTL:%u %s %s\n"), + _DH(), + m_pcHostName, + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), + m_pcHostName, + _NSECBitmap2String(pNSECBitmap)); + ); + + if (pNSECBitmap) + { + delete pNSECBitmap; + pNSECBitmap = 0; + } + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC (host): FAILED!\n"), _DH());); + return bResult; +} + + +#ifdef MDNS_IPV4_SUPPORT +/* + MDNSResponder::_writeMDNSAnswer_NSEC_PTR_IPv4(host) + + eg. 012.789.456.123.in-addr.arpa NSEC 0x8001 120 XX 012.789.456.123.in-addr.arpa xxx + http://www.zytrax.com/books/dns/ch8/nsec.html +*/ +bool MDNSResponder::clsHost::_writeMDNSAnswer_NSEC_PTR_IPv4(IPAddress p_IPAddress, + stcSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC_PTR_IPv4\n"));); + + stcRRAttributes attributes(DNS_RRTYPE_NSEC, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + stcNSECBitmap* pNSECBitmap = _createNSECBitmap(static_cast(enuContentFlag::PTR_IPv4)); + stcRRDomain reverseIPv4Domain; + bool bResult = ((p_IPAddress.isV4()) && + (pNSECBitmap) && // NSEC bitmap created + (_buildDomainForReverseIPv4(p_IPAddress, reverseIPv4Domain)) && // 012.789.456.123.in-addr.arpa + (_writeMDNSRRDomain(reverseIPv4Domain, p_rSendParameter)) && // 012.789.456.123.in-addr.arpa + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), p_rSendParameter)) && // TTL + (_write16((reverseIPv4Domain.m_u16NameLength + (2 + pNSECBitmap->length())), p_rSendParameter)) && + (_writeMDNSRRDomain(reverseIPv4Domain, p_rSendParameter)) && // 012.789.456.123.in-addr.arpa + (_writeMDNSNSECBitmap(*pNSECBitmap, p_rSendParameter))); // NSEC bitmap + + DEBUG_EX_INFO(if (bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC_PTR_IPv4 "), _DH()); + _printRRDomain(reverseIPv4Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:%s Class:0x%04X TTL:%u "), + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL))); + _printRRDomain(reverseIPv4Domain); + DEBUG_OUTPUT.printf_P(PSTR(" %s\n"), _NSECBitmap2String(pNSECBitmap)); + }); + + if (pNSECBitmap) + { + delete pNSECBitmap; + pNSECBitmap = 0; + } + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC_PTR_IPv4 (host): FAILED!\n"), _DH());); + return bResult; +} +#endif + + +#ifdef MDNS_IPV6_SUPPORT +/* + MDNSResponder::_writeMDNSAnswer_NSEC_PTR_IPv6(host) + + eg. 9.0.0.0.0.0.0.0.0.0.0.0.0.7.8.5.6.3.4.1.2.ip6.arpa NSEC 0x8001 120 XX 9.0.0.0.0.0.0.0.0.0.0.0.0.7.8.5.6.3.4.1.2.ip6.arpa xxx + http://www.zytrax.com/books/dns/ch8/nsec.html +*/ +bool MDNSResponder::clsHost::_writeMDNSAnswer_NSEC_PTR_IPv6(IPAddress p_IPAddress, + stcSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC_PTR_IPv6\n"));); + + stcRRAttributes attributes(DNS_RRTYPE_NSEC, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + stcNSECBitmap* pNSECBitmap = _createNSECBitmap(static_cast(enuContentFlag::PTR_IPv6)); + stcRRDomain reverseIPv6Domain; + bool bResult = ((p_IPAddress.isV6()) && + (pNSECBitmap) && // NSEC bitmap created + (_buildDomainForReverseIPv6(p_IPAddress, reverseIPv6Domain)) && // 9.0.0.0.0.0.0.0.0.0.0.0.0.7.8.5.6.3.4.1.2.ip6.arpa + (_writeMDNSRRDomain(reverseIPv6Domain, p_rSendParameter)) && // 9.0.0.0.0.0.0.0.0.0.0.0.0.7.8.5.6.3.4.1.2.ip6.arpa + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), p_rSendParameter)) && // TTL + (_write16((reverseIPv6Domain.m_u16NameLength + (2 + pNSECBitmap->length())), p_rSendParameter)) && + (_writeMDNSRRDomain(reverseIPv6Domain, p_rSendParameter)) && // 9.0.0.0.0.0.0.0.0.0.0.0.0.7.8.5.6.3.4.1.2.ip6.arpa + (_writeMDNSNSECBitmap(*pNSECBitmap, p_rSendParameter))); // NSEC bitmap + + DEBUG_EX_INFO(if (bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC_PTR_IPv6 "), _DH()); + _printRRDomain(reverseIPv6Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:%s Class:0x%04X TTL:%u "), + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL))); + _printRRDomain(reverseIPv6Domain); + DEBUG_OUTPUT.printf_P(PSTR(" %s\n"), _NSECBitmap2String(pNSECBitmap)); + }); + + if (pNSECBitmap) + { + delete pNSECBitmap; + pNSECBitmap = 0; + } + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC_PTR_IPv6 (host): FAILED!\n"), _DH());); + return bResult; +} +#endif + +/* + MDNSResponder::_writeMDNSAnswer_NSEC(service) + + eg. MyESP._http.tcp.local NSEC 0x8001 4500 XX MyESP._http.tcp.local xxx + http://www.zytrax.com/books/dns/ch8/nsec.html +*/ +bool MDNSResponder::clsHost::_writeMDNSAnswer_NSEC(MDNSResponder::clsHost::stcService& p_rService, + uint32_t p_u32NSECContent, + MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC (service: %s)\n"), _DH(), _replyFlags2String(p_u32NSECContent));); + + stcRRAttributes attributes(DNS_RRTYPE_NSEC, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + stcNSECBitmap* pNSECBitmap = _createNSECBitmap(p_u32NSECContent); + bool bResult = ((pNSECBitmap) && // NSEC bitmap created + (_writeMDNSServiceDomain(p_rService, true, false, 0, p_rSendParameter)) && // MyESP._http._tcp.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL)), p_rSendParameter)) && // TTL + (_writeMDNSServiceDomain(p_rService, true, true, (2 + pNSECBitmap->length()), p_rSendParameter)) && // XX MyESP._http._tcp.local + (_writeMDNSNSECBitmap(*pNSECBitmap, p_rSendParameter))); // NSEC bitmap + + DEBUG_EX_INFO(if (bResult) + DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC %s._%s._%s.local Type:%s Class:0x%04X TTL:%u %s\n"), + _DH(), + p_rService.m_pcName, + p_rService.m_pcServiceType, + p_rService.m_pcProtocol, + _RRType2Name(attributes.m_u16Type), + attributes.m_u16Class, + (p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL)), + _NSECBitmap2String(pNSECBitmap)); + ); + + if (pNSECBitmap) + { + delete pNSECBitmap; + pNSECBitmap = 0; + } + + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC (service): FAILED!\n"), _DH());); + return bResult; +} + +} // namespace MDNSImplementation + +} // namespace esp8266 + + + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h new file mode 100755 index 0000000000..240700b7cf --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h @@ -0,0 +1,169 @@ +/* + LEAmDNS2_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 LEAMDNS2_PRIV_H +#define LEAMDNS2_PRIV_H + +namespace esp8266 +{ + +/* + LEAmDNS +*/ + +namespace experimental +{ + +// Enable class debug functions +#define ESP_8266_MDNS_INCLUDE +#define DEBUG_ESP_MDNS_RESPONDER + + +#ifndef LWIP_OPEN_SRC +#define LWIP_OPEN_SRC +#endif + +// +// If ENABLE_ESP_MDNS_RESPONDER_PASSIV_MODE is defined, the mDNS responder ignores a successful probing +// This allows to drive the responder in a environment, where 'update()' isn't called in the loop +//#define ENABLE_ESP_MDNS_RESPONDER_PASSIV_MODE + +// 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 + +/* + This is NOT the TTL (Time-To-Live) for MDNS records, but the + subnet level distance MDNS records should travel. + 1 sets the subnet distance to 'local', which is default for MDNS. + (Btw.: 255 would set it to 'as far as possible' -> internet) + + However, RFC 3171 seems to force 255 instead +*/ +#define MDNS_MULTICAST_TTL 255 /* some say 1 is right*/ + +/* + This is the MDNS record TTL + Host level records are set to 2min (120s) + service level records are set to 75min (4500s) +*/ +#define MDNS_LEGACY_TTL 10 +#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 + Delay between and number of announces for host and service domains + Delay between and number of queries; the delay is multiplied by the resent number in '_checkQueryCache' +*/ +#define MDNS_PROBE_DELAY 250 +#define MDNS_PROBE_COUNT 3 +#define MDNS_ANNOUNCE_DELAY 1000 +#define MDNS_ANNOUNCE_COUNT 3 +#define MDNS_DYNAMIC_QUERY_RESEND_DELAY 1000 + + +/* + 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 +#else +#ifdef F +#undef F +#endif +#define F(A) A +#endif + +} // namespace MDNSImplementation + +} // namespace esp8266 + +// Include the main header, so the submodlues only need to include this header +#include "LEAmDNS2.h" + + +#endif // LEAMDNS2_PRIV_H diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h b/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h new file mode 100755 index 0000000000..b28ea2a26e --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h @@ -0,0 +1,44 @@ +/* + LEAmDNS2_lwIPdefs.h + + License (MIT license): + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#ifndef LEAMDNS2_LWIPDEFS_H +#define LEAMDNS2_LWIPDEFS_H + +#include +#if LWIP_VERSION_MAJOR == 1 + +#include // DNS_RRTYPE_xxx + +// cherry pick from lwip1 dns.c/mdns.c source files: +#define DNS_MQUERY_PORT 5353 +#define DNS_MQUERY_IPV4_GROUP_INIT IPAddress(224,0,0,251) /* resolver1.opendns.com */ +#define DNS_RRCLASS_ANY 255 /* any class */ + +#else // lwIP > 1 + +#include // DNS_RRTYPE_xxx, DNS_MQUERY_PORT + +#endif + +#endif // LEAMDNS2_LWIPDEFS_H From 560c2a5ca1ecdd066940ae292c56f0e29a8c2b14 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 8 May 2020 00:28:47 +0200 Subject: [PATCH 003/152] updated Source files from LEA --- .../LEAmDNS/TwoInterfaces/TwoInterfaces.ino | 165 + libraries/ESP8266mDNS/src/ESP8266mDNS.h | 7 +- libraries/ESP8266mDNS/src/LEAmDNS2.h | 1303 ------ libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 1428 ++++++ libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 1658 +++++++ ...t_Control.cpp => LEAmDNS2Host_Control.cpp} | 1134 +++-- ..._Host_Debug.cpp => LEAmDNS2Host_Debug.cpp} | 153 +- .../ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 3253 +++++++++++++ ...Transfer.cpp => LEAmDNS2Host_Transfer.cpp} | 932 ++-- libraries/ESP8266mDNS/src/LEAmDNS2_API.cpp | 4164 ----------------- .../ESP8266mDNS/src/LEAmDNS2_APIHelpers.cpp | 354 -- .../ESP8266mDNS/src/LEAmDNS2_Backbone.cpp | 339 ++ libraries/ESP8266mDNS/src/LEAmDNS2_Host.cpp | 1336 ------ libraries/ESP8266mDNS/src/LEAmDNS2_Host.hpp | 1177 ----- .../ESP8266mDNS/src/LEAmDNS2_Host_Structs.cpp | 2435 ---------- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 1493 ++++++ libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h | 679 +++ libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h | 169 - libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h | 0 19 files changed, 10156 insertions(+), 12023 deletions(-) create mode 100644 libraries/ESP8266mDNS/examples/LEAmDNS/TwoInterfaces/TwoInterfaces.ino delete mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2.h create mode 100644 libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp create mode 100644 libraries/ESP8266mDNS/src/LEAmDNS2Host.h rename libraries/ESP8266mDNS/src/{LEAmDNS2_Host_Control.cpp => LEAmDNS2Host_Control.cpp} (66%) mode change 100755 => 100644 rename libraries/ESP8266mDNS/src/{LEAmDNS2_Host_Debug.cpp => LEAmDNS2Host_Debug.cpp} (65%) mode change 100755 => 100644 create mode 100644 libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp rename libraries/ESP8266mDNS/src/{LEAmDNS2_Host_Transfer.cpp => LEAmDNS2Host_Transfer.cpp} (71%) mode change 100755 => 100644 delete mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2_API.cpp delete mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2_APIHelpers.cpp create mode 100644 libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp delete mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2_Host.cpp delete mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2_Host.hpp delete mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2_Host_Structs.cpp create mode 100644 libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp create mode 100644 libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h delete mode 100755 libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h mode change 100755 => 100644 libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/TwoInterfaces/TwoInterfaces.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/TwoInterfaces/TwoInterfaces.ino new file mode 100644 index 0000000000..3434454491 --- /dev/null +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/TwoInterfaces/TwoInterfaces.ino @@ -0,0 +1,165 @@ + +#include +#include + +#include +#include + + +clsMDNSHost mDNSHost_AP; +clsMDNSHost mDNSHost_STA; +ESP8266WebServer server(80); + +void connectToWiFi(const char* p_pcSSID, + const char* p_pcPWD, + uint32_t p_u32Timeout = 20) +{ + WiFi.begin(p_pcSSID, p_pcPWD); + Serial.println(""); + + // Wait for connection + uint8 u8Tries = p_u32Timeout; + while ((WiFi.status() != WL_CONNECTED) && + (u8Tries--)) { + delay(500); + Serial.print("."); + } + if (WiFi.status() == WL_CONNECTED) + { + Serial.println(""); + Serial.print("Connected to "); + Serial.println(p_pcSSID); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + } + else + { + Serial.printf("FAILED to connect to '%s'!\n", p_pcSSID); + } +} + +void setup(void) +{ + Serial.begin(115200); + Serial.setDebugOutput(false); + delay(2000); + Serial.printf("\nStart\n"); + + // Setup WiFi and AP + WiFi.setAutoConnect(false); + WiFi.mode(WIFI_AP_STA); + WiFi.softAP("ESP8266", "12345678"); + Serial.print("Created AP "); + Serial.println("ESP8266"); + Serial.print("AP-IP address: "); + Serial.println(WiFi.softAPIP()); + + if (mDNSHost_AP.begin("ESP8266", WIFI_AP, [](clsMDNSHost& p_rMDNSHost, + const char* p_pcDomainName, + bool p_bProbeResult)->void + { + Serial.printf("mDNSHost_AP::ProbeResultCallback: '%s' is %s\n", p_pcDomainName, (p_bProbeResult ? "FREE" : "USED!")); + + // Unattended added service + p_rMDNSHost.addService(0, "http", "tcp", 80); + })) + { + Serial.println("mDNS-AP started"); + } + else + { + Serial.println("FAILED to start mDNS-AP"); + } + + // Connect to WiFi network, with WRONG password + connectToWiFi("AP8", "WRONG_PW", 5); + + if (mDNSHost_STA.begin("esp8266", WIFI_STA, [](clsMDNSHost& p_rMDNSHost, + const char* p_pcDomainName, + bool p_bProbeResult)->void + { + Serial.printf("mDNSHost_STA::ProbeResultCallback: '%s' is %s\n", p_pcDomainName, (p_bProbeResult ? "FREE" : "USED!")); + if (p_bProbeResult) + { + p_rMDNSHost.addService("LEA_Weather", "http", "tcp", 80, [](clsMDNSHost::clsService& p_rService, + const char* p_pcInstanceName, + bool p_bProbeResult)->void + { + Serial.printf("mDNSHost_STA::HTTP-Service::ProbeResultCallback: '%s' is %s\n", p_pcInstanceName, (p_bProbeResult ? "FREE" : "USED!")); + if (p_bProbeResult) + { + if (!p_rService.addServiceTxt("path", "/")) + { + Serial.println("FAILED to add service TXT item!"); + } + p_rService.setDynamicServiceTxtCallback([](clsMDNSHost::clsService& p_rService)->void + { + Serial.printf("mDNSHost_STA::HTTP-Service::DynamicTXTCallback\n"); + + p_rService.addDynamicServiceTxt("user", "admin"); + static uint32_t u32Counter = 0; + p_rService.addDynamicServiceTxt("cnt", ++u32Counter); + }); + } + else + { + if (p_rService.indexInstanceName()) + { + Serial.printf("Changed service instance name to '%s'\n", p_rService.instanceName()); + } + else + { + Serial.println("FAILED to index service instance name!"); + } + } + }); + + // Unattended added service + p_rMDNSHost.addService("MQTTInstance", "mqtt", "tcp", 1883); + } + else + { + p_rMDNSHost.indexHostName(); + } + })) + { + Serial.println("mDNS-STA started"); + } + else + { + Serial.println("FAILED to start mDNS-STA"); + } + + // Non-synchronized added service + mDNSHost_STA.addService(0, "test", "tcp", 999); + + // Setup HTTP server + server.on("/", [](void) + { + Serial.println("server.on"); + server.send(200, "text/plain", "test"); + }); + server.begin(); + Serial.println("HTTP server started"); +} + +void loop(void) +{ + server.handleClient(); + mDNSHost_AP.update(); + mDNSHost_STA.update(); + + static esp8266::polledTimeout::oneShotMs timer2(esp8266::polledTimeout::oneShotMs::alwaysExpired); + if (timer2) + { + Serial.println("FIX PASSWORD"); + connectToWiFi("AP8", "_______"); + + timer2.reset(esp8266::polledTimeout::oneShotMs::neverExpires); + } +} + + + + + diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS.h b/libraries/ESP8266mDNS/src/ESP8266mDNS.h index 585ddfbbec..fdcf489fb0 100644 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS.h +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS.h @@ -44,15 +44,16 @@ #include "ESP8266mDNS_Legacy.h" #include "LEAmDNS.h" -#include "LEAmDNS2.h" +#include "LEAmDNS2Host.h" #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) // Maps the implementation to use to the global namespace type //using MDNSResponder = Legacy_MDNSResponder::MDNSResponder; //legacy -using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; //new +//using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; //new //using MDNSResponder = esp8266::experimental::MDNSResponder; //new^2 not compatible +using clsMDNSHost = esp8266::experimental::clsLEAMDNSHost; -extern MDNSResponder MDNS; +//extern MDNSResponder MDNS; #endif diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2.h b/libraries/ESP8266mDNS/src/LEAmDNS2.h deleted file mode 100755 index 7aa3bb0160..0000000000 --- a/libraries/ESP8266mDNS/src/LEAmDNS2.h +++ /dev/null @@ -1,1303 +0,0 @@ -/* - LEAmDNS2.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_hMDNSService, 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_hMDNSService, void* p_pUserdata)': - Add dynamic TXT items by calling 'MDNS.addDynamicServiceTxt(p_hMDNSService, "c#", "1");' - - In loop(): - Call 'MDNS.update();' - - - For querying services/hosts: - Static: - Call 'uint32_t u32AnswerCount = MDNS.queryService("http", "tcp");' or 'MDNS.queryHost("esp8266")'; - Iterate answers by: 'for (uint32_t u=0; u // for UdpContext.h -#include -#include -#include - -#include "lwip/netif.h" -#include "WiFiUdp.h" -#include "lwip/udp.h" -#include "debug.h" -#include "include/UdpContext.h" -#include - -#include "ESP8266WiFi.h" - - -namespace esp8266 -{ - -/** - LEAmDNS -*/ -namespace experimental -{ - -//this should be user-defined at build time -#ifndef ARDUINO_BOARD -#define ARDUINO_BOARD "generic" -#endif - -#define MDNS_IPV4_SUPPORT -#if LWIP_IPV6 -#define MDNS_IPV6_SUPPORT // If we've got IPv6 support, then we need IPv6 support :-) -#endif - - -#ifdef MDNS_IPV4_SUPPORT -#define MDNS_IPV4_SIZE 4 -#endif -#ifdef MDNS_IPV6_SUPPORT -#define MDNS_IPV6_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 5000 - -/* - DNS_RRTYPE_NSEC -*/ -#ifndef DNS_RRTYPE_NSEC -#define DNS_RRTYPE_NSEC 0x2F -#endif - - -/** - MDNSResponder -*/ -class MDNSResponder -{ -protected: -#include "LEAmDNS2_Host.hpp" - -public: - // MISC HELPERS - // Domain name helper - static bool indexDomain(char*& p_rpcDomain, - const char* p_pcDivider = "-", - const char* p_pcDefaultDomain = 0); - // Host name helper - static bool setNetIfHostName(netif* p_pNetIf, - const char* p_pcHostName); - - // INTERFACE - MDNSResponder(void); - virtual ~MDNSResponder(void); - - // HANDLEs for opaque access to responder objects - /** - hMDNSHost - */ - using hMDNSHost = const void*; - /** - hMDNSService - */ - using hMDNSService = const void*; - /** - hMDNSTxt - */ - using hMDNSTxt = const void*; - /** - hMDNSQuery - */ - using hMDNSQuery = const void*; - - // CALLBACKS - /** - MDNSHostProbeResultCallbackFn - Callback function for host domain probe results - */ - using MDNSHostProbeResultCallbackFn = std::function; - - // Create a MDNS netif responder netif by setting the default hostname - // Later call 'update()' in every 'loop' to run the process loop - // (probing, announcing, responding, ...) - // If no callback is given, the (maybe) already installed callback stays set - hMDNSHost begin(const char* p_pcHostName, - netif* p_pNetIf, - MDNSHostProbeResultCallbackFn p_fnCallback = 0); - bool begin(const char* p_pcHostName, - WiFiMode_t p_WiFiMode, - MDNSHostProbeResultCallbackFn p_fnCallback = 0); - bool begin(const char* p_pcHostName, - MDNSHostProbeResultCallbackFn p_fnCallback = 0); - - // Finish MDNS processing - bool close(const hMDNSHost p_hMDNSHost); - bool close(void); - - hMDNSHost getMDNSHost(netif* p_pNetIf) const; - hMDNSHost getMDNSHost(WiFiMode_t p_WiFiMode) const; - - // Change hostname (probing is restarted) - // If no callback is given, the (maybe) already installed callback stays set - bool setHostName(const hMDNSHost p_hMDNSHost, - const char* p_pcHostName, - MDNSHostProbeResultCallbackFn p_fnCallback = 0); - - const char* hostName(const hMDNSHost p_hMDNSHost) const; - - // Set a callback function for host probe results - // The callback function is called, when the probeing for the host domain - // succeededs or fails. - // In case of failure, the failed domain name should be changed. - bool setHostProbeResultCallback(const hMDNSHost p_hMDNSHost, - MDNSHostProbeResultCallbackFn p_fnCallback); - - // Returns 'true' is host domain probing is done - bool status(const hMDNSHost p_hMDNSHost) const; - - // Add a 'global' default' instance name for new services - bool setInstanceName(const hMDNSHost p_hMDNSHost, - const char* p_pcInstanceName); - const char* instanceName(const hMDNSHost p_hMDNSHost) const; - - /** - MDNSServiceProbeResultCallbackFn - Callback function for service domain probe results - */ - using MDNSServiceProbeResultCallbackFn = std::function; - // 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.# - // If no callback is given, the (maybe) already installed callback stays set - hMDNSService addService(const hMDNSHost p_hMDNSHost, - const char* p_pcName, - const char* p_pcServiceType, - const char* p_pcProtocol, - uint16_t p_u16Port, - MDNSServiceProbeResultCallbackFn p_fnCallback = 0); - // Removes a service from the MDNS responder - bool removeService(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService); - bool removeService(const hMDNSHost p_hMDNSHost, - const char* p_pcInstanceName, - const char* p_pcServiceType, - const char* p_pcProtocol); - hMDNSService findService(const hMDNSHost p_hMDNSHost, - const char* p_pcName, - const char* p_pcService, - const char* p_pcProtocol); - - // Change the services instance name (and restart probing). - // If no callback is given, the (maybe) already installed callback stays set - bool setServiceName(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcInstanceName, - MDNSServiceProbeResultCallbackFn p_fnCallback = 0); - const char* serviceName(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const; - const char* serviceType(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const; - const char* serviceProtocol(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const; - uint16_t servicePort(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const; - - // Set a service specific probe result callcack - bool setServiceProbeResultCallback(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - MDNSServiceProbeResultCallbackFn p_fnCallback); - - bool serviceStatus(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const; - - // Add a (static) MDNS TXT item ('key' = 'value') to the service - hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - const char* p_pcValue); - hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint32_t p_u32Value); - hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint16_t p_u16Value); - hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint8_t p_u8Value); - hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - int32_t p_i32Value); - hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - int16_t p_i16Value); - hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - int8_t p_i8Value); - - // Remove an existing (static) MDNS TXT item from the service - bool removeServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const hMDNSTxt p_hTxt); - bool removeServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey); - - /** - MDNSDynamicServiceTxtCallbackFn - Callback function for dynamic MDNS TXT items - */ - using MDNSDynamicServiceTxtCallbackFn = std::function; - bool setDynamicServiceTxtCallback(const hMDNSHost p_hMDNSHost, - MDNSDynamicServiceTxtCallbackFn p_fnCallback); - bool setDynamicServiceTxtCallback(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - MDNSDynamicServiceTxtCallbackFn p_fnCallback); - - // 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(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - const char* p_pcValue); - hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint32_t p_u32Value); - hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint16_t p_u16Value); - hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint8_t p_u8Value); - hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - int32_t p_i32Value); - hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - int16_t p_i16Value); - hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - int8_t p_i8Value); - - // QUERIES & ANSWERS - /** - clsMDNSAnswerAccessor & clsAnswerAccessorVector - */ - struct clsMDNSAnswerAccessor - { - protected: - /** - stcCompareTxtKey - */ - struct stcCompareTxtKey - { - bool operator()(char const* p_pA, char const* p_pB) const; - }; - public: - clsMDNSAnswerAccessor(const clsHost::stcQuery::stcAnswer* p_pAnswer); - ~clsMDNSAnswerAccessor(void); - - /** - clsTxtKeyValueMap - */ - using clsTxtKeyValueMap = std::map; - - bool serviceDomainAvailable(void) const; - const char* serviceDomain(void) const; - bool hostDomainAvailable(void) const; - const char* hostDomain(void) const; - bool hostPortAvailable(void) const; - uint16_t hostPort(void) const; -#ifdef MDNS_IPV4_SUPPORT - bool IPv4AddressAvailable(void) const; - std::vector IPv4Addresses(void) const; -#endif -#ifdef MDNS_IPV6_SUPPORT - bool IPv6AddressAvailable(void) const; - std::vector IPv6Addresses(void) const; -#endif - bool txtsAvailable(void) const; - const char* txts(void) const; - const clsTxtKeyValueMap& txtKeyValues(void) const; - const char* txtValue(const char* p_pcKey) const; - - size_t printTo(Print& p_Print) const; - - protected: - const clsHost::stcQuery::stcAnswer* m_pAnswer; - clsTxtKeyValueMap m_TxtKeyValueMap; - }; - using clsMDNSAnswerAccessorVector = std::vector; - using typeQueryAnswerType = clsHost::typeQueryAnswerType; - using enuQueryAnswerType = clsHost::enuQueryAnswerType; - - // STATIC QUERY - // Perform a (static) service/host query. The function returns after p_u16Timeout milliseconds - // The answers (the number of received answers is returned) can be retrieved by calling - // - answerHostName (or hostname) - // - answerIP (or IP) - // - answerPort (or port) - uint32_t queryService(const hMDNSHost p_hMDNSHost, - const char* p_pcService, - const char* p_pcProtocol, - const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME); - uint32_t queryHost(const hMDNSHost p_hMDNSHost, - const char* p_pcHostName, - const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME); - bool removeQuery(const hMDNSHost p_hMDNSHost); - bool hasQuery(const hMDNSHost p_hMDNSHost); - hMDNSQuery getQuery(const hMDNSHost p_hMDNSHost); - - clsMDNSAnswerAccessorVector answerAccessors(const hMDNSHost p_hMDNSHost); - uint32_t answerCount(const hMDNSHost p_hMDNSHost); - clsMDNSAnswerAccessor answerAccessor(const hMDNSHost p_hMDNSHost, - uint32_t p_u32AnswerIndex); - - // DYNAMIC QUERIES - /** - MDNSQueryCallbackFn - - Callback function for received answers for dynamic queries - */ - using MDNSQueryCallbackFn = std::function; // true: Answer component set, false: component deleted - - // Install a dynamic service/host query. For every received answer (part) the given callback - // function is called. The query will be updated every time, the TTL for an answer - // has timed-out. - // The answers can also be retrieved by calling - // - answerCount service/host (for host queries, this should never be >1) - // - answerServiceDomain service - // - hasAnswerHostDomain/answerHostDomain service/host - // - hasAnswerIPv4Address/answerIPv4Address service/host - // - hasAnswerIPv6Address/answerIPv6Address service/host - // - hasAnswerPort/answerPort service - // - hasAnswerTxts/answerTxts service - hMDNSQuery installServiceQuery(const hMDNSHost p_hMDNSHost, - const char* p_pcServiceType, - const char* p_pcProtocol, - MDNSQueryCallbackFn p_fnCallback); - hMDNSQuery installHostQuery(const hMDNSHost p_hMDNSHost, - const char* p_pcHostName, - MDNSQueryCallbackFn p_fnCallback); - // Remove a dynamic service query - bool removeQuery(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hMDNSQuery); - - - uint32_t answerCount(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hMDNSQuery); - clsMDNSAnswerAccessorVector answerAccessors(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hMDNSQuery); - clsMDNSAnswerAccessor answerAccessor(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hMDNSQuery, - uint32 p_u32AnswerIndex); - - /* bool hasAnswerServiceDomain(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - const char* answerServiceDomain(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - bool hasAnswerHostDomain(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - const char* answerHostDomain(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - #ifdef MDNS_IPV4_SUPPORT - bool hasAnswerIPv4Address(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - uint32_t answerIPv4AddressCount(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - IPAddress answerIPv4Address(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex, - const uint32_t p_u32AddressIndex); - #endif - #ifdef MDNS_IPV6_SUPPORT - bool hasAnswerIPv6Address(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - uint32_t answerIPv6AddressCount(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - IPAddress answerIPv6Address(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex, - const uint32_t p_u32AddressIndex); - #endif - bool hasAnswerPort(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - uint16_t answerPort(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - bool hasAnswerTxts(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - // Get the TXT items as a ';'-separated string - const char* answerTxts(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex);*/ - - // GENERAL MANAGEMENT - // Application should call this whenever AP is configured/disabled - bool notifyNetIfChange(netif* p_pNetIf); - - // 'update' should be called in every 'loop' to run the MDNS processing - bool update(const hMDNSHost p_hMDNSHost); - bool update(void); // Convenience - - // 'announce' can be called every time, the configuration of some service - // changes. Mainly, this would be changed content of TXT items. - bool announce(const hMDNSHost p_hMDNSHost); - bool announce(void); // Convenience - - // MISC - // Enable OTA update - hMDNSService enableArduino(const hMDNSHost p_hMDNSHost, - uint16_t p_u16Port, - bool p_bAuthUpload = false); - -protected: - /** Internal CLASSES & STRUCTS **/ - - // InstanceData - UdpContext* m_pUDPContext; - clsHostList m_HostList; - - // UDP CONTEXT - bool _allocUDPContext(void); - bool _releaseUDPContext(void); - bool _processUDPInput(void); - - // NETIF - clsHost* _createHost(netif* p_pNetIf); - bool _releaseHost(clsHost* p_pHost); - - const clsHost* _findHost(netif* p_pNetIf) const; - clsHost* _findHost(netif* p_pNetIf); - const clsHost* _findHost(const hMDNSHost p_hMDNSHost) const; - clsHost* _findHost(const hMDNSHost p_hMDNSHost); - - - // HANDLE HELPERS - bool _validateMDNSHostHandle(const hMDNSHost p_hMDNSHost) const; - bool _validateMDNSHostHandle(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const; - - clsHost* _NRH2Ptr(const hMDNSHost p_hMDNSHost); - const clsHost* _NRH2Ptr(const hMDNSHost p_hMDNSHost) const; - clsHost::stcService* _SH2Ptr(const hMDNSService p_hMDNSService); - const clsHost::stcService* _SH2Ptr(const hMDNSService p_hMDNSService) const; - - // INIT - clsHost* _begin(const char* p_pcHostName, - netif* p_pNetIf, - MDNSHostProbeResultCallbackFn p_fnCallback); - bool _close(clsHost& p_rHost); - - -#if not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_MDNS_RESPONDER - - const char* _DH(hMDNSHost p_hMDNSResponder = 0) const; - -#endif // not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_MDNS_RESPONDER - -}; - -#ifdef __MDNS_USE_LEGACY -/** - MDNSResponder_Legacy -*/ -class MDNSResponder_Legacy //: public MDNSResponder -{ -public: - /* INTERFACE */ - MDNSResponder_Legacy(void); - virtual ~MDNSResponder_Legacy(void); - - /** - hMDNSHost (opaque handle to access a netif binding) - */ - using hMDNSHost = const void*; - /** - hMDNSService (opaque handle to access the service) - */ - using hMDNSService = const void*; - /** - MDNSHostProbeResultCallbackFn - Callback function for host domain probe results - */ - using MDNSHostProbeResultCallbackFn = std::function; - /* LEGACY 2 */ - using MDNSServiceProbeResultCallbackFn1 = std::function; - using MDNSServiceProbeResultCallbackFn2 = std::function; - /** - hMDNSTxt (opaque handle to access the TXT items) - */ - using hMDNSTxt = const void*; - /** - MDNSDynamicServiceTxtCallbackFn - Callback function for dynamic MDNS TXT items - */ - using MDNSDynamicServiceTxtCallbackFn = std::function; - // LEGACY - using MDNSDynamicServiceTxtCallbackFn1 = std::function; - using MDNSDynamicServiceTxtCallbackFn2 = std::function; - - - hMDNSHost getNetIfBinding(netif* p_pNetIf) const; - hMDNSHost getNetIfBinding(WiFiMode_t p_WiFiMode) const; - - // Create a MDNS responder netif binding by setting the default hostname - // Later call 'update()' in every 'loop' to run the process loop - // (probing, announcing, responding, ...) - - hMDNSHost begin(const char* p_pcHostName, - netif* p_pNetIf, - MDNSHostProbeResultCallbackFn p_fnCallback = 0); - bool begin(const char* p_pcHostName, - WiFiMode_t p_WiFiMode, - MDNSHostProbeResultCallbackFn p_fnCallback = 0); - bool begin(const char* p_pcHostName, - MDNSHostProbeResultCallbackFn p_fnCallback = 0); - - /* bool begin(const String& p_strHostName) {return begin(p_strHostName.c_str());} - // for compatibility - bool begin(const char* p_pcHostName, - IPAddress p_IPAddress, // ignored - uint32_t p_u32TTL = 120); // ignored - bool begin(const String& p_strHostName, - IPAddress p_IPAddress, // ignored - uint32_t p_u32TTL = 120) { // ignored - return begin(p_strHostName.c_str(), p_IPAddress, p_u32TTL); - }*/ - // Finish MDNS processing - bool close(const hMDNSHost p_hMDNSHost); - bool close(void); - // for ESP32 compatibility - bool end(void); - - // Change hostname (probing is restarted) - bool setHostName(const hMDNSHost p_hMDNSHost, - const char* p_pcHostName); - // for compatibility... - bool setHostname(const char* p_pcHostName); - bool setHostname(String p_strHostName); - - const char* hostName(const hMDNSHost p_hMDNSHost) const; - const char* hostname(void) const; - - // Returns 'true' is host domain probing is done - bool status(const hMDNSHost p_hMDNSHost) const; - bool status(void) const; - - bool setInstanceName(const hMDNSHost p_hMDNSHost, - const char* p_pcInstanceName); - bool setInstanceName(const char* p_pcInstanceName); - // for ESP32 compatibility - bool setInstanceName(const String& p_strHostName); - - // 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 hMDNSHost p_hMDNSHost, - const char* p_pcName, - const char* p_pcService, - const char* p_pcProtocol, - uint16_t p_u16Port); - 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 hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService); - bool removeService(const hMDNSHost p_hMDNSHost, - const char* p_pcInstanceName, - const char* p_pcServiceName, - const char* p_pcProtocol); - bool removeService(const hMDNSService p_hMDNSService); - 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); - hMDNSService findService(const hMDNSHost p_hMDNSHost, - const char* p_pcName, - const char* p_pcService, - const char* p_pcProtocol); - hMDNSService findService(const char* p_pcName, - const char* p_pcService, - const char* p_pcProtocol); - - // Change the services instance name (and restart probing). - bool setServiceName(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcInstanceName); - bool setServiceName(const hMDNSService p_hMDNSService, - const char* p_pcInstanceName); - - const char* serviceName(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const; - const char* service(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const; - const char* serviceProtocol(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const; - /* LEGACY */ - const char* serviceName(const hMDNSService p_hMDNSService) const; - const char* service(const hMDNSService p_hMDNSService) const; - const char* serviceProtocol(const hMDNSService p_hMDNSService) const; - - bool serviceStatus(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const; - bool serviceStatus(const hMDNSService p_hMDNSService) const; - - // Add a (static) MDNS TXT item ('key' = 'value') to the service - hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - const char* p_pcValue); - hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint32_t p_u32Value); - hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint16_t p_u16Value); - hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint8_t p_u8Value); - hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - int32_t p_i32Value); - hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - int16_t p_i16Value); - hMDNSTxt addServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - int8_t p_i8Value); - // LEGACY - hMDNSTxt addServiceTxt(const hMDNSService p_hMDNSService, - const char* p_pcKey, - const char* p_pcValue); - hMDNSTxt addServiceTxt(const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint32_t p_u32Value); - hMDNSTxt addServiceTxt(const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint16_t p_u16Value); - hMDNSTxt addServiceTxt(const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint8_t p_u8Value); - hMDNSTxt addServiceTxt(const hMDNSService p_hMDNSService, - const char* p_pcKey, - int32_t p_i32Value); - hMDNSTxt addServiceTxt(const hMDNSService p_hMDNSService, - const char* p_pcKey, - int16_t p_i16Value); - hMDNSTxt addServiceTxt(const hMDNSService p_hMDNSService, - const char* p_pcKey, - int8_t p_i8Value); - - // Remove an existing (static) MDNS TXT item from the service - bool removeServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const hMDNSTxt p_hTxt); - bool removeServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey); - bool removeServiceTxt(const hMDNSHost p_hMDNSHost, - const char* p_pcinstanceName, - const char* p_pcServiceName, - const char* p_pcProtocol, - const char* p_pcKey); - bool removeServiceTxt(const hMDNSService p_hMDNSService, - const hMDNSTxt p_hTxt); - bool removeServiceTxt(const hMDNSService p_hMDNSService, - 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); - - bool setDynamicServiceTxtCallback(const hMDNSHost p_hMDNSHost, - MDNSDynamicServiceTxtCallbackFn p_fnCallback); - bool setDynamicServiceTxtCallback(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - MDNSDynamicServiceTxtCallbackFn p_fnCallback); - - // 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(MDNSDynamicServiceTxtCallbackFn1 p_fnCallback); - bool setDynamicServiceTxtCallback(MDNSDynamicServiceTxtCallbackFn2 p_fnCallback); - - // 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_hMDNSService, - MDNSDynamicServiceTxtCallbackFn1 p_fnCallback); - bool setDynamicServiceTxtCallback(const hMDNSService p_hMDNSService, - MDNSDynamicServiceTxtCallbackFn2 p_fnCallback); - - // 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(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - const char* p_pcValue); - hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint32_t p_u32Value); - hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint16_t p_u16Value); - hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint8_t p_u8Value); - hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - int32_t p_i32Value); - hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - int16_t p_i16Value); - hMDNSTxt addDynamicServiceTxt(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcKey, - int8_t p_i8Value); - /* LEGACY */ - hMDNSTxt addDynamicServiceTxt(const hMDNSService p_hMDNSService, - const char* p_pcKey, - const char* p_pcValue); - hMDNSTxt addDynamicServiceTxt(const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint32_t p_u32Value); - hMDNSTxt addDynamicServiceTxt(const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint16_t p_u16Value); - hMDNSTxt addDynamicServiceTxt(const hMDNSService p_hMDNSService, - const char* p_pcKey, - uint8_t p_u8Value); - hMDNSTxt addDynamicServiceTxt(const hMDNSService p_hMDNSService, - const char* p_pcKey, - int32_t p_i32Value); - hMDNSTxt addDynamicServiceTxt(const hMDNSService p_hMDNSService, - const char* p_pcKey, - int16_t p_i16Value); - hMDNSTxt addDynamicServiceTxt(const hMDNSService p_hMDNSService, - const char* p_pcKey, - int8_t p_i8Value); - - /** - hMDNSQuery (opaque handle to access dynamic service queries) - */ - using hMDNSQuery = const void*; - //using hMDNSServiceQuery = hMDNSQuery; // for compatibility with V1 - - // Perform a (static) service/host query. The function returns after p_u16Timeout milliseconds - // The answers (the number of received answers is returned) can be retrieved by calling - // - answerHostName (or hostname) - // - answerIP (or IP) - // - answerPort (or port) - uint32_t queryService(const hMDNSHost p_hMDNSHost, - const char* p_pcService, - const char* p_pcProtocol, - const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME); - uint32_t queryService(const char* p_pcService, - const char* p_pcProtocol, - const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME); - // for compatibility... - uint32_t queryService(const String& p_strService, - const String& p_strProtocol); - uint32_t queryHost(const hMDNSHost p_hMDNSHost, - const char* p_pcHostName, - const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME); - uint32_t queryHost(const char* p_pcHostName, - const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME); - bool removeQuery(const hMDNSHost p_hMDNSHost); - bool removeQuery(void); - bool hasQuery(const hMDNSHost p_hMDNSHost); - bool hasQuery(void); - hMDNSQuery getQuery(const hMDNSHost p_hMDNSHost); - hMDNSQuery getQuery(void); - - const char* answerHostName(const hMDNSHost p_hMDNSHost, - const uint32_t p_u32AnswerIndex); - const char* answerHostName(const uint32_t p_u32AnswerIndex); - // for compatibility... - String hostname(const uint32_t p_u32AnswerIndex); -#ifdef MDNS_IPV4_SUPPORT - IPAddress answerIPv4(const hMDNSHost p_hMDNSHost, - const uint32_t p_u32AnswerIndex); - IPAddress answerIPv4(const uint32_t p_u32AnswerIndex); - // for compatibility - IPAddress answerIP(const uint32_t p_u32AnswerIndex); - IPAddress IP(const uint32_t p_u32AnswerIndex); -#endif -#ifdef MDNS_IPV6_SUPPORT - IPAddress answerIPv6(const hMDNSHost p_hMDNSHost, - const uint32_t p_u32AnswerIndex); - IPAddress answerIPv6(const uint32_t p_u32AnswerIndex); -#endif - uint16_t answerPort(const hMDNSHost p_hMDNSHost, - const uint32_t p_u32AnswerIndex); - uint16_t answerPort(const uint32_t p_u32AnswerIndex); - // for compatibility - uint16_t port(const uint32_t p_u32AnswerIndex); - - /** - typeQueryAnswerType & enuQueryAnswerType - */ - using typeQueryAnswerType = uint8_t; - enum class enuQueryAnswerType : typeQueryAnswerType - { - Unknown = 0x00, - ServiceDomain = 0x01, // Service domain - HostDomain = 0x02, // Host domain - Port = 0x04, // Port - Txts = 0x08, // TXT items -#ifdef MDNS_IPV4_SUPPORT - IPv4Address = 0x10, // IPv4 address -#endif -#ifdef MDNS_IPV6_SUPPORT - IPv6Address = 0x20, // IPv6 address -#endif - }; - //using AnswerType = enuQueryAnswerType; // for compatibility with V1 - - /** - stcAnswerAccessor - */ - struct stcAnswerAccessor - { - protected: - /** - stcCompareTxtKey - */ - struct stcCompareTxtKey - { - bool operator()(char const* p_pA, char const* p_pB) const; - }; - public: - stcAnswerAccessor(MDNSResponder& p_rMDNSResponder, - hMDNSQuery p_hQuery, - uint32_t p_u32AnswerIndex); - /** - clsTxtKeyValueMap - */ - using clsTxtKeyValueMap = std::map; - - bool serviceDomainAvailable(void) const; - const char* serviceDomain(void) const; - bool hostDomainAvailable(void) const; - const char* hostDomain(void) const; - bool hostPortAvailable(void) const; - uint16_t hostPort(void) const; -#ifdef MDNS_IPV4_SUPPORT - bool IPv4AddressAvailable(void) const; - std::vector IPv4Addresses(void) const; -#endif -#ifdef MDNS_IPV6_SUPPORT - bool IPv6AddressAvailable(void) const; - std::vector IPv6Addresses(void) const; -#endif - bool txtsAvailable(void) const; - const char* txts(void) const; - const clsTxtKeyValueMap& txtKeyValues(void) const; - const char* txtValue(const char* p_pcKey) const; - - size_t printTo(Print& p_Print) const; - - protected: - MDNSResponder& m_rMDNSResponder; - hMDNSQuery m_hQuery; - uint32_t m_u32AnswerIndex; - clsTxtKeyValueMap m_TxtKeyValueMap; - }; - - /** - MDNSQueryCallbackFn - - Callback function for received answers for dynamic queries - */ - using MDNSQueryCallbackFn = std::function; // true: Answer component set, false: component deleted - // LEGACY - using MDNSQueryCallbackFn1 = std::function; // true: Answer component set, false: component deleted - using MDNSQueryCallbackFn2 = std::function; // true: Answer component set, false: component deleted - //using MDNSServiceInfo = stcAnswerAccessor; // for compatibility with V1 - - // Install a dynamic service/host query. For every received answer (part) the given callback - // function is called. The query will be updated every time, the TTL for an answer - // has timed-out. - // The answers can also be retrieved by calling - // - answerCount service/host (for host queries, this should never be >1) - // - answerServiceDomain service - // - hasAnswerHostDomain/answerHostDomain service/host - // - hasAnswerIPv4Address/answerIPv4Address service/host - // - hasAnswerIPv6Address/answerIPv6Address service/host - // - hasAnswerPort/answerPort service - // - hasAnswerTxts/answerTxts service - hMDNSQuery installServiceQuery(const hMDNSHost p_hMDNSHost, - const char* p_pcService, - const char* p_pcProtocol, - MDNSQueryCallbackFn p_fnCallback); - hMDNSQuery installHostQuery(const hMDNSHost p_hMDNSHost, - const char* p_pcHostName, - MDNSQueryCallbackFn p_fnCallback); - - hMDNSQuery installServiceQuery(const char* p_pcService, - const char* p_pcProtocol, - MDNSQueryCallbackFn1 p_fnCallback); - hMDNSQuery installServiceQuery(const char* p_pcService, - const char* p_pcProtocol, - MDNSQueryCallbackFn2 p_fnCallback); - - hMDNSQuery installHostQuery(const char* p_pcHostName, - MDNSQueryCallbackFn1 p_fnCallback); - hMDNSQuery installHostQuery(const char* p_pcHostName, - MDNSQueryCallbackFn2 p_fnCallback); - // Remove a dynamic service query - bool removeDynamicQuery(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hMDNSQuery); - bool removeDynamicQuery(const hMDNSQuery p_hQuery); - - /** - clsMDNSAnswerAccessorVector - */ - using clsMDNSAnswerAccessorVector = std::vector; - - clsMDNSAnswerAccessorVector answerAccessors(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hMDNSQuery); - clsMDNSAnswerAccessorVector answerAccessors(const hMDNSQuery p_hQuery); - - uint32_t answerCount(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hMDNSQuery); - uint32_t answerCount(const hMDNSQuery p_hQuery); - - bool hasAnswerServiceDomain(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - bool hasAnswerServiceDomain(const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - const char* answerServiceDomain(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - const char* answerServiceDomain(const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - bool hasAnswerHostDomain(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - bool hasAnswerHostDomain(const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - const char* answerHostDomain(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - const char* answerHostDomain(const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); -#ifdef MDNS_IPV4_SUPPORT - bool hasAnswerIPv4Address(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - uint32_t answerIPv4AddressCount(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - IPAddress answerIPv4Address(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex, - const uint32_t p_u32AddressIndex); - bool hasAnswerIPv4Address(const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - uint32_t answerIPv4AddressCount(const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - IPAddress answerIPv4Address(const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex, - const uint32_t p_u32AddressIndex); -#endif -#ifdef MDNS_IPV6_SUPPORT - bool hasAnswerIPv6Address(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - uint32_t answerIPv6AddressCount(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - IPAddress answerIPv6Address(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex, - const uint32_t p_u32AddressIndex); - bool hasAnswerIPv6Address(const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - uint32_t answerIPv6AddressCount(const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - IPAddress answerIPv6Address(const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex, - const uint32_t p_u32AddressIndex); -#endif - bool hasAnswerPort(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - bool hasAnswerPort(const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - uint16_t answerPort(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - /* uint16_t answerPort(const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex);*/ - bool hasAnswerTxts(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - bool hasAnswerTxts(const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - // Get the TXT items as a ';'-separated string - const char* answerTxts(const hMDNSHost p_hMDNSHost, - const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - const char* answerTxts(const hMDNSQuery p_hQuery, - const uint32_t p_u32AnswerIndex); - - // Set a callback function for host probe results - // The callback function is called, when the probeing for the host domain - // succeededs or fails. - // In case of failure, the failed domain name should be changed. - bool setHostProbeResultCallback(MDNSHostProbeResultCallbackFn p_fnCallback); - /* LEGACY 2 */ - using MDNSHostProbeResultCallbackFn1 = std::function; - using MDNSHostProbeResultCallbackFn2 = std::function; - - bool setHostProbeResultCallback(MDNSHostProbeResultCallbackFn1 p_fnCallback); - bool setHostProbeResultCallback(MDNSHostProbeResultCallbackFn2 p_fnCallback); - - /** - MDNSServiceProbeResultCallbackFn - Callback function for service domain probe results - */ - using MDNSServiceProbeResultCallbackFn = std::function; - // Set a service specific probe result callcack - bool setServiceProbeResultCallback(const hMDNSService p_hMDNSService, - MDNSServiceProbeResultCallbackFn p_fnCallback); - - bool setServiceProbeResultCallback(const hMDNSService p_hMDNSService, - MDNSServiceProbeResultCallbackFn1 p_fnCallback); - bool setServiceProbeResultCallback(const hMDNSService p_hMDNSService, - MDNSServiceProbeResultCallbackFn2 p_fnCallback); - - // Application should call this whenever AP is configured/disabled - bool notifyNetIfChange(netif* p_pNetIf); - - // 'update' should be called in every 'loop' to run the MDNS processing - bool update(const hMDNSHost p_hMDNSHost); - 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(const hMDNSHost p_hMDNSHost); - bool announce(void); - - // Enable OTA update - hMDNSService enableArduino(const hMDNSHost p_hMDNSHost, - uint16_t p_u16Port, - bool p_bAuthUpload = false); - 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); - // Host name helper - static bool setNetIfHostName(netif* p_pNetIf, - const char* p_pcHostName); -}; -#endif - -} // namespace MDNSImplementation - -} // namespace esp8266 - -#endif // LEAMDNS2_H - - - diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp new file mode 100644 index 0000000000..dd46d09ad3 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -0,0 +1,1428 @@ +/* + LEAmDNS2Host.cpp + + License (MIT license): + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#include "LEAmDNS2Host.h" + +#ifdef MDNS_IPV4_SUPPORT +#include +#endif +#ifdef MDNS_IPV6_SUPPORT +#include +#endif + + +namespace // anonymous +{ + +/* + strrstr (static) + + Backwards search for p_pcPattern in p_pcString + Based on: https://stackoverflow.com/a/1634398/2778898 + +*/ +const char* strrstr(const char*__restrict p_pcString, + const char*__restrict p_pcPattern) +{ + const char* pcResult = 0; + + size_t stStringLength = (p_pcString ? strlen(p_pcString) : 0); + size_t stPatternLength = (p_pcPattern ? strlen(p_pcPattern) : 0); + + if ((stStringLength) && + (stPatternLength) && + (stPatternLength <= stStringLength)) + { + // Pattern is shorter or has the same length than the string + for (const char* s = (p_pcString + stStringLength - stPatternLength); s >= p_pcString; --s) + { + if (0 == strncmp(s, p_pcPattern, stPatternLength)) + { + pcResult = s; + break; + } + } + } + return pcResult; +} + + +} // anonymous + + +namespace esp8266 +{ + + +namespace experimental +{ + + +/* + + HELPERS + +*/ + +/* + clsLEAmDNS2_Host::indexDomainName (static) + + Increments the given domain 'p_pcDomainName' by appending a delimiter and an index number. + + If the given domain name already has a numeric index (after the given delimiter), this index + is incremented. If not, the delimiter and index '2' is added. + + If 'p_pcDomainName' is empty (==0), the given default name 'p_pcDefaultDomainName' is used, + if no default is given, 'ESP8266' is used. + +*/ +//static +const char* clsLEAMDNSHost::indexDomainName(const char* p_pcDomainName, + const char* p_pcDivider /*= "-"*/, + const char* p_pcDefaultDomainName /*= 0*/) +{ + static char acResultDomainName[clsConsts::stDomainLabelMaxLength]; + *acResultDomainName = 0; + + // Ensure a divider exists; use '-' as default + const char* pcDivider = (p_pcDivider ? : "-"); + + if (p_pcDomainName) + { + // Given domain + const char* pFoundDivider = strrstr(p_pcDomainName, pcDivider); + if (pFoundDivider) // maybe already extended + { + char* pEnd = 0; + unsigned long ulIndex = strtoul((pFoundDivider + strlen(pcDivider)), &pEnd, 10); + if ((ulIndex) && + ((pEnd - p_pcDomainName) == (ptrdiff_t)strlen(p_pcDomainName)) && + (!*pEnd)) + { + // Valid (old) index found + char acIndexBuffer[16]; + sprintf(acIndexBuffer, "%lu", (++ulIndex)); + //size_t stLength = ((pFoundDivider - p_pcDomainName + strlen(pcDivider)) + strlen(acIndexBuffer) + 1); + + memcpy(acResultDomainName, p_pcDomainName, (pFoundDivider - p_pcDomainName + strlen(pcDivider))); + acResultDomainName[pFoundDivider - p_pcDomainName + strlen(pcDivider)] = 0; + strcat(acResultDomainName, acIndexBuffer); + } + else + { + pFoundDivider = 0; // Flag the need to (base) extend the hostname + } + } + + if (!pFoundDivider) + { + // not yet extended (or failed to increment extension) -> start indexing + //size_t stLength = strlen(p_pcDomainName) + (strlen(pcDivider) + 1 + 1); // Name + Divider + '2' + '\0' + sprintf(acResultDomainName, "%s%s2", p_pcDomainName, pcDivider); + } + } + else + { + // No given domain, use base or default + const char* cpcDefaultName = (p_pcDefaultDomainName ? : "ESP8266"); + size_t stLength = strlen(cpcDefaultName) + 1; // '\0' + strncpy(acResultDomainName, cpcDefaultName, stLength); + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[mDNS] indexDomainName: From '%s' to '%s'\n"), (p_pcDomainName ? : ""), acResultDomainName);); + return acResultDomainName; +} + + +/* + clsLEAmDNS2_Host::setStationHostName (static) + + Sets the staion hostname + +*/ +// static +bool clsLEAMDNSHost::setNetIfHostName(netif* p_pNetIf, + const char* p_pcHostName) +{ + if ((p_pNetIf) && + (p_pcHostName)) + { + netif_set_hostname(p_pNetIf, (char*)p_pcHostName); // LWIP 1.x forces 'char*' instead of 'const char*' + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[mDNS] setNetIfHostName host name: %s!\n"), p_pcHostName);); + } + return true; +} + + +/** + clsLEAmDNS2_Host::sm_pBackbone + +*/ +clsLEAMDNSHost::clsBackbone* clsLEAMDNSHost::clsBackbone::sm_pBackbone = 0; + +/** + Consts::... + +*/ +const char* clsLEAMDNSHost::clsConsts::pcLocal = "local"; +const char* clsLEAMDNSHost::clsConsts::pcServices = "services"; +const char* clsLEAMDNSHost::clsConsts::pcDNSSD = "dns-sd"; +const char* clsLEAMDNSHost::clsConsts::pcUDP = "udp"; +//const char* clsLEAMDNSHost::clsConsts::pcTCP = "tcp"; + +#ifdef MDNS_IPV4_SUPPORT +const char* clsLEAMDNSHost::clsConsts::pcReverseIPv4Domain = "in-addr"; +#endif +#ifdef MDNS_IPV6_SUPPORT +const char* clsLEAMDNSHost::clsConsts::pcReverseIPv6Domain = "ip6"; +#endif +const char* clsLEAMDNSHost::clsConsts::pcReverseTopDomain = "arpa"; + + +/* + clsLEAmDNS2_Host::clsLEAmDNS2_Host constructor + +*/ +clsLEAMDNSHost::clsLEAMDNSHost(void) + : m_pNetIf(0), + m_NetIfState(static_cast(enuNetIfState::None)), + m_pUDPContext(0), + m_pcHostName(0), + m_pcDefaultInstanceName(0), + m_ProbeInformation() +{ +} + +/* + clsLEAmDNS2_Host::~clsLEAmDNS2_Host destructor + +*/ +clsLEAMDNSHost::~clsLEAMDNSHost(void) +{ + close(); +} + +/* + + INIT + +*/ + +/* + clsLEAmDNS2_Host::begin (hostname, netif, probe_callback) + + Creates a new mDNS host (adding the netif to the multicast groups), + sets up the instance data (hostname, ...) and starts the probing process + +*/ +bool clsLEAMDNSHost::begin(const char* p_pcHostName, + netif* p_pNetIf, + clsLEAMDNSHost::fnProbeResultCallback p_fnCallback /*= 0*/) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s, netif: %u)\n"), _DH(), (p_pcHostName ? : "_"), (p_pNetIf ? netif_get_index(p_pNetIf) : 0));); + + bool bResult = false; + + if (!((bResult = ((setHostName(p_pcHostName)) && + ((m_pNetIf = p_pNetIf)) && + (_joinMulticastGroups()) && + (p_fnCallback ? setProbeResultCallback(p_fnCallback) : true) && + ((m_pUDPContext = _allocBackbone())) && + (restart()))))) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s begin: FAILED for '%s'!\n"), _DH(), (p_pcHostName ? : "-"));); + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin: %s to init with hostname %s!\n"), _DH(), (bResult ? "Succeeded" : "FAILED"), (p_pcHostName ? : "-"));); + return bResult; +} + +/* + clsLEAmDNS2_Host::begin (hostname, probe_callback) + +*/ +bool clsLEAMDNSHost::begin(const char* p_pcHostName, + clsLEAMDNSHost::fnProbeResultCallback p_fnCallback /*= 0*/) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s)\n"), _DH(), (p_pcHostName ? : "_"));); + + return begin(p_pcHostName, (WiFiMode_t)wifi_get_opmode(), p_fnCallback); +} + +/* + clsLEAmDNS2_Host::begin (hostname, WiFiMode, probe_callback) + +*/ +bool clsLEAMDNSHost::begin(const char* p_pcHostName, + WiFiMode_t p_WiFiMode, + clsLEAMDNSHost::fnProbeResultCallback p_fnCallback /*= 0*/) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s, opmode: %u)\n"), _DH(), (p_pcHostName ? : "_"), (uint32_t)p_WiFiMode);); + + bool bResult = false; + + if (p_WiFiMode == WIFI_STA) + { + bResult = begin(p_pcHostName, netif_get_by_index(WIFI_STA), p_fnCallback); + } + else if (p_WiFiMode == WIFI_AP) + { + bResult = begin(p_pcHostName, netif_get_by_index(WIFI_AP), p_fnCallback); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s begin: FAILED for WiFi mode '%u'! Only 'WIFI_STA' or 'WIFI_AP' is allowed (HostName: %s)!\n"), _DH(), (bResult ? "Succeeded" : "FAILED"), p_WiFiMode, (p_pcHostName ? : "-"));); + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin: %s for WiFi mode '%u' (HostName: %s)!\n"), _DH(), (bResult ? "Succeeded" : "FAILED"), p_WiFiMode, (p_pcHostName ? : "-"));); + return bResult; +} + +/* + clsLEAmDNS2_Host::close + +*/ +bool clsLEAMDNSHost::close(void) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s close\n"), _DH());); + + m_pUDPContext = 0; + return ((_leaveMulticastGroups()) && + (_releaseBackbone())); +} + + +/* + + HOSTNAME + +*/ + +/* + clsLEAmDNS2_Host::setHostName + +*/ +bool clsLEAMDNSHost::setHostName(const char* p_pcHostName) +{ + bool bResult; + if ((bResult = _allocHostName(p_pcHostName))) + { + m_ProbeInformation.clear(false); + m_ProbeInformation.m_ProbingStatus = clsProbeInformation_Base::enuProbingStatus::ReadyToStart; + + // Replace 'auto-set' service names + for (clsService* pService : m_Services) + { + if ((pService->m_bAutoName) && + (!m_pcDefaultInstanceName)) + { + if (!((bResult = pService->setInstanceName(p_pcHostName)))) + { + break; + } + } + } + } + return bResult; +} + +/* + clsLEAmDNS2_Host::indexHostName + +*/ +bool clsLEAMDNSHost::indexHostName(void) +{ + return setHostName(clsLEAMDNSHost::indexDomainName(hostName(), "-", 0)); +} + +/* + clsLEAmDNS2_Host::hostName + +*/ +const char* clsLEAMDNSHost::hostName(void) const +{ + return m_pcHostName; +} + +/* + clsLEAmDNS2_Host::setProbeResultCallback + +*/ +bool clsLEAMDNSHost::setProbeResultCallback(clsLEAMDNSHost::fnProbeResultCallback p_fnCallback) +{ + m_ProbeInformation.m_fnProbeResultCallback = p_fnCallback; + return true; +} + +/* + clsLEAmDNS2_Host::probeStatus + +*/ +bool clsLEAMDNSHost::probeStatus(void) const +{ + return (clsProbeInformation_Base::enuProbingStatus::DoneFinally == m_ProbeInformation.m_ProbingStatus); +} + + +/* + + SERVICES + +*/ + +/* + clsLEAmDNS2_Host::setDefaultInstanceName + +*/ +bool clsLEAMDNSHost::setDefaultInstanceName(const char* p_pcDefaultInstanceName) +{ + bool bResult; + if ((bResult = _allocDefaultInstanceName(p_pcDefaultInstanceName))) + { + // Replace 'auto-set' service names + for (clsService* pService : m_Services) + { + if (pService->m_bAutoName) + { + if (!((bResult = pService->setInstanceName(p_pcDefaultInstanceName)))) + { + break; + } + } + } + } + return bResult; +} + +/* + clsLEAmDNS2_Host::defaultInstanceName + +*/ +const char* clsLEAMDNSHost::defaultInstanceName(void) const +{ + return m_pcDefaultInstanceName; +} + +/* + clsLEAmDNS2_Host::addService + +*/ +clsLEAMDNSHost::clsService* clsLEAMDNSHost::addService(const char* p_pcInstanceName, + const char* p_pcType, + const char* p_pcProtocol, + uint16_t p_u16Port, + clsLEAMDNSHost::clsService::fnProbeResultCallback p_fnCallback /*= 0*/) +{ + clsService* pService = 0; + + if (!((pService = findService(_instanceName(p_pcInstanceName), p_pcType, p_pcProtocol, p_u16Port)))) + { + // Not already used + if ((pService = new clsService)) + { + if ((pService->setInstanceName(_instanceName(p_pcInstanceName))) && + (pService->setType(p_pcType)) && + (pService->setProtocol(p_pcProtocol)) && + (pService->setPort(p_u16Port)) && + (p_fnCallback ? pService->setProbeResultCallback(p_fnCallback) : true)) + { + m_Services.push_back(pService); + } + else + { + delete pService; + pService = 0; + } + } + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s addService: %s to add service '%s.%s.%s.local'!\n"), _DH(pService), (pService ? "Succeeded" : "FAILED"), _instanceName(p_pcInstanceName, false), (p_pcType ? : ""), (p_pcProtocol ? : ""));); + DEBUG_EX_ERR(if (!pService) DEBUG_OUTPUT.printf_P(PSTR("%s addService: FAILED to add service '%s.%s.%s.local'!\n"), _DH(pService), _instanceName(p_pcInstanceName, false), (p_pcType ? : ""), (p_pcProtocol ? : ""));); + return pService; +} + +/* + clsLEAmDNS2_Host::removeService + +*/ +bool clsLEAMDNSHost::removeService(clsLEAMDNSHost::clsService* p_pService) +{ + bool bResult = false; + + if ((bResult = ((p_pService) && + (m_Services.end() != std::find(m_Services.begin(), m_Services.end(), p_pService)) && + (_announceService(*p_pService, false))))) + { + m_Services.remove(p_pService); + delete p_pService; + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _removeService: FAILED!\n"), _DH(p_pService));); + return bResult; +} + +/* + clsLEAmDNS2_Host::findService (const) + +*/ +const clsLEAMDNSHost::clsService* clsLEAMDNSHost::findService(const char* p_pcInstanceName, + const char* p_pcType, + const char* p_pcProtocol, + uint16_t p_u16Port/*= (uint16_t)(-1)*/) const +{ + clsService* pFoundService = 0; + + for (clsService* pService : m_Services) + { + if ((0 == strcmp(pService->instanceName(), _instanceName(p_pcInstanceName))) && + (0 == strcmp(pService->type(), p_pcType)) && + (0 == strcmp(pService->protocol(), p_pcProtocol)) && + (((uint16_t)(-1) == p_u16Port) || + (pService->port() == p_u16Port))) + { + pFoundService = pService; + break; + } + } + return pFoundService; +} + +/* + clsLEAmDNS2_Host::findService + +*/ +clsLEAMDNSHost::clsService* clsLEAMDNSHost::findService(const char* p_pcInstanceName, + const char* p_pcType, + const char* p_pcProtocol, + uint16_t p_u16Port /*= (uint16_t)(-1)*/) +{ + return (clsService*)((const clsLEAMDNSHost*)this)->findService(p_pcInstanceName, p_pcType, p_pcProtocol, p_u16Port); +} + +/* + clsLEAMDNSHost::services + +*/ +const clsLEAMDNSHost::clsService::list& clsLEAMDNSHost::services(void) const +{ + return m_Services; +} + + +/* + + QUERIES + +*/ + +/* + clsLEAmDNS2_Host::queryService + +*/ +clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryService(const char* p_pcService, + const char* p_pcProtocol, + const uint16_t p_u16Timeout) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryService '_%s._%s.local'\n"), _DH(), p_pcService, p_pcProtocol);); + + clsQuery* pQuery = 0; + if ((p_pcService) && (*p_pcService) && + (p_pcProtocol) && (*p_pcProtocol) && + (p_u16Timeout) && + ((pQuery = _allocQuery(clsQuery::enuQueryType::Service))) && + (_buildDomainForService(p_pcService, p_pcProtocol, pQuery->m_Domain))) + { + if ((_removeLegacyQuery()) && + ((pQuery->m_bStaticQuery = true)) && + (_sendQuery(*pQuery))) + { + // Wait for answers to arrive + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryService: Waiting %u ms for answers...\n"), _DH(), p_u16Timeout);); + delay(p_u16Timeout); + + // All answers should have arrived by now -> stop adding new answers + pQuery->m_bAwaitingAnswers = false; + } + else + { + // FAILED to send query + _removeQuery(pQuery); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s queryService: INVALID input data!\n"), _DH());); + } + return ((pQuery) + ? pQuery->answerAccessors() + : clsQuery::clsAnswerAccessor::vector()); +} + +/* + clsLEAmDNS2_Host::queryHost + +*/ +clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryHost(const char* p_pcHostName, + const uint16_t p_u16Timeout) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost '%s.local'\n"), _DH(), p_pcHostName);); + + clsQuery* pQuery = 0; + if ((p_pcHostName) && (*p_pcHostName) && + (p_u16Timeout) && + ((pQuery = _allocQuery(clsQuery::enuQueryType::Host))) && + (_buildDomainForHost(p_pcHostName, pQuery->m_Domain))) + { + if ((_removeLegacyQuery()) && + ((pQuery->m_bStaticQuery = true)) && + (_sendQuery(*pQuery))) + { + // Wait for answers to arrive + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost: Waiting %u ms for answers...\n"), _DH(), p_u16Timeout);); + delay(p_u16Timeout); + + // All answers should have arrived by now -> stop adding new answers + pQuery->m_bAwaitingAnswers = false; + } + else + { + // FAILED to send query + _removeQuery(pQuery); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost: INVALID input data!\n"), _DH());); + } + return ((pQuery) + ? pQuery->answerAccessors() + : clsQuery::clsAnswerAccessor::vector()); +} + +/* + clsLEAmDNS2_Host::removeQuery + +*/ +bool clsLEAMDNSHost::removeQuery(void) +{ + return _removeLegacyQuery(); +} + +/* + clsLEAmDNS2_Host::hasQuery + +*/ +bool clsLEAMDNSHost::hasQuery(void) +{ + return (0 != _findLegacyQuery()); +} + +/* + clsLEAmDNS2_Host::getQuery + +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::getQuery(void) +{ + return _findLegacyQuery(); +} + +/* + clsLEAmDNS2_Host::installServiceQuery (answer) + +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcService, + const char* p_pcProtocol, + clsLEAMDNSHost::clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer) +{ + clsQuery* pQuery = 0; + if ((pQuery = _installServiceQuery(p_pcService, p_pcProtocol))) + { + pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; + } + return pQuery; +} + +/* + clsLEAmDNS2_Host::installServiceQuery (accessor) + +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcService, + const char* p_pcProtocol, + clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor) +{ + clsQuery* pQuery = 0; + if ((pQuery = _installServiceQuery(p_pcService, p_pcProtocol))) + { + pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; + } + return pQuery; +} + +/* + clsLEAmDNS2_Host::installHostQuery (answer) +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installHostQuery(const char* p_pcHostName, + clsLEAMDNSHost::clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer) +{ + clsQuery* pQuery = 0; + if ((p_pcHostName) && (*p_pcHostName)) + { + clsRRDomain domain; + if ((pQuery = ((_buildDomainForHost(p_pcHostName, domain)) + ? _installDomainQuery(domain, clsQuery::enuQueryType::Host) + : 0))) + { + pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; + } + } + return pQuery; +} + +/* + clsLEAmDNS2_Host::installHostQuery (accessor) +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installHostQuery(const char* p_pcHostName, + clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor) +{ + clsQuery* pQuery = 0; + if ((p_pcHostName) && (*p_pcHostName)) + { + clsRRDomain domain; + if ((pQuery = ((_buildDomainForHost(p_pcHostName, domain)) + ? _installDomainQuery(domain, clsQuery::enuQueryType::Host) + : 0))) + { + pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; + } + } + return pQuery; +} + +/* + clsLEAmDNS2_Host::removeQuery +*/ +bool clsLEAMDNSHost::removeQuery(clsLEAMDNSHost::clsQuery* p_pMDNSQuery) +{ + bool bResult = ((p_pMDNSQuery) && + (_removeQuery(p_pMDNSQuery))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeQuery: FAILED!\n"), _DH());); + return bResult; +} + + +/* + PROCESSING +*/ + +/* + clsLEAmDNS2_Host::update +*/ +bool clsLEAMDNSHost::update(void) +{ + bool bResult = false; + + //if (clsBackbone::sm_pBackbone->setDelayUDPProcessing(true)) + //{ + bResult = ((_checkNetIfState()) && // Any changes in the netif state? + (_updateProbeStatus()) && // Probing and announcing + (_checkQueryCache())); + + // clsBackbone::sm_pBackbone->setDelayUDPProcessing(false); + //} + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s update: FAILED (Not connected?)!\n"), _DH());); + + return bResult; +} + +/* + clsLEAmDNS2_Host::announce +*/ +bool clsLEAMDNSHost::announce(bool p_bAnnounce /*= true*/, + bool p_bIncludeServices /*= true*/) +{ + return _announce(p_bAnnounce, p_bIncludeServices); +} + +/* + clsLEAmDNS2_Host::announceService +*/ +bool clsLEAMDNSHost::announceService(clsService* p_pService, + bool p_bAnnounce /*= true*/) +{ + return _announceService(*p_pService, p_bAnnounce); +} + +/* + clsLEAmDNS2_Host::restart +*/ +bool clsLEAMDNSHost::restart(void) +{ + return (_resetProbeStatus(true)); // Stop and restart probing +} + + + +/* + P R O T E C T E D +*/ + +/* + + BACKBONE + +*/ + +/* + clsLEAmDNS2_Host::_allocBackbone + +*/ +UdpContext* clsLEAMDNSHost::_allocBackbone(void) +{ + UdpContext* pUDPContext = 0; + + if (!clsBackbone::sm_pBackbone) + { + // Not yet created + clsBackbone::sm_pBackbone = new clsBackbone; + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocBackbone: Created backbone.\n"), _DH());); + + if ((clsBackbone::sm_pBackbone) && + (!clsBackbone::sm_pBackbone->init())) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _allocBackbone: FAILED to init backbone!\n"), _DH());); + + delete clsBackbone::sm_pBackbone; + clsBackbone::sm_pBackbone = 0; + } + } + if (clsBackbone::sm_pBackbone) + { + pUDPContext = clsBackbone::sm_pBackbone->addHost(this); + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocBackbone: %s to add host to backbone.\n"), _DH(), (pUDPContext ? "Succeeded" : "FAILED"));); + return pUDPContext; +} + +/* + clsLEAmDNS2_Host::_releaseBackbone + +*/ +bool clsLEAMDNSHost::_releaseBackbone(void) +{ + bool bResult = false; + + if ((clsBackbone::sm_pBackbone) && + ((bResult = clsBackbone::sm_pBackbone->removeHost(this))) && + (0 == clsBackbone::sm_pBackbone->hostCount())) + { + delete clsBackbone::sm_pBackbone; + clsBackbone::sm_pBackbone = 0; + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _releaseBackbone: Released backbone."), _DH());); + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _releaseBackbone: %s to remove host from backbone."), _DH(), (bResult ? "Succeeded" : "FAILED"));); + return bResult; +} + + +/* + + MULTICAST GROUPS + +*/ + +/* + clsLEAmDNS2_Host::_joinMulticastGroups +*/ +bool clsLEAMDNSHost::_joinMulticastGroups(void) +{ + bool bResult = false; + + // Join multicast group(s) + if (m_pNetIf) + { + bResult = true; + +#ifdef MDNS_IPV4_SUPPORT + ip_addr_t multicast_addr_V4 = DNS_MQUERY_IPV4_GROUP_INIT; + if (!(m_pNetIf->flags & NETIF_FLAG_IGMP)) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: Setting flag: flags & NETIF_FLAG_IGMP\n"), _DH());); + m_pNetIf->flags |= NETIF_FLAG_IGMP; + + if (ERR_OK != igmp_start(m_pNetIf)) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: igmp_start FAILED!\n"), _DH());); + } + } + + bResult = ((bResult) && +#if LWIP_VERSION_MAJOR == 1 + (ERR_OK == igmp_joingroup(ip_2_ip4(&m_pNetIf->ip_addr), ip_2_ip4(&multicast_addr_V4)))); +#else + (ERR_OK == igmp_joingroup_netif(m_pNetIf, ip_2_ip4(&multicast_addr_V4)))); +#endif + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: igmp_joingroup_netif(%s) FAILED!\n"), _DH(), IPAddress(multicast_addr_V4).toString().c_str());); +#endif + +#ifdef MDNS_IPV6_SUPPORT + ip_addr_t multicast_addr_V6 = DNS_MQUERY_IPV6_GROUP_INIT; + bResult = ((bResult) && + (ERR_OK == mld6_joingroup_netif(m_pNetIf, ip_2_ip6(&multicast_addr_V6)))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: mld6_joingroup_netif FAILED!\n"), _DH());); +#endif + } + return bResult; +} + +/* + clsLEAmDNS2_Host::_leaveMulticastGroups +*/ +bool clsLEAMDNSHost::_leaveMulticastGroups(void) +{ + bool bResult = false; + + if (m_pNetIf) + { + bResult = true; + /* _resetProbeStatus(false); // Stop probing + + _releaseQueries(); + _releaseServices(); + _releaseHostName();*/ + + // Leave multicast group(s) +#ifdef MDNS_IPV4_SUPPORT + ip_addr_t multicast_addr_V4 = DNS_MQUERY_IPV4_GROUP_INIT; +#if LWIP_VERSION_MAJOR == 1 + if (ERR_OK != igmp_leavegroup(ip_2_ip4(&m_rNetIf.ip_addr), ip_2_ip4(&multicast_addr_V4))) +#else + if (ERR_OK != igmp_leavegroup_netif(m_pNetIf, ip_2_ip4(&multicast_addr_V4))) +#endif + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("\n"));); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + ip_addr_t multicast_addr_V6 = DNS_MQUERY_IPV6_GROUP_INIT; + if (ERR_OK != mld6_leavegroup_netif(m_pNetIf, ip_2_ip6(&multicast_addr_V6)/*&(multicast_addr_V6.u_addr.ip6)*/)) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("\n"));); + } +#endif + } + return bResult; +} + + +/* + NETIF +*/ + +/* + clsLEAmDNS2_Host::_getNetIfState + + Returns the current netif state. + +*/ +clsLEAMDNSHost::typeNetIfState clsLEAMDNSHost::_getNetIfState(void) const +{ + typeNetIfState curNetIfState = static_cast(enuNetIfState::None); + + if ((m_pNetIf) && + (netif_is_up(m_pNetIf))) + { + curNetIfState |= static_cast(enuNetIfState::IsUp); + + // Check if netif link is up + if ((netif_is_link_up(m_pNetIf)) && + ((m_pNetIf != netif_get_by_index(WIFI_STA)) || + (STATION_GOT_IP == wifi_station_get_connect_status()))) + { + curNetIfState |= static_cast(enuNetIfState::LinkIsUp); + } + +#ifdef MDNS_IPV4_SUPPORT + // Check for IPv4 address + if (_getResponderIPAddress(enuIPProtocolType::V4).isSet()) + { + curNetIfState |= static_cast(enuNetIfState::IPv4); + } +#endif +#ifdef MDNS_IPV6_SUPPORT + // Check for IPv6 address + if (_getResponderIPAddress(enuIPProtocolType::V6).isSet()) + { + curNetIfState |= static_cast(enuNetIfState::IPv6); + } +#endif + } + return curNetIfState; +} + +/* + clsLEAmDNS2_Host::_checkNetIfState + + Checks the netif state. + If eg. a new address appears, the announcing is restarted. + +*/ +bool clsLEAMDNSHost::_checkNetIfState(void) +{ + typeNetIfState curNetIfState; + if (m_NetIfState != ((curNetIfState = _getNetIfState()))) + { + // Some state change happened + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: DID CHANGE NETIF STATE\n\n"), _DH());); + DEBUG_EX_INFO( + if ((curNetIfState & static_cast(enuNetIfState::IsUp)) != (m_NetIfState & static_cast(enuNetIfState::IsUp))) DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Netif is up: %s\n"), _DH(), ((curNetIfState & static_cast(enuNetIfState::IsUp)) ? "YES" : "NO")); + if ((curNetIfState & static_cast(enuNetIfState::LinkIsUp)) != (m_NetIfState & static_cast(enuNetIfState::LinkIsUp))) DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Netif link is up: %s\n"), _DH(), ((curNetIfState & static_cast(enuNetIfState::LinkIsUp)) ? "YES" : "NO")); +#ifdef MDNS_IPV4_SUPPORT + if ((curNetIfState & static_cast(enuNetIfState::IPv4)) != (m_NetIfState & static_cast(enuNetIfState::IPv4))) + { + DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IPv4 address is set: %s\n"), _DH(), ((curNetIfState & static_cast(enuNetIfState::IPv4)) ? "YES" : "NO")); + if (curNetIfState & static_cast(enuNetIfState::IPv4)) + { + DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IPv4 address: %s\n"), _DH(), _getResponderIPAddress(enuIPProtocolType::V4).toString().c_str()); + } + } +#endif +#ifdef MDNS_IPV6_SUPPORT + if ((curNetIfState & static_cast(enuNetIfState::IPv6)) != (m_NetIfState & static_cast(enuNetIfState::IPv6))) + { + DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IPv6 address is set: %s\n"), _DH(), ((curNetIfState & static_cast(enuNetIfState::IPv6)) ? "YES" : "NO")); + if (curNetIfState & static_cast(enuNetIfState::IPv6)) + { + DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IPv6 address: %s\n"), _DH(), _getResponderIPAddress(enuIPProtocolType::V6).toString().c_str()); + } + } +#endif + ); + if ((curNetIfState & static_cast(enuNetIfState::LinkMask)) != (m_NetIfState & static_cast(enuNetIfState::LinkMask))) + { + // Link came up or down + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Link state changed -> restarting\n"), _DH());); + restart(); + } + else if (curNetIfState & static_cast(enuNetIfState::LinkIsUp)) + { + // Link is up (unchanged) + if ((curNetIfState & static_cast(enuNetIfState::IPMask)) != (m_NetIfState & static_cast(enuNetIfState::IPMask))) + { + // IP state changed + // TODO: If just a new IP address was added, a simple re-announcement should be enough + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IP state changed -> restarting\n"), _DH());); + restart(); + } + } + /* if (enuProbingStatus::Done == m_HostProbeInformation.m_ProbingStatus) { + // Probing is done, prepare to (re)announce host + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Preparing to (re)announce host.\n"));); + //m_HostProbeInformation.m_ProbingStatus = enuProbingStatus::Done; + m_HostProbeInformation.m_u8SentCount = 0; + m_HostProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); + }*/ + m_NetIfState = curNetIfState; + } + + bool bResult = ((curNetIfState & static_cast(enuNetIfState::LinkMask)) && // Continue if Link is UP + (curNetIfState & static_cast(enuNetIfState::IPMask))); // AND has any IP + DEBUG_EX_INFO(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Link is DOWN(%s) or NO IP address(%s)!\n"), _DH(), (curNetIfState & static_cast(enuNetIfState::LinkMask) ? "NO" : "YES"), (curNetIfState & static_cast(enuNetIfState::IPMask) ? "NO" : "YES"));); + return bResult; +} + + +/* + PROCESSING +*/ + +/* + clsLEAmDNS2_Host::_processUDPInput +*/ +bool clsLEAMDNSHost::_processUDPInput(void) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput\n"), _DH());); + + bool bResult = _parseMessage(); + + /* bResult = ((_checkNetIfState()) && // Any changes in the netif state? + (_parseMessage()));*/ + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s processUDPInput: FAILED!\n"), _DH());); + + return bResult; +} + + +/* + DOMAIN NAMES +*/ + +/* + clsLEAmDNS2_Host::_allocDomainName +*/ +bool clsLEAMDNSHost::_allocDomainName(const char* p_pcNewDomainName, + char*& p_rpcDomainName) +{ + bool bResult = false; + + _releaseDomainName(p_rpcDomainName); + + size_t stLength = 0; + if ((p_pcNewDomainName) && + (clsConsts::stDomainLabelMaxLength >= (stLength = strlen(p_pcNewDomainName)))) // char max size for a single label + { + // Copy in hostname characters as lowercase + if ((bResult = (0 != (p_rpcDomainName = new char[stLength + 1])))) + { +#ifdef MDNS_FORCE_LOWERCASE_HOSTNAME + size_t i = 0; + for (; i < stLength; ++i) + { + p_rpcDomainName[i] = (isupper(p_pcNewDomainName[i]) ? tolower(p_pcNewDomainName[i]) : p_pcNewDomainName[i]); + } + p_rpcDomainName[i] = 0; +#else + strncpy(p_rpcDomainName, p_pcNewDomainName, (stLength + 1)); +#endif + } + } + return bResult; +} + +/* + clsLEAmDNS2_Host::_releaseDomainName +*/ +bool clsLEAMDNSHost::_releaseDomainName(char*& p_rpcDomainName) +{ + bool bResult; + if ((bResult = (0 != p_rpcDomainName))) + { + delete[] p_rpcDomainName; + p_rpcDomainName = 0; + } + return bResult; +} + +/* + clsLEAmDNS2_Host::_allocHostName +*/ +bool clsLEAMDNSHost::_allocHostName(const char* p_pcHostName) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocHostName (%s)\n"), _DH(), p_pcHostName);); + return _allocDomainName(p_pcHostName, m_pcHostName); +} + +/* + clsLEAmDNS2_Host::_releaseHostName +*/ +bool clsLEAMDNSHost::_releaseHostName(void) +{ + return _releaseDomainName(m_pcHostName); +} + +/* + clsLEAmDNS2_Host::_allocDefaultInstanceName +*/ +bool clsLEAMDNSHost::_allocDefaultInstanceName(const char* p_pcDefaultInstanceName) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocDefaultInstanceName (%s)\n"), _DH(), p_pcDefaultInstanceName);); + return _allocDomainName(p_pcDefaultInstanceName, m_pcDefaultInstanceName); +} + +/* + clsLEAmDNS2_Host::_releaseDefaultInstanceName +*/ +bool clsLEAMDNSHost::_releaseDefaultInstanceName(void) +{ + return _releaseDomainName(m_pcDefaultInstanceName); +} + +/* + clsLEAmDNS2_Host::_instanceName +*/ +const char* clsLEAMDNSHost::_instanceName(const char* p_pcInstanceName, + bool p_bReturnZero /*= true*/) const +{ + return (p_pcInstanceName ? : (m_pcDefaultInstanceName ? : (m_pcHostName ? : (p_bReturnZero ? 0 : "-")))); +} + + +/* + SERVICE TXT +*/ + +/* + clsLEAmDNS2_Host::_collectServiceTxts +*/ +bool clsLEAMDNSHost::_collectServiceTxts(clsLEAMDNSHost::clsService& p_rService) +{ + if (p_rService.m_fnTxtCallback) + { + p_rService.m_fnTxtCallback(p_rService); + } + return true; +} + +/* + clsLEAmDNS2_Host::_releaseTempServiceTxts +*/ +bool clsLEAMDNSHost::_releaseTempServiceTxts(clsLEAMDNSHost::clsService& p_rService) +{ + return (p_rService.m_Txts.removeTempTxts()); +} + + +/* + + QUERIES + +*/ + +/* + MDNSResponder::_allocQuery + +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_allocQuery(clsLEAMDNSHost::clsQuery::enuQueryType p_QueryType) +{ + clsQuery* pQuery = new clsQuery(p_QueryType); + if (pQuery) + { + // Link to query list + m_Queries.push_back(pQuery); + } + return pQuery; +} + +/* + MDNSResponder:clsHost:::_removeQuery + +*/ +bool clsLEAMDNSHost::_removeQuery(clsLEAMDNSHost::clsQuery* p_pQuery) +{ + bool bResult = false; + + clsQuery::list::iterator it(p_pQuery + ? std::find(m_Queries.begin(), m_Queries.end(), p_pQuery) + : m_Queries.end()); + if (m_Queries.end() != it) + { + m_Queries.erase(it); + delete p_pQuery; + + bResult = true; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _releaseQuery: INVALID query!"), _DH());); + } + return bResult; +} + +/* + clsLEAmDNS2_Host::_removeLegacyQuery + +*/ +bool clsLEAMDNSHost::_removeLegacyQuery(void) +{ + clsQuery* pLegacyQuery = 0; + return (((pLegacyQuery = _findLegacyQuery())) + ? _removeQuery(pLegacyQuery) + : true); +} + +/* + clsLEAmDNS2_Host::_findLegacyQuery + +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_findLegacyQuery(void) +{ + clsQuery* pLegacyQuery = 0; + + for (clsQuery* pQuery : m_Queries) + { + if (pQuery->m_bStaticQuery) + { + pLegacyQuery = pQuery; + break; + } + } + return pLegacyQuery; +} + +/* + clsLEAmDNS2_Host::_releaseQueries + +*/ +bool clsLEAMDNSHost::_releaseQueries(void) +{ + for (clsQuery* pQuery : m_Queries) + { + delete pQuery; + } + m_Queries.clear(); + return true; +} + +/* + clsLEAmDNS2_Host::_findNextQueryByDomain + +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_findNextQueryByDomain(const clsLEAMDNSHost::clsRRDomain& p_Domain, + const clsLEAMDNSHost::clsQuery::enuQueryType p_QueryType, + const clsQuery* p_pPrevQuery) +{ + clsQuery* pMatchingQuery = 0; + + clsQuery::list::iterator it(m_Queries.begin()); + if (p_pPrevQuery) + { + if (m_Queries.end() != ((it = std::find(m_Queries.begin(), m_Queries.end(), p_pPrevQuery)))) + { // Found previous object + it++; + } + DEBUG_EX_ERR(else DEBUG_OUTPUT.printf_P(PSTR("%s _findNextQueryByDomain: FAILED to find 'previous' object!\n"), _DH());); // if not prev was found -> 'cancel' + } + + for (; it != m_Queries.end(); it++) + { + if (((clsQuery::enuQueryType::None == p_QueryType) || + ((*it)->m_QueryType == p_QueryType)) && + (p_Domain == (*it)->m_Domain)) + { + pMatchingQuery = *it; + break; + } + } + return pMatchingQuery; +} + +/* + clsLEAmDNS2_Host::_installServiceQuery + +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installServiceQuery(const char* p_pcService, + const char* p_pcProtocol) +{ + clsQuery* pMDNSQuery = 0; + + if ((p_pcService) && (*p_pcService) && + (p_pcProtocol) && (*p_pcProtocol) && + ((pMDNSQuery = _allocQuery(clsQuery::enuQueryType::Service))) && + (_buildDomainForService(p_pcService, p_pcProtocol, pMDNSQuery->m_Domain))) + { + pMDNSQuery->m_bStaticQuery = false; + + if (_sendQuery(*pMDNSQuery)) + { + pMDNSQuery->m_u8SentCount = 1; + pMDNSQuery->m_ResendTimeout.reset(clsConsts::u32DynamicQueryResendDelay); + } + else + { + _removeQuery(pMDNSQuery); + } + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _installServiceQuery: %s for '_%s._%s.local'!\n\n"), _DH(), (pMDNSQuery ? "Succeeded" : "FAILED"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); + DEBUG_EX_ERR(if (!pMDNSQuery) DEBUG_OUTPUT.printf_P(PSTR("%s _installServiceQuery: FAILED for '_%s._%s.local'!\n\n"), _DH(), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); + return pMDNSQuery; +} + +/* + clsLEAmDNS2_Host::_installDomainQuery +*/ +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installDomainQuery(clsLEAMDNSHost::clsRRDomain& p_Domain, + clsLEAMDNSHost::clsQuery::enuQueryType p_QueryType) +{ + clsQuery* pQuery = 0; + + if ((pQuery = _allocQuery(p_QueryType))) + { + pQuery->m_Domain = p_Domain; + pQuery->m_bStaticQuery = false; + + if (_sendQuery(*pQuery)) + { + pQuery->m_u8SentCount = 1; + pQuery->m_ResendTimeout.reset(clsConsts::u32DynamicQueryResendDelay); + } + else + { + _removeQuery(pQuery); + } + } + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("%s _installDomainQuery: %s for "), (pQuery ? "Succeeded" : "FAILED"), _DH()); + _printRRDomain(p_Domain); + DEBUG_OUTPUT.println(); + ); + DEBUG_EX_ERR(if (!pQuery) +{ + DEBUG_OUTPUT.printf_P(PSTR("%s _installDomainQuery: FAILED for "), _DH()); + _printRRDomain(p_Domain); + DEBUG_OUTPUT.println(); + } + ); + return pQuery; +} + +/* + clsLEAmDNS2_Host::_hasQueriesWaitingForAnswers +*/ +bool clsLEAMDNSHost::_hasQueriesWaitingForAnswers(void) const +{ + bool bOpenQueries = false; + + for (const clsQuery* pQuery : m_Queries) + { + if (pQuery->m_bAwaitingAnswers) + { + bOpenQueries = true; + break; + } + } + return bOpenQueries; +} + +/* + clsLEAmDNS2_Host::_executeQueryCallback +*/ +bool clsLEAMDNSHost::_executeQueryCallback(const clsQuery& p_Query, + const clsQuery::clsAnswer& p_Answer, + clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, + bool p_bSetContent) +{ + if (p_Query.m_fnCallbackAnswer) + { + p_Query.m_fnCallbackAnswer(p_Query, p_Answer, p_QueryAnswerTypeFlags, p_bSetContent); + } + if (p_Query.m_fnCallbackAccessor) + { + p_Query.m_fnCallbackAccessor(p_Query, clsQuery::clsAnswerAccessor(&p_Answer), p_QueryAnswerTypeFlags, p_bSetContent); + } + return true; +} + + +} // namespace MDNSImplementation + + +} // namespace esp8266 + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h new file mode 100644 index 0000000000..5a63d1f59e --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -0,0 +1,1658 @@ +/* + LEAmDNS2Host.h + (c) 2020, LaborEtArs + + Version 0.9 beta + + Some notes (from LaborEtArs, 2020): + + Supported mDNS features (in some cases somewhat limited): + - Announcing a DNS-SD service to interested observers, eg. a http server by announcing a esp8266._http._tcp.local. service + - Support for multi-level compressed names in input; in output only a very simple one-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 hosts or DNS-SD services (creating a fixed answer set after a certain timeout period) + - Dynamic queries for hosts or DNS-SD services with cached and updated answers and user notifications + + + Usage: + A LEAmDNS2Host is attached to an existing netif (Network Interface). + If more than one netif is used (eg. in WIFI_AP_STA mode) and mDNS support is needed, every used netif needs its own LEAmDNS2Host! + + For presenting services: + In 'setup()': + Create an clsLEAMDNSHost instance for every netif you plan to use. + Call 'begin' on every instance with the intended hostname and the associated netif (or WiFi mode, WIFI_STA). + The given hostname is the 'probed' for uniqueness in the netifs local link. If domain name conflicts occure, the host name + will be automatically changed until it is unique in the local link. + Optionally a callback can be registered in 'begin', to control the probing process manually. + Next you can register DNS-SD services with 'addService("MyESP", "http", "tcp", 5000)' + All added service domain names are also probed for uniqueness and updated if needed. + Optionally a 'probe result' callback can be given for every service in 'addService', too. + + Finally you can add service TXT items with 'pService->addServiceTxt("c#", "1")' or by installing a service TXT callback + using 'pService->setDynamicServiceTxtCallback()' and calling 'pService->addDynamicServiceTxt("c#", "1")' inside. + + In 'loop()': + Call 'update()' for every clsLEAmDNS_Host instance. + + For querying services/hosts: + Static: + Call 'queryService("http", "tcp")' or 'queryHost("esp8266")'; + You should call MDNS.removeQuery() sometimes later (when the answers are not needed anymore) + + Dynamic: + Install a dynamic service query by calling 'installService/HostQuery("http", "tcp", serviceQueryCallback);' + The callback is called for any change in the answer set. + Call 'MDNS.removeQuery(pQuery)' when the answers are not needed anymore + + + Reference: + Used mDNS messages: + A (0x01): eg. esp8266.local A OP TTL 123.456.789.012 + AAAA (0x1C): eg. esp8266.local AAAA OP TTL 1234:5678::90 + PTR (0x0C, srv name): eg. _http._tcp.local PTR OP TTL MyESP._http._tcp.local + PTR (0x0C, srv type): eg. _services._dns-sd._udp.local PTR OP TTL _http._tcp.local + PTR (0x0C, IPv4): eg. 012.789.456.123.in-addr.arpa PTR OP TTL esp8266.local + PTR (0x0C, IPv6): eg. 90.0.0.0.0.0.0.0.0.0.0.0.78.56.34.12.ip6.arpa PTR OP TTL esp8266.local + SRV (0x21): eg. MyESP._http._tcp.local SRV OP TTL PRIORITY WEIGHT PORT esp8266.local + TXT (0x10): eg. MyESP._http._tcp.local TXT OP TTL c#=1 + NSEC (0x2F): eg. esp8266.local ... (DNSSEC) + + Some NOT used message types: + OPT (0x29): eDNS + + + License (MIT license): + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#ifndef __LEAMDNS2HOST_H__ +#define __LEAMDNS2HOST_H__ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "LEAmDNS2_lwIPdefs.h" + + +#define MDNS_IPV4_SUPPORT +#if LWIP_IPV6 +#define MDNS_IPV6_SUPPORT // If we've got IPv6 support, then we need IPv6 support :-) +#endif + +/* + LWIP_OPEN_SRC +*/ +#ifndef LWIP_OPEN_SRC +#define LWIP_OPEN_SRC +#endif + +/* + Enable class debug functions +*/ +#define ESP_8266_MDNS_INCLUDE +#define DEBUG_ESP_MDNS_RESPONDER + +/* + Enable/disable debug trace macros +*/ +#ifdef DEBUG_ESP_MDNS_RESPONDER +//#define DEBUG_ESP_MDNS_INFO +//#define DEBUG_ESP_MDNS_INFO2 +#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_INFO2 +#define DEBUG_EX_INFO2(A) A +#else +#define DEBUG_EX_INFO2(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_INFO2(A) +#define DEBUG_EX_ERR(A) +#define DEBUG_EX_TX(A) +#define DEBUG_EX_RX(A) +#endif + +/* + Enable/disable the usage of the F() macro in debug trace printf calls. + There needs to be an PGM comptible printf function to use this. + + USE_PGM_PRINTF and F +*/ +#define USE_PGM_PRINTF + +#ifdef USE_PGM_PRINTF +#else +#ifdef F +#undef F +#endif +#define F(A) A +#endif + + +namespace esp8266 +{ + + +namespace experimental +{ + + +/** + clsLEAMDNSHost +*/ +class clsLEAMDNSHost +{ +protected: + /* + clsConsts + */ + class clsConsts + { + public: +#ifdef MDNS_IPV4_SUPPORT + static const uint16_t u16IPv4Size = 4; // IPv4 address size in bytes +#endif +#ifdef MDNS_IPV6_SUPPORT + static const uint16_t u16IPv6Size = 16; // IPv6 address size in bytes +#endif + static const size_t stServiceTxtMaxLength = 1300; // Maximum length for all service txts for one service + static const size_t stDomainMaxLength = 256; // Maximum length for a full domain name eg. MyESP._http._tcp.local + static const size_t stDomainLabelMaxLength = 63; // Maximum length of on label in a domain name (length info fits into 6 bits) + static const size_t stServiceTypeMaxLength = 15; // Maximum length of a service name eg. http + static const size_t stServiceProtocolMaxLength = 3; // Maximum length of a service protocol name eg. tcp + + static const uint32_t u32LegacyTTL = 10; // Legacy DNS record TTL + static const uint32_t u32HostTTL = 120; // Host level records are set to 2min (120s) + static const uint32_t u32ServiceTTL = 4500; // Service level records are set to 75min (4500s) + + static const uint16_t u16SRVPriority = 0; // Default service priority and weight in SRV answers + static const uint16_t u16SRVWeight = 0; // + static const uint8_t u8DomainCompressMark = 0xC0; // Compressed labels are flaged by the two topmost bits of the length byte being set + static const uint8_t u8DomainMaxRedirections = 6; // Avoid endless recursion because of malformed compressed labels + + static const uint32_t u32ProbeDelay = 1000; // Default 250, but ESP is slow...; delay between and number of probes for host and service domains + static const uint32_t u32ProbeCount = 3; + static const uint32_t u32AnnounceDelay = 1000; // Delay between and number of announces for host and service domains + static const uint32_t u32AnnounceCount = 3; + static const uint32_t u32DynamicQueryResendDelay = 1000; // Delay between and number of queries; the delay is multiplied by the resent number in '_checkQueryCache' + + static const char* pcLocal; // "local"; + static const char* pcServices; // "services"; + static const char* pcDNSSD; // "dns-sd"; + static const char* pcUDP; // "udp"; + //static const char* pcTCP; // "tcp"; + +#ifdef MDNS_IPV4_SUPPORT + static const char* pcReverseIPv4Domain; // "in-addr"; +#endif +#ifdef MDNS_IPV6_SUPPORT + static const char* pcReverseIPv6Domain; // "ip6"; +#endif + static const char* pcReverseTopDomain; // "arpa"; + +#ifdef DNS_RRTYPE_NSEC + static const uint8_t u8DNS_RRTYPE_NSEC = DNS_RRTYPE_NSEC; +#else + static const uint8_t u8DNS_RRTYPE_NSEC = 0x2F; +#endif + static const uint32_t u32SendCooldown = 50; // Delay (ms) between to 'UDPContext->send()' calls + + }; + + /** + list + */ + using list = std::list; + + // File: ..._Backbone + /** + clsBackbone + */ + class clsBackbone + { + public: + static clsBackbone* sm_pBackbone; + clsBackbone(void); + ~clsBackbone(void); + + bool init(void); + + UdpContext* addHost(clsLEAMDNSHost* p_pHost); + bool removeHost(clsLEAMDNSHost* p_pHost); + size_t hostCount(void) const; + bool setDelayUDPProcessing(bool p_bDelayProcessing); + + protected: + UdpContext* m_pUDPContext; + bool m_bDelayUDPProcessing; + uint32_t m_u32DelayedDatagrams; + list m_HostList; + + bool _allocUDPContext(void); + bool _releaseUDPContext(void); + + bool _processUDPInput(void); + + const clsLEAMDNSHost* _findHost(netif* p_pNetIf) const; + clsLEAMDNSHost* _findHost(netif* p_pNetIf); + + const char* _DH(void) const; + }; + + + // File: ..._Host_Structs + /** + typeIPProtocolType & enuIPProtocolType + */ + using typeIPProtocolType = uint8_t; + enum class enuIPProtocolType : typeIPProtocolType + { +#ifdef MDNS_IPV4_SUPPORT + V4 = 0x01, +#endif +#ifdef MDNS_IPV6_SUPPORT + V6 = 0x02, +#endif + }; + + /** + typeNetIfState & enuNetIfState + */ + using typeNetIfState = uint8_t; + enum class enuNetIfState : typeNetIfState + { + None = 0x00, + + IsUp = 0x01, + UpMask = (IsUp), + + LinkIsUp = 0x02, + LinkMask = (LinkIsUp), + + IPv4 = 0x04, + IPv6 = 0x08, + IPMask = (IPv4 | IPv6), + }; + +public: + /** + clsServiceTxt + */ + class clsServiceTxt + { + public: + char* m_pcKey; + char* m_pcValue; + bool m_bTemp; + + clsServiceTxt(const char* p_pcKey = 0, + const char* p_pcValue = 0, + bool p_bTemp = false); + clsServiceTxt(const clsServiceTxt& p_Other); + ~clsServiceTxt(void); + + clsServiceTxt& operator=(const clsServiceTxt& p_Other); + bool clear(void); + + char* allocKey(size_t p_stLength); + bool setKey(const char* p_pcKey, + size_t p_stLength); + bool setKey(const char* p_pcKey); + bool releaseKey(void); + + char* allocValue(size_t p_stLength); + bool setValue(const char* p_pcValue, + size_t p_stLength); + bool setValue(const char* p_pcValue); + bool releaseValue(void); + + bool set(const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp = false); + + bool update(const char* p_pcValue); + + size_t length(void) const; + + /** + list + */ + using list = std::list; + }; + + /** + clsServiceTxts + */ + class clsServiceTxts + { + public: + clsServiceTxt::list m_Txts; + char* m_pcCache; + + clsServiceTxts(void); + clsServiceTxts(const clsServiceTxts& p_Other); + ~clsServiceTxts(void); + + clsServiceTxts& operator=(const clsServiceTxts& p_Other); + + bool clear(void); + bool clearCache(void); + + bool add(clsServiceTxt* p_pTxt); + bool remove(clsServiceTxt* p_pTxt); + + size_t count(void) const; + + bool removeTempTxts(void); + + clsServiceTxt* find(const char* p_pcKey); + const clsServiceTxt* find(const char* p_pcKey) const; + clsServiceTxt* find(const clsServiceTxt* p_pTxt); + + size_t length(void) const; + + size_t c_strLength(void) const; + bool c_str(char* p_pcBuffer); + const char* c_str(void) const; + + size_t bufferLength(void) const; + bool buffer(char* p_pcBuffer); + + bool compare(const clsServiceTxts& p_Other) const; + bool operator==(const clsServiceTxts& p_Other) const; + bool operator!=(const clsServiceTxts& p_Other) const; + }; + +protected: + /** + clsProbeInformation_Base + */ + class clsProbeInformation_Base + { + public: + /** + typeProbingStatus & enuProbingStatus + */ + using typeProbingStatus = uint8_t; + enum class enuProbingStatus : typeProbingStatus + { + WaitingForData, + ReadyToStart, + InProgress, + ReadyToAnnounce, + DoneFinally + }; + + enuProbingStatus m_ProbingStatus; + uint8_t m_u8SentCount; // Used for probes and announcements + esp8266::polledTimeout::oneShot m_Timeout; // Used for probes and announcements + bool m_bConflict; + bool m_bTiebreakNeeded; + + clsProbeInformation_Base(void); + + bool clear(void); // No 'virtual' needed, no polymorphic use (save 4 bytes) + }; + +public: + /** + fnProbeResultCallback + Callback function for host domain probe results + */ + using fnProbeResultCallback = std::function; + +protected: + /** + clsProbeInformation + */ + class clsProbeInformation : public clsProbeInformation_Base + { + public: + fnProbeResultCallback m_fnProbeResultCallback; + + clsProbeInformation(void); + + bool clear(bool p_bClearUserdata = false); + }; + +public: + /** + clsService + */ + struct clsService + { + public: + /** + fnDynamicServiceTxtCallback + */ + using fnDynamicServiceTxtCallback = std::function; + + /** + fnProbeResultCallback + */ + using fnProbeResultCallback = std::function; + + protected: + friend clsLEAMDNSHost; + /** + clsProbeInformation + */ + class clsProbeInformation : public clsProbeInformation_Base + { + public: + fnProbeResultCallback m_fnProbeResultCallback; + + clsProbeInformation(void); + + bool clear(bool p_bClearUserdata = false); + }; + + char* m_pcInstanceName; + bool m_bAutoName; // Name was set automatically to hostname (if no name was supplied) + char* m_pcType; + char* m_pcProtocol; + uint16_t m_u16Port; + uint32_t m_u32ReplyMask; + clsServiceTxts m_Txts; + fnDynamicServiceTxtCallback m_fnTxtCallback; + clsProbeInformation m_ProbeInformation; + + clsService(void); + ~clsService(void); + + bool _releaseInstanceName(void); + bool _releaseType(void); + bool _releaseProtocol(void); + + void _resetProbeStatus(void); + + clsServiceTxt* _addServiceTxt(const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp); + clsServiceTxt* _addServiceTxt(const char* p_pcKey, + uint32_t p_u32Value, + bool p_bTemp); + clsServiceTxt* _addServiceTxt(const char* p_pcKey, + uint16_t p_u16Value, + bool p_bTemp); + clsServiceTxt* _addServiceTxt(const char* p_pcKey, + uint8_t p_u8Value, + bool p_bTemp); + clsServiceTxt* _addServiceTxt(const char* p_pcKey, + int32_t p_i32Value, + bool p_bTemp); + clsServiceTxt* _addServiceTxt(const char* p_pcKey, + int16_t p_i16Value, + bool p_bTemp); + clsServiceTxt* _addServiceTxt(const char* p_pcKey, + int8_t p_i8Value, + bool p_bTemp); + + public: + bool setInstanceName(const char* p_pcInstanceName); + bool indexInstanceName(void); + const char* instanceName(void) const; + + bool setType(const char* p_pcType); + const char* type(void) const; + + bool setProtocol(const char* p_pcProtocol); + const char* protocol(void) const; + + bool setPort(uint16_t p_i16Port); + uint16_t port(void) const; + + bool setProbeResultCallback(fnProbeResultCallback p_fnProbeResultCallback); + bool probeStatus(void) const; + + // TXT + // Add a (static) MDNS TXT item ('key' = 'value') to the service + clsServiceTxt* addServiceTxt(const char* p_pcKey, + const char* p_pcValue); + clsServiceTxt* addServiceTxt(const char* p_pcKey, + uint32_t p_u32Value); + clsServiceTxt* addServiceTxt(const char* p_pcKey, + uint16_t p_u16Value); + clsServiceTxt* addServiceTxt(const char* p_pcKey, + uint8_t p_u8Value); + clsServiceTxt* addServiceTxt(const char* p_pcKey, + int32_t p_i32Value); + clsServiceTxt* addServiceTxt(const char* p_pcKey, + int16_t p_i16Value); + clsServiceTxt* addServiceTxt(const char* p_pcKey, + int8_t p_i8Value); + + bool removeServiceTxt(const char* p_pcKey); + bool removeServiceTxt(clsServiceTxt* p_pTxt); + const clsServiceTxt* findServiceTxt(const char* p_pcKey) const; + clsServiceTxt* findServiceTxt(const char* p_pcKey); + + // Add a (dynamic) MDNS TXT item ('key' = 'value') to the service + // Dynamic TXT items are removed right after one-time use. So they need to be added + // every time the value s needed (via callback). + clsServiceTxt* addDynamicServiceTxt(const char* p_pcKey, + const char* p_pcValue); + clsServiceTxt* addDynamicServiceTxt(const char* p_pcKey, + uint32_t p_u32Value); + clsServiceTxt* addDynamicServiceTxt(const char* p_pcKey, + uint16_t p_u16Value); + clsServiceTxt* addDynamicServiceTxt(const char* p_pcKey, + uint8_t p_u8Value); + clsServiceTxt* addDynamicServiceTxt(const char* p_pcKey, + int32_t p_i32Value); + clsServiceTxt* addDynamicServiceTxt(const char* p_pcKey, + int16_t p_i16Value); + clsServiceTxt* addDynamicServiceTxt(const char* p_pcKey, + int8_t p_i8Value); + + bool setDynamicServiceTxtCallback(fnDynamicServiceTxtCallback p_fnCallback); + + /** + list + */ + using list = std::list; + }; + +protected: + /** + typeContentFlag & enuContentFlag + */ + using typeContentFlag = uint16_t; + enum class enuContentFlag : typeContentFlag + { + // Host + A = 0x0001, + PTR_IPv4 = 0x0002, + PTR_IPv6 = 0x0004, + AAAA = 0x0008, + // Service + PTR_TYPE = 0x0010, + PTR_NAME = 0x0020, + TXT = 0x0040, + SRV = 0x0080, + // DNSSEC + NSEC = 0x0100, + + PTR = (PTR_IPv4 | PTR_IPv6 | PTR_TYPE | PTR_NAME) + }; + + /** + clsMsgHeader + */ + class clsMsgHeader + { + public: + uint16_t m_u16ID; // Identifier + bool m_1bQR : 1; // Query/Response flag + uint8_t m_4bOpcode : 4; // Operation code + bool m_1bAA : 1; // Authoritative Answer flag + bool m_1bTC : 1; // Truncation flag + bool m_1bRD : 1; // Recursion desired + bool m_1bRA : 1; // Recursion available + uint8_t m_3bZ : 3; // Zero + uint8_t m_4bRCode : 4; // Response code + uint16_t m_u16QDCount; // Question count + uint16_t m_u16ANCount; // Answer count + uint16_t m_u16NSCount; // Authority Record count + uint16_t m_u16ARCount; // Additional Record count + + clsMsgHeader(uint16_t p_u16ID = 0, + bool p_bQR = false, + uint8_t p_u8Opcode = 0, + bool p_bAA = false, + bool p_bTC = false, + bool p_bRD = false, + bool p_bRA = false, + uint8_t p_u8RCode = 0, + uint16_t p_u16QDCount = 0, + uint16_t p_u16ANCount = 0, + uint16_t p_u16NSCount = 0, + uint16_t p_u16ARCount = 0); + }; + + /** + clsRRDomain + */ + class clsRRDomain + { + public: + char m_acName[clsConsts::stDomainMaxLength]; // Encoded domain name + uint16_t m_u16NameLength; // Length (incl. '\0') + char* m_pcDecodedName; + + clsRRDomain(void); + clsRRDomain(const clsRRDomain& p_Other); + ~clsRRDomain(void); + + clsRRDomain& operator=(const clsRRDomain& p_Other); + + bool clear(void); + bool clearNameCache(void); + + bool addLabel(const char* p_pcLabel, + bool p_bPrependUnderline = false); + + bool compare(const clsRRDomain& p_Other) const; + bool operator==(const clsRRDomain& p_Other) const; + bool operator!=(const clsRRDomain& p_Other) const; + bool operator>(const clsRRDomain& p_Other) const; + + size_t c_strLength(void) const; + bool c_str(char* p_pcBuffer) const; + const char* c_str(void) const; + }; + + /** + clsRRAttributes + */ + class clsRRAttributes + { + public: + uint16_t m_u16Type; // Type + uint16_t m_u16Class; // Class, nearly always 'IN' + + clsRRAttributes(uint16_t p_u16Type = 0, + uint16_t p_u16Class = 1 /*DNS_RRCLASS_IN Internet*/); + clsRRAttributes(const clsRRAttributes& p_Other); + + clsRRAttributes& operator=(const clsRRAttributes& p_Other); + }; + + /** + clsRRHeader + */ + class clsRRHeader + { + public: + clsRRDomain m_Domain; + clsRRAttributes m_Attributes; + + clsRRHeader(void); + clsRRHeader(const clsRRHeader& p_Other); + + clsRRHeader& operator=(const clsRRHeader& p_Other); + + bool clear(void); + }; + + /** + clsRRQuestion + */ + struct clsRRQuestion + { + clsRRHeader m_Header; + bool m_bUnicast; // Unicast reply requested + + /** + list + */ + using list = std::list; + + clsRRQuestion(void); + }; + + /** + clsNSECBitmap + */ + class clsNSECBitmap + { + public: + uint8_t m_au8BitmapData[6]; // 6 bytes data + + clsNSECBitmap(void); + + bool clear(void); + uint16_t length(void) const; + bool setBit(uint16_t p_u16Bit); + bool getBit(uint16_t p_u16Bit) const; + }; + + /** + typeAnswerType & enuAnswerType + */ + using typeAnswerType = uint8_t; + enum class enuAnswerType : typeAnswerType + { + A, + PTR, + TXT, + AAAA, + SRV, + //NSEC, + Generic + }; + + /** + clsRRAnswer + */ + struct clsRRAnswer + { + clsRRAnswer* m_pNext; + const enuAnswerType m_AnswerType; + clsRRHeader m_Header; + bool m_bCacheFlush; // Cache flush command bit + uint32_t m_u32TTL; // Validity time in seconds + + virtual ~clsRRAnswer(void); + + enuAnswerType answerType(void) const; + + bool clear(void); + + protected: + clsRRAnswer(enuAnswerType p_AnswerType, + const clsRRHeader& p_Header, + uint32_t p_u32TTL); + }; + +#ifdef MDNS_IPV4_SUPPORT + /** + clsRRAnswerA + */ + class clsRRAnswerA : public clsRRAnswer + { + public: + IPAddress m_IPAddress; + + clsRRAnswerA(const clsRRHeader& p_Header, + uint32_t p_u32TTL); + ~clsRRAnswerA(void); + + bool clear(void); + }; +#endif + + /** + clsRRAnswerPTR + */ + class clsRRAnswerPTR : public clsRRAnswer + { + public: + clsRRDomain m_PTRDomain; + + clsRRAnswerPTR(const clsRRHeader& p_Header, + uint32_t p_u32TTL); + ~clsRRAnswerPTR(void); + + bool clear(void); + }; + + /** + clsRRAnswerTXT + */ + class clsRRAnswerTXT : public clsRRAnswer + { + public: + clsServiceTxts m_Txts; + + clsRRAnswerTXT(const clsRRHeader& p_Header, + uint32_t p_u32TTL); + ~clsRRAnswerTXT(void); + + bool clear(void); + }; + +#ifdef MDNS_IPV6_SUPPORT + /** + clsRRAnswerAAAA + */ + class clsRRAnswerAAAA : public clsRRAnswer + { + public: + IPAddress m_IPAddress; + + clsRRAnswerAAAA(const clsRRHeader& p_Header, + uint32_t p_u32TTL); + ~clsRRAnswerAAAA(void); + + bool clear(void); + }; +#endif + + /** + clsRRAnswerSRV + */ + class clsRRAnswerSRV : public clsRRAnswer + { + public: + uint16_t m_u16Priority; + uint16_t m_u16Weight; + uint16_t m_u16Port; + clsRRDomain m_SRVDomain; + + clsRRAnswerSRV(const clsRRHeader& p_Header, + uint32_t p_u32TTL); + ~clsRRAnswerSRV(void); + + bool clear(void); + }; + + /** + clsRRAnswerGeneric + */ + class clsRRAnswerGeneric : public clsRRAnswer + { + public: + uint16_t m_u16RDLength; // Length of variable answer + uint8_t* m_pu8RDData; // Offset of start of variable answer in packet + + clsRRAnswerGeneric(const clsRRHeader& p_Header, + uint32_t p_u32TTL); + ~clsRRAnswerGeneric(void); + + bool clear(void); + }; + + + /** + clsSendParameter + */ + class clsSendParameter + { + protected: + /** + clsDomainCacheItem + */ + class clsDomainCacheItem + { + public: + const void* m_pHostNameOrService; // Opaque id for host or service domain (pointer) + bool m_bAdditionalData; // Opaque flag for special info (service domain included) + uint16_t m_u16Offset; // Offset in UDP output buffer + + /** + list + */ + using list = std::list; + + clsDomainCacheItem(const void* p_pHostNameOrService, + bool p_bAdditionalData, + uint32_t p_u16Offset); + }; + + public: + /** + typeResponseType & enuResponseType + */ + using typeResponseType = uint8_t; + enum class enuResponseType : typeResponseType + { + None, + Response, + Unsolicited + }; + + uint16_t m_u16ID; // Query ID (used only in lagacy queries) + clsRRQuestion::list m_RRQuestions; // A list of queries + uint32_t m_u32HostReplyMask; // Flags for reply components/answers + bool m_bLegacyDNSQuery; // Flag: Legacy query + enuResponseType m_Response; // Enum: Response to a query + bool m_bAuthorative; // Flag: Authorative (owner) response + bool m_bCacheFlush; // Flag: Clients should flush their caches + bool m_bUnicast; // Flag: Unicast response + bool m_bUnannounce; // Flag: Unannounce service + + // Temp content; created while processing _prepareMessage + uint16_t m_u16Offset; // Current offset in UDP write buffer (mainly for domain cache) + clsDomainCacheItem::list m_DomainCacheItems; // Cached host and service domains + + clsSendParameter(void); + ~clsSendParameter(void); + + bool clear(void); + bool flushQuestions(void); + bool flushDomainCache(void); + bool flushTempContent(void); + + bool shiftOffset(uint16_t p_u16Shift); + + bool addDomainCacheItem(const void* p_pHostNameOrService, + bool p_bAdditionalData, + uint16_t p_u16Offset); + uint16_t findCachedDomainOffset(const void* p_pHostNameOrService, + bool p_bAdditionalData) const; + }; + +public: + // QUERIES & ANSWERS + + /** + clsQuery + */ + class clsQuery + { + public: + /** + clsAnswer + */ + class clsAnswer + { + public: + /** + typeQueryAnswerType & enuQueryAnswerType + */ + using typeQueryAnswerType = uint8_t; + enum class enuQueryAnswerType : typeQueryAnswerType + { + Unknown = 0x00, + ServiceDomain = 0x01, // Service domain + HostDomain = 0x02, // Host domain + Port = 0x04, // Port + Txts = 0x08, // TXT items +#ifdef MDNS_IPV4_SUPPORT + IPv4Address = 0x10, // IPv4 address +#endif +#ifdef MDNS_IPV6_SUPPORT + IPv6Address = 0x20, // IPv6 address +#endif + }; + + /** + stcTTL + */ + class clsTTL + { + public: + /** + typeTimeoutLevel & enuTimeoutLevel + */ + using typeTimeoutLevel = uint8_t; + enum class enuTimeoutLevel : typeTimeoutLevel + { + None = 0, + Base = 80, + Interval = 5, + Final = 100 + }; + + uint32_t m_u32TTL; + esp8266::polledTimeout::oneShot m_TTLTimeout; + typeTimeoutLevel m_TimeoutLevel; + + clsTTL(void); + bool set(uint32_t p_u32TTL); + + bool flagged(void) const; + bool restart(void); + + bool prepareDeletion(void); + bool finalTimeoutLevel(void) const; + + unsigned long timeout(void) const; + }; + + /** + clsIPAddressWithTTL + */ + class clsIPAddressWithTTL + { + public: + IPAddress m_IPAddress; + clsTTL m_TTL; + + /** + list + */ + using list = std::list; + + clsIPAddressWithTTL(IPAddress p_IPAddress, + uint32_t p_u32TTL = 0); + }; + + // The service domain is the first 'answer' (from PTR answer, using service and protocol) to be set + // Defines the key for additional answer, like host domain, etc. + clsRRDomain m_ServiceDomain; // 1. level answer (PTR), eg. MyESP._http._tcp.local + clsTTL m_TTLServiceDomain; + clsRRDomain m_HostDomain; // 2. level answer (SRV, using service domain), eg. esp8266.local + uint16_t m_u16Port; // 2. level answer (SRV, using service domain), eg. 5000 + clsTTL m_TTLHostDomainAndPort; + clsServiceTxts m_Txts; // 2. level answer (TXT, using service domain), eg. c#=1 + clsTTL m_TTLTxts; +#ifdef MDNS_IPV4_SUPPORT + clsIPAddressWithTTL::list m_IPv4Addresses; // 3. level answer (A, using host domain), eg. 123.456.789.012 +#endif +#ifdef MDNS_IPV6_SUPPORT + clsIPAddressWithTTL::list m_IPv6Addresses; // 3. level answer (AAAA, using host domain), eg. 1234::09 +#endif + typeQueryAnswerType m_QueryAnswerFlags; // enuQueryAnswerType + + /** + list + */ + using list = std::list; + + clsAnswer(void); + ~clsAnswer(void); + + bool clear(void); + +#ifdef MDNS_IPV4_SUPPORT + bool releaseIPv4Addresses(void); + bool addIPv4Address(clsIPAddressWithTTL* p_pIPAddress); + bool removeIPv4Address(clsIPAddressWithTTL* p_pIPAddress); + const clsIPAddressWithTTL* findIPv4Address(const IPAddress& p_IPAddress) const; + clsIPAddressWithTTL* findIPv4Address(const IPAddress& p_IPAddress); + uint32_t IPv4AddressCount(void) const; + const clsIPAddressWithTTL* IPv4AddressAtIndex(uint32_t p_u32Index) const; + clsIPAddressWithTTL* IPv4AddressAtIndex(uint32_t p_u32Index); +#endif +#ifdef MDNS_IPV6_SUPPORT + bool releaseIPv6Addresses(void); + bool addIPv6Address(clsIPAddressWithTTL* p_pIPAddress); + bool removeIPv6Address(clsIPAddressWithTTL* p_pIPAddress); + const clsIPAddressWithTTL* findIPv6Address(const IPAddress& p_IPAddress) const; + clsIPAddressWithTTL* findIPv6Address(const IPAddress& p_IPAddress); + uint32_t IPv6AddressCount(void) const; + const clsIPAddressWithTTL* IPv6AddressAtIndex(uint32_t p_u32Index) const; + clsIPAddressWithTTL* IPv6AddressAtIndex(uint32_t p_u32Index); +#endif + }; // clsAnswer + + /** + clsAnswerAccessor + */ + class clsAnswerAccessor + { + protected: + /** + stcCompareTxtKey + */ + struct stcCompareTxtKey + { + bool operator()(char const* p_pA, char const* p_pB) const; + }; + public: + clsAnswerAccessor(const clsAnswer* p_pAnswer); + ~clsAnswerAccessor(void); + + /** + clsTxtKeyValueMap + */ + using clsTxtKeyValueMap = std::map; + /** + clsIPAddressVector + */ + using clsIPAddressVector = std::vector; + /** + vector + */ + using vector = std::vector; + + bool serviceDomainAvailable(void) const; + const char* serviceDomain(void) const; + bool hostDomainAvailable(void) const; + const char* hostDomain(void) const; + bool hostPortAvailable(void) const; + uint16_t hostPort(void) const; +#ifdef MDNS_IPV4_SUPPORT + bool IPv4AddressAvailable(void) const; + clsIPAddressVector IPv4Addresses(void) const; +#endif +#ifdef MDNS_IPV6_SUPPORT + bool IPv6AddressAvailable(void) const; + clsIPAddressVector IPv6Addresses(void) const; +#endif + bool txtsAvailable(void) const; + const char* txts(void) const; + const clsTxtKeyValueMap& txtKeyValues(void) const; + const char* txtValue(const char* p_pcKey) const; + + size_t printTo(Print& p_Print) const; + + protected: + const clsAnswer* m_pAnswer; + clsTxtKeyValueMap m_TxtKeyValueMap; + }; + + /** + typeQueryType & enuQueryType + */ + using typeQueryType = uint8_t; + enum class enuQueryType : typeQueryType + { + None, + Service, + Host + }; + + /** + QueryCallbackAnswerFn + */ + using QueryCallbackAnswerFn = std::function; // true: Answer component set, false: component deleted + /** + QueryCallbackAccessorFn + */ + using QueryCallbackAccessorFn = std::function; // true: Answer component set, false: component deleted + + protected: + friend clsLEAMDNSHost; + + enuQueryType m_QueryType; + clsRRDomain m_Domain; // Type:Service -> _http._tcp.local; Type:Host -> esp8266.local + QueryCallbackAnswerFn m_fnCallbackAnswer; + QueryCallbackAccessorFn m_fnCallbackAccessor; + bool m_bStaticQuery; + uint8_t m_u8SentCount; + esp8266::polledTimeout::oneShot m_ResendTimeout; + bool m_bAwaitingAnswers; + clsAnswer::list m_Answers; + + /** + list + */ + using list = std::list; + + clsQuery(const enuQueryType p_QueryType); + ~clsQuery(void); + + bool clear(void); + + bool addAnswer(clsAnswer* p_pAnswer); + bool removeAnswer(clsAnswer* p_pAnswer); + + clsAnswer* findAnswerForServiceDomain(const clsRRDomain& p_ServiceDomain); + clsAnswer* findAnswerForHostDomain(const clsRRDomain& p_HostDomain); + + public: + uint32_t answerCount(void) const; + const clsAnswer* answer(uint32_t p_u32Index) const; + uint32_t indexOfAnswer(const clsAnswer* p_pAnswer) const; + + clsAnswerAccessor::vector answerAccessors(void) const; + clsAnswerAccessor answerAccessor(uint32 p_u32AnswerIndex) const; + }; + +public: + static const char* indexDomainName(const char* p_pcDomainName, + const char* p_pcDivider = "-", + const char* p_pcDefaultDomainName = 0); + static bool setNetIfHostName(netif* p_pNetIf, + const char* p_pcHostName); + + clsLEAMDNSHost(void); + ~clsLEAMDNSHost(void); + + // INIT + // Create a MDNS host by setting the default hostname + // Later call 'update()' in every 'loop' to run the process loop + // (probing, announcing, responding, ...) + // If no callback is given, the (maybe) already installed callback stays set + bool begin(const char* p_pcHostName, + netif* p_pNetIf, + fnProbeResultCallback p_fnCallback = 0); + bool begin(const char* p_pcHostName, + WiFiMode_t p_WiFiMode, + fnProbeResultCallback p_fnCallback = 0); + bool begin(const char* p_pcHostName, + fnProbeResultCallback p_fnCallback = 0); + + bool close(void); + + // HOST + bool setHostName(const char* p_pcHostName); + bool indexHostName(void); + const char* hostName(void) const; + + bool setProbeResultCallback(fnProbeResultCallback p_fnCallback); + + // Returns 'true' is host domain probing is done + bool probeStatus(void) const; + + // SERVICE + bool setDefaultInstanceName(const char* p_pcInstanceName); + const char* defaultInstanceName(void) const; + + clsService* addService(const char* p_pcInstanceName, + const char* p_pcServiceType, + const char* p_pcProtocol, + uint16_t p_u16Port, + clsService::fnProbeResultCallback p_fnCallback = 0); + bool removeService(clsService* p_pMDNSService); + + const clsService* findService(const char* p_pcInstanceName, + const char* p_pcType, + const char* p_pcProtocol, + uint16_t p_u16Port = (uint16_t)(-1)) const; + clsService* findService(const char* p_pcInstanceName, + const char* p_pcType, + const char* p_pcProtocol, + uint16_t p_u16Port = (uint16_t)(-1)); + const clsService::list& services(void) const; + + // QUERIES + + // - STATIC + // Perform a (static) service/host query. The function returns after p_u16Timeout milliseconds + // The answers (the number of received answers is returned) can be retrieved by calling + // - answerHostName (or hostname) + // - answerIP (or IP) + // - answerPort (or port) + clsQuery::clsAnswerAccessor::vector queryService(const char* p_pcService, + const char* p_pcProtocol, + const uint16_t p_u16Timeout); + clsQuery::clsAnswerAccessor::vector queryHost(const char* p_pcHostName, + const uint16_t p_u16Timeout); + bool removeQuery(void); + bool hasQuery(void); + clsQuery* getQuery(void); + + // - DYNAMIC + // Install a dynamic service/host query. For every received answer (part) the given callback + // function is called. The query will be updated every time, the TTL for an answer + // has timed-out. + // The answers can also be retrieved by calling + // - answerCount service/host (for host queries, this should never be >1) + // - answerServiceDomain service + // - hasAnswerHostDomain/answerHostDomain service/host + // - hasAnswerIPv4Address/answerIPv4Address service/host + // - hasAnswerIPv6Address/answerIPv6Address service/host + // - hasAnswerPort/answerPort service + // - hasAnswerTxts/answerTxts service + clsQuery* installServiceQuery(const char* p_pcServiceType, + const char* p_pcProtocol, + clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer); + clsQuery* installServiceQuery(const char* p_pcServiceType, + const char* p_pcProtocol, + clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor); + clsQuery* installHostQuery(const char* p_pcHostName, + clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer); + clsQuery* installHostQuery(const char* p_pcHostName, + clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor); + // Remove a dynamic service query + bool removeQuery(clsQuery* p_pQuery); + + // PROCESSING + bool update(void); + + bool announce(bool p_bAnnounce = true, + bool p_bIncludeServices = true); + bool announceService(clsService* p_pService, + bool p_bAnnounce = true); + + bool restart(void); + +protected: + // File: ..._Host + UdpContext* _allocBackbone(void); + bool _releaseBackbone(void); + + bool _joinMulticastGroups(void); + bool _leaveMulticastGroups(void); + + // NETIF + typeNetIfState _getNetIfState(void) const; + bool _checkNetIfState(void); + + // PROCESSING + bool _processUDPInput(void); + + // DOMAIN NAMES + bool _allocDomainName(const char* p_pcNewDomainName, + char*& p_rpcDomainName); + bool _releaseDomainName(char*& p_rpcDomainName); + bool _allocHostName(const char* p_pcHostName); + bool _releaseHostName(void); + + bool _allocDefaultInstanceName(const char* p_pcInstanceName); + bool _releaseDefaultInstanceName(void); + const char* _instanceName(const char* p_pcInstanceName, + bool p_bReturnZero = true) const; + + // SERVICE + clsService* _allocService(const char* p_pcName, + const char* p_pcServiceType, + const char* p_pcProtocol, + uint16_t p_u16Port); + bool _releaseService(clsService* p_pService); + + // SERVICE TXT + clsServiceTxt* _allocServiceTxt(clsService* p_pService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp); + bool _releaseServiceTxt(clsService* p_pService, + clsServiceTxt* p_pTxt); + clsServiceTxt* _updateServiceTxt(clsService* p_pService, + clsServiceTxt* p_pTxt, + const char* p_pcValue, + bool p_bTemp); + clsServiceTxt* _findServiceTxt(clsService* p_pService, + const char* p_pcKey); + clsServiceTxt* _addServiceTxt(clsService* p_pService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp); + clsServiceTxt* _answerKeyValue(const clsQuery p_pQuery, + const uint32_t p_u32AnswerIndex); + bool _collectServiceTxts(clsService& p_rService); + bool _releaseTempServiceTxts(clsService& p_rService); + + + // QUERIES + clsQuery* _allocQuery(clsQuery::enuQueryType p_QueryType); + bool _removeQuery(clsQuery* p_pQuery); + bool _removeLegacyQuery(void); + clsQuery* _findLegacyQuery(void); + bool _releaseQueries(void); + clsQuery* _findNextQueryByDomain(const clsRRDomain& p_Domain, + const clsQuery::enuQueryType p_QueryType, + const clsQuery* p_pPrevQuery); + clsQuery* _installServiceQuery(const char* p_pcService, + const char* p_pcProtocol); + clsQuery* _installDomainQuery(clsRRDomain& p_Domain, + clsQuery::enuQueryType p_QueryType); + bool _hasQueriesWaitingForAnswers(void) const; + bool _executeQueryCallback(const clsQuery& p_Query, + const clsQuery::clsAnswer& p_Answer, + clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, + bool p_SetContent); + + + // File: ..._Host_Control + // RECEIVING + bool _parseMessage(void); + bool _parseQuery(const clsMsgHeader& p_Header); + + bool _parseResponse(const clsMsgHeader& p_Header); + bool _processAnswers(const clsRRAnswer* p_pPTRAnswers); + bool _processPTRAnswer(const clsRRAnswerPTR* p_pPTRAnswer, + bool& p_rbFoundNewKeyAnswer); + bool _processSRVAnswer(const clsRRAnswerSRV* p_pSRVAnswer, + bool& p_rbFoundNewKeyAnswer); + bool _processTXTAnswer(const clsRRAnswerTXT* p_pTXTAnswer); +#ifdef MDNS_IPV4_SUPPORT + bool _processAAnswer(const clsRRAnswerA* p_pAAnswer); +#endif +#ifdef MDNS_IPV6_SUPPORT + bool _processAAAAAnswer(const clsRRAnswerAAAA* p_pAAAAAnswer); +#endif + + // PROBING + bool _updateProbeStatus(void); + bool _resetProbeStatus(bool p_bRestart = true); + bool _hasProbesWaitingForAnswers(void) const; + bool _sendHostProbe(void); + bool _sendServiceProbe(clsService& p_rService); + bool _cancelProbingForHost(void); + bool _cancelProbingForService(clsService& p_rService); + bool _callHostProbeResultCallback(bool p_bResult); + bool _callServiceProbeResultCallback(clsService& p_rService, + bool p_bResult); + + // ANNOUNCE + bool _announce(bool p_bAnnounce, + bool p_bIncludeServices); + bool _announceService(clsService& p_pService, + bool p_bAnnounce = true); + + // QUERY CACHE + bool _checkQueryCache(void); + + uint32_t _replyMaskForHost(const clsRRHeader& p_RRHeader, + bool* p_pbFullNameMatch = 0) const; + uint32_t _replyMaskForService(const clsRRHeader& p_RRHeader, + clsService& p_rService, + bool* p_pbFullNameMatch = 0); + + + // File: ..._Host_Transfer + // SENDING + bool _sendMessage(clsSendParameter& p_SendParameter); + bool _sendMessage_Multicast(clsSendParameter& p_rSendParameter, + uint8_t p_IPProtocolTypes); + bool _prepareMessage(clsSendParameter& p_SendParameter); + bool _addQueryRecord(clsSendParameter& p_rSendParameter, + const clsRRDomain& p_QueryDomain, + uint16_t p_u16QueryType); + bool _sendQuery(const clsQuery& p_Query, + clsQuery::clsAnswer::list* p_pKnownAnswers = 0); + bool _sendQuery(const clsRRDomain& p_QueryDomain, + uint16_t p_u16RecordType, + clsQuery::clsAnswer::list* p_pKnownAnswers = 0); + + IPAddress _getResponderIPAddress(enuIPProtocolType p_IPProtocolType) const; + + // RESOURCE RECORD + bool _readRRQuestion(clsRRQuestion& p_rQuestion); + bool _readRRAnswer(clsRRAnswer*& p_rpAnswer); +#ifdef MDNS_IPV4_SUPPORT + bool _readRRAnswerA(clsRRAnswerA& p_rRRAnswerA, + uint16_t p_u16RDLength); +#endif + bool _readRRAnswerPTR(clsRRAnswerPTR& p_rRRAnswerPTR, + uint16_t p_u16RDLength); + bool _readRRAnswerTXT(clsRRAnswerTXT& p_rRRAnswerTXT, + uint16_t p_u16RDLength); +#ifdef MDNS_IPV6_SUPPORT + bool _readRRAnswerAAAA(clsRRAnswerAAAA& p_rRRAnswerAAAA, + uint16_t p_u16RDLength); +#endif + bool _readRRAnswerSRV(clsRRAnswerSRV& p_rRRAnswerSRV, + uint16_t p_u16RDLength); + bool _readRRAnswerGeneric(clsRRAnswerGeneric& p_rRRAnswerGeneric, + uint16_t p_u16RDLength); + + bool _readRRHeader(clsRRHeader& p_rHeader); + bool _readRRDomain(clsRRDomain& p_rRRDomain); + bool _readRRDomain_Loop(clsRRDomain& p_rRRDomain, + uint8_t p_u8Depth); + bool _readRRAttributes(clsRRAttributes& p_rAttributes); + + // DOMAIN NAMES + bool _buildDomainForHost(const char* p_pcHostName, + clsRRDomain& p_rHostDomain) const; + bool _buildDomainForDNSSD(clsRRDomain& p_rDNSSDDomain) const; + bool _buildDomainForService(const clsService& p_Service, + bool p_bIncludeName, + clsRRDomain& p_rServiceDomain) const; + bool _buildDomainForService(const char* p_pcService, + const char* p_pcProtocol, + clsRRDomain& p_rServiceDomain) const; +#ifdef MDNS_IPV4_SUPPORT + bool _buildDomainForReverseIPv4(IPAddress p_IPv4Address, + clsRRDomain& p_rReverseIPv4Domain) const; +#endif +#ifdef MDNS_IPV6_SUPPORT + bool _buildDomainForReverseIPv6(IPAddress p_IPv4Address, + clsRRDomain& p_rReverseIPv6Domain) const; +#endif + + // UDP + bool _udpReadBuffer(unsigned char* p_pBuffer, + size_t p_stLength); + bool _udpRead8(uint8_t& p_ru8Value); + bool _udpRead16(uint16_t& p_ru16Value); + bool _udpRead32(uint32_t& p_ru32Value); + + bool _udpAppendBuffer(const unsigned char* p_pcBuffer, + size_t p_stLength); + bool _udpAppend8(uint8_t p_u8Value); + bool _udpAppend16(uint16_t p_u16Value); + bool _udpAppend32(uint32_t p_u32Value); + +#if not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_MDNS_RESPONDER + bool _udpDump(bool p_bMovePointer = false); + bool _udpDump(unsigned p_uOffset, + unsigned p_uLength); +#endif + + // READ/WRITE MDNS STRUCTS + bool _readMDNSMsgHeader(clsMsgHeader& p_rMsgHeader); + + bool _write8(uint8_t p_u8Value, + clsSendParameter& p_rSendParameter); + bool _write16(uint16_t p_u16Value, + clsSendParameter& p_rSendParameter); + bool _write32(uint32_t p_u32Value, + clsSendParameter& p_rSendParameter); + + bool _writeMDNSMsgHeader(const clsMsgHeader& p_MsgHeader, + clsSendParameter& p_rSendParameter); + bool _writeMDNSRRAttributes(const clsRRAttributes& p_Attributes, + clsSendParameter& p_rSendParameter); + bool _writeMDNSRRDomain(const clsRRDomain& p_Domain, + clsSendParameter& p_rSendParameter); + bool _writeMDNSHostDomain(const char* m_pcHostName, + bool p_bPrependRDLength, + uint16_t p_u16AdditionalLength, + clsSendParameter& p_rSendParameter); + bool _writeMDNSServiceDomain(const clsService& p_Service, + bool p_bIncludeName, + bool p_bPrependRDLength, + uint16_t p_u16AdditionalLength, + clsSendParameter& p_rSendParameter); + + bool _writeMDNSQuestion(clsRRQuestion& p_Question, + clsSendParameter& p_rSendParameter); + +#ifdef MDNS_IPV4_SUPPORT + bool _writeMDNSAnswer_A(IPAddress p_IPAddress, + clsSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_PTR_IPv4(IPAddress p_IPAddress, + clsSendParameter& p_rSendParameter); +#endif + bool _writeMDNSAnswer_PTR_TYPE(clsService& p_rService, + clsSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_PTR_NAME(clsService& p_rService, + clsSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_TXT(clsService& p_rService, + clsSendParameter& p_rSendParameter); +#ifdef MDNS_IPV6_SUPPORT + bool _writeMDNSAnswer_AAAA(IPAddress p_IPAddress, + clsSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_PTR_IPv6(IPAddress p_IPAddress, + clsSendParameter& p_rSendParameter); +#endif + bool _writeMDNSAnswer_SRV(clsService& p_rService, + clsSendParameter& p_rSendParameter); + clsNSECBitmap* _createNSECBitmap(uint32_t p_u32NSECContent); + bool _writeMDNSNSECBitmap(const clsNSECBitmap& p_NSECBitmap, + clsSendParameter& p_rSendParameter); + bool _writeMDNSAnswer_NSEC(uint32_t p_u32NSECContent, + clsSendParameter& p_rSendParameter); +#ifdef MDNS_IPV4_SUPPORT + bool _writeMDNSAnswer_NSEC_PTR_IPv4(IPAddress p_IPAddress, + clsSendParameter& p_rSendParameter); +#endif +#ifdef MDNS_IPV6_SUPPORT + bool _writeMDNSAnswer_NSEC_PTR_IPv6(IPAddress p_IPAddress, + clsSendParameter& p_rSendParameter); +#endif + bool _writeMDNSAnswer_NSEC(clsService& p_rService, + uint32_t p_u32NSECContent, + clsSendParameter& p_rSendParameter); + + + // File: ..._Host_Debug +#if not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_MDNS_RESPONDER + const char* _DH(const clsService* p_pMDNSService = 0) const; + const char* _service2String(const clsService* p_pMDNSService) const; + + bool _printRRDomain(const clsRRDomain& p_rRRDomain) const; + bool _printRRAnswer(const clsRRAnswer& p_RRAnswer) const; + const char* _RRType2Name(uint16_t p_u16RRType) const; + const char* _RRClass2String(uint16_t p_u16RRClass, + bool p_bIsQuery) const; + const char* _replyFlags2String(uint32_t p_u32ReplyFlags) const; + const char* _NSECBitmap2String(const clsNSECBitmap* p_pNSECBitmap) const; +#endif + + +protected: + netif* m_pNetIf; + typeNetIfState m_NetIfState; + UdpContext* m_pUDPContext; + + char* m_pcHostName; + char* m_pcDefaultInstanceName; + clsService::list m_Services; + clsQuery::list m_Queries; + clsProbeInformation m_ProbeInformation; +}; + + +} // namespace MDNSImplementation + + +} // namespace esp8266 + + +#endif // __LEAMDNS2HOST_H__ + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp old mode 100755 new mode 100644 similarity index 66% rename from libraries/ESP8266mDNS/src/LEAmDNS2_Host_Control.cpp rename to libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index 7b4402c587..11fece09c1 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -1,5 +1,5 @@ /* - LEAmDNS2_Host_Control.cpp + LEAmDNS2Host_Control.cpp License (MIT license): Permission is hereby granted, free of charge, to any person obtaining a copy @@ -22,54 +22,41 @@ */ -#include -#include -#include -#include -#include -#include -#include +#include "LEAmDNS2Host.h" -/* - ESP8266mDNS Control.cpp -*/ - -extern "C" { -#include "user_interface.h" -} - -#include "LEAmDNS2_lwIPdefs.h" -#include "LEAmDNS2_Priv.h" namespace esp8266 { -/* - LEAmDNS -*/ + + namespace experimental { -/** + +/* + RECEIVING + */ /* - MDNSResponder::clsHost::_parseMessage + clsLEAmDNS2_Host::_parseMessage + */ -bool MDNSResponder::clsHost::_parseMessage(void) +bool clsLEAMDNSHost::_parseMessage(void) { DEBUG_EX_INFO( unsigned long ulStartTime = millis(); unsigned uStartMemory = ESP.getFreeHeap(); DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage (Time: %lu ms, heap: %u bytes, from %s, to %s)\n"), _DH(), ulStartTime, uStartMemory, - m_rUDPContext.getRemoteAddress().toString().c_str(), - m_rUDPContext.getDestAddress().toString().c_str()); + m_pUDPContext->getRemoteAddress().toString().c_str(), + m_pUDPContext->getDestAddress().toString().c_str()); ); //DEBUG_EX_INFO(_udpDump();); bool bResult = false; - stcMsgHeader header; + clsMsgHeader header; if (_readMDNSMsgHeader(header)) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), @@ -81,15 +68,18 @@ bool MDNSResponder::clsHost::_parseMessage(void) (unsigned)header.m_u16ANCount, (unsigned)header.m_u16NSCount, (unsigned)header.m_u16ARCount)); - if (0 == header.m_4bOpcode) // A standard query + if (0 == header.m_4bOpcode) { - if (header.m_1bQR) // Received a response -> answers to a query + // A standard query + if (header.m_1bQR) { + // Received a response -> answers to a query //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: Reading answers: ID:%u, Q:%u, A:%u, NS:%u, AR:%u\n"), _DH(), header.m_u16ID, header.m_u16QDCount, header.m_u16ANCount, header.m_u16NSCount, header.m_u16ARCount);); bResult = _parseResponse(header); } - else // Received a query (Questions) + else { + // Received a query (Questions) //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: Reading query: ID:%u, Q:%u, A:%u, NS:%u, AR:%u\n"), _DH(), header.m_u16ID, header.m_u16QDCount, header.m_u16ANCount, header.m_u16NSCount, header.m_u16ARCount);); bResult = _parseQuery(header); } @@ -97,13 +87,13 @@ bool MDNSResponder::clsHost::_parseMessage(void) else { DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: Received UNEXPECTED opcode:%u. Ignoring message!\n"), _DH(), header.m_4bOpcode);); - m_rUDPContext.flush(); + m_pUDPContext->flush(); } } else { DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: FAILED to read header\n"), _DH());); - m_rUDPContext.flush(); + m_pUDPContext->flush(); } DEBUG_EX_INFO( unsigned uFreeHeap = ESP.getFreeHeap(); @@ -113,7 +103,7 @@ bool MDNSResponder::clsHost::_parseMessage(void) } /* - MDNSResponder::clsHost::_parseQuery + clsLEAmDNS2_Host::_parseQuery Queries are of interest in two cases: 1. allow for tiebreaking while probing in the case of a race condition between two instances probing for @@ -127,17 +117,17 @@ bool MDNSResponder::clsHost::_parseMessage(void) 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::clsHost::_parseQuery(const MDNSResponder::clsHost::stcMsgHeader& p_MsgHeader) +bool clsLEAMDNSHost::_parseQuery(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader) { bool bResult = true; - stcSendParameter sendParameter; - uint32_t u32HostOrServiceReplies = 0; + clsSendParameter sendParameter; + uint32_t u32HostOrServiceReplies = 0; + bool bHostOrServiceTiebreakNeeded = false; for (uint16_t qd = 0; ((bResult) && (qd < p_MsgHeader.m_u16QDCount)); ++qd) { - stcRRQuestion questionRR; + clsRRQuestion questionRR; if ((bResult = _readRRQuestion(questionRR))) { // Define host replies, BUT only answer queries after probing is done @@ -148,26 +138,28 @@ bool MDNSResponder::clsHost::_parseQuery(const MDNSResponder::clsHost::stcMsgHea DEBUG_EX_INFO(if (u32HostOrServiceReplies) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Host reply needed %s\n"), _DH(), _replyFlags2String(u32HostOrServiceReplies));); // Check tiebreak need for host domain - if (enuProbingStatus::InProgress == m_HostProbeInformation.m_ProbingStatus) + if (clsProbeInformation_Base::clsProbeInformation_Base::enuProbingStatus::InProgress == m_ProbeInformation.m_ProbingStatus) { bool bFullNameMatch = false; if ((_replyMaskForHost(questionRR.m_Header, &bFullNameMatch)) && (bFullNameMatch)) { // We're in 'probing' state and someone is asking for our host domain: this might be - // a race-condition: Two host with the same domain names try simutanously to probe their domains + // a race-condition: Two hosts with the same domain names try simutanously to probe their domains // See: RFC 6762, 8.2 (Tiebraking) // However, we're using a max. reduced approach for tiebreaking here: The higher IP-address wins! DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Possible race-condition for host domain detected while probing.\n"), _DH());); - Serial.printf_P(PSTR("%s _parseQuery: Possible race-condition for host domain detected while probing.\n"), _DH()); - m_HostProbeInformation.m_bTiebreakNeeded = true; + bHostOrServiceTiebreakNeeded = + m_ProbeInformation.m_bTiebreakNeeded = true; } } // Define service replies - for (stcService* pService = m_pServices; pService; pService = pService->m_pNext) + for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) { + clsService* pService = *it; + // Define service replies, BUT only answer queries after probing is done uint32_t u32ReplyMaskForQuestion = ((pService->probeStatus()) ? _replyMaskForService(questionRR.m_Header, *pService, 0) @@ -176,7 +168,7 @@ bool MDNSResponder::clsHost::_parseQuery(const MDNSResponder::clsHost::stcMsgHea DEBUG_EX_INFO(if (u32ReplyMaskForQuestion) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service reply needed: %s\n"), _DH(pService), _replyFlags2String(u32ReplyMaskForQuestion));); // Check tiebreak need for service domain - if (enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) + if (clsProbeInformation_Base::clsProbeInformation_Base::enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) { bool bFullNameMatch = false; if ((_replyMaskForService(questionRR.m_Header, *pService, &bFullNameMatch)) && @@ -187,26 +179,25 @@ bool MDNSResponder::clsHost::_parseQuery(const MDNSResponder::clsHost::stcMsgHea // See: RFC 6762, 8.2 (Tiebraking) // However, we're using a max. reduced approach for tiebreaking here: The 'higher' SRV host wins! DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Possible race-condition for service domain detected while probing.\n"), _DH(pService));); - Serial.printf_P(PSTR("%s _parseQuery: Possible race-condition for service domain detected while probing.\n"), _DH(pService)); - pService->m_ProbeInformation.m_bTiebreakNeeded = true; + bHostOrServiceTiebreakNeeded = + pService->m_ProbeInformation.m_bTiebreakNeeded = true; } } } // Handle unicast and legacy specialities // If only one question asks for unicast reply, the whole reply packet is send unicast - if (((DNS_MQUERY_PORT != m_rUDPContext.getRemotePort()) || // Unicast (maybe legacy) query OR + if (((DNS_MQUERY_PORT != m_pUDPContext->getRemotePort()) || // Unicast (maybe legacy) query OR (questionRR.m_bUnicast)) && // Expressivly unicast query (!sendParameter.m_bUnicast)) { - sendParameter.m_bUnicast = true; //sendParameter.m_bCacheFlush = false; - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Unicast response asked for %s!\n"), _DH(), m_rUDPContext.getRemoteAddress().toString().c_str());); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Unicast response asked for %s!\n"), _DH(), m_pUDPContext->getRemoteAddress().toString().c_str());); //Serial.printf_P(PSTR("%s _parseQuery: Ignored Unicast response asked for by %s!\n"), _DH(), IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str()); - if ((DNS_MQUERY_PORT != m_rUDPContext.getRemotePort()) && // Unicast (maybe legacy) query AND + if ((DNS_MQUERY_PORT != m_pUDPContext->getRemotePort()) && // Unicast (maybe legacy) query AND (1 == p_MsgHeader.m_u16QDCount) && // Only one question AND ((sendParameter.m_u32HostReplyMask) || // Host replies OR (u32HostOrServiceReplies))) // Host or service replies available @@ -217,22 +208,23 @@ bool MDNSResponder::clsHost::_parseQuery(const MDNSResponder::clsHost::stcMsgHea #ifdef MDNS_IPV4_SUPPORT ip_info IPInfo_Local; #endif - if ( + if ((m_pNetIf) && + (m_pUDPContext) && #ifdef MDNS_IPV4_SUPPORT - (m_rUDPContext.getRemoteAddress().isV4()) && - ((wifi_get_ip_info(netif_get_index(&m_rNetIf), &IPInfo_Local))) && - (ip4_addr_netcmp(ip_2_ip4((const ip_addr_t*)m_rUDPContext.getRemoteAddress()), &IPInfo_Local.ip, &IPInfo_Local.netmask)) + (m_pUDPContext->getRemoteAddress().isV4()) && + ((wifi_get_ip_info(netif_get_index(m_pNetIf), &IPInfo_Local))) && + (ip4_addr_netcmp(ip_2_ip4((const ip_addr_t*)m_pUDPContext->getRemoteAddress()), &IPInfo_Local.ip, &IPInfo_Local.netmask)) #else (true) #endif && #ifdef MDNS_IPV6_SUPPORT - (m_rUDPContext.getRemoteAddress().isV6()) && - (ip6_addr_islinklocal(ip_2_ip6((const ip_addr_t*)m_rUDPContext.getRemoteAddress()))) + (m_pUDPContext->getRemoteAddress().isV6()) && + (ip6_addr_islinklocal(ip_2_ip6((const ip_addr_t*)m_pUDPContext->getRemoteAddress()))) #else (true) #endif - ) + ) { /* ip_info IPInfo_Local; ip_info IPInfo_Remote; @@ -243,27 +235,29 @@ bool MDNSResponder::clsHost::_parseQuery(const MDNSResponder::clsHost::stcMsgHea (ip4_addr_netcmp(&IPInfo_Remote.ip, &IPInfo_Local.ip, &IPInfo_Local.netmask))))) // Remote IP in STATION's subnet {*/ Serial.println("\n\n\nUNICAST QUERY\n\n"); - DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Legacy query from local host %s!\n"), _DH(), m_rUDPContext.getRemoteAddress().toString().c_str());); + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Legacy DNS query from local host %s!\n"), _DH(), m_pUDPContext->getRemoteAddress().toString().c_str());); sendParameter.m_u16ID = p_MsgHeader.m_u16ID; - sendParameter.m_bLegacyQuery = true; + sendParameter.m_bLegacyDNSQuery = true; sendParameter.m_bCacheFlush = false; - sendParameter.m_pQuestions = new stcRRQuestion; - if ((bResult = (0 != sendParameter.m_pQuestions))) + clsRRQuestion* pNewRRQuestion = new clsRRQuestion; + if (pNewRRQuestion) { - 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; + pNewRRQuestion->m_Header.m_Domain = questionRR.m_Header.m_Domain; + pNewRRQuestion->m_Header.m_Attributes.m_u16Type = questionRR.m_Header.m_Attributes.m_u16Type; + pNewRRQuestion->m_Header.m_Attributes.m_u16Class = questionRR.m_Header.m_Attributes.m_u16Class; + + sendParameter.m_RRQuestions.push_back(pNewRRQuestion); } else { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: FAILED to add legacy question!\n"), _DH());); + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: FAILED to add legacy DNS question!\n"), _DH());); } } else { - Serial.println("\n\n\nINVALID UNICAST QUERY\n\n"); - DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Legacy query from NON-LOCAL host!\n"), _DH());); + Serial.printf("\n\n\nINVALID UNICAST QUERY from %s\n\n\n", m_pUDPContext->getRemoteAddress().toString().c_str()); + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Legacy DNS query from NON-LOCAL host at %s!\n"), _DH(), m_pUDPContext->getRemoteAddress().toString().c_str());); bResult = false; } } @@ -276,30 +270,31 @@ bool MDNSResponder::clsHost::_parseQuery(const MDNSResponder::clsHost::stcMsgHea } // for questions //DEBUG_EX_INFO(if (u8HostOrServiceReplies) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Reply needed: %u (%s: %s->%s)\n"), _DH(), u8HostOrServiceReplies, clsTimeSyncer::timestr(), IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str(), IPAddress(m_pUDPContext->getDestAddress()).toString().c_str());); + //bHostOrServiceTiebreakNeeded = false; // Handle known answers uint32_t u32Answers = (p_MsgHeader.m_u16ANCount + p_MsgHeader.m_u16NSCount + p_MsgHeader.m_u16ARCount); - if ((u32HostOrServiceReplies) && + if (((u32HostOrServiceReplies) || + (bHostOrServiceTiebreakNeeded)) && (u32Answers)) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Reading known answers(%u):\n"), _DH(), u32Answers);); for (uint32_t an = 0; ((bResult) && (an < u32Answers)); ++an) { - stcRRAnswer* pKnownRRAnswer = 0; + clsRRAnswer* pKnownRRAnswer = 0; if (((bResult = _readRRAnswer(pKnownRRAnswer))) && (pKnownRRAnswer)) { - if ((DNS_RRTYPE_ANY != pKnownRRAnswer->m_Header.m_Attributes.m_u16Type) && // No ANY type answer (DNS_RRCLASS_ANY != (pKnownRRAnswer->m_Header.m_Attributes.m_u16Class & (~0x8000)))) // No ANY class answer { - - // Find match between planned answer (sendParameter.m_u8HostReplyMask) and this 'known answer' - uint32_t u32HostMatchMask = (sendParameter.m_u32HostReplyMask & _replyMaskForHost(pKnownRRAnswer->m_Header)); - if ((u32HostMatchMask) && // 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) - { + /* - RFC6762 7.1 Suppression only for 'Shared Records' - + // Find match between planned answer (sendParameter.m_u8HostReplyMask) and this 'known answer' + uint32_t u32HostMatchMask = (sendParameter.m_u32HostReplyMask & _replyMaskForHost(pKnownRRAnswer->m_Header)); + if ((u32HostMatchMask) && // The RR in the known answer matches an RR we are planning to send, AND + ((Consts::u32HostTTL / 2) <= pKnownRRAnswer->m_u32TTL)) // The TTL of the known answer is longer than half of the new host TTL (120s) + { // Compare contents if (enuAnswerType::PTR == pKnownRRAnswer->answerType()) @@ -309,41 +304,41 @@ bool MDNSResponder::clsHost::_parseQuery(const MDNSResponder::clsHost::stcMsgHea (((stcRRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain == hostDomain)) { // Host domain match -#ifdef MDNS_IPV4_SUPPORT + #ifdef MDNS_IPV4_SUPPORT if (u32HostMatchMask & static_cast(enuContentFlag::PTR_IPv4)) { // IPv4 PTR was asked for, but is already known -> skipping - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: IPv4 PTR already known... skipping!\n"), _DH());); + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: IPv4 PTR already known (TTL:%u)... skipping!\n"), _DH(), pKnownRRAnswer->m_u32TTL);); sendParameter.m_u32HostReplyMask &= ~static_cast(enuContentFlag::PTR_IPv4); } -#endif -#ifdef MDNS_IPV6_SUPPORT + #endif + #ifdef MDNS_IPV6_SUPPORT if (u32HostMatchMask & static_cast(enuContentFlag::PTR_IPv6)) { // IPv6 PTR was asked for, but is already known -> skipping - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: IPv6 PTR already known... skipping!\n"), _DH());); + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: IPv6 PTR already known (TTL:%u)... skipping!\n"), _DH(), pKnownRRAnswer->m_u32TTL);); sendParameter.m_u32HostReplyMask &= ~static_cast(enuContentFlag::PTR_IPv6); } -#endif + #endif } } else if (u32HostMatchMask & static_cast(enuContentFlag::A)) { // IPv4 address was asked for -#ifdef MDNS_IPV4_SUPPORT + #ifdef MDNS_IPV4_SUPPORT if ((enuAnswerType::A == pKnownRRAnswer->answerType()) && (((stcRRAnswerA*)pKnownRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V4))) { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: IPv4 address already known... skipping!\n"), _DH());); + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: IPv4 address already known (TTL:%u)... skipping!\n"), _DH(), pKnownRRAnswer->m_u32TTL);); sendParameter.m_u32HostReplyMask &= ~static_cast(enuContentFlag::A); } // else: RData NOT IPv4 length !! -#endif + #endif } else if (u32HostMatchMask & static_cast(enuContentFlag::AAAA)) { // IPv6 address was asked for -#ifdef MDNS_IPV6_SUPPORT + #ifdef MDNS_IPV6_SUPPORT if ((enuAnswerType::AAAA == pKnownRRAnswer->answerType()) && (((stcRRAnswerAAAA*)pKnownRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V6))) { @@ -351,15 +346,16 @@ bool MDNSResponder::clsHost::_parseQuery(const MDNSResponder::clsHost::stcMsgHea DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: IPv6 address already known... skipping!\n"), _DH());); sendParameter.m_u32HostReplyMask &= ~static_cast(enuContentFlag::AAAA); } // else: RData NOT IPv6 length !! -#endif + #endif } - } // Host match /*and TTL*/ + } // Host match and TTL + */ // // Check host tiebreak possibility - if (m_HostProbeInformation.m_bTiebreakNeeded) + if (m_ProbeInformation.m_bTiebreakNeeded) { - stcRRDomain hostDomain; + clsRRDomain hostDomain; if ((_buildDomainForHost(m_pcHostName, hostDomain)) && (pKnownRRAnswer->m_Header.m_Domain == hostDomain)) { @@ -369,26 +365,26 @@ bool MDNSResponder::clsHost::_parseQuery(const MDNSResponder::clsHost::stcMsgHea { // CHECK IPAddress localIPAddress(_getResponderIPAddress(enuIPProtocolType::V4)); - if (((stcRRAnswerA*)pKnownRRAnswer)->m_IPAddress == localIPAddress) + if (((clsRRAnswerA*)pKnownRRAnswer)->m_IPAddress == localIPAddress) { // SAME IP address -> We've received an old message from ourselfs (same IP) DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv4) WON (was an old message)!\n"), _DH());); - m_HostProbeInformation.m_bTiebreakNeeded = false; + m_ProbeInformation.m_bTiebreakNeeded = false; } else { - if ((uint32_t)(((stcRRAnswerA*)pKnownRRAnswer)->m_IPAddress) > (uint32_t)localIPAddress) // The OTHER IP is 'higher' -> LOST + if ((uint32_t)(((clsRRAnswerA*)pKnownRRAnswer)->m_IPAddress) > (uint32_t)localIPAddress) // The OTHER IP is 'higher' -> LOST { // LOST tiebreak - DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv4) LOST (lower)!\n"), _DH());); + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv4) LOST (lower IPv4)!\n"), _DH());); _cancelProbingForHost(); - m_HostProbeInformation.m_bTiebreakNeeded = false; + m_ProbeInformation.m_bTiebreakNeeded = false; } - else // WON tiebreak + else { - //TiebreakState = TiebreakState_Won; // We received an 'old' message from ourselfs -> Just ignore - DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv4) WON (higher IP)!\n"), _DH());); - m_HostProbeInformation.m_bTiebreakNeeded = false; + // WON tiebreak + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv4) WON (higher IPv4)!\n"), _DH());); + m_ProbeInformation.m_bTiebreakNeeded = false; } } } @@ -396,28 +392,28 @@ bool MDNSResponder::clsHost::_parseQuery(const MDNSResponder::clsHost::stcMsgHea #ifdef MDNS_IPV6_SUPPORT if (enuAnswerType::AAAA == pKnownRRAnswer->answerType()) { - // TODO / CHECK IPAddress localIPAddress(_getResponderIPAddress(enuIPProtocolType::V6)); - if (((stcRRAnswerAAAA*)pKnownRRAnswer)->m_IPAddress == localIPAddress) + if (((clsRRAnswerAAAA*)pKnownRRAnswer)->m_IPAddress == localIPAddress) { // SAME IP address -> We've received an old message from ourselfs (same IP) DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv6) WON (was an old message)!\n"), _DH());); - m_HostProbeInformation.m_bTiebreakNeeded = false; + m_ProbeInformation.m_bTiebreakNeeded = false; } else { - if ((uint32_t)(((stcRRAnswerAAAA*)pKnownRRAnswer)->m_IPAddress) > (uint32_t)localIPAddress) // The OTHER IP is 'higher' -> LOST + // memcmp delivers >0 (positive) if the first non-matching byte in A is higher than in B + if (0 < memcmp((((clsRRAnswerAAAA*)pKnownRRAnswer)->m_IPAddress).raw6(), localIPAddress.raw6(), clsConsts::u16IPv6Size)) // The OTHER IP is 'higher' -> LOST { // LOST tiebreak - DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv6) LOST (lower)!\n"), _DH());); + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv6) LOST (lower IPv6)!\n"), _DH());); _cancelProbingForHost(); - m_HostProbeInformation.m_bTiebreakNeeded = false; + m_ProbeInformation.m_bTiebreakNeeded = false; } - else // WON tiebreak + else { - //TiebreakState = TiebreakState_Won; // We received an 'old' message from ourselfs -> Just ignore - DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv6) WON (higher IP)!\n"), _DH());); - m_HostProbeInformation.m_bTiebreakNeeded = false; + // WON tiebreak + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (IPv6) WON (higher IPv6)!\n"), _DH());); + m_ProbeInformation.m_bTiebreakNeeded = false; } } } @@ -426,97 +422,98 @@ bool MDNSResponder::clsHost::_parseQuery(const MDNSResponder::clsHost::stcMsgHea } // Host tiebreak possibility // Check service answers - for (stcService* pService = m_pServices; pService; pService = pService->m_pNext) + for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) { + clsService* pService = *it; uint32_t u32ServiceMatchMask = (pService->m_u32ReplyMask & _replyMaskForService(pKnownRRAnswer->m_Header, *pService)); if ((u32ServiceMatchMask) && // The RR in the known answer matches an RR we are planning to send, AND - ((MDNS_SERVICE_TTL / 2) <= pKnownRRAnswer->m_u32TTL)) // The TTL of the known answer is longer than half of the new service TTL (4500s) + ((clsConsts::u32ServiceTTL / 2) <= pKnownRRAnswer->m_u32TTL)) // The TTL of the known answer is longer than half of the new service TTL (4500s) { - if (enuAnswerType::PTR == pKnownRRAnswer->answerType()) { - stcRRDomain serviceDomain; + clsRRDomain serviceDomain; if ((u32ServiceMatchMask & static_cast(enuContentFlag::PTR_TYPE)) && (_buildDomainForService(*pService, false, serviceDomain)) && - (serviceDomain == ((stcRRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain)) + (serviceDomain == ((clsRRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain)) { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service type PTR already known... skipping!\n"), _DH());); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service type PTR already known (TTL:%u)... skipping!\n"), _DH(pService), pKnownRRAnswer->m_u32TTL);); pService->m_u32ReplyMask &= ~static_cast(enuContentFlag::PTR_TYPE); } if ((u32ServiceMatchMask & static_cast(enuContentFlag::PTR_NAME)) && (_buildDomainForService(*pService, true, serviceDomain)) && - (serviceDomain == ((stcRRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain)) + (serviceDomain == ((clsRRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain)) { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service name PTR already known... skipping!\n"), _DH());); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service name PTR already known (TTL:%u)... skipping!\n"), _DH(pService), pKnownRRAnswer->m_u32TTL);); pService->m_u32ReplyMask &= ~static_cast(enuContentFlag::PTR_NAME); } } - else if (u32ServiceMatchMask & static_cast(enuContentFlag::SRV)) - { - DEBUG_EX_ERR(if (enuAnswerType::SRV != pKnownRRAnswer->answerType()) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: ERROR! INVALID answer type (SRV)!\n"), _DH());); + /* - RFC6762 7.1 Suppression only for 'Shared Records' + else if (u32ServiceMatchMask & static_cast(enuContentFlag::SRV)) + { + DEBUG_EX_ERR(if (enuAnswerType::SRV != pKnownRRAnswer->answerType()) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: ERROR! INVALID answer type (SRV)!\n"), _DH(pService));); stcRRDomain hostDomain; if ((_buildDomainForHost(m_pcHostName, hostDomain)) && (hostDomain == ((stcRRAnswerSRV*)pKnownRRAnswer)->m_SRVDomain)) // Host domain match { - if ((MDNS_SRV_PRIORITY == ((stcRRAnswerSRV*)pKnownRRAnswer)->m_u16Priority) && - (MDNS_SRV_WEIGHT == ((stcRRAnswerSRV*)pKnownRRAnswer)->m_u16Weight) && + if ((Consts::u16SRVPriority == ((stcRRAnswerSRV*)pKnownRRAnswer)->m_u16Priority) && + (Consts::u16SRVWeight == ((stcRRAnswerSRV*)pKnownRRAnswer)->m_u16Weight) && (pService->m_u16Port == ((stcRRAnswerSRV*)pKnownRRAnswer)->m_u16Port)) { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service SRV answer already known... skipping!\n"), _DH());); + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service SRV answer already known (TTL:%u)... skipping!\n"), _DH(pService), pKnownRRAnswer->m_u32TTL);); pService->m_u32ReplyMask &= ~static_cast(enuContentFlag::SRV); } // else: Small differences -> send update message } - } - else if (u32ServiceMatchMask & static_cast(enuContentFlag::TXT)) - { - DEBUG_EX_ERR(if (enuAnswerType::TXT != pKnownRRAnswer->answerType()) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: ERROR! INVALID answer type (TXT)!\n"), _DH());); + }*/ + /* - RFC6762 7.1 Suppression only for 'Shared Records' + else if (u32ServiceMatchMask & static_cast(enuContentFlag::TXT)) + { + DEBUG_EX_ERR(if (enuAnswerType::TXT != pKnownRRAnswer->answerType()) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: ERROR! INVALID answer type (TXT)!\n"), _DH(pService));); _collectServiceTxts(*pService); if (pService->m_Txts == ((stcRRAnswerTXT*)pKnownRRAnswer)->m_Txts) { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service TXT answer already known... skipping!\n"), _DH());); + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service TXT answer already known (TTL:%u)... skipping!\n"), _DH(pService), pKnownRRAnswer->m_u32TTL);); pService->m_u32ReplyMask &= ~static_cast(enuContentFlag::TXT); } _releaseTempServiceTxts(*pService); - } + }*/ } // Service match and enough TTL // // Check service tiebreak possibility if (pService->m_ProbeInformation.m_bTiebreakNeeded) { - stcRRDomain serviceDomain; + clsRRDomain serviceDomain; if ((_buildDomainForService(*pService, true, serviceDomain)) && (pKnownRRAnswer->m_Header.m_Domain == serviceDomain)) { // Service domain match if (enuAnswerType::SRV == pKnownRRAnswer->answerType()) { - stcRRDomain hostDomain; + clsRRDomain hostDomain; if ((_buildDomainForHost(m_pcHostName, hostDomain)) && - (hostDomain == ((stcRRAnswerSRV*)pKnownRRAnswer)->m_SRVDomain)) // Host domain match + (hostDomain == ((clsRRAnswerSRV*)pKnownRRAnswer)->m_SRVDomain)) // Host domain match { - // We've received an old message from ourselfs (same SRV) - DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (SRV) won (was an old message)!\n"), _DH());); + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (SRV) won (was an old message)!\n"), _DH(pService));); pService->m_ProbeInformation.m_bTiebreakNeeded = false; } else { - if (((stcRRAnswerSRV*)pKnownRRAnswer)->m_SRVDomain > hostDomain) // The OTHER domain is 'higher' -> LOST + if (((clsRRAnswerSRV*)pKnownRRAnswer)->m_SRVDomain > hostDomain) // The OTHER domain is 'higher' -> LOST { // LOST tiebreak - DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (SRV) LOST (lower)!\n"), _DH());); + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (SRV) LOST (lower)!\n"), _DH(pService));); _cancelProbingForService(*pService); pService->m_ProbeInformation.m_bTiebreakNeeded = false; } - else // WON tiebreak + else { - //TiebreakState = TiebreakState_Won; // We received an 'old' message from ourselfs -> Just ignore - DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (SRV) won (higher)!\n"), _DH());); + // WON tiebreak + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (SRV) won (higher)!\n"), _DH(pService));); pService->m_ProbeInformation.m_bTiebreakNeeded = false; } } @@ -541,48 +538,47 @@ bool MDNSResponder::clsHost::_parseQuery(const MDNSResponder::clsHost::stcMsgHea else { DEBUG_EX_INFO(if (u32Answers) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Skipped %u known answers!\n"), _DH(), u32Answers);); - m_rUDPContext.flush(); + m_pUDPContext->flush(); } if (bResult) { // Check, if a reply is needed uint32_t u32ReplyNeeded = sendParameter.m_u32HostReplyMask; - for (stcService* pService = m_pServices; pService; pService = pService->m_pNext) + for (const clsService* pService : m_Services) { u32ReplyNeeded |= pService->m_u32ReplyMask; } if (u32ReplyNeeded) { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Sending answer(%s)...\n"), _DH(), _replyFlags2String(u32ReplyNeeded));); + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Sending %s answer(%s)...\n"), _DH(), (sendParameter.m_bUnicast ? "UC" : "MC"), _replyFlags2String(u32ReplyNeeded));); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Sending %s answer(%s)...\n"), _DH(), (sendParameter.m_bUnicast ? "UC" : "MC"), _replyFlags2String(u32ReplyNeeded));); - sendParameter.m_Response = stcSendParameter::enuResponseType::Response; + sendParameter.m_Response = clsSendParameter::enuResponseType::Response; sendParameter.m_bAuthorative = true; - bResult = _sendMDNSMessage(sendParameter); + bResult = _sendMessage(sendParameter); } - DEBUG_EX_INFO( - else + DEBUG_EX_INFO(else { DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: No reply needed\n"), _DH()); - } - ); + }); } else { DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Something FAILED!\n"), _DH());); - m_rUDPContext.flush(); + m_pUDPContext->flush(); } // // Check and reset tiebreak-states - if (m_HostProbeInformation.m_bTiebreakNeeded) + if (m_ProbeInformation.m_bTiebreakNeeded) { DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: UNSOLVED tiebreak-need for host domain!\n"), _DH());); - m_HostProbeInformation.m_bTiebreakNeeded = false; + m_ProbeInformation.m_bTiebreakNeeded = false; } - for (stcService* pService = m_pServices; pService; pService = pService->m_pNext) + for (clsService* pService : m_Services) { if (pService->m_ProbeInformation.m_bTiebreakNeeded) { @@ -595,7 +591,7 @@ bool MDNSResponder::clsHost::_parseQuery(const MDNSResponder::clsHost::stcMsgHea } /* - MDNSResponder::clsHost::_parseResponse + clsLEAmDNS2_Host::_parseResponse Responses are of interest in two cases: 1. find domain name conflicts while probing @@ -621,7 +617,7 @@ bool MDNSResponder::clsHost::_parseQuery(const MDNSResponder::clsHost::stcMsgHea TXT - links the instance name to services TXTs Level 3: A/AAAA - links the host domain to an IP address */ -bool MDNSResponder::clsHost::_parseResponse(const MDNSResponder::clsHost::stcMsgHeader& p_MsgHeader) +bool clsLEAMDNSHost::_parseResponse(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse\n"));); //DEBUG_EX_INFO(_udpDump();); @@ -632,7 +628,6 @@ bool MDNSResponder::clsHost::_parseResponse(const MDNSResponder::clsHost::stcMsg if ((_hasQueriesWaitingForAnswers()) || // Waiting for query answers OR (_hasProbesWaitingForAnswers())) // Probe responses { - DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: Received a response\n"), _DH()); //_udpDump(); @@ -641,7 +636,7 @@ bool MDNSResponder::clsHost::_parseResponse(const MDNSResponder::clsHost::stcMsg bResult = true; // // Ignore questions here - stcRRQuestion dummyRRQ; + clsRRQuestion dummyRRQ; for (uint16_t qd = 0; ((bResult) && (qd < p_MsgHeader.m_u16QDCount)); ++qd) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: Received a response containing a question... ignoring!\n"), _DH());); @@ -650,11 +645,11 @@ bool MDNSResponder::clsHost::_parseResponse(const MDNSResponder::clsHost::stcMsg // // Read and collect answers - stcRRAnswer* pCollectedRRAnswers = 0; + clsRRAnswer* pCollectedRRAnswers = 0; uint32_t u32NumberOfAnswerRRs = (p_MsgHeader.m_u16ANCount + p_MsgHeader.m_u16NSCount + p_MsgHeader.m_u16ARCount); for (uint32_t an = 0; ((bResult) && (an < u32NumberOfAnswerRRs)); ++an) { - stcRRAnswer* pRRAnswer = 0; + clsRRAnswer* pRRAnswer = 0; if (((bResult = _readRRAnswer(pRRAnswer))) && (pRRAnswer)) { @@ -684,14 +679,14 @@ bool MDNSResponder::clsHost::_parseResponse(const MDNSResponder::clsHost::stcMsg else // Some failure while reading answers { DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: FAILED to read answers!\n"), _DH());); - m_rUDPContext.flush(); + m_pUDPContext->flush(); } // Delete collected answers while (pCollectedRRAnswers) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: DELETING answer!\n"), _DH());); - stcRRAnswer* pNextAnswer = pCollectedRRAnswers->m_pNext; + clsRRAnswer* pNextAnswer = pCollectedRRAnswers->m_pNext; delete pCollectedRRAnswers; pCollectedRRAnswers = pNextAnswer; } @@ -719,7 +714,7 @@ bool MDNSResponder::clsHost::_parseResponse(const MDNSResponder::clsHost::stcMsg } */ ); - m_rUDPContext.flush(); + m_pUDPContext->flush(); bResult = true; } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: FAILED!\n"), _DH());); @@ -727,7 +722,7 @@ bool MDNSResponder::clsHost::_parseResponse(const MDNSResponder::clsHost::stcMsg } /* - MDNSResponder::clsHost::_processAnswers + clsLEAmDNS2_Host::_processAnswers Host: A (0x01): eg. esp8266.local A OP TTL 123.456.789.012 AAAA (01Cx): eg. esp8266.local AAAA OP TTL 1234:5678::90 @@ -740,7 +735,7 @@ bool MDNSResponder::clsHost::_parseResponse(const MDNSResponder::clsHost::stcMsg TXT (0x10): eg. MyESP._http._tcp.local TXT OP TTL c#=1 */ -bool MDNSResponder::clsHost::_processAnswers(const MDNSResponder::clsHost::stcRRAnswer* p_pAnswers) +bool clsLEAMDNSHost::_processAnswers(const clsLEAMDNSHost::clsRRAnswer* p_pAnswers) { bool bResult = false; @@ -756,78 +751,70 @@ bool MDNSResponder::clsHost::_processAnswers(const MDNSResponder::clsHost::stcRR { bFoundNewKeyAnswer = false; - const stcRRAnswer* pRRAnswer = p_pAnswers; + const clsRRAnswer* pRRAnswer = p_pAnswers; while ((pRRAnswer) && (bResult)) { // 1. level answer (PTR) if (enuAnswerType::PTR == pRRAnswer->answerType()) - { - // eg. _http._tcp.local PTR xxxx xx MyESP._http._tcp.local - bResult = _processPTRAnswer((stcRRAnswerPTR*)pRRAnswer, bFoundNewKeyAnswer); // May 'enable' new SRV or TXT answers to be linked to queries + { // eg. _http._tcp.local PTR xxxx xx MyESP._http._tcp.local + bResult = _processPTRAnswer((clsRRAnswerPTR*)pRRAnswer, bFoundNewKeyAnswer); // May 'enable' new SRV or TXT answers to be linked to queries } // 2. level answers // SRV -> host domain and port else if (enuAnswerType::SRV == pRRAnswer->answerType()) - { - // eg. MyESP._http._tcp.local SRV xxxx xx yy zz 5000 esp8266.local - bResult = _processSRVAnswer((stcRRAnswerSRV*)pRRAnswer, bFoundNewKeyAnswer); // May 'enable' new A/AAAA answers to be linked to queries + { // eg. MyESP._http._tcp.local SRV xxxx xx yy zz 5000 esp8266.local + bResult = _processSRVAnswer((clsRRAnswerSRV*)pRRAnswer, bFoundNewKeyAnswer); // May 'enable' new A/AAAA answers to be linked to queries } // TXT -> Txts else if (enuAnswerType::TXT == pRRAnswer->answerType()) - { - // eg. MyESP_http._tcp.local TXT xxxx xx c#=1 - bResult = _processTXTAnswer((stcRRAnswerTXT*)pRRAnswer); + { // eg. MyESP_http._tcp.local TXT xxxx xx c#=1 + bResult = _processTXTAnswer((clsRRAnswerTXT*)pRRAnswer); } // 3. level answers #ifdef MDNS_IPV4_SUPPORT // A -> IPv4Address else if (enuAnswerType::A == pRRAnswer->answerType()) - { - // eg. esp8266.local A xxxx xx 192.168.2.120 - bResult = _processAAnswer((stcRRAnswerA*)pRRAnswer); + { // eg. esp8266.local A xxxx xx 192.168.2.120 + bResult = _processAAnswer((clsRRAnswerA*)pRRAnswer); } #endif #ifdef MDNS_IPV6_SUPPORT // AAAA -> IPv6Address else if (enuAnswerType::AAAA == pRRAnswer->answerType()) - { - // eg. esp8266.local AAAA xxxx xx 09cf::0c - bResult = _processAAAAAnswer((stcRRAnswerAAAA*)pRRAnswer); + { // eg. esp8266.local AAAA xxxx xx 09cf::0c + bResult = _processAAAAAnswer((clsRRAnswerAAAA*)pRRAnswer); } #endif // Finally check for probing conflicts // Host domain - if ((enuProbingStatus::InProgress == m_HostProbeInformation.m_ProbingStatus) && + if ((clsProbeInformation_Base::enuProbingStatus::InProgress == m_ProbeInformation.m_ProbingStatus) && ((enuAnswerType::A == pRRAnswer->answerType()) || (enuAnswerType::AAAA == pRRAnswer->answerType()))) { - - stcRRDomain hostDomain; + clsRRDomain hostDomain; if ((_buildDomainForHost(m_pcHostName, hostDomain)) && (pRRAnswer->m_Header.m_Domain == hostDomain)) { - bool bPossibleEcho = false; #ifdef MDNS_IPV4_SUPPORT if ((enuAnswerType::A == pRRAnswer->answerType()) && - (((stcRRAnswerA*)pRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V4))) + (((clsRRAnswerA*)pRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V4))) { - bPossibleEcho = true; } #endif #ifdef MDNS_IPV6_SUPPORT if ((enuAnswerType::AAAA == pRRAnswer->answerType()) && - (((stcRRAnswerAAAA*)pRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V6))) + (((clsRRAnswerAAAA*)pRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V6))) { - bPossibleEcho = true; } #endif if (!bPossibleEcho) { + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _processAnswers: Probing CONFLICT found with '%s.local'\n"), _DH(), m_pcHostName);); DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processAnswers: Probing CONFLICT found with '%s.local'\n"), _DH(), m_pcHostName);); _cancelProbingForHost(); } @@ -838,19 +825,18 @@ bool MDNSResponder::clsHost::_processAnswers(const MDNSResponder::clsHost::stcRR } } // Service domains - for (stcService* pService = m_pServices; pService; pService = pService->m_pNext) + for (clsService* pService : m_Services) { - if ((enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) && + if ((clsProbeInformation_Base::enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) && ((enuAnswerType::TXT == pRRAnswer->answerType()) || (enuAnswerType::SRV == pRRAnswer->answerType()))) { - - stcRRDomain serviceDomain; + clsRRDomain serviceDomain; if ((_buildDomainForService(*pService, true, serviceDomain)) && (pRRAnswer->m_Header.m_Domain == serviceDomain)) { - // TODO: Echo management needed? + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _processAnswers: Probing CONFLICT found with '%s'\n"), _DH(), _service2String(pService));); DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processAnswers: Probing CONFLICT found with '%s'\n"), _DH(), _service2String(pService));); _cancelProbingForService(*pService); } @@ -867,10 +853,10 @@ bool MDNSResponder::clsHost::_processAnswers(const MDNSResponder::clsHost::stcRR } /* - MDNSResponder::clsHost::_processPTRAnswer (level 1) + clsLEAmDNS2_Host::_processPTRAnswer (level 1) */ -bool MDNSResponder::clsHost::_processPTRAnswer(const MDNSResponder::clsHost::stcRRAnswerPTR* p_pPTRAnswer, - bool& p_rbFoundNewKeyAnswer) +bool clsLEAMDNSHost::_processPTRAnswer(const clsLEAMDNSHost::clsRRAnswerPTR* p_pPTRAnswer, + bool& p_rbFoundNewKeyAnswer) { bool bResult = false; @@ -880,17 +866,16 @@ bool MDNSResponder::clsHost::_processPTRAnswer(const MDNSResponder::clsHost::stc // eg. _http._tcp.local PTR xxxx xx MyESP._http._tcp.local // Check pending service queries for eg. '_http._tcp' - stcQuery* pQuery = _findNextQueryByDomain(p_pPTRAnswer->m_Header.m_Domain, stcQuery::enuQueryType::Service, 0); + clsQuery* pQuery = _findNextQueryByDomain(p_pPTRAnswer->m_Header.m_Domain, clsQuery::enuQueryType::Service, 0); while (pQuery) { if (pQuery->m_bAwaitingAnswers) - { - // Find answer for service domain (eg. MyESP._http._tcp.local) - stcQuery::stcAnswer* pSQAnswer = pQuery->findAnswerForServiceDomain(p_pPTRAnswer->m_PTRDomain); - if (pSQAnswer) // existing answer - { - if (p_pPTRAnswer->m_u32TTL) // Received update message - { + { // Find answer for service domain (eg. MyESP._http._tcp.local) + clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForServiceDomain(p_pPTRAnswer->m_PTRDomain); + if (pSQAnswer) + { // existing answer + if (p_pPTRAnswer->m_u32TTL) + { // Received update message pSQAnswer->m_TTLServiceDomain.set(p_pPTRAnswer->m_u32TTL); // Update TTL tag DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processPTRAnswer: Updated TTL(%lu) for "), _DH(), p_pPTRAnswer->m_u32TTL); @@ -898,8 +883,8 @@ bool MDNSResponder::clsHost::_processPTRAnswer(const MDNSResponder::clsHost::stc DEBUG_OUTPUT.printf_P(PSTR("\n")); ); } - else // received goodbye-message - { + else + { // received goodbye-message pSQAnswer->m_TTLServiceDomain.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processPTRAnswer: 'Goodbye' received for "), _DH()); @@ -908,11 +893,11 @@ bool MDNSResponder::clsHost::_processPTRAnswer(const MDNSResponder::clsHost::stc ); } } - else if ((p_pPTRAnswer->m_u32TTL) && // Not just a goodbye-message - ((pSQAnswer = new stcQuery::stcAnswer))) // Not yet included -> add answer + else if ((p_pPTRAnswer->m_u32TTL) && // Not just a goodbye-message + ((pSQAnswer = new clsQuery::clsAnswer))) // Not yet included -> add answer { pSQAnswer->m_ServiceDomain = p_pPTRAnswer->m_PTRDomain; - pSQAnswer->m_QueryAnswerFlags |= static_cast(enuQueryAnswerType::ServiceDomain); + pSQAnswer->m_QueryAnswerFlags |= static_cast(clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain); pSQAnswer->m_TTLServiceDomain.set(p_pPTRAnswer->m_u32TTL); //pSQAnswer->releaseServiceDomain(); @@ -925,10 +910,10 @@ bool MDNSResponder::clsHost::_processPTRAnswer(const MDNSResponder::clsHost::stc ); p_rbFoundNewKeyAnswer = true; - _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::ServiceDomain), true); + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain), true); } } - pQuery = _findNextQueryByDomain(p_pPTRAnswer->m_Header.m_Domain, stcQuery::enuQueryType::Service, pQuery); + pQuery = _findNextQueryByDomain(p_pPTRAnswer->m_Header.m_Domain, clsQuery::enuQueryType::Service, pQuery); } } // else: No p_pPTRAnswer DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _processPTRAnswer: FAILED!\n"), _DH());); @@ -936,27 +921,28 @@ bool MDNSResponder::clsHost::_processPTRAnswer(const MDNSResponder::clsHost::stc } /* - MDNSResponder::clsHost::_processSRVAnswer (level 2) + clsLEAmDNS2_Host::_processSRVAnswer (level 2) */ -bool MDNSResponder::clsHost::_processSRVAnswer(const MDNSResponder::clsHost::stcRRAnswerSRV* p_pSRVAnswer, - bool& p_rbFoundNewKeyAnswer) +bool clsLEAMDNSHost::_processSRVAnswer(const clsLEAMDNSHost::clsRRAnswerSRV* p_pSRVAnswer, + bool& p_rbFoundNewKeyAnswer) { bool bResult = false; if ((bResult = (0 != p_pSRVAnswer))) { // eg. MyESP._http._tcp.local SRV xxxx xx yy zz 5000 esp8266.local - - stcQuery* pQuery = m_pQueries; - while (pQuery) + for (clsQuery::list::iterator it = m_Queries.begin(); ((bResult) && (it != m_Queries.end())); it++) { + clsQuery* pQuery = *it; + if (pQuery->m_bAwaitingAnswers) { - stcQuery::stcAnswer* pSQAnswer = pQuery->findAnswerForServiceDomain(p_pSRVAnswer->m_Header.m_Domain); - if (pSQAnswer) // Answer for this service domain (eg. MyESP._http._tcp.local) available + clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForServiceDomain(p_pSRVAnswer->m_Header.m_Domain); + if (pSQAnswer) { - if (p_pSRVAnswer->m_u32TTL) // First or update message (TTL != 0) - { + // Answer for this service domain (eg. MyESP._http._tcp.local) available + if (p_pSRVAnswer->m_u32TTL) + { // First or update message (TTL != 0) pSQAnswer->m_TTLHostDomainAndPort.set(p_pSRVAnswer->m_u32TTL); // Update TTL tag DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processSRVAnswer: Updated TTL(%lu) for "), _DH(), p_pSRVAnswer->m_u32TTL); @@ -971,7 +957,7 @@ bool MDNSResponder::clsHost::_processSRVAnswer(const MDNSResponder::clsHost::stc pSQAnswer->m_HostDomain = p_pSRVAnswer->m_SRVDomain; //pSQAnswer->releaseHostDomain(); pSQAnswer->m_u16Port = p_pSRVAnswer->m_u16Port; - pSQAnswer->m_QueryAnswerFlags |= (static_cast(enuQueryAnswerType::HostDomain) | static_cast(enuQueryAnswerType::Port)); + pSQAnswer->m_QueryAnswerFlags |= (static_cast(clsQuery::clsAnswer::enuQueryAnswerType::HostDomain) | static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Port)); DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processSVRAnswer: Added host domain and port to "), _DH()); @@ -982,11 +968,11 @@ bool MDNSResponder::clsHost::_processSRVAnswer(const MDNSResponder::clsHost::stc ); p_rbFoundNewKeyAnswer = true; - _executeQueryCallback(*pQuery, *pSQAnswer, (static_cast(enuQueryAnswerType::HostDomain) | static_cast(enuQueryAnswerType::Port)), true); + _executeQueryCallback(*pQuery, *pSQAnswer, (static_cast(clsQuery::clsAnswer::enuQueryAnswerType::HostDomain) | static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Port)), true); } } - else // Goodby message - { + else + { // Goodby message pSQAnswer->m_TTLHostDomainAndPort.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processSRVAnswer: 'Goodbye' received for "), _DH()); @@ -996,34 +982,33 @@ bool MDNSResponder::clsHost::_processSRVAnswer(const MDNSResponder::clsHost::stc } } } // m_bAwaitingAnswers - pQuery = pQuery->m_pNext; - } // while(service query) + } // for(queries) } // else: No p_pSRVAnswer DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _processSRVAnswer: FAILED!\n"), _DH());); return bResult; } /* - MDNSResponder::clsHost::_processTXTAnswer (level 2) + clsLEAmDNS2_Host::_processTXTAnswer (level 2) */ -bool MDNSResponder::clsHost::_processTXTAnswer(const MDNSResponder::clsHost::stcRRAnswerTXT* p_pTXTAnswer) +bool clsLEAMDNSHost::_processTXTAnswer(const clsLEAMDNSHost::clsRRAnswerTXT* p_pTXTAnswer) { bool bResult = false; if ((bResult = (0 != p_pTXTAnswer))) { // eg. MyESP._http._tcp.local TXT xxxx xx c#=1 - - stcQuery* pQuery = m_pQueries; - while (pQuery) + for (clsQuery::list::iterator it = m_Queries.begin(); ((bResult) && (it != m_Queries.end())); it++) { + clsQuery* pQuery = *it; + if (pQuery->m_bAwaitingAnswers) { - stcQuery::stcAnswer* pSQAnswer = pQuery->findAnswerForServiceDomain(p_pTXTAnswer->m_Header.m_Domain); - if (pSQAnswer) // Answer for this service domain (eg. MyESP._http._tcp.local) available - { - if (p_pTXTAnswer->m_u32TTL) // First or update message - { + clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForServiceDomain(p_pTXTAnswer->m_Header.m_Domain); + if (pSQAnswer) + { // Answer for this service domain (eg. MyESP._http._tcp.local) available + if (p_pTXTAnswer->m_u32TTL) + { // First or update message pSQAnswer->m_TTLTxts.set(p_pTXTAnswer->m_u32TTL); // Update TTL tag DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processTXTAnswer: Updated TTL(%lu) for "), _DH(), p_pTXTAnswer->m_u32TTL); @@ -1033,7 +1018,7 @@ bool MDNSResponder::clsHost::_processTXTAnswer(const MDNSResponder::clsHost::stc if (!pSQAnswer->m_Txts.compare(p_pTXTAnswer->m_Txts)) { pSQAnswer->m_Txts = p_pTXTAnswer->m_Txts; - pSQAnswer->m_QueryAnswerFlags |= static_cast(enuQueryAnswerType::Txts); + pSQAnswer->m_QueryAnswerFlags |= static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Txts); //pSQAnswer->releaseTxts(); DEBUG_EX_INFO( @@ -1042,11 +1027,11 @@ bool MDNSResponder::clsHost::_processTXTAnswer(const MDNSResponder::clsHost::stc DEBUG_OUTPUT.println(); ); - _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::Txts), true); + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Txts), true); } } - else // Goodby message - { + else + { // Goodby message pSQAnswer->m_TTLTxts.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processTXTAnswer: 'Goodbye' received for "), _DH()); @@ -1056,8 +1041,7 @@ bool MDNSResponder::clsHost::_processTXTAnswer(const MDNSResponder::clsHost::stc } } } // m_bAwaitingAnswers - pQuery = pQuery->m_pNext; - } // while(service query) + } // for(queries) } // else: No p_pTXTAnswer DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _processTXTAnswer: FAILED!\n"), _DH());); return bResult; @@ -1065,32 +1049,29 @@ bool MDNSResponder::clsHost::_processTXTAnswer(const MDNSResponder::clsHost::stc #ifdef MDNS_IPV4_SUPPORT /* - MDNSResponder::clsHost::_processAAnswer (level 3) + clsLEAmDNS2_Host::_processAAnswer (level 3) */ -bool MDNSResponder::clsHost::_processAAnswer(const MDNSResponder::clsHost::stcRRAnswerA* p_pAAnswer) +bool clsLEAMDNSHost::_processAAnswer(const clsLEAMDNSHost::clsRRAnswerA* p_pAAnswer) { bool bResult = false; if ((bResult = (0 != p_pAAnswer))) { // eg. esp8266.local A xxxx xx 192.168.2.120 - - stcQuery* pQuery = m_pQueries; - while (pQuery) + for (clsQuery::list::iterator it = m_Queries.begin(); ((bResult) && (it != m_Queries.end())); it++) { + clsQuery* pQuery = *it; + if (pQuery->m_bAwaitingAnswers) - { - // Look for answers to host queries - if ((p_pAAnswer->m_u32TTL) && // NOT just a goodbye message - (stcQuery::enuQueryType::Host == pQuery->m_QueryType) && // AND a host query - (pQuery->m_Domain == p_pAAnswer->m_Header.m_Domain)) // AND a matching host domain + { // Look for answers to host queries + if ((p_pAAnswer->m_u32TTL) && // NOT just a goodbye message + (clsQuery::enuQueryType::Host == pQuery->m_QueryType) && // AND a host query + (pQuery->m_Domain == p_pAAnswer->m_Header.m_Domain)) // AND a matching host domain { - - stcQuery::stcAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAnswer->m_Header.m_Domain); + clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAnswer->m_Header.m_Domain); if ((!pSQAnswer) && - ((pSQAnswer = new stcQuery::stcAnswer))) - { - // Add not yet included answer + ((pSQAnswer = new clsQuery::clsAnswer))) + { // Add not yet included answer pSQAnswer->m_HostDomain = p_pAAnswer->m_Header.m_Domain; //pSQAnswer->releaseHostDomain(); @@ -1101,21 +1082,20 @@ bool MDNSResponder::clsHost::_processAAnswer(const MDNSResponder::clsHost::stcRR DEBUG_OUTPUT.println(); ); - pSQAnswer->m_QueryAnswerFlags |= static_cast(enuQueryAnswerType::HostDomain); - _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::HostDomain), true); + pSQAnswer->m_QueryAnswerFlags |= static_cast(clsQuery::clsAnswer::enuQueryAnswerType::HostDomain); + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::HostDomain), true); } } // Look for answers to service queries - stcQuery::stcAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAnswer->m_Header.m_Domain); - if (pSQAnswer) // Answer for this host domain (eg. esp8266.local) available - { - stcQuery::stcAnswer::stcIPAddress* pIPAddress = pSQAnswer->findIPv4Address(p_pAAnswer->m_IPAddress); + clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAnswer->m_Header.m_Domain); + if (pSQAnswer) + { // Answer for this host domain (eg. esp8266.local) available + clsQuery::clsAnswer::clsIPAddressWithTTL* pIPAddress = pSQAnswer->findIPv4Address(p_pAAnswer->m_IPAddress); if (pIPAddress) - { - // Already known IPv4 address - if (p_pAAnswer->m_u32TTL) // Valid TTL -> Update answers TTL - { + { // Already known IPv4 address + if (p_pAAnswer->m_u32TTL) + { // Valid TTL -> Update answers TTL pIPAddress->m_TTL.set(p_pAAnswer->m_u32TTL); DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: Updated TTL(%lu) for "), _DH(), p_pAAnswer->m_u32TTL); @@ -1123,8 +1103,8 @@ bool MDNSResponder::clsHost::_processAAnswer(const MDNSResponder::clsHost::stcRR DEBUG_OUTPUT.printf_P(PSTR(" IPv4 address (%s)\n"), pIPAddress->m_IPAddress.toString().c_str()); ); } - else // 'Goodbye' message for known IPv4 address - { + else + { // 'Goodbye' message for known IPv4 address pIPAddress->m_TTL.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: 'Goodbye' received for "), _DH()); @@ -1134,23 +1114,21 @@ bool MDNSResponder::clsHost::_processAAnswer(const MDNSResponder::clsHost::stcRR } } else - { - // Until now unknown IPv4 address -> Add (if the message isn't just a 'Goodbye' note) - if (p_pAAnswer->m_u32TTL) // NOT just a 'Goodbye' message - { - pIPAddress = new stcQuery::stcAnswer::stcIPAddress(p_pAAnswer->m_IPAddress, p_pAAnswer->m_u32TTL); + { // Until now unknown IPv4 address -> Add (if the message isn't just a 'Goodbye' note) + if (p_pAAnswer->m_u32TTL) + { // NOT just a 'Goodbye' message + pIPAddress = new clsQuery::clsAnswer::clsIPAddressWithTTL(p_pAAnswer->m_IPAddress, p_pAAnswer->m_u32TTL); if ((pIPAddress) && (pSQAnswer->addIPv4Address(pIPAddress))) { - DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: Added IPv4 address to "), _DH()); _printRRDomain(pSQAnswer->m_ServiceDomain); DEBUG_OUTPUT.printf_P(PSTR(": %s\n"), pIPAddress->m_IPAddress.toString().c_str()); ); - pSQAnswer->m_QueryAnswerFlags |= static_cast(enuQueryAnswerType::IPv4Address); - _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::IPv4Address), true); + pSQAnswer->m_QueryAnswerFlags |= static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address); + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address), true); } else { @@ -1160,8 +1138,7 @@ bool MDNSResponder::clsHost::_processAAnswer(const MDNSResponder::clsHost::stcRR } } } // m_bAwaitingAnswers - pQuery = pQuery->m_pNext; - } // while(service query) + } // for(queries) } // else: No p_pAAnswer DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: FAILED!\n"), _DH());); return bResult; @@ -1170,32 +1147,29 @@ bool MDNSResponder::clsHost::_processAAnswer(const MDNSResponder::clsHost::stcRR #ifdef MDNS_IPV6_SUPPORT /* - MDNSResponder::clsHost::_processAAAAAnswer (level 3) + clsLEAmDNS2_Host::_processAAAAAnswer (level 3) */ -bool MDNSResponder::clsHost::_processAAAAAnswer(const MDNSResponder::clsHost::stcRRAnswerAAAA* p_pAAAAAnswer) +bool clsLEAMDNSHost::_processAAAAAnswer(const clsLEAMDNSHost::clsRRAnswerAAAA* p_pAAAAAnswer) { bool bResult = false; if ((bResult = (0 != p_pAAAAAnswer))) { // eg. esp8266.local AAAA xxxx xx 0bf3::0c - - stcQuery* pQuery = m_pQueries; - while (pQuery) + for (clsQuery::list::iterator it = m_Queries.begin(); ((bResult) && (it != m_Queries.end())); it++) { + clsQuery* pQuery = *it; + if (pQuery->m_bAwaitingAnswers) - { - // Look for answers to host queries - if ((p_pAAAAAnswer->m_u32TTL) && // NOT just a goodbye message - (stcQuery::enuQueryType::Host == pQuery->m_QueryType) && // AND a host query - (pQuery->m_Domain == p_pAAAAAnswer->m_Header.m_Domain)) // AND a matching host domain + { // Look for answers to host queries + if ((p_pAAAAAnswer->m_u32TTL) && // NOT just a goodbye message + (clsQuery::enuQueryType::Host == pQuery->m_QueryType) && // AND a host query + (pQuery->m_Domain == p_pAAAAAnswer->m_Header.m_Domain)) // AND a matching host domain { - - stcQuery::stcAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAAAAnswer->m_Header.m_Domain); + clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAAAAnswer->m_Header.m_Domain); if ((!pSQAnswer) && - ((pSQAnswer = new stcQuery::stcAnswer))) - { - // Add not yet included answer + ((pSQAnswer = new clsQuery::clsAnswer))) + { // Add not yet included answer pSQAnswer->m_HostDomain = p_pAAAAAnswer->m_Header.m_Domain; //pSQAnswer->releaseHostDomain(); @@ -1206,21 +1180,20 @@ bool MDNSResponder::clsHost::_processAAAAAnswer(const MDNSResponder::clsHost::st DEBUG_OUTPUT.println(); ); - pSQAnswer->m_QueryAnswerFlags |= static_cast(enuQueryAnswerType::HostDomain); - _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::HostDomain), true); + pSQAnswer->m_QueryAnswerFlags |= static_cast(clsQuery::clsAnswer::enuQueryAnswerType::HostDomain); + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::HostDomain), true); } } // Look for answers to service queries - stcQuery::stcAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAAAAnswer->m_Header.m_Domain); + clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAAAAnswer->m_Header.m_Domain); if (pSQAnswer) // Answer for this host domain (eg. esp8266.local) available { - stcQuery::stcAnswer::stcIPAddress* pIPAddress = pSQAnswer->findIPv6Address(p_pAAAAAnswer->m_IPAddress); + clsQuery::clsAnswer::clsIPAddressWithTTL* pIPAddress = pSQAnswer->findIPv6Address(p_pAAAAAnswer->m_IPAddress); if (pIPAddress) - { - // Already known IPv6 address - if (p_pAAAAAnswer->m_u32TTL) // Valid TTL -> Update answers TTL - { + { // Already known IPv6 address + if (p_pAAAAAnswer->m_u32TTL) + { // Valid TTL -> Update answers TTL pIPAddress->m_TTL.set(p_pAAAAAnswer->m_u32TTL); DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processAAAAAnswer: Updated TTL(%lu) for "), _DH(), p_pAAAAAnswer->m_u32TTL); @@ -1228,8 +1201,8 @@ bool MDNSResponder::clsHost::_processAAAAAnswer(const MDNSResponder::clsHost::st DEBUG_OUTPUT.printf_P(PSTR(" IPv6 address (%s)\n"), pIPAddress->m_IPAddress.toString().c_str()); ); } - else // 'Goodbye' message for known IPv6 address - { + else + { // 'Goodbye' message for known IPv6 address pIPAddress->m_TTL.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processAAAAAnswer: 'Goodbye' received for "), _DH()); @@ -1239,23 +1212,22 @@ bool MDNSResponder::clsHost::_processAAAAAnswer(const MDNSResponder::clsHost::st } } else - { - // Until now unknown IPv6 address -> Add (if the message isn't just a 'Goodbye' note) - if (p_pAAAAAnswer->m_u32TTL) // NOT just a 'Goodbye' message + { // Until now unknown IPv6 address -> Add (if the message isn't just a 'Goodbye' note) + if (p_pAAAAAnswer->m_u32TTL) { - pIPAddress = new stcQuery::stcAnswer::stcIPAddress(p_pAAAAAnswer->m_IPAddress, p_pAAAAAnswer->m_u32TTL); + // NOT just a 'Goodbye' message + pIPAddress = new clsQuery::clsAnswer::clsIPAddressWithTTL(p_pAAAAAnswer->m_IPAddress, p_pAAAAAnswer->m_u32TTL); if ((pIPAddress) && (pSQAnswer->addIPv6Address(pIPAddress))) { - DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processAAAAAnswer: Added IPv6 address to "), _DH()); _printRRDomain(pSQAnswer->m_ServiceDomain); DEBUG_OUTPUT.printf_P(PSTR(": %s\n"), pIPAddress->m_IPAddress.toString().c_str()); ); - pSQAnswer->m_QueryAnswerFlags |= static_cast(enuQueryAnswerType::IPv6Address); - _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::IPv6Address), true); + pSQAnswer->m_QueryAnswerFlags |= static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address); + _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address), true); } else { @@ -1265,8 +1237,7 @@ bool MDNSResponder::clsHost::_processAAAAAnswer(const MDNSResponder::clsHost::st } } } // m_bAwaitingAnswers - pQuery = pQuery->m_pNext; - } // while(service query) + } // for(queries) } // else: No p_pAAAAAnswer return bResult; @@ -1275,11 +1246,13 @@ bool MDNSResponder::clsHost::_processAAAAAnswer(const MDNSResponder::clsHost::st /* + PROBING + */ /* - MDNSResponder::clsHost::_updateProbeStatus + clsLEAmDNS2_Host::_updateProbeStatus Manages the (outgoing) probing process. - If probing has not been started yet (ProbingStatus_NotStarted), the initial delay (see RFC 6762) is determined and @@ -1290,13 +1263,13 @@ bool MDNSResponder::clsHost::_processAAAAAnswer(const MDNSResponder::clsHost::st Conflict management is handled in '_parseResponse ff.' Tiebraking is handled in 'parseQuery ff.' */ -bool MDNSResponder::clsHost::_updateProbeStatus(void) +bool clsLEAMDNSHost::_updateProbeStatus(void) { bool bResult = true; // // Probe host domain - if ((enuProbingStatus::ReadyToStart == m_HostProbeInformation.m_ProbingStatus) && // Ready to get started AND + if ((clsProbeInformation_Base::enuProbingStatus::ReadyToStart == m_ProbeInformation.m_ProbingStatus) && // Ready to get started AND (( #ifdef MDNS_IPV4_SUPPORT _getResponderIPAddress(enuIPProtocolType::V4).isSet() // AND has IPv4 address @@ -1314,111 +1287,122 @@ bool MDNSResponder::clsHost::_updateProbeStatus(void) DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Starting host probing...\n"), _DH());); // First probe delay SHOULD be random 0-250 ms - m_HostProbeInformation.m_Timeout.reset(rand() % MDNS_PROBE_DELAY); - m_HostProbeInformation.m_ProbingStatus = enuProbingStatus::InProgress; + m_ProbeInformation.m_Timeout.reset(rand() % clsConsts::u32ProbeDelay); + m_ProbeInformation.m_ProbingStatus = clsProbeInformation_Base::enuProbingStatus::InProgress; } - else if ((enuProbingStatus::InProgress == m_HostProbeInformation.m_ProbingStatus) && // Probing AND - (m_HostProbeInformation.m_Timeout.expired())) // Time for next probe + else if ((clsProbeInformation_Base::enuProbingStatus::InProgress == m_ProbeInformation.m_ProbingStatus) && // Probing AND + (m_ProbeInformation.m_Timeout.expired())) // Time for next probe { - - if (MDNS_PROBE_COUNT > m_HostProbeInformation.m_u8SentCount) // Send next probe + if (clsConsts::u32ProbeCount > m_ProbeInformation.m_u8SentCount) { + // Send next probe if ((bResult = _sendHostProbe())) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Did sent host probe for '%s.local'\n\n"), _DH(), (m_pcHostName ? : ""));); - m_HostProbeInformation.m_Timeout.reset(MDNS_PROBE_DELAY); - ++m_HostProbeInformation.m_u8SentCount; + m_ProbeInformation.m_Timeout.reset(clsConsts::u32ProbeDelay); + ++m_ProbeInformation.m_u8SentCount; } } - else // Probing finished + else { + // Probing finished DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("\n%s _updateProbeStatus: Done host probing for '%s.local'.\n\n\n"), _DH(), (m_pcHostName ? : ""));); - m_HostProbeInformation.m_ProbingStatus = enuProbingStatus::Done; - m_HostProbeInformation.m_Timeout.reset(std::numeric_limits::max()); + m_ProbeInformation.m_ProbingStatus = clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce; + m_ProbeInformation.m_Timeout.reset(esp8266::polledTimeout::oneShot::neverExpires); _callHostProbeResultCallback(true); // Prepare to announce host - m_HostProbeInformation.m_u8SentCount = 0; - m_HostProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); + m_ProbeInformation.m_u8SentCount = 0; + m_ProbeInformation.m_Timeout.reset(clsConsts::u32AnnounceDelay); DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Prepared host announcing.\n\n"), _DH());); } } // else: Probing already finished OR waiting for next time slot - else if ((enuProbingStatus::Done == m_HostProbeInformation.m_ProbingStatus) && - (m_HostProbeInformation.m_Timeout.expired())) + else if ((clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == m_ProbeInformation.m_ProbingStatus) && + (m_ProbeInformation.m_Timeout.expired())) { - - if ((bResult = _announce(true, false))) // Don't announce services here + if ((bResult = _announce(true, false))) { - ++m_HostProbeInformation.m_u8SentCount; // 1.. + // Don't announce services here + ++m_ProbeInformation.m_u8SentCount; // 1.. - if (MDNS_ANNOUNCE_COUNT > m_HostProbeInformation.m_u8SentCount) + if (clsConsts::u32AnnounceCount > m_ProbeInformation.m_u8SentCount) { - m_HostProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY * pow(2, (m_HostProbeInformation.m_u8SentCount - 1))); // 2^(0..) -> 1, 2, 4, ... - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Announcing host '%s.local' (%lu).\n\n"), _DH(), (m_pcHostName ? : ""), m_HostProbeInformation.m_u8SentCount);); + m_ProbeInformation.m_Timeout.reset(clsConsts::u32AnnounceDelay * pow(2, (m_ProbeInformation.m_u8SentCount - 1))); // 2^(0..) -> 1, 2, 4, ... + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Announcing host '%s.local' (%lu).\n\n"), _DH(), (m_pcHostName ? : ""), m_ProbeInformation.m_u8SentCount);); } else { - m_HostProbeInformation.m_Timeout.reset(std::numeric_limits::max()); + m_ProbeInformation.m_ProbingStatus = clsProbeInformation_Base::enuProbingStatus::DoneFinally; + m_ProbeInformation.m_Timeout.reset(esp8266::polledTimeout::oneShot::neverExpires); + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Done host announcing for '%s.local'.\n"), _DH(), (m_pcHostName ? : ""));); DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Done host announcing for '%s.local'.\n\n"), _DH(), (m_pcHostName ? : ""));); + //DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Done host announcing for '%s.local'.\n"), _DH(), (m_pcHostName ? : "")); } } } // // Probe services - for (stcService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) + for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) { - if (enuProbingStatus::ReadyToStart == pService->m_ProbeInformation.m_ProbingStatus) // Ready to get started - { + clsService* pService = *it; - pService->m_ProbeInformation.m_Timeout.reset(MDNS_PROBE_DELAY); // More or equal than first probe for host domain - pService->m_ProbeInformation.m_ProbingStatus = enuProbingStatus::InProgress; + if (clsProbeInformation_Base::enuProbingStatus::ReadyToStart == pService->m_ProbeInformation.m_ProbingStatus) + { + // Ready to get started + pService->m_ProbeInformation.m_Timeout.reset(clsConsts::u32ProbeDelay); // More or equal than first probe for host domain + pService->m_ProbeInformation.m_ProbingStatus = clsProbeInformation_Base::enuProbingStatus::InProgress; } - else if ((enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) && // Probing AND - (pService->m_ProbeInformation.m_Timeout.expired())) // Time for next probe + else if ((clsProbeInformation_Base::enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) && // Probing AND + (pService->m_ProbeInformation.m_Timeout.expired())) // Time for next probe { - - if (MDNS_PROBE_COUNT > pService->m_ProbeInformation.m_u8SentCount) // Send next probe + if (clsConsts::u32ProbeCount > pService->m_ProbeInformation.m_u8SentCount) { + // Send next probe if ((bResult = _sendServiceProbe(*pService))) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Did sent service probe for '%s' (%u)\n\n"), _DH(), _service2String(pService), (pService->m_ProbeInformation.m_u8SentCount + 1));); - pService->m_ProbeInformation.m_Timeout.reset(MDNS_PROBE_DELAY); + pService->m_ProbeInformation.m_Timeout.reset(clsConsts::u32ProbeDelay); ++pService->m_ProbeInformation.m_u8SentCount; } } - else // Probing finished + else { + // Probing finished DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("\n%s _updateProbeStatus: Done service probing '%s'\n\n\n"), _DH(), _service2String(pService));); - pService->m_ProbeInformation.m_ProbingStatus = enuProbingStatus::Done; - pService->m_ProbeInformation.m_Timeout.reset(std::numeric_limits::max()); + pService->m_ProbeInformation.m_ProbingStatus = clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce; + pService->m_ProbeInformation.m_Timeout.reset(esp8266::polledTimeout::oneShot::neverExpires); _callServiceProbeResultCallback(*pService, true); // Prepare to announce service pService->m_ProbeInformation.m_u8SentCount = 0; - pService->m_ProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); + pService->m_ProbeInformation.m_Timeout.reset(clsConsts::u32AnnounceDelay); DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Prepared service announcing.\n\n"), _DH());); } - } // else: Probing already finished OR waiting for next time slot - else if ((enuProbingStatus::Done == pService->m_ProbeInformation.m_ProbingStatus) && + } + else if ((clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == pService->m_ProbeInformation.m_ProbingStatus) && (pService->m_ProbeInformation.m_Timeout.expired())) { - - if ((bResult = _announceService(*pService))) // Announce service + // Probing already finished OR waiting for next time slot + if ((bResult = _announceService(*pService))) { + // Announce service ++pService->m_ProbeInformation.m_u8SentCount; // 1.. - if (MDNS_ANNOUNCE_COUNT > pService->m_ProbeInformation.m_u8SentCount) + if (clsConsts::u32AnnounceCount > pService->m_ProbeInformation.m_u8SentCount) { - pService->m_ProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY * pow(2, (pService->m_ProbeInformation.m_u8SentCount - 1))); // 2^(0..) -> 1, 2, 4, ... + pService->m_ProbeInformation.m_Timeout.reset(clsConsts::u32AnnounceDelay * pow(2, (pService->m_ProbeInformation.m_u8SentCount - 1))); // 2^(0..) -> 1, 2, 4, ... DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Announcing service '%s' (%lu)\n\n"), _DH(), _service2String(pService), pService->m_ProbeInformation.m_u8SentCount);); } else { - pService->m_ProbeInformation.m_Timeout.reset(std::numeric_limits::max()); + pService->m_ProbeInformation.m_ProbingStatus = clsProbeInformation_Base::enuProbingStatus::DoneFinally; + pService->m_ProbeInformation.m_Timeout.reset(esp8266::polledTimeout::oneShot::neverExpires); + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Done service announcing for '%s'\n"), _DH(), _service2String(pService));); DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Done service announcing for '%s'\n\n"), _DH(), _service2String(pService));); + //DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Done service announcing for '%s'\n"), _DH(), _service2String(pService)); } } } @@ -1428,44 +1412,48 @@ bool MDNSResponder::clsHost::_updateProbeStatus(void) } /* - MDNSResponder::clsHost::_resetProbeStatus + clsLEAmDNS2_Host::_resetProbeStatus Resets the probe status. If 'p_bRestart' is set, the status is set to ProbingStatus_NotStarted. Consequently, when running 'updateProbeStatus' (which is done in every '_update' loop), the probing process is restarted. + */ -bool MDNSResponder::clsHost::_resetProbeStatus(bool p_bRestart /*= true*/) +bool clsLEAMDNSHost::_resetProbeStatus(bool p_bRestart /*= true*/) { - m_HostProbeInformation.clear(false); - m_HostProbeInformation.m_ProbingStatus = (p_bRestart ? enuProbingStatus::ReadyToStart : enuProbingStatus::Done); + m_ProbeInformation.clear(false); + m_ProbeInformation.m_ProbingStatus = (p_bRestart ? clsProbeInformation_Base::enuProbingStatus::ReadyToStart : clsProbeInformation_Base::enuProbingStatus::DoneFinally); - for (stcService* pService = m_pServices; pService; pService = pService->m_pNext) + for (clsService* pService : m_Services) { pService->m_ProbeInformation.clear(false); - pService->m_ProbeInformation.m_ProbingStatus = (p_bRestart ? enuProbingStatus::ReadyToStart : enuProbingStatus::Done); + pService->m_ProbeInformation.m_ProbingStatus = (p_bRestart ? clsProbeInformation_Base::enuProbingStatus::ReadyToStart : clsProbeInformation_Base::enuProbingStatus::DoneFinally); } return true; } /* - MDNSResponder::clsHost::_hasProbesWaitingForAnswers + clsLEAmDNS2_Host::_hasProbesWaitingForAnswers + */ -bool MDNSResponder::clsHost::_hasProbesWaitingForAnswers(void) const +bool clsLEAMDNSHost::_hasProbesWaitingForAnswers(void) const { - bool bResult = ((enuProbingStatus::InProgress == m_HostProbeInformation.m_ProbingStatus) && // Probing - (0 < m_HostProbeInformation.m_u8SentCount)); // And really probing + bool bResult = ((clsProbeInformation_Base::enuProbingStatus::InProgress == m_ProbeInformation.m_ProbingStatus) && // Probing + (0 < m_ProbeInformation.m_u8SentCount)); // And really probing - for (stcService* pService = m_pServices; ((!bResult) && (pService)); pService = pService->m_pNext) + for (clsService::list::const_iterator it = m_Services.cbegin(); ((!bResult) && (it != m_Services.cend())); it++) { - bResult = ((enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) && // Probing - (0 < pService->m_ProbeInformation.m_u8SentCount)); // And really probing + clsService* pService = *it; + + bResult = ((clsProbeInformation_Base::enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) && // Probing + (0 < pService->m_ProbeInformation.m_u8SentCount)); // And really probing } return bResult; } /* - MDNSResponder::clsHost::_sendHostProbe + clsLEAmDNS2_Host::_sendHostProbe Asks (probes) in the local network for the planned host domain - (eg. esp8266.local) @@ -1474,50 +1462,52 @@ bool MDNSResponder::clsHost::_hasProbesWaitingForAnswers(void) const the 'knwon answers' section of the query. Host domain: - A/AAAA (eg. esp8266.esp -> 192.168.2.120) + */ -bool MDNSResponder::clsHost::_sendHostProbe(void) +bool clsLEAMDNSHost::_sendHostProbe(void) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendHostProbe (%s.local, %lu)\n"), _DH(), m_pcHostName, millis());); bool bResult = true; // Requests for host domain - stcSendParameter sendParameter; + clsSendParameter sendParameter; sendParameter.m_bCacheFlush = false; // RFC 6762 10.2 - sendParameter.m_pQuestions = new stcRRQuestion; - if (((bResult = (0 != sendParameter.m_pQuestions))) && - ((bResult = _buildDomainForHost(m_pcHostName, sendParameter.m_pQuestions->m_Header.m_Domain)))) + clsRRQuestion* pNewRRQuestion = new clsRRQuestion; + if (((bResult = (0 != pNewRRQuestion))) && + ((bResult = _buildDomainForHost(m_pcHostName, pNewRRQuestion->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 + pNewRRQuestion->m_Header.m_Attributes.m_u16Type = DNS_RRTYPE_ANY; + pNewRRQuestion->m_Header.m_Attributes.m_u16Class = (0x8000 | DNS_RRCLASS_IN); // Unicast & INternet + + sendParameter.m_RRQuestions.push_back(pNewRRQuestion); // Add known answers #ifdef MDNS_IPV4_SUPPORT - sendParameter.m_u32HostReplyMask |= static_cast(enuContentFlag::A); // Add A answer + sendParameter.m_u32HostReplyMask |= static_cast(enuContentFlag::A); // Add A answer #endif #ifdef MDNS_IPV6_SUPPORT - sendParameter.m_u32HostReplyMask |= static_cast(enuContentFlag::AAAA); // Add AAAA answer + sendParameter.m_u32HostReplyMask |= static_cast(enuContentFlag::AAAA); // Add AAAA answer #endif } else { DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _sendHostProbe: FAILED to create host question!\n"), _DH());); - if (sendParameter.m_pQuestions) + if (pNewRRQuestion) { - delete sendParameter.m_pQuestions; - sendParameter.m_pQuestions = 0; + delete pNewRRQuestion; + pNewRRQuestion = 0; } } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendHostProbe: FAILED!\n"), _DH());); return ((bResult) && - (_sendMDNSMessage(sendParameter))); + (_sendMessage(sendParameter))); } /* - MDNSResponder::clsHost::_sendServiceProbe + clsLEAmDNS2_Host::_sendServiceProbe Asks (probes) in the local network for the planned service instance domain - (eg. MyESP._http._tcp.local). @@ -1527,25 +1517,27 @@ bool MDNSResponder::clsHost::_sendHostProbe(void) 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::clsHost::_sendServiceProbe(stcService& p_rService) +bool clsLEAMDNSHost::_sendServiceProbe(clsService& p_rService) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendServiceProbe (%s, %lu)\n"), _DH(), _service2String(&p_rService), millis());); bool bResult = true; // Requests for service instance domain - stcSendParameter sendParameter; + clsSendParameter sendParameter; sendParameter.m_bCacheFlush = false; // RFC 6762 10.2 - sendParameter.m_pQuestions = new stcRRQuestion; - if (((bResult = (0 != sendParameter.m_pQuestions))) && - ((bResult = _buildDomainForService(p_rService, true, sendParameter.m_pQuestions->m_Header.m_Domain)))) + clsRRQuestion* pNewRRQuestion = new clsRRQuestion; + if (((bResult = (0 != pNewRRQuestion))) && + ((bResult = _buildDomainForService(p_rService, true, pNewRRQuestion->m_Header.m_Domain)))) { + pNewRRQuestion->m_bUnicast = true; + pNewRRQuestion->m_Header.m_Attributes.m_u16Type = DNS_RRTYPE_ANY; + pNewRRQuestion->m_Header.m_Attributes.m_u16Class = (0x8000 | DNS_RRCLASS_IN); // Unicast & INternet - sendParameter.m_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 + sendParameter.m_RRQuestions.push_back(pNewRRQuestion); // Add known answers p_rService.m_u32ReplyMask = (static_cast(enuContentFlag::SRV) | static_cast(enuContentFlag::PTR_NAME)); // Add SRV and PTR NAME answers @@ -1553,30 +1545,31 @@ bool MDNSResponder::clsHost::_sendServiceProbe(stcService& p_rService) else { DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _sendServiceProbe: FAILED to create service question!\n"), _DH());); - if (sendParameter.m_pQuestions) + if (pNewRRQuestion) { - delete sendParameter.m_pQuestions; - sendParameter.m_pQuestions = 0; + delete pNewRRQuestion; + pNewRRQuestion = 0; } } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendServiceProbe: FAILED!\n"), _DH());); return ((bResult) && - (_sendMDNSMessage(sendParameter))); + (_sendMessage(sendParameter))); } /* - MDNSResponder::clsHost::_cancelProbingForHost + clsLEAmDNS2_Host::_cancelProbingForHost + */ -bool MDNSResponder::clsHost::_cancelProbingForHost(void) +bool clsLEAMDNSHost::_cancelProbingForHost(void) { bool bResult = false; - m_HostProbeInformation.clear(false); + m_ProbeInformation.clear(false); // Send host notification bResult = _callHostProbeResultCallback(false); - for (stcService* pService = m_pServices; ((!bResult) && (pService)); pService = pService->m_pNext) + for (clsService* pService : m_Services) { bResult = _cancelProbingForService(*pService); } @@ -1584,9 +1577,10 @@ bool MDNSResponder::clsHost::_cancelProbingForHost(void) } /* - MDNSResponder::clsHost::_cancelProbingForService + clsLEAmDNS2_Host::_cancelProbingForService + */ -bool MDNSResponder::clsHost::_cancelProbingForService(stcService& p_rService) +bool clsLEAMDNSHost::_cancelProbingForService(clsService& p_rService) { p_rService.m_ProbeInformation.clear(false); @@ -1595,81 +1589,53 @@ bool MDNSResponder::clsHost::_cancelProbingForService(stcService& p_rService) } /* - MDNSResponder::clsHost::_callHostProbeResultCallback + clsLEAmDNS2_Host::_callHostProbeResultCallback */ -bool MDNSResponder::clsHost::_callHostProbeResultCallback(bool p_bResult) +bool clsLEAMDNSHost::_callHostProbeResultCallback(bool p_bResult) { - if (m_HostProbeInformation.m_fnProbeResultCallback) + if (m_ProbeInformation.m_fnProbeResultCallback) { - m_HostProbeInformation.m_fnProbeResultCallback(*this, m_pcHostName, p_bResult); + m_ProbeInformation.m_fnProbeResultCallback(*this, m_pcHostName, p_bResult); } else if (!p_bResult) { // Auto-Handle failure by changing the host name, use '-' as divider between base name and index - char* pcHostDomainTemp = strdup(m_pcHostName); - if (pcHostDomainTemp) - { - if (MDNSResponder::indexDomain(pcHostDomainTemp, "-", 0)) - { - setHostName(pcHostDomainTemp); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _callHostProbeResultCallback: FAILED to update host domain '%s'!\n"), _DH(), (m_pcHostName ? : ""));); - } - free(pcHostDomainTemp); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _callHostProbeResultCallback: FAILED to copy host domain '%s'!\n"), _DH(), (m_pcHostName ? : ""));); - } + indexHostName(); + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _callHostProbeResultCallback: Changed Host Name: %s\n"), _DH(), (m_pcHostName ? : ""))); } return true; } /* - MDNSResponder::clsHost::_callServiceProbeResultCallback + clsLEAmDNS2_Host::_callServiceProbeResultCallback */ -bool MDNSResponder::clsHost::_callServiceProbeResultCallback(MDNSResponder::clsHost::stcService& p_rService, - bool p_bResult) +bool clsLEAMDNSHost::_callServiceProbeResultCallback(clsLEAMDNSHost::clsService& p_rService, + bool p_bResult) { if (p_rService.m_ProbeInformation.m_fnProbeResultCallback) { - p_rService.m_ProbeInformation.m_fnProbeResultCallback(*this, p_rService, p_rService.m_pcName, p_bResult); + p_rService.m_ProbeInformation.m_fnProbeResultCallback(p_rService, p_rService.instanceName(), p_bResult); } else if (!p_bResult) { // Auto-Handle failure by changing the service name, use ' #' as divider between base name and index - char* pcServiceNameTemp = strdup(p_rService.m_pcName); - if (pcServiceNameTemp) - { - if (MDNSResponder::indexDomain(pcServiceNameTemp, " #", 0)) - { - setServiceName(&p_rService, pcServiceNameTemp); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _callServiceProbeResultCallback: FAILED to update service name for '%s'!\n"), _DH(), _service2String(&p_rService));); - } - free(pcServiceNameTemp); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _callServiceProbeResultCallback: FAILED to copy service name for '%s'!\n"), _DH(), _service2String(&p_rService));); - } + p_rService.indexInstanceName(); + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _callServiceProbeResultCallback: Changed Service Domain: %s\n"), _DH(), _service2String(&p_rService))); } return true; } -/** +/* + ANNOUNCING + */ /* - MDNSResponder::clsHost::_announce + clsLEAmDNS2_Host::_announce Announces the host domain: - A/AAAA (eg. esp8266.local -> 192.168.2.120) @@ -1684,18 +1650,19 @@ bool MDNSResponder::clsHost::_callServiceProbeResultCallback(MDNSResponder::clsH 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::clsHost::_announce(bool p_bAnnounce, - bool p_bIncludeServices) +bool clsLEAMDNSHost::_announce(bool p_bAnnounce, + bool p_bIncludeServices) { bool bResult = false; - stcSendParameter sendParameter; - if (enuProbingStatus::Done == m_HostProbeInformation.m_ProbingStatus) + clsSendParameter sendParameter; + if (clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == m_ProbeInformation.m_ProbingStatus) { bResult = true; - sendParameter.m_Response = stcSendParameter::enuResponseType::Unsolicited; // Announces are 'Unsolicited authorative responses' + sendParameter.m_Response = clsSendParameter::enuResponseType::Unsolicited; // Announces are 'Unsolicited authorative responses' sendParameter.m_bAuthorative = true; sendParameter.m_bCacheFlush = true; // RFC 6762 8.3 sendParameter.m_bUnannounce = !p_bAnnounce; // When unannouncing, the TTL is set to '0' while creating the answers @@ -1716,12 +1683,14 @@ bool MDNSResponder::clsHost::_announce(bool p_bAnnounce, if (p_bIncludeServices) { // Announce services (service type, name, SRV (location) and TXTs) - for (stcService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) + for (clsService* pService : m_Services) { - if (enuProbingStatus::Done == pService->m_ProbeInformation.m_ProbingStatus) + if (clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == pService->m_ProbeInformation.m_ProbingStatus) { - pService->m_u32ReplyMask = (static_cast(enuContentFlag::PTR_TYPE) | static_cast(enuContentFlag::PTR_NAME) | static_cast(enuContentFlag::SRV) | static_cast(enuContentFlag::TXT)); - + pService->m_u32ReplyMask = (static_cast(enuContentFlag::PTR_TYPE) | + static_cast(enuContentFlag::PTR_NAME) | + static_cast(enuContentFlag::SRV) | + static_cast(enuContentFlag::TXT)); DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _announce: Announcing service '%s' (content %s)\n"), _DH(), _service2String(pService), _replyFlags2String(pService->m_u32ReplyMask));); } } @@ -1729,22 +1698,22 @@ bool MDNSResponder::clsHost::_announce(bool p_bAnnounce, } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _announce: FAILED!\n"), _DH());); return ((bResult) && - (_sendMDNSMessage(sendParameter))); + (_sendMessage(sendParameter))); } /* - MDNSResponder::clsHost::_announceService + clsLEAmDNS2_Host::_announceService + */ -bool MDNSResponder::clsHost::_announceService(MDNSResponder::clsHost::stcService& p_rService, - bool p_bAnnounce /*= true*/) +bool clsLEAMDNSHost::_announceService(clsLEAMDNSHost::clsService& p_rService, + bool p_bAnnounce /*= true*/) { bool bResult = false; - stcSendParameter sendParameter; - if (enuProbingStatus::Done == p_rService.m_ProbeInformation.m_ProbingStatus) + clsSendParameter sendParameter; + if (clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == p_rService.m_ProbeInformation.m_ProbingStatus) { - - sendParameter.m_Response = stcSendParameter::enuResponseType::Unsolicited; // Announces are 'Unsolicited authorative responses' + sendParameter.m_Response = clsSendParameter::enuResponseType::Unsolicited; // Announces are 'Unsolicited authorative responses' sendParameter.m_bAuthorative = true; sendParameter.m_bUnannounce = !p_bAnnounce; // When unannouncing, the TTL is set to '0' while creating the answers @@ -1752,23 +1721,28 @@ bool MDNSResponder::clsHost::_announceService(MDNSResponder::clsHost::stcService sendParameter.m_u32HostReplyMask = 0; // Announce services (service type, name, SRV (location) and TXTs) - p_rService.m_u32ReplyMask = (static_cast(enuContentFlag::PTR_TYPE) | static_cast(enuContentFlag::PTR_NAME) | static_cast(enuContentFlag::SRV) | static_cast(enuContentFlag::TXT)); + p_rService.m_u32ReplyMask = (static_cast(enuContentFlag::PTR_TYPE) | + static_cast(enuContentFlag::PTR_NAME) | + static_cast(enuContentFlag::SRV) | + static_cast(enuContentFlag::TXT)); DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _announceService: Announcing service '%s' (content: %s)\n"), _DH(), _service2String(&p_rService), _replyFlags2String(p_rService.m_u32ReplyMask));); bResult = true; } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _announceService: FAILED!\n"), _DH());); return ((bResult) && - (_sendMDNSMessage(sendParameter))); + (_sendMessage(sendParameter))); } -/** +/* + QUERY CACHE + */ /* - MDNSResponder::clsHost::_checkQueryCache + clsLEAmDNS2_Host::_checkQueryCache For any 'living' query (m_bAwaitingAnswers == true) all available answers (their components) are checked for topicality based on the stored reception time and the answers TTL. @@ -1776,29 +1750,33 @@ bool MDNSResponder::clsHost::_announceService(MDNSResponder::clsHost::stcService When no update arrived (in time), the component is removed from the answer (cache). */ -bool MDNSResponder::clsHost::_checkQueryCache(void) +bool clsLEAMDNSHost::_checkQueryCache(void) { bool bResult = true; DEBUG_EX_INFO( bool printedInfo = false; ); - for (stcQuery* pQuery = m_pQueries; ((bResult) && (pQuery)); pQuery = pQuery->m_pNext) + for (clsQuery::list::iterator itQ = m_Queries.begin(); ((bResult) && (itQ != m_Queries.end())); itQ++) { + clsQuery* pQuery = *itQ; // // Resend dynamic queries, if not already done often enough - if ((!pQuery->m_bLegacyQuery) && + if ((!pQuery->m_bStaticQuery) && (pQuery->m_ResendTimeout.expired())) { - - if ((bResult = _sendMDNSQuery(*pQuery))) + if ((bResult = _sendQuery(*pQuery))) { // The re-query rate is increased to more than one hour (RFC 6762 5.2) ++pQuery->m_u8SentCount; - uint32_t u32NewDelay = (MDNS_DYNAMIC_QUERY_RESEND_DELAY * pow(2, std::min((pQuery->m_u8SentCount - 1), 12))); + uint32_t u32NewDelay = (clsConsts::u32DynamicQueryResendDelay * pow(2, std::min((pQuery->m_u8SentCount - 1), 12))); DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Next query in %u seconds!\n"), _DH(), (u32NewDelay));); pQuery->m_ResendTimeout.reset(u32NewDelay); } + else + { + break; + } DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: %s to resend query!\n"), _DH(), (bResult ? "Succeeded" : "FAILED")); printedInfo = true; @@ -1809,25 +1787,22 @@ bool MDNSResponder::clsHost::_checkQueryCache(void) // Schedule updates for cached answers if (pQuery->m_bAwaitingAnswers) { - stcQuery::stcAnswer* pSQAnswer = pQuery->m_pAnswers; - while ((bResult) && - (pSQAnswer)) + clsQuery::clsAnswer::list expiredAnswers; + for (clsQuery::clsAnswer::list::iterator itQA = pQuery->m_Answers.begin(); ((bResult) && (itQA != pQuery->m_Answers.end())); itQA++) { - stcQuery::stcAnswer* pNextSQAnswer = pSQAnswer->m_pNext; + clsQuery::clsAnswer* pQAnswer = *itQA; // 1. level answer if ((bResult) && - (pSQAnswer->m_TTLServiceDomain.flagged())) + (pQAnswer->m_TTLServiceDomain.flagged())) { - - if (!pSQAnswer->m_TTLServiceDomain.finalTimeoutLevel()) + if (!pQAnswer->m_TTLServiceDomain.finalTimeoutLevel()) { - - bResult = ((_sendMDNSQuery(*pQuery)) && - (pSQAnswer->m_TTLServiceDomain.restart())); + bResult = ((_sendQuery(*pQuery)) && + (pQAnswer->m_TTLServiceDomain.restart())); DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: PTR update scheduled for "), _DH()); - _printRRDomain(pSQAnswer->m_ServiceDomain); + _printRRDomain(pQAnswer->m_ServiceDomain); DEBUG_OUTPUT.printf_P(PSTR(" %s\n"), (bResult ? "OK" : "FAILURE")); printedInfo = true; ); @@ -1835,16 +1810,15 @@ bool MDNSResponder::clsHost::_checkQueryCache(void) else { // Timed out! -> Delete - _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::ServiceDomain), false); + _executeQueryCallback(*pQuery, *pQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain), false); DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Will remove PTR answer for "), _DH()); - _printRRDomain(pSQAnswer->m_ServiceDomain); + _printRRDomain(pQAnswer->m_ServiceDomain); DEBUG_OUTPUT.printf_P(PSTR("\n")); printedInfo = true; ); - bResult = pQuery->removeAnswer(pSQAnswer); - pSQAnswer = 0; + expiredAnswers.push_back(pQAnswer); continue; // Don't use this answer anymore } } // ServiceDomain flagged @@ -1852,17 +1826,15 @@ bool MDNSResponder::clsHost::_checkQueryCache(void) // 2. level answers // HostDomain & Port (from SRV) if ((bResult) && - (pSQAnswer->m_TTLHostDomainAndPort.flagged())) + (pQAnswer->m_TTLHostDomainAndPort.flagged())) { - - if (!pSQAnswer->m_TTLHostDomainAndPort.finalTimeoutLevel()) + if (!pQAnswer->m_TTLHostDomainAndPort.finalTimeoutLevel()) { - - bResult = ((_sendMDNSQuery(pSQAnswer->m_ServiceDomain, DNS_RRTYPE_SRV)) && - (pSQAnswer->m_TTLHostDomainAndPort.restart())); + bResult = ((_sendQuery(pQAnswer->m_ServiceDomain, DNS_RRTYPE_SRV)) && + (pQAnswer->m_TTLHostDomainAndPort.restart())); DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: SRV update scheduled for "), _DH()); - _printRRDomain(pSQAnswer->m_ServiceDomain); + _printRRDomain(pQAnswer->m_ServiceDomain); DEBUG_OUTPUT.printf_P(PSTR(" host domain and port %s\n"), (bResult ? "OK" : "FAILURE")); printedInfo = true; ); @@ -1872,45 +1844,43 @@ bool MDNSResponder::clsHost::_checkQueryCache(void) // Timed out! -> Delete DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Will remove SRV answer for "), _DH()); - _printRRDomain(pSQAnswer->m_ServiceDomain); + _printRRDomain(pQAnswer->m_ServiceDomain); DEBUG_OUTPUT.printf_P(PSTR(" host domain and port\n")); printedInfo = true; ); // Delete - pSQAnswer->m_HostDomain.clear(); + pQAnswer->m_HostDomain.clear(); //pSQAnswer->releaseHostDomain(); - pSQAnswer->m_u16Port = 0; - pSQAnswer->m_TTLHostDomainAndPort.set(0); - typeQueryAnswerType queryAnswerContentFlags = (static_cast(enuQueryAnswerType::HostDomain) | static_cast(enuQueryAnswerType::Port)); + pQAnswer->m_u16Port = 0; + pQAnswer->m_TTLHostDomainAndPort.set(0); + clsQuery::clsAnswer::typeQueryAnswerType queryAnswerContentFlags = (static_cast(clsQuery::clsAnswer::enuQueryAnswerType::HostDomain) | static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Port)); // As the host domain is the base for the IPv4- and IPv6Address, remove these too #ifdef MDNS_IPV4_SUPPORT - pSQAnswer->releaseIPv4Addresses(); - queryAnswerContentFlags |= static_cast(enuQueryAnswerType::IPv4Address); + pQAnswer->releaseIPv4Addresses(); + queryAnswerContentFlags |= static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address); #endif #ifdef MDNS_IPV6_SUPPORT - pSQAnswer->releaseIPv6Addresses(); - queryAnswerContentFlags |= static_cast(enuQueryAnswerType::IPv6Address); + pQAnswer->releaseIPv6Addresses(); + queryAnswerContentFlags |= static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address); #endif // Remove content flags for deleted answer parts - pSQAnswer->m_QueryAnswerFlags &= ~queryAnswerContentFlags; - _executeQueryCallback(*pQuery, *pSQAnswer, queryAnswerContentFlags, false); + pQAnswer->m_QueryAnswerFlags &= ~queryAnswerContentFlags; + _executeQueryCallback(*pQuery, *pQAnswer, queryAnswerContentFlags, false); } } // HostDomainAndPort flagged // Txts (from TXT) if ((bResult) && - (pSQAnswer->m_TTLTxts.flagged())) + (pQAnswer->m_TTLTxts.flagged())) { - - if (!pSQAnswer->m_TTLTxts.finalTimeoutLevel()) + if (!pQAnswer->m_TTLTxts.finalTimeoutLevel()) { - - bResult = ((_sendMDNSQuery(pSQAnswer->m_ServiceDomain, DNS_RRTYPE_TXT)) && - (pSQAnswer->m_TTLTxts.restart())); + bResult = ((_sendQuery(pQAnswer->m_ServiceDomain, DNS_RRTYPE_TXT)) && + (pQAnswer->m_TTLTxts.restart())); DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: TXT update scheduled for "), _DH()); - _printRRDomain(pSQAnswer->m_ServiceDomain); + _printRRDomain(pQAnswer->m_ServiceDomain); DEBUG_OUTPUT.printf_P(PSTR(" TXTs %s\n"), (bResult ? "OK" : "FAILURE")); printedInfo = true; ); @@ -1920,47 +1890,43 @@ bool MDNSResponder::clsHost::_checkQueryCache(void) // Timed out! -> Delete DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Will remove TXT answer for "), _DH()); - _printRRDomain(pSQAnswer->m_ServiceDomain); + _printRRDomain(pQAnswer->m_ServiceDomain); DEBUG_OUTPUT.printf_P(PSTR(" TXTs\n")); printedInfo = true; ); // Delete - pSQAnswer->m_Txts.clear(); - pSQAnswer->m_TTLTxts.set(0); + pQAnswer->m_Txts.clear(); + pQAnswer->m_TTLTxts.set(0); // Remove content flags for deleted answer parts - pSQAnswer->m_QueryAnswerFlags &= ~static_cast(enuQueryAnswerType::Txts); - _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::Txts), false); + pQAnswer->m_QueryAnswerFlags &= ~static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Txts); + _executeQueryCallback(*pQuery, *pQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Txts), false); } } // TXTs flagged // 3. level answers #ifdef MDNS_IPV4_SUPPORT // IPv4Address (from A) - stcQuery::stcAnswer::stcIPAddress* pIPv4Address = pSQAnswer->m_pIPv4Addresses; + clsQuery::clsAnswer::clsIPAddressWithTTL::list expiredIPv4Addresses; bool bAUpdateQuerySent = false; - while ((pIPv4Address) && - (bResult)) + for (clsQuery::clsAnswer::clsIPAddressWithTTL::list::iterator itQAIPv4 = pQAnswer->m_IPv4Addresses.begin(); ((bResult) && (itQAIPv4 != pQAnswer->m_IPv4Addresses.end())); itQAIPv4++) { - - stcQuery::stcAnswer::stcIPAddress* pNextIPv4Address = pIPv4Address->m_pNext; // Get 'next' early, as 'current' may be deleted at the end... + clsQuery::clsAnswer::clsIPAddressWithTTL* pIPv4Address = *itQAIPv4; if (pIPv4Address->m_TTL.flagged()) { - - if (!pIPv4Address->m_TTL.finalTimeoutLevel()) // Needs update + if (!pIPv4Address->m_TTL.finalTimeoutLevel()) { - + // Needs update if ((bAUpdateQuerySent) || - ((bResult = _sendMDNSQuery(pSQAnswer->m_HostDomain, DNS_RRTYPE_A)))) + ((bResult = _sendQuery(pQAnswer->m_HostDomain, DNS_RRTYPE_A)))) { - pIPv4Address->m_TTL.restart(); bAUpdateQuerySent = true; DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: IPv4 update scheduled for "), _DH()); - _printRRDomain(pSQAnswer->m_ServiceDomain); + _printRRDomain(pQAnswer->m_ServiceDomain); DEBUG_OUTPUT.printf_P(PSTR(" IPv4 address (%s)\n"), (pIPv4Address->m_IPAddress.toString().c_str())); printedInfo = true; ); @@ -1971,49 +1937,49 @@ bool MDNSResponder::clsHost::_checkQueryCache(void) // Timed out! -> Delete DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Will remove IPv4 answer for "), _DH()); - _printRRDomain(pSQAnswer->m_ServiceDomain); + _printRRDomain(pQAnswer->m_ServiceDomain); DEBUG_OUTPUT.printf_P(PSTR(" IPv4 address\n")); printedInfo = true; ); - pSQAnswer->removeIPv4Address(pIPv4Address); - if (!pSQAnswer->m_pIPv4Addresses) // NO IPv4 address left -> remove content flag + if (1 == pQAnswer->m_IPv4Addresses.size()) { - pSQAnswer->m_QueryAnswerFlags &= ~static_cast(enuQueryAnswerType::IPv4Address); + // NO IPv4 address left after this -> remove content flag + pQAnswer->m_QueryAnswerFlags &= ~static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address); } // Notify client - _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::IPv4Address), false); + _executeQueryCallback(*pQuery, *pQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address), false); + expiredIPv4Addresses.push_back(pIPv4Address); } } // IPv4 flagged - - pIPv4Address = pNextIPv4Address; // Next - } // while + } // for + // Finally remove expired IPv4 addresses + for (clsQuery::clsAnswer::clsIPAddressWithTTL* pIPv4Address : expiredIPv4Addresses) + { + pQAnswer->removeIPv4Address(pIPv4Address); + } #endif #ifdef MDNS_IPV6_SUPPORT // IPv6Address (from AAAA) - stcQuery::stcAnswer::stcIPAddress* pIPv6Address = pSQAnswer->m_pIPv6Addresses; + clsQuery::clsAnswer::clsIPAddressWithTTL::list expiredIPv6Addresses; bool bAAAAUpdateQuerySent = false; - while ((pIPv6Address) && - (bResult)) + for (clsQuery::clsAnswer::clsIPAddressWithTTL::list::iterator itQAIPv6 = pQAnswer->m_IPv6Addresses.begin(); ((bResult) && (itQAIPv6 != pQAnswer->m_IPv6Addresses.end())); itQAIPv6++) { - - stcQuery::stcAnswer::stcIPAddress* pNextIPv6Address = pIPv6Address->m_pNext; // Get 'next' early, as 'current' may be deleted at the end... + clsQuery::clsAnswer::clsIPAddressWithTTL* pIPv6Address = *itQAIPv6; if (pIPv6Address->m_TTL.flagged()) { - - if (!pIPv6Address->m_TTL.finalTimeoutLevel()) // Needs update + if (!pIPv6Address->m_TTL.finalTimeoutLevel()) { - + // Needs update if ((bAAAAUpdateQuerySent) || - ((bResult = _sendMDNSQuery(pSQAnswer->m_HostDomain, DNS_RRTYPE_AAAA)))) + ((bResult = _sendQuery(pQAnswer->m_HostDomain, DNS_RRTYPE_AAAA)))) { - pIPv6Address->m_TTL.restart(); bAAAAUpdateQuerySent = true; DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: IPv6 update scheduled for "), _DH()); - _printRRDomain(pSQAnswer->m_ServiceDomain); + _printRRDomain(pQAnswer->m_ServiceDomain); DEBUG_OUTPUT.printf_P(PSTR(" IPv6 address (%s)\n"), (pIPv6Address->m_IPAddress.toString().c_str())); printedInfo = true; ); @@ -2024,24 +1990,33 @@ bool MDNSResponder::clsHost::_checkQueryCache(void) // Timed out! -> Delete DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Will remove answer for "), _DH()); - _printRRDomain(pSQAnswer->m_ServiceDomain); + _printRRDomain(pQAnswer->m_ServiceDomain); DEBUG_OUTPUT.printf_P(PSTR(" IPv6 address\n")); printedInfo = true; ); - pSQAnswer->removeIPv6Address(pIPv6Address); - if (!pSQAnswer->m_pIPv6Addresses) // NO IPv6 address left -> remove content flag + if (1 == pQAnswer->m_IPv6Addresses.size()) { - pSQAnswer->m_QueryAnswerFlags &= ~static_cast(enuQueryAnswerType::IPv6Address); + // NO IPv6 address left after this -> remove content flag + pQAnswer->m_QueryAnswerFlags &= ~static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address); } // Notify client - _executeQueryCallback(*pQuery, *pSQAnswer, static_cast(enuQueryAnswerType::IPv6Address), false); + _executeQueryCallback(*pQuery, *pQAnswer, static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address), false); + expiredIPv6Addresses.push_back(pIPv6Address); } } // IPv6 flagged - - pIPv6Address = pNextIPv6Address; // Next + // Finally remove expired IPv6 addresses + for (clsQuery::clsAnswer::clsIPAddressWithTTL* pIPv6Address : expiredIPv6Addresses) + { + pQAnswer->removeIPv6Address(pIPv6Address); + } } // while #endif - pSQAnswer = pNextSQAnswer; + } + + // Finally remove expired answers + for (clsQuery::clsAnswer* pAnswer : expiredAnswers) + { + pQuery->removeAnswer(pAnswer); } } } @@ -2052,7 +2027,7 @@ bool MDNSResponder::clsHost::_checkQueryCache(void) /* - MDNSResponder::clsHost::_replyMaskForHost + clsLEAmDNS2_Host::_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. @@ -2060,8 +2035,8 @@ bool MDNSResponder::clsHost::_checkQueryCache(void) In addition, a full name match (question domain == host domain) is marked. */ -uint32_t MDNSResponder::clsHost::_replyMaskForHost(const MDNSResponder::clsHost::stcRRHeader& p_RRHeader, - bool* p_pbFullNameMatch /*= 0*/) const +uint32_t clsLEAMDNSHost::_replyMaskForHost(const clsLEAMDNSHost::clsRRHeader& p_RRHeader, + bool* p_pbFullNameMatch /*= 0*/) const { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _replyMaskForHost\n"));); @@ -2077,7 +2052,7 @@ uint32_t MDNSResponder::clsHost::_replyMaskForHost(const MDNSResponder::clsHost: { // PTR request #ifdef MDNS_IPV4_SUPPORT - stcRRDomain reverseIPv4Domain; + clsRRDomain reverseIPv4Domain; if ((_getResponderIPAddress(enuIPProtocolType::V4).isSet()) && (_buildDomainForReverseIPv4(_getResponderIPAddress(enuIPProtocolType::V4), reverseIPv4Domain)) && (p_RRHeader.m_Domain == reverseIPv4Domain)) @@ -2087,7 +2062,7 @@ uint32_t MDNSResponder::clsHost::_replyMaskForHost(const MDNSResponder::clsHost: } #endif #ifdef MDNS_IPV6_SUPPORT - stcRRDomain reverseIPv6Domain; + clsRRDomain reverseIPv6Domain; if ((_getResponderIPAddress(enuIPProtocolType::V6).isSet()) && (_buildDomainForReverseIPv6(_getResponderIPAddress(enuIPProtocolType::V6), reverseIPv6Domain)) && (p_RRHeader.m_Domain == reverseIPv6Domain)) @@ -2098,11 +2073,10 @@ uint32_t MDNSResponder::clsHost::_replyMaskForHost(const MDNSResponder::clsHost: #endif } // Address qeuest - stcRRDomain hostDomain; + clsRRDomain hostDomain; if ((_buildDomainForHost(m_pcHostName, hostDomain)) && (p_RRHeader.m_Domain == hostDomain)) // Host domain match { - (p_pbFullNameMatch ? (*p_pbFullNameMatch = true) : (0)); #ifdef MDNS_IPV4_SUPPORT @@ -2132,7 +2106,7 @@ uint32_t MDNSResponder::clsHost::_replyMaskForHost(const MDNSResponder::clsHost: } /* - MDNSResponder::clsHost::_replyMaskForService + clsLEAmDNS2_Host::_replyMaskForService Determines the relevant service answers for the given question - A PTR dns-sd service enum question (_services.dns-sd._udp.local) will result into an PTR_TYPE (eg. _http._tcp.local) answer @@ -2142,10 +2116,11 @@ uint32_t MDNSResponder::clsHost::_replyMaskForHost(const MDNSResponder::clsHost: - A TXT service name question (eg. MyESP._http._tcp.local) will result into an TXT (eg. c#=1) answer In addition, a full name match (question domain == service instance domain) is marked. + */ -uint32_t MDNSResponder::clsHost::_replyMaskForService(const MDNSResponder::clsHost::stcRRHeader& p_RRHeader, - const MDNSResponder::clsHost::stcService& p_Service, - bool* p_pbFullNameMatch /*= 0*/) const +uint32_t clsLEAMDNSHost::_replyMaskForService(const clsLEAMDNSHost::clsRRHeader& p_RRHeader, + clsLEAMDNSHost::clsService& p_rService, + bool* p_pbFullNameMatch /*= 0*/) { uint32_t u32ReplyMask = 0; (p_pbFullNameMatch ? *p_pbFullNameMatch = false : 0); @@ -2153,8 +2128,7 @@ uint32_t MDNSResponder::clsHost::_replyMaskForService(const MDNSResponder::clsHo if ((DNS_RRCLASS_IN == (p_RRHeader.m_Attributes.m_u16Class & (~0x8000))) || (DNS_RRCLASS_ANY == (p_RRHeader.m_Attributes.m_u16Class & (~0x8000)))) { - - stcRRDomain DNSSDDomain; + clsRRDomain DNSSDDomain; if ((_buildDomainForDNSSD(DNSSDDomain)) && // _services._dns-sd._udp.local (p_RRHeader.m_Domain == DNSSDDomain) && ((DNS_RRTYPE_PTR == p_RRHeader.m_Attributes.m_u16Type) || @@ -2164,8 +2138,8 @@ uint32_t MDNSResponder::clsHost::_replyMaskForService(const MDNSResponder::clsHo u32ReplyMask |= static_cast(enuContentFlag::PTR_TYPE); } - stcRRDomain serviceDomain; - if ((_buildDomainForService(p_Service, false, serviceDomain)) && // eg. _http._tcp.local + clsRRDomain serviceDomain; + if ((_buildDomainForService(p_rService, false, serviceDomain)) && // eg. _http._tcp.local (p_RRHeader.m_Domain == serviceDomain) && ((DNS_RRTYPE_PTR == p_RRHeader.m_Attributes.m_u16Type) || (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type))) @@ -2174,10 +2148,9 @@ uint32_t MDNSResponder::clsHost::_replyMaskForService(const MDNSResponder::clsHo u32ReplyMask |= static_cast(enuContentFlag::PTR_NAME); } - if ((_buildDomainForService(p_Service, true, serviceDomain)) && // eg. MyESP._http._tcp.local + if ((_buildDomainForService(p_rService, true, serviceDomain)) && // eg. MyESP._http._tcp.local (p_RRHeader.m_Domain == serviceDomain)) { - (p_pbFullNameMatch ? (*p_pbFullNameMatch = true) : (0)); if ((DNS_RRTYPE_SRV == p_RRHeader.m_Attributes.m_u16Type) || @@ -2198,10 +2171,15 @@ uint32_t MDNSResponder::clsHost::_replyMaskForService(const MDNSResponder::clsHo { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _replyMaskForService: INVALID RR-class (0x%04X)!\n"), p_RRHeader.m_Attributes.m_u16Class);); } - DEBUG_EX_INFO(if (u32ReplyMask) DEBUG_OUTPUT.printf_P(PSTR("%s _replyMaskForService(%s.%s.%s): %s\n"), _DH(), p_Service.m_pcName, p_Service.m_pcServiceType, p_Service.m_pcProtocol, _replyFlags2String(u32ReplyMask));); + DEBUG_EX_INFO(if (u32ReplyMask) DEBUG_OUTPUT.printf_P(PSTR("%s _replyMaskForService(%s.%s.%s): %s\n"), _DH(), p_rService.m_pcInstanceName, p_rService.m_pcType, p_rService.m_pcProtocol, _replyFlags2String(u32ReplyMask));); return u32ReplyMask; } + } // namespace MDNSImplementation + } // namespace esp8266 + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Debug.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp old mode 100755 new mode 100644 similarity index 65% rename from libraries/ESP8266mDNS/src/LEAmDNS2_Host_Debug.cpp rename to libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp index 3f43a54089..434f61a0ea --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Debug.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp @@ -1,5 +1,5 @@ /* - LEAmDNS2_Host_Debug.h + LEAmDNS2Host_Debug.h License (MIT license): Permission is hereby granted, free of charge, to any person obtaining a copy @@ -22,88 +22,70 @@ */ -//#include -//#include -//#include -//#include -//#include -//#include -//#include -//#include - -/* - ESP8266mDNS Control.cpp -*/ - -extern "C" { - //#include "user_interface.h" -} - -#include "LEAmDNS2_lwIPdefs.h" -#include "LEAmDNS2_Priv.h" - -#ifdef MDNS_IPV4_SUPPORT -//#include -#endif -#ifdef MDNS_IPV6_SUPPORT -//#include -#endif +#include "LEAmDNS2Host.h" namespace esp8266 { -/* - LEAmDNS -*/ + namespace experimental { + #ifdef DEBUG_ESP_MDNS_RESPONDER /* - MDNSResponder::clsHost::_DH + clsLEAmDNS2_Host::_DH + */ -const char* MDNSResponder::clsHost::_DH(const MDNSResponder::clsHost::stcService* p_pMDNSService /*= 0*/) const +const char* clsLEAMDNSHost::_DH(const clsLEAMDNSHost::clsService* p_pService /*= 0*/) const { static char acBuffer[16 + 64]; *acBuffer = 0; - if (p_pMDNSService) + if (m_pNetIf) { - sprintf_P(acBuffer, PSTR("[MDNSResponder (%c%c%u)->%s]"), m_rNetIf.name[0], m_rNetIf.name[1], m_rNetIf.num, _service2String(p_pMDNSService)); + sprintf_P(acBuffer, PSTR("[mDNS %c%c%u]"), m_pNetIf->name[0], m_pNetIf->name[1], m_pNetIf->num); + if (p_pService) + { + strcat_P(acBuffer, PSTR(">")); + strcat(acBuffer, _service2String(p_pService)); + } } else { - sprintf_P(acBuffer, PSTR("[MDNSResponder (%c%c%u)]"), m_rNetIf.name[0], m_rNetIf.name[1], m_rNetIf.num); + strcpy_P(acBuffer, PSTR("[mDNS]")); } return acBuffer; } /* - MDNSResponder::clsHost::_service2String + clsLEAmDNS2_Host::_service2String + */ -const char* MDNSResponder::clsHost::_service2String(const MDNSResponder::clsHost::stcService* p_pMDNSService) const +const char* clsLEAMDNSHost::_service2String(const clsLEAMDNSHost::clsService* p_pService) const { static char acBuffer[64]; *acBuffer = 0; - if (p_pMDNSService) + if (p_pService) { sprintf_P(acBuffer, PSTR("%s.%s%s.%s%s.local"), - (p_pMDNSService->m_pcName ? : "-"), - (p_pMDNSService->m_pcServiceType ? ('_' == *(p_pMDNSService->m_pcServiceType) ? "" : "_") : "-"), - (p_pMDNSService->m_pcServiceType ? : "-"), - (p_pMDNSService->m_pcProtocol ? ('_' == *(p_pMDNSService->m_pcProtocol) ? "" : "_") : "-"), - (p_pMDNSService->m_pcProtocol ? : "-")); + (p_pService->m_pcInstanceName ? : "-"), + (p_pService->m_pcType ? ('_' == *(p_pService->m_pcType) ? "" : "_") : "-"), + (p_pService->m_pcType ? : "-"), + (p_pService->m_pcProtocol ? ('_' == *(p_pService->m_pcProtocol) ? "" : "_") : "-"), + (p_pService->m_pcProtocol ? : "-")); } return acBuffer; } /* - MDNSResponder::clsHost::_printRRDomain + clsLEAmDNS2_Host::_printRRDomain + */ -bool MDNSResponder::clsHost::_printRRDomain(const MDNSResponder::clsHost::stcRRDomain& p_RRDomain) const +bool clsLEAMDNSHost::_printRRDomain(const clsLEAMDNSHost::clsRRDomain& p_RRDomain) const { //DEBUG_OUTPUT.printf_P(PSTR("Domain: ")); @@ -134,9 +116,10 @@ bool MDNSResponder::clsHost::_printRRDomain(const MDNSResponder::clsHost::stcRRD } /* - MDNSResponder::clsHost::_printRRAnswer + clsLEAmDNS2_Host::_printRRAnswer + */ -bool MDNSResponder::clsHost::_printRRAnswer(const MDNSResponder::clsHost::stcRRAnswer& p_RRAnswer) const +bool clsLEAMDNSHost::_printRRAnswer(const clsLEAMDNSHost::clsRRAnswer& p_RRAnswer) const { DEBUG_OUTPUT.printf_P(PSTR("%s RRAnswer: "), _DH()); _printRRDomain(p_RRAnswer.m_Header.m_Domain); @@ -145,20 +128,20 @@ bool MDNSResponder::clsHost::_printRRAnswer(const MDNSResponder::clsHost::stcRRA { #ifdef MDNS_IPV4_SUPPORT case DNS_RRTYPE_A: - DEBUG_OUTPUT.printf_P(PSTR("A IP:%s"), ((const stcRRAnswerA*)&p_RRAnswer)->m_IPAddress.toString().c_str()); + DEBUG_OUTPUT.printf_P(PSTR("A IP:%s"), ((const clsRRAnswerA*)&p_RRAnswer)->m_IPAddress.toString().c_str()); break; #endif case DNS_RRTYPE_PTR: DEBUG_OUTPUT.printf_P(PSTR("PTR ")); - _printRRDomain(((const stcRRAnswerPTR*)&p_RRAnswer)->m_PTRDomain); + _printRRDomain(((const clsRRAnswerPTR*)&p_RRAnswer)->m_PTRDomain); break; case DNS_RRTYPE_TXT: { - size_t stTxtLength = ((const stcRRAnswerTXT*)&p_RRAnswer)->m_Txts.c_strLength(); + size_t stTxtLength = ((const clsRRAnswerTXT*)&p_RRAnswer)->m_Txts.c_strLength(); char* pTxts = new char[stTxtLength]; if (pTxts) { - ((/*const c_str()!!*/stcRRAnswerTXT*)&p_RRAnswer)->m_Txts.c_str(pTxts); + ((/*const c_str()!!*/clsRRAnswerTXT*)&p_RRAnswer)->m_Txts.c_str(pTxts); DEBUG_OUTPUT.printf_P(PSTR("TXT(%u) %s"), stTxtLength, pTxts); delete[] pTxts; } @@ -166,12 +149,12 @@ bool MDNSResponder::clsHost::_printRRAnswer(const MDNSResponder::clsHost::stcRRA } #ifdef MDNS_IPV6_SUPPORT case DNS_RRTYPE_AAAA: - DEBUG_OUTPUT.printf_P(PSTR("AAAA IP:%s"), ((stcRRAnswerAAAA*&)p_RRAnswer)->m_IPAddress.toString().c_str()); + DEBUG_OUTPUT.printf_P(PSTR("AAAA IP:%s"), ((clsRRAnswerAAAA*&)p_RRAnswer)->m_IPAddress.toString().c_str()); break; #endif case DNS_RRTYPE_SRV: - DEBUG_OUTPUT.printf_P(PSTR("SRV Port:%u "), ((const stcRRAnswerSRV*)&p_RRAnswer)->m_u16Port); - _printRRDomain(((const stcRRAnswerSRV*)&p_RRAnswer)->m_SRVDomain); + DEBUG_OUTPUT.printf_P(PSTR("SRV Port:%u "), ((const clsRRAnswerSRV*)&p_RRAnswer)->m_u16Port); + _printRRDomain(((const clsRRAnswerSRV*)&p_RRAnswer)->m_SRVDomain); break; default: DEBUG_OUTPUT.printf_P(PSTR("generic ")); @@ -183,33 +166,38 @@ bool MDNSResponder::clsHost::_printRRAnswer(const MDNSResponder::clsHost::stcRRA } /* - MDNSResponder::clsHost::_RRType2Name + clsLEAmDNS2_Host::_RRType2Name + */ -const char* MDNSResponder::clsHost::_RRType2Name(uint16_t p_u16RRType) const +const char* clsLEAMDNSHost::_RRType2Name(uint16_t p_u16RRType) const { static char acRRName[16]; *acRRName = 0; switch (p_u16RRType & (~0x8000)) // Topmost bit might carry 'cache flush' flag { - case DNS_RRTYPE_A: strcpy(acRRName, "A"); break; - case DNS_RRTYPE_PTR: strcpy(acRRName, "PTR"); break; - case DNS_RRTYPE_TXT: strcpy(acRRName, "TXT"); break; - case DNS_RRTYPE_AAAA: strcpy(acRRName, "AAAA"); break; - case DNS_RRTYPE_SRV: strcpy(acRRName, "SRV"); break; - case DNS_RRTYPE_NSEC: strcpy(acRRName, "NSEC"); break; - case DNS_RRTYPE_ANY: strcpy(acRRName, "ANY"); break; - default: - sprintf(acRRName, "Unknown(0x%04X)", p_u16RRType); // MAX 15! +#ifdef MDNS_IPV4_SUPPORT + case DNS_RRTYPE_A: strcpy(acRRName, "A"); break; +#endif + case DNS_RRTYPE_PTR: strcpy(acRRName, "PTR"); break; + case DNS_RRTYPE_TXT: strcpy(acRRName, "TXT"); break; +#ifdef MDNS_IPV6_SUPPORT + case DNS_RRTYPE_AAAA: strcpy(acRRName, "AAAA"); break; +#endif + case DNS_RRTYPE_SRV: strcpy(acRRName, "SRV"); break; + case clsConsts::u8DNS_RRTYPE_NSEC: strcpy(acRRName, "NSEC"); break; + case DNS_RRTYPE_ANY: strcpy(acRRName, "ANY"); break; + default: sprintf(acRRName, "Unknown(0x%04X)", p_u16RRType); // MAX 15! } return acRRName; } /* - MDNSResponder::clsHost::_RRClass2String + clsLEAmDNS2_Host::_RRClass2String + */ -const char* MDNSResponder::clsHost::_RRClass2String(uint16_t p_u16RRClass, - bool p_bIsQuery) const +const char* clsLEAMDNSHost::_RRClass2String(uint16_t p_u16RRClass, + bool p_bIsQuery) const { static char acClassString[16]; *acClassString = 0; @@ -227,9 +215,10 @@ const char* MDNSResponder::clsHost::_RRClass2String(uint16_t p_u16RRClass, } /* - MDNSResponder::clsHost::_replyFlags2String + clsLEAmDNS2_Host::_replyFlags2String + */ -const char* MDNSResponder::clsHost::_replyFlags2String(uint32_t p_u32ReplyFlags) const +const char* clsLEAMDNSHost::_replyFlags2String(uint32_t p_u32ReplyFlags) const { static char acFlagsString[64]; @@ -276,29 +265,41 @@ const char* MDNSResponder::clsHost::_replyFlags2String(uint32_t p_u32ReplyFlags) strcpy(acFlagsString, "none"); } + // Remove trailing spaces + while ((*acFlagsString) && + (' ' == acFlagsString[strlen(acFlagsString) - 1])) + { + acFlagsString[strlen(acFlagsString) - 1] = 0; + } + return acFlagsString; // 63 } /* - MDNSResponder::clsHost::_NSECBitmap2String + clsLEAmDNS2_Host::_NSECBitmap2String + */ -const char* MDNSResponder::clsHost::_NSECBitmap2String(const stcNSECBitmap* p_pNSECBitmap) const +const char* clsLEAMDNSHost::_NSECBitmap2String(const clsNSECBitmap* p_pNSECBitmap) const { static char acFlagsString[32]; *acFlagsString = 0; +#ifdef MDNS_IPV4_SUPPORT if (p_pNSECBitmap->getBit(DNS_RRTYPE_A)) { strcat(acFlagsString, "A "); // 2 } +#endif if (p_pNSECBitmap->getBit(DNS_RRTYPE_PTR)) { strcat(acFlagsString, "PTR "); // 4 } +#ifdef MDNS_IPV6_SUPPORT if (p_pNSECBitmap->getBit(DNS_RRTYPE_AAAA)) { strcat(acFlagsString, "AAAA "); // 5 } +#endif if (p_pNSECBitmap->getBit(DNS_RRTYPE_TXT)) { strcat(acFlagsString, "TXT "); // 4 @@ -307,7 +308,7 @@ const char* MDNSResponder::clsHost::_NSECBitmap2String(const stcNSECBitmap* p_pN { strcat(acFlagsString, "SRV "); // 4 } - if (p_pNSECBitmap->getBit(DNS_RRTYPE_NSEC)) + if (p_pNSECBitmap->getBit(clsConsts::u8DNS_RRTYPE_NSEC)) { strcat(acFlagsString, "NSEC "); // 5 } @@ -322,6 +323,12 @@ const char* MDNSResponder::clsHost::_NSECBitmap2String(const stcNSECBitmap* p_pN #endif // DEBUG_ESP_MDNS_RESPONDER + } // namespace MDNSImplementation + } // namespace esp8266 + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp new file mode 100644 index 0000000000..b4db3c0531 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -0,0 +1,3253 @@ +/* + LEAmDNS2Host_Structs.cpp + + License (MIT license): + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#include "LEAmDNS2Host.h" + + +namespace esp8266 +{ + + +namespace experimental +{ + + +/** + Internal CLASSES & STRUCTS +*/ + +/** + clsLEAMDNSHost::clsServiceTxt + + One MDNS TXT item. + m_pcValue may be '\0'. + Objects can be chained together (list). + A 'm_bTemp' flag differentiates between static and dynamic items. + Output as byte array 'c#=1' is supported. +*/ + +/* + clsLEAMDNSHost::clsServiceTxt::clsServiceTxt constructor + +*/ +clsLEAMDNSHost::clsServiceTxt::clsServiceTxt(const char* p_pcKey /*= 0*/, + const char* p_pcValue /*= 0*/, + bool p_bTemp /*= false*/) + : m_pcKey(0), + m_pcValue(0), + m_bTemp(p_bTemp) +{ + setKey(p_pcKey); + setValue(p_pcValue); +} + +/* + clsLEAMDNSHost::clsServiceTxt::clsServiceTxt copy-constructor + +*/ +clsLEAMDNSHost::clsServiceTxt::clsServiceTxt(const clsLEAMDNSHost::clsServiceTxt& p_Other) + : m_pcKey(0), + m_pcValue(0), + m_bTemp(false) +{ + operator=(p_Other); +} + +/* + clsLEAMDNSHost::clsServiceTxt::~stcServiceTxt destructor + +*/ +clsLEAMDNSHost::clsServiceTxt::~clsServiceTxt(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsServiceTxt::operator= + +*/ +clsLEAMDNSHost::clsServiceTxt& clsLEAMDNSHost::clsServiceTxt::operator=(const clsLEAMDNSHost::clsServiceTxt& p_Other) +{ + if (&p_Other != this) + { + clear(); + set(p_Other.m_pcKey, p_Other.m_pcValue, p_Other.m_bTemp); + } + return *this; +} + +/* + clsLEAMDNSHost::clsServiceTxt::clear + +*/ +bool clsLEAMDNSHost::clsServiceTxt::clear(void) +{ + releaseKey(); + releaseValue(); + return true; +} + +/* + clsLEAMDNSHost::clsServiceTxt::allocKey + +*/ +char* clsLEAMDNSHost::clsServiceTxt::allocKey(size_t p_stLength) +{ + releaseKey(); + if (p_stLength) + { + m_pcKey = new char[p_stLength + 1]; + } + return m_pcKey; +} + +/* + clsLEAMDNSHost::clsServiceTxt::setKey + +*/ +bool clsLEAMDNSHost::clsServiceTxt::setKey(const char* p_pcKey, + size_t p_stLength) +{ + bool bResult = false; + + releaseKey(); + if (p_stLength) + { + if (allocKey(p_stLength)) + { + strncpy(m_pcKey, p_pcKey, p_stLength); + m_pcKey[p_stLength] = 0; + bResult = true; + } + } + return bResult; +} + +/* + clsLEAMDNSHost::clsServiceTxt::setKey + +*/ +bool clsLEAMDNSHost::clsServiceTxt::setKey(const char* p_pcKey) +{ + return setKey(p_pcKey, (p_pcKey ? strlen(p_pcKey) : 0)); +} + +/* + clsLEAMDNSHost::clsServiceTxt::releaseKey + +*/ +bool clsLEAMDNSHost::clsServiceTxt::releaseKey(void) +{ + if (m_pcKey) + { + delete[] m_pcKey; + m_pcKey = 0; + } + return true; +} + +/* + clsLEAMDNSHost::clsServiceTxt::allocValue + +*/ +char* clsLEAMDNSHost::clsServiceTxt::allocValue(size_t p_stLength) +{ + releaseValue(); + if (p_stLength) + { + m_pcValue = new char[p_stLength + 1]; + } + return m_pcValue; +} + +/* + clsLEAMDNSHost::clsServiceTxt::setValue + +*/ +bool clsLEAMDNSHost::clsServiceTxt::setValue(const char* p_pcValue, + size_t p_stLength) +{ + bool bResult = false; + + releaseValue(); + if (p_stLength) + { + if (allocValue(p_stLength)) + { + strncpy(m_pcValue, p_pcValue, p_stLength); + m_pcValue[p_stLength] = 0; + bResult = true; + } + } + else + { + // No value -> also OK + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsServiceTxt::setValue + +*/ +bool clsLEAMDNSHost::clsServiceTxt::setValue(const char* p_pcValue) +{ + return setValue(p_pcValue, (p_pcValue ? strlen(p_pcValue) : 0)); +} + +/* + clsLEAMDNSHost::clsServiceTxt::releaseValue + +*/ +bool clsLEAMDNSHost::clsServiceTxt::releaseValue(void) +{ + if (m_pcValue) + { + delete[] m_pcValue; + m_pcValue = 0; + } + return true; +} + +/* + clsLEAMDNSHost::clsServiceTxt::set + +*/ +bool clsLEAMDNSHost::clsServiceTxt::set(const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp /*= false*/) +{ + m_bTemp = p_bTemp; + return ((setKey(p_pcKey)) && + (setValue(p_pcValue))); +} + +/* + clsLEAMDNSHost::clsServiceTxt::update + +*/ +bool clsLEAMDNSHost::clsServiceTxt::update(const char* p_pcValue) +{ + return setValue(p_pcValue); +} + +/* + clsLEAMDNSHost::clsServiceTxt::length + + length of eg. 'c#=1' without any closing '\0' + +*/ +size_t clsLEAMDNSHost::clsServiceTxt::length(void) const +{ + size_t stLength = 0; + if (m_pcKey) + { + stLength += strlen(m_pcKey); // Key + stLength += 1; // '=' + stLength += (m_pcValue ? strlen(m_pcValue) : 0); // Value + } + return stLength; +} + + +/** + clsLEAMDNSHost::clsServiceTxts + + A list of zero or more MDNS TXT (stcServiceTxt) items. + Dynamic TXT items can be removed by 'removeTempTxts'. + A TXT item can be looked up by its 'key' member. + Export as ';'-separated byte array is supported. + Export as 'length byte coded' byte array is supported. + Comparison ((all A TXT items in B and equal) AND (all B TXT items in A and equal)) is supported. + +*/ + +/* + clsLEAMDNSHost::clsServiceTxts::clsServiceTxts contructor + +*/ +clsLEAMDNSHost::clsServiceTxts::clsServiceTxts(void) + : m_pcCache(0) +{ +} + +/* + clsLEAMDNSHost::clsServiceTxts::clsServiceTxts copy-constructor + +*/ +clsLEAMDNSHost::clsServiceTxts::clsServiceTxts(const clsServiceTxts& p_Other) + : m_pcCache(0) +{ + operator=(p_Other); +} + +/* + clsLEAMDNSHost::clsServiceTxts::~stcServiceTxts destructor + +*/ +clsLEAMDNSHost::clsServiceTxts::~clsServiceTxts(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsServiceTxts::operator= + +*/ +clsLEAMDNSHost::clsServiceTxts& clsLEAMDNSHost::clsServiceTxts::operator=(const clsServiceTxts& p_Other) +{ + if (this != &p_Other) + { + clear(); + + for (const clsServiceTxt* pOtherTxt : p_Other.m_Txts) + { + add(new clsServiceTxt(*pOtherTxt)); + } + } + return *this; +} + +/* + clsLEAMDNSHost::clsServiceTxts::clear + +*/ +bool clsLEAMDNSHost::clsServiceTxts::clear(void) +{ + for (clsServiceTxt* pTxt : m_Txts) + { + delete pTxt; + } + m_Txts.clear(); + + return clearCache(); +} + +/* + clsLEAMDNSHost::clsServiceTxts::clearCache + +*/ +bool clsLEAMDNSHost::clsServiceTxts::clearCache(void) +{ + if (m_pcCache) + { + delete[] m_pcCache; + m_pcCache = 0; + } + return true; +} + +/* + clsLEAMDNSHost::clsServiceTxts::add + +*/ +bool clsLEAMDNSHost::clsServiceTxts::add(clsLEAMDNSHost::clsServiceTxt* p_pTxt) +{ + bool bResult = false; + + if (p_pTxt) + { + m_Txts.push_back(p_pTxt); + bResult = true; + } + return ((clearCache()) && + (bResult)); +} + +/* + clsLEAMDNSHost::clsServiceTxts::remove + +*/ +bool clsLEAMDNSHost::clsServiceTxts::remove(clsServiceTxt* p_pTxt) +{ + bool bResult = false; + + clsServiceTxt::list::iterator it(p_pTxt + ? std::find(m_Txts.begin(), m_Txts.end(), p_pTxt) + : m_Txts.end()); + if (m_Txts.end() != it) + { + m_Txts.erase(it); + delete p_pTxt; + + bResult = true; + } + return ((clearCache()) && + (bResult)); +} + +/* + clsLEAMDNSHost::clsServiceTxts::count + +*/ +size_t clsLEAMDNSHost::clsServiceTxts::count(void) const +{ + size_t stResult = m_Txts.size(); + return stResult; +} + +/* + clsLEAMDNSHost::clsServiceTxts::removeTempTxts + +*/ +bool clsLEAMDNSHost::clsServiceTxts::removeTempTxts(void) +{ + bool bResult = true; + + // Delete content + clsServiceTxt::list tempTxts; + for (clsServiceTxt* pTxt : m_Txts) + { + if (pTxt->m_bTemp) + { + tempTxts.push_back(pTxt); + delete pTxt; + } + } + // Remove objects from list + for (clsServiceTxt* pTempTxt : tempTxts) + { + m_Txts.remove(pTempTxt); + } + return ((clearCache()) && + (bResult)); +} + +/* + clsLEAMDNSHost::clsServiceTxts::find + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsServiceTxts::find(const char* p_pcKey) +{ + clsServiceTxt* pResult = 0; + + for (clsServiceTxt* pTxt : m_Txts) + { + if ((p_pcKey) && + (0 == strcmp(pTxt->m_pcKey, p_pcKey))) + { + pResult = pTxt; + break; + } + } + return pResult; +} + +/* + clsLEAMDNSHost::clsServiceTxts::find (const) + +*/ +const clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsServiceTxts::find(const char* p_pcKey) const +{ + const clsServiceTxt* pResult = 0; + + for (const clsServiceTxt* pTxt : m_Txts) + { + if ((p_pcKey) && + (0 == strcmp(pTxt->m_pcKey, p_pcKey))) + { + pResult = pTxt; + break; + } + } + return pResult; +} + +/* + clsLEAMDNSHost::clsServiceTxts::find + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsServiceTxts::find(const clsServiceTxt* p_pTxt) +{ + clsServiceTxt* pResult = 0; + + for (clsServiceTxt* pTxt : m_Txts) + { + if (p_pTxt == pTxt) + { + pResult = pTxt; + break; + } + } + return pResult; +} + +/* + clsLEAMDNSHost::clsServiceTxts::length + +*/ +size_t clsLEAMDNSHost::clsServiceTxts::length(void) const +{ + size_t szLength = 0; + + for (clsServiceTxt* pTxt : m_Txts) + { + szLength += 1; // Length byte + szLength += pTxt->length(); // Text + } + return szLength; +} + +/* + clsLEAMDNSHost::clsServiceTxts::c_strLength + + (incl. closing '\0'). Length bytes place is used for delimiting ';' and closing '\0' + +*/ +size_t clsLEAMDNSHost::clsServiceTxts::c_strLength(void) const +{ + return length(); +} + +/* + clsLEAMDNSHost::clsServiceTxts::c_str + +*/ +bool clsLEAMDNSHost::clsServiceTxts::c_str(char* p_pcBuffer) +{ + bool bResult = false; + + if (p_pcBuffer) + { + bResult = true; + + char* pcCursor = p_pcBuffer; + *pcCursor = 0; + for (const clsServiceTxt* pTxt : m_Txts) + { + size_t stLength; + if ((bResult = (0 != (stLength = (pTxt->m_pcKey ? strlen(pTxt->m_pcKey) : 0))))) + { + if (pcCursor != p_pcBuffer) + { + *pcCursor++ = ';'; + } + strncpy(pcCursor, pTxt->m_pcKey, stLength); pcCursor[stLength] = 0; + pcCursor += stLength; + *pcCursor++ = '='; + if ((stLength = (pTxt->m_pcValue ? strlen(pTxt->m_pcValue) : 0))) + { + strncpy(pcCursor, pTxt->m_pcValue, stLength); pcCursor[stLength] = 0; + pcCursor += stLength; + } + } + else + { + break; + } + } + *pcCursor++ = 0; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsServiceTxts::c_str + +*/ +const char* clsLEAMDNSHost::clsServiceTxts::c_str(void) const +{ + + if ((!m_pcCache) && + (m_Txts.size()) && + ((((clsServiceTxts*)this)->m_pcCache = new char[c_strLength()]))) // TRANSPARENT caching + { + ((clsServiceTxts*)this)->c_str(m_pcCache); + } + return m_pcCache; +} + +/* + clsLEAMDNSHost::clsServiceTxts::bufferLength + + (incl. closing '\0'). + +*/ +size_t clsLEAMDNSHost::clsServiceTxts::bufferLength(void) const +{ + return (length() + 1); +} + +/* + clsLEAMDNSHost::clsServiceTxts::toBuffer + +*/ +bool clsLEAMDNSHost::clsServiceTxts::buffer(char* p_pcBuffer) +{ + bool bResult = false; + + if (p_pcBuffer) + { + bResult = true; + + *p_pcBuffer = 0; + for (const clsServiceTxt* pTxt : m_Txts) + { + *(unsigned char*)p_pcBuffer++ = pTxt->length(); + size_t stLength; + if ((bResult = (0 != (stLength = (pTxt->m_pcKey ? strlen(pTxt->m_pcKey) : 0))))) + { + memcpy(p_pcBuffer, pTxt->m_pcKey, stLength); + p_pcBuffer += stLength; + *p_pcBuffer++ = '='; + if ((stLength = (pTxt->m_pcValue ? strlen(pTxt->m_pcValue) : 0))) + { + memcpy(p_pcBuffer, pTxt->m_pcValue, stLength); + p_pcBuffer += stLength; + } + } + else + { + break; + } + } + *p_pcBuffer++ = 0; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsServiceTxts::compare + +*/ +bool clsLEAMDNSHost::clsServiceTxts::compare(const clsLEAMDNSHost::clsServiceTxts& p_Other) const +{ + bool bResult = false; + + if ((bResult = (length() == p_Other.length()))) + { + // Compare A->B + for (const clsServiceTxt* pTxt : m_Txts) + { + const clsServiceTxt* pOtherTxt = p_Other.find(pTxt->m_pcKey); + if (!((bResult = ((pOtherTxt) && + (pTxt->m_pcValue) && + (pOtherTxt->m_pcValue) && + (strlen(pTxt->m_pcValue) == strlen(pOtherTxt->m_pcValue)) && + (0 == strcmp(pTxt->m_pcValue, pOtherTxt->m_pcValue)))))) + { + break; + } + } + // Compare B->A + for (const clsServiceTxt* pOtherTxt : p_Other.m_Txts) + { + const clsServiceTxt* pTxt = find(pOtherTxt->m_pcKey); + if (!((bResult = ((pTxt) && + (pOtherTxt->m_pcValue) && + (pTxt->m_pcValue) && + (strlen(pOtherTxt->m_pcValue) == strlen(pTxt->m_pcValue)) && + (0 == strcmp(pOtherTxt->m_pcValue, pTxt->m_pcValue)))))) + { + break; + } + } + } + return bResult; +} + +/* + clsLEAMDNSHost::clsServiceTxts::operator== + +*/ +bool clsLEAMDNSHost::clsServiceTxts::operator==(const clsServiceTxts& p_Other) const +{ + return compare(p_Other); +} + +/* + clsLEAMDNSHost::clsServiceTxts::operator!= + +*/ +bool clsLEAMDNSHost::clsServiceTxts::operator!=(const clsServiceTxts& p_Other) const +{ + return !compare(p_Other); +} + + +/** + clsLEAMDNSHost::clsProbeInformation_Base + + Probing status information for a host or service domain + +*/ + +/* + clsLEAMDNSHost::clsProbeInformation_Base::clsProbeInformation_Base constructor +*/ +clsLEAMDNSHost::clsProbeInformation_Base::clsProbeInformation_Base(void) + : m_ProbingStatus(enuProbingStatus::WaitingForData), + m_u8SentCount(0), + m_Timeout(std::numeric_limits::max()), + m_bConflict(false), + m_bTiebreakNeeded(false) +{ +} + +/* + clsLEAMDNSHost::clsProbeInformation_Base::clear +*/ +bool clsLEAMDNSHost::clsProbeInformation_Base::clear(void) +{ + m_ProbingStatus = enuProbingStatus::WaitingForData; + m_u8SentCount = 0; + m_Timeout.reset(std::numeric_limits::max()); + m_bConflict = false; + m_bTiebreakNeeded = false; + + return true; +} + + +/** + clsLEAMDNSHost::clsProbeInformation_Host + + Probing status information for a host or service domain + +*/ + +/* + clsLEAMDNSHost::clsProbeInformation::clsProbeInformation constructor +*/ +clsLEAMDNSHost::clsProbeInformation::clsProbeInformation(void) + : m_fnProbeResultCallback(0) +{ +} + +/* + clsLEAMDNSHost::clsProbeInformation::clear +*/ +bool clsLEAMDNSHost::clsProbeInformation::clear(bool p_bClearUserdata /*= false*/) +{ + if (p_bClearUserdata) + { + m_fnProbeResultCallback = 0; + } + return clsProbeInformation_Base::clear(); +} + + +/** + clsLEAMDNSHost::clsService::clsProbeInformation + + Probing status information for a host or service domain + +*/ + +/* + clsLEAMDNSHost::clsService::clsProbeInformation::clsProbeInformation constructor +*/ +clsLEAMDNSHost::clsService::clsProbeInformation::clsProbeInformation(void) + : m_fnProbeResultCallback(0) +{ +} + +/* + clsLEAMDNSHost::clsService::clsProbeInformation::clear +*/ +bool clsLEAMDNSHost::clsService::clsProbeInformation::clear(bool p_bClearUserdata /*= false*/) +{ + if (p_bClearUserdata) + { + m_fnProbeResultCallback = 0; + } + return clsProbeInformation_Base::clear(); +} + + +/** + clsLEAMDNSHost::clsService + + A MDNS service object (to be announced by the MDNS responder) + The service instance may be '\0'; in this case the hostname is used + and the flag m_bAutoName is set. If the hostname changes, all 'auto- + named' services are renamed also. + m_u8Replymask is used while preparing a response to a MDNS query. It is + resetted in '_sendMDNSMessage' afterwards. +*/ + +/* + clsLEAMDNSHost::clsService::clsService constructor + +*/ +clsLEAMDNSHost::clsService::clsService(void) + : m_pcInstanceName(0), + m_bAutoName(false), + m_pcType(0), + m_pcProtocol(0), + m_u16Port(0), + m_u32ReplyMask(0), + m_fnTxtCallback(0) +{ +} + +/* + clsLEAMDNSHost::clsService::~clsService destructor + +*/ +clsLEAMDNSHost::clsService::~clsService(void) +{ + _releaseInstanceName(); + _releaseType(); + _releaseProtocol(); +} + +/* + clsLEAMDNSHost::clsService::setInstanceName + +*/ +bool clsLEAMDNSHost::clsService::setInstanceName(const char* p_pcInstanceName) +{ + bool bResult = false; + + _releaseInstanceName(); + size_t stLength = (p_pcInstanceName ? strlen(p_pcInstanceName) : 0); + if ((stLength) && + (stLength <= clsConsts::stDomainLabelMaxLength)) + { + if ((bResult = (0 != (m_pcInstanceName = new char[stLength + 1])))) + { + strncpy(m_pcInstanceName, p_pcInstanceName, stLength); + m_pcInstanceName[stLength] = 0; + + _resetProbeStatus(); + } + } + return bResult; +} + +/* + clsLEAMDNSHost::clsService::indexInstanceName + +*/ +bool clsLEAMDNSHost::clsService::indexInstanceName(void) +{ + bool bResult = false; + + if ((bResult = setInstanceName(clsLEAMDNSHost::indexDomainName(m_pcInstanceName, "#", 0)))) + { + _resetProbeStatus(); + } + return bResult; +} + +/* + clsLEAMDNSHost::clsService::instanceName + +*/ +const char* clsLEAMDNSHost::clsService::instanceName(void) const +{ + return m_pcInstanceName; +} + +/* + clsLEAMDNSHost::clsService::_releaseInstanceName + +*/ +bool clsLEAMDNSHost::clsService::_releaseInstanceName(void) +{ + if (m_pcInstanceName) + { + delete[] m_pcInstanceName; + m_pcInstanceName = 0; + } + return true; +} + +/* + clsLEAMDNSHost::clsService::setType + +*/ +bool clsLEAMDNSHost::clsService::setType(const char* p_pcType) +{ + bool bResult = false; + + _releaseType(); + size_t stLength = (p_pcType ? strlen(p_pcType) : 0); + if ((stLength) && + (stLength <= clsConsts::stServiceTypeMaxLength)) + { + if ((bResult = (0 != (m_pcType = new char[stLength + 1])))) + { + strncpy(m_pcType, p_pcType, stLength); + m_pcType[stLength] = 0; + + _resetProbeStatus(); + } + } + return bResult; +} + +/* + clsLEAMDNSHost::clsService::type + +*/ +const char* clsLEAMDNSHost::clsService::type(void) const +{ + return m_pcType; +} + +/* + clsLEAMDNSHost::clsService::_releaseType + +*/ +bool clsLEAMDNSHost::clsService::_releaseType(void) +{ + if (m_pcType) + { + delete[] m_pcType; + m_pcType = 0; + } + return true; +} + +/* + clsLEAMDNSHost::clsService::setProtocol + +*/ +bool clsLEAMDNSHost::clsService::setProtocol(const char* p_pcProtocol) +{ + bool bResult = false; + + _releaseProtocol(); + size_t stLength = (p_pcProtocol ? strlen(p_pcProtocol) : 0); + if ((stLength) && + (stLength <= clsConsts::stServiceProtocolMaxLength)) + { + if ((bResult = (0 != (m_pcProtocol = new char[stLength + 1])))) + { + strncpy(m_pcProtocol, p_pcProtocol, stLength); + m_pcProtocol[stLength] = 0; + + _resetProbeStatus(); + } + } + return bResult; +} + +/* + clsLEAMDNSHost::clsService::protocol + +*/ +const char* clsLEAMDNSHost::clsService::protocol(void) const +{ + return m_pcProtocol; +} + +/* + clsLEAMDNSHost::clsService::_releaseProtocol + +*/ +bool clsLEAMDNSHost::clsService::_releaseProtocol(void) +{ + if (m_pcProtocol) + { + delete[] m_pcProtocol; + m_pcProtocol = 0; + } + return true; +} + +/* + clsLEAMDNSHost::clsService::setPort + +*/ +bool clsLEAMDNSHost::clsService::setPort(uint16_t p_u16Port) +{ + bool bResult = false; + + if ((bResult = (0 != p_u16Port))) + { + m_u16Port = p_u16Port; + + _resetProbeStatus(); + } + return bResult; +} + +/* + clsLEAMDNSHost::clsService::port + +*/ +uint16_t clsLEAMDNSHost::clsService::port(void) const +{ + return m_u16Port; +} + +/* + clsLEAMDNSHost::clsService::setProbeResultCallback + +*/ +bool clsLEAMDNSHost::clsService::setProbeResultCallback(fnProbeResultCallback p_fnProbeResultCallback) +{ + m_ProbeInformation.m_fnProbeResultCallback = p_fnProbeResultCallback; + return true; +} + +/* + clsLEAMDNSHost::clsService::probeStatus + +*/ +bool clsLEAMDNSHost::clsService::probeStatus(void) const +{ + return (clsProbeInformation_Base::enuProbingStatus::DoneFinally == m_ProbeInformation.m_ProbingStatus); +} + +/* + clsLEAMDNSHost::clsService::_resetProbeStatus + +*/ +void clsLEAMDNSHost::clsService::_resetProbeStatus(void) +{ + m_ProbeInformation.clear(false); + m_ProbeInformation.m_ProbingStatus = clsProbeInformation_Base::enuProbingStatus::ReadyToStart; +} + +/* + clsLEAMDNSHost::clsService::addServiceTxt (const char*) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, + const char* p_pcValue) +{ + return _addServiceTxt(p_pcKey, p_pcValue, false); +} + +/* + clsLEAMDNSHost::clsService::addServiceTxt (uint32_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, + uint32_t p_u32Value) +{ + return _addServiceTxt(p_pcKey, p_u32Value, false); +} + +/* + clsLEAMDNSHost::clsService::addServiceTxt (uint16_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, + uint16_t p_u16Value) +{ + return _addServiceTxt(p_pcKey, p_u16Value, false); +} + +/* + clsLEAMDNSHost::clsService::addServiceTxt (uint8_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, + uint8_t p_u8Value) +{ + return _addServiceTxt(p_pcKey, p_u8Value, false); +} + +/* + clsLEAMDNSHost::clsService::addServiceTxt (int32_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, + int32_t p_i32Value) +{ + return _addServiceTxt(p_pcKey, p_i32Value, false); +} + +/* + clsLEAMDNSHost::clsService::addServiceTxt (int16_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, + int16_t p_i16Value) +{ + return _addServiceTxt(p_pcKey, p_i16Value, false); +} + +/* + clsLEAMDNSHost::clsService::addServiceTxt (int8_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, + int8_t p_i8Value) +{ + return _addServiceTxt(p_pcKey, p_i8Value, false); +} + +/* + clsLEAMDNSHost::clsService::addDynamicServiceTxt (const char*) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, + const char* p_pcValue) +{ + return _addServiceTxt(p_pcKey, p_pcValue, true); +} + +/* + clsLEAMDNSHost::clsService::addDynamicServiceTxt (uint32_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, + uint32_t p_u32Value) +{ + return _addServiceTxt(p_pcKey, p_u32Value, true); +} + +/* + clsLEAMDNSHost::clsService::addDynamicServiceTxt (uint16_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, + uint16_t p_u16Value) +{ + return _addServiceTxt(p_pcKey, p_u16Value, true); +} + +/* + clsLEAMDNSHost::clsService::addDynamicServiceTxt (uint8_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, + uint8_t p_u8Value) +{ + return _addServiceTxt(p_pcKey, p_u8Value, true); +} + +/* + clsLEAMDNSHost::clsService::addDynamicServiceTxt (int32_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, + int32_t p_i32Value) +{ + return _addServiceTxt(p_pcKey, p_i32Value, true); +} + +/* + clsLEAMDNSHost::clsService::addDynamicServiceTxt (int16_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, + int16_t p_i16Value) +{ + return _addServiceTxt(p_pcKey, p_i16Value, true); +} + +/* + clsLEAMDNSHost::clsService::addDynamicServiceTxt (int8_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, + int8_t p_i8Value) +{ + return _addServiceTxt(p_pcKey, p_i8Value, true); +} + +/* + clsLEAMDNSHost::clsService::setDynamicServiceTxtCallback + +*/ +bool clsLEAMDNSHost::clsService::setDynamicServiceTxtCallback(fnDynamicServiceTxtCallback p_fnCallback) +{ + m_fnTxtCallback = p_fnCallback; + return true; +} + +/* + clsLEAMDNSHost::clsService::_addServiceTxt (const char*) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp) +{ + clsServiceTxt* pServiceTxt = 0; + + if ((p_pcKey) && + (*p_pcKey)) + { + if ((pServiceTxt = m_Txts.find(p_pcKey))) + { + // Change existing TXT + if (clsConsts::stServiceTxtMaxLength > (m_Txts.length() - + (pServiceTxt->m_pcValue ? strlen(pServiceTxt->m_pcValue) : 0) + + (p_pcValue ? strlen(p_pcValue) : 0))) + { + // Enough space left for changed content + if (!pServiceTxt->update(p_pcValue)) + { + // FAILED to update + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[LEAmDNS2_Host] clsService::_addServiceTxt: FAILED to update TXT item '%s'!\n"), p_pcKey)); + pServiceTxt = 0; + } + } + else + { + // NOT enough space for changed TXT content + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[LEAmDNS2_Host] clsService::_addServiceTxt: FAILED to change TXT item '%s' (too large)!\n"), p_pcKey)); + pServiceTxt = 0; + } + } + else + { + // Create new TXT + if (clsConsts::stServiceTxtMaxLength > (m_Txts.length() + + 1 + // Length byte + (p_pcKey ? strlen(p_pcKey) : 0) + + 1 + // '=' + (p_pcValue ? strlen(p_pcValue) : 0))) + { + if (!(((pServiceTxt = new clsServiceTxt)) && + (pServiceTxt->set(p_pcKey, p_pcValue, p_bTemp)) && + (m_Txts.add(pServiceTxt)))) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[LEAmDNS2_Host] clsService::_addServiceTxt: FAILED to add TXT item '%s'!\n"), p_pcKey)); + if (pServiceTxt) + { + delete pServiceTxt; + pServiceTxt = 0; + } + } + } + else + { + // NOT enough space for added TXT item + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[LEAmDNS2_Host] clsService::_addServiceTxt: FAILED to add TXT item '%s' (too large)!\n"), p_pcKey)); + pServiceTxt = 0; + } + } + } + return pServiceTxt; +} + +/* + clsLEAMDNSHost::clsService::_addServiceTxt (uint32_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, + uint32_t p_u32Value, + bool p_bTemp) +{ + char acValueBuffer[16]; // 32-bit max 10 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%u", p_u32Value); + + return _addServiceTxt(p_pcKey, acValueBuffer, p_bTemp); +} + +/* + clsLEAMDNSHost::clsService::_addServiceTxt (uint16_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, + uint16_t p_u16Value, + bool p_bTemp) +{ + char acValueBuffer[8]; // 16-bit max 5 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%hu", p_u16Value); + + return _addServiceTxt(p_pcKey, acValueBuffer, p_bTemp); +} + +/* + clsLEAMDNSHost::clsService::_addServiceTxt (uint8_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, + uint8_t p_u8Value, + bool p_bTemp) +{ + char acValueBuffer[8]; // 8-bit max 3 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%hhu", p_u8Value); + + return _addServiceTxt(p_pcKey, acValueBuffer, p_bTemp); +} + +/* + clsLEAMDNSHost::clsService::_addServiceTxt (int32_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, + int32_t p_i32Value, + bool p_bTemp) +{ + char acValueBuffer[16]; // 32-bit max 10 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%i", p_i32Value); + + return _addServiceTxt(p_pcKey, acValueBuffer, p_bTemp); +} + +/* + clsLEAMDNSHost::clsService::_addServiceTxt (int16_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, + int16_t p_i16Value, + bool p_bTemp) +{ + char acValueBuffer[8]; // 16-bit max 5 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%hi", p_i16Value); + + return _addServiceTxt(p_pcKey, acValueBuffer, p_bTemp); +} + +/* + clsLEAMDNSHost::clsService::_addServiceTxt (int8_t) + +*/ +clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, + int8_t p_i8Value, + bool p_bTemp) +{ + char acValueBuffer[8]; // 8-bit max 3 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%hhi", p_i8Value); + + return _addServiceTxt(p_pcKey, acValueBuffer, p_bTemp); +} + + +/** + clsLEAMDNSHost::clsMsgHeader + + A MDNS message header. + +*/ + +/* + clsLEAMDNSHost::clsMsgHeader::clsMsgHeader + +*/ +clsLEAMDNSHost::clsMsgHeader::clsMsgHeader(uint16_t p_u16ID /*= 0*/, + bool p_bQR /*= false*/, + uint8_t p_u8Opcode /*= 0*/, + bool p_bAA /*= false*/, + bool p_bTC /*= false*/, + bool p_bRD /*= false*/, + bool p_bRA /*= false*/, + uint8_t p_u8RCode /*= 0*/, + uint16_t p_u16QDCount /*= 0*/, + uint16_t p_u16ANCount /*= 0*/, + uint16_t p_u16NSCount /*= 0*/, + uint16_t p_u16ARCount /*= 0*/) + : m_u16ID(p_u16ID), + m_1bQR(p_bQR), m_4bOpcode(p_u8Opcode), m_1bAA(p_bAA), m_1bTC(p_bTC), m_1bRD(p_bRD), + m_1bRA(p_bRA), m_3bZ(0), m_4bRCode(p_u8RCode), + m_u16QDCount(p_u16QDCount), + m_u16ANCount(p_u16ANCount), + m_u16NSCount(p_u16NSCount), + m_u16ARCount(p_u16ARCount) +{ +} + + +/** + clsLEAMDNSHost::clsRRDomain + + A MDNS domain object. + The labels of the domain are stored (DNS-like encoded) in 'm_acName': + [length byte]varlength label[length byte]varlength label[0] + 'm_u16NameLength' stores the used length of 'm_acName'. + Dynamic label addition is supported. + Comparison is supported. + Export as byte array 'esp8266.local' is supported. + +*/ + +/* + clsLEAMDNSHost::clsRRDomain::clsRRDomain constructor + +*/ +clsLEAMDNSHost::clsRRDomain::clsRRDomain(void) + : m_u16NameLength(0), + m_pcDecodedName(0) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsRRDomain::clsRRDomain copy-constructor + +*/ +clsLEAMDNSHost::clsRRDomain::clsRRDomain(const clsRRDomain& p_Other) + : m_u16NameLength(0), + m_pcDecodedName(0) +{ + operator=(p_Other); +} + +/* + clsLEAMDNSHost::clsRRDomain::clsRRDomain destructor + +*/ +clsLEAMDNSHost::clsRRDomain::~clsRRDomain(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsRRDomain::operator = + +*/ +clsLEAMDNSHost::clsRRDomain& clsLEAMDNSHost::clsRRDomain::operator=(const clsRRDomain& p_Other) +{ + if (&p_Other != this) + { + clear(); + memcpy(m_acName, p_Other.m_acName, sizeof(m_acName)); + m_u16NameLength = p_Other.m_u16NameLength; + } + return *this; +} + +/* + clsLEAMDNSHost::clsRRDomain::clear + +*/ +bool clsLEAMDNSHost::clsRRDomain::clear(void) +{ + memset(m_acName, 0, sizeof(m_acName)); + m_u16NameLength = 0; + return clearNameCache(); +} + +/* + clsLEAMDNSHost::clsRRDomain::clearNameCache + +*/ +bool clsLEAMDNSHost::clsRRDomain::clearNameCache(void) +{ + if (m_pcDecodedName) + { + delete[] m_pcDecodedName; + m_pcDecodedName = 0; + } + return true; +} + +/* + clsLEAMDNSHost::clsRRDomain::addLabel + +*/ +bool clsLEAMDNSHost::clsRRDomain::addLabel(const char* p_pcLabel, + bool p_bPrependUnderline /*= false*/) +{ + bool bResult = false; + + size_t stLength = (p_pcLabel + ? (strlen(p_pcLabel) + (p_bPrependUnderline ? 1 : 0)) + : 0); + if ((clsConsts::stDomainLabelMaxLength >= stLength) && + (clsConsts::stDomainMaxLength >= (m_u16NameLength + (1 + stLength)))) + { + // Length byte + m_acName[m_u16NameLength] = (unsigned char)stLength; // Might be 0! + ++m_u16NameLength; + // Label + if (stLength) + { + if (p_bPrependUnderline) + { + m_acName[m_u16NameLength++] = '_'; + --stLength; + } + strncpy(&(m_acName[m_u16NameLength]), p_pcLabel, stLength); m_acName[m_u16NameLength + stLength] = 0; + m_u16NameLength += stLength; + } + bResult = clearNameCache(); + } + return bResult; +} + +/* + clsLEAMDNSHost::clsRRDomain::compare + +*/ +bool clsLEAMDNSHost::clsRRDomain::compare(const clsRRDomain& p_Other) const +{ + bool bResult = false; + + if (m_u16NameLength == p_Other.m_u16NameLength) + { + const char* pT = m_acName; + const char* pO = p_Other.m_acName; + while ((pT) && + (pO) && + (*((unsigned char*)pT) == *((unsigned char*)pO)) && // Same length AND + (0 == strncasecmp((pT + 1), (pO + 1), *((unsigned char*)pT)))) // Same content + { + if (*((unsigned char*)pT)) // Not 0 + { + pT += (1 + * ((unsigned char*)pT)); // Shift by length byte and lenght + pO += (1 + * ((unsigned char*)pO)); + } + else // Is 0 -> Successfully reached the end + { + bResult = true; + break; + } + } + } + return bResult; +} + +/* + clsLEAMDNSHost::clsRRDomain::operator == + +*/ +bool clsLEAMDNSHost::clsRRDomain::operator==(const clsRRDomain& p_Other) const +{ + return compare(p_Other); +} + +/* + clsLEAMDNSHost::clsRRDomain::operator != + +*/ +bool clsLEAMDNSHost::clsRRDomain::operator!=(const clsRRDomain& p_Other) const +{ + return !compare(p_Other); +} + +/* + clsLEAMDNSHost::clsRRDomain::operator > + +*/ +bool clsLEAMDNSHost::clsRRDomain::operator>(const clsRRDomain& p_Other) const +{ + // TODO: Check, if this is a good idea... + return !compare(p_Other); +} + +/* + clsLEAMDNSHost::clsRRDomain::c_strLength + +*/ +size_t clsLEAMDNSHost::clsRRDomain::c_strLength(void) const +{ + size_t stLength = 0; + + unsigned char* pucLabelLength = (unsigned char*)m_acName; + while (*pucLabelLength) + { + stLength += (*pucLabelLength + 1 /* +1 for '.' or '\0'*/); + pucLabelLength += (*pucLabelLength + 1); + } + return stLength; +} + +/* + clsLEAMDNSHost::clsRRDomain::c_str (const) + +*/ +bool clsLEAMDNSHost::clsRRDomain::c_str(char* p_pcBuffer) const +{ + bool bResult = false; + + if (p_pcBuffer) + { + *p_pcBuffer = 0; + unsigned char* pucLabelLength = (unsigned char*)m_acName; + while (*pucLabelLength) + { + memcpy(p_pcBuffer, (const char*)(pucLabelLength + 1), *pucLabelLength); + p_pcBuffer += *pucLabelLength; + pucLabelLength += (*pucLabelLength + 1); + *p_pcBuffer++ = (*pucLabelLength ? '.' : '\0'); + } + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsRRDomain::c_str + +*/ +const char* clsLEAMDNSHost::clsRRDomain::c_str(void) const +{ + if ((!m_pcDecodedName) && + (m_u16NameLength) && + ((((clsRRDomain*)this)->m_pcDecodedName = new char[c_strLength()]))) // TRANSPARENT caching + { + ((clsRRDomain*)this)->c_str(m_pcDecodedName); + } + return m_pcDecodedName; +} + + +/** + clsLEAMDNSHost::clsRRAttributes + + A MDNS attributes object. + +*/ + +/* + clsLEAMDNSHost::clsRRAttributes::clsRRAttributes constructor + +*/ +clsLEAMDNSHost::clsRRAttributes::clsRRAttributes(uint16_t p_u16Type /*= 0*/, + uint16_t p_u16Class /*= 1 DNS_RRCLASS_IN Internet*/) + : m_u16Type(p_u16Type), + m_u16Class(p_u16Class) +{ +} + +/* + clsLEAMDNSHost::clsRRAttributes::clsRRAttributes copy-constructor + +*/ +clsLEAMDNSHost::clsRRAttributes::clsRRAttributes(const clsLEAMDNSHost::clsRRAttributes& p_Other) +{ + operator=(p_Other); +} + +/* + clsLEAMDNSHost::clsRRAttributes::operator = + +*/ +clsLEAMDNSHost::clsRRAttributes& clsLEAMDNSHost::clsRRAttributes::operator=(const clsLEAMDNSHost::clsRRAttributes& p_Other) +{ + if (&p_Other != this) + { + m_u16Type = p_Other.m_u16Type; + m_u16Class = p_Other.m_u16Class; + } + return *this; +} + + +/** + clsLEAMDNSHost::clsRRHeader + + A MDNS record header (domain and attributes) object. + +*/ + +/* + clsLEAMDNSHost::clsRRHeader::clsRRHeader constructor + +*/ +clsLEAMDNSHost::clsRRHeader::clsRRHeader(void) +{ +} + +/* + clsLEAMDNSHost::clsRRHeader::clsRRHeader copy-constructor + +*/ +clsLEAMDNSHost::clsRRHeader::clsRRHeader(const clsRRHeader& p_Other) +{ + operator=(p_Other); +} + +/* + clsLEAMDNSHost::clsRRHeader::operator = + +*/ +clsLEAMDNSHost::clsRRHeader& clsLEAMDNSHost::clsRRHeader::operator=(const clsLEAMDNSHost::clsRRHeader& p_Other) +{ + if (&p_Other != this) + { + m_Domain = p_Other.m_Domain; + m_Attributes = p_Other.m_Attributes; + } + return *this; +} + +/* + clsLEAMDNSHost::clsRRHeader::clear + +*/ +bool clsLEAMDNSHost::clsRRHeader::clear(void) +{ + m_Domain.clear(); + return true; +} + + +/** + clsLEAMDNSHost::clsRRQuestion + + A MDNS question record object (header + question flags) + +*/ + +/* + clsLEAMDNSHost::clsRRQuestion::clsRRQuestion constructor +*/ +clsLEAMDNSHost::clsRRQuestion::clsRRQuestion(void) + : m_bUnicast(false) +{ +} + + +/** + clsLEAMDNSHost::clsNSECBitmap + + A MDNS question record object (header + question flags) + +*/ + +/* + clsLEAMDNSHost::clsNSECBitmap::clsNSECBitmap constructor + +*/ +clsLEAMDNSHost::clsNSECBitmap::clsNSECBitmap(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsNSECBitmap::clsNSECBitmap destructor + +*/ +bool clsLEAMDNSHost::clsNSECBitmap::clear(void) +{ + memset(m_au8BitmapData, 0, sizeof(m_au8BitmapData)); + return true; +} + +/* + clsLEAMDNSHost::clsNSECBitmap::length + +*/ +uint16_t clsLEAMDNSHost::clsNSECBitmap::length(void) const +{ + return sizeof(m_au8BitmapData); // 6 +} + +/* + clsLEAMDNSHost::clsNSECBitmap::setBit + +*/ +bool clsLEAMDNSHost::clsNSECBitmap::setBit(uint16_t p_u16Bit) +{ + bool bResult = false; + + if ((p_u16Bit) && + (length() > (p_u16Bit / 8))) // bit between 0..47(2F) + { + + uint8_t& ru8Byte = m_au8BitmapData[p_u16Bit / 8]; + uint8_t u8Flag = 1 << (7 - (p_u16Bit % 8)); // (7 - (0..7)) = 7..0 + + ru8Byte |= u8Flag; + + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsNSECBitmap::getBit + +*/ +bool clsLEAMDNSHost::clsNSECBitmap::getBit(uint16_t p_u16Bit) const +{ + bool bResult = false; + + if ((p_u16Bit) && + (length() > (p_u16Bit / 8))) // bit between 0..47(2F) + { + + uint8_t u8Byte = m_au8BitmapData[p_u16Bit / 8]; + uint8_t u8Flag = 1 << (7 - (p_u16Bit % 8)); // (7 - (0..7)) = 7..0 + + bResult = (u8Byte & u8Flag); + } + return bResult; +} + + +/** + clsLEAMDNSHost::clsRRAnswer + + A MDNS answer record object (header + answer content). + This is a 'virtual' base class for all other MDNS answer classes. + +*/ + +/* + clsLEAMDNSHost::clsRRAnswer::clsRRAnswer constructor + +*/ +clsLEAMDNSHost::clsRRAnswer::clsRRAnswer(enuAnswerType p_AnswerType, + const clsLEAMDNSHost::clsRRHeader& p_Header, + uint32_t p_u32TTL) + : m_pNext(0), + m_AnswerType(p_AnswerType), + m_Header(p_Header), + m_u32TTL(p_u32TTL) +{ + // Extract 'cache flush'-bit + m_bCacheFlush = (m_Header.m_Attributes.m_u16Class & 0x8000); + m_Header.m_Attributes.m_u16Class &= (~0x8000); +} + +/* + clsLEAMDNSHost::clsRRAnswer::~stcRRAnswer destructor + +*/ +clsLEAMDNSHost::clsRRAnswer::~clsRRAnswer(void) +{ +} + +/* + clsLEAMDNSHost::clsRRAnswer::answerType + +*/ +clsLEAMDNSHost::enuAnswerType clsLEAMDNSHost::clsRRAnswer::answerType(void) const +{ + return m_AnswerType; +} + +/* + clsLEAMDNSHost::clsRRAnswer::clear + +*/ +bool clsLEAMDNSHost::clsRRAnswer::clear(void) +{ + m_pNext = 0; + m_Header.clear(); + return true; +} + + +/** + clsLEAMDNSHost::clsRRAnswerA + + A MDNS A answer object. + Extends the base class by an IPv4 address member. + +*/ + +#ifdef MDNS_IPV4_SUPPORT +/* + clsLEAMDNSHost::clsRRAnswerA::clsRRAnswerA constructor + +*/ +clsLEAMDNSHost::clsRRAnswerA::clsRRAnswerA(const clsLEAMDNSHost::clsRRHeader& p_Header, + uint32_t p_u32TTL) + : clsRRAnswer(enuAnswerType::A, p_Header, p_u32TTL), + m_IPAddress() +{ +} + +/* + clsLEAMDNSHost::clsRRAnswerA::clsRRAnswerA destructor + +*/ +clsLEAMDNSHost::clsRRAnswerA::~clsRRAnswerA(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsRRAnswerA::clear + +*/ +bool clsLEAMDNSHost::clsRRAnswerA::clear(void) +{ + m_IPAddress = IPAddress(); + return true; +} +#endif + + +/** + clsLEAMDNSHost::clsRRAnswerPTR + + A MDNS PTR answer object. + Extends the base class by a MDNS domain member. + +*/ + +/* + clsLEAMDNSHost::clsRRAnswerPTR::clsRRAnswerPTR constructor + +*/ +clsLEAMDNSHost::clsRRAnswerPTR::clsRRAnswerPTR(const clsLEAMDNSHost::clsRRHeader& p_Header, + uint32_t p_u32TTL) + : clsRRAnswer(enuAnswerType::PTR, p_Header, p_u32TTL) +{ +} + +/* + clsLEAMDNSHost::clsRRAnswerPTR::~stcRRAnswerPTR destructor + +*/ +clsLEAMDNSHost::clsRRAnswerPTR::~clsRRAnswerPTR(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsRRAnswerPTR::clear + +*/ +bool clsLEAMDNSHost::clsRRAnswerPTR::clear(void) +{ + m_PTRDomain.clear(); + return true; +} + + +/** + clsLEAMDNSHost::clsRRAnswerTXT + + A MDNS TXT answer object. + Extends the base class by a MDNS TXT items list member. + +*/ + +/* + clsLEAMDNSHost::clsRRAnswerTXT::clsRRAnswerTXT constructor + +*/ +clsLEAMDNSHost::clsRRAnswerTXT::clsRRAnswerTXT(const clsLEAMDNSHost::clsRRHeader& p_Header, + uint32_t p_u32TTL) + : clsRRAnswer(enuAnswerType::TXT, p_Header, p_u32TTL) +{ +} + +/* + clsLEAMDNSHost::clsRRAnswerTXT::~stcRRAnswerTXT destructor + +*/ +clsLEAMDNSHost::clsRRAnswerTXT::~clsRRAnswerTXT(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsRRAnswerTXT::clear + +*/ +bool clsLEAMDNSHost::clsRRAnswerTXT::clear(void) +{ + m_Txts.clear(); + return true; +} + + +/** + clsLEAMDNSHost::clsRRAnswerAAAA + + A MDNS AAAA answer object. + Extends the base class by an IPv6 address member. + +*/ + +#ifdef MDNS_IPV6_SUPPORT +/* + clsLEAMDNSHost::clsRRAnswerAAAA::clsRRAnswerAAAA constructor + +*/ +clsLEAMDNSHost::clsRRAnswerAAAA::clsRRAnswerAAAA(const clsLEAMDNSHost::clsRRHeader& p_Header, + uint32_t p_u32TTL) + : clsRRAnswer(enuAnswerType::AAAA, p_Header, p_u32TTL), + m_IPAddress() +{ +} + +/* + clsLEAMDNSHost::clsRRAnswerAAAA::~stcRRAnswerAAAA destructor + +*/ +clsLEAMDNSHost::clsRRAnswerAAAA::~clsRRAnswerAAAA(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsRRAnswerAAAA::clear + +*/ +bool clsLEAMDNSHost::clsRRAnswerAAAA::clear(void) +{ + m_IPAddress = IPAddress(); + return true; +} +#endif + + +/** + clsLEAMDNSHost::clsRRAnswerSRV + + A MDNS SRV answer object. + Extends the base class by a port member. + +*/ + +/* + clsLEAMDNSHost::clsRRAnswerSRV::clsRRAnswerSRV constructor + +*/ +clsLEAMDNSHost::clsRRAnswerSRV::clsRRAnswerSRV(const clsLEAMDNSHost::clsRRHeader& p_Header, + uint32_t p_u32TTL) + : clsRRAnswer(enuAnswerType::SRV, p_Header, p_u32TTL), + m_u16Priority(0), + m_u16Weight(0), + m_u16Port(0) +{ +} + +/* + clsLEAMDNSHost::clsRRAnswerSRV::~stcRRAnswerSRV destructor + +*/ +clsLEAMDNSHost::clsRRAnswerSRV::~clsRRAnswerSRV(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsRRAnswerSRV::clear + +*/ +bool clsLEAMDNSHost::clsRRAnswerSRV::clear(void) +{ + m_u16Priority = 0; + m_u16Weight = 0; + m_u16Port = 0; + m_SRVDomain.clear(); + return true; +} + + +/** + clsLEAMDNSHost::clsRRAnswerGeneric + + An unknown (generic) MDNS answer object. + Extends the base class by a RDATA buffer member. + +*/ + +/* + clsLEAMDNSHost::clsRRAnswerGeneric::clsRRAnswerGeneric constructor + +*/ +clsLEAMDNSHost::clsRRAnswerGeneric::clsRRAnswerGeneric(const clsRRHeader& p_Header, + uint32_t p_u32TTL) + : clsRRAnswer(enuAnswerType::Generic, p_Header, p_u32TTL), + m_u16RDLength(0), + m_pu8RDData(0) +{ +} + +/* + clsLEAMDNSHost::clsRRAnswerGeneric::~stcRRAnswerGeneric destructor + +*/ +clsLEAMDNSHost::clsRRAnswerGeneric::~clsRRAnswerGeneric(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsRRAnswerGeneric::clear + +*/ +bool clsLEAMDNSHost::clsRRAnswerGeneric::clear(void) +{ + if (m_pu8RDData) + { + delete[] m_pu8RDData; + m_pu8RDData = 0; + } + m_u16RDLength = 0; + + return true; +} + + +/** + clsLEAMDNSHost::clsSendParameter + + A 'collection' of properties and flags for one MDNS query or response. + Mainly managed by the 'Control' functions. + The current offset in the UPD output buffer is tracked to be able to do + a simple host or service domain compression. + +*/ + +/** + clsLEAMDNSHost::clsSendParameter::clsDomainCacheItem + + A cached host or service domain, incl. the offset in the UDP output buffer. + +*/ + +/* + clsLEAMDNSHost::clsSendParameter::clsDomainCacheItem::clsDomainCacheItem constructor + +*/ +clsLEAMDNSHost::clsSendParameter::clsDomainCacheItem::clsDomainCacheItem(const void* p_pHostNameOrService, + bool p_bAdditionalData, + uint32_t p_u16Offset) + : m_pHostNameOrService(p_pHostNameOrService), + m_bAdditionalData(p_bAdditionalData), + m_u16Offset(p_u16Offset) +{ +} + +/** + clsLEAMDNSHost::clsSendParameter + +*/ + +/* + clsLEAMDNSHost::clsSendParameter::clsSendParameter constructor + +*/ +clsLEAMDNSHost::clsSendParameter::clsSendParameter(void) + : m_u16ID(0), + m_u32HostReplyMask(0), + m_bLegacyDNSQuery(false), + m_Response(enuResponseType::None), + m_bAuthorative(false), + m_bCacheFlush(false), + m_bUnicast(false), + m_bUnannounce(false), + m_u16Offset(0) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsSendParameter::~stcSendParameter destructor + +*/ +clsLEAMDNSHost::clsSendParameter::~clsSendParameter(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsSendParameter::clear + +*/ +bool clsLEAMDNSHost::clsSendParameter::clear(void) +{ + m_u16ID = 0; + flushQuestions(); + m_u32HostReplyMask = 0; + + m_bLegacyDNSQuery = false; + m_Response = enuResponseType::None; + m_bAuthorative = false; + m_bCacheFlush = true; + m_bUnicast = false; + m_bUnannounce = false; + + m_u16Offset = 0; + flushDomainCache(); + return true; +} + +/* + clsLEAMDNSHost::clsSendParameter::flushQuestions + +*/ +bool clsLEAMDNSHost::clsSendParameter::flushQuestions(void) +{ + for (clsRRQuestion* pRRQuestion : m_RRQuestions) + { + delete pRRQuestion; + } + m_RRQuestions.clear(); + return true; +} + +/* + clsLEAMDNSHost::clsSendParameter::flushDomainCache + +*/ +bool clsLEAMDNSHost::clsSendParameter::flushDomainCache(void) +{ + for (clsDomainCacheItem* pDomainCacheItem : m_DomainCacheItems) + { + delete pDomainCacheItem; + } + m_DomainCacheItems.clear(); + return true; +} + +/* + clsLEAMDNSHost::clsSendParameter::flushTempContent + +*/ +bool clsLEAMDNSHost::clsSendParameter::flushTempContent(void) +{ + m_u16Offset = 0; + flushDomainCache(); + return true; +} + +/* + clsLEAMDNSHost::clsSendParameter::shiftOffset + +*/ +bool clsLEAMDNSHost::clsSendParameter::shiftOffset(uint16_t p_u16Shift) +{ + m_u16Offset += p_u16Shift; + return true; +} + +/* + clsLEAMDNSHost::clsSendParameter::addDomainCacheItem + +*/ +bool clsLEAMDNSHost::clsSendParameter::addDomainCacheItem(const void* p_pHostNameOrService, + bool p_bAdditionalData, + uint16_t p_u16Offset) +{ + bool bResult = false; + + clsDomainCacheItem* pNewItem = 0; + if ((p_pHostNameOrService) && + (p_u16Offset) && + ((pNewItem = new clsDomainCacheItem(p_pHostNameOrService, p_bAdditionalData, p_u16Offset)))) + { + m_DomainCacheItems.push_back(pNewItem); + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsSendParameter::findCachedDomainOffset + +*/ +uint16_t clsLEAMDNSHost::clsSendParameter::findCachedDomainOffset(const void* p_pHostNameOrService, + bool p_bAdditionalData) const +{ + const clsDomainCacheItem* pMatchingCacheItem = 0; + + for (const clsDomainCacheItem* pCacheItem : m_DomainCacheItems) + { + if ((pCacheItem->m_pHostNameOrService == p_pHostNameOrService) && + (pCacheItem->m_bAdditionalData == p_bAdditionalData)) // Found cache item + { + pMatchingCacheItem = pCacheItem; + break; + } + } + return (pMatchingCacheItem ? pMatchingCacheItem->m_u16Offset : 0); +} + + +/** + clsLEAMDNSHost::clsQuery + + A MDNS service query object. + Service queries may be static or dynamic. + As the static service query is processed in the blocking function 'queryService', + only one static service service may exist. The processing of the answers is done + on the WiFi-stack side of the ESP stack structure (via 'UDPContext.onRx(_update)'). + +*/ + +/** + clsLEAMDNSHost::clsQuery::clsAnswer + + One answer for a query. + Every answer must contain + - a service instance entry (pivot), + and may contain + - a host domain, + - a port + - an IPv4 address + (- an IPv6 address) + - a MDNS TXTs + The existance of a component is flaged in 'm_u32ContentFlags'. + For every answer component a TTL value is maintained. + Answer objects can be connected to a linked list. + + For the host domain, service domain and TXTs components, a char array + representation can be retrieved (which is created on demand). + +*/ + +/** + clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL + + The TTL (Time-To-Live) for an specific answer content. + If the answer is scheduled for an update, the corresponding flag should be set. + +*/ + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::clsTTL constructor + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::clsTTL(void) + : m_u32TTL(0), + m_TTLTimeout(std::numeric_limits::max()), + m_TimeoutLevel(static_cast(enuTimeoutLevel::None)) +{ +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::set + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::set(uint32_t p_u32TTL) +{ + m_u32TTL = p_u32TTL; + if (m_u32TTL) + { + m_TimeoutLevel = static_cast(enuTimeoutLevel::Base); // Set to 80% + m_TTLTimeout.reset(timeout()); + } + else + { + m_TimeoutLevel = static_cast(enuTimeoutLevel::None); // undef + m_TTLTimeout.reset(std::numeric_limits::max()); + } + return true; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::flagged + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::flagged(void) const +{ + return ((m_u32TTL) && + (static_cast(enuTimeoutLevel::None) != m_TimeoutLevel) && + (((esp8266::polledTimeout::timeoutTemplate*)&m_TTLTimeout)->expired())); // Cast-away the const; in case of oneShot-timer OK (but ugly...) +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::restart + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::restart(void) +{ + bool bResult = true; + + if ((static_cast(enuTimeoutLevel::Base) <= m_TimeoutLevel) && // >= 80% AND + (static_cast(enuTimeoutLevel::Final) > m_TimeoutLevel)) // < 100% + { + m_TimeoutLevel += static_cast(enuTimeoutLevel::Interval); // increment by 5% + m_TTLTimeout.reset(timeout()); + } + else + { + bResult = false; + m_TTLTimeout.reset(std::numeric_limits::max()); + m_TimeoutLevel = static_cast(enuTimeoutLevel::None); + } + return bResult; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::prepareDeletion + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::prepareDeletion(void) +{ + m_TimeoutLevel = static_cast(enuTimeoutLevel::Final); + m_TTLTimeout.reset(1 * 1000); // See RFC 6762, 10.1 + + return true; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::finalTimeoutLevel + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::finalTimeoutLevel(void) const +{ + return (static_cast(enuTimeoutLevel::Final) == m_TimeoutLevel); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::timeout + +*/ +unsigned long clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::timeout(void) const +{ + uint32_t u32Timeout = std::numeric_limits::max(); + + if (static_cast(enuTimeoutLevel::Base) == m_TimeoutLevel) // 80% + { + u32Timeout = (m_u32TTL * 800); // to milliseconds + } + else if ((static_cast(enuTimeoutLevel::Base) < m_TimeoutLevel) && // >80% AND + (static_cast(enuTimeoutLevel::Final) >= m_TimeoutLevel)) // <= 100% + { + u32Timeout = (m_u32TTL * 50); + } // else: invalid + return u32Timeout; +} + + +/** + clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddress + +*/ + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddress::clsIPAddress constructor + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL::clsIPAddressWithTTL(IPAddress p_IPAddress, + uint32_t p_u32TTL /*= 0*/) + : m_IPAddress(p_IPAddress) +{ + m_TTL.set(p_u32TTL); +} + + +/** + clsLEAMDNSHost::clsQuery::clsAnswer + +*/ + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clsAnswer constructor + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer::clsAnswer(void) + : m_u16Port(0), + m_QueryAnswerFlags(0) +{ +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::~clsAnswer destructor + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer::~clsAnswer(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::clear + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::clear(void) +{ + return ( +#ifdef MDNS_IPV4_SUPPORT + (releaseIPv4Addresses()) +#else + (true) +#endif + && +#ifdef MDNS_IPV6_SUPPORT + (releaseIPv6Addresses()) +#else + (true) +#endif + ); +} + +#ifdef MDNS_IPV4_SUPPORT +/* + clsLEAMDNSHost::clsQuery::clsAnswer::releaseIPv4Addresses + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::releaseIPv4Addresses(void) +{ + for (clsIPAddressWithTTL* pIPAddress : m_IPv4Addresses) + { + delete pIPAddress; + } + m_IPv4Addresses.clear(); + return true; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::addIPv4Address + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::addIPv4Address(clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* p_pIPv4Address) +{ + bool bResult = false; + + if (p_pIPv4Address) + { + m_IPv4Addresses.push_back(p_pIPv4Address); + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::removeIPv4Address + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::removeIPv4Address(clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* p_pIPv4Address) +{ + bool bResult = false; + + clsIPAddressWithTTL::list::iterator it(p_pIPv4Address + ? std::find(m_IPv4Addresses.begin(), m_IPv4Addresses.end(), p_pIPv4Address) + : m_IPv4Addresses.end()); + if (m_IPv4Addresses.end() != it) + { + m_IPv4Addresses.erase(it); + delete p_pIPv4Address; + + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::findIPv4Address (const) + +*/ +const clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost::clsQuery::clsAnswer::findIPv4Address(const IPAddress& p_IPAddress) const +{ + return (clsIPAddressWithTTL*)(((const clsAnswer*)this)->findIPv4Address(p_IPAddress)); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::findIPv4Address + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost::clsQuery::clsAnswer::findIPv4Address(const IPAddress& p_IPAddress) +{ + clsIPAddressWithTTL* pMatchingIPv4Address = 0; + + for (clsIPAddressWithTTL* pIPv4Address : m_IPv4Addresses) + { + if (pIPv4Address->m_IPAddress == p_IPAddress) + { + pMatchingIPv4Address = pIPv4Address; + break; + } + } + return pMatchingIPv4Address; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::IPv4AddressCount + +*/ +uint32_t clsLEAMDNSHost::clsQuery::clsAnswer::IPv4AddressCount(void) const +{ + uint32_t u32Count = m_IPv4Addresses.size(); + return u32Count; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::IPv4AddressAtIndex + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost::clsQuery::clsAnswer::IPv4AddressAtIndex(uint32_t p_u32Index) +{ + return (clsIPAddressWithTTL*)(((const clsAnswer*)this)->IPv4AddressAtIndex(p_u32Index)); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::IPv4AddressAtIndex (const) + +*/ +const clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost::clsQuery::clsAnswer::IPv4AddressAtIndex(uint32_t p_u32Index) const +{ + const clsIPAddressWithTTL* pIPv4AddressAtIndex = 0; + + uint32_t u32CurIndex = 0; + for (clsIPAddressWithTTL::list::const_iterator it = m_IPv4Addresses.begin(); + (((uint32_t)(-1) != p_u32Index) && (u32CurIndex <= p_u32Index) && (it != m_IPv4Addresses.end())); + it++, u32CurIndex++) + { + if (p_u32Index == u32CurIndex++) + { + pIPv4AddressAtIndex = *it; + break; + } + } + return pIPv4AddressAtIndex; +} +#endif + +#ifdef MDNS_IPV6_SUPPORT +/* + clsLEAMDNSHost::clsQuery::clsAnswer::releaseIPv6Addresses + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::releaseIPv6Addresses(void) +{ + for (clsIPAddressWithTTL* pIPAddress : m_IPv6Addresses) + { + delete pIPAddress; + } + m_IPv6Addresses.clear(); + return true; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::addIPv6Address + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::addIPv6Address(clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* p_pIPv6Address) +{ + bool bResult = false; + + if (p_pIPv6Address) + { + m_IPv6Addresses.push_back(p_pIPv6Address); + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::removeIPv6Address + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswer::removeIPv6Address(clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* p_pIPv6Address) +{ + bool bResult = false; + + clsIPAddressWithTTL::list::iterator it(p_pIPv6Address + ? std::find(m_IPv6Addresses.begin(), m_IPv6Addresses.end(), p_pIPv6Address) + : m_IPv6Addresses.end()); + if (m_IPv6Addresses.end() != it) + { + m_IPv6Addresses.erase(it); + delete p_pIPv6Address; + + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::findIPv6Address + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost::clsQuery::clsAnswer::findIPv6Address(const IPAddress& p_IPAddress) +{ + return (clsIPAddressWithTTL*)(((const clsAnswer*)this)->findIPv6Address(p_IPAddress)); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::findIPv6Address (const) + +*/ +const clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost::clsQuery::clsAnswer::findIPv6Address(const IPAddress& p_IPAddress) const +{ + clsIPAddressWithTTL* pMatchingIPv6Address = 0; + + for (clsIPAddressWithTTL* pIPv6Address : m_IPv6Addresses) + { + if (pIPv6Address->m_IPAddress == p_IPAddress) + { + pMatchingIPv6Address = pIPv6Address; + break; + } + } + return pMatchingIPv6Address; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::IPv6AddressCount + +*/ +uint32_t clsLEAMDNSHost::clsQuery::clsAnswer::IPv6AddressCount(void) const +{ + uint32_t u32Count = m_IPv6Addresses.size(); + return u32Count; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::IPv6AddressAtIndex + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost::clsQuery::clsAnswer::IPv6AddressAtIndex(uint32_t p_u32Index) +{ + return (clsIPAddressWithTTL*)(((const clsAnswer*)this)->IPv6AddressAtIndex(p_u32Index)); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswer::IPv6AddressAtIndex (const) + +*/ +const clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost::clsQuery::clsAnswer::IPv6AddressAtIndex(uint32_t p_u32Index) const +{ + const clsIPAddressWithTTL* pIPv6AddressAtIndex = 0; + + uint32_t u32CurIndex = 0; + for (clsIPAddressWithTTL::list::const_iterator it = m_IPv6Addresses.begin(); + (((uint32_t)(-1) != p_u32Index) && (u32CurIndex <= p_u32Index) && (it != m_IPv6Addresses.end())); + it++, u32CurIndex++) + { + if (p_u32Index == u32CurIndex++) + { + pIPv6AddressAtIndex = *it; + break; + } + } + return pIPv6AddressAtIndex; +} +#endif + + +/** + clsLEAMDNSHost::clsQuery::clsAnswerAccessor + +*/ + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::clsAnswerAccessor constructor + +*/ +clsLEAMDNSHost::clsQuery::clsAnswerAccessor::clsAnswerAccessor(const clsLEAMDNSHost::clsQuery::clsAnswer* p_pAnswer) + : m_pAnswer(p_pAnswer) +{ + if ((m_pAnswer) && + (txtsAvailable())) + { + // Prepare m_TxtKeyValueMap + for (const clsLEAMDNSHost::clsServiceTxt* pTxt : m_pAnswer->m_Txts.m_Txts) + { + m_TxtKeyValueMap.emplace(std::pair(pTxt->m_pcKey, pTxt->m_pcValue)); + } + } +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::~clsAnswerAccessor destructor +*/ +clsLEAMDNSHost::clsQuery::clsAnswerAccessor::~clsAnswerAccessor(void) +{ +} + +/** + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::clsCompareTxtKey + +*/ + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::stcCompareTxtKey::operator() + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswerAccessor::stcCompareTxtKey::operator()(char const* p_pA, + char const* p_pB) const +{ + return (0 > strcasecmp(p_pA, p_pB)); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::serviceDomainAvailable + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswerAccessor::serviceDomainAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain))); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::serviceDomain + +*/ +const char* clsLEAMDNSHost::clsQuery::clsAnswerAccessor::serviceDomain(void) const +{ + return ((m_pAnswer) + ? m_pAnswer->m_ServiceDomain.c_str() + : 0); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::hostDomainAvailable + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswerAccessor::hostDomainAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(clsQuery::clsAnswer::enuQueryAnswerType::HostDomain))); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::hostDomain + +*/ +const char* clsLEAMDNSHost::clsQuery::clsAnswerAccessor::hostDomain(void) const +{ + return ((m_pAnswer) + ? m_pAnswer->m_HostDomain.c_str() + : 0); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::hostPortAvailable + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswerAccessor::hostPortAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Port))); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::hostPort + +*/ +uint16_t clsLEAMDNSHost::clsQuery::clsAnswerAccessor::hostPort(void) const +{ + return ((m_pAnswer) + ? (m_pAnswer->m_u16Port) + : 0); +} + +#ifdef MDNS_IPV4_SUPPORT +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::IPv4AddressAvailable + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswerAccessor::IPv4AddressAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address))); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::IPv4Addresses + +*/ +clsLEAMDNSHost::clsQuery::clsAnswerAccessor::clsIPAddressVector clsLEAMDNSHost::clsQuery::clsAnswerAccessor::IPv4Addresses(void) const +{ + clsIPAddressVector internalIP; + if ((m_pAnswer) && + (IPv4AddressAvailable())) + { + for (uint32_t u = 0; u < m_pAnswer->IPv4AddressCount(); ++u) + { + const clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* pIPAddr = m_pAnswer->IPv4AddressAtIndex(u); + if (pIPAddr) + { + internalIP.emplace_back(pIPAddr->m_IPAddress); + } + } + } + return internalIP; +} +#endif + +#ifdef MDNS_IPV6_SUPPORT +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::IPv6AddressAvailable + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswerAccessor::IPv6AddressAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address))); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::IPv6Addresses + +*/ +clsLEAMDNSHost::clsQuery::clsAnswerAccessor::clsIPAddressVector clsLEAMDNSHost::clsQuery::clsAnswerAccessor::IPv6Addresses(void) const +{ + clsIPAddressVector internalIP; + if ((m_pAnswer) && + (IPv6AddressAvailable())) + { + for (uint32_t u = 0; u < m_pAnswer->IPv6AddressCount(); ++u) + { + const clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* pIPAddr = m_pAnswer->IPv6AddressAtIndex(u); + if (pIPAddr) + { + internalIP.emplace_back(pIPAddr->m_IPAddress); + } + } + } + return internalIP; +} +#endif + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::txtsAvailable + +*/ +bool clsLEAMDNSHost::clsQuery::clsAnswerAccessor::txtsAvailable(void) const +{ + return ((m_pAnswer) && + (m_pAnswer->m_QueryAnswerFlags & static_cast(clsQuery::clsAnswer::enuQueryAnswerType::Txts))); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::txts + + Returns all TXT items for the given service as a ';'-separated string. + If not already existing; the string is alloced, filled and attached to the answer. + +*/ +const char* clsLEAMDNSHost::clsQuery::clsAnswerAccessor::txts(void) const +{ + return ((m_pAnswer) + ? m_pAnswer->m_Txts.c_str() + : 0); +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::txtKeyValues + +*/ +const clsLEAMDNSHost::clsQuery::clsAnswerAccessor::clsTxtKeyValueMap& clsLEAMDNSHost::clsQuery::clsAnswerAccessor::txtKeyValues(void) const +{ + return m_TxtKeyValueMap; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::txtValue + +*/ +const char* clsLEAMDNSHost::clsQuery::clsAnswerAccessor::txtValue(const char* p_pcKey) const +{ + char* pcResult = 0; + + if (m_pAnswer) + { + for (const clsLEAMDNSHost::clsServiceTxt* pTxt : m_pAnswer->m_Txts.m_Txts) + { + if ((p_pcKey) && + (0 == strcasecmp(pTxt->m_pcKey, p_pcKey))) + { + pcResult = pTxt->m_pcValue; + break; + } + } + } + return pcResult; +} + +/* + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::printTo + +*/ +size_t clsLEAMDNSHost::clsQuery::clsAnswerAccessor::printTo(Print& p_Print) const +{ + size_t stLen = 0; + const char* cpcI = " * "; + const char* cpcS = " "; + + stLen += p_Print.println(" * * * * *"); + if (hostDomainAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print("Host domain: "); + stLen += p_Print.println(hostDomain()); + } +#ifdef MDNS_IPV4_SUPPORT + if (IPv4AddressAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.println("IPv4 address(es):"); + for (const IPAddress& addr : IPv4Addresses()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print(cpcS); + stLen += p_Print.println(addr); + } + } +#endif +#ifdef MDNS_IPV6_SUPPORT + if (IPv6AddressAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.println("IPv6 address(es):"); + for (const IPAddress& addr : IPv6Addresses()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print(cpcS); + stLen += p_Print.println(addr); + } + } +#endif + if (serviceDomainAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print("Service domain: "); + stLen += p_Print.println(serviceDomain()); + } + if (hostPortAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print("Host port: "); + stLen += p_Print.println(hostPort()); + } + if (txtsAvailable()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print("TXTs:"); + for (auto const& x : txtKeyValues()) + { + stLen += p_Print.print(cpcI); + stLen += p_Print.print(cpcS); + stLen += p_Print.print(x.first); + stLen += p_Print.print("="); + stLen += p_Print.println(x.second); + } + } + stLen += p_Print.println(" * * * * *"); + + return stLen; +} + + +/** + clsLEAMDNSHost::clsQuery + + A service or host query object. + A static query is flaged via 'm_bLegacyQuery'; while the function 'queryService' + is waiting for answers, the internal flag 'm_bAwaitingAnswers' is set. When the + timeout is reached, the flag is removed. These two flags are only used for static + service queries. + All answers to the query are stored in the 'm_Answers' list. + Individual answers may be addressed by index (in the list of answers). + Every time a answer component is added (or changed) in a dynamic query, + the callback 'm_fnCallback' is called. + The answer list may be searched by service and host domain. + + Query object may be connected to a linked list. + +*/ + +/* + clsLEAMDNSHost::clsQuery::clsQuery constructor + +*/ +clsLEAMDNSHost::clsQuery::clsQuery(const enuQueryType p_QueryType) + : m_QueryType(p_QueryType), + m_fnCallbackAnswer(0), + m_fnCallbackAccessor(0), + m_bStaticQuery(false), + m_u8SentCount(0), + m_ResendTimeout(std::numeric_limits::max()), + m_bAwaitingAnswers(true) +{ + clear(); + m_QueryType = p_QueryType; +} + +/* + clsLEAMDNSHost::clsQuery::~stcQuery destructor + +*/ +clsLEAMDNSHost::clsQuery::~clsQuery(void) +{ + clear(); +} + +/* + clsLEAMDNSHost::clsQuery::clear + +*/ +bool clsLEAMDNSHost::clsQuery::clear(void) +{ + m_QueryType = enuQueryType::None; + m_fnCallbackAnswer = 0; + m_fnCallbackAccessor = 0; + m_bStaticQuery = false; + m_u8SentCount = 0; + m_ResendTimeout.reset(std::numeric_limits::max()); + m_bAwaitingAnswers = true; + for (clsAnswer* pAnswer : m_Answers) + { + delete pAnswer; + } + m_Answers.clear(); + return true; +} + +/* + clsLEAMDNSHost::clsQuery::answerCount + +*/ +uint32_t clsLEAMDNSHost::clsQuery::answerCount(void) const +{ + uint32_t u32Count = m_Answers.size(); + return u32Count; +} + +/* + clsLEAMDNSHost::clsQuery::answer + +*/ +const clsLEAMDNSHost::clsQuery::clsAnswer* clsLEAMDNSHost::clsQuery::answer(uint32_t p_u32Index) const +{ + const clsAnswer* pAnswerAtIndex = 0; + + uint32_t u32CurIndex = 0; + for (clsAnswer::list::const_iterator it = m_Answers.begin(); + (((uint32_t)(-1) != p_u32Index) && (u32CurIndex <= p_u32Index) && (it != m_Answers.end())); + it++, u32CurIndex++) + { + if (p_u32Index == u32CurIndex++) + { + pAnswerAtIndex = *it; + break; + } + } + return pAnswerAtIndex; +} + +/* + clsLEAMDNSHost::clsQuery::indexOfAnswer + +*/ +uint32_t clsLEAMDNSHost::clsQuery::indexOfAnswer(const clsLEAMDNSHost::clsQuery::clsAnswer* p_pAnswer) const +{ + uint32_t u32IndexOfAnswer = ((uint32_t)(-1)); + + uint32_t u32CurIndex = 0; + for (const clsAnswer* pAnswer : m_Answers) + { + if (pAnswer == p_pAnswer) + { + u32IndexOfAnswer = u32CurIndex; + break; + } + ++u32CurIndex; + } + return u32IndexOfAnswer; +} + +/* + clsLEAMDNSHost::clsQuery::answerAccessors + +*/ +clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::clsQuery::answerAccessors(void) const +{ + clsAnswerAccessor::vector tempAccessors; + for (const clsAnswer* pAnswer : m_Answers) + { + tempAccessors.emplace_back(pAnswer); + } + return tempAccessors; +} + +/* + clsLEAMDNSHost::clsQuery::answerAccessor + +*/ +clsLEAMDNSHost::clsQuery::clsAnswerAccessor clsLEAMDNSHost::clsQuery::answerAccessor(uint32 p_u32AnswerIndex) const +{ + return clsAnswerAccessor(answer(p_u32AnswerIndex)); +} + +/* + clsLEAMDNSHost::clsQuery::addAnswer + +*/ +bool clsLEAMDNSHost::clsQuery::addAnswer(clsLEAMDNSHost::clsQuery::clsAnswer* p_pAnswer) +{ + bool bResult = false; + + if (p_pAnswer) + { + m_Answers.push_back(p_pAnswer); + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsQuery::removeAnswer + +*/ +bool clsLEAMDNSHost::clsQuery::removeAnswer(clsLEAMDNSHost::clsQuery::clsAnswer* p_pAnswer) +{ + bool bResult = false; + + clsAnswer::list::iterator it(p_pAnswer + ? std::find(m_Answers.begin(), m_Answers.end(), p_pAnswer) + : m_Answers.end()); + if (m_Answers.end() != it) + { + m_Answers.erase(it); + delete p_pAnswer; + + bResult = true; + } + return bResult; +} + +/* + clsLEAMDNSHost::clsQuery::findAnswerForServiceDomain + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer* clsLEAMDNSHost::clsQuery::findAnswerForServiceDomain(const clsLEAMDNSHost::clsRRDomain& p_ServiceDomain) +{ + clsAnswer* pAnswerForServiceDomain = 0; + + for (clsAnswer* pAnswer : m_Answers) + { + if (pAnswer->m_ServiceDomain == p_ServiceDomain) + { + pAnswerForServiceDomain = pAnswer; + break; + } + } + return pAnswerForServiceDomain; +} + +/* + clsLEAMDNSHost::clsQuery::findAnswerForHostDomain + +*/ +clsLEAMDNSHost::clsQuery::clsAnswer* clsLEAMDNSHost::clsQuery::findAnswerForHostDomain(const clsLEAMDNSHost::clsRRDomain& p_HostDomain) +{ + clsAnswer* pAnswerForHostDomain = 0; + + for (clsAnswer* pAnswer : m_Answers) + { + if (pAnswer->m_HostDomain == p_HostDomain) + { + pAnswerForHostDomain = pAnswer; + break; + } + } + return pAnswerForHostDomain; +} + + +} // namespace MDNSImplementation + + +} // namespace esp8266 + + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp old mode 100755 new mode 100644 similarity index 71% rename from libraries/ESP8266mDNS/src/LEAmDNS2_Host_Transfer.cpp rename to libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 5da81255ab..50880585cc --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -1,5 +1,5 @@ /* - LEAmDNS2_Host_Transfer.cpp + LEAmDNS2Host_Transfer.cpp License (MIT license): Permission is hereby granted, free of charge, to any person obtaining a copy @@ -22,61 +22,35 @@ */ -extern "C" { -#include "user_interface.h" -} - -#include "lwip/netif.h" +#include // for can_yield() -#include "LEAmDNS2_lwIPdefs.h" -#include "LEAmDNS2_Priv.h" +#include "LEAmDNS2Host.h" namespace esp8266 { -/* - LEAmDNS -*/ + namespace experimental { -/** - 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_IPV4_SUPPORT -static const char* scpcReverseIPv4Domain = "in-addr"; -#endif -#ifdef MDNS_IPV6_SUPPORT -static const char* scpcReverseIPv6Domain = "ip6"; -#endif -static const char* scpcReverseTopDomain = "arpa"; - -/** - TRANSFER -*/ +/* -/** SENDING + */ /* - MDNSResponder::_sendMDNSMessage + MDNSResponder::_sendMessage Unicast responses are prepared and sent directly to the querier. - Multicast responses or queries are transferred to _sendMDNSMessage_Multicast + Multicast responses or queries are transferred to _sendMessage_Multicast Any reply flags in installed services are removed at the end! */ -bool MDNSResponder::clsHost::_sendMDNSMessage(MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_sendMessage(clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { bool bResult = false; @@ -89,7 +63,7 @@ bool MDNSResponder::clsHost::_sendMDNSMessage(MDNSResponder::clsHost::stcSendPar } DEBUG_EX_INFO(else { - DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage: No IPv4 address available!\n"), _DH()); + DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage: No IPv4 address available!\n"), _DH()); }); #endif #ifdef MDNS_IPV6_SUPPORT @@ -100,72 +74,86 @@ bool MDNSResponder::clsHost::_sendMDNSMessage(MDNSResponder::clsHost::stcSendPar } DEBUG_EX_INFO(else { - DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage: No IPv6 address available!\n"), _DH()); + DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage: No IPv6 address available!\n"), _DH()); }); #endif - if (stcSendParameter::enuResponseType::None != p_rSendParameter.m_Response) + if (clsBackbone::sm_pBackbone->setDelayUDPProcessing(true)) { - IPAddress ipRemote = ((stcSendParameter::enuResponseType::Response == p_rSendParameter.m_Response) - ? m_rUDPContext.getRemoteAddress() - : IPAddress()); - - if (p_rSendParameter.m_bUnicast) // Unicast response -> Send to querier + // Avoid 're-entry-like problems because delay() is called! + if (clsSendParameter::enuResponseType::None != p_rSendParameter.m_Response) { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage: Will send unicast to '%s'.\n"), _DH(), ipRemote.toString().c_str());); - DEBUG_EX_ERR(if (!ipRemote.isSet()) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage: MISSING remote address for unicast response!\n"), _DH());); + IPAddress ipRemote = ((clsSendParameter::enuResponseType::Response == p_rSendParameter.m_Response) + ? m_pUDPContext->getRemoteAddress() + : IPAddress()); - bResult = ((ipRemote.isSet()) && - (_prepareMDNSMessage(p_rSendParameter)) && - (m_rUDPContext.send(ipRemote, m_rUDPContext.getRemotePort()))); - } - else // Multicast response -> Send via the same network interface, that received the query - { -#ifdef MDNS_IPV4_SUPPORT - if (((!ipRemote.isSet()) || // NO remote IP - (ipRemote.isV4())) && // OR IPv4 - (u8AvailableProtocols & static_cast(enuIPProtocolType::V4))) // AND IPv4 protocol available + if (p_rSendParameter.m_bUnicast) { - - bResult = _sendMDNSMessage_Multicast(p_rSendParameter, static_cast(enuIPProtocolType::V4)); + // Unicast response -> Send to querier + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage: Will send unicast to '%s'.\n"), _DH(), ipRemote.toString().c_str());); + DEBUG_EX_ERR(if (!ipRemote.isSet()) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage: MISSING remote address for unicast response!\n"), _DH());); + + bResult = ((ipRemote.isSet()) && + (_prepareMessage(p_rSendParameter)) && + (m_pUDPContext->send(ipRemote, m_pUDPContext->getRemotePort())) /*&& + (Serial.println("Did send UC"), true)*/); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage (V4): FAILED!\n"), _DH());); + + if ((clsConsts::u32SendCooldown) && + (can_yield())) + { + delay(clsConsts::u32SendCooldown); + } } + else + { + // Multicast response -> Send via the same network interface, that received the query +#ifdef MDNS_IPV4_SUPPORT + if (((!ipRemote.isSet()) || // NO remote IP + (ipRemote.isV4())) && // OR IPv4 + (u8AvailableProtocols & static_cast(enuIPProtocolType::V4))) // AND IPv4 protocol available + { + bResult = _sendMessage_Multicast(p_rSendParameter, static_cast(enuIPProtocolType::V4)); + } #endif #ifdef MDNS_IPV6_SUPPORT - if (((!ipRemote.isSet()) || // NO remote IP - (ipRemote.isV6())) && // OR IPv6 - (u8AvailableProtocols & static_cast(enuIPProtocolType::V6))) // AND IPv6 protocol available - { - - bResult = _sendMDNSMessage_Multicast(p_rSendParameter, static_cast(enuIPProtocolType::V6)); - } + if (((!ipRemote.isSet()) || // NO remote IP + (ipRemote.isV6())) && // OR IPv6 + (u8AvailableProtocols & static_cast(enuIPProtocolType::V6))) // AND IPv6 protocol available + { + bResult = _sendMessage_Multicast(p_rSendParameter, static_cast(enuIPProtocolType::V6)); + } #endif + } + } + else + { + // Multicast query -> Send by all available protocols + bResult = ((u8AvailableProtocols) && + (_sendMessage_Multicast(p_rSendParameter, u8AvailableProtocols))); } - } - else // Multicast query -> Send by all available protocols - { - bResult = ((u8AvailableProtocols) && - (_sendMDNSMessage_Multicast(p_rSendParameter, u8AvailableProtocols))); - } - // Finally clear service reply masks - for (stcService* pService = m_pServices; pService; pService = pService->m_pNext) - { - pService->m_u32ReplyMask = 0; + // Finally clear service reply masks + for (clsService* pService : m_Services) + { + pService->m_u32ReplyMask = 0; + } + + clsBackbone::sm_pBackbone->setDelayUDPProcessing(false); } - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage: FAILED!\n"), _DH());); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage: FAILED!\n"), _DH());); return bResult; } -#include "cont.h" /* - MDNSResponder::_sendMDNSMessage_Multicast + MDNSResponder::_sendMessage_Multicast - Fills the UDP output buffer (via _prepareMDNSMessage) and sends the buffer + Fills the UDP output buffer (via _prepareMessage) and sends the buffer via the selected WiFi protocols */ -bool MDNSResponder::clsHost::_sendMDNSMessage_Multicast(MDNSResponder::clsHost::stcSendParameter& p_rSendParameter, - uint8_t p_IPProtocolTypes) +bool clsLEAMDNSHost::_sendMessage_Multicast(clsLEAMDNSHost::clsSendParameter& p_rSendParameter, + uint8_t p_IPProtocolTypes) { bool bIPv4Result = true; bool bIPv6Result = true; @@ -175,13 +163,20 @@ bool MDNSResponder::clsHost::_sendMDNSMessage_Multicast(MDNSResponder::clsHost:: { IPAddress ip4MulticastAddress(DNS_MQUERY_IPV4_GROUP_INIT); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage_Multicast v4: Will send to '%s'.\n"), _DH(), ip4MulticastAddress.toString().c_str());); - DEBUG_EX_INFO(if (!_getResponderIPAddress(enuIPProtocolType::V4)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage_Multicast v4: NO IPv4 address!.\n"), _DH());); - bIPv4Result = ((_prepareMDNSMessage(p_rSendParameter)) && - (m_rUDPContext.setMulticastInterface(&m_rNetIf), true) && - (m_rUDPContext.send(ip4MulticastAddress, DNS_MQUERY_PORT)) && - (m_rUDPContext.setMulticastInterface(0), true)); - DEBUG_EX_ERR(if (!bIPv4Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage_Multicast (V4): FAILED!\n"), _DH());); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast IPv4: Will send to '%s'.\n"), _DH(), ip4MulticastAddress.toString().c_str());); + DEBUG_EX_INFO(if (!_getResponderIPAddress(enuIPProtocolType::V4)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast IPv4: NO IPv4 address!.\n"), _DH());); + bIPv4Result = ((_prepareMessage(p_rSendParameter)) && + (m_pUDPContext->setMulticastInterface(m_pNetIf), true) && + (m_pUDPContext->send(ip4MulticastAddress, DNS_MQUERY_PORT)) && + (m_pUDPContext->setMulticastInterface(0), true) /*&& + (Serial.println("Did send MC V4"), true)*/); + DEBUG_EX_ERR(if (!bIPv4Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast (V4): FAILED!\n"), _DH());); + + if ((clsConsts::u32SendCooldown) && + (can_yield())) + { + delay(clsConsts::u32SendCooldown); + } } #endif @@ -190,27 +185,34 @@ bool MDNSResponder::clsHost::_sendMDNSMessage_Multicast(MDNSResponder::clsHost:: { IPAddress ip6MulticastAddress(DNS_MQUERY_IPV6_GROUP_INIT); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage_Multicast v6: Will send to '%s'.\n"), _DH(), ip6MulticastAddress.toString().c_str());); - DEBUG_EX_INFO(if (!_getResponderIPAddress(enuIPProtocolType::V6)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage_Multicast v6: NO IPv6 address!.\n"), _DH());); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast IPv6: Will send to '%s'.\n"), _DH(), ip6MulticastAddress.toString().c_str());); + DEBUG_EX_INFO(if (!_getResponderIPAddress(enuIPProtocolType::V6)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast IPv6: NO IPv6 address!.\n"), _DH());); DEBUG_EX_ERR( bool bPrepareMessage = false; bool bUDPContextSend = false; ); - bIPv6Result = ((DEBUG_EX_ERR(bPrepareMessage =)_prepareMDNSMessage(p_rSendParameter)) && - (m_rUDPContext.setMulticastInterface(&m_rNetIf), true) && - (DEBUG_EX_ERR(bUDPContextSend =)m_rUDPContext.send(ip6MulticastAddress, DNS_MQUERY_PORT)) && - (m_rUDPContext.setMulticastInterface(0), true)); - DEBUG_EX_ERR(if (!bIPv6Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage_Multicast (V6): FAILED! (%s, %s, %s)\n"), _DH(), (_getResponderIPAddress(enuIPProtocolType::V6).isSet() ? "1" : "0"), (bPrepareMessage ? "1" : "0"), (bUDPContextSend ? "1" : "0"));); + bIPv6Result = ((DEBUG_EX_ERR(bPrepareMessage =)_prepareMessage(p_rSendParameter)) && + (m_pUDPContext->setMulticastInterface(m_pNetIf), true) && + (DEBUG_EX_ERR(bUDPContextSend =)m_pUDPContext->send(ip6MulticastAddress, DNS_MQUERY_PORT)) && + (m_pUDPContext->setMulticastInterface(0), true) /*&& + (Serial.println("Did send MC V6"), true)*/); + DEBUG_EX_ERR(if (!bIPv6Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast (IPv6): FAILED! (%s, %s, %s)\n"), _DH(), (_getResponderIPAddress(enuIPProtocolType::V6).isSet() ? "1" : "0"), (bPrepareMessage ? "1" : "0"), (bUDPContextSend ? "1" : "0"));); + + if ((clsConsts::u32SendCooldown) && + (can_yield())) + { + delay(clsConsts::u32SendCooldown); + } } #endif - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage_Multicast: %s!\n\n"), _DH(), ((bIPv4Result && bIPv6Result) ? "Succeeded" : "FAILED"));); - DEBUG_EX_ERR(if (!(bIPv4Result && bIPv6Result)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSMessage_Multicast: FAILED!\n"), _DH());); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast: %s!\n\n"), _DH(), ((bIPv4Result && bIPv6Result) ? "Succeeded" : "FAILED"));); + DEBUG_EX_ERR(if (!(bIPv4Result && bIPv6Result)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast: FAILED!\n"), _DH());); return (bIPv4Result && bIPv6Result); } /* - MDNSResponder::_prepareMDNSMessage + MDNSResponder::_prepareMessage The MDNS message is composed in a two-step process. In the first loop 'only' the header informations (mainly number of answers) are collected, @@ -218,7 +220,7 @@ bool MDNSResponder::clsHost::_sendMDNSMessage_Multicast(MDNSResponder::clsHost:: output buffer. */ -bool MDNSResponder::clsHost::_prepareMDNSMessage(MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage\n"));); bool bResult = true; @@ -227,13 +229,13 @@ bool MDNSResponder::clsHost::_prepareMDNSMessage(MDNSResponder::clsHost::stcSend p_rSendParameter.flushTempContent(); // Prepare header; count answers - stcMsgHeader msgHeader(p_rSendParameter.m_u16ID, - (static_cast(stcSendParameter::enuResponseType::None) != p_rSendParameter.m_Response), + clsMsgHeader msgHeader(p_rSendParameter.m_u16ID, + (static_cast(clsSendParameter::enuResponseType::None) != p_rSendParameter.m_Response), 0, p_rSendParameter.m_bAuthorative); // If this is a response, the answers are anwers, // else this is a query or probe and the answers go into auth section - uint16_t& ru16Answers = ((stcSendParameter::enuResponseType::None != p_rSendParameter.m_Response) + uint16_t& ru16Answers = ((clsSendParameter::enuResponseType::None != p_rSendParameter.m_Response) ? msgHeader.m_u16ANCount // Usual answers : msgHeader.m_u16NSCount); // Authorative answers @@ -269,8 +271,10 @@ bool MDNSResponder::clsHost::_prepareMDNSMessage(MDNSResponder::clsHost::stcSend : _writeMDNSMsgHeader(msgHeader, p_rSendParameter)); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSMsgHeader FAILED!\n"), _DH());); // Questions - for (stcRRQuestion* pQuestion = p_rSendParameter.m_pQuestions; ((bResult) && (pQuestion)); pQuestion = pQuestion->m_pNext) + for (clsRRQuestion::list::iterator it = p_rSendParameter.m_RRQuestions.begin(); ((bResult) && (it != p_rSendParameter.m_RRQuestions.end())); it++) { + clsRRQuestion* pQuestion = *it; + ((static_cast(enuSequence::Count) == sequence) ? ++msgHeader.m_u16QDCount : (bResult = _writeMDNSQuestion(*pQuestion, p_rSendParameter))); @@ -333,8 +337,10 @@ bool MDNSResponder::clsHost::_prepareMDNSMessage(MDNSResponder::clsHost::stcSend } #endif - for (stcService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) + for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) { + clsService* pService = *it; + // PTR_TYPE if ((bResult) && (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_TYPE))) @@ -382,8 +388,10 @@ bool MDNSResponder::clsHost::_prepareMDNSMessage(MDNSResponder::clsHost::stcSend #ifdef MDNS_IPV6_SUPPORT bool bNeedsAdditionalAnswerAAAA = false; #endif - for (stcService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) + for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) { + clsService* pService = *it; + if ((bResult) && (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_NAME)) && // If PTR_NAME is requested, AND (!(pService->m_u32ReplyMask & static_cast(enuContentFlag::SRV)))) // NOT SRV -> add SRV as additional answer @@ -425,9 +433,8 @@ bool MDNSResponder::clsHost::_prepareMDNSMessage(MDNSResponder::clsHost::stcSend // NSEC record for service if ((bResult) && (pService->m_u32ReplyMask) && - ((stcSendParameter::enuResponseType::None != p_rSendParameter.m_Response))) + ((clsSendParameter::enuResponseType::None != p_rSendParameter.m_Response))) { - ((static_cast(enuSequence::Count) == sequence) ? ++ru16AdditionalAnswers : (bResult = _writeMDNSAnswer_NSEC(*pService, (static_cast(enuContentFlag::TXT) | static_cast(enuContentFlag::SRV)), p_rSendParameter))); @@ -468,10 +475,9 @@ bool MDNSResponder::clsHost::_prepareMDNSMessage(MDNSResponder::clsHost::stcSend // NSEC host (part 2) if ((bResult) && - ((stcSendParameter::enuResponseType::None != p_rSendParameter.m_Response)) && + ((clsSendParameter::enuResponseType::None != p_rSendParameter.m_Response)) && (u32NSECContent)) { - // NSEC PTR IPv4/IPv6 are separate answers; make sure, that this is counted for #ifdef MDNS_IPV4_SUPPORT uint32_t u32NSECContent_PTR_IPv4 = (u32NSECContent & static_cast(enuContentFlag::PTR_IPv4)); @@ -518,61 +524,60 @@ bool MDNSResponder::clsHost::_prepareMDNSMessage(MDNSResponder::clsHost::stcSend } /* - MDNSResponder::_addMDNSQueryRecord + MDNSResponder::_addQueryRecord Adds a query for the given domain and query type. */ -bool MDNSResponder::clsHost::_addMDNSQueryRecord(MDNSResponder::clsHost::stcSendParameter& p_rSendParameter, - const MDNSResponder::clsHost::stcRRDomain& p_QueryDomain, - uint16_t p_u16RecordType) +bool clsLEAMDNSHost::_addQueryRecord(clsLEAMDNSHost::clsSendParameter& p_rSendParameter, + const clsLEAMDNSHost::clsRRDomain& p_QueryDomain, + uint16_t p_u16RecordType) { bool bResult = false; - stcRRQuestion* pQuestion = new stcRRQuestion; - if ((bResult = (0 != pQuestion))) + clsRRQuestion* pNewRRQuestion = new clsRRQuestion; + if ((bResult = (0 != pNewRRQuestion))) { // Link to list of questions - pQuestion->m_pNext = p_rSendParameter.m_pQuestions; - p_rSendParameter.m_pQuestions = pQuestion; + p_rSendParameter.m_RRQuestions.push_back(pNewRRQuestion); - pQuestion->m_Header.m_Domain = p_QueryDomain; + pNewRRQuestion->m_Header.m_Domain = p_QueryDomain; - pQuestion->m_Header.m_Attributes.m_u16Type = p_u16RecordType; + pNewRRQuestion->m_Header.m_Attributes.m_u16Type = p_u16RecordType; // It seems, that some mDNS implementations don't support 'unicast response' questions... - pQuestion->m_Header.m_Attributes.m_u16Class = (/*0x8000 |*/ DNS_RRCLASS_IN); // /*Unicast &*/ INternet + pNewRRQuestion->m_Header.m_Attributes.m_u16Class = (/*0x8000 |*/ DNS_RRCLASS_IN); // /*Unicast &*/ INternet } return bResult; } /* - MDNSResponder::_sendMDNSQuery + MDNSResponder::_sendQuery Creates and sends a query for the given domain and query type. */ -bool MDNSResponder::clsHost::_sendMDNSQuery(const MDNSResponder::clsHost::stcQuery& p_Query, - MDNSResponder::clsHost::stcQuery::stcAnswer* p_pKnownAnswers /*= 0*/) +bool clsLEAMDNSHost::_sendQuery(const clsLEAMDNSHost::clsQuery& p_Query, + clsLEAMDNSHost::clsQuery::clsAnswer::list* p_pKnownAnswers /*= 0*/) { bool bResult = false; - stcSendParameter sendParameter; + clsSendParameter sendParameter; switch (p_Query.m_QueryType) { - case stcQuery::enuQueryType::Host: + case clsQuery::enuQueryType::Host: #ifdef MDNS_IPV4_SUPPORT - bResult = _addMDNSQueryRecord(sendParameter, p_Query.m_Domain, DNS_RRTYPE_A); + bResult = _addQueryRecord(sendParameter, p_Query.m_Domain, DNS_RRTYPE_A); #endif #ifdef MDNS_IPV6_SUPPORT - bResult = _addMDNSQueryRecord(sendParameter, p_Query.m_Domain, DNS_RRTYPE_AAAA); + bResult = _addQueryRecord(sendParameter, p_Query.m_Domain, DNS_RRTYPE_AAAA); #endif break; - case stcQuery::enuQueryType::Service: - bResult = _addMDNSQueryRecord(sendParameter, p_Query.m_Domain, DNS_RRTYPE_PTR); + case clsQuery::enuQueryType::Service: + bResult = _addQueryRecord(sendParameter, p_Query.m_Domain, DNS_RRTYPE_PTR); break; - case stcQuery::enuQueryType::None: + case clsQuery::enuQueryType::None: default: break; } @@ -581,44 +586,48 @@ bool MDNSResponder::clsHost::_sendMDNSQuery(const MDNSResponder::clsHost::stcQue (void)p_pKnownAnswers; bResult = ((bResult) && - (_sendMDNSMessage(sendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSQuery: FAILED!\n"), _DH());); + (_sendMessage(sendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendQuery: FAILED!\n"), _DH());); return bResult; } /* - MDNSResponder::_sendMDNSQuery + MDNSResponder::_sendQuery Creates and sends a query for the given domain and record type. */ -bool MDNSResponder::clsHost::_sendMDNSQuery(const MDNSResponder::clsHost::stcRRDomain& p_QueryDomain, - uint16_t p_u16RecordType, - MDNSResponder::clsHost::stcQuery::stcAnswer* p_pKnownAnswers /*= 0*/) +bool clsLEAMDNSHost::_sendQuery(const clsLEAMDNSHost::clsRRDomain& p_QueryDomain, + uint16_t p_u16RecordType, + clsLEAMDNSHost::clsQuery::clsAnswer::list* p_pKnownAnswers /*= 0*/) { bool bResult = false; - stcSendParameter sendParameter; - bResult = ((_addMDNSQueryRecord(sendParameter, p_QueryDomain, p_u16RecordType)) && - (_sendMDNSMessage(sendParameter))); + clsSendParameter sendParameter; + bResult = ((_addQueryRecord(sendParameter, p_QueryDomain, p_u16RecordType)) && + (_sendMessage(sendParameter))); // TODO: Add known answer records (void) p_pKnownAnswers; - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMDNSQuery: FAILED!\n"), _DH());); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendQuery: FAILED!\n"), _DH());); return bResult; } /* MDNSResponder::_getResponderIPAddress */ -IPAddress MDNSResponder::clsHost::_getResponderIPAddress(enuIPProtocolType p_IPProtocolType) const +IPAddress clsLEAMDNSHost::_getResponderIPAddress(enuIPProtocolType p_IPProtocolType) const { IPAddress ipResponder; #ifdef MDNS_IPV4_SUPPORT if (enuIPProtocolType::V4 == p_IPProtocolType) { - ipResponder = netif_ip_addr4(&m_rNetIf); +#if LWIP_VERSION_MAJOR == 1 + ipResponder = ip_2_ip4(m_rNetIf.ip_addr); +#else + ipResponder = netif_ip_addr4(m_pNetIf); +#endif } #endif #ifdef MDNS_IPV6_SUPPORT @@ -629,13 +638,13 @@ IPAddress MDNSResponder::clsHost::_getResponderIPAddress(enuIPProtocolType p_IPP { for (int idx = 0; idx < LWIP_IPV6_NUM_ADDRESSES; ++idx) { - //DEBUG_EX_INFO(if ip6_addr_isvalid(netif_ip6_addr_state(&m_rNetIf, idx)) DEBUG_OUTPUT.printf_P(PSTR("%s _getResponderIPAddress: Checking IPv6 address %s (LL: %s)\n"), _DH(), IPAddress(netif_ip_addr6(&m_rNetIf, idx)).toString().c_str(), (bCheckLinkLocal ? "YES" : "NO"));); - if ((ip6_addr_isvalid(netif_ip6_addr_state(&m_rNetIf, idx))) && + //DEBUG_EX_INFO(if ip6_addr_isvalid(netif_ip6_addr_state(&m_rNetIf, idx)) DEBUG_OUTPUT.printf_P(PSTR("%s _getResponderIPAddress: Checking IPv6 address %s (LL: %s)\n"), _DH(), IPAddress(netif_ip_addr6(m_pNetIf, idx)).toString().c_str(), (bCheckLinkLocal ? "YES" : "NO"));); + if ((ip6_addr_isvalid(netif_ip6_addr_state(m_pNetIf, idx))) && (((!bCheckLinkLocal) || - (ip6_addr_islinklocal(netif_ip6_addr(&m_rNetIf, idx)))))) + (ip6_addr_islinklocal(netif_ip6_addr(m_pNetIf, idx)))))) { - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _getResponderIPAddress: Selected IPv6 address %s (LL: %s)\n"), _DH(), IPAddress(netif_ip_addr6(&m_rNetIf, idx)).toString().c_str(), (bCheckLinkLocal ? "YES" : "NO"));); - ipResponder = netif_ip_addr6(&m_rNetIf, idx); + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _getResponderIPAddress: Selected IPv6 address %s (LL: %s)\n"), _DH(), IPAddress(netif_ip_addr6(m_pNetIf, idx)).toString().c_str(), (bCheckLinkLocal ? "YES" : "NO"));); + ipResponder = netif_ip_addr6(m_pNetIf, idx); break; } } @@ -661,7 +670,7 @@ IPAddress MDNSResponder::clsHost::_getResponderIPAddress(enuIPProtocolType p_IPP Reads a question (eg. MyESP._http._tcp.local ANY IN) from the UPD input buffer. */ -bool MDNSResponder::clsHost::_readRRQuestion(MDNSResponder::clsHost::stcRRQuestion& p_rRRQuestion) +bool clsLEAMDNSHost::_readRRQuestion(clsLEAMDNSHost::clsRRQuestion& p_rRRQuestion) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRQuestion\n"));); @@ -694,20 +703,19 @@ bool MDNSResponder::clsHost::_readRRQuestion(MDNSResponder::clsHost::stcRRQuesti from the input buffer). */ -bool MDNSResponder::clsHost::_readRRAnswer(MDNSResponder::clsHost::stcRRAnswer*& p_rpRRAnswer) +bool clsLEAMDNSHost::_readRRAnswer(clsLEAMDNSHost::clsRRAnswer*& p_rpRRAnswer) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswer\n"));); bool bResult = false; - stcRRHeader header; + clsRRHeader header; uint32_t u32TTL; uint16_t u16RDLength; if ((_readRRHeader(header)) && (_udpRead32(u32TTL)) && (_udpRead16(u16RDLength))) { - /* DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswer: Reading 0x%04X answer (class:0x%04X, TTL:%u, RDLength:%u) for "), header.m_Attributes.m_u16Type, header.m_Attributes.m_u16Class, u32TTL, u16RDLength); _printRRDomain(header.m_Domain); @@ -718,31 +726,31 @@ bool MDNSResponder::clsHost::_readRRAnswer(MDNSResponder::clsHost::stcRRAnswer*& { #ifdef MDNS_IPV4_SUPPORT case DNS_RRTYPE_A: - p_rpRRAnswer = new stcRRAnswerA(header, u32TTL); - bResult = _readRRAnswerA(*(stcRRAnswerA*&)p_rpRRAnswer, u16RDLength); + p_rpRRAnswer = new clsRRAnswerA(header, u32TTL); + bResult = _readRRAnswerA(*(clsRRAnswerA*&)p_rpRRAnswer, u16RDLength); break; #endif case DNS_RRTYPE_PTR: - p_rpRRAnswer = new stcRRAnswerPTR(header, u32TTL); - bResult = _readRRAnswerPTR(*(stcRRAnswerPTR*&)p_rpRRAnswer, u16RDLength); + p_rpRRAnswer = new clsRRAnswerPTR(header, u32TTL); + bResult = _readRRAnswerPTR(*(clsRRAnswerPTR*&)p_rpRRAnswer, u16RDLength); break; case DNS_RRTYPE_TXT: - p_rpRRAnswer = new stcRRAnswerTXT(header, u32TTL); - bResult = _readRRAnswerTXT(*(stcRRAnswerTXT*&)p_rpRRAnswer, u16RDLength); + p_rpRRAnswer = new clsRRAnswerTXT(header, u32TTL); + bResult = _readRRAnswerTXT(*(clsRRAnswerTXT*&)p_rpRRAnswer, u16RDLength); break; #ifdef MDNS_IPV6_SUPPORT case DNS_RRTYPE_AAAA: - p_rpRRAnswer = new stcRRAnswerAAAA(header, u32TTL); - bResult = _readRRAnswerAAAA(*(stcRRAnswerAAAA*&)p_rpRRAnswer, u16RDLength); + p_rpRRAnswer = new clsRRAnswerAAAA(header, u32TTL); + bResult = _readRRAnswerAAAA(*(clsRRAnswerAAAA*&)p_rpRRAnswer, u16RDLength); break; #endif case DNS_RRTYPE_SRV: - p_rpRRAnswer = new stcRRAnswerSRV(header, u32TTL); - bResult = _readRRAnswerSRV(*(stcRRAnswerSRV*&)p_rpRRAnswer, u16RDLength); + p_rpRRAnswer = new clsRRAnswerSRV(header, u32TTL); + bResult = _readRRAnswerSRV(*(clsRRAnswerSRV*&)p_rpRRAnswer, u16RDLength); break; default: - p_rpRRAnswer = new stcRRAnswerGeneric(header, u32TTL); - bResult = _readRRAnswerGeneric(*(stcRRAnswerGeneric*&)p_rpRRAnswer, u16RDLength); + p_rpRRAnswer = new clsRRAnswerGeneric(header, u32TTL); + bResult = _readRRAnswerGeneric(*(clsRRAnswerGeneric*&)p_rpRRAnswer, u16RDLength); break; } DEBUG_EX_INFO( @@ -760,20 +768,20 @@ bool MDNSResponder::clsHost::_readRRAnswer(MDNSResponder::clsHost::stcRRAnswer*& { #ifdef MDNS_IPV4_SUPPORT case DNS_RRTYPE_A: - DEBUG_OUTPUT.printf_P(PSTR("A IP:%s"), ((stcRRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); + DEBUG_OUTPUT.printf_P(PSTR("A IP:%s"), ((clsRRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); break; #endif case DNS_RRTYPE_PTR: DEBUG_OUTPUT.printf_P(PSTR("PTR ")); - _printRRDomain(((stcRRAnswerPTR*&)p_rpRRAnswer)->m_PTRDomain); + _printRRDomain(((clsRRAnswerPTR*&)p_rpRRAnswer)->m_PTRDomain); break; case DNS_RRTYPE_TXT: { - size_t stTxtLength = ((stcRRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_strLength(); + size_t stTxtLength = ((clsRRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_strLength(); char* pTxts = new char[stTxtLength]; if (pTxts) { - ((stcRRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_str(pTxts); + ((clsRRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_str(pTxts); DEBUG_OUTPUT.printf_P(PSTR("TXT(%u) %s"), stTxtLength, pTxts); delete[] pTxts; } @@ -781,12 +789,12 @@ bool MDNSResponder::clsHost::_readRRAnswer(MDNSResponder::clsHost::stcRRAnswer*& } #ifdef MDNS_IPV6_SUPPORT case DNS_RRTYPE_AAAA: - DEBUG_OUTPUT.printf_P(PSTR("AAAA IP:%s"), ((stcRRAnswerAAAA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); + DEBUG_OUTPUT.printf_P(PSTR("AAAA IP:%s"), ((clsRRAnswerAAAA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); break; #endif case DNS_RRTYPE_SRV: - DEBUG_OUTPUT.printf_P(PSTR("SRV Port:%u "), ((stcRRAnswerSRV*&)p_rpRRAnswer)->m_u16Port); - _printRRDomain(((stcRRAnswerSRV*&)p_rpRRAnswer)->m_SRVDomain); + DEBUG_OUTPUT.printf_P(PSTR("SRV Port:%u "), ((clsRRAnswerSRV*&)p_rpRRAnswer)->m_u16Port); + _printRRDomain(((clsRRAnswerSRV*&)p_rpRRAnswer)->m_SRVDomain); break; /* case DNS_RRTYPE_NSEC: DEBUG_OUTPUT.printf_P(PSTR("NSEC ")); @@ -819,13 +827,12 @@ bool MDNSResponder::clsHost::_readRRAnswer(MDNSResponder::clsHost::stcRRAnswer*& /* MDNSResponder::_readRRAnswerA */ -bool MDNSResponder::clsHost::_readRRAnswerA(MDNSResponder::clsHost::stcRRAnswerA& p_rRRAnswerA, - uint16_t p_u16RDLength) +bool clsLEAMDNSHost::_readRRAnswerA(clsLEAMDNSHost::clsRRAnswerA& p_rRRAnswerA, + uint16_t p_u16RDLength) { - uint32_t u32IPv4Address; - bool bResult = ((MDNS_IPV4_SIZE == p_u16RDLength) && - (_udpReadBuffer((unsigned char*)&u32IPv4Address, MDNS_IPV4_SIZE)) && + bool bResult = ((clsConsts::u16IPv4Size == p_u16RDLength) && + (_udpReadBuffer((unsigned char*)&u32IPv4Address, clsConsts::u16IPv4Size)) && ((p_rRRAnswerA.m_IPAddress = IPAddress(u32IPv4Address)))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerA: FAILED!\n"), _DH());); return bResult; @@ -835,8 +842,8 @@ bool MDNSResponder::clsHost::_readRRAnswerA(MDNSResponder::clsHost::stcRRAnswerA /* MDNSResponder::_readRRAnswerPTR */ -bool MDNSResponder::clsHost::_readRRAnswerPTR(MDNSResponder::clsHost::stcRRAnswerPTR& p_rRRAnswerPTR, - uint16_t p_u16RDLength) +bool clsLEAMDNSHost::_readRRAnswerPTR(clsLEAMDNSHost::clsRRAnswerPTR& p_rRRAnswerPTR, + uint16_t p_u16RDLength) { bool bResult = ((p_u16RDLength) && (_readRRDomain(p_rRRAnswerPTR.m_PTRDomain))); @@ -849,8 +856,8 @@ bool MDNSResponder::clsHost::_readRRAnswerPTR(MDNSResponder::clsHost::stcRRAnswe Read TXT items from a buffer like 4c#=15ff=20 */ -bool MDNSResponder::clsHost::_readRRAnswerTXT(MDNSResponder::clsHost::stcRRAnswerTXT& p_rRRAnswerTXT, - uint16_t p_u16RDLength) +bool clsLEAMDNSHost::_readRRAnswerTXT(clsLEAMDNSHost::clsRRAnswerTXT& p_rRRAnswerTXT, + uint16_t p_u16RDLength) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: RDLength:%u\n"), _DH(), p_u16RDLength);); bool bResult = true; @@ -873,7 +880,7 @@ bool MDNSResponder::clsHost::_readRRAnswerTXT(MDNSResponder::clsHost::stcRRAnswe { bResult = false; - stcServiceTxt* pTxt = 0; + clsServiceTxt* pTxt = 0; unsigned char ucLength = *pucCursor++; // Length of the next txt item if (ucLength) { @@ -890,7 +897,7 @@ bool MDNSResponder::clsHost::_readRRAnswerTXT(MDNSResponder::clsHost::stcRRAnswe ((ucKeyLength = (pucEqualSign - pucCursor)))) { unsigned char ucValueLength = (ucLength - (pucEqualSign - pucCursor + 1)); - bResult = (((pTxt = new stcServiceTxt)) && + bResult = (((pTxt = new clsServiceTxt)) && (pTxt->setKey((const char*)pucCursor, ucKeyLength)) && (pTxt->setValue((const char*)(pucEqualSign + 1), ucValueLength))); } @@ -902,25 +909,26 @@ bool MDNSResponder::clsHost::_readRRAnswerTXT(MDNSResponder::clsHost::stcRRAnswe } else // no/zero length TXT { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: TXT answer contains no items.\n"), _DH());); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: INFO! TXT answer contains no items.\n"), _DH());); bResult = true; } if ((bResult) && - (pTxt)) // Everythings fine so far + (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; + p_rRRAnswerTXT.m_Txts.add(pTxt); } - else // At least no TXT (migth be OK, if length was 0) OR an error + else { + // At least no TXT (migth be OK, if length was 0) OR an error if (!bResult) { DEBUG_EX_ERR( DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: FAILED to read TXT item!\n"), _DH()); DEBUG_OUTPUT.printf_P(PSTR("RData dump:\n")); - _udpDump((m_rUDPContext.tell() - p_u16RDLength), p_u16RDLength); + _udpDump((m_pUDPContext->tell() - p_u16RDLength), p_u16RDLength); DEBUG_OUTPUT.printf_P(PSTR("\n")); ); } @@ -937,7 +945,7 @@ bool MDNSResponder::clsHost::_readRRAnswerTXT(MDNSResponder::clsHost::stcRRAnswe if (!bResult) // Some failure { DEBUG_OUTPUT.printf_P(PSTR("RData dump:\n")); - _udpDump((m_rUDPContext.tell() - p_u16RDLength), p_u16RDLength); + _udpDump((m_pUDPContext->tell() - p_u16RDLength), p_u16RDLength); DEBUG_OUTPUT.printf_P(PSTR("\n")); } ); @@ -956,23 +964,26 @@ bool MDNSResponder::clsHost::_readRRAnswerTXT(MDNSResponder::clsHost::stcRRAnswe } else { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: WARNING! No content!\n"), _DH());); + DEBUG_EX_ERR( + DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: WARNING! No content in TXT answer from "), _DH()); + _printRRDomain(p_rRRAnswerTXT.m_Header.m_Domain); + DEBUG_OUTPUT.printf_P(PSTR("\n")); + ); } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: FAILED!\n"), _DH());); return bResult; } #ifdef MDNS_IPV6_SUPPORT -bool MDNSResponder::clsHost::_readRRAnswerAAAA(MDNSResponder::clsHost::stcRRAnswerAAAA& p_rRRAnswerAAAA, - uint16_t p_u16RDLength) +bool clsLEAMDNSHost::_readRRAnswerAAAA(clsLEAMDNSHost::clsRRAnswerAAAA& p_rRRAnswerAAAA, + uint16_t p_u16RDLength) { bool bResult = false; uint32_t au32IPv6Address[4]; // 16 bytes - if ((bResult = ((MDNS_IPV6_SIZE == p_u16RDLength) && - (_udpReadBuffer((uint8_t*)&au32IPv6Address[0], MDNS_IPV6_SIZE))))) + if ((bResult = ((clsConsts::u16IPv6Size == p_u16RDLength) && + (_udpReadBuffer((uint8_t*)&au32IPv6Address[0], clsConsts::u16IPv6Size))))) { - // ?? IPADDR6_INIT_HOST ?? ip_addr_t addr = IPADDR6_INIT(au32IPv6Address[0], au32IPv6Address[1], au32IPv6Address[2], au32IPv6Address[3]); p_rRRAnswerAAAA.m_IPAddress = IPAddress(addr); @@ -985,8 +996,8 @@ bool MDNSResponder::clsHost::_readRRAnswerAAAA(MDNSResponder::clsHost::stcRRAnsw /* MDNSResponder::_readRRAnswerSRV */ -bool MDNSResponder::clsHost::_readRRAnswerSRV(MDNSResponder::clsHost::stcRRAnswerSRV& p_rRRAnswerSRV, - uint16_t p_u16RDLength) +bool clsLEAMDNSHost::_readRRAnswerSRV(clsLEAMDNSHost::clsRRAnswerSRV& p_rRRAnswerSRV, + uint16_t p_u16RDLength) { bool bResult = (((3 * sizeof(uint16_t)) < p_u16RDLength) && (_udpRead16(p_rRRAnswerSRV.m_u16Priority)) && @@ -1000,8 +1011,8 @@ bool MDNSResponder::clsHost::_readRRAnswerSRV(MDNSResponder::clsHost::stcRRAnswe /* MDNSResponder::_readRRAnswerGeneric */ -bool MDNSResponder::clsHost::_readRRAnswerGeneric(MDNSResponder::clsHost::stcRRAnswerGeneric& p_rRRAnswerGeneric, - uint16_t p_u16RDLength) +bool clsLEAMDNSHost::_readRRAnswerGeneric(clsLEAMDNSHost::clsRRAnswerGeneric& p_rRRAnswerGeneric, + uint16_t p_u16RDLength) { bool bResult = (0 == p_u16RDLength); @@ -1009,7 +1020,6 @@ bool MDNSResponder::clsHost::_readRRAnswerGeneric(MDNSResponder::clsHost::stcRRA if (((p_rRRAnswerGeneric.m_u16RDLength = p_u16RDLength)) && ((p_rRRAnswerGeneric.m_pu8RDData = new unsigned char[p_rRRAnswerGeneric.m_u16RDLength]))) { - bResult = _udpReadBuffer(p_rRRAnswerGeneric.m_pu8RDData, p_rRRAnswerGeneric.m_u16RDLength); } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerGeneric: FAILED!\n"), _DH());); @@ -1019,7 +1029,7 @@ bool MDNSResponder::clsHost::_readRRAnswerGeneric(MDNSResponder::clsHost::stcRRA /* MDNSResponder::_readRRHeader */ -bool MDNSResponder::clsHost::_readRRHeader(MDNSResponder::clsHost::stcRRHeader& p_rRRHeader) +bool clsLEAMDNSHost::_readRRHeader(clsLEAMDNSHost::clsRRHeader& p_rRRHeader) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRHeader\n"));); @@ -1035,7 +1045,7 @@ bool MDNSResponder::clsHost::_readRRHeader(MDNSResponder::clsHost::stcRRHeader& Reads a (maybe multilevel compressed) domain from the UDP input buffer. */ -bool MDNSResponder::clsHost::_readRRDomain(MDNSResponder::clsHost::stcRRDomain& p_rRRDomain) +bool clsLEAMDNSHost::_readRRDomain(clsLEAMDNSHost::clsRRDomain& p_rRRDomain) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain\n"));); @@ -1053,40 +1063,40 @@ bool MDNSResponder::clsHost::_readRRDomain(MDNSResponder::clsHost::stcRRDomain& the maximum recursion depth is set by MDNS_DOMAIN_MAX_REDIRCTION. */ -bool MDNSResponder::clsHost::_readRRDomain_Loop(MDNSResponder::clsHost::stcRRDomain& p_rRRDomain, - uint8_t p_u8Depth) +bool clsLEAMDNSHost::_readRRDomain_Loop(clsLEAMDNSHost::clsRRDomain& p_rRRDomain, + uint8_t p_u8Depth) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u)\n"), _DH(), p_u8Depth);); bool bResult = false; - if (MDNS_DOMAIN_MAX_REDIRCTION >= p_u8Depth) + if (clsConsts::u8DomainMaxRedirections >= p_u8Depth) { bResult = true; uint8_t u8Len = 0; do { - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u): Offset:%u p0:%02x\n"), _DH(), p_u8Depth, m_rUDPContext.tell(), m_rUDPContext.peek());); + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u): Offset:%u p0:%02x\n"), _DH(), p_u8Depth, m_pUDPContext->tell(), m_pUDPContext->peek());); _udpRead8(u8Len); - if (u8Len & MDNS_DOMAIN_COMPRESS_MARK) + if (u8Len & clsConsts::u8DomainCompressMark) { // Compressed label(s) - uint16_t u16Offset = ((u8Len & ~MDNS_DOMAIN_COMPRESS_MARK) << 8); // Implicit BE to LE conversion! + uint16_t u16Offset = ((u8Len & ~clsConsts::u8DomainCompressMark) << 8); // Implicit BE to LE conversion! _udpRead8(u8Len); u16Offset |= u8Len; - if (m_rUDPContext.isValidOffset(u16Offset)) + if (m_pUDPContext->isValidOffset(u16Offset)) { - size_t stCurrentPosition = m_rUDPContext.tell(); // Prepare return from recursion + size_t stCurrentPosition = m_pUDPContext->tell(); // Prepare return from recursion //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u): Redirecting from %u to %u!\n"), _DH(), p_u8Depth, stCurrentPosition, u16Offset);); - m_rUDPContext.seek(u16Offset); + m_pUDPContext->seek(u16Offset); if (_readRRDomain_Loop(p_rRRDomain, p_u8Depth + 1)) // Do recursion { //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(%u): Succeeded to read redirected label! Returning to %u\n"), _DH(), p_u8Depth, stCurrentPosition);); - m_rUDPContext.seek(stCurrentPosition); // Restore after recursion + m_pUDPContext->seek(stCurrentPosition); // Restore after recursion } else { @@ -1104,7 +1114,7 @@ bool MDNSResponder::clsHost::_readRRDomain_Loop(MDNSResponder::clsHost::stcRRDom else { // Normal (uncompressed) label (maybe '\0' only) - if (MDNS_DOMAIN_MAXLENGTH > (p_rRRDomain.m_u16NameLength + u8Len)) + if (clsConsts::stDomainMaxLength > (p_rRRDomain.m_u16NameLength + u8Len)) { // Add length byte p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength] = u8Len; @@ -1121,7 +1131,7 @@ bool MDNSResponder::clsHost::_readRRDomain_Loop(MDNSResponder::clsHost::stcRRDom p_rRRDomain.m_u16NameLength += u8Len; } } - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(2) offset:%u p0:%x\n"), _DH(), m_rUDPContext.tell(), m_rUDPContext.peek());); + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRDomain_Loop(2) offset:%u p0:%x\n"), _DH(), m_pUDPContext->tell(), m_pUDPContext->peek());); } else { @@ -1142,8 +1152,9 @@ bool MDNSResponder::clsHost::_readRRDomain_Loop(MDNSResponder::clsHost::stcRRDom /* MDNSResponder::_readRRAttributes + */ -bool MDNSResponder::clsHost::_readRRAttributes(MDNSResponder::clsHost::stcRRAttributes& p_rRRAttributes) +bool clsLEAMDNSHost::_readRRAttributes(clsLEAMDNSHost::clsRRAttributes& p_rRRAttributes) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAttributes\n"));); @@ -1155,7 +1166,9 @@ bool MDNSResponder::clsHost::_readRRAttributes(MDNSResponder::clsHost::stcRRAttr /* + DOMAIN NAMES + */ /* @@ -1164,15 +1177,15 @@ bool MDNSResponder::clsHost::_readRRAttributes(MDNSResponder::clsHost::stcRRAttr Builds a MDNS host domain (eg. esp8266.local) for the given hostname. */ -bool MDNSResponder::clsHost::_buildDomainForHost(const char* p_pcHostName, - MDNSResponder::clsHost::stcRRDomain& p_rHostDomain) const +bool clsLEAMDNSHost::_buildDomainForHost(const char* p_pcHostName, + clsLEAMDNSHost::clsRRDomain& p_rHostDomain) const { p_rHostDomain.clear(); bool bResult = ((p_pcHostName) && (*p_pcHostName) && (p_rHostDomain.addLabel(p_pcHostName)) && - (p_rHostDomain.addLabel(scpcLocal)) && + (p_rHostDomain.addLabel(clsConsts::pcLocal)) && (p_rHostDomain.addLabel(0))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _buildDomainForHost: FAILED!\n"), _DH());); return bResult; @@ -1185,13 +1198,13 @@ bool MDNSResponder::clsHost::_buildDomainForHost(const char* p_pcHostName, Used while detecting generic service enum question (DNS-SD) and answering these questions. */ -bool MDNSResponder::clsHost::_buildDomainForDNSSD(MDNSResponder::clsHost::stcRRDomain& p_rDNSSDDomain) const +bool clsLEAMDNSHost::_buildDomainForDNSSD(clsLEAMDNSHost::clsRRDomain& 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)) && + bool bResult = ((p_rDNSSDDomain.addLabel(clsConsts::pcServices, true)) && + (p_rDNSSDDomain.addLabel(clsConsts::pcDNSSD, true)) && + (p_rDNSSDDomain.addLabel(clsConsts::pcUDP, true)) && + (p_rDNSSDDomain.addLabel(clsConsts::pcLocal)) && (p_rDNSSDDomain.addLabel(0))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _buildDomainForDNSSD: FAILED!\n"), _DH());); return bResult; @@ -1204,16 +1217,16 @@ bool MDNSResponder::clsHost::_buildDomainForDNSSD(MDNSResponder::clsHost::stcRRD MyESP._http._tcp.local (if p_bIncludeName is set)). */ -bool MDNSResponder::clsHost::_buildDomainForService(const MDNSResponder::clsHost::stcService& p_Service, - bool p_bIncludeName, - MDNSResponder::clsHost::stcRRDomain& p_rServiceDomain) const +bool clsLEAMDNSHost::_buildDomainForService(const clsLEAMDNSHost::clsService& p_Service, + bool p_bIncludeName, + clsLEAMDNSHost::clsRRDomain& p_rServiceDomain) const { p_rServiceDomain.clear(); bool bResult = (((!p_bIncludeName) || - (p_rServiceDomain.addLabel(p_Service.m_pcName))) && - (p_rServiceDomain.addLabel(p_Service.m_pcServiceType, ('_' != *p_Service.m_pcServiceType))) && - (p_rServiceDomain.addLabel(p_Service.m_pcProtocol, ('_' != *p_Service.m_pcProtocol))) && - (p_rServiceDomain.addLabel(scpcLocal)) && + (p_rServiceDomain.addLabel(p_Service.instanceName()))) && + (p_rServiceDomain.addLabel(p_Service.type(), ('_' != *p_Service.type()))) && + (p_rServiceDomain.addLabel(p_Service.protocol(), ('_' != *p_Service.protocol()))) && + (p_rServiceDomain.addLabel(clsConsts::pcLocal)) && (p_rServiceDomain.addLabel(0))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _buildDomainForService: FAILED!\n"), _DH());); return bResult; @@ -1226,16 +1239,16 @@ bool MDNSResponder::clsHost::_buildDomainForService(const MDNSResponder::clsHost The usual prepended '_' are added, if missing in the input strings. */ -bool MDNSResponder::clsHost::_buildDomainForService(const char* p_pcServiceType, - const char* p_pcProtocol, - MDNSResponder::clsHost::stcRRDomain& p_rServiceDomain) const +bool clsLEAMDNSHost::_buildDomainForService(const char* p_pcServiceType, + const char* p_pcProtocol, + clsLEAMDNSHost::clsRRDomain& p_rServiceDomain) const { p_rServiceDomain.clear(); bool bResult = ((p_pcServiceType) && (p_pcProtocol) && (p_rServiceDomain.addLabel(p_pcServiceType, ('_' != *p_pcServiceType))) && (p_rServiceDomain.addLabel(p_pcProtocol, ('_' != *p_pcProtocol))) && - (p_rServiceDomain.addLabel(scpcLocal)) && + (p_rServiceDomain.addLabel(clsConsts::pcLocal)) && (p_rServiceDomain.addLabel(0))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _buildDomainForService: FAILED for (%s.%s)!\n"), _DH(), (p_pcServiceType ? : "-"), (p_pcProtocol ? : "-"));); return bResult; @@ -1248,9 +1261,10 @@ bool MDNSResponder::clsHost::_buildDomainForService(const char* p_pcServiceType, The IPv4 address is stringized by printing the four address bytes into a char buffer in reverse order and adding 'in-addr.arpa' (eg. 012.789.456.123.in-addr.arpa). Used while detecting reverse IPv4 questions and answering these + */ -bool MDNSResponder::clsHost::_buildDomainForReverseIPv4(IPAddress p_IPv4Address, - MDNSResponder::clsHost::stcRRDomain& p_rReverseIPv4Domain) const +bool clsLEAMDNSHost::_buildDomainForReverseIPv4(IPAddress p_IPv4Address, + clsLEAMDNSHost::clsRRDomain& p_rReverseIPv4Domain) const { bool bResult = ((p_IPv4Address.isSet()) && (p_IPv4Address.isV4())); @@ -1258,14 +1272,14 @@ bool MDNSResponder::clsHost::_buildDomainForReverseIPv4(IPAddress p_IPv4Address, p_rReverseIPv4Domain.clear(); char acBuffer[32]; - for (int i = MDNS_IPV4_SIZE; ((bResult) && (i >= 1)); --i) + for (int i = clsConsts::u16IPv4Size; ((bResult) && (i >= 1)); --i) { itoa(p_IPv4Address[i - 1], acBuffer, 10); bResult = p_rReverseIPv4Domain.addLabel(acBuffer); } bResult = ((bResult) && - (p_rReverseIPv4Domain.addLabel(scpcReverseIPv4Domain)) && - (p_rReverseIPv4Domain.addLabel(scpcReverseTopDomain)) && + (p_rReverseIPv4Domain.addLabel(clsConsts::pcReverseIPv4Domain)) && + (p_rReverseIPv4Domain.addLabel(clsConsts::pcReverseTopDomain)) && (p_rReverseIPv4Domain.addLabel(0))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _buildDomainForReverseIPv4: FAILED!\n"), _DH());); return bResult; @@ -1279,9 +1293,10 @@ bool MDNSResponder::clsHost::_buildDomainForReverseIPv4(IPAddress p_IPv4Address, The IPv6 address is stringized by printing the 16 address bytes (32 nibbles) into a char buffer in reverse order and adding 'ip6.arpa' (eg. 3.B.6.E.A.1.B.B.A.B.F.7.F.8.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.8.E.F.ip6.arpa). Used while detecting reverse IPv6 questions and answering these + */ -bool MDNSResponder::clsHost::_buildDomainForReverseIPv6(IPAddress p_IPv6Address, - MDNSResponder::clsHost::stcRRDomain& p_rReverseIPv6Domain) const +bool clsLEAMDNSHost::_buildDomainForReverseIPv6(IPAddress p_IPv6Address, + clsLEAMDNSHost::clsRRDomain& p_rReverseIPv6Domain) const { bool bResult = ((p_IPv6Address.isSet()) && (p_IPv6Address.isV6())); @@ -1289,7 +1304,7 @@ bool MDNSResponder::clsHost::_buildDomainForReverseIPv6(IPAddress p_IPv6Address, p_rReverseIPv6Domain.clear(); const uint16_t* pRaw = p_IPv6Address.raw6(); - for (int8_t i8 = (MDNS_IPV6_SIZE / 2); ((bResult) && (i8 > 0)); --i8) // 8..1 + for (int8_t i8 = (clsConsts::u16IPv6Size / 2); ((bResult) && (i8 > 0)); --i8) // 8..1 { uint16_t u16Part = ntohs(pRaw[i8 - 1] & 0xFFFF); char acBuffer[2]; @@ -1301,8 +1316,8 @@ bool MDNSResponder::clsHost::_buildDomainForReverseIPv6(IPAddress p_IPv6Address, } } bResult = ((bResult) && - (p_rReverseIPv6Domain.addLabel(scpcReverseIPv6Domain)) && // .ip6.arpa - (p_rReverseIPv6Domain.addLabel(scpcReverseTopDomain)) && // .local + (p_rReverseIPv6Domain.addLabel(clsConsts::pcReverseIPv6Domain)) && // .ip6.arpa + (p_rReverseIPv6Domain.addLabel(clsConsts::pcReverseTopDomain)) && // .local (p_rReverseIPv6Domain.addLabel(0))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _buildDomainForReverseIPv6: FAILED!\n"), _DH());); return bResult; @@ -1311,35 +1326,40 @@ bool MDNSResponder::clsHost::_buildDomainForReverseIPv6(IPAddress p_IPv6Address, /* + UDP + */ /* MDNSResponder::_udpReadBuffer + */ -bool MDNSResponder::clsHost::_udpReadBuffer(unsigned char* p_pBuffer, - size_t p_stLength) +bool clsLEAMDNSHost::_udpReadBuffer(unsigned char* p_pBuffer, + size_t p_stLength) { - bool bResult = ((true/*m_rUDPContext.getSize() > p_stLength*/) && + bool bResult = ((m_pUDPContext->getSize() >= p_stLength) && (p_pBuffer) && (p_stLength) && - ((p_stLength == m_rUDPContext.read((char*)p_pBuffer, p_stLength)))); + ((p_stLength == m_pUDPContext->read((char*)p_pBuffer, p_stLength)))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _udpReadBuffer: FAILED!\n"), _DH());); return bResult; } /* MDNSResponder::_udpRead8 + */ -bool MDNSResponder::clsHost::_udpRead8(uint8_t& p_ru8Value) +bool clsLEAMDNSHost::_udpRead8(uint8_t& p_ru8Value) { return _udpReadBuffer((unsigned char*)&p_ru8Value, sizeof(p_ru8Value)); } /* MDNSResponder::_udpRead16 + */ -bool MDNSResponder::clsHost::_udpRead16(uint16_t& p_ru16Value) +bool clsLEAMDNSHost::_udpRead16(uint16_t& p_ru16Value) { bool bResult = false; @@ -1353,8 +1373,9 @@ bool MDNSResponder::clsHost::_udpRead16(uint16_t& p_ru16Value) /* MDNSResponder::_udpRead32 + */ -bool MDNSResponder::clsHost::_udpRead32(uint32_t& p_ru32Value) +bool clsLEAMDNSHost::_udpRead32(uint32_t& p_ru32Value) { bool bResult = false; @@ -1368,29 +1389,32 @@ bool MDNSResponder::clsHost::_udpRead32(uint32_t& p_ru32Value) /* MDNSResponder::_udpAppendBuffer + */ -bool MDNSResponder::clsHost::_udpAppendBuffer(const unsigned char* p_pcBuffer, - size_t p_stLength) +bool clsLEAMDNSHost::_udpAppendBuffer(const unsigned char* p_pcBuffer, + size_t p_stLength) { bool bResult = ((p_pcBuffer) && (p_stLength) && - (p_stLength == m_rUDPContext.append((const char*)p_pcBuffer, p_stLength))); + (p_stLength == m_pUDPContext->append((const char*)p_pcBuffer, p_stLength))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _udpAppendBuffer: FAILED!\n"), _DH());); return bResult; } /* MDNSResponder::_udpAppend8 + */ -bool MDNSResponder::clsHost::_udpAppend8(uint8_t p_u8Value) +bool clsLEAMDNSHost::_udpAppend8(uint8_t p_u8Value) { return (_udpAppendBuffer((unsigned char*)&p_u8Value, sizeof(p_u8Value))); } /* MDNSResponder::_udpAppend16 + */ -bool MDNSResponder::clsHost::_udpAppend16(uint16_t p_u16Value) +bool clsLEAMDNSHost::_udpAppend16(uint16_t p_u16Value) { p_u16Value = lwip_htons(p_u16Value); return (_udpAppendBuffer((unsigned char*)&p_u16Value, sizeof(p_u16Value))); @@ -1398,8 +1422,9 @@ bool MDNSResponder::clsHost::_udpAppend16(uint16_t p_u16Value) /* MDNSResponder::_udpAppend32 + */ -bool MDNSResponder::clsHost::_udpAppend32(uint32_t p_u32Value) +bool clsLEAMDNSHost::_udpAppend32(uint32_t p_u32Value) { p_u32Value = lwip_htonl(p_u32Value); return (_udpAppendBuffer((unsigned char*)&p_u32Value, sizeof(p_u32Value))); @@ -1408,12 +1433,13 @@ bool MDNSResponder::clsHost::_udpAppend32(uint32_t p_u32Value) #ifdef DEBUG_ESP_MDNS_RESPONDER /* MDNSResponder::_udpDump + */ -bool MDNSResponder::clsHost::_udpDump(bool p_bMovePointer /*= false*/) +bool clsLEAMDNSHost::_udpDump(bool p_bMovePointer /*= false*/) { const uint8_t cu8BytesPerLine = 16; - uint32_t u32StartPosition = m_rUDPContext.tell(); + uint32_t u32StartPosition = m_pUDPContext->tell(); DEBUG_OUTPUT.println("UDP Context Dump:"); uint32_t u32Counter = 0; uint8_t u8Byte = 0; @@ -1426,29 +1452,30 @@ bool MDNSResponder::clsHost::_udpDump(bool p_bMovePointer /*= false*/) if (!p_bMovePointer) // Restore { - m_rUDPContext.seek(u32StartPosition); + m_pUDPContext->seek(u32StartPosition); } return true; } /* MDNSResponder::_udpDump + */ -bool MDNSResponder::clsHost::_udpDump(unsigned p_uOffset, - unsigned p_uLength) +bool clsLEAMDNSHost::_udpDump(unsigned p_uOffset, + unsigned p_uLength) { - if (m_rUDPContext.isValidOffset(p_uOffset)) + if (m_pUDPContext->isValidOffset(p_uOffset)) { - unsigned uCurrentPosition = m_rUDPContext.tell(); // Remember start position + unsigned uCurrentPosition = m_pUDPContext->tell(); // Remember start position - m_rUDPContext.seek(p_uOffset); + m_pUDPContext->seek(p_uOffset); uint8_t u8Byte; for (unsigned u = 0; ((u < p_uLength) && (_udpRead8(u8Byte))); ++u) { DEBUG_OUTPUT.printf_P(PSTR("%02x "), u8Byte); } // Return to start position - m_rUDPContext.seek(uCurrentPosition); + m_pUDPContext->seek(uCurrentPosition); } return true; } @@ -1471,8 +1498,9 @@ bool MDNSResponder::clsHost::_udpDump(unsigned p_uOffset, 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::clsHost::_readMDNSMsgHeader(MDNSResponder::clsHost::stcMsgHeader& p_rMsgHeader) +bool clsLEAMDNSHost::_readMDNSMsgHeader(clsLEAMDNSHost::clsMsgHeader& p_rMsgHeader) { bool bResult = false; @@ -1514,9 +1542,10 @@ bool MDNSResponder::clsHost::_readMDNSMsgHeader(MDNSResponder::clsHost::stcMsgHe /* MDNSResponder::_write8 + */ -bool MDNSResponder::clsHost::_write8(uint8_t p_u8Value, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_write8(uint8_t p_u8Value, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { return ((_udpAppend8(p_u8Value)) && (p_rSendParameter.shiftOffset(sizeof(p_u8Value)))); @@ -1524,9 +1553,10 @@ bool MDNSResponder::clsHost::_write8(uint8_t p_u8Value, /* MDNSResponder::_write16 + */ -bool MDNSResponder::clsHost::_write16(uint16_t p_u16Value, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_write16(uint16_t p_u16Value, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { return ((_udpAppend16(p_u16Value)) && (p_rSendParameter.shiftOffset(sizeof(p_u16Value)))); @@ -1534,9 +1564,10 @@ bool MDNSResponder::clsHost::_write16(uint16_t p_u16Value, /* MDNSResponder::_write32 + */ -bool MDNSResponder::clsHost::_write32(uint32_t p_u32Value, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_write32(uint32_t p_u32Value, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { return ((_udpAppend32(p_u32Value)) && (p_rSendParameter.shiftOffset(sizeof(p_u32Value)))); @@ -1550,9 +1581,10 @@ bool MDNSResponder::clsHost::_write32(uint32_t p_u32Value, 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::clsHost::_writeMDNSMsgHeader(const MDNSResponder::clsHost::stcMsgHeader& p_MsgHeader, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSMsgHeader(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { /* DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSMsgHeader: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), _DH(), @@ -1580,9 +1612,10 @@ bool MDNSResponder::clsHost::_writeMDNSMsgHeader(const MDNSResponder::clsHost::s /* MDNSResponder::_writeRRAttributes + */ -bool MDNSResponder::clsHost::_writeMDNSRRAttributes(const MDNSResponder::clsHost::stcRRAttributes& p_Attributes, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSRRAttributes(const clsLEAMDNSHost::clsRRAttributes& p_Attributes, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { bool bResult = ((_write16(p_Attributes.m_u16Type, p_rSendParameter)) && (_write16(p_Attributes.m_u16Class, p_rSendParameter))); @@ -1593,9 +1626,10 @@ bool MDNSResponder::clsHost::_writeMDNSRRAttributes(const MDNSResponder::clsHost /* MDNSResponder::_writeMDNSRRDomain + */ -bool MDNSResponder::clsHost::_writeMDNSRRDomain(const MDNSResponder::clsHost::stcRRDomain& p_Domain, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSRRDomain(const clsLEAMDNSHost::clsRRDomain& p_Domain, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { bool bResult = ((_udpAppendBuffer((const unsigned char*)p_Domain.m_acName, p_Domain.m_u16NameLength)) && (p_rSendParameter.shiftOffset(p_Domain.m_u16NameLength))); @@ -1619,26 +1653,26 @@ bool MDNSResponder::clsHost::_writeMDNSRRDomain(const MDNSResponder::clsHost::st and written to the output buffer. */ -bool MDNSResponder::clsHost::_writeMDNSHostDomain(const char* p_pcHostName, - bool p_bPrependRDLength, - uint16_t p_u16AdditionalLength, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSHostDomain(const char* p_pcHostName, + bool p_bPrependRDLength, + uint16_t p_u16AdditionalLength, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV' uint16_t u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)p_pcHostName, false); - stcRRDomain hostDomain; + clsRRDomain hostDomain; bool bResult = (u16CachedDomainOffset // Found cached domain -> mark as compressed domain - ? ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset + ? ((clsConsts::u8DomainCompressMark > ((u16CachedDomainOffset >> 8) & ~clsConsts::u8DomainCompressMark)) && // Valid offset ((!p_bPrependRDLength) || - (_write16((2 + p_u16AdditionalLength), p_rSendParameter))) && // Length of 'Cxxx' - (_write8(((u16CachedDomainOffset >> 8) | MDNS_DOMAIN_COMPRESS_MARK), p_rSendParameter)) && // Compression mark (and offset) + (_write16((2 + p_u16AdditionalLength), p_rSendParameter))) && // Length of 'Cxxx' + (_write8(((u16CachedDomainOffset >> 8) | clsConsts::u8DomainCompressMark), p_rSendParameter)) && // Compression mark (and offset) (_write8((uint8_t)(u16CachedDomainOffset & 0xFF), p_rSendParameter))) // No cached domain -> add this domain to cache and write full domain name - : ((_buildDomainForHost(p_pcHostName, hostDomain)) && // eg. esp8266.local + : ((_buildDomainForHost(p_pcHostName, hostDomain)) && // eg. esp8266.local ((!p_bPrependRDLength) || - (_write16((hostDomain.m_u16NameLength + p_u16AdditionalLength), p_rSendParameter))) && // RDLength (if needed) + (_write16((hostDomain.m_u16NameLength + p_u16AdditionalLength), p_rSendParameter))) && // RDLength (if needed) (p_rSendParameter.addDomainCacheItem((const void*)p_pcHostName, false, p_rSendParameter.m_u16Offset)) && (_writeMDNSRRDomain(hostDomain, p_rSendParameter)))); @@ -1659,27 +1693,27 @@ bool MDNSResponder::clsHost::_writeMDNSHostDomain(const char* p_pcHostName, the instance name (p_bIncludeName is set) and thoose who don't. */ -bool MDNSResponder::clsHost::_writeMDNSServiceDomain(const MDNSResponder::clsHost::stcService& p_Service, - bool p_bIncludeName, - bool p_bPrependRDLength, - uint16_t p_u16AdditionalLength, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSServiceDomain(const clsLEAMDNSHost::clsService& p_Service, + bool p_bIncludeName, + bool p_bPrependRDLength, + uint16_t p_u16AdditionalLength, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV' uint16_t u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)&p_Service, p_bIncludeName); - stcRRDomain serviceDomain; + clsRRDomain serviceDomain; bool bResult = (u16CachedDomainOffset // Found cached domain -> mark as compressed domain - ? ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset + ? ((clsConsts::u8DomainCompressMark > ((u16CachedDomainOffset >> 8) & ~clsConsts::u8DomainCompressMark)) && // Valid offset ((!p_bPrependRDLength) || - (_write16((2 + p_u16AdditionalLength), p_rSendParameter))) && // Lenght of 'Cxxx' - (_write8(((u16CachedDomainOffset >> 8) | MDNS_DOMAIN_COMPRESS_MARK), p_rSendParameter)) && // Compression mark (and offset) + (_write16((2 + p_u16AdditionalLength), p_rSendParameter))) && // Lenght of 'Cxxx' + (_write8(((u16CachedDomainOffset >> 8) | clsConsts::u8DomainCompressMark), p_rSendParameter)) && // Compression mark (and offset) (_write8((uint8_t)(u16CachedDomainOffset & 0xFF), p_rSendParameter))) // No cached domain -> add this domain to cache and write full domain name - : ((_buildDomainForService(p_Service, p_bIncludeName, serviceDomain)) && // eg. MyESP._http._tcp.local + : ((_buildDomainForService(p_Service, p_bIncludeName, serviceDomain)) && // eg. MyESP._http._tcp.local ((!p_bPrependRDLength) || - (_write16((serviceDomain.m_u16NameLength + p_u16AdditionalLength), p_rSendParameter))) && // RDLength (if needed) + (_write16((serviceDomain.m_u16NameLength + p_u16AdditionalLength), p_rSendParameter))) && // RDLength (if needed) (p_rSendParameter.addDomainCacheItem((const void*)&p_Service, p_bIncludeName, p_rSendParameter.m_u16Offset)) && (_writeMDNSRRDomain(serviceDomain, p_rSendParameter)))); @@ -1698,8 +1732,8 @@ bool MDNSResponder::clsHost::_writeMDNSServiceDomain(const MDNSResponder::clsHos QCLASS (16bit, eg. IN) */ -bool MDNSResponder::clsHost::_writeMDNSQuestion(MDNSResponder::clsHost::stcRRQuestion& p_Question, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSQuestion(clsLEAMDNSHost::clsRRQuestion& p_Question, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSQuestion\n"));); @@ -1716,7 +1750,6 @@ bool MDNSResponder::clsHost::_writeMDNSQuestion(MDNSResponder::clsHost::stcRRQue }); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSQuestion: FAILED!\n"), _DH());); return bResult; - } @@ -1735,24 +1768,25 @@ bool MDNSResponder::clsHost::_writeMDNSQuestion(MDNSResponder::clsHost::stcRRQue eg. esp8266.local A 0x8001 120 4 123.456.789.012 Ref: http://www.zytrax.com/books/dns/ch8/a.html + */ -bool MDNSResponder::clsHost::_writeMDNSAnswer_A(IPAddress p_IPAddress, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSAnswer_A(IPAddress p_IPAddress, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_A (%s)%s\n"), p_IPAddress.toString().c_str(), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); - stcRRAttributes attributes(DNS_RRTYPE_A, + clsRRAttributes attributes(DNS_RRTYPE_A, ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - const unsigned char aucIPAddress[MDNS_IPV4_SIZE] = { p_IPAddress[0], p_IPAddress[1], p_IPAddress[2], p_IPAddress[3] }; + const unsigned char aucIPAddress[clsConsts::u16IPv4Size] = { p_IPAddress[0], p_IPAddress[1], p_IPAddress[2], p_IPAddress[3] }; bool bResult = ((p_IPAddress.isV4()) && (_writeMDNSHostDomain(m_pcHostName, false, 0, p_rSendParameter)) && (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS (_write32((p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), p_rSendParameter)) && // TTL - (_write16(MDNS_IPV4_SIZE, p_rSendParameter)) && // RDLength - (_udpAppendBuffer(aucIPAddress, MDNS_IPV4_SIZE)) && // RData - (p_rSendParameter.shiftOffset(MDNS_IPV4_SIZE))); + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), p_rSendParameter)) && // TTL + (_write16(clsConsts::u16IPv4Size, p_rSendParameter)) && // RDLength + (_udpAppendBuffer(aucIPAddress, clsConsts::u16IPv4Size)) && // RData + (p_rSendParameter.shiftOffset(clsConsts::u16IPv4Size))); DEBUG_EX_INFO(if (bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_A %s.local Type:%s Class:0x%04X TTL:%u %s\n"), @@ -1762,7 +1796,7 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_A(IPAddress p_IPAddress, attributes.m_u16Class, (p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), p_IPAddress.toString().c_str()); ); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_A: FAILED!\n"), _DH());); @@ -1778,23 +1812,24 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_A(IPAddress p_IPAddress, eg. 012.789.456.123.in-addr.arpa PTR 0x8001 120 15 esp8266.local Used while answering reverse IPv4 questions + */ -bool MDNSResponder::clsHost::_writeMDNSAnswer_PTR_IPv4(IPAddress p_IPAddress, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSAnswer_PTR_IPv4(IPAddress p_IPAddress, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv4 (%s)%s\n"), p_IPAddress.toString().c_str(), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); - stcRRDomain reverseIPv4Domain; - stcRRAttributes attributes(DNS_RRTYPE_PTR, + clsRRDomain reverseIPv4Domain; + clsRRAttributes attributes(DNS_RRTYPE_PTR, ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - stcRRDomain hostDomain; + clsRRDomain hostDomain; bool bResult = ((p_IPAddress.isV4()) && (_buildDomainForReverseIPv4(p_IPAddress, reverseIPv4Domain)) && // 012.789.456.123.in-addr.arpa (_writeMDNSRRDomain(reverseIPv4Domain, p_rSendParameter)) && (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS (_write32((p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), p_rSendParameter)) && // TTL + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), p_rSendParameter)) && // TTL (_writeMDNSHostDomain(m_pcHostName, true, 0, p_rSendParameter))); // RDLength & RData (host domain, eg. esp8266.local) DEBUG_EX_INFO(if (bResult) @@ -1805,7 +1840,7 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_PTR_IPv4(IPAddress p_IPAddress, attributes.m_u16Class, (p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), m_pcHostName); ); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv4: FAILED!\n"), _DH());); @@ -1822,21 +1857,22 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_PTR_IPv4(IPAddress p_IPAddress, 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::clsHost::_writeMDNSAnswer_PTR_TYPE(MDNSResponder::clsHost::stcService& p_rService, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSAnswer_PTR_TYPE(clsLEAMDNSHost::clsService& p_rService, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_TYPE\n"));); - stcRRDomain dnssdDomain; - stcRRDomain serviceDomain; - stcRRAttributes attributes(DNS_RRTYPE_PTR, DNS_RRCLASS_IN); // No cache flush for shared records! only INternet + clsRRDomain dnssdDomain; + clsRRDomain serviceDomain; + clsRRAttributes attributes(DNS_RRTYPE_PTR, DNS_RRCLASS_IN); // No cache flush for shared records! only INternet bool bResult = ((_buildDomainForDNSSD(dnssdDomain)) && // _services._dns-sd._udp.local (_writeMDNSRRDomain(dnssdDomain, p_rSendParameter)) && (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS (_write32((p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL)), p_rSendParameter)) && // TTL + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL)), p_rSendParameter)) && // TTL (_writeMDNSServiceDomain(p_rService, false, true, 0, p_rSendParameter))); // RDLength & RData (service domain, eg. _http._tcp.local) DEBUG_EX_INFO(if (bResult) @@ -1847,8 +1883,8 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_PTR_TYPE(MDNSResponder::clsHost::s attributes.m_u16Class, (p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL)), - p_rService.m_pcServiceType, + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL)), + p_rService.m_pcType, p_rService.m_pcProtocol); ); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_TYPE: FAILED!\n"), _DH());); @@ -1864,32 +1900,33 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_PTR_TYPE(MDNSResponder::clsHost::s 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::clsHost::_writeMDNSAnswer_PTR_NAME(MDNSResponder::clsHost::stcService& p_rService, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSAnswer_PTR_NAME(clsLEAMDNSHost::clsService& p_rService, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_NAME\n"), _DH());); - stcRRAttributes attributes(DNS_RRTYPE_PTR, DNS_RRCLASS_IN); // No cache flush for shared records! only INternet + clsRRAttributes attributes(DNS_RRTYPE_PTR, DNS_RRCLASS_IN); // No cache flush for shared records! only INternet bool bResult = ((_writeMDNSServiceDomain(p_rService, false, false, 0, p_rSendParameter)) && // _http._tcp.local (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS (_write32((p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL)), p_rSendParameter)) && // TTL + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL)), p_rSendParameter)) && // TTL (_writeMDNSServiceDomain(p_rService, true, true, 0, p_rSendParameter))); // RDLength & RData (service domain, eg. MyESP._http._tcp.local) DEBUG_EX_INFO(if (bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_NAME _%s._%s.local Type:%s Class:0x%04X TTL:%u %s._%s._%s.local\n"), _DH(), - p_rService.m_pcServiceType, + p_rService.m_pcType, p_rService.m_pcProtocol, _RRType2Name(attributes.m_u16Type), attributes.m_u16Class, (p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL)), - p_rService.m_pcName, - p_rService.m_pcServiceType, + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL)), + p_rService.m_pcInstanceName, + p_rService.m_pcType, p_rService.m_pcProtocol); ); DEBUG_EX_ERR(if (!bResult)DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_NAME: FAILED!\n"), _DH());); @@ -1907,15 +1944,16 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_PTR_NAME(MDNSResponder::clsHost::s eg. myESP._http._tcp.local TXT 0x8001 120 4 c#=1 http://www.zytrax.com/books/dns/ch8/txt.html + */ -bool MDNSResponder::clsHost::_writeMDNSAnswer_TXT(MDNSResponder::clsHost::stcService& p_rService, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSAnswer_TXT(clsLEAMDNSHost::clsService& p_rService, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_TXT%s\n"), (p_rSendParameter.m_bCacheFlush ? "" : " nF"), _DH());); bool bResult = false; - stcRRAttributes attributes(DNS_RRTYPE_TXT, + clsRRAttributes attributes(DNS_RRTYPE_TXT, ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet if ((_collectServiceTxts(p_rService)) && @@ -1923,42 +1961,59 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_TXT(MDNSResponder::clsHost::stcSer (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS (_write32((p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL)), p_rSendParameter)) && // TTL - (_write16(p_rService.m_Txts.length(), p_rSendParameter))) // RDLength + : (p_rSendParameter.m_bLegacyDNSQuery // TTL + ? clsConsts::u32LegacyTTL + : clsConsts::u32ServiceTTL)), p_rSendParameter)) && + (_write16((p_rService.m_Txts.count() // RDLength + ? p_rService.m_Txts.length() // default case + : 1), p_rSendParameter))) // If no TXT records exist, a single 0 byte is sent { - bResult = true; // RData Txts - for (stcServiceTxt* pTxt = p_rService.m_Txts.m_pTxts; ((bResult) && (pTxt)); pTxt = pTxt->m_pNext) + if (p_rService.m_Txts.count()) { - 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_rUDPContext.append(pTxt->m_pcKey, os_strlen(pTxt->m_pcKey))) && // Key - (p_rSendParameter.shiftOffset((size_t)os_strlen(pTxt->m_pcKey))) && - (1 == m_rUDPContext.append("=", 1)) && // = - (p_rSendParameter.shiftOffset(1)) && - ((!pTxt->m_pcValue) || - (((size_t)os_strlen(pTxt->m_pcValue) == m_rUDPContext.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("%s _writeMDNSAnswer_TXT: FAILED to write %sTxt %s=%s!\n"), _DH(), (pTxt->m_bTemp ? "temp. " : ""), (pTxt->m_pcKey ? : "?"), (pTxt->m_pcValue ? : "?"));); + for (const clsServiceTxt* pTxt : p_rService.m_Txts.m_Txts) + { + unsigned char ucLengthByte = pTxt->length(); + if (!((bResult = ((_udpAppendBuffer((unsigned char*)&ucLengthByte, sizeof(ucLengthByte))) && // Length + (p_rSendParameter.shiftOffset(sizeof(ucLengthByte))) && + ((size_t)os_strlen(pTxt->m_pcKey) == m_pUDPContext->append(pTxt->m_pcKey, os_strlen(pTxt->m_pcKey))) && // Key + (p_rSendParameter.shiftOffset((size_t)os_strlen(pTxt->m_pcKey))) && + (1 == m_pUDPContext->append("=", 1)) && // = + (p_rSendParameter.shiftOffset(1)) && + ((!pTxt->m_pcValue) || + (((size_t)os_strlen(pTxt->m_pcValue) == m_pUDPContext->append(pTxt->m_pcValue, os_strlen(pTxt->m_pcValue))) && // Value + (p_rSendParameter.shiftOffset((size_t)os_strlen(pTxt->m_pcValue))))))))) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_TXT: FAILED to write %sTxt %s=%s!\n"), _DH(), (pTxt->m_bTemp ? "temp. " : ""), (pTxt->m_pcKey ? : "?"), (pTxt->m_pcValue ? : "?"));); + break; + } + } + } + else + { + // RFC 6763 Ch.6: Every DNS-SD service MUST have a TXT record in addition to its SRV record, ... + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_TXT: Adding EMPTY TXT record!\n"), _DH());); + unsigned char ucLengthByte = 0; + bResult = ((_udpAppendBuffer((unsigned char*)&ucLengthByte, sizeof(ucLengthByte))) && // Length + (p_rSendParameter.shiftOffset(sizeof(ucLengthByte)))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_TXT: FAILED to write EMPTY TXT record!\n"), _DH());); } } DEBUG_EX_INFO(if (bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_TXT %s._%s._%s.local Type:%s Class:0x%04X TTL:%u \n"), _DH(), - p_rService.m_pcName, - p_rService.m_pcServiceType, + p_rService.m_pcInstanceName, + p_rService.m_pcType, p_rService.m_pcProtocol, _RRType2Name(attributes.m_u16Type), attributes.m_u16Class, (p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL)), - p_rService.m_pcName, - p_rService.m_pcServiceType, + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL)), + p_rService.m_pcInstanceName, + p_rService.m_pcType, p_rService.m_pcProtocol); ); @@ -1977,23 +2032,24 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_TXT(MDNSResponder::clsHost::stcSer eg. esp8266.local AAAA 0x8001 120 16 xxxx::xx http://www.zytrax.com/books/dns/ch8/aaaa.html + */ -bool MDNSResponder::clsHost::_writeMDNSAnswer_AAAA(IPAddress p_IPAddress, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSAnswer_AAAA(IPAddress p_IPAddress, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_AAAA (%s)%s\n"), p_IPAddress.toString().c_str(), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); - stcRRAttributes attributes(DNS_RRTYPE_AAAA, + clsRRAttributes attributes(DNS_RRTYPE_AAAA, ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet bool bResult = ((p_IPAddress.isV6()) && (_writeMDNSHostDomain(m_pcHostName, false, 0, p_rSendParameter)) && // esp8266.local (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS (_write32((p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), p_rSendParameter)) && // TTL - (_write16(MDNS_IPV6_SIZE, p_rSendParameter)) && // RDLength - (_udpAppendBuffer((uint8_t*)p_IPAddress.raw6(), MDNS_IPV6_SIZE)) && // RData - (p_rSendParameter.shiftOffset(MDNS_IPV6_SIZE))); + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), p_rSendParameter)) && // TTL + (_write16(clsConsts::u16IPv6Size, p_rSendParameter)) && // RDLength + (_udpAppendBuffer((uint8_t*)p_IPAddress.raw6(), clsConsts::u16IPv6Size)) && // RData + (p_rSendParameter.shiftOffset(clsConsts::u16IPv6Size))); DEBUG_EX_INFO(if (bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_AAAA %s.local Type:%s Class:0x%04X TTL:%u %s\n"), @@ -2003,7 +2059,7 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_AAAA(IPAddress p_IPAddress, attributes.m_u16Class, (p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), p_IPAddress.toString().c_str()); ); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_AAAA: FAILED!\n"), _DH());); @@ -2018,14 +2074,15 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_AAAA(IPAddress p_IPAddress, eg. xxxx::xx.ip6.arpa PTR 0x8001 120 15 esp8266.local Used while answering reverse IPv6 questions + */ -bool MDNSResponder::clsHost::_writeMDNSAnswer_PTR_IPv6(IPAddress p_IPAddress, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSAnswer_PTR_IPv6(IPAddress p_IPAddress, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv6%s\n"), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); - stcRRDomain reverseIPv6Domain; - stcRRAttributes attributes(DNS_RRTYPE_PTR, + clsRRDomain reverseIPv6Domain; + clsRRAttributes attributes(DNS_RRTYPE_PTR, ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet bool bResult = ((p_IPAddress.isV6()) && (_buildDomainForReverseIPv6(p_IPAddress, reverseIPv6Domain)) && // xxxx::xx.ip6.arpa @@ -2033,7 +2090,7 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_PTR_IPv6(IPAddress p_IPAddress, (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS (_write32((p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), p_rSendParameter)) && // TTL + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), p_rSendParameter)) && // TTL (_writeMDNSHostDomain(m_pcHostName, true, 0, p_rSendParameter))); // RDLength & RData (host domain, eg. esp8266.local) DEBUG_EX_INFO(if (bResult) @@ -2044,7 +2101,7 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_PTR_IPv6(IPAddress p_IPAddress, attributes.m_u16Class, (p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), m_pcHostName); ); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv6: FAILED!\n"), _DH());); @@ -2057,24 +2114,25 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_PTR_IPv6(IPAddress p_IPAddress, 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::clsHost::_writeMDNSAnswer_SRV(MDNSResponder::clsHost::stcService& p_rService, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSAnswer_SRV(clsLEAMDNSHost::clsService& p_rService, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_SRV%s\n"), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); - uint16_t u16CachedDomainOffset = (p_rSendParameter.m_bLegacyQuery + uint16_t u16CachedDomainOffset = (p_rSendParameter.m_bLegacyDNSQuery ? 0 : p_rSendParameter.findCachedDomainOffset((const void*)m_pcHostName, false)); - stcRRAttributes attributes(DNS_RRTYPE_SRV, + clsRRAttributes attributes(DNS_RRTYPE_SRV, ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - stcRRDomain hostDomain; + clsRRDomain hostDomain; bool bResult = ((_writeMDNSServiceDomain(p_rService, true, false, 0, p_rSendParameter)) && // MyESP._http._tcp.local (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS (_write32((p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL/*MDNS_SERVICE_TTL*/)), p_rSendParameter)) && // TTL + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL/*Consts::u32ServiceTTL*/)), p_rSendParameter)) && // TTL (!u16CachedDomainOffset // No cache for domain name (or no compression allowed) ? ((_buildDomainForHost(m_pcHostName, hostDomain)) && @@ -2082,36 +2140,36 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_SRV(MDNSResponder::clsHost::stcSer 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(clsConsts::u16SRVPriority, p_rSendParameter)) && // Priority + (_write16(clsConsts::u16SRVWeight, p_rSendParameter)) && // Weight (_write16(p_rService.m_u16Port, p_rSendParameter)) && // Port (p_rSendParameter.addDomainCacheItem((const void*)m_pcHostName, false, p_rSendParameter.m_u16Offset)) && (_writeMDNSRRDomain(hostDomain, p_rSendParameter))) // Host, eg. esp8266.local // Cache available for domain - : ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset + : ((clsConsts::u8DomainCompressMark > ((u16CachedDomainOffset >> 8) & ~clsConsts::u8DomainCompressMark)) && // Valid offset (_write16((sizeof(uint16_t /*Prio*/) + // RDLength sizeof(uint16_t /*Weight*/) + sizeof(uint16_t /*Port*/) + 2), p_rSendParameter)) && // Length of 'C0xx' - (_write16(MDNS_SRV_PRIORITY, p_rSendParameter)) && // Priority - (_write16(MDNS_SRV_WEIGHT, p_rSendParameter)) && // Weight + (_write16(clsConsts::u16SRVPriority, p_rSendParameter)) && // Priority + (_write16(clsConsts::u16SRVWeight, 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(((u16CachedDomainOffset >> 8) | clsConsts::u8DomainCompressMark), p_rSendParameter)) && // Compression mark (and offset) (_write8((uint8_t)u16CachedDomainOffset, p_rSendParameter))))); // Offset DEBUG_EX_INFO(if (bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_SRV %s._%s._%s.local Type:%s Class:0x%04X TTL:%u %u %u %u %s.local\n"), _DH(), - p_rService.m_pcName, - p_rService.m_pcServiceType, + p_rService.m_pcInstanceName, + p_rService.m_pcType, p_rService.m_pcProtocol, _RRType2Name(attributes.m_u16Type), attributes.m_u16Class, (p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), - MDNS_SRV_PRIORITY, - MDNS_SRV_WEIGHT, + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), + clsConsts::u16SRVPriority, + clsConsts::u16SRVWeight, p_rService.m_u16Port, m_pcHostName); ); @@ -2121,37 +2179,42 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_SRV(MDNSResponder::clsHost::stcSer /* MDNSResponder::_createNSECBitmap + */ -MDNSResponder::clsHost::stcNSECBitmap* MDNSResponder::clsHost::_createNSECBitmap(uint32_t p_u32NSECContent) +clsLEAMDNSHost::clsNSECBitmap* clsLEAMDNSHost::_createNSECBitmap(uint32_t p_u32NSECContent) { // Currently 6 bytes (6*8 -> 0..47) are long enough, and only this is implemented - stcNSECBitmap* pNSECBitmap = new stcNSECBitmap; + clsNSECBitmap* pNSECBitmap = new clsNSECBitmap; if (pNSECBitmap) { +#ifdef MDNS_IPV4_SUPPORT if (p_u32NSECContent & static_cast(enuContentFlag::A)) { - pNSECBitmap->setBit(DNS_RRTYPE_A); // 01/0x01 + pNSECBitmap->setBit(DNS_RRTYPE_A); // 01/0x01 } +#endif if ((p_u32NSECContent & static_cast(enuContentFlag::PTR_IPv4)) || (p_u32NSECContent & static_cast(enuContentFlag::PTR_IPv6))) { - pNSECBitmap->setBit(DNS_RRTYPE_PTR); // 12/0x0C + pNSECBitmap->setBit(DNS_RRTYPE_PTR); // 12/0x0C } +#ifdef MDNS_IPV6_SUPPORT if (p_u32NSECContent & static_cast(enuContentFlag::AAAA)) { - pNSECBitmap->setBit(DNS_RRTYPE_AAAA); // 28/0x1C + pNSECBitmap->setBit(DNS_RRTYPE_AAAA); // 28/0x1C } +#endif if (p_u32NSECContent & static_cast(enuContentFlag::TXT)) { - pNSECBitmap->setBit(DNS_RRTYPE_TXT); // 16/0x10 + pNSECBitmap->setBit(DNS_RRTYPE_TXT); // 16/0x10 } if (p_u32NSECContent & static_cast(enuContentFlag::SRV)) { - pNSECBitmap->setBit(DNS_RRTYPE_SRV); // 33/0x21 + pNSECBitmap->setBit(DNS_RRTYPE_SRV); // 33/0x21 } if (p_u32NSECContent & static_cast(enuContentFlag::NSEC)) { - pNSECBitmap->setBit(DNS_RRTYPE_NSEC); // 47/0x2F + pNSECBitmap->setBit(clsConsts::u8DNS_RRTYPE_NSEC); // 47/0x2F } } return pNSECBitmap; @@ -2159,9 +2222,10 @@ MDNSResponder::clsHost::stcNSECBitmap* MDNSResponder::clsHost::_createNSECBitmap /* MDNSResponder::_writeMDNSNSECBitmap + */ -bool MDNSResponder::clsHost::_writeMDNSNSECBitmap(const MDNSResponder::clsHost::stcNSECBitmap& p_NSECBitmap, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSNSECBitmap(const clsLEAMDNSHost::clsNSECBitmap& p_NSECBitmap, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { /* DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("_writeMDNSNSECBitmap: ")); for (uint16_t u=0; ulength()), p_rSendParameter)) && // XX esp8266.local (_writeMDNSNSECBitmap(*pNSECBitmap, p_rSendParameter))); // NSEC bitmap @@ -2208,7 +2273,7 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_NSEC(uint32_t p_u32NSECContent, attributes.m_u16Class, (p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), m_pcHostName, _NSECBitmap2String(pNSECBitmap)); ); @@ -2228,18 +2293,19 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_NSEC(uint32_t p_u32NSECContent, /* MDNSResponder::_writeMDNSAnswer_NSEC_PTR_IPv4(host) - eg. 012.789.456.123.in-addr.arpa NSEC 0x8001 120 XX 012.789.456.123.in-addr.arpa xxx + eg. 012.789.456.123.in-addr.arpa NSEC 0x8001 120 XX 012.789.456.123.in-addr.arpa xyz http://www.zytrax.com/books/dns/ch8/nsec.html + */ -bool MDNSResponder::clsHost::_writeMDNSAnswer_NSEC_PTR_IPv4(IPAddress p_IPAddress, - stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSAnswer_NSEC_PTR_IPv4(IPAddress p_IPAddress, + clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC_PTR_IPv4\n"));); - stcRRAttributes attributes(DNS_RRTYPE_NSEC, + clsRRAttributes attributes(clsConsts::u8DNS_RRTYPE_NSEC, ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - stcNSECBitmap* pNSECBitmap = _createNSECBitmap(static_cast(enuContentFlag::PTR_IPv4)); - stcRRDomain reverseIPv4Domain; + clsNSECBitmap* pNSECBitmap = _createNSECBitmap(static_cast(enuContentFlag::PTR_IPv4)); + clsRRDomain reverseIPv4Domain; bool bResult = ((p_IPAddress.isV4()) && (pNSECBitmap) && // NSEC bitmap created (_buildDomainForReverseIPv4(p_IPAddress, reverseIPv4Domain)) && // 012.789.456.123.in-addr.arpa @@ -2247,7 +2313,7 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_NSEC_PTR_IPv4(IPAddress p_IPAddres (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS (_write32((p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), p_rSendParameter)) && // TTL + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), p_rSendParameter)) && // TTL (_write16((reverseIPv4Domain.m_u16NameLength + (2 + pNSECBitmap->length())), p_rSendParameter)) && (_writeMDNSRRDomain(reverseIPv4Domain, p_rSendParameter)) && // 012.789.456.123.in-addr.arpa (_writeMDNSNSECBitmap(*pNSECBitmap, p_rSendParameter))); // NSEC bitmap @@ -2261,7 +2327,7 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_NSEC_PTR_IPv4(IPAddress p_IPAddres attributes.m_u16Class, (p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL))); + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL))); _printRRDomain(reverseIPv4Domain); DEBUG_OUTPUT.printf_P(PSTR(" %s\n"), _NSECBitmap2String(pNSECBitmap)); }); @@ -2282,18 +2348,19 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_NSEC_PTR_IPv4(IPAddress p_IPAddres /* MDNSResponder::_writeMDNSAnswer_NSEC_PTR_IPv6(host) - eg. 9.0.0.0.0.0.0.0.0.0.0.0.0.7.8.5.6.3.4.1.2.ip6.arpa NSEC 0x8001 120 XX 9.0.0.0.0.0.0.0.0.0.0.0.0.7.8.5.6.3.4.1.2.ip6.arpa xxx + eg. 9.0.0.0.0.0.0.0.0.0.0.0.0.7.8.5.6.3.4.1.2.ip6.arpa NSEC 0x8001 120 XX 9.0.0.0.0.0.0.0.0.0.0.0.0.7.8.5.6.3.4.1.2.ip6.arpa xyz http://www.zytrax.com/books/dns/ch8/nsec.html + */ -bool MDNSResponder::clsHost::_writeMDNSAnswer_NSEC_PTR_IPv6(IPAddress p_IPAddress, - stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSAnswer_NSEC_PTR_IPv6(IPAddress p_IPAddress, + clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC_PTR_IPv6\n"));); - stcRRAttributes attributes(DNS_RRTYPE_NSEC, + clsRRAttributes attributes(clsConsts::u8DNS_RRTYPE_NSEC, ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - stcNSECBitmap* pNSECBitmap = _createNSECBitmap(static_cast(enuContentFlag::PTR_IPv6)); - stcRRDomain reverseIPv6Domain; + clsNSECBitmap* pNSECBitmap = _createNSECBitmap(static_cast(enuContentFlag::PTR_IPv6)); + clsRRDomain reverseIPv6Domain; bool bResult = ((p_IPAddress.isV6()) && (pNSECBitmap) && // NSEC bitmap created (_buildDomainForReverseIPv6(p_IPAddress, reverseIPv6Domain)) && // 9.0.0.0.0.0.0.0.0.0.0.0.0.7.8.5.6.3.4.1.2.ip6.arpa @@ -2301,7 +2368,7 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_NSEC_PTR_IPv6(IPAddress p_IPAddres (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS (_write32((p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_HOST_TTL)), p_rSendParameter)) && // TTL + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32HostTTL)), p_rSendParameter)) && // TTL (_write16((reverseIPv6Domain.m_u16NameLength + (2 + pNSECBitmap->length())), p_rSendParameter)) && (_writeMDNSRRDomain(reverseIPv6Domain, p_rSendParameter)) && // 9.0.0.0.0.0.0.0.0.0.0.0.0.7.8.5.6.3.4.1.2.ip6.arpa (_writeMDNSNSECBitmap(*pNSECBitmap, p_rSendParameter))); // NSEC bitmap @@ -2315,7 +2382,7 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_NSEC_PTR_IPv6(IPAddress p_IPAddres attributes.m_u16Class, (p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL))); + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL))); _printRRDomain(reverseIPv6Domain); DEBUG_OUTPUT.printf_P(PSTR(" %s\n"), _NSECBitmap2String(pNSECBitmap)); }); @@ -2334,38 +2401,39 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_NSEC_PTR_IPv6(IPAddress p_IPAddres /* MDNSResponder::_writeMDNSAnswer_NSEC(service) - eg. MyESP._http.tcp.local NSEC 0x8001 4500 XX MyESP._http.tcp.local xxx + eg. MyESP._http.tcp.local NSEC 0x8001 4500 XX MyESP._http.tcp.local xyz http://www.zytrax.com/books/dns/ch8/nsec.html + */ -bool MDNSResponder::clsHost::_writeMDNSAnswer_NSEC(MDNSResponder::clsHost::stcService& p_rService, - uint32_t p_u32NSECContent, - MDNSResponder::clsHost::stcSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_writeMDNSAnswer_NSEC(clsLEAMDNSHost::clsService& p_rService, + uint32_t p_u32NSECContent, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC (service: %s)\n"), _DH(), _replyFlags2String(p_u32NSECContent));); - stcRRAttributes attributes(DNS_RRTYPE_NSEC, + clsRRAttributes attributes(clsConsts::u8DNS_RRTYPE_NSEC, ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - stcNSECBitmap* pNSECBitmap = _createNSECBitmap(p_u32NSECContent); + clsNSECBitmap* pNSECBitmap = _createNSECBitmap(p_u32NSECContent); bool bResult = ((pNSECBitmap) && // NSEC bitmap created (_writeMDNSServiceDomain(p_rService, true, false, 0, p_rSendParameter)) && // MyESP._http._tcp.local (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS (_write32((p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL)), p_rSendParameter)) && // TTL + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL)), p_rSendParameter)) && // TTL (_writeMDNSServiceDomain(p_rService, true, true, (2 + pNSECBitmap->length()), p_rSendParameter)) && // XX MyESP._http._tcp.local (_writeMDNSNSECBitmap(*pNSECBitmap, p_rSendParameter))); // NSEC bitmap DEBUG_EX_INFO(if (bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_NSEC %s._%s._%s.local Type:%s Class:0x%04X TTL:%u %s\n"), _DH(), - p_rService.m_pcName, - p_rService.m_pcServiceType, + p_rService.m_pcInstanceName, + p_rService.m_pcType, p_rService.m_pcProtocol, _RRType2Name(attributes.m_u16Type), attributes.m_u16Class, (p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyQuery ? MDNS_LEGACY_TTL : MDNS_SERVICE_TTL)), + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL)), _NSECBitmap2String(pNSECBitmap)); ); @@ -2379,8 +2447,10 @@ bool MDNSResponder::clsHost::_writeMDNSAnswer_NSEC(MDNSResponder::clsHost::stcSe return bResult; } + } // namespace MDNSImplementation + } // namespace esp8266 diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_API.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_API.cpp deleted file mode 100755 index 4b3d22902e..0000000000 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_API.cpp +++ /dev/null @@ -1,4164 +0,0 @@ -/* - LEAmDNS2_API.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 "LEAmDNS2_Priv.h" - - -namespace -{ - -/* - strrstr (static) - - Backwards search for p_pcPattern in p_pcString - Based on: https://stackoverflow.com/a/1634398/2778898 - -*/ -const char* strrstr(const char*__restrict p_pcString, const char*__restrict p_pcPattern) -{ - const char* pcResult = 0; - - size_t stStringLength = (p_pcString ? strlen(p_pcString) : 0); - size_t stPatternLength = (p_pcPattern ? strlen(p_pcPattern) : 0); - - if ((stStringLength) && - (stPatternLength) && - (stPatternLength <= stStringLength)) - { - // Pattern is shorter or has the same length tham the string - - for (const char* s = (p_pcString + stStringLength - stPatternLength); s >= p_pcString; --s) - { - if (0 == strncmp(s, p_pcPattern, stPatternLength)) - { - pcResult = s; - break; - } - } - } - return pcResult; -} - -} // anonymous - - -namespace esp8266 -{ - -/* - LEAmDNS -*/ -namespace experimental -{ - -/** - STRINGIZE -*/ -#ifndef STRINGIZE -#define STRINGIZE(x) #x -#endif -#ifndef STRINGIZE_VALUE_OF -#define STRINGIZE_VALUE_OF(x) STRINGIZE(x) -#endif - -/** - HELPERS -*/ - -/* - MDNSResponder::indexDomain (static) - - Updates the given domain 'p_rpcHostName' by appending a delimiter and an index number. - - If the given domain already hasa numeric index (after the given delimiter), this index - incremented. If not, the delimiter and index '2' is added. - - If 'p_rpcHostName' is empty (==0), the given default name 'p_pcDefaultHostName' is used, - if no default is given, 'esp8266' is used. - -*/ -/*static*/ bool MDNSResponder::indexDomain(char*& p_rpcDomain, - const char* p_pcDivider /*= "-"*/, - const char* p_pcDefaultDomain /*= 0*/) -{ - bool bResult = false; - - // Ensure a divider exists; use '-' as default - const char* pcDivider = (p_pcDivider ? : "-"); - - if (p_rpcDomain) - { - const char* pFoundDivider = strrstr(p_rpcDomain, pcDivider); - if (pFoundDivider) // maybe already extended - { - char* pEnd = 0; - unsigned long ulIndex = strtoul((pFoundDivider + strlen(pcDivider)), &pEnd, 10); - if ((ulIndex) && - ((pEnd - p_rpcDomain) == (ptrdiff_t)strlen(p_rpcDomain)) && - (!*pEnd)) // Valid (old) index found - { - - char acIndexBuffer[16]; - sprintf(acIndexBuffer, "%lu", (++ulIndex)); - size_t stLength = ((pFoundDivider - p_rpcDomain + strlen(pcDivider)) + strlen(acIndexBuffer) + 1); - char* pNewHostName = new char[stLength]; - if (pNewHostName) - { - memcpy(pNewHostName, p_rpcDomain, (pFoundDivider - p_rpcDomain + strlen(pcDivider))); - pNewHostName[pFoundDivider - p_rpcDomain + strlen(pcDivider)] = 0; - strcat(pNewHostName, acIndexBuffer); - - delete[] p_rpcDomain; - p_rpcDomain = pNewHostName; - - bResult = true; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] indexDomain: FAILED to alloc new hostname!\n"));); - } - } - else - { - pFoundDivider = 0; // Flag the need to (base) extend the hostname - } - } - - if (!pFoundDivider) // not yet extended (or failed to increment extension) -> start indexing - { - size_t stLength = strlen(p_rpcDomain) + (strlen(pcDivider) + 1 + 1); // Name + Divider + '2' + '\0' - char* pNewHostName = new char[stLength]; - if (pNewHostName) - { - sprintf(pNewHostName, "%s%s2", p_rpcDomain, pcDivider); - - delete[] p_rpcDomain; - p_rpcDomain = pNewHostName; - - bResult = true; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] indexDomain: FAILED to alloc new hostname!\n"));); - } - } - } - else - { - // No given host domain, use base or default - const char* cpcDefaultName = (p_pcDefaultDomain ? : "esp8266"); - - size_t stLength = strlen(cpcDefaultName) + 1; // '\0' - p_rpcDomain = new char[stLength]; - if (p_rpcDomain) - { - strncpy(p_rpcDomain, cpcDefaultName, stLength); - bResult = true; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] indexDomain: FAILED to alloc new hostname!\n"));); - } - } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] indexDomain: %s\n"), p_rpcDomain);); - return bResult; -} - - -/* - MDNSResponder::setStationHostName (static) - - Sets the staion hostname - -*/ -/*static*/ bool MDNSResponder::setNetIfHostName(netif* p_pNetIf, - const char* p_pcHostName) -{ - if ((p_pNetIf) && - (p_pcHostName)) - { - netif_set_hostname(p_pNetIf, p_pcHostName); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] setNetIfHostName host name: %s!\n"), p_pcHostName);); - } - return true; -} - - -/** - INTERFACE -*/ - -/** - MDNSResponder::MDNSResponder -*/ -MDNSResponder::MDNSResponder(void) - : m_pUDPContext(0) -{ - _allocUDPContext(); -} - -/* - MDNSResponder::~MDNSResponder -*/ -MDNSResponder::~MDNSResponder(void) -{ - close(); -} - -/* - MDNSResponder::begin (hostname, netif, probe_callback) -*/ -MDNSResponder::hMDNSHost MDNSResponder::begin(const char* p_pcHostName, - netif* p_pNetIf, - MDNSHostProbeResultCallbackFn p_fnCallback /*= 0*/) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s, netif: %u)\n"), _DH(), (p_pcHostName ? : "-"), (p_pNetIf ? netif_get_index(p_pNetIf) : 0));); - - return (hMDNSHost)_begin(p_pcHostName, p_pNetIf, p_fnCallback); -} - -/* - MDNSResponder::begin (hostname, probe_callback) -*/ -bool MDNSResponder::begin(const char* p_pcHostName, - MDNSHostProbeResultCallbackFn p_fnCallback /*= 0*/) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s)\n"), _DH(), (p_pcHostName ? : "_"));); - - return begin(p_pcHostName, (WiFiMode_t)wifi_get_opmode(), p_fnCallback); -} - -/* - MDNSResponder::begin (hostname, WiFiMode, probe_callback) -*/ -bool MDNSResponder::begin(const char* p_pcHostName, - WiFiMode_t p_WiFiMode, - MDNSHostProbeResultCallbackFn p_fnCallback /*= 0*/) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s, opmode: %u)\n"), _DH(), (p_pcHostName ? : "_"), (uint32_t)p_WiFiMode);); - - bool bResult = true; - - if ((bResult) && - (p_WiFiMode & WIFI_STA)) - { - bResult = (0 != _begin(p_pcHostName, netif_get_by_index(WIFI_STA), p_fnCallback)); - } - if ((bResult) && - (p_WiFiMode & WIFI_AP)) - { - bResult = (0 != _begin(p_pcHostName, netif_get_by_index(WIFI_AP), p_fnCallback)); - } - return bResult; -} - -/* - MDNSResponder::close -*/ -bool MDNSResponder::close(const MDNSResponder::hMDNSHost p_hMDNSHost) -{ - return ((_validateMDNSHostHandle(p_hMDNSHost)) && - (_close(*(clsHost*)p_hMDNSHost))); -} - -/* - MDNSResponder::close (convenience) -*/ -bool MDNSResponder::close(void) -{ - clsHostList::iterator it(m_HostList.begin()); - while (m_HostList.end() != it) - { - _close(**it++); - } - - return _releaseUDPContext(); -} - - -/* - MDNSResponder::getMDNSHost (netif) -*/ -MDNSResponder::hMDNSHost MDNSResponder::getMDNSHost(netif* p_pNetIf) const -{ - return (hMDNSHost)(p_pNetIf ? _findHost(p_pNetIf) : 0); -} - -/* - MDNSResponder::getMDNSHost (WiFiMode) -*/ -MDNSResponder::hMDNSHost MDNSResponder::getMDNSHost(WiFiMode_t p_WiFiMode) const -{ - hMDNSHost hResult = 0; - - if (WIFI_STA == p_WiFiMode) - { - hResult = getMDNSHost(netif_get_by_index(WIFI_STA)); - } - else if (WIFI_AP == p_WiFiMode) - { - hResult = getMDNSHost(netif_get_by_index(WIFI_AP)); - } - return hResult; -} - -/* - MDNSResponder::setHostName -*/ -bool MDNSResponder::setHostName(const MDNSResponder::hMDNSHost p_hMDNSHost, - const char* p_pcHostName, - MDNSHostProbeResultCallbackFn p_fnCallback /*= 0*/) -{ - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && - (_NRH2Ptr(p_hMDNSHost)->setHostName(p_pcHostName)) && - (p_fnCallback ? setHostProbeResultCallback(p_hMDNSHost, p_fnCallback) : true)); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setHostName: FAILED for '%s'!\n"), _DH(p_hMDNSHost), (p_pcHostName ? : "-"));); - return bResult; -} - -/* - MDNSResponder::hostName -*/ -const char* MDNSResponder::hostName(const MDNSResponder::hMDNSHost p_hMDNSHost) const -{ - return (_validateMDNSHostHandle(p_hMDNSHost) - ? (_NRH2Ptr(p_hMDNSHost)->hostName()) - : 0); -} - -/* - MDNSResponder::setHostProbeResultCallback -*/ -bool MDNSResponder::setHostProbeResultCallback(const hMDNSHost p_hMDNSHost, - MDNSHostProbeResultCallbackFn p_fnCallback) -{ - bool bResult = false; - if ((bResult = _validateMDNSHostHandle(p_hMDNSHost))) - { - if (p_fnCallback) - { - _NRH2Ptr(p_hMDNSHost)->m_HostProbeInformation.m_fnProbeResultCallback = [this, p_fnCallback](clsHost & p_rHost, - const char* p_pcDomainName, - bool p_bProbeResult)->void - { - p_fnCallback(*this, (hMDNSHost)&p_rHost, p_pcDomainName, p_bProbeResult); - }; - } - else - { - _NRH2Ptr(p_hMDNSHost)->m_HostProbeInformation.m_fnProbeResultCallback = 0; - } - } - return bResult; -} - -/* - MDNSResponder::status -*/ -bool MDNSResponder::status(const MDNSResponder::hMDNSHost p_hMDNSHost) const -{ - return ((_validateMDNSHostHandle(p_hMDNSHost)) && - (_NRH2Ptr(p_hMDNSHost)->probeStatus())); -} - - -/* - SERVICES -*/ - -/* - MDNSResponder::setInstanceName -*/ -bool MDNSResponder::setInstanceName(const MDNSResponder::hMDNSHost p_hMDNSHost, - const char* p_pcInstanceName) -{ - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && - (_NRH2Ptr(p_hMDNSHost)->setInstanceName(p_pcInstanceName))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setInstanceName: FAILED for '%s'!\n"), _DH(p_hMDNSHost), (p_pcInstanceName ? : "-"));); - return bResult; -} - -/* - MDNSResponder::instanceName -*/ -const char* MDNSResponder::instanceName(const MDNSResponder::hMDNSHost p_hMDNSHost) const -{ - return (_validateMDNSHostHandle(p_hMDNSHost) - ? (_NRH2Ptr(p_hMDNSHost)->instanceName()) - : 0); -} - -/* - 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 MDNSResponder::hMDNSHost p_hMDNSHost, - const char* p_pcName, - const char* p_pcServiceType, - const char* p_pcProtocol, - uint16_t p_u16Port, - MDNSServiceProbeResultCallbackFn p_fnCallback /*= 0*/) -{ - hMDNSService hService = (_validateMDNSHostHandle(p_hMDNSHost) - ? (hMDNSService)_NRH2Ptr(p_hMDNSHost)->addService(p_pcName, p_pcServiceType, p_pcProtocol, p_u16Port) - : 0); - if ((p_fnCallback) && - (hService)) - { - setServiceProbeResultCallback(p_hMDNSHost, hService, p_fnCallback); - } - DEBUG_EX_ERR(if (!hService) DEBUG_OUTPUT.printf_P(PSTR("%s addService: FAILED for '%s._%s._%s.local'!\n"), _DH(p_hMDNSHost), (p_pcName ? : "-"), (p_pcServiceType ? : "-"), (p_pcProtocol ? : "-"));); - return hService; -} - -/* - MDNSResponder::removeService - - Unanounce a service (by sending a goodbye message) and remove it - from the MDNS responder - -*/ -bool MDNSResponder::removeService(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService) -{ - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && - (_NRH2Ptr(p_hMDNSHost)->removeService(_SH2Ptr(p_hMDNSService)))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeService: FAILED!\n"), _DH(p_hMDNSHost));); - return bResult; -} - -/* - MDNSResponder::removeService -*/ -bool MDNSResponder::removeService(const MDNSResponder::hMDNSHost p_hMDNSHost, - const char* p_pcInstanceName, - const char* p_pcServiceType, - const char* p_pcProtocol) -{ - clsHost* pMDNSHost; - clsHost::stcService* pMDNSService; - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && - (pMDNSHost = (clsHost*)p_hMDNSHost) && - ((pMDNSService = _NRH2Ptr(p_hMDNSHost)->findService(p_pcInstanceName, p_pcServiceType, p_pcProtocol))) && - (_NRH2Ptr(p_hMDNSHost)->removeService(pMDNSService))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeService: FAILED for '%s._%s._%s.local'!\n"), _DH(p_hMDNSHost), (p_pcInstanceName ? : "-"), (p_pcServiceType ? : "-"), (p_pcProtocol ? : "-"));); - return bResult; -} - -/* - MDNSResponder::findService - - Find an existing service. - -*/ -MDNSResponder::hMDNSService MDNSResponder::findService(const MDNSResponder::hMDNSHost p_hMDNSHost, - const char* p_pcInstanceName, - const char* p_pcServiceType, - const char* p_pcProtocol) -{ - clsHost* pMDNSHost; - return (((_validateMDNSHostHandle(p_hMDNSHost)) && - (pMDNSHost = (clsHost*)p_hMDNSHost)) - ? _NRH2Ptr(p_hMDNSHost)->findService(p_pcInstanceName, p_pcServiceType, p_pcProtocol) - : 0); -} - -/* - MDNSResponder::setServiceName -*/ -bool MDNSResponder::setServiceName(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcInstanceName, - MDNSServiceProbeResultCallbackFn p_fnCallback /*= 0*/) -{ - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && - (_NRH2Ptr(p_hMDNSHost)->setServiceName(_SH2Ptr(p_hMDNSService), p_pcInstanceName)) && - (p_fnCallback ? setServiceProbeResultCallback(p_hMDNSHost, p_hMDNSService, p_fnCallback) : true)); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setServiceName: FAILED for '%s'!\n"), _DH(p_hMDNSHost), (p_pcInstanceName ? : "-"));); - return bResult; -} - -/* - MDNSResponder::serviceName -*/ -const char* MDNSResponder::serviceName(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const -{ - return (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) - ? (_SH2Ptr(p_hMDNSService)->m_pcName) - : 0); -} - -/* - MDNSResponder::serviceType -*/ -const char* MDNSResponder::serviceType(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const -{ - return (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) - ? (_SH2Ptr(p_hMDNSService)->m_pcServiceType) - : 0); -} - -/* - MDNSResponder::serviceProtocol -*/ -const char* MDNSResponder::serviceProtocol(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const -{ - return (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) - ? (_SH2Ptr(p_hMDNSService)->m_pcProtocol) - : 0); -} - -/* - MDNSResponder::serviceProtocol -*/ -uint16_t MDNSResponder::servicePort(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const -{ - return (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) - ? (_SH2Ptr(p_hMDNSService)->m_u16Port) - : 0); -} - -/* - 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::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - MDNSResponder::MDNSServiceProbeResultCallbackFn p_fnCallback) -{ - bool bResult = false; - if ((bResult = _validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService))) - { - if (p_fnCallback) - { - _SH2Ptr(p_hMDNSService)->m_ProbeInformation.m_fnProbeResultCallback = [this, p_fnCallback](clsHost & p_rHost, - clsHost::stcService & p_rMDNSService, - const char* p_pcDomainName, - bool p_bProbeResult)->void - { - p_fnCallback(*this, (hMDNSHost)&p_rHost, (hMDNSService)&p_rMDNSService, p_pcDomainName, p_bProbeResult); - }; - } - else - { - _SH2Ptr(p_hMDNSService)->m_ProbeInformation.m_fnProbeResultCallback = 0; - } - } - return bResult; -} - -/* - MDNSResponder::serviceStatus -*/ -bool MDNSResponder::serviceStatus(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const -{ - return ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && - (_SH2Ptr(p_hMDNSService)->probeStatus())); -} - - -/* - SERVICE TXT -*/ - -/* - MDNSResponder::addServiceTxt - - Add a static service TXT item ('Key'='Value') to a service. - -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - const char* p_pcValue) -{ - hMDNSTxt hTxt = (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) - ? (hMDNSTxt)_NRH2Ptr(p_hMDNSHost)->addServiceTxt(_SH2Ptr(p_hMDNSService), p_pcKey, p_pcValue) - : 0); - DEBUG_EX_ERR(if (!hTxt) DEBUG_OUTPUT.printf_P(PSTR("%s addServiceTxt: FAILED for '%s=%s'!\n"), _DH(p_hMDNSHost), (p_pcKey ? : "-"), (p_pcValue ? : "-"));); - return hTxt; -} - -/* - MDNSRESPONDER_xxx_TO_CHAR - Formats: http://www.cplusplus.com/reference/cstdio/printf/ -*/ -#define MDNSRESPONDER_U32_TO_CHAR(BUFFERNAME, U32VALUE) \ - char BUFFERNAME[16]; /* 32-bit max 10 digits */ \ - *BUFFERNAME = 0; \ - sprintf(BUFFERNAME, "%u", U32VALUE); -#define MDNSRESPONDER_U16_TO_CHAR(BUFFERNAME, U16VALUE) \ - char BUFFERNAME[8]; /* 16-bit max 5 digits */ \ - *BUFFERNAME = 0; \ - sprintf(BUFFERNAME, "%hu", U16VALUE); -#define MDNSRESPONDER_U8_TO_CHAR(BUFFERNAME, U8VALUE) \ - char BUFFERNAME[8]; /* 8-bit max 3 digits */ \ - *BUFFERNAME = 0; \ - sprintf(BUFFERNAME, "%hhu", U8VALUE); -#define MDNSRESPONDER_I32_TO_CHAR(BUFFERNAME, I32VALUE) \ - char BUFFERNAME[16]; /* 32-bit max 10 digits */ \ - *BUFFERNAME = 0; \ - sprintf(BUFFERNAME, "%i", I32VALUE); -#define MDNSRESPONDER_I16_TO_CHAR(BUFFERNAME, I16VALUE) \ - char BUFFERNAME[8]; /* 16-bit max 5 digits */ \ - *BUFFERNAME = 0; \ - sprintf(BUFFERNAME, "%hi", I16VALUE); -#define MDNSRESPONDER_I8_TO_CHAR(BUFFERNAME, I8VALUE) \ - char BUFFERNAME[8]; /* 8-bit max 3 digits */ \ - *BUFFERNAME = 0; \ - sprintf(BUFFERNAME, "%hhi", I8VALUE); - -/* - MDNSResponder::addServiceTxt (uint32_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint32_t p_u32Value) -{ - MDNSRESPONDER_U32_TO_CHAR(acBuffer, p_u32Value); - return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); -} - -/* - MDNSResponder::addServiceTxt (uint16_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint16_t p_u16Value) -{ - MDNSRESPONDER_U16_TO_CHAR(acBuffer, p_u16Value); - return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); -} - -/* - MDNSResponder::addServiceTxt (uint8_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint8_t p_u8Value) -{ - MDNSRESPONDER_U8_TO_CHAR(acBuffer, p_u8Value); - return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); -} - -/* - MDNSResponder::addServiceTxt (int32_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - int32_t p_i32Value) -{ - MDNSRESPONDER_I32_TO_CHAR(acBuffer, p_i32Value); - return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); -} - -/* - MDNSResponder::addServiceTxt (int16_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - int16_t p_i16Value) -{ - MDNSRESPONDER_I16_TO_CHAR(acBuffer, p_i16Value); - return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); -} - -/* - MDNSResponder::addServiceTxt (int8_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - int8_t p_i8Value) -{ - MDNSRESPONDER_I8_TO_CHAR(acBuffer, p_i8Value); - return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); -} - -/* - MDNSResponder::removeServiceTxt - - Remove a static service TXT item from a service. -*/ -bool MDNSResponder::removeServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const MDNSResponder::hMDNSTxt p_hTxt) -{ - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && - (p_hTxt) && - (_NRH2Ptr(p_hMDNSHost)->removeServiceTxt(_SH2Ptr(p_hMDNSService), (clsHost::stcServiceTxt*)p_hTxt))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeServiceTxt: FAILED!\n"), _DH(p_hMDNSHost));); - return bResult; -} - -/* - MDNSResponder::removeServiceTxt -*/ -bool MDNSResponder::removeServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey) -{ - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && - (p_pcKey) && - (_NRH2Ptr(p_hMDNSHost)->removeServiceTxt(_SH2Ptr(p_hMDNSService), _NRH2Ptr(p_hMDNSHost)->findServiceTxt(_SH2Ptr(p_hMDNSService), p_pcKey)))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeServiceTxt: FAILED!\n"), _DH(p_hMDNSHost));); - return bResult; -} - -/* - MDNSResponder::setDynamicServiceTxtCallback (binding) - - Set a netif binding specific callback for dynamic service TXT items. The callback is called, whenever - service TXT items are needed for any service on the netif binding. - -*/ -bool MDNSResponder::setDynamicServiceTxtCallback(const MDNSResponder::hMDNSHost p_hMDNSHost, - MDNSResponder::MDNSDynamicServiceTxtCallbackFn p_fnCallback) -{ - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && - ((!p_fnCallback) || - (_NRH2Ptr(p_hMDNSHost)->setDynamicServiceTxtCallback([this, p_fnCallback](clsHost & p_rHost, - clsHost::stcService & p_rMDNSService)->void - { - if (p_fnCallback) - { - p_fnCallback(*this, (hMDNSHost)&p_rHost, (hMDNSService)&p_rMDNSService); - } - })))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setDynamicServiceTxtCallback: FAILED!\n"), _DH(p_hMDNSHost));); - return bResult; -} - -/* - MDNSResponder::setDynamicServiceTxtCallback (service) - - 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(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - MDNSResponder::MDNSDynamicServiceTxtCallbackFn p_fnCallback) -{ - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && - ((!p_fnCallback) || - (_NRH2Ptr(p_hMDNSHost)->setDynamicServiceTxtCallback(_SH2Ptr(p_hMDNSService), [this, p_fnCallback](clsHost & p_rHost, - clsHost::stcService & p_rMDNSService)->void - { - if (p_fnCallback) - { - p_fnCallback(*this, (hMDNSHost)&p_rHost, (hMDNSService)&p_rMDNSService); - } - })))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setDynamicServiceTxtCallback: FAILED!\n"), _DH(p_hMDNSHost));); - return bResult; -} - -/* - MDNSResponder::addDynamicServiceTxt -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - const char* p_pcValue) -{ - hMDNSTxt hTxt = (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) - ? (hMDNSTxt)_NRH2Ptr(p_hMDNSHost)->addDynamicServiceTxt(_SH2Ptr(p_hMDNSService), p_pcKey, p_pcValue) - : 0); - DEBUG_EX_ERR(if (!hTxt) DEBUG_OUTPUT.printf_P(PSTR("%s addServiceTxt: FAILED for '%s=%s'!\n"), _DH(p_hMDNSHost), (p_pcKey ? : "-"), (p_pcValue ? : "-"));); - return hTxt; -} - -/* - MDNSResponder::addDynamicServiceTxt (uint32_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint32_t p_u32Value) -{ - MDNSRESPONDER_U32_TO_CHAR(acBuffer, p_u32Value); - return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); -} - -/* - MDNSResponder::addDynamicServiceTxt (uint16_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint16_t p_u16Value) -{ - MDNSRESPONDER_U16_TO_CHAR(acBuffer, p_u16Value); - return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); -} - -/* - MDNSResponder::addDynamicServiceTxt (uint8_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint8_t p_u8Value) -{ - MDNSRESPONDER_U8_TO_CHAR(acBuffer, p_u8Value); - return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); -} - -/* - MDNSResponder::addDynamicServiceTxt (int32_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - int32_t p_i32Value) -{ - MDNSRESPONDER_I32_TO_CHAR(acBuffer, p_i32Value); - return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); -} - -/* - MDNSResponder::addDynamicServiceTxt (int16_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - int16_t p_i16Value) -{ - MDNSRESPONDER_I16_TO_CHAR(acBuffer, p_i16Value); - return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); -} - -/* - MDNSResponder::addDynamicServiceTxt (int8_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - int8_t p_i8Value) -{ - MDNSRESPONDER_I8_TO_CHAR(acBuffer, p_i8Value); - return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer); -} - - -/** - QUERIES -*/ - - -/** - MDNSResponder::clsMDNSAnswerAccessor - -*/ - -/* - MDNSResponder::clsMDNSAnswerAccessor::clsMDNSAnswerAccessor constructor -*/ -MDNSResponder::clsMDNSAnswerAccessor::clsMDNSAnswerAccessor(const MDNSResponder::clsHost::stcQuery::stcAnswer* p_pAnswer) - : m_pAnswer(p_pAnswer) -{ - if ((m_pAnswer) && - (txtsAvailable())) - { - // Prepare m_TxtKeyValueMap - for (const clsHost::stcServiceTxt* pTxt = m_pAnswer->m_Txts.m_pTxts; pTxt; pTxt = pTxt->m_pNext) - { - m_TxtKeyValueMap.emplace(std::pair(pTxt->m_pcKey, pTxt->m_pcValue)); - } - } -} - -/* - MDNSResponder::clsMDNSAnswerAccessor::~clsMDNSAnswerAccessor destructor -*/ -MDNSResponder::clsMDNSAnswerAccessor::~clsMDNSAnswerAccessor(void) -{ -} - -/** - MDNSResponder::clsMDNSAnswerAccessor::stcCompareTxtKey -*/ - -/* - MDNSResponder::clsMDNSAnswerAccessor::stcCompareTxtKey::operator() -*/ -bool MDNSResponder::clsMDNSAnswerAccessor::stcCompareTxtKey::operator()(char const* p_pA, - char const* p_pB) const -{ - return (0 > strcasecmp(p_pA, p_pB)); -} - -/* - MDNSResponder::clsMDNSAnswerAccessor::serviceDomainAvailable -*/ -bool MDNSResponder::clsMDNSAnswerAccessor::serviceDomainAvailable(void) const -{ - return ((m_pAnswer) && - (m_pAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::ServiceDomain))); -} - -/* - MDNSResponder::clsMDNSAnswerAccessor::serviceDomain -*/ -const char* MDNSResponder::clsMDNSAnswerAccessor::serviceDomain(void) const -{ - return ((m_pAnswer) - ? m_pAnswer->m_ServiceDomain.c_str() - : 0); -} - -/* - MDNSResponder::clsMDNSAnswerAccessor::hostDomainAvailable -*/ -bool MDNSResponder::clsMDNSAnswerAccessor::hostDomainAvailable(void) const -{ - return ((m_pAnswer) && - (m_pAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::HostDomain))); -} - -/* - MDNSResponder::clsMDNSAnswerAccessor::hostDomain -*/ -const char* MDNSResponder::clsMDNSAnswerAccessor::hostDomain(void) const -{ - return ((m_pAnswer) - ? m_pAnswer->m_HostDomain.c_str() - : 0); -} - -/* - MDNSResponder::clsMDNSAnswerAccessor::hostPortAvailable -*/ -bool MDNSResponder::clsMDNSAnswerAccessor::hostPortAvailable(void) const -{ - return ((m_pAnswer) && - (m_pAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::Port))); -} - -/* - MDNSResponder::clsMDNSAnswerAccessor::hostPort -*/ -uint16_t MDNSResponder::clsMDNSAnswerAccessor::hostPort(void) const -{ - return ((m_pAnswer) - ? (m_pAnswer->m_u16Port) - : 0); -} - -#ifdef MDNS_IPV4_SUPPORT -/* - MDNSResponder::clsMDNSAnswerAccessor::IPv4AddressAvailable -*/ -bool MDNSResponder::clsMDNSAnswerAccessor::IPv4AddressAvailable(void) const -{ - return ((m_pAnswer) && - (m_pAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::IPv4Address))); -} - -/* - MDNSResponder::clsMDNSAnswerAccessor::IPv4Addresses -*/ -std::vector MDNSResponder::clsMDNSAnswerAccessor::IPv4Addresses(void) const -{ - std::vector internalIP; - if ((m_pAnswer) && - (IPv4AddressAvailable())) - { - for (uint32_t u = 0; u < m_pAnswer->IPv4AddressCount(); ++u) - { - const clsHost::stcQuery::stcAnswer::stcIPAddress* pIPAddr = m_pAnswer->IPv4AddressAtIndex(u); - if (pIPAddr) - { - internalIP.emplace_back(pIPAddr->m_IPAddress); - } - } - } - return internalIP; -} -#endif - -#ifdef MDNS_IPV6_SUPPORT -/* - MDNSResponder::clsMDNSAnswerAccessor::IPv6AddressAvailable -*/ -bool MDNSResponder::clsMDNSAnswerAccessor::IPv6AddressAvailable(void) const -{ - return ((m_pAnswer) && - (m_pAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::IPv6Address))); -} - -/* - MDNSResponder::clsMDNSAnswerAccessor::IPv6Addresses -*/ -std::vector MDNSResponder::clsMDNSAnswerAccessor::IPv6Addresses(void) const -{ - std::vector internalIP; - if ((m_pAnswer) && - (IPv6AddressAvailable())) - { - for (uint32_t u = 0; u < m_pAnswer->IPv6AddressCount(); ++u) - { - const clsHost::stcQuery::stcAnswer::stcIPAddress* pIPAddr = m_pAnswer->IPv6AddressAtIndex(u); - if (pIPAddr) - { - internalIP.emplace_back(pIPAddr->m_IPAddress); - } - } - } - return internalIP; -} -#endif - -/* - MDNSResponder::clsMDNSAnswerAccessor::txtsAvailable -*/ -bool MDNSResponder::clsMDNSAnswerAccessor::txtsAvailable(void) const -{ - return ((m_pAnswer) && - (m_pAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::Txts))); -} - -/* - MDNSResponder::clsMDNSAnswerAccessor::txts - - Returns all TXT items for the given service as a ';'-separated string. - If not already existing; the string is alloced, filled and attached to the answer. -*/ -const char* MDNSResponder::clsMDNSAnswerAccessor::txts(void) const -{ - return ((m_pAnswer) - ? m_pAnswer->m_Txts.c_str() - : 0); -} - -/* - MDNSResponder::clsMDNSAnswerAccessor::txtKeyValues -*/ -const MDNSResponder::clsMDNSAnswerAccessor::clsTxtKeyValueMap& MDNSResponder::clsMDNSAnswerAccessor::txtKeyValues(void) const -{ - return m_TxtKeyValueMap; -} - -/* - MDNSResponder::clsMDNSAnswerAccessor::txtValue -*/ -const char* MDNSResponder::clsMDNSAnswerAccessor::txtValue(const char* p_pcKey) const -{ - char* pcResult = 0; - - if (m_pAnswer) - { - for (const clsHost::stcServiceTxt* pTxt = m_pAnswer->m_Txts.m_pTxts; pTxt; pTxt = pTxt->m_pNext) - { - if ((p_pcKey) && - (0 == strcasecmp(pTxt->m_pcKey, p_pcKey))) - { - pcResult = pTxt->m_pcValue; - break; - } - } - } - return pcResult; -} - -/* - MDNSResponder::clsMDNSAnswerAccessor::printTo - **/ -size_t MDNSResponder::clsMDNSAnswerAccessor::printTo(Print& p_Print) const -{ - size_t stLen = 0; - const char* cpcI = " * "; - const char* cpcS = " "; - - stLen += p_Print.println(" * * * * *"); - if (hostDomainAvailable()) - { - stLen += p_Print.print(cpcI); - stLen += p_Print.print("Host domain: "); - stLen += p_Print.println(hostDomain()); - } -#ifdef MDNS_IPV4_SUPPORT - if (IPv4AddressAvailable()) - { - stLen += p_Print.print(cpcI); - stLen += p_Print.println("IPv4 address(es):"); - for (const IPAddress& addr : IPv4Addresses()) - { - stLen += p_Print.print(cpcI); - stLen += p_Print.print(cpcS); - stLen += p_Print.println(addr); - } - } -#endif -#ifdef MDNS_IPV6_SUPPORT - if (IPv6AddressAvailable()) - { - stLen += p_Print.print(cpcI); - stLen += p_Print.println("IPv6 address(es):"); - for (const IPAddress& addr : IPv6Addresses()) - { - stLen += p_Print.print(cpcI); - stLen += p_Print.print(cpcS); - stLen += p_Print.println(addr); - } - } -#endif - if (serviceDomainAvailable()) - { - stLen += p_Print.print(cpcI); - stLen += p_Print.print("Service domain: "); - stLen += p_Print.println(serviceDomain()); - } - if (hostPortAvailable()) - { - stLen += p_Print.print(cpcI); - stLen += p_Print.print("Host port: "); - stLen += p_Print.println(hostPort()); - } - if (txtsAvailable()) - { - stLen += p_Print.print(cpcI); - stLen += p_Print.print("TXTs:"); - for (auto const& x : txtKeyValues()) - { - stLen += p_Print.print(cpcI); - stLen += p_Print.print(cpcS); - stLen += p_Print.print(x.first); - stLen += p_Print.print("="); - stLen += p_Print.println(x.second); - } - } - stLen += p_Print.println(" * * * * *"); - - return stLen; -} - -/** - STATIC QUERIES -*/ - -/* - 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 MDNSResponder::hMDNSHost p_hMDNSHost, - 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("%s queryService '_%s._%s.local'\n"), _DH(p_hMDNSHost), p_pcService, p_pcProtocol);); - - uint32_t u32Result = ((_validateMDNSHostHandle(p_hMDNSHost)) - ? (_NRH2Ptr(p_hMDNSHost)->queryService(p_pcService, p_pcProtocol, p_u16Timeout)) - : 0); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryService '_%s._%s.local' returned %u hits!\n"), _DH(p_hMDNSHost), p_pcService, p_pcProtocol, u32Result);); - return u32Result; -} - -/* - MDNSResponder::queryHost - - Perform a (blocking) static host query. - The arrived answers can be queried by calling: - - answerHostName (or 'hostname') - - answerIP (or 'IP') - - answerPort (or 'port') - -*/ -uint32_t MDNSResponder::queryHost(const MDNSResponder::hMDNSHost p_hMDNSHost, - const char* p_pcHostName, - const uint16_t p_u16Timeout /*= MDNS_QUERYSERVICES_WAIT_TIME*/) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost '%s.local'\n"), _DH(p_hMDNSHost), p_pcHostName);); - - uint32_t u32Result = ((_validateMDNSHostHandle(p_hMDNSHost)) - ? (_NRH2Ptr(p_hMDNSHost)->queryHost(p_pcHostName, p_u16Timeout)) - : 0); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost '%s.local' returned %u hits!\n"), _DH(p_hMDNSHost), p_pcHostName, u32Result);); - return u32Result; -} - -/* - MDNSResponder::removeQuery - - Remove the last static query (and all answers). - -*/ -bool MDNSResponder::removeQuery(const MDNSResponder::hMDNSHost p_hMDNSHost) -{ - return ((_validateMDNSHostHandle(p_hMDNSHost)) && - (_NRH2Ptr(p_hMDNSHost)->removeQuery())); -} - -/* - MDNSResponder::hasQuery - - Return 'true', if a static query is currently installed - -*/ -bool MDNSResponder::hasQuery(const MDNSResponder::hMDNSHost p_hMDNSHost) -{ - return ((_validateMDNSHostHandle(p_hMDNSHost)) && - (0 != _NRH2Ptr(p_hMDNSHost)->hasQuery())); -} - -/* - MDNSResponder::getQuery - - Return handle to the last static query - -*/ -MDNSResponder::hMDNSQuery MDNSResponder::getQuery(const MDNSResponder::hMDNSHost p_hMDNSHost) -{ - return (_validateMDNSHostHandle(p_hMDNSHost) - ? (hMDNSQuery)_NRH2Ptr(p_hMDNSHost)->getQuery() - : 0); -} - - -/* - MDNSResponder::answerAccessors -*/ -MDNSResponder::clsMDNSAnswerAccessorVector MDNSResponder::answerAccessors(const MDNSResponder::hMDNSHost p_hMDNSHost) -{ - hMDNSQuery hLegacyQuery = getQuery(p_hMDNSHost); - return ((hLegacyQuery) - ? answerAccessors(p_hMDNSHost, hLegacyQuery) - : clsMDNSAnswerAccessorVector()); -} - -/* - MDNSResponder::answerCount -*/ -uint32_t MDNSResponder::answerCount(const MDNSResponder::hMDNSHost p_hMDNSHost) -{ - hMDNSQuery hLegacyQuery = getQuery(p_hMDNSHost); - return ((hLegacyQuery) - ? answerCount(p_hMDNSHost, hLegacyQuery) - : 0); -} - -/* - MDNSResponder::answerAccessor -*/ -MDNSResponder::clsMDNSAnswerAccessor MDNSResponder::answerAccessor(const MDNSResponder::hMDNSHost p_hMDNSHost, - uint32_t p_u32AnswerIndex) -{ - hMDNSQuery hLegacyQuery = getQuery(p_hMDNSHost); - return ((hLegacyQuery) - ? answerAccessor(p_hMDNSHost, hLegacyQuery, p_u32AnswerIndex) - : clsMDNSAnswerAccessor(0)); -} - - - -#ifdef NOTUSED - -/* - MDNSResponder::answerHostName -*/ -const char* MDNSResponder::answerHostName(const MDNSResponder::hMDNSHost p_hMDNSHost, - const uint32_t p_u32AnswerIndex) -{ - const stcQuery* pQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findLegacyQuery(*(clsHost*)p_hMDNSHost) : 0); - stcQuery::stcAnswer* pSQAnswer = (pQuery ? pQuery->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_IPV4_SUPPORT -/* - MDNSResponder::answerIPv4 -*/ -IPAddress MDNSResponder::answerIPv4(const MDNSResponder::hMDNSHost p_hMDNSHost, - const uint32_t p_u32AnswerIndex) -{ - const stcQuery* pQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findLegacyQuery(*(clsHost*)p_hMDNSHost) : 0); - const stcQuery::stcAnswer* pSQAnswer = (pQuery ? pQuery->answerAtIndex(p_u32AnswerIndex) : 0); - const stcQuery::stcAnswer::stcIPv4Address* pIPv4Address = (((pSQAnswer) && (pSQAnswer->m_pIPv4Addresses)) ? pSQAnswer->IPv4AddressAtIndex(0) : 0); - return (pIPv4Address ? pIPv4Address->m_IPAddress : IPAddress()); -} -#endif - -#ifdef MDNS_IPV6_SUPPORT -/* - MDNSResponder::answerIPv6 -*/ -IPAddress MDNSResponder::answerIPv6(const MDNSResponder::hMDNSHost p_hMDNSHost, - const uint32_t p_u32AnswerIndex) -{ - const stcQuery* pQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findLegacyQuery(*(clsHost*)p_hMDNSHost) : 0); - const stcQuery::stcAnswer* pSQAnswer = (pQuery ? pQuery->answerAtIndex(p_u32AnswerIndex) : 0); - const stcQuery::stcAnswer::stcIPv6Address* pIPv6Address = (((pSQAnswer) && (pSQAnswer->m_pIPv6Addresses)) ? pSQAnswer->IPv6AddressAtIndex(0) : 0); - return (pIPv6Address ? pIPv6Address->m_IPAddress : IPAddress()); -} -#endif - -/* - MDNSResponder::answerPort -*/ -uint16_t MDNSResponder::answerPort(const MDNSResponder::hMDNSHost p_hMDNSHost, - const uint32_t p_u32AnswerIndex) -{ - const stcQuery* pQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findLegacyQuery(*(clsHost*)p_hMDNSHost) : 0); - const stcQuery::stcAnswer* pSQAnswer = (pQuery ? pQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return (pSQAnswer ? pSQAnswer->m_u16Port : 0); -} - -#endif - - -/** - 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 - - answerIPv4Address/answerIPv6Address - - answerPort - - answerTxts - -*/ -MDNSResponder::hMDNSQuery MDNSResponder::installServiceQuery(const MDNSResponder::hMDNSHost p_hMDNSHost, - const char* p_pcService, - const char* p_pcProtocol, - MDNSResponder::MDNSQueryCallbackFn p_fnCallback) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryService '_%s._%s.local'\n"), _DH(p_hMDNSHost), p_pcService, p_pcProtocol);); - - hMDNSQuery hResult = ((_validateMDNSHostHandle(p_hMDNSHost)) - ? (_NRH2Ptr(p_hMDNSHost)->installServiceQuery(p_pcService, p_pcProtocol, [this, p_fnCallback](clsHost & p_rHost, - const clsHost::stcQuery & p_Query, - const clsHost::stcQuery::stcAnswer & p_Answer, - clsHost::typeQueryAnswerType p_QueryAnswerTypeFlags, // flags for the updated answer item - bool p_bSetContent)->void - { - if (p_fnCallback) - { - p_fnCallback(*this, (hMDNSHost)&p_rHost, (hMDNSQuery)&p_Query, clsMDNSAnswerAccessor(&p_Answer), p_QueryAnswerTypeFlags, p_bSetContent); - } - })) - : 0); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s installServiceQuery: %s for '_%s._%s.local'!\n\n"), _DH(p_hMDNSHost), (hResult ? "Succeeded" : "FAILED"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); - DEBUG_EX_ERR(if (!hResult) DEBUG_OUTPUT.printf_P(PSTR("%s installServiceQuery: FAILED for '_%s._%s.local'!\n\n"), _DH(p_hMDNSHost), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); - return hResult; -} - -/* - MDNSResponder::installHostQuery -*/ -MDNSResponder::hMDNSQuery MDNSResponder::installHostQuery(const MDNSResponder::hMDNSHost p_hMDNSHost, - const char* p_pcHostName, - MDNSResponder::MDNSQueryCallbackFn p_fnCallback) -{ - hMDNSQuery hResult = ((_validateMDNSHostHandle(p_hMDNSHost)) - ? (_NRH2Ptr(p_hMDNSHost)->installHostQuery(p_pcHostName, [this, p_fnCallback](clsHost & p_rHost, - const clsHost::stcQuery & p_Query, - const clsHost::stcQuery::stcAnswer & p_Answer, - clsHost::typeQueryAnswerType p_QueryAnswerTypeFlags, // flags for the updated answer item - bool p_bSetContent)->void - { - if (p_fnCallback) - { - p_fnCallback(*this, (hMDNSHost)&p_rHost, (hMDNSQuery)&p_Query, clsMDNSAnswerAccessor(&p_Answer), p_QueryAnswerTypeFlags, p_bSetContent); - } - })) - : 0); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s installHostQuery: %s for '%s.local'!\n\n"), _DH(p_hMDNSHost), (hResult ? "Succeeded" : "FAILED"), (p_pcHostName ? : "-"));); - DEBUG_EX_ERR(if (!hResult) DEBUG_OUTPUT.printf_P(PSTR("%s installHostQuery: FAILED for '%s.local'!\n\n"), _DH(p_hMDNSHost), (p_pcHostName ? : "-"));); - return hResult; -} - -/* - MDNSResponder::removeQuery - - Remove a dynamic query (and all collected answers) from the MDNS responder - -*/ -bool MDNSResponder::removeQuery(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery) -{ - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && - (p_hMDNSQuery) && - (_NRH2Ptr(p_hMDNSHost)->removeQuery((clsHost::stcQuery*)p_hMDNSQuery))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeQuery: FAILED!\n"), _DH(p_hMDNSHost));); - return bResult; -} - -/* - MDNSResponder::answerAccessors -*/ -MDNSResponder::clsMDNSAnswerAccessorVector MDNSResponder::answerAccessors(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery) -{ - clsMDNSAnswerAccessorVector tempVector; - for (uint32_t u = 0; u < answerCount(p_hMDNSHost, p_hMDNSQuery); ++u) - { - clsHost::stcQuery::stcAnswer* pAnswer = 0; - if ((_validateMDNSHostHandle(p_hMDNSHost)) && - (p_hMDNSQuery) && - ((pAnswer = ((clsHost::stcQuery*)p_hMDNSQuery)->answerAtIndex(u)))) - { - tempVector.emplace_back(clsMDNSAnswerAccessor(pAnswer)); - //tempVector.emplace_back(*pAnswer); - } - } - return tempVector; -} - -/* - MDNSResponder::answerCount -*/ -uint32_t MDNSResponder::answerCount(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery) -{ - _validateMDNSHostHandle(p_hMDNSHost); - return ((clsHost::stcQuery*)p_hMDNSQuery)->answerCount(); -} - -/* - MDNSResponder::answerAccessor -*/ -MDNSResponder::clsMDNSAnswerAccessor MDNSResponder::answerAccessor(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - uint32_t p_u32AnswerIndex) -{ - clsHost::stcQuery::stcAnswer* pAnswer = (((_validateMDNSHostHandle(p_hMDNSHost)) && - (p_hMDNSQuery)) - ? ((clsHost::stcQuery*)p_hMDNSQuery)->answerAtIndex(p_u32AnswerIndex) - : 0); - return MDNSResponder::clsMDNSAnswerAccessor(pAnswer); -} - -#ifdef LATER -/* - MDNSResponder::hasAnswerServiceDomain -*/ -bool MDNSResponder::hasAnswerServiceDomain(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return ((pSQAnswer) && - (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::ServiceDomain))); -} - - /* - 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::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->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::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return ((pSQAnswer) && - (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::HostDomain))); -} - - /* - 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::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->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_IPV4_SUPPORT -/* - MDNSResponder::hasAnswerIPv4Address -*/ -bool MDNSResponder::hasAnswerIPv4Address(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return ((pSQAnswer) && - (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::IPv4Address))); -} - - /* - MDNSResponder::answerIPv4AddressCount - */ - uint32_t MDNSResponder::answerIPv4AddressCount(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return (pSQAnswer ? pSQAnswer->IPv4AddressCount() : 0); -} - - /* - MDNSResponder::answerIPv4Address - */ - IPAddress MDNSResponder::answerIPv4Address(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex, - const uint32_t p_u32AddressIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - stcQuery::stcAnswer::stcIPv4Address* pIPv4Address = (pSQAnswer ? pSQAnswer->IPv4AddressAtIndex(p_u32AddressIndex) : 0); - return (pIPv4Address ? pIPv4Address->m_IPAddress : IPAddress()); -} -#endif - -#ifdef MDNS_IPV6_SUPPORT - /* - MDNSResponder::hasAnswerIPv6Address - */ - bool MDNSResponder::hasAnswerIPv6Address(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return ((pSQAnswer) && - (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::IPv6Address))); -} - - /* - MDNSResponder::answerIPv6AddressCount - */ - uint32_t MDNSResponder::answerIPv6AddressCount(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return (pSQAnswer ? pSQAnswer->IPv6AddressCount() : 0); -} - - /* - MDNSResponder::answerIPv6Address - */ - IPAddress MDNSResponder::answerIPv6Address(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex, - const uint32_t p_u32AddressIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - stcQuery::stcAnswer::stcIPv6Address* pIPv6Address = (pSQAnswer ? pSQAnswer->IPv6AddressAtIndex(p_u32AddressIndex) : 0); - return (pIPv6Address ? pIPv6Address->m_IPAddress : IPAddress()); -} -#endif - - /* - MDNSResponder::hasAnswerPort - */ - bool MDNSResponder::hasAnswerPort(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return ((pSQAnswer) && - (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::Port))); -} - - /* - MDNSResponder::answerPort - */ - uint16_t MDNSResponder::answerPort(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return (pSQAnswer ? pSQAnswer->m_u16Port : 0); -} - - /* - MDNSResponder::hasAnswerTxts - */ - bool MDNSResponder::hasAnswerTxts(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return ((pSQAnswer) && - (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::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::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->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::setHostProbeResultCallback - - Set a callback for probe results. The callback is called, when probing - for the host domain failes or succeedes. - In the case of failure, the domain name should be changed via 'setHostName' - When succeeded, the host domain will be announced by the MDNS responder. - -*/ -bool MDNSResponder::setHostProbeResultCallback(const MDNSResponder::hMDNSHost p_hMDNSHost, - MDNSResponder::MDNSHostProbeResultCallbackFn p_fnCallback) -{ - return ((_validateMDNSHostHandle(p_hMDNSHost)) && - (((clsHost*)p_hMDNSHost)->m_HostProbeInformation.m_fnProbeResultCallback = p_fnCallback, true)); -} - - -/* - MISC -*/ - -/* - MDNSResponder::notifyNetIfChange - - 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::notifyNetIfChange(netif* p_pNetIf) -{ - clsHost* pMDNSHost; - return (((pMDNSHost = _findHost(p_pNetIf))) && - (_restart(*pMDNSHost))); -} - -/* - MDNSResponder::update - - Should be called in every 'loop'. - -*/ -bool MDNSResponder::update(const MDNSResponder::hMDNSHost p_hMDNSHost) -{ - return ((_validateMDNSHostHandle(p_hMDNSHost)) && - (_process(*(clsHost*)p_hMDNSHost, true))); -} - -/* - MDNSResponder::update (convenience) -*/ -bool MDNSResponder::update(void) -{ - bool bResult = true; - for (clsHost*& pMDNSHost : m_HostList) - { - if (!_process(*it, true)) - { - bResult = false; - } - } - return bResult; -} - -/* - MDNSResponder::announce (convenience) - - Should be called, if the 'configuration' changes. Mainly this will be changes in the TXT items... -*/ -bool MDNSResponder::announce(const MDNSResponder::hMDNSHost p_hMDNSHost) -{ - return ((_validateMDNSHostHandle(p_hMDNSHost)) && - (_announce(*(clsHost*)p_hMDNSHost, true, true))); -} - -/* - MDNSResponder::announce (convenience) -*/ -bool MDNSResponder::announce(void) -{ - bool bResult = true; - for (clsHost*& pMDNSHost : m_HostList) - { - if (!_announce(*it, true, true)) - { - bResult = false; - } - } - return bResult; -} - -/* - MDNSResponder::enableArduino - - Enable the OTA update service. - -*/ -MDNSResponder::hMDNSService MDNSResponder::enableArduino(const MDNSResponder::hMDNSHost p_hMDNSHost, - uint16_t p_u16Port, - bool p_bAuthUpload /*= false*/) -{ - hMDNSService hService = addService(p_hMDNSHost, 0, "arduino", "tcp", p_u16Port); - if (hService) - { - if ((!addServiceTxt(p_hMDNSHost, hService, "tcp_check", "no")) || - (!addServiceTxt(p_hMDNSHost, hService, "ssh_upload", "no")) || - (!addServiceTxt(p_hMDNSHost, hService, "board", STRINGIZE_VALUE_OF(ARDUINO_BOARD))) || - (!addServiceTxt(p_hMDNSHost, hService, "auth_upload", (p_bAuthUpload) ? "yes" : "no"))) - { - - removeService(p_hMDNSHost, hService); - hService = 0; - } - } - return hService; -} -#endif // LATER - -#ifdef __MDNS_USE_LEGACY - -/** - INTERFACE -*/ - -/** - MDNSResponder::MDNSResponder -*/ -MDNSResponder::MDNSResponder(void) - : m_pUDPContext(0) -{ -} - -/* - MDNSResponder::~MDNSResponder -*/ -MDNSResponder::~MDNSResponder(void) -{ - close(); -} - - -/* - MDNSResponder::getHost (netif) -*/ -MDNSResponder::hMDNSHost MDNSResponder::getHost(netif* p_pNetIf) const -{ - return (hMDNSHost)(p_pNetIf ? _findHost(p_pNetIf) : 0); -} - -/* - MDNSResponder::getHost (WiFiMode) -*/ -MDNSResponder::hMDNSHost MDNSResponder::getHost(WiFiMode_t p_WiFiMode) const -{ - hMDNSHost hResult = 0; - - if (WIFI_STA == p_WiFiMode) - { - hResult = getHost(netif_get_by_index(WIFI_STA)); - } - else if (WIFI_AP == p_WiFiMode) - { - hResult = getHost(netif_get_by_index(WIFI_AP)); - } - return hResult; -} - -/* - MDNSResponder::begin (hostname, netif, probe_callback) -*/ -MDNSResponder::hMDNSHost MDNSResponder::begin(const char* p_pcHostName, - netif* p_pNetIf, - MDNSHostProbeResultCallbackFn p_fnCallback /*= 0*/) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s, netif: %u)\n"), _DH(), (p_pcHostName ? : "_"), (p_pNetIf ? netif_get_index(p_pNetIf) : 0));); - - return (hMDNSHost)_begin(p_pcHostName, p_pNetIf, p_fnCallback); -} - -/* - MDNSResponder::begin (hostname, probe_callback) -*/ -bool MDNSResponder::begin(const char* p_pcHostName, - MDNSHostProbeResultCallbackFn p_fnCallback /*= 0*/) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s)\n"), _DH(), (p_pcHostName ? : "_"));); - - return begin(p_pcHostName, (WiFiMode_t)wifi_get_opmode(), p_fnCallback); -} - -/* - MDNSResponder::begin (hostname, WiFiMode, probe_callback) -*/ -bool MDNSResponder::begin(const char* p_pcHostName, - WiFiMode_t p_WiFiMode, - MDNSHostProbeResultCallbackFn p_fnCallback /*= 0*/) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s, opmode: %u)\n"), _DH(), (p_pcHostName ? : "_"), (uint32_t)p_WiFiMode);); - - bool bResult = true; - - if ((bResult) && - (p_WiFiMode & WIFI_STA)) - { - bResult = (0 != _begin(p_pcHostName, netif_get_by_index(WIFI_STA), p_fnCallback)); - } - if ((bResult) && - (p_WiFiMode & WIFI_AP)) - { - bResult = (0 != _begin(p_pcHostName, netif_get_by_index(WIFI_AP), p_fnCallback)); - } - return bResult; -} - -/* - MDNSResponder::close -*/ -bool MDNSResponder::close(const MDNSResponder::hMDNSHost p_hMDNSHost) -{ - return ((_validateMDNSHostHandle(p_hMDNSHost)) && - (_close(*(clsHost*)p_hMDNSHost))); -} - -/* - MDNSResponder::close (convenience) -*/ -bool MDNSResponder::close(void) -{ - clsHostList::iterator it(m_HostList.begin()); - while (m_HostList.end() != it) - { - _close(**it++); - } - - _releaseUDPContext(); - - return true; -} - -/* - MDNSResponder::end (ESP32) -*/ -bool MDNSResponder::end(void) -{ - return close(); -} - -/* - MDNSResponder::setHostName -*/ -bool MDNSResponder::setHostName(const MDNSResponder::hMDNSHost p_hMDNSHost, - const char* p_pcHostName) -{ - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && - (_setHostName(*(clsHost*)p_hMDNSHost, p_pcHostName))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setHostName: FAILED for '%s'!\n"), _DH(), (p_pcHostName ? : "-"));); - return bResult; -} - -/* - MDNSResponder::setHostname (LEGACY 2) - - Set the host name in all netif bindings - -*/ -bool MDNSResponder::setHostname(const char* p_pcHostName) -{ - bool bResult = true; - for (clsHost*& pMDNSHost : m_HostList) - { - if (!setHostName((hMDNSHost)pMDNSHost, p_pcHostName)) - { - bResult = false; - } - } - return bResult; -} - -/* - MDNSResponder::setHostname (LEGACY) -*/ -bool MDNSResponder::setHostname(String p_strHostName) -{ - return setHostname(p_strHostName.c_str()); -} - -/* - MDNSResponder::hostName -*/ -const char* MDNSResponder::hostName(const MDNSResponder::hMDNSHost p_hMDNSHost) const -{ - return (_validateMDNSHostHandle(p_hMDNSHost) - ? (((clsHost*)p_hMDNSHost)->m_pcHostName) - : 0); -} - -/* - MDNSResponder::hostname (LEGACY 2) -*/ -const char* MDNSResponder::hostname(void) const -{ - return ((!m_HostList.empty()) - ? hostName((hMDNSHost)m_HostList.front()) - : 0); -} - -/* - MDNSResponder::status -*/ -bool MDNSResponder::status(const MDNSResponder::hMDNSHost p_hMDNSHost) const -{ - return ((_validateMDNSHostHandle(p_hMDNSHost)) && - (enuProbingStatus::Done == ((clsHost*)p_hMDNSHost)->m_HostProbeInformation.m_ProbingStatus)); -} - -/* - MDNSResponder::status (LEGACY 2) -*/ -bool MDNSResponder::status(void) const -{ - bool bResult = true; - for (clsHost * const& pMDNSHost : m_HostList) - { - if (!((bResult = status((hMDNSHost)pMDNSHost)))) - { - break; - } - } - return bResult; -} - - -/* - SERVICES -*/ - -/* - MDNSResponder::setInstanceName -*/ -bool MDNSResponder::setInstanceName(const MDNSResponder::hMDNSHost p_hMDNSHost, - const char* p_pcInstanceName) -{ - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && - (_setInstanceName(*(clsHost*)p_hMDNSHost, p_pcInstanceName))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setInstanceName: FAILED for '%s'!\n"), _DH(), (p_pcInstanceName ? : "-"));); - return bResult; -} - -/* - MDNSResponder::setInstanceName (LEGACY 2) - - Set the instance name in all netif bindings - -*/ -bool MDNSResponder::setInstanceName(const char* p_pcInstanceName) -{ - bool bResult = true; - for (clsHost*& pMDNSHost : m_HostList) - { - if (!setInstanceName((hMDNSHost)pMDNSHost, p_pcInstanceName)) - { - bResult = false; - } - } - return bResult; -} - -/* - MDNSResponder::setInstanceName (LEGACY 2) -*/ -bool MDNSResponder::setInstanceName(const String& p_strInstanceName) -{ - return setInstanceName(p_strInstanceName.c_str()); -} - -/* - 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 MDNSResponder::hMDNSHost p_hMDNSHost, - const char* p_pcName, - const char* p_pcService, - const char* p_pcProtocol, - uint16_t p_u16Port) -{ - hMDNSService hService = (_validateMDNSHostHandle(p_hMDNSHost) - ? (hMDNSService)_addService(*(clsHost*)p_hMDNSHost, p_pcName, p_pcService, p_pcProtocol, p_u16Port) - : 0); - DEBUG_EX_ERR(if (!hService) DEBUG_OUTPUT.printf_P(PSTR("%s addService: FAILED for '%s._%s._%s.local'!\n"), _DH((clsHost*)p_hMDNSHost), (p_pcName ? : "-"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); - return hService; -} - -/* - MDNSResponder::addService (LEGACY 2) - - Add a service to all netif bindings. - (Only) the first service (handle) is returned. - -*/ -MDNSResponder::hMDNSService MDNSResponder::addService(const char* p_pcName, - const char* p_pcService, - const char* p_pcProtocol, - uint16_t p_u16Port) -{ - hMDNSService hResult = 0; - for (clsHost*& pMDNSHost : m_HostList) - { - hMDNSService hNewService = addService((hMDNSHost)pMDNSHost, p_pcName, p_pcService, p_pcProtocol, p_u16Port); - if (!hResult) - { - hResult = hNewService; - } - } - return hResult; -} - -/* - MDNSResponder::removeService - - Unanounce a service (by sending a goodbye message) and remove it - from the MDNS responder - -*/ -bool MDNSResponder::removeService(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService) -{ - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && - (_removeService(*(clsHost*)p_hMDNSHost, *(stcService*)p_hMDNSService))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeService: FAILED!\n"), _DH((clsHost*)p_hMDNSHost));); - return bResult; -} - -/* - MDNSResponder::removeService (LEGACY 2) - - Find and remove the service from one netif binding -*/ -bool MDNSResponder::removeService(const MDNSResponder::hMDNSService p_hMDNSService) -{ - clsHost* pHost = 0; - return ((_validateMDNSHostHandle(p_hMDNSService, &pHost)) && - (removeService((hMDNSHost)pHost, p_hMDNSService))); -} - -/* - MDNSResponder::removeService -*/ -bool MDNSResponder::removeService(const MDNSResponder::hMDNSHost p_hMDNSHost, - const char* p_pcName, - const char* p_pcService, - const char* p_pcProtocol) -{ - clsHost* pMDNSHost; - stcService* pMDNSService; - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && - (pMDNSHost = (clsHost*)p_hMDNSHost) && - ((pMDNSService = _findService(*pMDNSHost, (p_pcName ? : (pMDNSHost->m_pcInstanceName ? : pMDNSHost->m_pcHostName)), p_pcService, p_pcProtocol))) && - (_removeService(*(clsHost*)p_hMDNSHost, *pMDNSService))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeService: FAILED for '%s._%s._%s.local'!\n"), _DH((clsHost*)p_hMDNSHost), (p_pcName ? : "-"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); - return bResult; -} - -/* - MDNSResponder::removeService (LEGACY 2) -*/ -bool MDNSResponder::removeService(const char* p_pcName, - const char* p_pcService, - const char* p_pcProtocol) -{ - bool bResult = true; - - for (clsHost*& pMDNSHost : m_HostList) - { - if (!removeService((hMDNSHost)pMDNSHost, p_pcName, p_pcService, p_pcProtocol)) - { - bResult = false; - } - } - return bResult; -} - -/* - MDNSResponder::addService (LEGACY) -*/ -bool MDNSResponder::addService(String p_strService, - String p_strProtocol, - uint16_t p_u16Port) -{ - return (0 != addService(0, p_strService.c_str(), p_strProtocol.c_str(), p_u16Port)); -} - -/* - MDNSResponder::findService - - Find an existing service. - -*/ -MDNSResponder::hMDNSService MDNSResponder::findService(const MDNSResponder::hMDNSHost p_hMDNSHost, - const char* p_pcName, - const char* p_pcService, - const char* p_pcProtocol) -{ - clsHost* pMDNSHost; - return (((_validateMDNSHostHandle(p_hMDNSHost)) && - (pMDNSHost = (clsHost*)p_hMDNSHost)) - ? _findService(*pMDNSHost, (p_pcName ? : (pMDNSHost->m_pcInstanceName ? : pMDNSHost->m_pcHostName)), p_pcService, p_pcProtocol) - : 0); -} - -/* - MDNSResponder::findService (LEGACY 2) - - (Only) the first service handle is returned. - -*/ -MDNSResponder::hMDNSService MDNSResponder::findService(const char* p_pcName, - const char* p_pcService, - const char* p_pcProtocol) -{ - hMDNSService hResult = 0; - for (clsHost*& pMDNSHost : m_HostList) - { - if ((hResult = findService((hMDNSHost)pMDNSHost, p_pcName, p_pcService, p_pcProtocol))) - { - break; - } - } - return hResult; -} - -/* - MDNSResponder::setServiceName -*/ -bool MDNSResponder::setServiceName(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcInstanceName) -{ - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && - (_setServiceName(*(clsHost*)p_hMDNSHost, *(stcService*)p_hMDNSService, p_pcInstanceName))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setServiceName: FAILED for '%s'!\n"), _DH((clsHost*)p_hMDNSHost), (p_pcInstanceName ? : "-"));); - return bResult; -} - -/* - MDNSResponder::setServiceName (LEGACY 2) -*/ -bool MDNSResponder::setServiceName(const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcInstanceName) -{ - clsHost* pHost = 0; - return ((_validateMDNSServiceHandle(p_hMDNSService, &pHost)) && - (setServiceName((hMDNSHost)pHost, p_hMDNSService, p_pcInstanceName))); -} - -/* - MDNSResponder::serviceName -*/ -const char* MDNSResponder::serviceName(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const -{ - return (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) - ? ((stcService*)p_hMDNSService)->m_pcName - : 0); -} - -/* - MDNSResponder::serviceName (LEGACY 2) -*/ -const char* MDNSResponder::serviceName(const hMDNSService p_hMDNSService) const -{ - clsHost* pHost = 0; - return (_validateMDNSServiceHandle(p_hMDNSService, &pHost) - ? serviceName((hMDNSHost)pHost, p_hMDNSService) - : 0); -} - -/* - MDNSResponder::service -*/ -const char* MDNSResponder::service(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const -{ - return (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) - ? ((stcService*)p_hMDNSService)->m_pcService - : 0); -} - -/* - MDNSResponder::service (LEGACY 2) -*/ -const char* MDNSResponder::service(const hMDNSService p_hMDNSService) const -{ - clsHost* pHost = 0; - return (_validateMDNSServiceHandle(p_hMDNSService, &pHost) - ? service((hMDNSHost)pHost, p_hMDNSService) - : 0); -} - -/* - MDNSResponder::serviceProtocol -*/ -const char* MDNSResponder::serviceProtocol(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const -{ - return (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) - ? ((stcService*)p_hMDNSService)->m_pcProtocol - : 0); -} - -/* - MDNSResponder::serviceProtocol (LEGACY) -*/ -const char* MDNSResponder::serviceProtocol(const hMDNSService p_hMDNSService) const -{ - clsHost* pHost = 0; - return (_validateMDNSServiceHandle(p_hMDNSService, &pHost) - ? serviceProtocol((hMDNSHost)pHost, p_hMDNSService) - : 0); -} - -/* - MDNSResponder::serviceStatus -*/ -bool MDNSResponder::serviceStatus(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const -{ - return ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && - (enuProbingStatus::Done == ((stcService*)p_hMDNSService)->m_ProbeInformation.m_ProbingStatus)); -} - -/* - MDNSResponder::serviceStatus (LEGACY 2) - - Returns 'true' if probing for the service 'hMDNSService' is done - -*/ -bool MDNSResponder::serviceStatus(const hMDNSService p_hMDNSService) const -{ - clsHost* pHost = 0; - return (_validateMDNSServiceHandle(p_hMDNSService, &pHost) - ? serviceStatus((hMDNSHost)pHost, p_hMDNSService) - : false); -} - - -/* - SERVICE TXT -*/ - -/* - MDNSResponder::addServiceTxt - - Add a static service TXT item ('Key'='Value') to a service. - -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - const char* p_pcValue) -{ - hMDNSTxt hTxt = (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) - ? (hMDNSTxt)_addServiceTxt((clsHost*)p_hMDNSHost, (stcService*)p_hMDNSService, p_pcKey, p_pcValue, false) - : 0); - DEBUG_EX_ERR(if (!hTxt) DEBUG_OUTPUT.printf_P(PSTR("%s addServiceTxt: FAILED for '%s=%s'!\n"), _DH(), (p_pcKey ? : "-"), (p_pcValue ? : "-"));); - return hTxt; -} - -/* - MDNSResponder::addServiceTxt (LEGACY 2) - - Add a static service TXT item ('Key'='Value') to a service. - -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - const char* p_pcValue) -{ - clsHost* pHost = 0; - return (_validateMDNSHostHandle(p_hMDNSService, &pHost) - ? addServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_pcValue) - : 0); -} - -/* - MDNSRESPONDER_xxx_TO_CHAR - Formats: http://www.cplusplus.com/reference/cstdio/printf/ -*/ -#define MDNSRESPONDER_U32_TO_CHAR(BUFFERNAME, U32VALUE) \ - char BUFFERNAME[16]; /* 32-bit max 10 digits */ \ - *BUFFERNAME = 0; \ - sprintf(BUFFERNAME, "%u", U32VALUE); -#define MDNSRESPONDER_U16_TO_CHAR(BUFFERNAME, U16VALUE) \ - char BUFFERNAME[8]; /* 16-bit max 5 digits */ \ - *BUFFERNAME = 0; \ - sprintf(BUFFERNAME, "%hu", U16VALUE); -#define MDNSRESPONDER_U8_TO_CHAR(BUFFERNAME, U8VALUE) \ - char BUFFERNAME[8]; /* 8-bit max 3 digits */ \ - *BUFFERNAME = 0; \ - sprintf(BUFFERNAME, "%hhu", U8VALUE); -#define MDNSRESPONDER_I32_TO_CHAR(BUFFERNAME, I32VALUE) \ - char BUFFERNAME[16]; /* 32-bit max 10 digits */ \ - *BUFFERNAME = 0; \ - sprintf(BUFFERNAME, "%i", I32VALUE); -#define MDNSRESPONDER_I16_TO_CHAR(BUFFERNAME, I16VALUE) \ - char BUFFERNAME[8]; /* 16-bit max 5 digits */ \ - *BUFFERNAME = 0; \ - sprintf(BUFFERNAME, "%hi", I16VALUE); -#define MDNSRESPONDER_I8_TO_CHAR(BUFFERNAME, I8VALUE) \ - char BUFFERNAME[8]; /* 8-bit max 3 digits */ \ - *BUFFERNAME = 0; \ - sprintf(BUFFERNAME, "%hhi", I8VALUE); - - -/* - MDNSResponder::addServiceTxt (uint32_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint32_t p_u32Value) -{ - MDNSRESPONDER_U32_TO_CHAR(acBuffer, p_u32Value); - return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): - } - - /* - MDNSResponder::addServiceTxt (uint32_t) (LEGACY 2) - */ - MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint32_t p_u32Value) -{ - clsHost* pHost = 0; - return (_validateMDNSHostHandle(p_hMDNSService, &pHost) - ? addServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_u32Value) - : 0); -} - -/* - MDNSResponder::addServiceTxt (uint16_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint16_t p_u16Value) -{ - MDNSRESPONDER_U16_TO_CHAR(acBuffer, p_u16Value); - return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): - } - - /* - MDNSResponder::addServiceTxt (uint16_t) (LEGACY 2) - */ - MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint16_t p_u16Value) -{ - clsHost* pHost = 0; - return (_validateMDNSHostHandle(p_hMDNSService, &pHost) - ? addServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_u16Value) - : 0); -} - -/* - MDNSResponder::addServiceTxt (uint8_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint8_t p_u8Value) -{ - MDNSRESPONDER_U8_TO_CHAR(acBuffer, p_u8Value); - return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): - } - - /* - MDNSResponder::addServiceTxt (uint8_t) (LEGACY 2) - */ - MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint8_t p_u8Value) -{ - clsHost* pHost = 0; - return (_validateMDNSHostHandle(p_hMDNSService, &pHost) - ? addServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_u8Value) - : 0); -} - -/* - MDNSResponder::addServiceTxt (int32_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - int32_t p_i32Value) -{ - MDNSRESPONDER_I32_TO_CHAR(acBuffer, p_i32Value); - return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): - } - - /* - MDNSResponder::addServiceTxt (int32_t) (LEGACY 2) - */ - MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - int32_t p_i32Value) -{ - clsHost* pHost = 0; - return (_validateMDNSHostHandle(p_hMDNSService, &pHost) - ? addServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_i32Value) - : 0); -} - -/* - MDNSResponder::addServiceTxt (int16_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - int16_t p_i16Value) -{ - MDNSRESPONDER_I16_TO_CHAR(acBuffer, p_i16Value); - return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): - } - - /* - MDNSResponder::addServiceTxt (int16_t) (LEGACY 2) - */ - MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - int16_t p_i16Value) -{ - clsHost* pHost = 0; - return (_validateMDNSHostHandle(p_hMDNSService, &pHost) - ? addServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_i16Value) - : 0); -} - -/* - MDNSResponder::addServiceTxt (int8_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - int8_t p_i8Value) -{ - MDNSRESPONDER_I8_TO_CHAR(acBuffer, p_i8Value); - return addServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): - } - - /* - MDNSResponder::addServiceTxt (int8_t) (LEGACY 2) - */ - MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - int8_t p_i8Value) -{ - clsHost* pHost = 0; - return (_validateMDNSHostHandle(p_hMDNSService, &pHost) - ? addServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_i8Value) - : 0); -} - -/* - MDNSResponder::removeServiceTxt - - Remove a static service TXT item from a service. -*/ -bool MDNSResponder::removeServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const MDNSResponder::hMDNSTxt p_hTxt) -{ - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && - (p_hTxt) && - (_findServiceTxt(*(clsHost*)p_hMDNSHost, *(stcService*)p_hMDNSService, p_hTxt)) && - (_releaseServiceTxt(*(clsHost*)p_hMDNSHost, *(stcService*)p_hMDNSService, (stcServiceTxt*)p_hTxt))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeServiceTxt: FAILED!\n"), _DH((clsHost*)p_hMDNSHost));); - return bResult; -} - -/* - MDNSResponder::removeServiceTxt (LEGACY 2) -*/ -bool MDNSResponder::removeServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, - const MDNSResponder::hMDNSTxt p_hTxt) -{ - clsHost* pHost = 0; - return (_validateMDNSHostHandle(p_hMDNSService, &pHost) - ? removeServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_hTxt) - : false); -} - -/* - MDNSResponder::removeServiceTxt -*/ -bool MDNSResponder::removeServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey) -{ - stcServiceTxt* pTxt; - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && - (p_pcKey) && - ((pTxt = _findServiceTxt(*(clsHost*)p_hMDNSHost, *(stcService*)p_hMDNSService, p_pcKey))) && - (_releaseServiceTxt(*(clsHost*)p_hMDNSHost, *(stcService*)p_hMDNSService, pTxt))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeServiceTxt: FAILED!\n"), _DH((clsHost*)p_hMDNSHost));); - return bResult; -} - -/* - MDNSResponder::removeServiceTxt (LEGACY 2) -*/ -bool MDNSResponder::removeServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey) -{ - clsHost* pHost = 0; - return (_validateMDNSHostHandle(p_hMDNSService, &pHost) - ? removeServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey) - : false); -} - -/* - MDNSResponder::removeServiceTxt (LEGACY) -*/ -bool MDNSResponder::removeServiceTxt(const char* p_pcName, - const char* p_pcService, - const char* p_pcProtocol, - const char* p_pcKey) -{ - hMDNSService hService; - return (((hService = findService(p_pcName, p_pcService, p_pcProtocol))) - ? removeServiceTxt(hService, p_pcKey) - : false); -} - -/* - MDNSResponder::addServiceTxt (LEGACY) -*/ -bool MDNSResponder::addServiceTxt(const char* p_pcService, - const char* p_pcProtocol, - const char* p_pcKey, - const char* p_pcValue) -{ - hMDNSService hService; - return (((hService = findService(p_pcName, p_pcService, p_pcProtocol))) - ? addServiceTxt(hService, p_pcKey, p_pcValue) - : false); -} - -/* - MDNSResponder::addServiceTxt (LEGACY) -*/ -bool MDNSResponder::addServiceTxt(String p_strService, - String p_strProtocol, - String p_strKey, - String p_strValue) -{ - return addServiceTxt(p_strService.c_str(), p_strProtocol.c_str(), p_strKey.c_str(), p_strValue.c_str()); -} - -/* - MDNSResponder::setDynamicServiceTxtCallback (binding) - - Set a netif binding specific callback for dynamic service TXT items. The callback is called, whenever - service TXT items are needed for any service on the netif binding. - -*/ -bool MDNSResponder::setDynamicServiceTxtCallback(const MDNSResponder::hMDNSHost p_hMDNSHost, - MDNSResponder::MDNSDynamicServiceTxtCallbackFn p_fnCallback) -{ - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && - ((!p_fnCallback) || - ((((clsHost*)p_hMDNSHost)->m_fnServiceTxtCallback = p_fnCallback)))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setDynamicServiceTxtCallback: FAILED!\n"), _DH((clsHost*)p_hMDNSHost));); - return bResult; -} - -/* - MDNSResponder::setDynamicServiceTxtCallback (service) - - 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(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - MDNSResponder::MDNSDynamicServiceTxtCallbackFn p_fnCallback) -{ - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && - ((!p_fnCallback) || - ((((stcService*)p_hMDNSService)->m_fnServiceTxtCallback = p_fnCallback)))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s setDynamicServiceTxtCallback: FAILED!\n"), _DH((clsHost*)p_hMDNSHost));); - return bResult; -} - -/* - MDNSResponder::setDynamicServiceTxtCallback (global) (LEGACY 2) - - Set a global callback for dynamic service TXT items. The callback is called, whenever - service TXT items are needed. - -*/ -bool MDNSResponder::setDynamicServiceTxtCallback(MDNSResponder::MDNSDynamicServiceTxtCallbackFn1 p_fnCallback) -{ - for (clsHostList : .iterator it : m_HostList) - { - setDynamicServiceTxtCallback((hMDNSHost)it, p_fnCallback); - } - return true; -} - -/* - MDNSResponder::setDynamicServiceTxtCallback (global) (LEGACY 2 (Fn2)) -*/ -bool MDNSResponder::setDynamicServiceTxtCallback(MDNSResponder::MDNSDynamicServiceTxtCallbackFn2 p_fnCallback) -{ - return setDynamicServiceTxtCallback([p_fnCallback](MDNSResponder*, const hMDNSService p_hMDNSService) - { - if (p_fnCallback) - { - p_fnCallback(p_hMDNSService); - } - }); -} - -/* - MDNSResponder::setDynamicServiceTxtCallback (service) (LEGACY 2) -*/ -bool MDNSResponder::setDynamicServiceTxtCallback(MDNSResponder::hMDNSService p_hMDNSService, - MDNSResponder::MDNSDynamicServiceTxtCallbackFn1 p_fnCallback) -{ - clsHost* pHost; - return ((_validateMDNSHostHandle(p_hMDNSService, &pHost)) && - (setDynamicServiceTxtCallback((hMDNSHost)pHost, hMDNSService, p_fnCallback))); -} - -/* - MDNSResponder::setDynamicServiceTxtCallback (service) (LEGACY 2 (Fn2)) -*/ -bool MDNSResponder::setDynamicServiceTxtCallback(MDNSResponder::hMDNSService p_hMDNSService, - MDNSResponder::MDNSDynamicServiceTxtCallbackFn2 p_fnCallback) -{ - return setDynamicServiceTxtCallback(p_hMDNSService, [p_fnCallback](MDNSResponder*, const hMDNSService p_hMDNSService) - { - if (p_fnCallback) - { - p_fnCallback(p_hMDNSService); - } - }); -} - -/* - MDNSResponder::addDynamicServiceTxt -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - const char* p_pcValue) -{ - hMDNSTxt hTxt = (_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService) - ? _addServiceTxt(*(clsHost*)p_hMDNSHost, *(stcService*)p_hMDNSService, p_pcKey, p_pcValue, true) - : 0); - DEBUG_EX_ERR(if (!hTxt) DEBUG_OUTPUT.printf_P(PSTR("%s addDynamicServiceTxt: FAILED for '%s=%s'!\n"), _DH(), (p_pcKey ? : "-"), (p_pcValue ? : "-"));); - return hTxt; -} - -/* - MDNSResponder::addDynamicServiceTxt (LEGACY 2) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - const char* p_pcValue) -{ - clsHost* pHost = 0; - return (_validateMDNSHostHandle(p_hMDNSService, &pHost) - ? addDynamicServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_pcValue) - : 0); -} - -/* - MDNSResponder::addDynamicServiceTxt (uint32_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint32_t p_u32Value) -{ - MDNSRESPONDER_U32_TO_CHAR(acBuffer, p_u32Value); - return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): - } - - /* - MDNSResponder::addDynamicServiceTxt (uint32_t) (LEGACY 2) - */ - MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint32_t p_u32Value) -{ - clsHost* pHost = 0; - return (_validateMDNSHostHandle(p_hMDNSService, &pHost) - ? addDynamicServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_u32Value) - : 0); -} - -/* - MDNSResponder::addDynamicServiceTxt (uint16_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint16_t p_u16Value) -{ - MDNSRESPONDER_U16_TO_CHAR(acBuffer, p_u16Value); - return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): - } - - /* - MDNSResponder::addDynamicServiceTxt (uint16_t) (LEGACY 2) - */ - MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint16_t p_u16Value) -{ - clsHost* pHost = 0; - return (_validateMDNSHostHandle(p_hMDNSService, &pHost) - ? addDynamicServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_u16Value) - : 0); -} - -/* - MDNSResponder::addDynamicServiceTxt (uint8_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint8_t p_u8Value) -{ - MDNSRESPONDER_U8_TO_CHAR(acBuffer, p_u8Value); - return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): - } - - /* - MDNSResponder::addDynamicServiceTxt (uint8_t) (LEGACY 2) - */ - MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint8_t p_u8Value) -{ - clsHost* pHost = 0; - return (_validateMDNSHostHandle(p_hMDNSService, &pHost) - ? addDynamicServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_u8Value) - : 0); -} - -/* - MDNSResponder::addDynamicServiceTxt (int32_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - int32_t p_i32Value) -{ - MDNSRESPONDER_I32_TO_CHAR(acBuffer, p_i32Value); - return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): - } - - /* - MDNSResponder::addDynamicServiceTxt (int32_t) (LEGACY 2) - */ - MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint32_t p_i32Value) -{ - clsHost* pHost = 0; - return (_validateMDNSHostHandle(p_hMDNSService, &pHost) - ? addDynamicServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_i32Value) - : 0); -} - -/* - MDNSResponder::addDynamicServiceTxt (int16_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - int16_t p_i16Value) -{ - MDNSRESPONDER_I16_TO_CHAR(acBuffer, p_i16Value); - return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): - } - - /* - MDNSResponder::addDynamicServiceTxt (int16_t) (LEGACY 2) - */ - MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint16_t p_i16Value) -{ - clsHost* pHost = 0; - return (_validateMDNSHostHandle(p_hMDNSService, &pHost) - ? addDynamicServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_i16Value) - : 0); -} - -/* - MDNSResponder::addDynamicServiceTxt (int8_t) -*/ -MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - int8_t p_i8Value) -{ - MDNSRESPONDER_I8_TO_CHAR(acBuffer, p_i8Value); - return addDynamicServiceTxt(p_hMDNSHost, p_hMDNSService, p_pcKey, acBuffer): - } - - /* - MDNSResponder::addDynamicServiceTxt (int8_t) (LEGACY 2) - */ - MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(const MDNSResponder::hMDNSService p_hMDNSService, - const char* p_pcKey, - uint8_t p_i8Value) -{ - clsHost* pHost = 0; - return (_validateMDNSHostHandle(p_hMDNSService, &pHost) - ? addDynamicServiceTxt((hMDNSHost)pHost, p_hMDNSService, p_pcKey, p_i8Value) - : 0); -} - - -/** - STATIC QUERIES -*/ - -/* - 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 MDNSResponder::hMDNSHost p_hMDNSHost, - 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("%s queryService '%s.%s'\n"), _DH(), p_pcService, p_pcProtocol);); - - uint32_t u32Result = 0; - - stcQuery* pMDNSQuery = 0; - if ((_validateMDNSHostHandle(p_hMDNSHost)) && - (p_pcService) && - (os_strlen(p_pcService)) && - (p_pcProtocol) && - (os_strlen(p_pcProtocol)) && - (p_u16Timeout) && - (_removeLegacyQuery()) && - ((pMDNSQuery = _allocQuery(*(clsHost*)p_hMDNSHost, stcQuery::enuQueryType::Service))) && - (_buildDomainForService(p_pcService, p_pcProtocol, pMDNSQuery->m_Domain))) - { - pMDNSQuery->m_bLegacyQuery = true; - - if (_sendMDNSQuery(*(clsHost*)p_hMDNSHost, *pMDNSQuery)) - { - // Wait for answers to arrive - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryService: Waiting %u ms for answers...\n"), _DH(), p_u16Timeout);); - delay(p_u16Timeout); - - // All answers should have arrived by now -> stop adding new answers - pMDNSQuery->m_bAwaitingAnswers = false; - u32Result = pMDNSQuery->answerCount(); - } - else // FAILED to send query - { - _removeQuery(*(clsHost*)p_hMDNSHost, pMDNSQuery); - } - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s queryService: INVALID input data!\n"), _DH());); - } - return u32Result; -} - -/* - MDNSResponder::queryService (LEGACY 2) -*/ -uint32_t MDNSResponder::queryService(const char* p_pcService, - const char* p_pcProtocol, - const uint16_t p_u16Timeout /*= MDNS_QUERYSERVICES_WAIT_TIME*/) -{ - return ((!m_HostList.empty()) - ? queryService((hMDNSHost)m_HostList.front(), p_pcService, p_pcProtocol, p_u16Timeout) - : 0); -} - -/* - MDNSResponder::queryService (LEGACY) -*/ -uint32_t MDNSResponder::queryService(const String& p_strService, - const String& p_strProtocol) -{ - return queryService(p_strService.c_str(), p_strProtocol.c_str()); -} - -/* - MDNSResponder::queryHost - - Perform a (blocking) static host query. - The arrived answers can be queried by calling: - - answerHostName (or 'hostname') - - answerIP (or 'IP') - - answerPort (or 'port') - -*/ -uint32_t MDNSResponder::queryHost(const MDNSResponder::hMDNSHost p_hMDNSHost, - const char* p_pcHostName, - const uint16_t p_u16Timeout /*= MDNS_QUERYSERVICES_WAIT_TIME*/) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost '%s.local'\n"), _DH(), p_pcHostName);); - - uint32_t u32Result = 0; - - stcQuery* pHostQuery = 0; - if ((_validateMDNSHostHandle(p_hMDNSHost)) && - (p_pcHostName) && - (os_strlen(p_pcHostName)) && - (p_u16Timeout) && - (_removeLegacyQuery()) && - ((pHostQuery = _allocQuery(*(clsHost*)p_hMDNSHost, stcQuery::enuQueryType::Host))) && - (_buildDomainForHost(p_pcHostName, pHostQuery->m_Domain))) - { - - pHostQuery->m_bLegacyQuery = true; - - if (_sendMDNSQuery(*(clsHost*)p_hMDNSHost, *pHostQuery)) - { - // Wait for answers to arrive - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost: Waiting %u ms for answers...\n"), _DH(), p_u16Timeout);); - delay(p_u16Timeout); - - // All answers should have arrived by now -> stop adding new answers - pHostQuery->m_bAwaitingAnswers = false; - u32Result = pHostQuery->answerCount(); - } - else // FAILED to send query - { - _removeQuery(*(clsHost*)p_hMDNSHost, pHostQuery); - } - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost: INVALID input data!\n"), _DH());); - } - return u32Result; -} - -/* - queryHost (LEGACY 2) -*/ -uint32_t MDNSResponder::queryHost(const char* p_pcHostName, - const uint16_t p_u16Timeout /*= MDNS_QUERYSERVICES_WAIT_TIME*/) -{ - return ((!m_HostList.empty()) - ? queryHost((hMDNSHost)m_HostList.front(), p_pcService, p_pcProtocol, p_u16Timeout) - : 0); -} - -/* - MDNSResponder::removeQuery - - Remove the last static query (and all answers). - -*/ -bool MDNSResponder::removeQuery(const MDNSResponder::hMDNSHost p_hMDNSHost) -{ - return ((_validateMDNSHostHandle(p_hMDNSHost)) && - (_removeLegacyQuery(*(clsHost*)p_hMDNSHost))); -} - -/* - MDNSResponder::removeQuery (LEGACY 2) -*/ -bool MDNSResponder::removeQuery(void) -{ - return ((!m_HostList.empty()) - ? removeQuery((hMDNSHost)m_HostList.front()) - : false); -} - -/* - MDNSResponder::hasQuery - - Return 'true', if a static query is currently installed - -*/ -bool MDNSResponder::hasQuery(const MDNSResponder::hMDNSHost p_hMDNSHost) -{ - return ((_validateMDNSHostHandle(p_hMDNSHost)) && - (0 != _findLegacyQuery(*(clsHost*)p_hMDNSHost))); -} - -/* - MDNSResponder::hasQuery (LEGACY 2) -*/ -bool MDNSResponder::hasQuery(void) -{ - return ((!m_HostList.empty()) - ? hasQuery((hMDNSHost)m_HostList.front()) - : false); -} - -/* - MDNSResponder::getQuery - - Return handle to the last static query - -*/ -MDNSResponder::hMDNSQuery MDNSResponder::getQuery(const MDNSResponder::hMDNSHost p_hMDNSHost) -{ - return (_validateMDNSHostHandle(p_hMDNSHost) - ? (hMDNSQuery)_findLegacyQuery() - : 0); -} - -/* - MDNSResponder::getQuery (LEGACY 2) -*/ -MDNSResponder::hMDNSQuery MDNSResponder::getQuery(void) -{ - return ((!m_HostList.empty()) - ? getQuery((hMDNSHost)m_HostList.front()) - : false); -} - -/* - MDNSResponder::answerHostName -*/ -const char* MDNSResponder::answerHostName(const MDNSResponder::hMDNSHost p_hMDNSHost, - const uint32_t p_u32AnswerIndex) -{ - const stcQuery* pQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findLegacyQuery(*(clsHost*)p_hMDNSHost) : 0); - stcQuery::stcAnswer* pSQAnswer = (pQuery ? pQuery->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); -} - -/* - MDNSResponder::answerHostName (LEGACY 2) -*/ -const char* MDNSResponder::answerHostName(const uint32_t p_u32AnswerIndex) -{ - return ((!m_HostList.empty()) - ? answerHostName((hMDNSHost)m_HostList.front(), p_u32AnswerIndex) - : 0); -} - -/* - MDNSResponder::hostname (LEGACY) -*/ -String MDNSResponder::hostname(const uint32_t p_u32AnswerIndex) -{ - return String(answerHostName(p_u32AnswerIndex)); -} - -#ifdef MDNS_IPV4_SUPPORT -/* - MDNSResponder::answerIPv4 -*/ -IPAddress MDNSResponder::answerIPv4(const MDNSResponder::hMDNSHost p_hMDNSHost, - const uint32_t p_u32AnswerIndex) -{ - const stcQuery* pQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findLegacyQuery(*(clsHost*)p_hMDNSHost) : 0); - const stcQuery::stcAnswer* pSQAnswer = (pQuery ? pQuery->answerAtIndex(p_u32AnswerIndex) : 0); - const stcQuery::stcAnswer::stcIPv4Address* pIPv4Address = (((pSQAnswer) && (pSQAnswer->m_pIPv4Addresses)) ? pSQAnswer->IPv4AddressAtIndex(0) : 0); - return (pIPv4Address ? pIPv4Address->m_IPAddress : IPAddress()); -} - -/* - MDNSResponder::answerIPv4 (LEGACY 2) -*/ -IPAddress MDNSResponder::answerIPv4(const uint32_t p_u32AnswerIndex) -{ - return ((!m_HostList.empty()) - ? answerIPv4((hMDNSHost)m_HostList.front(), p_u32AnswerIndex) - : IPAddress()); -} - -/* - MDNSResponder::answerIP (LEGACY 2) -*/ -IPAddress MDNSResponder::answerIP(const uint32_t p_u32AnswerIndex) -{ - return answerIPv4(p_u32AnswerIndex); -} - -/* - MDNSResponder::IP (LEGACY) -*/ -IPAddress MDNSResponder::IP(const uint32_t p_u32AnswerIndex) -{ - return answerIPv4(p_u32AnswerIndex); -} -#endif - -#ifdef MDNS_IPV6_SUPPORT -/* - MDNSResponder::answerIPv6 -*/ -IPAddress MDNSResponder::answerIPv6(const MDNSResponder::hMDNSHost p_hMDNSHost, - const uint32_t p_u32AnswerIndex) -{ - const stcQuery* pQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findLegacyQuery(*(clsHost*)p_hMDNSHost) : 0); - const stcQuery::stcAnswer* pSQAnswer = (pQuery ? pQuery->answerAtIndex(p_u32AnswerIndex) : 0); - const stcQuery::stcAnswer::stcIPv6Address* pIPv6Address = (((pSQAnswer) && (pSQAnswer->m_pIPv6Addresses)) ? pSQAnswer->IPv6AddressAtIndex(0) : 0); - return (pIPv6Address ? pIPv6Address->m_IPAddress : IPAddress()); -} - -/* - MDNSResponder::answerIPv6 (LEGACY 2) -*/ -IPAddress MDNSResponder::answerIPv6(const uint32_t p_u32AnswerIndex) -{ - return ((!m_HostList.empty()) - ? answerIPv6((hMDNSHost)m_HostList.front(), p_u32AnswerIndex) - : IPAddress()); -} -#endif - -/* - MDNSResponder::answerPort -*/ -uint16_t MDNSResponder::answerPort(const MDNSResponder::hMDNSHost p_hMDNSHost, - const uint32_t p_u32AnswerIndex) -{ - const stcQuery* pQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findLegacyQuery(*(clsHost*)p_hMDNSHost) : 0); - const stcQuery::stcAnswer* pSQAnswer = (pQuery ? pQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return (pSQAnswer ? pSQAnswer->m_u16Port : 0); -} - -/* - MDNSResponder::answerPort (LEGACY 2) -*/ -uint16_t MDNSResponder::answerPort(const uint32_t p_u32AnswerIndex) -{ - return ((!m_HostList.empty()) - ? answerPort((hMDNSHost)m_HostList.front(), p_u32AnswerIndex) - : 0); -} - -/* - 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 - - answerIPv4Address/answerIPv6Address - - answerPort - - answerTxts - -*/ -MDNSResponder::hMDNSQuery MDNSResponder::installServiceQuery(const MDNSResponder::hMDNSHost p_hMDNSHost, - const char* p_pcService, - const char* p_pcProtocol, - MDNSResponder::MDNSQueryCallbackFn p_fnCallback) -{ - hMDNSQuery hResult = 0; - - stcQuery* pMDNSQuery = 0; - if ((_validateMDNSHostHandle(p_hMDNSHost)) && - (p_pcService) && - (os_strlen(p_pcService)) && - (p_pcProtocol) && - (os_strlen(p_pcProtocol)) && - (p_fnCallback) && - ((pMDNSQuery = _allocQuery(*(clsHost*)p_hMDNSHost, stcQuery::enuQueryType::Service))) && - (_buildDomainForService(p_pcService, p_pcProtocol, pMDNSQuery->m_Domain))) - { - - pMDNSQuery->m_fnCallback = p_fnCallback; - pMDNSQuery->m_bLegacyQuery = false; - - if (_sendMDNSQuery(*(clsHost*)p_hMDNSHost, *pMDNSQuery)) - { - pMDNSQuery->m_u8SentCount = 1; - pMDNSQuery->m_ResendTimeout.reset(MDNS_DYNAMIC_QUERY_RESEND_DELAY); - - hResult = (hMDNSQuery)pMDNSQuery; - } - else - { - _removeQuery(*(clsHost*)p_hMDNSHost, pMDNSQuery); - } - } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s installServiceQuery: %s for '%s.%s'!\n\n"), _DH(), (hResult ? "Succeeded" : "FAILED"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); - DEBUG_EX_ERR(if (!hResult) DEBUG_OUTPUT.printf_P(PSTR("%s installServiceQuery: FAILED for '%s.%s'!\n\n"), _DH(), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); - return hResult; -} - -/* - MDNSResponder::installServiceQuery (LEGACY 2) -*/ -MDNSResponder::hMDNSQuery MDNSResponder::installServiceQuery(const char* p_pcService, - const char* p_pcProtocol, - MDNSResponder::MDNSQueryCallbackFn1 p_fnCallback) -{ - return ((!m_HostList.empty()) - ? installServiceQuery((hMDNSHost)m_HostList.front(), p_pcService, p_pcProtocol, [p_fnCallback])(MDNSResponder * p_pMDNSResponder, - MDNSResponder::hMDNSHost, - const stcAnswerAccessor & p_MDNSAnswerAccessor, - typeQueryAnswerType p_QueryAnswerTypeFlags, - bool p_bSetContent) - { - if (p_fnCallback) - { - p_fnCallback(p_pMDNSResponder, p_MDNSAnswerAccessor, p_QueryAnswerTypeFlags, p_bSetContent); - } - }) - : 0); - } - - /* - MDNSResponder::installServiceQuery (LEGACY 2) - */ - MDNSResponder::hMDNSQuery MDNSResponder::installServiceQuery(const char* p_pcService, - const char* p_pcProtocol, - MDNSResponder::MDNSQueryCallbackFn2 p_fnCallback) -{ - return ((!m_HostList.empty()) - ? installServiceQuery((hMDNSHost)m_HostList.front(), p_pcService, p_pcProtocol, [p_fnCallback])(MDNSResponder*, - MDNSResponder::hMDNSHost, - const stcAnswerAccessor & p_MDNSAnswerAccessor, - typeQueryAnswerType p_QueryAnswerTypeFlags, - bool p_bSetContent) - { - if (p_fnCallback) - { - p_fnCallback(p_MDNSAnswerAccessor, p_QueryAnswerTypeFlags, p_bSetContent); - } - }) - : 0); - } - - /* - MDNSResponder::installHostQuery - */ - MDNSResponder::hMDNSQuery MDNSResponder::installHostQuery(const MDNSResponder::hMDNSHost p_hMDNSHost, - const char* p_pcHostName, - MDNSResponder::MDNSQueryCallbackFn p_fnCallback) -{ - hMDNSQuery hResult = 0; - - if ((_validateMDNSHostHandle(p_hMDNSHost)) && - (p_pcHostName) && - (os_strlen(p_pcHostName))) - { - stcRRDomain domain; - hResult = ((_buildDomainForHost(p_pcHostName, domain)) - ? _installDomainQuery(*(clsHost*)p_hMDNSHost, domain, stcQuery::enuQueryType::Host, p_fnCallback) - : 0); - } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s installHostQuery: %s for '%s.local'!\n\n"), _DH(), (hResult ? "Succeeded" : "FAILED"), (p_pcHostName ? : "-"));); - DEBUG_EX_ERR(if (!hResult) DEBUG_OUTPUT.printf_P(PSTR("%s installHostQuery: FAILED for '%s.local'!\n\n"), _DH(), (p_pcHostName ? : "-"));); - return hResult; -} - -/* - MDNSResponder::installHostQuery (LEGACY 2) -*/ -MDNSResponder::hMDNSQuery MDNSResponder::installHostQuery(const char* p_pcHostName, - MDNSResponder::MDNSQueryCallbackFn1 p_fnCallback) -{ - return installHostQuery(p_pcHostName, [p_fnCallback](MDNSResponder * p_pMDNSResponder, - hMDNSHost, - const stcAnswerAccessor & p_MDNSAnswerAccessor, - typeQueryAnswerType p_QueryAnswerTypeFlags, - bool p_bSetContent) - { - if (p_fnCallback) - { - p_fnCallback(p_pMDNSResponder, p_MDNSAnswerAccessor, p_QueryAnswerTypeFlags, p_bSetContent); - } - }); -} - -/* - MDNSResponder::installHostQuery (LEGACY 2) -*/ -MDNSResponder::hMDNSQuery MDNSResponder::installHostQuery(const char* p_pcHostName, - MDNSResponder::MDNSQueryCallbackFn2 p_fnCallback) -{ - return installHostQuery(p_pcHostName, [p_fnCallback](MDNSResponder*, - hMDNSHost, - const stcAnswerAccessor & p_MDNSAnswerAccessor, - typeQueryAnswerType p_QueryAnswerTypeFlags, - bool p_bSetContent) - { - if (p_fnCallback) - { - p_fnCallback(p_MDNSAnswerAccessor, p_QueryAnswerTypeFlags, p_bSetContent); - } - }); -} - -/* - MDNSResponder::removeQuery - - Remove a dynamic query (and all collected answers) from the MDNS responder - -*/ -bool MDNSResponder::removeQuery(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery) -{ - stcQuery* pMDNSQuery = 0; - bool bResult = ((_validateMDNSHostHandle(p_hMDNSHost)) && - ((pMDNSQuery = _findQuery(*(clsHost*)p_hMDNSHost, p_hQuery))) && - (_removeQuery(*(clsHost*)p_hMDNSHost, pMDNSQuery))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeQuery: FAILED!\n"), _DH());); - return bResult; -} - -/* - MDNSResponder::removeQuery (LEGACY 2) -*/ -bool MDNSResponder::removeQuery(const MDNSResponder::hMDNSQuery p_hMDNSQuery) -{ - return ((!m_HostList.empty()) - ? removeQuery((hMDNSHost)m_HostList.front(), p_hMDNSQuery) - : false); -} - -/* - MDNSResponder::answerAccessors -*/ -MDNSResponder::clsMDNSAnswerAccessorVector MDNSResponder::answerAccessors(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery) -{ - MDNSResponder::clsMDNSAnswerAccessorVector tempVector; - for (uint32_t u = 0; u < answerCount(p_hMDNSHost, p_hMDNSQuery); ++u) - { - tempVector.emplace_back(*this, p_hMDNSQuery, u); - } - return tempVector; -} - -/* - MDNSResponder::answerAccessors (LEGACY 2) -*/ -MDNSResponder::clsMDNSAnswerAccessorVector MDNSResponder::answerAccessors(const MDNSResponder::hMDNSQuery p_hMDNSQuery) -{ - return ((!m_HostList.empty()) - ? answerAccessors((hMDNSHost)m_HostList.front(), p_hMDNSQuery) - : MDNSResponder::clsMDNSAnswerAccessorVector()); -} - -/* - MDNSResponder::answerCount -*/ -uint32_t MDNSResponder::answerCount(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery) : 0); - return (pMDNSQuery ? pMDNSQuery->answerCount() : 0); -} - -/* - MDNSResponder::answerCount (LEGACY 2) -*/ -uint32_t MDNSResponder::answerCount(const MDNSResponder::hMDNSQuery p_hMDNSQuery) -{ - return ((!m_HostList.empty()) - ? answerCount((hMDNSHost)m_HostList.front(), p_hMDNSQuery) - : 0); -} - -/* - MDNSResponder::hasAnswerServiceDomain -*/ -bool MDNSResponder::hasAnswerServiceDomain(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return ((pSQAnswer) && - (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::ServiceDomain))); -} - - /* - MDNSResponder::hasAnswerServiceDomain (LEGACY 2) - */ - bool MDNSResponder::hasAnswerServiceDomain(const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - return ((!m_HostList.empty()) - ? hasAnswerServiceDomain((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) - : false); -} - -/* - 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::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->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::answerServiceDomain (LEGACY 2) -*/ -const char* MDNSResponder::answerServiceDomain(const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - return ((!m_HostList.empty()) - ? answerServiceDomain((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) - : 0); -} - -/* - MDNSResponder::hasAnswerHostDomain -*/ -bool MDNSResponder::hasAnswerHostDomain(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return ((pSQAnswer) && - (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::HostDomain))); -} - - /* - MDNSResponder::hasAnswerHostDomain (LEGACY 2) - */ - bool MDNSResponder::hasAnswerHostDomain(const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - return ((!m_HostList.empty()) - ? hasAnswerHostDomain((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) - : false); -} - -/* - 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::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->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); -} - -/* - MDNSResponder::answerHostDomain (LEGACY 2) -*/ -const char* MDNSResponder::answerHostDomain(const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - return ((!m_HostList.empty()) - ? answerHostDomain((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) - : 0); -} - -#ifdef MDNS_IPV4_SUPPORT -/* - MDNSResponder::hasAnswerIPv4Address -*/ -bool MDNSResponder::hasAnswerIPv4Address(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return ((pSQAnswer) && - (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::IPv4Address))); -} - - /* - MDNSResponder::hasAnswerIPv4Address (LEGACY 2) - */ - bool MDNSResponder::hasAnswerIPv4Address(const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - return ((!m_HostList.empty()) - ? hasAnswerIPv4Address((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) - : false); -} - -/* - MDNSResponder::answerIPv4AddressCount -*/ -uint32_t MDNSResponder::answerIPv4AddressCount(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return (pSQAnswer ? pSQAnswer->IPv4AddressCount() : 0); -} - - /* - MDNSResponder::answerIPv4AddressCount (LEGACY 2) - */ - uint32_t MDNSResponder::answerIPv4AddressCount(const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - return ((!m_HostList.empty()) - ? answerIPv4AddressCount((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) - : 0); -} - -/* - MDNSResponder::answerIPv4Address -*/ -IPAddress MDNSResponder::answerIPv4Address(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex, - const uint32_t p_u32AddressIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - stcQuery::stcAnswer::stcIPv4Address* pIPv4Address = (pSQAnswer ? pSQAnswer->IPv4AddressAtIndex(p_u32AddressIndex) : 0); - return (pIPv4Address ? pIPv4Address->m_IPAddress : IPAddress()); -} - - /* - MDNSResponder::answerIPv4Address (LEGACY 2) - */ - IPAddress MDNSResponder::answerIPv4Address(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex, - const uint32_t p_u32AddressIndex) -{ - return ((!m_HostList.empty()) - ? answerIPv4Address((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex, p_u32AddressIndex) - : IPAddress()); -} -#endif - -#ifdef MDNS_IPV6_SUPPORT -/* - MDNSResponder::hasAnswerIPv6Address -*/ -bool MDNSResponder::hasAnswerIPv6Address(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return ((pSQAnswer) && - (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::IPv6Address))); -} - - /* - MDNSResponder::hasAnswerIPv6Address (LEGACY 2) - */ - bool MDNSResponder::hasAnswerIPv6Address(const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - return ((!m_HostList.empty()) - ? hasAnswerIPv6Address((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) - : false); -} - -/* - MDNSResponder::answerIPv6AddressCount -*/ -uint32_t MDNSResponder::answerIPv6AddressCount(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return (pSQAnswer ? pSQAnswer->IPv6AddressCount() : 0); -} - - /* - MDNSResponder::answerIPv6AddressCount (LEGACY 2) - */ - uint32_t MDNSResponder::answerIPv6AddressCount(const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - return ((!m_HostList.empty()) - ? answerIPv6AddressCount((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) - : 0); -} - -/* - MDNSResponder::answerIPv6Address -*/ -IPAddress MDNSResponder::answerIPv6Address(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex, - const uint32_t p_u32AddressIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - stcQuery::stcAnswer::stcIPv6Address* pIPv6Address = (pSQAnswer ? pSQAnswer->IPv6AddressAtIndex(p_u32AddressIndex) : 0); - return (pIPv6Address ? pIPv6Address->m_IPAddress : IPAddress()); -} - - /* - MDNSResponder::answerIPv6Address (LEGACY 2) - */ - IPAddress MDNSResponder::answerIPv6Address(const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex, - const uint32_t p_u32AddressIndex) -{ - return ((!m_HostList.empty()) - ? answerIPv6Address((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) - : IPAddress()); -} -#endif - -/* - MDNSResponder::hasAnswerPort -*/ -bool MDNSResponder::hasAnswerPort(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return ((pSQAnswer) && - (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::Port))); -} - - /* - MDNSResponder::hasAnswerPort (LEGACY 2) - */ - bool MDNSResponder::hasAnswerPort(const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - return ((!m_HostList.empty()) - ? hasAnswerPort((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) - : false); -} - -/* - MDNSResponder::answerPort -*/ -uint16_t MDNSResponder::answerPort(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return (pSQAnswer ? pSQAnswer->m_u16Port : 0); -} - - /* - MDNSResponder::answerPort (LEGACY 2) - */ - uint16_t MDNSResponder::answerPort(const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - return ((!m_HostList.empty()) - ? answerPort((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) - : 0); -} - -/* - MDNSResponder::hasAnswerTxts -*/ -bool MDNSResponder::hasAnswerTxts(const MDNSResponder::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->answerAtIndex(p_u32AnswerIndex) : 0); - return ((pSQAnswer) && - (pSQAnswer->m_QueryAnswerFlags & static_cast(enuQueryAnswerType::Txts))); -} - - /* - MDNSResponder::hasAnswerTxts (LEGACY 2) - */ - bool MDNSResponder::hasAnswerTxts(const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - return ((!m_HostList.empty()) - ? hasAnswerTxts((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) - : false); -} - -/* - 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::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - stcQuery* pMDNSQuery = (_validateMDNSHostHandle(p_hMDNSHost) ? _findQuery(*(clsHost*)p_hMDNSHost, p_hMDNSQuery); - stcQuery::stcAnswer* pSQAnswer = (pMDNSQuery ? pMDNSQuery->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); -} - -/* - MDNSResponder::answerTxts (LEGACY 2) -*/ -const char* MDNSResponder::answerTxts(const MDNSResponder::hMDNSQuery p_hMDNSQuery, - const uint32_t p_u32AnswerIndex) -{ - return ((!m_HostList.empty()) - ? answerTxts((hMDNSHost)m_HostList.front(), p_hMDNSQuery, p_u32AnswerIndex) - : 0); -} - - -/* - PROBING -*/ - -/* - MDNSResponder::setHostProbeResultCallback - - Set a callback for probe results. The callback is called, when probing - for the host domain failes or succeedes. - In the case of failure, the domain name should be changed via 'setHostName' - When succeeded, the host domain will be announced by the MDNS responder. - -*/ -bool MDNSResponder::setHostProbeResultCallback(const MDNSResponder::hMDNSHost p_hMDNSHost, - MDNSResponder::MDNSHostProbeResultCallbackFn p_fnCallback) -{ - return ((_validateMDNSHostHandle(p_hMDNSHost)) && - (((clsHost*)p_hMDNSHost)->m_HostProbeInformation.m_fnProbeResultCallback = p_fnCallback, true)); -} - -/* - MDNSResponder::setHostProbeResultCallback (LEGACY 2) -*/ -bool MDNSResponder::setHostProbeResultCallback(MDNSResponder::MDNSHostProbeResultCallbackFn1 p_fnCallback) -{ - return setHostProbeResultCallback([p_fnCallback](MDNSResponder * p_pMDNSResponder, - hMDNSHost, - const char* p_pcDomainName, - bool p_bProbeResult) - { - if (p_fnCallback) - { - p_fnCallback(p_pMDNSResponder, p_pcDomainName, p_bProbeResult); - } - }); -} - -/* - MDNSResponder::setHostProbeResultCallback (LEGACY 2) -*/ -bool MDNSResponder::setHostProbeResultCallback(MDNSResponder::MDNSHostProbeResultCallbackFn2 p_fnCallback) -{ - return setHostProbeResultCallback([p_fnCallback](MDNSResponder*, - hMDNSHost, - const char* p_pcDomainName, - bool p_bProbeResult) - { - if (p_fnCallback) - { - p_fnCallback(p_pcDomainName, p_bProbeResult); - } - }); -} - -/* - 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::hMDNSHost p_hMDNSHost, - const MDNSResponder::hMDNSService p_hMDNSService, - MDNSResponder::MDNSServiceProbeResultCallbackFn p_fnCallback) -{ - return ((_validateMDNSHostHandle(p_hMDNSHost, p_hMDNSService)) && - (((stcService*)p_hMDNSService)->m_ProbeInformation.m_fnProbeResultCallback = p_fnCallback, true)); -} - -/* - MDNSResponder::setServiceProbeResultCallback (LEGACY 2) -*/ -bool MDNSResponder::setServiceProbeResultCallback(const MDNSResponder::hMDNSService p_hMDNSService, - MDNSResponder::MDNSServiceProbeResultCallbackFn1 p_fnCallback) -{ - return setServiceProbeResultCallback(p_hMDNSService, [p_fnCallback](MDNSResponder * p_pMDNSResponder, - hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcServiceName, - bool p_bProbeResult) - { - if (p_fnCallback) - { - p_fnCallback(p_pMDNSResponder, p_hMDNSService, p_pcServiceName, p_bProbeResult); - } - }); -} - -/* - MDNSResponder::setServiceProbeResultCallback (LEGACY 2) -*/ -bool MDNSResponder::setServiceProbeResultCallback(const MDNSResponder::hMDNSService p_hMDNSService, - MDNSResponder::MDNSServiceProbeResultCallbackFn2 p_fnCallback) -{ - return setServiceProbeResultCallback(p_hMDNSService, [p_fnCallback](MDNSResponder*, - hMDNSHost, - const hMDNSService p_hMDNSService, - const char* p_pcServiceName, - bool p_bProbeResult) - { - if (p_fnCallback) - { - p_fnCallback(p_hMDNSService, p_pcServiceName, p_bProbeResult); - } - }); -} -#endif - -/* - MISC -*/ - -/* - MDNSResponder::notifyNetIfChange - - 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::notifyNetIfChange(netif* p_pNetIf) -{ - clsHost* pMDNSHost; - return (((pMDNSHost = _findHost(p_pNetIf))) && - (pMDNSHost->restart())); -} - -/* - MDNSResponder::update - - Should be called in every 'loop'. - -*/ -bool MDNSResponder::update(const MDNSResponder::hMDNSHost p_hMDNSHost) -{ - return ((_validateMDNSHostHandle(p_hMDNSHost)) && - (_NRH2Ptr(p_hMDNSHost)->update())); -} - -/* - MDNSResponder::update (convenience) -*/ -bool MDNSResponder::update(void) -{ - bool bResult = true; - for (clsHost* pMDNSHost : m_HostList) - { - if (!pMDNSHost->update()) - { - bResult = false; - } - } - return bResult; -} - -/* - MDNSResponder::announce - - Should be called, if the 'configuration' changes. Mainly this will be changes in the TXT items... -*/ -bool MDNSResponder::announce(const MDNSResponder::hMDNSHost p_hMDNSHost) -{ - return ((_validateMDNSHostHandle(p_hMDNSHost)) && - (_NRH2Ptr(p_hMDNSHost)->announce(true, true))); -} - -/* - MDNSResponder::announce (convenience) -*/ -bool MDNSResponder::announce(void) -{ - bool bResult = true; - for (clsHost* pMDNSHost : m_HostList) - { - if (!pMDNSHost->announce(true, true)) - { - bResult = false; - } - } - return bResult; -} - -/* - MDNSResponder::enableArduino - - Enable the OTA update service. - -*/ -MDNSResponder::hMDNSService MDNSResponder::enableArduino(const MDNSResponder::hMDNSHost p_hMDNSHost, - uint16_t p_u16Port, - bool p_bAuthUpload /*= false*/) -{ - hMDNSService hService = addService(p_hMDNSHost, 0, "arduino", "tcp", p_u16Port); - if (hService) - { - if ((!addServiceTxt(p_hMDNSHost, hService, "tcp_check", "no")) || - (!addServiceTxt(p_hMDNSHost, hService, "ssh_upload", "no")) || - (!addServiceTxt(p_hMDNSHost, hService, "board", STRINGIZE_VALUE_OF(ARDUINO_BOARD))) || - (!addServiceTxt(p_hMDNSHost, hService, "auth_upload", (p_bAuthUpload) ? "yes" : "no"))) - { - - removeService(p_hMDNSHost, hService); - hService = 0; - } - } - return hService; -} - -#ifdef LATER - -/* - MDNSResponder::enableArduino (LEGACY 2) -*/ -MDNSResponder::hMDNSService MDNSResponder::enableArduino(uint16_t p_u16Port, - bool p_bAuthUpload /*= false*/) -{ - hMDNSService hMDNSService = 0; - for (clsHost*& pMDNSHost : m_HostList) - { - hMDNSService hLastMDNSService = enableArduino((hMDNSHost)it, p_u16Port, p_bAuthUpload); - if ((hLastMDNSService) && - (!hMDNSService)) - { - hMDNSService = hLastMDNSService; - } - } - return hMDNSService; -} - -#endif - -} //namespace MDNSImplementation - -} //namespace esp8266 - - diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_APIHelpers.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_APIHelpers.cpp deleted file mode 100755 index 7c144499e4..0000000000 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_APIHelpers.cpp +++ /dev/null @@ -1,354 +0,0 @@ -/* - LEAmDNS2_APIHelpers.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 -#include - -/* - ESP8266mDNS Control.cpp -*/ - -extern "C" { -#include "user_interface.h" -} - -#include "LEAmDNS2_lwIPdefs.h" -#include "LEAmDNS2_Priv.h" - -namespace esp8266 -{ -/* - LEAmDNS -*/ -namespace experimental -{ - -/* - MDNSResponder::_allocUDPContext -*/ -bool MDNSResponder::_allocUDPContext(void) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext\n"), _DH());); - if (_releaseUDPContext()) - { - m_pUDPContext = new UdpContext; - if (m_pUDPContext) - { - m_pUDPContext->ref(); - - ip_set_option(m_pUDPContext->pcb(), SOF_REUSEADDR); - //udp_bind_netif(m_pUDPContext->pcb(), m_pNetIf); - - if (m_pUDPContext->listen(IP_ANY_TYPE, DNS_MQUERY_PORT)) - { - //m_pUDPContext->setMulticastInterface(m_pNetIf); - m_pUDPContext->setMulticastTTL(MDNS_MULTICAST_TTL); - m_pUDPContext->onRx(std::bind(&MDNSResponder::_processUDPInput, this)); - m_pUDPContext->connect(IP_ANY_TYPE, DNS_MQUERY_PORT); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext: Succeeded to alloc UDPContext!\n"), _DH());); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext: FAILED to make UDPContext listening!\n"), _DH());); - _releaseUDPContext(); - } - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext: FAILED to alloc UDPContext!\n"), _DH());); - } - } - DEBUG_EX_ERR(if (!m_pUDPContext) DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext: FAILED!\n"), _DH());); - return (0 != m_pUDPContext); -} - -/* - MDNSResponder::_releaseUDPContext -*/ -bool MDNSResponder::_releaseUDPContext(void) -{ - if (m_pUDPContext) - { - m_pUDPContext->unref(); - m_pUDPContext = 0; - } - return true; -} - -/* - MDNSResponder::_processUDPInput - - Called in SYS context! - -*/ -bool MDNSResponder::_processUDPInput(void) -{ - //DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput\n"), _DH());); - - if (m_pUDPContext->next()) - { - netif* pNetIf = ip_current_input_netif(); - MDNSResponder::clsHost* pHost = 0; - if ((pNetIf) && - ((pHost = _findHost(pNetIf)))) - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: netif:%u,src:%s,dest:%s\n"), _DH(), netif_get_index(pNetIf), IPAddress(ip_current_src_addr()).toString().c_str(), IPAddress(ip_current_dest_addr()).toString().c_str());); - pHost->processUDPInput(/*IPAddress(ip_current_src_addr()), IPAddress(ip_current_dest_addr())*/); - } - else - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Received UDP datagramm for unused netif at index: %u\n"), _DH(), (pNetIf ? netif_get_index(pNetIf) : (-1)));); - } - m_pUDPContext->flush(); - } - return true; -} - -/* - MDNSResponder::_createHost -*/ -MDNSResponder::clsHost* MDNSResponder::_createHost(netif* p_pNetIf) -{ - clsHost* pHost = 0; - - if ((p_pNetIf) && - (!((pHost = _findHost(p_pNetIf)))) && - (m_pUDPContext) && - ((pHost = new clsHost(*p_pNetIf, *m_pUDPContext)))) - { - if (pHost->init()) - { - //pHost->setHostProbeResultCallback(_defaultHostProbeResultCallback); - m_HostList.push_back(pHost); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: FAILED!\n"), _DH());); - _releaseHost(pHost); - pHost = 0; - } - } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: Attaching to netif %s!\n"), _DH(), (pHost ? "succeeded" : "FAILED"));); - return pHost; -} - -/* - MDNSResponder::_releaseHost -*/ -bool MDNSResponder::_releaseHost(MDNSResponder::clsHost* p_pHost) -{ - bool bResult = false; - - if ((p_pHost) && - (m_HostList.end() != std::find(m_HostList.begin(), m_HostList.end(), p_pHost))) - { - // Delete and remove Responder object - delete p_pHost; - m_HostList.remove(p_pHost); - bResult = true; - } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _releaseHost: %s to release netif Responder!\n"), _DH(), (bResult ? "Succeeded" : "FAILED"));); - return bResult; -} - -/* - MDNSResponder::_findHost -*/ -const MDNSResponder::clsHost* MDNSResponder::_findHost(netif* p_pNetIf) const -{ - const clsHost* pResult = 0; - for (const clsHost* pHost : m_HostList) - { - if ((p_pNetIf) && - (&(pHost->m_rNetIf) == p_pNetIf)) - { - pResult = pHost; - break; - } - } - return pResult; -} - -/* - MDNSResponder::_findHost -*/ -MDNSResponder::clsHost* MDNSResponder::_findHost(netif* p_pNetIf) -{ - return (clsHost*)(((const MDNSResponder*)this)->_findHost(p_pNetIf)); -} - -/* - MDNSResponder::_findHost -*/ -const MDNSResponder::clsHost* MDNSResponder::_findHost(const MDNSResponder::hMDNSHost p_hMDNSHost) const -{ - clsHostList::const_iterator it(std::find(m_HostList.begin(), m_HostList.end(), _NRH2Ptr(p_hMDNSHost))); - return ((m_HostList.end() != it) ? *it : 0); -} - -/* - MDNSResponder::_findHost -*/ -MDNSResponder::clsHost* MDNSResponder::_findHost(const MDNSResponder::hMDNSHost p_hMDNSHost) -{ - return (clsHost*)(((const MDNSResponder*)this)->_findHost(p_hMDNSHost)); -} - - -/* - HANDLE HELPERS -*/ - -/* - MDNSResponder::_validateMDNSHostHandle -*/ -bool MDNSResponder::_validateMDNSHostHandle(const hMDNSHost p_hMDNSHost) const -{ - return (0 != _findHost(_NRH2Ptr(p_hMDNSHost))); -} - -/* - MDNSResponder::_validateMDNSHostHandle -*/ -bool MDNSResponder::_validateMDNSHostHandle(const hMDNSHost p_hMDNSHost, - const hMDNSService p_hMDNSService) const -{ - return ((_validateMDNSHostHandle(p_hMDNSHost)) && - (_NRH2Ptr(p_hMDNSHost)->validateService(_SH2Ptr(p_hMDNSService)))); -} - -/* - MDNSResponder::_NRH2Ptr -*/ -MDNSResponder::clsHost* MDNSResponder::_NRH2Ptr(const hMDNSHost p_hMDNSHost) -{ - return (clsHost*)p_hMDNSHost; -} - -/* - MDNSResponder::_NRH2Ptr -*/ -const MDNSResponder::clsHost* MDNSResponder::_NRH2Ptr(const hMDNSHost p_hMDNSHost) const -{ - return (const clsHost*)p_hMDNSHost; -} - -/* - MDNSResponder::_SH2Ptr -*/ -MDNSResponder::clsHost::stcService* MDNSResponder::_SH2Ptr(const hMDNSService p_hMDNSService) -{ - return (clsHost::stcService*)p_hMDNSService; -} - -/* - MDNSResponder::_SH2Ptr -*/ -const MDNSResponder::clsHost::stcService* MDNSResponder::_SH2Ptr(const hMDNSService p_hMDNSService) const -{ - return (const clsHost::stcService*)p_hMDNSService; -} - -/* - MDNSResponder::_begin - - Creates a new netif responder (adding the netif to the multicast groups), - sets up the instance data (hostname, ...) and starts the probing process - -*/ -MDNSResponder::clsHost* MDNSResponder::_begin(const char* p_pcHostName, - netif* p_pNetIf, - MDNSHostProbeResultCallbackFn p_fnCallback) -{ - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _begin(%s, netif: %u)\n"), _DH(), (p_pcHostName ?: "_"), (p_pNetIf ? netif_get_index(p_pNetIf) : 0));); - - clsHost* pHost = 0; - if ((!m_pUDPContext) || - (!p_pNetIf) || - (!((pHost = _createHost(p_pNetIf)))) || - (p_fnCallback ? !setHostProbeResultCallback((hMDNSHost)pHost, p_fnCallback) : false) || - (!pHost->setHostName(p_pcHostName)) || - (!pHost->restart())) - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _begin: FAILED for '%s'!\n"), _DH(), (p_pcHostName ? : "-"));); - } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _begin: %s to init netif with hostname %s!\n"), _DH(), (pHost ? "Succeeded" : "FAILED"), (p_pcHostName ? : "-"));); - return pHost; -} - -/* - MDNSResponder::_close - - The announced host and services are unannounced (by multicasting a goodbye message) - All connected objects and finally the netif Responder is removed. - -*/ -bool MDNSResponder::_close(MDNSResponder::clsHost& p_rHost) -{ - _releaseHost(&p_rHost); // Will call 'delete' on the pHost object! - - return true; -} - - -/* - MISC -*/ - -#if not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_MDNS_RESPONDER - -/* - MDNSResponder::_DH -*/ -const char* MDNSResponder::_DH(const hMDNSHost p_hMDNSHost /*= 0*/) const -{ - static char acBuffer[64]; - - *acBuffer = 0; - if (p_hMDNSHost) - { - sprintf_P(acBuffer, PSTR("[MDNSResponder %s]"), ((WIFI_STA == netif_get_index(&((clsHost*)p_hMDNSHost)->m_rNetIf)) - ? "STA" - : ((WIFI_AP == netif_get_index(&((clsHost*)p_hMDNSHost)->m_rNetIf)) - ? "AP" - : "??"))); - } - else - { - sprintf_P(acBuffer, PSTR("[MDNSResponder]")); - } - return acBuffer; -} - -#endif - - -} // namespace MDNSImplementation - -} // namespace esp8266 diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp new file mode 100644 index 0000000000..1f5f0422d4 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp @@ -0,0 +1,339 @@ +/* + LEAmDNS2_Backbone.cpp + + License (MIT license): + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#include "LEAmDNS2Host.h" + + +namespace esp8266 +{ + + +namespace experimental +{ + + +/* + clsLEAmDNS2_Host::clsBackbone::clsBackbone constructor + +*/ +clsLEAMDNSHost::clsBackbone::clsBackbone(void) + : m_pUDPContext(0), + m_bDelayUDPProcessing(false), + m_u32DelayedDatagrams(0) +{ +} + +/* + clsLEAmDNS2_Host::clsBackbone::clsBackbone destructor + +*/ +clsLEAMDNSHost::clsBackbone::~clsBackbone(void) +{ + _releaseUDPContext(); +} + +/* + clsLEAmDNS2_Host::clsBackbone::init + +*/ +bool clsLEAMDNSHost::clsBackbone::init(void) +{ + return _allocUDPContext(); +} + +/* + clsLEAmDNS2_Host::clsBackbone::addHost + +*/ +UdpContext* clsLEAMDNSHost::clsBackbone::addHost(clsLEAMDNSHost* p_pHost) +{ + UdpContext* pUDPContext = 0; + + if ((m_pUDPContext) && + (p_pHost)) + { + m_HostList.push_back(p_pHost); + pUDPContext = m_pUDPContext; + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s addHost: %s to add host!\n"), _DH(), (pUDPContext ? "Succeeded" : "FAILED"));); + return pUDPContext; +} + +/* + clsLEAmDNS2_Host::clsBackbone::removeHost + +*/ +bool clsLEAMDNSHost::clsBackbone::removeHost(clsLEAMDNSHost* p_pHost) +{ + bool bResult = false; + + if ((p_pHost) && + (m_HostList.end() != std::find(m_HostList.begin(), m_HostList.end(), p_pHost))) + { + // Remove host object + m_HostList.remove(p_pHost); + bResult = true; + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s removeHost: %s to remove host!\n"), _DH(), (bResult ? "Succeeded" : "FAILED"));); + return bResult; +} + + +/* + clsLEAmDNS2_Host::clsBackbone::hostCount + +*/ +size_t clsLEAMDNSHost::clsBackbone::hostCount(void) const +{ + return m_HostList.size(); +} + +/* + clsLEAMDNSHost::clsBackbone::::setDelayUDPProcessing + + When executing _sendMessage, with multiple or larger messages, sometimes the ESP IP stack seems + to need a small delay to get the job done. To allow for this delay, a 'delay' was added after one + send operation. However, while 'taking' this delay, sometimes a UDP datagram is received and + processed (which might cause another send operation or change global states). + To avoid 're-entry-like' problems, UDP processing might be blocked for a short period of time. + +*/ +bool clsLEAMDNSHost::clsBackbone::setDelayUDPProcessing(bool p_bDelayUDPProcessing) +{ + if (m_bDelayUDPProcessing != p_bDelayUDPProcessing) + { + m_bDelayUDPProcessing = p_bDelayUDPProcessing; + + if ((!m_bDelayUDPProcessing) && + (m_u32DelayedDatagrams)) + { + DEBUG_EX_INFO2(if (6 <= m_u32DelayedDatagrams) DEBUG_OUTPUT.printf_P(PSTR("%s setDelayUDPProcessing: Processing %u delayed datagram(s)\n"), _DH(), m_u32DelayedDatagrams);); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s setDelayUDPProcessing: Processing %u delayed datagram(s)\n"), _DH(), m_u32DelayedDatagrams);); + _processUDPInput(); + } + m_u32DelayedDatagrams = 0; + } + return true; +} + +/* + clsLEAmDNS2_Host::clsBackbone::_allocUDPContext + +*/ +bool clsLEAMDNSHost::clsBackbone::_allocUDPContext(void) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext\n"), _DH());); + if (_releaseUDPContext()) + { + m_pUDPContext = new UdpContext; + if (m_pUDPContext) + { + m_pUDPContext->ref(); + + //ip_set_option(m_pUDPContext->pcb(), SOF_REUSEADDR); + //udp_bind_netif(m_pUDPContext->pcb(), m_pNetIf); + + if (m_pUDPContext->listen(IP_ANY_TYPE, DNS_MQUERY_PORT)) + { + // This is NOT the TTL (Time-To-Live) for MDNS records, but the subnet level distance MDNS records should travel. + // 1 sets the subnet distance to 'local', which is default for MDNS. + // (Btw.: 255 would set it to 'as far as possible' -> internet), however, RFC 3171 seems to force 255 instead + const uint8_t c_u8MulticastTTL = 255;//1;//255; + + m_pUDPContext->setMulticastTTL(c_u8MulticastTTL); + m_pUDPContext->onRx(std::bind(&clsLEAMDNSHost::clsBackbone::_processUDPInput, this)); + /* m_pUDPContext->onRx([&](void)->void + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext::onRx Received data!\n"), _DH());); + });*/ + m_pUDPContext->connect(IP_ANY_TYPE, DNS_MQUERY_PORT); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext: Succeeded to alloc UDPContext!\n"), _DH());); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext: FAILED to make UDPContext listening!\n"), _DH());); + _releaseUDPContext(); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext: FAILED to alloc UDPContext!\n"), _DH());); + } + } + DEBUG_EX_ERR(if (!m_pUDPContext) DEBUG_OUTPUT.printf_P(PSTR("%s _allocUDPContext: FAILED!\n"), _DH());); + return (0 != m_pUDPContext); +} + +/* + clsLEAmDNS2_Host::clsBackbone::_releaseUDPContext + +*/ +bool clsLEAMDNSHost::clsBackbone::_releaseUDPContext(void) +{ + if (m_pUDPContext) + { + m_pUDPContext->unref(); + m_pUDPContext = 0; + } + return true; +} + +/* + clsLEAmDNS2_Host::clsBackbone::_processUDPInput + + Called in SYS context! + +*/ +bool clsLEAMDNSHost::clsBackbone::_processUDPInput(void) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput\n"), _DH());); + + bool bResult = true; + + if (!m_bDelayUDPProcessing) + { + DEBUG_EX_INFO(uint32_t u32LoopCounter = 0; IPAddress remoteIPAddr;); + while ((m_pUDPContext) && + (m_pUDPContext->next())) + { + netif* pNetIf = m_pUDPContext->getInputNetif();//ip_current_input_netif(); // Probably changed inbetween!!!! + clsLEAMDNSHost* pHost = 0; + if ((pNetIf) && + ((pHost = _findHost(pNetIf)))) + { + DEBUG_EX_INFO( + if (u32LoopCounter++) + { + DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Multi-Loop (%u)!\n"), _DH(), u32LoopCounter); + if ((remoteIPAddr.isSet()) && + (remoteIPAddr != m_pUDPContext->getRemoteAddress())) + { + DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Changed IP address %s->%s!\n"), _DH(), remoteIPAddr.toString().c_str(), m_pUDPContext->getRemoteAddress().toString().c_str()); + } + } + remoteIPAddr = m_pUDPContext->getRemoteAddress(); + ); + bResult = pHost->_processUDPInput(); + DEBUG_EX_INFO2(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: FAILED to process UDP input!\n"), _DH());); + + DEBUG_EX_ERR(if ((-1) != m_pUDPContext->peek()) DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: !!!! CONTENT LEFT IN UDP BUFFER !!!!\n"), _DH());); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Received UDP datagramm for unused netif at index: %u\n"), _DH(), (pNetIf ? netif_get_index(pNetIf) : (-1)));); + } + m_pUDPContext->flush(); + } + } + else + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Delaying datagram!\n"), _DH());); + ++m_u32DelayedDatagrams; + } + return bResult; +} + +/* + clsLEAmDNS2_Host::clsBackbone::_findHost +*/ +const clsLEAMDNSHost* clsLEAMDNSHost::clsBackbone::_findHost(netif* p_pNetIf) const +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _findHost\n"), _DH());); + const clsLEAMDNSHost* pResult = 0; + for (const clsLEAMDNSHost* pHost : m_HostList) + { + if ((p_pNetIf) && + (pHost->m_pNetIf == p_pNetIf)) + { + pResult = pHost; + break; + } + } + return pResult; +} + +/* + MDNSResponder::_findHost +*/ +clsLEAMDNSHost* clsLEAMDNSHost::clsBackbone::_findHost(netif* p_pNetIf) +{ + return (clsLEAMDNSHost*)(((const clsLEAMDNSHost::clsBackbone*)this)->_findHost(p_pNetIf)); +} + + +/* + MISC +*/ + +#if not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_MDNS_RESPONDER + +/* + clsLEAmDNS2_Host::clsBackbone::_DH +*/ +const char* clsLEAMDNSHost::clsBackbone::_DH(void) const +{ + static char acBuffer[24]; + + *acBuffer = 0; + sprintf_P(acBuffer, PSTR("[mDNS::backbone]")); + + return acBuffer; +} + +#endif + +#if LWIP_VERSION_MAJOR == 1 + +/* + netif_get_by_index + + Extracted (and slightly changed) from: https://github.com/yarrick/lwip/blob/master/src/core/netif.c +*/ +struct netif* netif_get_by_index(u8_t idx) +{ + struct netif *netif; + + //LWIP_ASSERT_CORE_LOCKED(); + + if (idx != 0) // <- NETIF_NO_INDEX + { + for ((netif) = netif_list; (netif) != NULL; (netif) = (netif)->next) // <- NETIF_FOREACH(netif) + { + if (idx == netif_get_index(netif)) + { + return netif; /* found! */ + } + } + } + + return NULL; +} + +#endif // LWIP_VERSION_MAJOR == 1 + + +} // namespace MDNSImplementation + + +} // namespace esp8266 diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Host.cpp deleted file mode 100755 index b9df26ce8f..0000000000 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Host.cpp +++ /dev/null @@ -1,1336 +0,0 @@ -/* - LEAmDNS2_Host.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 -#include -#include - -/* - ESP8266mDNS Control.cpp -*/ - -extern "C" { -#include "user_interface.h" -} - -#include "LEAmDNS2_lwIPdefs.h" -#include "LEAmDNS2_Priv.h" - -#ifdef MDNS_IPV4_SUPPORT -#include -#endif -#ifdef MDNS_IPV6_SUPPORT -#include -#endif - - -namespace esp8266 -{ - -/* - LEAmDNS -*/ -namespace experimental -{ - -/* - MDNSResponder::clsHost::clsHost constructor -*/ -MDNSResponder::clsHost::clsHost(netif& p_rNetIf, - UdpContext& p_rUDPContext) - : m_rNetIf(p_rNetIf), - m_NetIfState(static_cast(enuNetIfState::None)), - m_rUDPContext(p_rUDPContext), - m_pcHostName(0), - m_pcInstanceName(0), - m_pServices(0), - m_pQueries(0), - m_fnServiceTxtCallback(0), - m_HostProbeInformation() -{ -} - -/* - MDNSResponder::clsHost::~clsHost destructor -*/ -MDNSResponder::clsHost::~clsHost(void) -{ - _close(); -} - -/* - MDNSResponder::clsHost::init -*/ -bool MDNSResponder::clsHost::init(void) -{ - bool bResult = true; - - // Join multicast group(s) -#ifdef MDNS_IPV4_SUPPORT - ip_addr_t multicast_addr_V4 = DNS_MQUERY_IPV4_GROUP_INIT; - if (!(m_rNetIf.flags & NETIF_FLAG_IGMP)) - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: Setting flag: flags & NETIF_FLAG_IGMP\n"), _DH());); - m_rNetIf.flags |= NETIF_FLAG_IGMP; - - if (ERR_OK != igmp_start(&m_rNetIf)) - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: igmp_start FAILED!\n"), _DH());); - } - } - - bResult = ((bResult) && - (ERR_OK == igmp_joingroup_netif(&m_rNetIf, ip_2_ip4(&multicast_addr_V4)))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: igmp_joingroup_netif(%s) FAILED!\n"), _DH(), IPAddress(multicast_addr_V4).toString().c_str());); -#endif - -#ifdef MDNS_IPV6_SUPPORT - ip_addr_t multicast_addr_V6 = DNS_MQUERY_IPV6_GROUP_INIT; - bResult = ((bResult) && - (ERR_OK == mld6_joingroup_netif(&m_rNetIf, ip_2_ip6(&multicast_addr_V6)))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: mld6_joingroup_netif FAILED!\n"), _DH());); -#endif - return bResult; -} - -/* - MDNSResponder::clsHost::setHostName -*/ -bool MDNSResponder::clsHost::setHostName(const char* p_pcHostName) -{ - bool bResult; - if ((bResult = _allocHostName(p_pcHostName))) - { - m_HostProbeInformation.m_ProbingStatus = enuProbingStatus::ReadyToStart; - - // Replace 'auto-set' service names - for (stcService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) - { - if ((pService->m_bAutoName) && - (!m_pcInstanceName)) - { - bResult = pService->setName(p_pcHostName); - pService->m_ProbeInformation.m_ProbingStatus = enuProbingStatus::ReadyToStart; - } - } - } - return bResult; -} - -/* - MDNSResponder::clsHost::setHostName -*/ -const char* MDNSResponder::clsHost::hostName(void) const -{ - return m_pcHostName; -} - -/* - MDNSResponder::clsHost::setHostProbeResultCallback -*/ -bool MDNSResponder::clsHost::setHostProbeResultCallback(HostProbeResultCallbackFn p_fnCallback) -{ - m_HostProbeInformation.m_fnProbeResultCallback = p_fnCallback; - return true; -} - -/* - MDNSResponder::clsHost::probeStatus -*/ -bool MDNSResponder::clsHost::probeStatus(void) const -{ - return (enuProbingStatus::Done == m_HostProbeInformation.m_ProbingStatus); -} - - -/* - SERVICE -*/ - -/* - MDNSResponder::clsHost::setInstanceName -*/ -bool MDNSResponder::clsHost::setInstanceName(const char* p_pcInstanceName) -{ - bool bResult; - if ((bResult = _allocInstanceName(p_pcInstanceName))) - { - // Replace 'auto-set' service names - for (stcService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) - { - if (pService->m_bAutoName) - { - bResult = pService->setName(p_pcInstanceName); - pService->m_ProbeInformation.m_ProbingStatus = enuProbingStatus::ReadyToStart; - } - } - } - return bResult; -} - -/* - MDNSResponder::clsHost::instanceName -*/ -const char* MDNSResponder::clsHost::instanceName(void) const -{ - return m_pcInstanceName; -} - -/* - MDNSResponder::clsHost::addService -*/ -MDNSResponder::clsHost::stcService* MDNSResponder::clsHost::addService(const char* p_pcInstanceName, - const char* p_pcServiceType, - const char* p_pcProtocol, - uint16_t p_u16Port) -{ - stcService* pService = 0; - - if (((!p_pcInstanceName) || // NO name OR - (MDNS_DOMAIN_LABEL_MAXLENGTH >= os_strlen(p_pcInstanceName))) && // Fitting name - (p_pcServiceType) && - (MDNS_SERVICE_NAME_LENGTH >= os_strlen(p_pcServiceType)) && - (p_pcProtocol) && - ((MDNS_SERVICE_PROTOCOL_LENGTH - 1) != os_strlen(p_pcProtocol)) && - (p_u16Port)) - { - if (!((pService = findService((p_pcInstanceName ? : (m_pcInstanceName ? : m_pcHostName)), p_pcServiceType, p_pcProtocol, p_u16Port)))) // Not already used - { - if (0 != (pService = _allocService(p_pcInstanceName, p_pcServiceType, p_pcProtocol, p_u16Port))) - { - // Init probing - pService->m_ProbeInformation.m_ProbingStatus = enuProbingStatus::ReadyToStart; - } - } - } // else: bad arguments - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s addService: %s to add service '%s.%s.%s.local'!\n"), _DH(pService), (pService ? "Succeeded" : "FAILED"), (p_pcInstanceName ? : (m_pcInstanceName ? : (m_pcHostName ? : "-"))), (p_pcServiceType ? : ""), (p_pcProtocol ? : ""));); - DEBUG_EX_ERR(if (!pService) DEBUG_OUTPUT.printf_P(PSTR("%s addService: FAILED to add service '%s.%s.%s.local'!\n"), _DH(pService), (p_pcInstanceName ? : (m_pcInstanceName ? : (m_pcHostName ? : "-"))), (p_pcServiceType ? : ""), (p_pcProtocol ? : ""));); - return pService; -} - -/* - MDNSResponder::clsHost::removeService -*/ -bool MDNSResponder::clsHost::removeService(MDNSResponder::clsHost::stcService* p_pMDNSService) -{ - bool bResult = ((p_pMDNSService) && - (_announceService(*p_pMDNSService, false)) && - (_releaseService(p_pMDNSService))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _removeService: FAILED!\n"), _DH(p_pMDNSService));); - return bResult; -} - -/* - MDNSResponder::clsHost::findService (const) -*/ -const MDNSResponder::clsHost::stcService* MDNSResponder::clsHost::findService(const char* p_pcInstanceName, - const char* p_pcServiceType, - const char* p_pcProtocol, - uint16_t p_u16Port /*= 0*/) const -{ - stcService* pService = m_pServices; - while (pService) - { - if ((0 == strcmp(pService->m_pcName, p_pcInstanceName)) && - (0 == strcmp(pService->m_pcServiceType, p_pcServiceType)) && - (0 == strcmp(pService->m_pcProtocol, p_pcProtocol)) && - ((!p_u16Port) || - (p_u16Port == pService->m_u16Port))) - { - - break; - } - pService = pService->m_pNext; - } - return pService; -} - -/* - MDNSResponder::clsHost::findService -*/ -MDNSResponder::clsHost::stcService* MDNSResponder::clsHost::findService(const char* p_pcInstanceName, - const char* p_pcServiceType, - const char* p_pcProtocol, - uint16_t p_u16Port /*= 0*/) -{ - return (stcService*)((const clsHost*)this)->findService(p_pcInstanceName, p_pcServiceType, p_pcProtocol, p_u16Port); -} - -/* - MDNSResponder::clsHost::validateService -*/ -bool MDNSResponder::clsHost::validateService(const MDNSResponder::clsHost::stcService* p_pService) const -{ - const stcService* pService = m_pServices; - while (pService) - { - if (pService == p_pService) - { - break; - } - pService = pService->m_pNext; - } - return (0 != pService); -} - -/* - MDNSResponder::clsHost::setServiceName -*/ -bool MDNSResponder::clsHost::setServiceName(MDNSResponder::clsHost::stcService* p_pMDNSService, - const char* p_pcInstanceName) -{ - p_pcInstanceName = p_pcInstanceName ? : m_pcInstanceName; - - bool bResult = ((p_pMDNSService) && - ((!p_pcInstanceName) || - (MDNS_DOMAIN_LABEL_MAXLENGTH >= os_strlen(p_pcInstanceName))) && - (p_pMDNSService->setName(p_pcInstanceName)) && - ((p_pMDNSService->m_ProbeInformation.m_ProbingStatus = enuProbingStatus::ReadyToStart), true)); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _setServiceName: FAILED for '%s'!\n"), _DH(p_pMDNSService), (p_pcInstanceName ? : "-"));); - return bResult; -} - -/* - MDNSResponder::clsHost::setServiceName -*/ -const char* MDNSResponder::clsHost::serviceName(const stcService* p_pMDNSService) const -{ - return ((p_pMDNSService) - ? (p_pMDNSService->m_pcName) - : 0); -} - -/* - MDNSResponder::clsHost::serviceType -*/ -const char* MDNSResponder::clsHost::serviceType(const stcService* p_pMDNSService) const -{ - return ((p_pMDNSService) - ? (p_pMDNSService->m_pcServiceType) - : 0); -} - -/* - MDNSResponder::clsHost::serviceProtocol -*/ -const char* MDNSResponder::clsHost::serviceProtocol(const stcService* p_pMDNSService) const -{ - return ((p_pMDNSService) - ? (p_pMDNSService->m_pcProtocol) - : 0); -} - -/* - MDNSResponder::clsHost::servicePort -*/ -uint16_t MDNSResponder::clsHost::servicePort(const stcService* p_pMDNSService) const -{ - return ((p_pMDNSService) - ? (p_pMDNSService->m_u16Port) - : 0); -} - - -/* - MDNSResponder::clsHost::setServiceProbeResultCallback -*/ -bool MDNSResponder::clsHost::setServiceProbeResultCallback(stcService* p_pMDNSService, - ServiceProbeResultCallbackFn p_fnCallback) -{ - return ((p_pMDNSService) - ? ((p_pMDNSService->m_ProbeInformation.m_fnProbeResultCallback = p_fnCallback), true) - : false); -} - -/* - MDNSResponder::clsHost::setServiceName -*/ -bool MDNSResponder::clsHost::serviceProbeStatus(const stcService* p_pMDNSService) const -{ - return ((p_pMDNSService) && - (enuProbingStatus::Done == p_pMDNSService->m_ProbeInformation.m_ProbingStatus)); -} - - -/* - SERVICE TXT -*/ - -/* - MDNSResponder::clsHost::addServiceTxt -*/ -MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::addServiceTxt(stcService* p_pMDNSService, - const char* p_pcKey, - const char* p_pcValue) -{ - return _addServiceTxt(p_pMDNSService, p_pcKey, p_pcValue, false); -} - -/* - MDNSResponder::clsHost::removeServiceTxt -*/ -bool MDNSResponder::clsHost::removeServiceTxt(stcService* p_pMDNSService, - stcServiceTxt* p_pTxt) -{ - bool bResult = ((p_pMDNSService) && - (p_pTxt) && - (_releaseServiceTxt(p_pMDNSService, p_pTxt))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeServiceTxt: FAILED!\n"), _DH(p_pMDNSService));); - return bResult; -} - -/* - MDNSResponder::clsHost::findServiceTxt (const) -*/ -const MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::findServiceTxt(MDNSResponder::clsHost::stcService* p_pMDNSService, - const char* p_pcKey) const -{ - return (const stcServiceTxt*)((const clsHost*)this)->findServiceTxt(p_pMDNSService, p_pcKey); -} - -/* - MDNSResponder::clsHost::findServiceTxt -*/ -MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::findServiceTxt(MDNSResponder::clsHost::stcService* p_pMDNSService, - const char* p_pcKey) -{ - return _findServiceTxt(p_pMDNSService, p_pcKey); -} - -/* - MDNSResponder::clsHost::setDynamicServiceTxtCallback -*/ -bool MDNSResponder::clsHost::setDynamicServiceTxtCallback(DynamicServiceTxtCallbackFn p_fnCallback) -{ - m_fnServiceTxtCallback = p_fnCallback; - return true; -} - -/* - MDNSResponder::clsHost::setDynamicServiceTxtCallback -*/ -bool MDNSResponder::clsHost::setDynamicServiceTxtCallback(stcService* p_pMDNSService, - DynamicServiceTxtCallbackFn p_fnCallback) -{ - return ((p_pMDNSService) - ? ((p_pMDNSService->m_fnTxtCallback = p_fnCallback), true) - : false); -} - -/* - MDNSResponder::clsHost::addDynamicServiceTxt - - 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). -*/ -MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::addDynamicServiceTxt(stcService* p_pMDNSService, - const char* p_pcKey, - const char* p_pcValue) -{ - return _addServiceTxt(p_pMDNSService, p_pcKey, p_pcValue, true); -} - - -/* - QUERIES -*/ -/* - MDNSResponder::clsHost::queryService -*/ -uint32_t MDNSResponder::clsHost::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("%s queryService '_%s._%s.local'\n"), _DH(), p_pcService, p_pcProtocol);); - - stcQuery* pMDNSQuery = 0; - if ((p_pcService) && (*p_pcService) && - (p_pcProtocol) && (*p_pcProtocol) && - (p_u16Timeout) && - ((pMDNSQuery = _allocQuery(stcQuery::enuQueryType::Service))) && - (_buildDomainForService(p_pcService, p_pcProtocol, pMDNSQuery->m_Domain))) - { - if ((_removeLegacyQuery()) && - ((pMDNSQuery->m_bLegacyQuery = true)) && - (_sendMDNSQuery(*pMDNSQuery))) - { - // Wait for answers to arrive - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryService: Waiting %u ms for answers...\n"), _DH(), p_u16Timeout);); - delay(p_u16Timeout); - - // All answers should have arrived by now -> stop adding new answers - pMDNSQuery->m_bAwaitingAnswers = false; - } - else // FAILED to send query - { - _removeQuery(pMDNSQuery); - } - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s queryService: INVALID input data!\n"), _DH());); - } - return ((pMDNSQuery) - ? pMDNSQuery->answerCount() - : 0); -} - -/* - MDNSResponder::clsHost::queryHost -*/ -uint32_t MDNSResponder::clsHost::queryHost(const char* p_pcHostName, - const uint16_t p_u16Timeout /*= MDNS_QUERYSERVICES_WAIT_TIME*/) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost '%s.local'\n"), _DH(), p_pcHostName);); - - stcQuery* pMDNSQuery = 0; - if ((p_pcHostName) && (*p_pcHostName) && - (p_u16Timeout) && - ((pMDNSQuery = _allocQuery(stcQuery::enuQueryType::Host))) && - (_buildDomainForHost(p_pcHostName, pMDNSQuery->m_Domain))) - { - if ((_removeLegacyQuery()) && - ((pMDNSQuery->m_bLegacyQuery = true)) && - (_sendMDNSQuery(*pMDNSQuery))) - { - // Wait for answers to arrive - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost: Waiting %u ms for answers...\n"), _DH(), p_u16Timeout);); - delay(p_u16Timeout); - - // All answers should have arrived by now -> stop adding new answers - pMDNSQuery->m_bAwaitingAnswers = false; - } - else // FAILED to send query - { - _removeQuery(pMDNSQuery); - } - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost: INVALID input data!\n"), _DH());); - } - return ((pMDNSQuery) - ? pMDNSQuery->answerCount() - : 0); -} - -/* - MDNSResponder::clsHost::removeQuery -*/ -bool MDNSResponder::clsHost::removeQuery(void) -{ - return _removeLegacyQuery(); -} - -/* - MDNSResponder::clsHost::hasQuery -*/ -bool MDNSResponder::clsHost::hasQuery(void) -{ - return (0 != _findLegacyQuery()); -} - -/* - MDNSResponder::clsHost::getQuery -*/ -MDNSResponder::clsHost::stcQuery* MDNSResponder::clsHost::getQuery(void) -{ - return _findLegacyQuery(); -} - -/* - MDNSResponder::clsHost::installServiceQuery -*/ -MDNSResponder::clsHost::stcQuery* MDNSResponder::clsHost::installServiceQuery(const char* p_pcService, - const char* p_pcProtocol, - MDNSResponder::clsHost::QueryCallbackFn p_fnCallback) -{ - stcQuery* pMDNSQuery = 0; - if ((p_pcService) && (*p_pcService) && - (p_pcProtocol) && (*p_pcProtocol) && - (p_fnCallback) && - ((pMDNSQuery = _allocQuery(stcQuery::enuQueryType::Service))) && - (_buildDomainForService(p_pcService, p_pcProtocol, pMDNSQuery->m_Domain))) - { - - pMDNSQuery->m_fnCallback = p_fnCallback; - pMDNSQuery->m_bLegacyQuery = false; - - if (_sendMDNSQuery(*pMDNSQuery)) - { - pMDNSQuery->m_u8SentCount = 1; - pMDNSQuery->m_ResendTimeout.reset(MDNS_DYNAMIC_QUERY_RESEND_DELAY); - } - else - { - _removeQuery(pMDNSQuery); - } - } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s installServiceQuery: %s for '_%s._%s.local'!\n\n"), _DH(), (pMDNSQuery ? "Succeeded" : "FAILED"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); - DEBUG_EX_ERR(if (!pMDNSQuery) DEBUG_OUTPUT.printf_P(PSTR("%s installServiceQuery: FAILED for '_%s._%s.local'!\n\n"), _DH(), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); - return pMDNSQuery; -} - -/* - MDNSResponder::clsHost::installHostQuery -*/ -MDNSResponder::clsHost::stcQuery* MDNSResponder::clsHost::installHostQuery(const char* p_pcHostName, - MDNSResponder::clsHost::QueryCallbackFn p_fnCallback) -{ - stcQuery* pMDNSQuery = 0; - if ((p_pcHostName) && (*p_pcHostName)) - { - stcRRDomain domain; - pMDNSQuery = ((_buildDomainForHost(p_pcHostName, domain)) - ? _installDomainQuery(domain, stcQuery::enuQueryType::Host, p_fnCallback) - : 0); - } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s installHostQuery: %s for '%s.local'!\n\n"), _DH(), (pMDNSQuery ? "Succeeded" : "FAILED"), (p_pcHostName ? : "-"));); - DEBUG_EX_ERR(if (!pMDNSQuery) DEBUG_OUTPUT.printf_P(PSTR("%s installHostQuery: FAILED for '%s.local'!\n\n"), _DH(), (p_pcHostName ? : "-"));); - return pMDNSQuery; -} - -/* - MDNSResponder::clsHost::removeQuery -*/ -bool MDNSResponder::clsHost::removeQuery(MDNSResponder::clsHost::stcQuery* p_pMDNSQuery) -{ - bool bResult = ((p_pMDNSQuery) && - (_removeQuery(p_pMDNSQuery))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeQuery: FAILED!\n"), _DH());); - return bResult; -} - - -/* - PROCESSING -*/ - -/* - MDNSResponder::clsHost::processUDPInput -*/ -bool MDNSResponder::clsHost::processUDPInput() -{ - bool bResult = false; - - bResult = ((_checkNetIfState()) && // Any changes in the netif state? - (_parseMessage())); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR(""));); - - return bResult; -} - -/* - MDNSResponder::clsHost::update -*/ -bool MDNSResponder::clsHost::update(void) -{ - return ((_checkNetIfState()) && // Any changes in the netif state? - (_updateProbeStatus()) && // Probing - (_checkQueryCache())); -} - -/* - MDNSResponder::clsHost::restart -*/ -bool MDNSResponder::clsHost::restart(void) -{ - return (_resetProbeStatus(true)); // Stop and restart probing -} - - - - - -/* - P R O T E C T E D -*/ - -/* - MDNSResponder::clsHost::_close -*/ -bool MDNSResponder::clsHost::_close(void) -{ - /* _resetProbeStatus(false); // Stop probing - - _releaseQueries(); - _releaseServices(); - _releaseHostName();*/ - - // Leave multicast group(s) -#ifdef MDNS_IPV4_SUPPORT - ip_addr_t multicast_addr_V4 = DNS_MQUERY_IPV4_GROUP_INIT; - if (ERR_OK != igmp_leavegroup_netif(&m_rNetIf, ip_2_ip4(&multicast_addr_V4)/*(const struct ip4_addr *)&multicast_addr_V4.u_addr.ip4*/)) - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("\n"));); - } -#endif -#ifdef MDNS_IPV6_SUPPORT - ip_addr_t multicast_addr_V6 = DNS_MQUERY_IPV6_GROUP_INIT; - if (ERR_OK != mld6_leavegroup_netif(&m_rNetIf, ip_2_ip6(&multicast_addr_V6)/*&(multicast_addr_V6.u_addr.ip6)*/)) - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("\n"));); - } -#endif - return true; -} - - -/* - NETIF -*/ - -/* - MDNSResponder::clsHost::_getNetIfState - - Returns the current netif state. - -*/ -MDNSResponder::clsHost::typeNetIfState MDNSResponder::clsHost::_getNetIfState(void) const -{ - typeNetIfState curNetIfState = static_cast(enuNetIfState::None); - - if (netif_is_up(&m_rNetIf)) - { - curNetIfState |= static_cast(enuNetIfState::IsUp); - - // Check if netif link is up - if ((netif_is_link_up(&m_rNetIf)) && - ((&m_rNetIf != netif_get_by_index(WIFI_STA)) || - (STATION_GOT_IP == wifi_station_get_connect_status()))) - { - curNetIfState |= static_cast(enuNetIfState::LinkIsUp); - } - - // Check for IPv4 address - if (_getResponderIPAddress(enuIPProtocolType::V4).isSet()) - { - curNetIfState |= static_cast(enuNetIfState::IPv4); - } - // Check for IPv6 address - if (_getResponderIPAddress(enuIPProtocolType::V6).isSet()) - { - curNetIfState |= static_cast(enuNetIfState::IPv6); - } - } - return curNetIfState; -} - -/* - MDNSResponder::clsHost::_checkNetIfState - - Checks the netif state. - If eg. a new address appears, the announcing is restarted. - -*/ -bool MDNSResponder::clsHost::_checkNetIfState(void) -{ - typeNetIfState curNetIfState; - if (m_NetIfState != ((curNetIfState = _getNetIfState()))) - { - // Some state change happened - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: DID CHANGE NETIF STATE\n\n"), _DH());); - DEBUG_EX_INFO( - if ((curNetIfState & static_cast(enuNetIfState::IsUp)) != (m_NetIfState & static_cast(enuNetIfState::IsUp))) - { - DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Netif is up: %s\n"), _DH(), ((curNetIfState & static_cast(enuNetIfState::IsUp)) ? "YES" : "NO")); - } - if ((curNetIfState & static_cast(enuNetIfState::LinkIsUp)) != (m_NetIfState & static_cast(enuNetIfState::LinkIsUp))) - { - DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Netif link is up: %s\n"), _DH(), ((curNetIfState & static_cast(enuNetIfState::LinkIsUp)) ? "YES" : "NO")); - } - if ((curNetIfState & static_cast(enuNetIfState::IPv4)) != (m_NetIfState & static_cast(enuNetIfState::IPv4))) - { - DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IPv4 address is set: %s\n"), _DH(), ((curNetIfState & static_cast(enuNetIfState::IPv4)) ? "YES" : "NO")); - if (curNetIfState & static_cast(enuNetIfState::IPv4)) - { - DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IPv4 address: %s\n"), _DH(), _getResponderIPAddress(enuIPProtocolType::V4).toString().c_str()); - } - } - if ((curNetIfState & static_cast(enuNetIfState::IPv6)) != (m_NetIfState & static_cast(enuNetIfState::IPv6))) - { - DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IPv6 address is set: %s\n"), _DH(), ((curNetIfState & static_cast(enuNetIfState::IPv6)) ? "YES" : "NO")); - if (curNetIfState & static_cast(enuNetIfState::IPv6)) - { - DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IPv6 address: %s\n"), _DH(), _getResponderIPAddress(enuIPProtocolType::V6).toString().c_str()); - } - } - ); - - if ((curNetIfState & static_cast(enuNetIfState::LinkMask)) != (m_NetIfState & static_cast(enuNetIfState::LinkMask))) - { - // Link came up (restart() will alloc a m_pUDPContext, ...) or down (_restart() will remove an existing m_pUDPContext, ...) - restart(); - } - else if (curNetIfState & static_cast(enuNetIfState::LinkIsUp)) - { - // Link is up (unchanged) - if ((curNetIfState & static_cast(enuNetIfState::IPMask)) != (m_NetIfState & static_cast(enuNetIfState::IPMask))) - { - // IP state changed - // TODO: If just a new IP address was added, a simple re-announcement should be enough - restart(); - } - } - /* if (enuProbingStatus::Done == m_HostProbeInformation.m_ProbingStatus) { - // Probing is done, prepare to (re)announce host - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Preparing to (re)announce host.\n"));); - //m_HostProbeInformation.m_ProbingStatus = enuProbingStatus::Done; - m_HostProbeInformation.m_u8SentCount = 0; - m_HostProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); - }*/ - m_NetIfState = curNetIfState; - } - - bool bResult = ((curNetIfState & static_cast(enuNetIfState::LinkMask)) && // Continue if Link is UP - (curNetIfState & static_cast(enuNetIfState::IPMask))); // AND has any IP - //DEBUG_EX_INFO(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Link is DOWN or NO IP address!\n"), _DH());); - return bResult; -} - - -/* - DOMAIN NAMES -*/ - -/* - MDNSResponder::clsHost::_allocDomainName -*/ -bool MDNSResponder::clsHost::_allocDomainName(const char* p_pcNewDomainName, - char*& p_rpcDomainName) -{ - bool bResult = false; - - _releaseDomainName(p_rpcDomainName); - - size_t stLength = 0; - if ((p_pcNewDomainName) && - (MDNS_DOMAIN_LABEL_MAXLENGTH >= (stLength = strlen(p_pcNewDomainName)))) // char max size for a single label - { - // Copy in hostname characters as lowercase - if ((bResult = (0 != (p_rpcDomainName = new char[stLength + 1])))) - { -#ifdef MDNS_FORCE_LOWERCASE_HOSTNAME - size_t i = 0; - for (; i < stLength; ++i) - { - p_rpcDomainName[i] = (isupper(p_pcNewDomainName[i]) ? tolower(p_pcNewDomainName[i]) : p_pcNewDomainName[i]); - } - p_rpcDomainName[i] = 0; -#else - strncpy(p_rpcDomainName, p_pcNewDomainName, (stLength + 1)); -#endif - } - } - return bResult; -} - -/* - MDNSResponder::clsHost::_releaseDomainName -*/ -bool MDNSResponder::clsHost::_releaseDomainName(char*& p_rpcDomainName) -{ - bool bResult; - if ((bResult = (0 != p_rpcDomainName))) - { - delete[] p_rpcDomainName; - p_rpcDomainName = 0; - } - return bResult; -} - -/* - MDNSResponder::clsHost::_allocHostName -*/ -bool MDNSResponder::clsHost::_allocHostName(const char* p_pcHostName) -{ - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocHostName (%s)\n"), _DH(), p_pcHostName);); - return _allocDomainName(p_pcHostName, m_pcHostName); -} - -/* - MDNSResponder::clsHost::_releaseHostName -*/ -bool MDNSResponder::clsHost::_releaseHostName(void) -{ - return _releaseDomainName(m_pcHostName); -} - -/* - MDNSResponder::clsHost::_allocInstanceName -*/ -bool MDNSResponder::clsHost::_allocInstanceName(const char* p_pcInstanceName) -{ - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocInstanceName (%s)\n"), _DH(), p_pcHostName);); - return _allocDomainName(p_pcInstanceName, m_pcInstanceName); -} - -/* - MDNSResponder::clsHost::_releaseInstanceName -*/ -bool MDNSResponder::clsHost::_releaseInstanceName(void) -{ - return _releaseDomainName(m_pcInstanceName); -} - - -/* - SERVICE -*/ - -/* - MDNSResponder::clsHost::_allocService -*/ -MDNSResponder::clsHost::stcService* MDNSResponder::clsHost::_allocService(const char* p_pcName, - const char* p_pcServiceType, - const char* p_pcProtocol, - uint16_t p_u16Port) -{ - stcService* pService = 0; - if (((!p_pcName) || - (MDNS_DOMAIN_LABEL_MAXLENGTH >= strlen(p_pcName))) && - (p_pcServiceType) && - (MDNS_SERVICE_NAME_LENGTH >= strlen(p_pcServiceType)) && - (p_pcProtocol) && - (MDNS_SERVICE_PROTOCOL_LENGTH >= strlen(p_pcProtocol)) && - (p_u16Port) && - (0 != ((pService = new stcService))) && - (pService->setName(p_pcName ? : (m_pcInstanceName ? : m_pcHostName))) && - (pService->setServiceType(p_pcServiceType)) && - (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::clsHost::_releaseService -*/ -bool MDNSResponder::clsHost::_releaseService(MDNSResponder::clsHost::stcService* p_pService) -{ - bool bResult = false; - - if (p_pService) - { - stcService* 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.printf_P(PSTR("%s _releaseService: INVALID service!"), _DH(p_pService));); - } - } - } - return bResult; -} - - -/* - SERVICE TXT -*/ - -/* - MDNSResponder::clsHost::_allocServiceTxt -*/ -MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::_allocServiceTxt(MDNSResponder::clsHost::stcService* p_pService, - const char* p_pcKey, - const char* p_pcValue, - bool p_bTemp) -{ - stcServiceTxt* pTxt = 0; - - if ((p_pService) && - (p_pcKey) && - (MDNS_SERVICE_TXT_MAXLENGTH > (p_pService->m_Txts.length() + - 1 + // Length byte - (p_pcKey ? strlen(p_pcKey) : 0) + - 1 + // '=' - (p_pcValue ? strlen(p_pcValue) : 0)))) - { - - pTxt = new stcServiceTxt; - if (pTxt) - { - size_t stLength = (p_pcKey ? strlen(p_pcKey) : 0); - pTxt->m_pcKey = new char[stLength + 1]; - if (pTxt->m_pcKey) - { - strncpy(pTxt->m_pcKey, p_pcKey, stLength); pTxt->m_pcKey[stLength] = 0; - } - - if (p_pcValue) - { - stLength = (p_pcValue ? strlen(p_pcValue) : 0); - pTxt->m_pcValue = new char[stLength + 1]; - if (pTxt->m_pcValue) - { - strncpy(pTxt->m_pcValue, p_pcValue, stLength); pTxt->m_pcValue[stLength] = 0; - } - } - pTxt->m_bTemp = p_bTemp; - - // Add to list (or start list) - p_pService->m_Txts.add(pTxt); - } - } - return pTxt; -} - -/* - MDNSResponder::clsHost::_releaseServiceTxt -*/ -bool MDNSResponder::clsHost::_releaseServiceTxt(MDNSResponder::clsHost::stcService* p_pService, - MDNSResponder::clsHost::stcServiceTxt* p_pTxt) -{ - return ((p_pService) && - (p_pTxt) && - (p_pService->m_Txts.remove(p_pTxt))); -} - -/* - MDNSResponder::clsHost::_updateServiceTxt -*/ -MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::_updateServiceTxt(MDNSResponder::clsHost::stcService* p_pService, - MDNSResponder::clsHost::stcServiceTxt* p_pTxt, - const char* p_pcValue, - bool p_bTemp) -{ - if ((p_pService) && - (p_pTxt) && - (MDNS_SERVICE_TXT_MAXLENGTH > (p_pService->m_Txts.length() - - (p_pTxt->m_pcValue ? strlen(p_pTxt->m_pcValue) : 0) + - (p_pcValue ? strlen(p_pcValue) : 0)))) - { - p_pTxt->update(p_pcValue); - p_pTxt->m_bTemp = p_bTemp; - } - return p_pTxt; -} - -/* - MDNSResponder::clsHost::_findServiceTxt -*/ -MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::_findServiceTxt(MDNSResponder::clsHost::stcService* p_pService, - const char* p_pcKey) -{ - return (p_pService ? p_pService->m_Txts.find(p_pcKey) : 0); -} - -/* - MDNSResponder::clsHost::_addServiceTxt -*/ -MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::_addServiceTxt(MDNSResponder::clsHost::stcService* p_pService, - const char* p_pcKey, - const char* p_pcValue, - bool p_bTemp) -{ - stcServiceTxt* pResult = 0; - - if ((p_pService) && - (p_pcKey) && - (strlen(p_pcKey))) - { - - stcServiceTxt* 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::clsHost::_answerKeyValue - / - MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::_answerKeyValue(const MDNSResponder::clsHost::stcQuery p_pQuery, - const uint32_t p_u32AnswerIndex) - { - stcQuery::stcAnswer* pSQAnswer = (p_pQuery ? p_pQuery->answerAtIndex(p_u32AnswerIndex) : 0); - // Fill m_pcTxts (if not already done) - return (pSQAnswer) ? pSQAnswer->m_Txts.m_pTxts : 0; - }*/ - -/* - MDNSResponder::clsHost::_collectServiceTxts -*/ -bool MDNSResponder::clsHost::_collectServiceTxts(MDNSResponder::clsHost::stcService& p_rService) -{ - if (m_fnServiceTxtCallback) - { - //m_fnServiceTxtCallback(*this, p_pService); - } - if (p_rService.m_fnTxtCallback) - { - //p_pService->m_fnTxtCallback(*this, p_pService); - } - return true; -} - -/* - MDNSResponder::clsHost::_releaseTempServiceTxts -*/ -bool MDNSResponder::clsHost::_releaseTempServiceTxts(MDNSResponder::clsHost::stcService& p_rService) -{ - return (p_rService.m_Txts.removeTempTxts()); -} - - -/* - QUERIES -*/ - -/* - MDNSResponder::_allocQuery -*/ -MDNSResponder::clsHost::stcQuery* MDNSResponder::clsHost::_allocQuery(MDNSResponder::clsHost::stcQuery::enuQueryType p_QueryType) -{ - stcQuery* pQuery = new stcQuery(p_QueryType); - if (pQuery) - { - // Link to query list - pQuery->m_pNext = m_pQueries; - m_pQueries = pQuery; - } - return m_pQueries; -} - -/* - MDNSResponder:clsHost:::_removeQuery -*/ -bool MDNSResponder::clsHost::_removeQuery(MDNSResponder::clsHost::stcQuery* p_pQuery) -{ - bool bResult = false; - - if (p_pQuery) - { - stcQuery* pPred = m_pQueries; - while ((pPred) && - (pPred->m_pNext != p_pQuery)) - { - pPred = pPred->m_pNext; - } - if (pPred) - { - pPred->m_pNext = p_pQuery->m_pNext; - delete p_pQuery; - bResult = true; - } - else // No predecesor - { - if (m_pQueries == p_pQuery) - { - m_pQueries = p_pQuery->m_pNext; - delete p_pQuery; - bResult = true; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _releaseQuery: INVALID query!"), _DH());); - } - } - } - return bResult; -} - -/* - MDNSResponder::clsHost::_removeLegacyQuery -*/ -bool MDNSResponder::clsHost::_removeLegacyQuery(void) -{ - stcQuery* pLegacyQuery = 0; - return (((pLegacyQuery = _findLegacyQuery())) - ? _removeQuery(pLegacyQuery) - : false); -} - -/* - MDNSResponder::clsHost::_findLegacyQuery -*/ -MDNSResponder::clsHost::stcQuery* MDNSResponder::clsHost::_findLegacyQuery(void) -{ - stcQuery* pLegacyQuery = m_pQueries; - while (pLegacyQuery) - { - if (pLegacyQuery->m_bLegacyQuery) - { - break; - } - pLegacyQuery = pLegacyQuery->m_pNext; - } - return pLegacyQuery; -} - -/* - MDNSResponder::clsHost::_releaseQueries -*/ -bool MDNSResponder::clsHost::_releaseQueries(void) -{ - while (m_pQueries) - { - stcQuery* pNext = m_pQueries->m_pNext; - delete m_pQueries; - m_pQueries = pNext; - } - return true; -} - -/* - MDNSResponder::clsHost::_findNextQueryByDomain -*/ -MDNSResponder::clsHost::stcQuery* MDNSResponder::clsHost::_findNextQueryByDomain(const MDNSResponder::clsHost::stcRRDomain& p_Domain, - const MDNSResponder::clsHost::stcQuery::enuQueryType p_QueryType, - const stcQuery* p_pPrevQuery) -{ - stcQuery* pMatchingQuery = 0; - - stcQuery* pQuery = (p_pPrevQuery ? p_pPrevQuery->m_pNext : m_pQueries); - while (pQuery) - { - if (((stcQuery::enuQueryType::None == p_QueryType) || - (pQuery->m_QueryType == p_QueryType)) && - (p_Domain == pQuery->m_Domain)) - { - - pMatchingQuery = pQuery; - break; - } - pQuery = pQuery->m_pNext; - } - return pMatchingQuery; -} - -/* - MDNSResponder::clsHost::_installDomainQuery -*/ -MDNSResponder::clsHost::stcQuery* MDNSResponder::clsHost::_installDomainQuery(MDNSResponder::clsHost::stcRRDomain& p_Domain, - MDNSResponder::clsHost::stcQuery::enuQueryType p_QueryType, - MDNSResponder::clsHost::QueryCallbackFn p_fnCallback) -{ - stcQuery* pQuery = 0; - - if ((p_fnCallback) && - ((pQuery = _allocQuery(p_QueryType)))) - { - pQuery->m_Domain = p_Domain; - pQuery->m_fnCallback = p_fnCallback; - pQuery->m_bLegacyQuery = false; - - if (_sendMDNSQuery(*pQuery)) - { - pQuery->m_u8SentCount = 1; - pQuery->m_ResendTimeout.reset(MDNS_DYNAMIC_QUERY_RESEND_DELAY); - } - else - { - _removeQuery(pQuery); - } - } - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("%s _installDomainQuery: %s for "), (pQuery ? "Succeeded" : "FAILED"), _DH()); - _printRRDomain(p_Domain); - DEBUG_OUTPUT.println(); - ); - DEBUG_EX_ERR( - if (!pQuery) -{ - DEBUG_OUTPUT.printf_P(PSTR("%s _installDomainQuery: FAILED for "), _DH()); - _printRRDomain(p_Domain); - DEBUG_OUTPUT.println(); - } - ); - return pQuery; -} - -/* - MDNSResponder::clsHost::_hasQueriesWaitingForAnswers -*/ -bool MDNSResponder::clsHost::_hasQueriesWaitingForAnswers(void) const -{ - bool bOpenQueries = false; - - for (stcQuery* pQuery = m_pQueries; pQuery; pQuery = pQuery->m_pNext) - { - if (pQuery->m_bAwaitingAnswers) - { - bOpenQueries = true; - break; - } - } - return bOpenQueries; -} - -/* - MDNSResponder::clsHost::_executeQueryCallback -*/ -bool MDNSResponder::clsHost::_executeQueryCallback(const stcQuery& p_Query, - const stcQuery::stcAnswer& p_Answer, - typeQueryAnswerType p_QueryAnswerTypeFlags, - bool p_bSetContent) -{ - if (p_Query.m_fnCallback) - { - p_Query.m_fnCallback(*this, p_Query, p_Answer, p_QueryAnswerTypeFlags, p_bSetContent); - } - return true; -} - - - -} // namespace MDNSImplementation - -} // namespace esp8266 diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Host.hpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Host.hpp deleted file mode 100755 index 4a5ff6f39d..0000000000 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Host.hpp +++ /dev/null @@ -1,1177 +0,0 @@ -/* - LEAmDNS2_Host.hpp - - 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. - -*/ - -/** - clsHost & clsHostList -*/ -class clsHost -{ -public: - - // File: ..._Host_Structs - /** - typeIPProtocolType & enuIPProtocolType - */ - using typeIPProtocolType = uint8_t; - enum class enuIPProtocolType : typeIPProtocolType - { -#ifdef MDNS_IPV4_SUPPORT - V4 = 0x01, -#endif -#ifdef MDNS_IPV6_SUPPORT - V6 = 0x02, -#endif - }; - - /** - typeNetIfState & enuNetIfState - */ - using typeNetIfState = uint8_t; - enum class enuNetIfState : typeNetIfState - { - None = 0x00, - - IsUp = 0x01, - UpMask = (IsUp), - - LinkIsUp = 0x02, - LinkMask = (LinkIsUp), - - IPv4 = 0x04, - IPv6 = 0x08, - IPMask = (IPv4 | IPv6), - }; - - /** - stcServiceTxt - */ - struct stcServiceTxt - { - stcServiceTxt* m_pNext; - char* m_pcKey; - char* m_pcValue; - bool m_bTemp; - - stcServiceTxt(const char* p_pcKey = 0, - const char* p_pcValue = 0, - bool p_bTemp = false); - stcServiceTxt(const stcServiceTxt& p_Other); - ~stcServiceTxt(void); - - stcServiceTxt& operator=(const stcServiceTxt& 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; - }; - - /** - stcServiceTxts - */ - struct stcServiceTxts - { - stcServiceTxt* m_pTxts; - char* m_pcCache; - - stcServiceTxts(void); - stcServiceTxts(const stcServiceTxts& p_Other); - ~stcServiceTxts(void); - - stcServiceTxts& operator=(const stcServiceTxts& p_Other); - - bool clear(void); - bool clearCache(void); - - bool add(stcServiceTxt* p_pTxt); - bool remove(stcServiceTxt* p_pTxt); - - bool removeTempTxts(void); - - stcServiceTxt* find(const char* p_pcKey); - const stcServiceTxt* find(const char* p_pcKey) const; - stcServiceTxt* find(const stcServiceTxt* p_pTxt); - - uint16_t length(void) const; - - size_t c_strLength(void) const; - bool c_str(char* p_pcBuffer); - const char* c_str(void) const; - - size_t bufferLength(void) const; - bool buffer(char* p_pcBuffer); - - bool compare(const stcServiceTxts& p_Other) const; - bool operator==(const stcServiceTxts& p_Other) const; - bool operator!=(const stcServiceTxts& p_Other) const; - }; - - /** - typeProbingStatus & enuProbingStatus - */ - using typeProbingStatus = uint8_t; - enum class enuProbingStatus : typeProbingStatus - { - WaitingForData, - ReadyToStart, - InProgress, - Done - }; - - /** - stcProbeInformation_Base - */ - struct stcProbeInformation_Base - { - enuProbingStatus m_ProbingStatus; - uint8_t m_u8SentCount; // Used for probes and announcements - esp8266::polledTimeout::oneShot m_Timeout; // Used for probes and announcements - bool m_bConflict; - bool m_bTiebreakNeeded; - - stcProbeInformation_Base(void); - - bool clear(void); // No 'virtual' needed, no polymorphic use (save 4 bytes) - }; - - /** - HostProbeResultCallbackFn - Callback function for host domain probe results - */ - using HostProbeResultCallbackFn = std::function; - /** - MDNSServiceProbeResultCallbackFn - Callback function for service domain probe results - */ - struct stcService; - using ServiceProbeResultCallbackFn = std::function; - - /** - stcProbeInformation_Host - */ - struct stcProbeInformation_Host : public stcProbeInformation_Base - { - HostProbeResultCallbackFn m_fnProbeResultCallback; - - stcProbeInformation_Host(void); - - bool clear(bool p_bClearUserdata = false); - }; - - /** - stcProbeInformation_Service - */ - struct stcProbeInformation_Service : public stcProbeInformation_Base - { - ServiceProbeResultCallbackFn m_fnProbeResultCallback; - - stcProbeInformation_Service(void); - - bool clear(bool p_bClearUserdata = false); - }; - - /** - DynamicServiceTxtCallbackFn - Callback function for dynamic MDNS TXT items - */ - struct stcService; - using DynamicServiceTxtCallbackFn = std::function; - - /** - stcService - */ - struct stcService - { - stcService* m_pNext; - char* m_pcName; - bool m_bAutoName; // Name was set automatically to hostname (if no name was supplied) - char* m_pcServiceType; - char* m_pcProtocol; - uint16_t m_u16Port; - uint32_t m_u32ReplyMask; - stcServiceTxts m_Txts; - DynamicServiceTxtCallbackFn m_fnTxtCallback; - stcProbeInformation_Service m_ProbeInformation; - - stcService(const char* p_pcName = 0, - const char* p_pcServiceType = 0, - const char* p_pcProtocol = 0); - ~stcService(void); - - bool setName(const char* p_pcName); - bool releaseName(void); - - bool setServiceType(const char* p_pcService); - bool releaseServiceType(void); - - bool setProtocol(const char* p_pcProtocol); - bool releaseProtocol(void); - - bool probeStatus(void) const; - }; - - /** - typeContentFlag & enuContentFlag - */ - using typeContentFlag = uint16_t; - enum class enuContentFlag : typeContentFlag - { - // Host - A = 0x0001, - PTR_IPv4 = 0x0002, - PTR_IPv6 = 0x0004, - AAAA = 0x0008, - // Service - PTR_TYPE = 0x0010, - PTR_NAME = 0x0020, - TXT = 0x0040, - SRV = 0x0080, - // DNSSEC - NSEC = 0x0100, - - PTR = (PTR_IPv4 | PTR_IPv6 | PTR_TYPE | PTR_NAME) - }; - - /** - stcMsgHeader - */ - struct stcMsgHeader - { - uint16_t m_u16ID; // Identifier - bool m_1bQR : 1; // Query/Response flag - uint8_t m_4bOpcode : 4; // Operation code - bool m_1bAA : 1; // Authoritative Answer flag - bool m_1bTC : 1; // Truncation flag - bool m_1bRD : 1; // Recursion desired - bool m_1bRA : 1; // Recursion available - uint8_t m_3bZ : 3; // Zero - uint8_t m_4bRCode : 4; // Response code - uint16_t m_u16QDCount; // Question count - uint16_t m_u16ANCount; // Answer count - uint16_t m_u16NSCount; // Authority Record count - uint16_t m_u16ARCount; // Additional Record count - - stcMsgHeader(uint16_t p_u16ID = 0, - bool p_bQR = false, - uint8_t p_u8Opcode = 0, - bool p_bAA = false, - bool p_bTC = false, - bool p_bRD = false, - bool p_bRA = false, - uint8_t p_u8RCode = 0, - uint16_t p_u16QDCount = 0, - uint16_t p_u16ANCount = 0, - uint16_t p_u16NSCount = 0, - uint16_t p_u16ARCount = 0); - }; - - /** - stcRRDomain - */ - struct stcRRDomain - { - char m_acName[MDNS_DOMAIN_MAXLENGTH]; // Encoded domain name - uint16_t m_u16NameLength; // Length (incl. '\0') - char* m_pcDecodedName; - - stcRRDomain(void); - stcRRDomain(const stcRRDomain& p_Other); - ~stcRRDomain(void); - - stcRRDomain& operator=(const stcRRDomain& p_Other); - - bool clear(void); - bool clearNameCache(void); - - bool addLabel(const char* p_pcLabel, - bool p_bPrependUnderline = false); - - bool compare(const stcRRDomain& p_Other) const; - bool operator==(const stcRRDomain& p_Other) const; - bool operator!=(const stcRRDomain& p_Other) const; - bool operator>(const stcRRDomain& p_Other) const; - - size_t c_strLength(void) const; - bool c_str(char* p_pcBuffer) const; - const char* c_str(void) const; - }; - - /** - stcRRAttributes - */ - struct stcRRAttributes - { - uint16_t m_u16Type; // Type - uint16_t m_u16Class; // Class, nearly always 'IN' - - stcRRAttributes(uint16_t p_u16Type = 0, - uint16_t p_u16Class = 1 /*DNS_RRCLASS_IN Internet*/); - stcRRAttributes(const stcRRAttributes& p_Other); - - stcRRAttributes& operator=(const stcRRAttributes& p_Other); - }; - - /** - stcRRHeader - */ - struct stcRRHeader - { - stcRRDomain m_Domain; - stcRRAttributes m_Attributes; - - stcRRHeader(void); - stcRRHeader(const stcRRHeader& p_Other); - - stcRRHeader& operator=(const stcRRHeader& p_Other); - - bool clear(void); - }; - - /** - stcRRQuestion - */ - struct stcRRQuestion - { - stcRRQuestion* m_pNext; - stcRRHeader m_Header; - bool m_bUnicast; // Unicast reply requested - - stcRRQuestion(void); - }; - - /** - stcNSECBitmap - */ - struct stcNSECBitmap - { - uint8_t m_au8BitmapData[6]; // 6 bytes data - - stcNSECBitmap(void); - - bool clear(void); - uint16_t length(void) const; - bool setBit(uint16_t p_u16Bit); - bool getBit(uint16_t p_u16Bit) const; - }; - - /** - typeAnswerType & enuAnswerType - */ - using typeAnswerType = uint8_t; - enum class enuAnswerType : typeAnswerType - { - A, - PTR, - TXT, - AAAA, - SRV, - //NSEC, - Generic - }; - - /** - stcRRAnswer - */ - struct stcRRAnswer - { - stcRRAnswer* m_pNext; - const enuAnswerType m_AnswerType; - stcRRHeader m_Header; - bool m_bCacheFlush; // Cache flush command bit - uint32_t m_u32TTL; // Validity time in seconds - - virtual ~stcRRAnswer(void); - - enuAnswerType answerType(void) const; - - bool clear(void); - - protected: - stcRRAnswer(enuAnswerType p_AnswerType, - const stcRRHeader& p_Header, - uint32_t p_u32TTL); - }; - -#ifdef MDNS_IPV4_SUPPORT - /** - stcRRAnswerA - */ - struct stcRRAnswerA : public stcRRAnswer - { - IPAddress m_IPAddress; - - stcRRAnswerA(const stcRRHeader& p_Header, - uint32_t p_u32TTL); - ~stcRRAnswerA(void); - - bool clear(void); - }; -#endif - - /** - stcRRAnswerPTR - */ - struct stcRRAnswerPTR : public stcRRAnswer - { - stcRRDomain m_PTRDomain; - - stcRRAnswerPTR(const stcRRHeader& p_Header, - uint32_t p_u32TTL); - ~stcRRAnswerPTR(void); - - bool clear(void); - }; - - /** - stcRRAnswerTXT - */ - struct stcRRAnswerTXT : public stcRRAnswer - { - stcServiceTxts m_Txts; - - stcRRAnswerTXT(const stcRRHeader& p_Header, - uint32_t p_u32TTL); - ~stcRRAnswerTXT(void); - - bool clear(void); - }; - -#ifdef MDNS_IPV6_SUPPORT - /** - stcRRAnswerAAAA - */ - struct stcRRAnswerAAAA : public stcRRAnswer - { - IPAddress m_IPAddress; - - stcRRAnswerAAAA(const stcRRHeader& p_Header, - uint32_t p_u32TTL); - ~stcRRAnswerAAAA(void); - - bool clear(void); - }; -#endif - - /** - stcRRAnswerSRV - */ - struct stcRRAnswerSRV : public stcRRAnswer - { - uint16_t m_u16Priority; - uint16_t m_u16Weight; - uint16_t m_u16Port; - stcRRDomain m_SRVDomain; - - stcRRAnswerSRV(const stcRRHeader& p_Header, - uint32_t p_u32TTL); - ~stcRRAnswerSRV(void); - - bool clear(void); - }; - - /** - stcRRAnswerGeneric - */ - struct stcRRAnswerGeneric : public stcRRAnswer - { - uint16_t m_u16RDLength; // Length of variable answer - uint8_t* m_pu8RDData; // Offset of start of variable answer in packet - - stcRRAnswerGeneric(const stcRRHeader& p_Header, - uint32_t p_u32TTL); - ~stcRRAnswerGeneric(void); - - bool clear(void); - }; - - - /** - stcSendParameter - */ - struct stcSendParameter - { - protected: - /** - stcDomainCacheItem - */ - 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); - }; - - public: - /** - typeResponseType & enuResponseType - */ - using typeResponseType = uint8_t; - enum class enuResponseType : typeResponseType - { - None, - Response, - Unsolicited - }; - - uint16_t m_u16ID; // Query ID (used only in lagacy queries) - stcRRQuestion* m_pQuestions; // A list of queries - uint32_t m_u32HostReplyMask; // Flags for reply components/answers - bool m_bLegacyQuery; // Flag: Legacy query - enuResponseType m_Response; // Enum: Response to a query - bool m_bAuthorative; // Flag: Authorative (owner) response - bool m_bCacheFlush; // Flag: Clients should flush their caches - bool m_bUnicast; // Flag: Unicast response - bool m_bUnannounce; // Flag: Unannounce service - - // Temp content; created while processing _prepareMessage - uint16_t m_u16Offset; // Current offset in UDP write buffer (mainly for domain cache) - stcDomainCacheItem* m_pDomainCacheItems; // Cached host and service domains - - stcSendParameter(void); - ~stcSendParameter(void); - - bool clear(void); - bool flushQuestions(void); - bool flushDomainCache(void); - bool flushTempContent(void); - - bool shiftOffset(uint16_t p_u16Shift); - - bool addDomainCacheItem(const void* p_pHostNameOrService, - bool p_bAdditionalData, - uint16_t p_u16Offset); - uint16_t findCachedDomainOffset(const void* p_pHostNameOrService, - bool p_bAdditionalData) const; - }; - - - // QUERIES & ANSWERS - /** - typeQueryAnswerType & enuQueryAnswerType - */ - using typeQueryAnswerType = uint8_t; - enum class enuQueryAnswerType : typeQueryAnswerType - { - Unknown = 0x00, - ServiceDomain = 0x01, // Service domain - HostDomain = 0x02, // Host domain - Port = 0x04, // Port - Txts = 0x08, // TXT items -#ifdef MDNS_IPV4_SUPPORT - IPv4Address = 0x10, // IPv4 address -#endif -#ifdef MDNS_IPV6_SUPPORT - IPv6Address = 0x20, // IPv6 address -#endif - }; - - /** - stcQuery - */ - struct stcQuery - { - /** - stcAnswer - */ - struct stcAnswer - { - /** - stcTTL - */ - struct stcTTL - { - /** - typeTimeoutLevel & enuTimeoutLevel - */ - using typeTimeoutLevel = uint8_t; - enum class enuTimeoutLevel : typeTimeoutLevel - { - None = 0, - Base = 80, - Interval = 5, - Final = 100 - }; - - uint32_t m_u32TTL; - esp8266::polledTimeout::oneShot m_TTLTimeout; - typeTimeoutLevel m_TimeoutLevel; - - stcTTL(void); - bool set(uint32_t p_u32TTL); - - bool flagged(void) const; - bool restart(void); - - bool prepareDeletion(void); - bool finalTimeoutLevel(void) const; - - unsigned long timeout(void) const; - }; - /** - stcIPAddress - */ - struct stcIPAddress - { - stcIPAddress* m_pNext; - IPAddress m_IPAddress; - stcTTL m_TTL; - - stcIPAddress(IPAddress p_IPAddress, - uint32_t p_u32TTL = 0); - }; - - 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. - stcRRDomain m_ServiceDomain; // 1. level answer (PTR), eg. MyESP._http._tcp.local - stcTTL m_TTLServiceDomain; - stcRRDomain m_HostDomain; // 2. level answer (SRV, using service domain), eg. esp8266.local - uint16_t m_u16Port; // 2. level answer (SRV, using service domain), eg. 5000 - stcTTL m_TTLHostDomainAndPort; - stcServiceTxts m_Txts; // 2. level answer (TXT, using service domain), eg. c#=1 - stcTTL m_TTLTxts; -#ifdef MDNS_IPV4_SUPPORT - stcIPAddress* m_pIPv4Addresses; // 3. level answer (A, using host domain), eg. 123.456.789.012 -#endif -#ifdef MDNS_IPV6_SUPPORT - stcIPAddress* m_pIPv6Addresses; // 3. level answer (AAAA, using host domain), eg. 1234::09 -#endif - typeQueryAnswerType m_QueryAnswerFlags; // enuQueryAnswerType - - stcAnswer(void); - ~stcAnswer(void); - - bool clear(void); - -#ifdef MDNS_IPV4_SUPPORT - bool releaseIPv4Addresses(void); - bool addIPv4Address(stcIPAddress* p_pIPAddress); - bool removeIPv4Address(stcIPAddress* p_pIPAddress); - const stcIPAddress* findIPv4Address(const IPAddress& p_IPAddress) const; - stcIPAddress* findIPv4Address(const IPAddress& p_IPAddress); - uint32_t IPv4AddressCount(void) const; - const stcIPAddress* IPv4AddressAtIndex(uint32_t p_u32Index) const; - stcIPAddress* IPv4AddressAtIndex(uint32_t p_u32Index); -#endif -#ifdef MDNS_IPV6_SUPPORT - bool releaseIPv6Addresses(void); - bool addIPv6Address(stcIPAddress* p_pIPAddress); - bool removeIPv6Address(stcIPAddress* p_pIPAddress); - const stcIPAddress* findIPv6Address(const IPAddress& p_IPAddress) const; - stcIPAddress* findIPv6Address(const IPAddress& p_IPAddress); - uint32_t IPv6AddressCount(void) const; - const stcIPAddress* IPv6AddressAtIndex(uint32_t p_u32Index) const; - stcIPAddress* IPv6AddressAtIndex(uint32_t p_u32Index); -#endif - }; //stcAnswer - - /** - typeQueryType & enuQueryType - */ - using typeQueryType = uint8_t; - enum class enuQueryType : typeQueryType - { - None, - Service, - Host - }; - using _QueryCallbackFn = std::function; // true: Answer component set, false: component deleted - - stcQuery* m_pNext; - enuQueryType m_QueryType; - stcRRDomain m_Domain; // Type:Service -> _http._tcp.local; Type:Host -> esp8266.local - _QueryCallbackFn m_fnCallback; - bool m_bLegacyQuery; - uint8_t m_u8SentCount; - esp8266::polledTimeout::oneShot m_ResendTimeout; - bool m_bAwaitingAnswers; - stcAnswer* m_pAnswers; - - stcQuery(const enuQueryType p_QueryType); - ~stcQuery(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 stcRRDomain& p_ServiceDomain); - stcAnswer* findAnswerForHostDomain(const stcRRDomain& p_HostDomain); - }; - /** - QueryCallbackFn - - Callback function for received answers for dynamic queries - */ - using QueryCallbackFn = stcQuery::_QueryCallbackFn; - -public: - clsHost(netif& p_rNetIf, - UdpContext& p_rUDPContext); - ~clsHost(void); - - bool init(void); - - // HOST - bool setHostName(const char* p_pcHostName); - const char* hostName(void) const; - - bool setHostProbeResultCallback(HostProbeResultCallbackFn p_fnCallback); - - // Returns 'true' is host domain probing is done - bool probeStatus(void) const; - - // SERVICE - bool setInstanceName(const char* p_pcInstanceName); - const char* instanceName(void) const; - - stcService* addService(const char* p_pcInstanceName, - const char* p_pcServiceType, - const char* p_pcProtocol, - uint16_t p_u16Port); - bool removeService(stcService* p_pMDNSService); - - const stcService* findService(const char* p_pcInstanceName, - const char* p_pcServiceType, - const char* p_pcProtocol, - uint16_t p_u16Port = 0) const; - stcService* findService(const char* p_pcInstanceName, - const char* p_pcServiceType, - const char* p_pcProtocol, - uint16_t p_u16Port = 0); - bool validateService(const stcService* p_pService) const; - - bool setServiceName(stcService* p_pMDNSService, - const char* p_pcInstanceName); - const char* serviceName(const stcService* p_pMDNSService) const; - const char* serviceType(const stcService* p_pMDNSService) const; - const char* serviceProtocol(const stcService* p_pMDNSService) const; - uint16_t servicePort(const stcService* p_pMDNSService) const; - - // Set a service specific probe result callcack - bool setServiceProbeResultCallback(stcService* p_pMDNSService, - ServiceProbeResultCallbackFn p_fnCallback); - - bool serviceProbeStatus(const stcService* p_pMDNSService) const; - - // SERVICE TXT - // Add a (static) MDNS TXT item ('key' = 'value') to the service - stcServiceTxt* addServiceTxt(stcService* p_pMDNSService, - const char* p_pcKey, - const char* p_pcValue); - bool removeServiceTxt(stcService* p_pMDNSService, - stcServiceTxt* p_pTxt); - const stcServiceTxt* findServiceTxt(stcService* p_pMDNSService, - const char* p_pcKey) const; - stcServiceTxt* findServiceTxt(stcService* p_pMDNSService, - const char* p_pcKey); - - bool setDynamicServiceTxtCallback(DynamicServiceTxtCallbackFn p_fnCallback); - bool setDynamicServiceTxtCallback(stcService* p_pMDNSService, - DynamicServiceTxtCallbackFn p_fnCallback); - - // 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). - stcServiceTxt* addDynamicServiceTxt(stcService* p_pMDNSService, - const char* p_pcKey, - const char* p_pcValue); - - // QUERIES - - // - STATIC - // Perform a (static) service/host query. The function returns after p_u16Timeout milliseconds - // The answers (the number of received answers is returned) can be retrieved by calling - // - answerHostName (or hostname) - // - answerIP (or IP) - // - answerPort (or port) - uint32_t queryService(const char* p_pcService, - const char* p_pcProtocol, - const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME); - uint32_t queryHost(const char* p_pcHostName, - const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME); - bool removeQuery(void); - bool hasQuery(void); - stcQuery* getQuery(void); - - // - DYNAMIC - // Install a dynamic service/host query. For every received answer (part) the given callback - // function is called. The query will be updated every time, the TTL for an answer - // has timed-out. - // The answers can also be retrieved by calling - // - answerCount service/host (for host queries, this should never be >1) - // - answerServiceDomain service - // - hasAnswerHostDomain/answerHostDomain service/host - // - hasAnswerIPv4Address/answerIPv4Address service/host - // - hasAnswerIPv6Address/answerIPv6Address service/host - // - hasAnswerPort/answerPort service - // - hasAnswerTxts/answerTxts service - stcQuery* installServiceQuery(const char* p_pcServiceType, - const char* p_pcProtocol, - QueryCallbackFn p_fnCallback); - stcQuery* installHostQuery(const char* p_pcHostName, - QueryCallbackFn p_fnCallback); - // Remove a dynamic service query - bool removeQuery(stcQuery* p_pMDNSQuery); - - - - - - // PROCESSING - bool processUDPInput(void); - bool update(void); - - bool announce(bool p_bAnnounce, - bool p_bIncludeServices); - bool announceService(stcService* p_pService, - bool p_bAnnounce = true); - - bool restart(void); - -protected: - // File: ..._Host - bool _close(void); - - // NETIF - typeNetIfState _getNetIfState(void) const; - bool _checkNetIfState(void); - - // DOMAIN NAMES - bool _allocDomainName(const char* p_pcNewDomainName, - char*& p_rpcDomainName); - bool _releaseDomainName(char*& p_rpcDomainName); - bool _allocHostName(const char* p_pcHostName); - bool _releaseHostName(void); - bool _allocInstanceName(const char* p_pcInstanceName); - bool _releaseInstanceName(void); - - // SERVICE - stcService* _allocService(const char* p_pcName, - const char* p_pcServiceType, - const char* p_pcProtocol, - uint16_t p_u16Port); - bool _releaseService(stcService* p_pService); - - // SERVICE TXT - stcServiceTxt* _allocServiceTxt(stcService* p_pService, - const char* p_pcKey, - const char* p_pcValue, - bool p_bTemp); - bool _releaseServiceTxt(stcService* p_pService, - stcServiceTxt* p_pTxt); - stcServiceTxt* _updateServiceTxt(stcService* p_pService, - stcServiceTxt* p_pTxt, - const char* p_pcValue, - bool p_bTemp); - stcServiceTxt* _findServiceTxt(stcService* p_pService, - const char* p_pcKey); - stcServiceTxt* _addServiceTxt(stcService* p_pService, - const char* p_pcKey, - const char* p_pcValue, - bool p_bTemp); - stcServiceTxt* _answerKeyValue(const stcQuery p_pQuery, - const uint32_t p_u32AnswerIndex); - bool _collectServiceTxts(stcService& p_rService); - bool _releaseTempServiceTxts(stcService& p_rService); - - // QUERIES - stcQuery* _allocQuery(stcQuery::enuQueryType p_QueryType); - bool _removeQuery(stcQuery* p_pQuery); - bool _removeLegacyQuery(void); - stcQuery* _findLegacyQuery(void); - bool _releaseQueries(void); - stcQuery* _findNextQueryByDomain(const stcRRDomain& p_Domain, - const stcQuery::enuQueryType p_QueryType, - const stcQuery* p_pPrevQuery); - stcQuery* _installDomainQuery(stcRRDomain& p_Domain, - stcQuery::enuQueryType p_QueryType, - QueryCallbackFn p_fnCallback); - bool _hasQueriesWaitingForAnswers(void) const; - bool _executeQueryCallback(const stcQuery& p_Query, - const stcQuery::stcAnswer& p_Answer, - typeQueryAnswerType p_QueryAnswerTypeFlags, - bool p_SetContent); - - - // File: ..._Host_Control - // RECEIVING - bool _parseMessage(void); - bool _parseQuery(const stcMsgHeader& p_Header); - - bool _parseResponse(const stcMsgHeader& p_Header); - bool _processAnswers(const stcRRAnswer* p_pPTRAnswers); - bool _processPTRAnswer(const stcRRAnswerPTR* p_pPTRAnswer, - bool& p_rbFoundNewKeyAnswer); - bool _processSRVAnswer(const stcRRAnswerSRV* p_pSRVAnswer, - bool& p_rbFoundNewKeyAnswer); - bool _processTXTAnswer(const stcRRAnswerTXT* p_pTXTAnswer); -#ifdef MDNS_IPV4_SUPPORT - bool _processAAnswer(const stcRRAnswerA* p_pAAnswer); -#endif -#ifdef MDNS_IPV6_SUPPORT - bool _processAAAAAnswer(const stcRRAnswerAAAA* p_pAAAAAnswer); -#endif - - // PROBING - bool _updateProbeStatus(void); - bool _resetProbeStatus(bool p_bRestart = true); - bool _hasProbesWaitingForAnswers(void) const; - bool _sendHostProbe(void); - bool _sendServiceProbe(stcService& p_rService); - bool _cancelProbingForHost(void); - bool _cancelProbingForService(stcService& p_rService); - bool _callHostProbeResultCallback(bool p_bResult); - bool _callServiceProbeResultCallback(stcService& p_rService, - bool p_bResult); - - // ANNOUNCE - bool _announce(bool p_bAnnounce, - bool p_bIncludeServices); - bool _announceService(stcService& p_pService, - bool p_bAnnounce = true); - - // QUERY CACHE - bool _checkQueryCache(void); - - uint32_t _replyMaskForHost(const stcRRHeader& p_RRHeader, - bool* p_pbFullNameMatch = 0) const; - uint32_t _replyMaskForService(const stcRRHeader& p_RRHeader, - const stcService& p_Service, - bool* p_pbFullNameMatch = 0) const; - - - // File: ..._Host_Transfer - // SENDING - bool _sendMDNSMessage(stcSendParameter& p_SendParameter); - bool _sendMDNSMessage_Multicast(stcSendParameter& p_rSendParameter, - uint8_t p_IPProtocolTypes); - bool _prepareMDNSMessage(stcSendParameter& p_SendParameter); - bool _addMDNSQueryRecord(stcSendParameter& p_rSendParameter, - const stcRRDomain& p_QueryDomain, - uint16_t p_u16QueryType); - bool _sendMDNSQuery(const stcQuery& p_Query, - stcQuery::stcAnswer* p_pKnownAnswers = 0); - bool _sendMDNSQuery(const stcRRDomain& p_QueryDomain, - uint16_t p_u16RecordType, - stcQuery::stcAnswer* p_pKnownAnswers = 0); - - IPAddress _getResponderIPAddress(enuIPProtocolType p_IPProtocolType) const; - - // RESOURCE RECORD - bool _readRRQuestion(stcRRQuestion& p_rQuestion); - bool _readRRAnswer(stcRRAnswer*& p_rpAnswer); -#ifdef MDNS_IPV4_SUPPORT - bool _readRRAnswerA(stcRRAnswerA& p_rRRAnswerA, - uint16_t p_u16RDLength); -#endif - bool _readRRAnswerPTR(stcRRAnswerPTR& p_rRRAnswerPTR, - uint16_t p_u16RDLength); - bool _readRRAnswerTXT(stcRRAnswerTXT& p_rRRAnswerTXT, - uint16_t p_u16RDLength); -#ifdef MDNS_IPV6_SUPPORT - bool _readRRAnswerAAAA(stcRRAnswerAAAA& p_rRRAnswerAAAA, - uint16_t p_u16RDLength); -#endif - bool _readRRAnswerSRV(stcRRAnswerSRV& p_rRRAnswerSRV, - uint16_t p_u16RDLength); - bool _readRRAnswerGeneric(stcRRAnswerGeneric& p_rRRAnswerGeneric, - uint16_t p_u16RDLength); - - bool _readRRHeader(stcRRHeader& p_rHeader); - bool _readRRDomain(stcRRDomain& p_rRRDomain); - bool _readRRDomain_Loop(stcRRDomain& p_rRRDomain, - uint8_t p_u8Depth); - bool _readRRAttributes(stcRRAttributes& p_rAttributes); - - // DOMAIN NAMES - bool _buildDomainForHost(const char* p_pcHostName, - stcRRDomain& p_rHostDomain) const; - bool _buildDomainForDNSSD(stcRRDomain& p_rDNSSDDomain) const; - bool _buildDomainForService(const stcService& p_Service, - bool p_bIncludeName, - stcRRDomain& p_rServiceDomain) const; - bool _buildDomainForService(const char* p_pcService, - const char* p_pcProtocol, - stcRRDomain& p_rServiceDomain) const; -#ifdef MDNS_IPV4_SUPPORT - bool _buildDomainForReverseIPv4(IPAddress p_IPv4Address, - stcRRDomain& p_rReverseIPv4Domain) const; -#endif -#ifdef MDNS_IPV6_SUPPORT - bool _buildDomainForReverseIPv6(IPAddress p_IPv4Address, - stcRRDomain& p_rReverseIPv6Domain) const; -#endif - - // UDP - bool _udpReadBuffer(unsigned char* p_pBuffer, - size_t p_stLength); - bool _udpRead8(uint8_t& p_ru8Value); - bool _udpRead16(uint16_t& p_ru16Value); - bool _udpRead32(uint32_t& p_ru32Value); - - bool _udpAppendBuffer(const unsigned char* p_pcBuffer, - size_t p_stLength); - bool _udpAppend8(uint8_t p_u8Value); - bool _udpAppend16(uint16_t p_u16Value); - bool _udpAppend32(uint32_t p_u32Value); - -#if not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_MDNS_RESPONDER - bool _udpDump(bool p_bMovePointer = false); - bool _udpDump(unsigned p_uOffset, - unsigned p_uLength); -#endif - - // READ/WRITE MDNS STRUCTS - bool _readMDNSMsgHeader(stcMsgHeader& p_rMsgHeader); - - bool _write8(uint8_t p_u8Value, - stcSendParameter& p_rSendParameter); - bool _write16(uint16_t p_u16Value, - stcSendParameter& p_rSendParameter); - bool _write32(uint32_t p_u32Value, - stcSendParameter& p_rSendParameter); - - bool _writeMDNSMsgHeader(const stcMsgHeader& p_MsgHeader, - stcSendParameter& p_rSendParameter); - bool _writeMDNSRRAttributes(const stcRRAttributes& p_Attributes, - stcSendParameter& p_rSendParameter); - bool _writeMDNSRRDomain(const stcRRDomain& p_Domain, - stcSendParameter& p_rSendParameter); - bool _writeMDNSHostDomain(const char* m_pcHostName, - bool p_bPrependRDLength, - uint16_t p_u16AdditionalLength, - stcSendParameter& p_rSendParameter); - bool _writeMDNSServiceDomain(const stcService& p_Service, - bool p_bIncludeName, - bool p_bPrependRDLength, - uint16_t p_u16AdditionalLength, - stcSendParameter& p_rSendParameter); - - bool _writeMDNSQuestion(stcRRQuestion& p_Question, - stcSendParameter& p_rSendParameter); - -#ifdef MDNS_IPV4_SUPPORT - bool _writeMDNSAnswer_A(IPAddress p_IPAddress, - stcSendParameter& p_rSendParameter); - bool _writeMDNSAnswer_PTR_IPv4(IPAddress p_IPAddress, - stcSendParameter& p_rSendParameter); -#endif - bool _writeMDNSAnswer_PTR_TYPE(stcService& p_rService, - stcSendParameter& p_rSendParameter); - bool _writeMDNSAnswer_PTR_NAME(stcService& p_rService, - stcSendParameter& p_rSendParameter); - bool _writeMDNSAnswer_TXT(stcService& p_rService, - stcSendParameter& p_rSendParameter); -#ifdef MDNS_IPV6_SUPPORT - bool _writeMDNSAnswer_AAAA(IPAddress p_IPAddress, - stcSendParameter& p_rSendParameter); - bool _writeMDNSAnswer_PTR_IPv6(IPAddress p_IPAddress, - stcSendParameter& p_rSendParameter); -#endif - bool _writeMDNSAnswer_SRV(stcService& p_rService, - stcSendParameter& p_rSendParameter); - stcNSECBitmap* _createNSECBitmap(uint32_t p_u32NSECContent); - bool _writeMDNSNSECBitmap(const stcNSECBitmap& p_NSECBitmap, - stcSendParameter& p_rSendParameter); - bool _writeMDNSAnswer_NSEC(uint32_t p_u32NSECContent, - stcSendParameter& p_rSendParameter); -#ifdef MDNS_IPV4_SUPPORT - bool _writeMDNSAnswer_NSEC_PTR_IPv4(IPAddress p_IPAddress, - stcSendParameter& p_rSendParameter); -#endif -#ifdef MDNS_IPV6_SUPPORT - bool _writeMDNSAnswer_NSEC_PTR_IPv6(IPAddress p_IPAddress, - stcSendParameter& p_rSendParameter); -#endif - bool _writeMDNSAnswer_NSEC(stcService& p_rService, - uint32_t p_u32NSECContent, - stcSendParameter& p_rSendParameter); - - - // File: ..._Host_Debug -#if not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_MDNS_RESPONDER - const char* _DH(const stcService* p_pMDNSService = 0) const; - const char* _service2String(const stcService* p_pMDNSService) const; - - bool _printRRDomain(const stcRRDomain& p_rRRDomain) const; - bool _printRRAnswer(const stcRRAnswer& p_RRAnswer) const; - const char* _RRType2Name(uint16_t p_u16RRType) const; - const char* _RRClass2String(uint16_t p_u16RRClass, - bool p_bIsQuery) const; - const char* _replyFlags2String(uint32_t p_u32ReplyFlags) const; - const char* _NSECBitmap2String(const stcNSECBitmap* p_pNSECBitmap) const; -#endif - - -public: - netif& m_rNetIf; - typeNetIfState m_NetIfState; - UdpContext& m_rUDPContext; - - char* m_pcHostName; - char* m_pcInstanceName; - stcService* m_pServices; - stcQuery* m_pQueries; - DynamicServiceTxtCallbackFn m_fnServiceTxtCallback; - stcProbeInformation_Host m_HostProbeInformation; -}; -using clsHostList = std::list; - diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Structs.cpp deleted file mode 100755 index 7121abcfca..0000000000 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Host_Structs.cpp +++ /dev/null @@ -1,2435 +0,0 @@ -/* - LEAmDNS2_Host_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 "LEAmDNS2_Priv.h" -#include "LEAmDNS2_lwIPdefs.h" - -namespace esp8266 -{ - -/* - LEAmDNS -*/ -namespace experimental -{ - -/** - Internal CLASSES & STRUCTS -*/ - -/** - MDNSResponder::clsHost::stcServiceTxt - - 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::clsHost::stcServiceTxt::stcServiceTxt constructor -*/ -MDNSResponder::clsHost::stcServiceTxt::stcServiceTxt(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::clsHost::stcServiceTxt::stcServiceTxt copy-constructor -*/ -MDNSResponder::clsHost::stcServiceTxt::stcServiceTxt(const MDNSResponder::clsHost::stcServiceTxt& p_Other) - : m_pNext(0), - m_pcKey(0), - m_pcValue(0), - m_bTemp(false) -{ - operator=(p_Other); -} - -/* - MDNSResponder::clsHost::stcServiceTxt::~stcServiceTxt destructor -*/ -MDNSResponder::clsHost::stcServiceTxt::~stcServiceTxt(void) -{ - clear(); -} - -/* - MDNSResponder::clsHost::stcServiceTxt::operator= -*/ -MDNSResponder::clsHost::stcServiceTxt& MDNSResponder::clsHost::stcServiceTxt::operator=(const MDNSResponder::clsHost::stcServiceTxt& p_Other) -{ - if (&p_Other != this) - { - clear(); - set(p_Other.m_pcKey, p_Other.m_pcValue, p_Other.m_bTemp); - } - return *this; -} - -/* - MDNSResponder::clsHost::stcServiceTxt::clear -*/ -bool MDNSResponder::clsHost::stcServiceTxt::clear(void) -{ - releaseKey(); - releaseValue(); - return true; -} - -/* - MDNSResponder::clsHost::stcServiceTxt::allocKey -*/ -char* MDNSResponder::clsHost::stcServiceTxt::allocKey(size_t p_stLength) -{ - releaseKey(); - if (p_stLength) - { - m_pcKey = new char[p_stLength + 1]; - } - return m_pcKey; -} - -/* - MDNSResponder::clsHost::stcServiceTxt::setKey -*/ -bool MDNSResponder::clsHost::stcServiceTxt::setKey(const char* p_pcKey, - size_t p_stLength) -{ - bool bResult = false; - - releaseKey(); - if (p_stLength) - { - if (allocKey(p_stLength)) - { - strncpy(m_pcKey, p_pcKey, p_stLength); - m_pcKey[p_stLength] = 0; - bResult = true; - } - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcServiceTxt::setKey -*/ -bool MDNSResponder::clsHost::stcServiceTxt::setKey(const char* p_pcKey) -{ - return setKey(p_pcKey, (p_pcKey ? strlen(p_pcKey) : 0)); -} - -/* - MDNSResponder::clsHost::stcServiceTxt::releaseKey -*/ -bool MDNSResponder::clsHost::stcServiceTxt::releaseKey(void) -{ - if (m_pcKey) - { - delete[] m_pcKey; - m_pcKey = 0; - } - return true; -} - -/* - MDNSResponder::clsHost::stcServiceTxt::allocValue -*/ -char* MDNSResponder::clsHost::stcServiceTxt::allocValue(size_t p_stLength) -{ - releaseValue(); - if (p_stLength) - { - m_pcValue = new char[p_stLength + 1]; - } - return m_pcValue; -} - -/* - MDNSResponder::clsHost::stcServiceTxt::setValue -*/ -bool MDNSResponder::clsHost::stcServiceTxt::setValue(const char* p_pcValue, - size_t p_stLength) -{ - bool bResult = false; - - releaseValue(); - if (p_stLength) - { - if (allocValue(p_stLength)) - { - strncpy(m_pcValue, p_pcValue, p_stLength); - m_pcValue[p_stLength] = 0; - bResult = true; - } - } - else // No value -> also OK - { - bResult = true; - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcServiceTxt::setValue -*/ -bool MDNSResponder::clsHost::stcServiceTxt::setValue(const char* p_pcValue) -{ - return setValue(p_pcValue, (p_pcValue ? strlen(p_pcValue) : 0)); -} - -/* - MDNSResponder::clsHost::stcServiceTxt::releaseValue -*/ -bool MDNSResponder::clsHost::stcServiceTxt::releaseValue(void) -{ - if (m_pcValue) - { - delete[] m_pcValue; - m_pcValue = 0; - } - return true; -} - -/* - MDNSResponder::clsHost::stcServiceTxt::set -*/ -bool MDNSResponder::clsHost::stcServiceTxt::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::clsHost::stcServiceTxt::update -*/ -bool MDNSResponder::clsHost::stcServiceTxt::update(const char* p_pcValue) -{ - return setValue(p_pcValue); -} - -/* - MDNSResponder::clsHost::stcServiceTxt::length - - length of eg. 'c#=1' without any closing '\0' -*/ -size_t MDNSResponder::clsHost::stcServiceTxt::length(void) const -{ - size_t stLength = 0; - if (m_pcKey) - { - stLength += strlen(m_pcKey); // Key - stLength += 1; // '=' - stLength += (m_pcValue ? strlen(m_pcValue) : 0); // Value - } - return stLength; -} - - -/** - MDNSResponder::clsHost::stcServiceTxts - - 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::clsHost::stcServiceTxts::stcServiceTxts contructor -*/ -MDNSResponder::clsHost::stcServiceTxts::stcServiceTxts(void) - : m_pTxts(0), - m_pcCache(0) -{ -} - -/* - MDNSResponder::clsHost::stcServiceTxts::stcServiceTxts copy-constructor -*/ -MDNSResponder::clsHost::stcServiceTxts::stcServiceTxts(const stcServiceTxts& p_Other) - : m_pTxts(0), - m_pcCache(0) -{ - operator=(p_Other); -} - -/* - MDNSResponder::clsHost::stcServiceTxts::~stcServiceTxts destructor -*/ -MDNSResponder::clsHost::stcServiceTxts::~stcServiceTxts(void) -{ - clear(); -} - -/* - MDNSResponder::clsHost::stcServiceTxts::operator= -*/ -MDNSResponder::clsHost::stcServiceTxts& MDNSResponder::clsHost::stcServiceTxts::operator=(const stcServiceTxts& p_Other) -{ - if (this != &p_Other) - { - clear(); - - for (stcServiceTxt* pOtherTxt = p_Other.m_pTxts; pOtherTxt; pOtherTxt = pOtherTxt->m_pNext) - { - add(new stcServiceTxt(*pOtherTxt)); - } - } - return *this; -} - -/* - MDNSResponder::clsHost::stcServiceTxts::clear -*/ -bool MDNSResponder::clsHost::stcServiceTxts::clear(void) -{ - while (m_pTxts) - { - stcServiceTxt* pNext = m_pTxts->m_pNext; - delete m_pTxts; - m_pTxts = pNext; - } - return clearCache(); -} - -/* - MDNSResponder::clsHost::stcServiceTxts::clearCache -*/ -bool MDNSResponder::clsHost::stcServiceTxts::clearCache(void) -{ - if (m_pcCache) - { - delete[] m_pcCache; - m_pcCache = 0; - } - return true; -} - -/* - MDNSResponder::clsHost::stcServiceTxts::add -*/ -bool MDNSResponder::clsHost::stcServiceTxts::add(MDNSResponder::clsHost::stcServiceTxt* p_pTxt) -{ - bool bResult = false; - - if (p_pTxt) - { - p_pTxt->m_pNext = m_pTxts; - m_pTxts = p_pTxt; - bResult = true; - } - return ((clearCache()) && - (bResult)); -} - -/* - MDNSResponder::clsHost::stcServiceTxts::remove -*/ -bool MDNSResponder::clsHost::stcServiceTxts::remove(stcServiceTxt* p_pTxt) -{ - bool bResult = false; - - if (p_pTxt) - { - stcServiceTxt* 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 ((clearCache()) && - (bResult)); -} - -/* - MDNSResponder::clsHost::stcServiceTxts::removeTempTxts -*/ -bool MDNSResponder::clsHost::stcServiceTxts::removeTempTxts(void) -{ - bool bResult = true; - - stcServiceTxt* pTxt = m_pTxts; - while ((bResult) && - (pTxt)) - { - stcServiceTxt* pNext = pTxt->m_pNext; - if (pTxt->m_bTemp) - { - bResult = remove(pTxt); - } - pTxt = pNext; - } - return ((clearCache()) && - (bResult)); -} - -/* - MDNSResponder::clsHost::stcServiceTxts::find -*/ -MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::stcServiceTxts::find(const char* p_pcKey) -{ - stcServiceTxt* pResult = 0; - - for (stcServiceTxt* pTxt = m_pTxts; pTxt; pTxt = pTxt->m_pNext) - { - if ((p_pcKey) && - (0 == strcmp(pTxt->m_pcKey, p_pcKey))) - { - pResult = pTxt; - break; - } - } - return pResult; -} - -/* - MDNSResponder::clsHost::stcServiceTxts::find -*/ -const MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::stcServiceTxts::find(const char* p_pcKey) const -{ - const stcServiceTxt* pResult = 0; - - for (const stcServiceTxt* pTxt = m_pTxts; pTxt; pTxt = pTxt->m_pNext) - { - if ((p_pcKey) && - (0 == strcmp(pTxt->m_pcKey, p_pcKey))) - { - - pResult = pTxt; - break; - } - } - return pResult; -} - -/* - MDNSResponder::clsHost::stcServiceTxts::find -*/ -MDNSResponder::clsHost::stcServiceTxt* MDNSResponder::clsHost::stcServiceTxts::find(const stcServiceTxt* p_pTxt) -{ - stcServiceTxt* pResult = 0; - - for (stcServiceTxt* pTxt = m_pTxts; pTxt; pTxt = pTxt->m_pNext) - { - if (p_pTxt == pTxt) - { - pResult = pTxt; - break; - } - } - return pResult; -} - -/* - MDNSResponder::clsHost::stcServiceTxts::length -*/ -uint16_t MDNSResponder::clsHost::stcServiceTxts::length(void) const -{ - uint16_t u16Length = 0; - - stcServiceTxt* pTxt = m_pTxts; - while (pTxt) - { - u16Length += 1; // Length byte - u16Length += pTxt->length(); // Text - pTxt = pTxt->m_pNext; - } - return u16Length; -} - -/* - MDNSResponder::clsHost::stcServiceTxts::c_strLength - - (incl. closing '\0'). Length bytes place is used for delimiting ';' and closing '\0' -*/ -size_t MDNSResponder::clsHost::stcServiceTxts::c_strLength(void) const -{ - return length(); -} - -/* - MDNSResponder::clsHost::stcServiceTxts::c_str -*/ -bool MDNSResponder::clsHost::stcServiceTxts::c_str(char* p_pcBuffer) -{ - bool bResult = false; - - if (p_pcBuffer) - { - bResult = true; - - *p_pcBuffer = 0; - for (stcServiceTxt* pTxt = m_pTxts; ((bResult) && (pTxt)); pTxt = pTxt->m_pNext) - { - size_t stLength; - if ((bResult = (0 != (stLength = (pTxt->m_pcKey ? strlen(pTxt->m_pcKey) : 0))))) - { - if (pTxt != m_pTxts) - { - *p_pcBuffer++ = ';'; - } - strncpy(p_pcBuffer, pTxt->m_pcKey, stLength); p_pcBuffer[stLength] = 0; - p_pcBuffer += stLength; - *p_pcBuffer++ = '='; - if ((stLength = (pTxt->m_pcValue ? strlen(pTxt->m_pcValue) : 0))) - { - strncpy(p_pcBuffer, pTxt->m_pcValue, stLength); p_pcBuffer[stLength] = 0; - p_pcBuffer += stLength; - } - } - } - *p_pcBuffer++ = 0; - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcServiceTxts::c_str -*/ -const char* MDNSResponder::clsHost::stcServiceTxts::c_str(void) const -{ - - if ((!m_pcCache) && - (m_pTxts) && - ((((stcServiceTxts*)this)->m_pcCache = new char[c_strLength()]))) // TRANSPARENT caching - { - ((stcServiceTxts*)this)->c_str(m_pcCache); - } - return m_pcCache; -} - -/* - MDNSResponder::clsHost::stcServiceTxts::bufferLength - - (incl. closing '\0'). -*/ -size_t MDNSResponder::clsHost::stcServiceTxts::bufferLength(void) const -{ - return (length() + 1); -} - -/* - MDNSResponder::clsHost::stcServiceTxts::toBuffer -*/ -bool MDNSResponder::clsHost::stcServiceTxts::buffer(char* p_pcBuffer) -{ - bool bResult = false; - - if (p_pcBuffer) - { - bResult = true; - - *p_pcBuffer = 0; - for (stcServiceTxt* 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 ? strlen(pTxt->m_pcKey) : 0))))) - { - memcpy(p_pcBuffer, pTxt->m_pcKey, stLength); - p_pcBuffer += stLength; - *p_pcBuffer++ = '='; - if ((stLength = (pTxt->m_pcValue ? strlen(pTxt->m_pcValue) : 0))) - { - memcpy(p_pcBuffer, pTxt->m_pcValue, stLength); - p_pcBuffer += stLength; - } - } - } - *p_pcBuffer++ = 0; - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcServiceTxts::compare -*/ -bool MDNSResponder::clsHost::stcServiceTxts::compare(const MDNSResponder::clsHost::stcServiceTxts& p_Other) const -{ - bool bResult = false; - - if ((bResult = (length() == p_Other.length()))) - { - // Compare A->B - for (const stcServiceTxt* pTxt = m_pTxts; ((bResult) && (pTxt)); pTxt = pTxt->m_pNext) - { - const stcServiceTxt* pOtherTxt = p_Other.find(pTxt->m_pcKey); - bResult = ((pOtherTxt) && - (pTxt->m_pcValue) && - (pOtherTxt->m_pcValue) && - (strlen(pTxt->m_pcValue) == strlen(pOtherTxt->m_pcValue)) && - (0 == strcmp(pTxt->m_pcValue, pOtherTxt->m_pcValue))); - } - // Compare B->A - for (const stcServiceTxt* pOtherTxt = p_Other.m_pTxts; ((bResult) && (pOtherTxt)); pOtherTxt = pOtherTxt->m_pNext) - { - const stcServiceTxt* pTxt = find(pOtherTxt->m_pcKey); - bResult = ((pTxt) && - (pOtherTxt->m_pcValue) && - (pTxt->m_pcValue) && - (strlen(pOtherTxt->m_pcValue) == strlen(pTxt->m_pcValue)) && - (0 == strcmp(pOtherTxt->m_pcValue, pTxt->m_pcValue))); - } - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcServiceTxts::operator== -*/ -bool MDNSResponder::clsHost::stcServiceTxts::operator==(const stcServiceTxts& p_Other) const -{ - return compare(p_Other); -} - -/* - MDNSResponder::clsHost::stcServiceTxts::operator!= -*/ -bool MDNSResponder::clsHost::stcServiceTxts::operator!=(const stcServiceTxts& p_Other) const -{ - return !compare(p_Other); -} - - -/** - MDNSResponder::clsHost::stcProbeInformation_Base - - Probing status information for a host or service domain - -*/ - -/* - MDNSResponder::clsHost::stcProbeInformation_Base::stcProbeInformation_Base constructor -*/ -MDNSResponder::clsHost::stcProbeInformation_Base::stcProbeInformation_Base(void) - : m_ProbingStatus(enuProbingStatus::WaitingForData), - m_u8SentCount(0), - m_Timeout(std::numeric_limits::max()), - m_bConflict(false), - m_bTiebreakNeeded(false) -{ -} - -/* - MDNSResponder::clsHost::stcProbeInformation_Base::clear -*/ -bool MDNSResponder::clsHost::stcProbeInformation_Base::clear(void) -{ - m_ProbingStatus = enuProbingStatus::WaitingForData; - m_u8SentCount = 0; - m_Timeout.reset(std::numeric_limits::max()); - m_bConflict = false; - m_bTiebreakNeeded = false; - - return true; -} - - -/** - MDNSResponder::clsHost::stcProbeInformation_Host - - Probing status information for a host or service domain - -*/ - -/* - MDNSResponder::clsHost::stcProbeInformation_Host::stcProbeInformation_Host constructor -*/ -MDNSResponder::clsHost::stcProbeInformation_Host::stcProbeInformation_Host(void) - : m_fnProbeResultCallback(0) -{ -} - -/* - MDNSResponder::clsHost::stcProbeInformation_Host::clear -*/ -bool MDNSResponder::clsHost::stcProbeInformation_Host::clear(bool p_bClearUserdata /*= false*/) -{ - if (p_bClearUserdata) - { - m_fnProbeResultCallback = 0; - } - return stcProbeInformation_Base::clear(); -} - - -/** - MDNSResponder::clsHost::stcProbeInformation_Service - - Probing status information for a host or service domain - -*/ - -/* - MDNSResponder::clsHost::stcProbeInformation_Service::stcProbeInformation_Service constructor -*/ -MDNSResponder::clsHost::stcProbeInformation_Service::stcProbeInformation_Service(void) - : m_fnProbeResultCallback(0) -{ -} - -/* - MDNSResponder::clsHost::stcProbeInformation_Service::clear -*/ -bool MDNSResponder::clsHost::stcProbeInformation_Service::clear(bool p_bClearUserdata /*= false*/) -{ - if (p_bClearUserdata) - { - m_fnProbeResultCallback = 0; - } - return stcProbeInformation_Base::clear(); -} - - -/** - MDNSResponder::clsHost::stcService - - 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::clsHost::stcService::stcService constructor -*/ -MDNSResponder::clsHost::stcService::stcService(const char* p_pcName /*= 0*/, - const char* p_pcServiceType /*= 0*/, - const char* p_pcProtocol /*= 0*/) - : m_pNext(0), - m_pcName(0), - m_bAutoName(false), - m_pcServiceType(0), - m_pcProtocol(0), - m_u16Port(0), - m_u32ReplyMask(0), - m_fnTxtCallback(0) -{ - setName(p_pcName); - setServiceType(p_pcServiceType); - setProtocol(p_pcProtocol); -} - -/* - MDNSResponder::clsHost::stcService::~stcService destructor -*/ -MDNSResponder::clsHost::stcService::~stcService(void) -{ - releaseName(); - releaseServiceType(); - releaseProtocol(); -} - -/* - MDNSResponder::clsHost::stcService::setName -*/ -bool MDNSResponder::clsHost::stcService::setName(const char* p_pcName) -{ - bool bResult = false; - - releaseName(); - size_t stLength = (p_pcName ? strlen(p_pcName) : 0); - if (stLength) - { - if ((bResult = (0 != (m_pcName = new char[stLength + 1])))) - { - strncpy(m_pcName, p_pcName, stLength); - m_pcName[stLength] = 0; - } - } - else - { - bResult = true; - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcService::releaseName -*/ -bool MDNSResponder::clsHost::stcService::releaseName(void) -{ - if (m_pcName) - { - delete[] m_pcName; - m_pcName = 0; - } - return true; -} - -/* - MDNSResponder::clsHost::stcService::setServiceType -*/ -bool MDNSResponder::clsHost::stcService::setServiceType(const char* p_pcServiceType) -{ - bool bResult = false; - - releaseServiceType(); - size_t stLength = (p_pcServiceType ? strlen(p_pcServiceType) : 0); - if (stLength) - { - if ((bResult = (0 != (m_pcServiceType = new char[stLength + 1])))) - { - strncpy(m_pcServiceType, p_pcServiceType, stLength); - m_pcServiceType[stLength] = 0; - } - } - else - { - bResult = true; - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcService::releaseServiceType -*/ -bool MDNSResponder::clsHost::stcService::releaseServiceType(void) -{ - if (m_pcServiceType) - { - delete[] m_pcServiceType; - m_pcServiceType = 0; - } - return true; -} - -/* - MDNSResponder::clsHost::stcService::setProtocol -*/ -bool MDNSResponder::clsHost::stcService::setProtocol(const char* p_pcProtocol) -{ - bool bResult = false; - - releaseProtocol(); - size_t stLength = (p_pcProtocol ? strlen(p_pcProtocol) : 0); - if (stLength) - { - if ((bResult = (0 != (m_pcProtocol = new char[stLength + 1])))) - { - strncpy(m_pcProtocol, p_pcProtocol, stLength); - m_pcProtocol[stLength] = 0; - } - } - else - { - bResult = true; - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcService::releaseProtocol -*/ -bool MDNSResponder::clsHost::stcService::releaseProtocol(void) -{ - if (m_pcProtocol) - { - delete[] m_pcProtocol; - m_pcProtocol = 0; - } - return true; -} - -/* - MDNSResponder::clsHost::stcService::probeStatus -*/ -bool MDNSResponder::clsHost::stcService::probeStatus(void) const -{ - return (enuProbingStatus::Done == m_ProbeInformation.m_ProbingStatus); -} - - -/** - MDNSResponder::clsHost::stcMsgHeader - - A MDNS message haeder. - -*/ - -/* - MDNSResponder::clsHost::stcMsgHeader::stcMsgHeader -*/ -MDNSResponder::clsHost::stcMsgHeader::stcMsgHeader(uint16_t p_u16ID /*= 0*/, - bool p_bQR /*= false*/, - uint8_t p_u8Opcode /*= 0*/, - bool p_bAA /*= false*/, - bool p_bTC /*= false*/, - bool p_bRD /*= false*/, - bool p_bRA /*= false*/, - uint8_t p_u8RCode /*= 0*/, - uint16_t p_u16QDCount /*= 0*/, - uint16_t p_u16ANCount /*= 0*/, - uint16_t p_u16NSCount /*= 0*/, - uint16_t p_u16ARCount /*= 0*/) - : m_u16ID(p_u16ID), - m_1bQR(p_bQR), m_4bOpcode(p_u8Opcode), m_1bAA(p_bAA), m_1bTC(p_bTC), m_1bRD(p_bRD), - m_1bRA(p_bRA), m_3bZ(0), m_4bRCode(p_u8RCode), - m_u16QDCount(p_u16QDCount), - m_u16ANCount(p_u16ANCount), - m_u16NSCount(p_u16NSCount), - m_u16ARCount(p_u16ARCount) -{ -} - - -/** - MDNSResponder::clsHost::stcRRDomain - - 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::clsHost::stcRRDomain::stcRRDomain constructor -*/ -MDNSResponder::clsHost::stcRRDomain::stcRRDomain(void) - : m_u16NameLength(0), - m_pcDecodedName(0) -{ - clear(); -} - -/* - MDNSResponder::clsHost::stcRRDomain::stcRRDomain copy-constructor -*/ -MDNSResponder::clsHost::stcRRDomain::stcRRDomain(const stcRRDomain& p_Other) - : m_u16NameLength(0), - m_pcDecodedName(0) -{ - operator=(p_Other); -} - -/* - MDNSResponder::clsHost::stcRRDomain::stcRRDomain destructor -*/ -MDNSResponder::clsHost::stcRRDomain::~stcRRDomain(void) -{ - clear(); -} - -/* - MDNSResponder::clsHost::stcRRDomain::operator = -*/ -MDNSResponder::clsHost::stcRRDomain& MDNSResponder::clsHost::stcRRDomain::operator=(const stcRRDomain& p_Other) -{ - if (&p_Other != this) - { - clear(); - memcpy(m_acName, p_Other.m_acName, sizeof(m_acName)); - m_u16NameLength = p_Other.m_u16NameLength; - } - return *this; -} - -/* - MDNSResponder::clsHost::stcRRDomain::clear -*/ -bool MDNSResponder::clsHost::stcRRDomain::clear(void) -{ - memset(m_acName, 0, sizeof(m_acName)); - m_u16NameLength = 0; - return clearNameCache(); -} - -/* - MDNSResponder::clsHost::stcRRDomain::clearNameCache -*/ -bool MDNSResponder::clsHost::stcRRDomain::clearNameCache(void) -{ - if (m_pcDecodedName) - { - delete[] m_pcDecodedName; - m_pcDecodedName = 0; - } - return true; -} - -/* - MDNSResponder::clsHost::stcRRDomain::addLabel -*/ -bool MDNSResponder::clsHost::stcRRDomain::addLabel(const char* p_pcLabel, - bool p_bPrependUnderline /*= false*/) -{ - bool bResult = false; - - size_t stLength = (p_pcLabel - ? (strlen(p_pcLabel) + (p_bPrependUnderline ? 1 : 0)) - : 0); - if ((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; - } - strncpy(&(m_acName[m_u16NameLength]), p_pcLabel, stLength); m_acName[m_u16NameLength + stLength] = 0; - m_u16NameLength += stLength; - } - bResult = clearNameCache(); - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcRRDomain::compare -*/ -bool MDNSResponder::clsHost::stcRRDomain::compare(const stcRRDomain& p_Other) const -{ - bool bResult = false; - - if (m_u16NameLength == p_Other.m_u16NameLength) - { - const char* pT = m_acName; - const char* pO = p_Other.m_acName; - while ((pT) && - (pO) && - (*((unsigned char*)pT) == *((unsigned char*)pO)) && // Same length AND - (0 == strncasecmp((pT + 1), (pO + 1), *((unsigned char*)pT)))) // Same content - { - if (*((unsigned char*)pT)) // Not 0 - { - pT += (1 + * ((unsigned char*)pT)); // Shift by length byte and lenght - pO += (1 + * ((unsigned char*)pO)); - } - else // Is 0 -> Successfully reached the end - { - bResult = true; - break; - } - } - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcRRDomain::operator == -*/ -bool MDNSResponder::clsHost::stcRRDomain::operator==(const stcRRDomain& p_Other) const -{ - return compare(p_Other); -} - -/* - MDNSResponder::clsHost::stcRRDomain::operator != -*/ -bool MDNSResponder::clsHost::stcRRDomain::operator!=(const stcRRDomain& p_Other) const -{ - return !compare(p_Other); -} - -/* - MDNSResponder::clsHost::stcRRDomain::operator > -*/ -bool MDNSResponder::clsHost::stcRRDomain::operator>(const stcRRDomain& p_Other) const -{ - // TODO: Check, if this is a good idea... - return !compare(p_Other); -} - -/* - MDNSResponder::clsHost::stcRRDomain::c_strLength -*/ -size_t MDNSResponder::clsHost::stcRRDomain::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::clsHost::stcRRDomain::c_str (const) -*/ -bool MDNSResponder::clsHost::stcRRDomain::c_str(char* p_pcBuffer) const -{ - bool bResult = false; - - if (p_pcBuffer) - { - *p_pcBuffer = 0; - unsigned char* pucLabelLength = (unsigned char*)m_acName; - while (*pucLabelLength) - { - memcpy(p_pcBuffer, (const char*)(pucLabelLength + 1), *pucLabelLength); - p_pcBuffer += *pucLabelLength; - pucLabelLength += (*pucLabelLength + 1); - *p_pcBuffer++ = (*pucLabelLength ? '.' : '\0'); - } - bResult = true; - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcRRDomain::c_str -*/ -const char* MDNSResponder::clsHost::stcRRDomain::c_str(void) const -{ - if ((!m_pcDecodedName) && - (m_u16NameLength) && - ((((stcRRDomain*)this)->m_pcDecodedName = new char[c_strLength()]))) // TRANSPARENT caching - { - ((stcRRDomain*)this)->c_str(m_pcDecodedName); - } - return m_pcDecodedName; -} - - -/** - MDNSResponder::clsHost::stcRRAttributes - - A MDNS attributes object. - -*/ - -/* - MDNSResponder::clsHost::stcRRAttributes::stcRRAttributes constructor -*/ -MDNSResponder::clsHost::stcRRAttributes::stcRRAttributes(uint16_t p_u16Type /*= 0*/, - uint16_t p_u16Class /*= 1 DNS_RRCLASS_IN Internet*/) - : m_u16Type(p_u16Type), - m_u16Class(p_u16Class) -{ -} - -/* - MDNSResponder::clsHost::stcRRAttributes::stcRRAttributes copy-constructor -*/ -MDNSResponder::clsHost::stcRRAttributes::stcRRAttributes(const MDNSResponder::clsHost::stcRRAttributes& p_Other) -{ - operator=(p_Other); -} - -/* - MDNSResponder::clsHost::stcRRAttributes::operator = -*/ -MDNSResponder::clsHost::stcRRAttributes& MDNSResponder::clsHost::stcRRAttributes::operator=(const MDNSResponder::clsHost::stcRRAttributes& p_Other) -{ - if (&p_Other != this) - { - m_u16Type = p_Other.m_u16Type; - m_u16Class = p_Other.m_u16Class; - } - return *this; -} - - -/** - MDNSResponder::clsHost::stcRRHeader - - A MDNS record header (domain and attributes) object. - -*/ - -/* - MDNSResponder::clsHost::stcRRHeader::stcRRHeader constructor -*/ -MDNSResponder::clsHost::stcRRHeader::stcRRHeader(void) -{ -} - -/* - MDNSResponder::clsHost::stcRRHeader::stcRRHeader copy-constructor -*/ -MDNSResponder::clsHost::stcRRHeader::stcRRHeader(const stcRRHeader& p_Other) -{ - operator=(p_Other); -} - -/* - MDNSResponder::clsHost::stcRRHeader::operator = -*/ -MDNSResponder::clsHost::stcRRHeader& MDNSResponder::clsHost::stcRRHeader::operator=(const MDNSResponder::clsHost::stcRRHeader& p_Other) -{ - if (&p_Other != this) - { - m_Domain = p_Other.m_Domain; - m_Attributes = p_Other.m_Attributes; - } - return *this; -} - -/* - MDNSResponder::clsHost::stcRRHeader::clear -*/ -bool MDNSResponder::clsHost::stcRRHeader::clear(void) -{ - m_Domain.clear(); - return true; -} - - -/** - MDNSResponder::clsHost::stcRRQuestion - - A MDNS question record object (header + question flags) - -*/ - -/* - MDNSResponder::clsHost::stcRRQuestion::stcRRQuestion constructor -*/ -MDNSResponder::clsHost::stcRRQuestion::stcRRQuestion(void) - : m_pNext(0), - m_bUnicast(false) -{ -} - - -/** - MDNSResponder::clsHost::stcNSECBitmap - - A MDNS question record object (header + question flags) - -*/ - -/* - MDNSResponder::clsHost::stcNSECBitmap::stcNSECBitmap constructor -*/ -MDNSResponder::clsHost::stcNSECBitmap::stcNSECBitmap(void) -{ - clear(); -} - -/* - MDNSResponder::clsHost::stcNSECBitmap::stcNSECBitmap destructor -*/ -bool MDNSResponder::clsHost::stcNSECBitmap::clear(void) -{ - memset(m_au8BitmapData, 0, sizeof(m_au8BitmapData)); - return true; -} - -/* - MDNSResponder::clsHost::stcNSECBitmap::length -*/ -uint16_t MDNSResponder::clsHost::stcNSECBitmap::length(void) const -{ - return sizeof(m_au8BitmapData); // 6 -} - -/* - MDNSResponder::clsHost::stcNSECBitmap::setBit -*/ -bool MDNSResponder::clsHost::stcNSECBitmap::setBit(uint16_t p_u16Bit) -{ - bool bResult = false; - - if ((p_u16Bit) && - (length() > (p_u16Bit / 8))) // bit between 0..47(2F) - { - - uint8_t& ru8Byte = m_au8BitmapData[p_u16Bit / 8]; - uint8_t u8Flag = 1 << (7 - (p_u16Bit % 8)); // (7 - (0..7)) = 7..0 - - ru8Byte |= u8Flag; - - bResult = true; - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcNSECBitmap::getBit -*/ -bool MDNSResponder::clsHost::stcNSECBitmap::getBit(uint16_t p_u16Bit) const -{ - bool bResult = false; - - if ((p_u16Bit) && - (length() > (p_u16Bit / 8))) // bit between 0..47(2F) - { - - uint8_t u8Byte = m_au8BitmapData[p_u16Bit / 8]; - uint8_t u8Flag = 1 << (7 - (p_u16Bit % 8)); // (7 - (0..7)) = 7..0 - - bResult = (u8Byte & u8Flag); - } - return bResult; -} - - -/** - MDNSResponder::clsHost::stcRRAnswer - - A MDNS answer record object (header + answer content). - This is a 'virtual' base class for all other MDNS answer classes. - -*/ - -/* - MDNSResponder::clsHost::stcRRAnswer::stcRRAnswer constructor -*/ -MDNSResponder::clsHost::stcRRAnswer::stcRRAnswer(enuAnswerType p_AnswerType, - const MDNSResponder::clsHost::stcRRHeader& 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::clsHost::stcRRAnswer::~stcRRAnswer destructor -*/ -MDNSResponder::clsHost::stcRRAnswer::~stcRRAnswer(void) -{ -} - -/* - MDNSResponder::clsHost::stcRRAnswer::answerType -*/ -MDNSResponder::clsHost::enuAnswerType MDNSResponder::clsHost::stcRRAnswer::answerType(void) const -{ - return m_AnswerType; -} - -/* - MDNSResponder::clsHost::stcRRAnswer::clear -*/ -bool MDNSResponder::clsHost::stcRRAnswer::clear(void) -{ - m_pNext = 0; - m_Header.clear(); - return true; -} - - -/** - MDNSResponder::clsHost::stcRRAnswerA - - A MDNS A answer object. - Extends the base class by an IPv4 address member. - -*/ - -#ifdef MDNS_IPV4_SUPPORT -/* - MDNSResponder::clsHost::stcRRAnswerA::stcRRAnswerA constructor -*/ -MDNSResponder::clsHost::stcRRAnswerA::stcRRAnswerA(const MDNSResponder::clsHost::stcRRHeader& p_Header, - uint32_t p_u32TTL) - : stcRRAnswer(enuAnswerType::A, p_Header, p_u32TTL), - m_IPAddress() -{ -} - -/* - MDNSResponder::clsHost::stcRRAnswerA::stcRRAnswerA destructor -*/ -MDNSResponder::clsHost::stcRRAnswerA::~stcRRAnswerA(void) -{ - clear(); -} - -/* - MDNSResponder::clsHost::stcRRAnswerA::clear -*/ -bool MDNSResponder::clsHost::stcRRAnswerA::clear(void) -{ - m_IPAddress = IPAddress(); - return true; -} -#endif - - -/** - MDNSResponder::clsHost::stcRRAnswerPTR - - A MDNS PTR answer object. - Extends the base class by a MDNS domain member. - -*/ - -/* - MDNSResponder::clsHost::stcRRAnswerPTR::stcRRAnswerPTR constructor -*/ -MDNSResponder::clsHost::stcRRAnswerPTR::stcRRAnswerPTR(const MDNSResponder::clsHost::stcRRHeader& p_Header, - uint32_t p_u32TTL) - : stcRRAnswer(enuAnswerType::PTR, p_Header, p_u32TTL) -{ -} - -/* - MDNSResponder::clsHost::stcRRAnswerPTR::~stcRRAnswerPTR destructor -*/ -MDNSResponder::clsHost::stcRRAnswerPTR::~stcRRAnswerPTR(void) -{ - clear(); -} - -/* - MDNSResponder::clsHost::stcRRAnswerPTR::clear -*/ -bool MDNSResponder::clsHost::stcRRAnswerPTR::clear(void) -{ - m_PTRDomain.clear(); - return true; -} - - -/** - MDNSResponder::clsHost::stcRRAnswerTXT - - A MDNS TXT answer object. - Extends the base class by a MDNS TXT items list member. - -*/ - -/* - MDNSResponder::clsHost::stcRRAnswerTXT::stcRRAnswerTXT constructor -*/ -MDNSResponder::clsHost::stcRRAnswerTXT::stcRRAnswerTXT(const MDNSResponder::clsHost::stcRRHeader& p_Header, - uint32_t p_u32TTL) - : stcRRAnswer(enuAnswerType::TXT, p_Header, p_u32TTL) -{ -} - -/* - MDNSResponder::clsHost::stcRRAnswerTXT::~stcRRAnswerTXT destructor -*/ -MDNSResponder::clsHost::stcRRAnswerTXT::~stcRRAnswerTXT(void) -{ - clear(); -} - -/* - MDNSResponder::clsHost::stcRRAnswerTXT::clear -*/ -bool MDNSResponder::clsHost::stcRRAnswerTXT::clear(void) -{ - m_Txts.clear(); - return true; -} - - -/** - MDNSResponder::clsHost::stcRRAnswerAAAA - - A MDNS AAAA answer object. - Extends the base class by an IPv6 address member. - -*/ - -#ifdef MDNS_IPV6_SUPPORT -/* - MDNSResponder::clsHost::stcRRAnswerAAAA::stcRRAnswerAAAA constructor -*/ -MDNSResponder::clsHost::stcRRAnswerAAAA::stcRRAnswerAAAA(const MDNSResponder::clsHost::stcRRHeader& p_Header, - uint32_t p_u32TTL) - : stcRRAnswer(enuAnswerType::AAAA, p_Header, p_u32TTL), - m_IPAddress() -{ -} - -/* - MDNSResponder::clsHost::stcRRAnswerAAAA::~stcRRAnswerAAAA destructor -*/ -MDNSResponder::clsHost::stcRRAnswerAAAA::~stcRRAnswerAAAA(void) -{ - clear(); -} - -/* - MDNSResponder::clsHost::stcRRAnswerAAAA::clear -*/ -bool MDNSResponder::clsHost::stcRRAnswerAAAA::clear(void) -{ - m_IPAddress = IPAddress(); - return true; -} -#endif - - -/** - MDNSResponder::clsHost::stcRRAnswerSRV - - A MDNS SRV answer object. - Extends the base class by a port member. - -*/ - -/* - MDNSResponder::clsHost::stcRRAnswerSRV::stcRRAnswerSRV constructor -*/ -MDNSResponder::clsHost::stcRRAnswerSRV::stcRRAnswerSRV(const MDNSResponder::clsHost::stcRRHeader& p_Header, - uint32_t p_u32TTL) - : stcRRAnswer(enuAnswerType::SRV, p_Header, p_u32TTL), - m_u16Priority(0), - m_u16Weight(0), - m_u16Port(0) -{ -} - -/* - MDNSResponder::clsHost::stcRRAnswerSRV::~stcRRAnswerSRV destructor -*/ -MDNSResponder::clsHost::stcRRAnswerSRV::~stcRRAnswerSRV(void) -{ - clear(); -} - -/* - MDNSResponder::clsHost::stcRRAnswerSRV::clear -*/ -bool MDNSResponder::clsHost::stcRRAnswerSRV::clear(void) -{ - m_u16Priority = 0; - m_u16Weight = 0; - m_u16Port = 0; - m_SRVDomain.clear(); - return true; -} - - -/** - MDNSResponder::clsHost::stcRRAnswerGeneric - - An unknown (generic) MDNS answer object. - Extends the base class by a RDATA buffer member. - -*/ - -/* - MDNSResponder::clsHost::stcRRAnswerGeneric::stcRRAnswerGeneric constructor -*/ -MDNSResponder::clsHost::stcRRAnswerGeneric::stcRRAnswerGeneric(const stcRRHeader& p_Header, - uint32_t p_u32TTL) - : stcRRAnswer(enuAnswerType::Generic, p_Header, p_u32TTL), - m_u16RDLength(0), - m_pu8RDData(0) -{ -} - -/* - MDNSResponder::clsHost::stcRRAnswerGeneric::~stcRRAnswerGeneric destructor -*/ -MDNSResponder::clsHost::stcRRAnswerGeneric::~stcRRAnswerGeneric(void) -{ - clear(); -} - -/* - MDNSResponder::clsHost::stcRRAnswerGeneric::clear -*/ -bool MDNSResponder::clsHost::stcRRAnswerGeneric::clear(void) -{ - if (m_pu8RDData) - { - delete[] m_pu8RDData; - m_pu8RDData = 0; - } - m_u16RDLength = 0; - - return true; -} - - -/** - MDNSResponder::clsHost::stcSendParameter - - 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::clsHost::stcSendParameter::stcDomainCacheItem - - A cached host or service domain, incl. the offset in the UDP output buffer. - -*/ - -/* - MDNSResponder::clsHost::stcSendParameter::stcDomainCacheItem::stcDomainCacheItem constructor -*/ -MDNSResponder::clsHost::stcSendParameter::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::clsHost::stcSendParameter -*/ - -/* - MDNSResponder::clsHost::stcSendParameter::stcSendParameter constructor -*/ -MDNSResponder::clsHost::stcSendParameter::stcSendParameter(void) - : m_pQuestions(0), - m_Response(enuResponseType::None), - m_pDomainCacheItems(0) -{ - clear(); -} - -/* - MDNSResponder::clsHost::stcSendParameter::~stcSendParameter destructor -*/ -MDNSResponder::clsHost::stcSendParameter::~stcSendParameter(void) -{ - clear(); -} - -/* - MDNSResponder::clsHost::stcSendParameter::clear -*/ -bool MDNSResponder::clsHost::stcSendParameter::clear(void) -{ - m_u16ID = 0; - flushQuestions(); - m_u32HostReplyMask = 0; - - m_bLegacyQuery = false; - m_Response = enuResponseType::None; - m_bAuthorative = false; - m_bCacheFlush = true; - m_bUnicast = false; - m_bUnannounce = false; - - m_u16Offset = 0; - flushDomainCache(); - return true; -} - -/* - MDNSResponder::clsHost::stcSendParameter::flushQuestions -*/ -bool MDNSResponder::clsHost::stcSendParameter::flushQuestions(void) -{ - while (m_pQuestions) - { - stcRRQuestion* pNext = m_pQuestions->m_pNext; - delete m_pQuestions; - m_pQuestions = pNext; - } - return true; -} - -/* - MDNSResponder::clsHost::stcSendParameter::flushDomainCache -*/ -bool MDNSResponder::clsHost::stcSendParameter::flushDomainCache(void) -{ - while (m_pDomainCacheItems) - { - stcDomainCacheItem* pNext = m_pDomainCacheItems->m_pNext; - delete m_pDomainCacheItems; - m_pDomainCacheItems = pNext; - } - return true; -} - -/* - MDNSResponder::clsHost::stcSendParameter::flushTempContent -*/ -bool MDNSResponder::clsHost::stcSendParameter::flushTempContent(void) -{ - m_u16Offset = 0; - flushDomainCache(); - return true; -} - -/* - MDNSResponder::clsHost::stcSendParameter::shiftOffset -*/ -bool MDNSResponder::clsHost::stcSendParameter::shiftOffset(uint16_t p_u16Shift) -{ - m_u16Offset += p_u16Shift; - return true; -} - -/* - MDNSResponder::clsHost::stcSendParameter::addDomainCacheItem -*/ -bool MDNSResponder::clsHost::stcSendParameter::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::clsHost::stcSendParameter::findCachedDomainOffset -*/ -uint16_t MDNSResponder::clsHost::stcSendParameter::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); -} - - -/** - MDNSResponder::clsHost::stcQuery - - 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::clsHost::stcQuery::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 IPv4 address - (- an IPv6 address) - - a MDNS TXTs - The existance of a component is flaged in 'm_u32ContentFlags'. - For every answer component a TTL value is maintained. - Answer objects can be connected to a linked list. - - For the host domain, service domain and TXTs components, a char array - representation can be retrieved (which is created on demand). - -*/ - -/** - MDNSResponder::clsHost::stcQuery::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::clsHost::stcQuery::stcAnswer::stcTTL::stcTTL constructor -*/ -MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::stcTTL(void) - : m_u32TTL(0), - m_TTLTimeout(std::numeric_limits::max()), - m_TimeoutLevel(static_cast(enuTimeoutLevel::None)) -{ -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::set -*/ -bool MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::set(uint32_t p_u32TTL) -{ - m_u32TTL = p_u32TTL; - if (m_u32TTL) - { - m_TimeoutLevel = static_cast(enuTimeoutLevel::Base); // Set to 80% - m_TTLTimeout.reset(timeout()); - } - else - { - m_TimeoutLevel = static_cast(enuTimeoutLevel::None); // undef - m_TTLTimeout.reset(std::numeric_limits::max()); - } - return true; -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::flagged -*/ -bool MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::flagged(void) const -{ - return ((m_u32TTL) && - (static_cast(enuTimeoutLevel::None) != m_TimeoutLevel) && - (((esp8266::polledTimeout::timeoutTemplate*)&m_TTLTimeout)->expired())); // Cast-away the const; in case of oneShot-timer OK (but ugly...) -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::restart -*/ -bool MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::restart(void) -{ - bool bResult = true; - - if ((static_cast(enuTimeoutLevel::Base) <= m_TimeoutLevel) && // >= 80% AND - (static_cast(enuTimeoutLevel::Final) > m_TimeoutLevel)) // < 100% - { - - m_TimeoutLevel += static_cast(enuTimeoutLevel::Interval); // increment by 5% - m_TTLTimeout.reset(timeout()); - } - else - { - bResult = false; - m_TTLTimeout.reset(std::numeric_limits::max()); - m_TimeoutLevel = static_cast(enuTimeoutLevel::None); - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::prepareDeletion -*/ -bool MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::prepareDeletion(void) -{ - m_TimeoutLevel = static_cast(enuTimeoutLevel::Final); - m_TTLTimeout.reset(1 * 1000); // See RFC 6762, 10.1 - - return true; -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::finalTimeoutLevel -*/ -bool MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::finalTimeoutLevel(void) const -{ - return (static_cast(enuTimeoutLevel::Final) == m_TimeoutLevel); -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::timeout -*/ -unsigned long MDNSResponder::clsHost::stcQuery::stcAnswer::stcTTL::timeout(void) const -{ - uint32_t u32Timeout = std::numeric_limits::max(); - - if (static_cast(enuTimeoutLevel::Base) == m_TimeoutLevel) // 80% - { - u32Timeout = (m_u32TTL * 800); // to milliseconds - } - else if ((static_cast(enuTimeoutLevel::Base) < m_TimeoutLevel) && // >80% AND - (static_cast(enuTimeoutLevel::Final) >= m_TimeoutLevel)) // <= 100% - { - - u32Timeout = (m_u32TTL * 50); - } // else: invalid - return u32Timeout; -} - - -/** - MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress - -*/ - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress::stcIPAddress constructor -*/ -MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress::stcIPAddress(IPAddress p_IPAddress, - uint32_t p_u32TTL /*= 0*/) - : m_pNext(0), - m_IPAddress(p_IPAddress) -{ - m_TTL.set(p_u32TTL); -} - - -/** - MDNSResponder::clsHost::stcQuery::stcAnswer -*/ - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::stcAnswer constructor -*/ -MDNSResponder::clsHost::stcQuery::stcAnswer::stcAnswer(void) - : m_pNext(0), - m_u16Port(0), -#ifdef MDNS_IPV4_SUPPORT - m_pIPv4Addresses(0), -#endif -#ifdef MDNS_IPV6_SUPPORT - m_pIPv6Addresses(0), -#endif - m_QueryAnswerFlags(0) -{ -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::~stcAnswer destructor -*/ -MDNSResponder::clsHost::stcQuery::stcAnswer::~stcAnswer(void) -{ - clear(); -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::clear -*/ -bool MDNSResponder::clsHost::stcQuery::stcAnswer::clear(void) -{ - return ( -#ifdef MDNS_IPV4_SUPPORT - (releaseIPv4Addresses()) && -#endif -#ifdef MDNS_IPV6_SUPPORT - (releaseIPv6Addresses()) -#endif - ); -} - -#ifdef MDNS_IPV4_SUPPORT -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::releaseIPv4Addresses -*/ -bool MDNSResponder::clsHost::stcQuery::stcAnswer::releaseIPv4Addresses(void) -{ - while (m_pIPv4Addresses) - { - stcIPAddress* pNext = m_pIPv4Addresses->m_pNext; - delete m_pIPv4Addresses; - m_pIPv4Addresses = pNext; - } - return true; -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::addIPv4Address -*/ -bool MDNSResponder::clsHost::stcQuery::stcAnswer::addIPv4Address(MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* p_pIPv4Address) -{ - bool bResult = false; - - if (p_pIPv4Address) - { - p_pIPv4Address->m_pNext = m_pIPv4Addresses; - m_pIPv4Addresses = p_pIPv4Address; - bResult = true; - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::removeIPv4Address -*/ -bool MDNSResponder::clsHost::stcQuery::stcAnswer::removeIPv4Address(MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* p_pIPv4Address) -{ - bool bResult = false; - - if (p_pIPv4Address) - { - stcIPAddress* pPred = m_pIPv4Addresses; - while ((pPred) && - (pPred->m_pNext != p_pIPv4Address)) - { - pPred = pPred->m_pNext; - } - if (pPred) - { - pPred->m_pNext = p_pIPv4Address->m_pNext; - delete p_pIPv4Address; - bResult = true; - } - else if (m_pIPv4Addresses == p_pIPv4Address) // No predecesor, but first item - { - m_pIPv4Addresses = p_pIPv4Address->m_pNext; - delete p_pIPv4Address; - bResult = true; - } - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::findIPv4Address (const) -*/ -const MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* MDNSResponder::clsHost::stcQuery::stcAnswer::findIPv4Address(const IPAddress& p_IPAddress) const -{ - return (stcIPAddress*)(((const stcAnswer*)this)->findIPv4Address(p_IPAddress)); -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::findIPv4Address -*/ -MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* MDNSResponder::clsHost::stcQuery::stcAnswer::findIPv4Address(const IPAddress& p_IPAddress) -{ - stcIPAddress* pIPv4Address = m_pIPv4Addresses; - while (pIPv4Address) - { - if (pIPv4Address->m_IPAddress == p_IPAddress) - { - break; - } - pIPv4Address = pIPv4Address->m_pNext; - } - return pIPv4Address; -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::IPv4AddressCount -*/ -uint32_t MDNSResponder::clsHost::stcQuery::stcAnswer::IPv4AddressCount(void) const -{ - uint32_t u32Count = 0; - - stcIPAddress* pIPv4Address = m_pIPv4Addresses; - while (pIPv4Address) - { - ++u32Count; - pIPv4Address = pIPv4Address->m_pNext; - } - return u32Count; -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::IPv4AddressAtIndex -*/ -MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* MDNSResponder::clsHost::stcQuery::stcAnswer::IPv4AddressAtIndex(uint32_t p_u32Index) -{ - return (stcIPAddress*)(((const stcAnswer*)this)->IPv4AddressAtIndex(p_u32Index)); -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::IPv4AddressAtIndex (const) -*/ -const MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* MDNSResponder::clsHost::stcQuery::stcAnswer::IPv4AddressAtIndex(uint32_t p_u32Index) const -{ - const stcIPAddress* pIPv4Address = 0; - - if (((uint32_t)(-1) != p_u32Index) && - (m_pIPv4Addresses)) - { - - uint32_t u32Index; - for (pIPv4Address = m_pIPv4Addresses, u32Index = 0; ((pIPv4Address) && (u32Index < p_u32Index)); pIPv4Address = pIPv4Address->m_pNext, ++u32Index); - } - return pIPv4Address; -} -#endif - -#ifdef MDNS_IPV6_SUPPORT -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::releaseIPv6Addresses -*/ -bool MDNSResponder::clsHost::stcQuery::stcAnswer::releaseIPv6Addresses(void) -{ - while (m_pIPv6Addresses) - { - stcIPAddress* pNext = m_pIPv6Addresses->m_pNext; - delete m_pIPv6Addresses; - m_pIPv6Addresses = pNext; - } - return true; -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::addIPv6Address -*/ -bool MDNSResponder::clsHost::stcQuery::stcAnswer::addIPv6Address(MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* p_pIPv6Address) -{ - bool bResult = false; - - if (p_pIPv6Address) - { - p_pIPv6Address->m_pNext = m_pIPv6Addresses; - m_pIPv6Addresses = p_pIPv6Address; - bResult = true; - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::removeIPv6Address -*/ -bool MDNSResponder::clsHost::stcQuery::stcAnswer::removeIPv6Address(MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* p_pIPv6Address) -{ - bool bResult = false; - - if (p_pIPv6Address) - { - stcIPAddress* pPred = m_pIPv6Addresses; - while ((pPred) && - (pPred->m_pNext != p_pIPv6Address)) - { - pPred = pPred->m_pNext; - } - if (pPred) - { - pPred->m_pNext = p_pIPv6Address->m_pNext; - delete p_pIPv6Address; - bResult = true; - } - else if (m_pIPv6Addresses == p_pIPv6Address) // No predecesor, but first item - { - m_pIPv6Addresses = p_pIPv6Address->m_pNext; - delete p_pIPv6Address; - bResult = true; - } - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::findIPv6Address -*/ -MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* MDNSResponder::clsHost::stcQuery::stcAnswer::findIPv6Address(const IPAddress& p_IPAddress) -{ - return (stcIPAddress*)(((const stcAnswer*)this)->findIPv6Address(p_IPAddress)); -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::findIPv6Address (const) -*/ -const MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* MDNSResponder::clsHost::stcQuery::stcAnswer::findIPv6Address(const IPAddress& p_IPAddress) const -{ - const stcIPAddress* pIPv6Address = m_pIPv6Addresses; - while (pIPv6Address) - { - if (pIPv6Address->m_IPAddress == p_IPAddress) - { - break; - } - pIPv6Address = pIPv6Address->m_pNext; - } - return pIPv6Address; -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::IPv6AddressCount -*/ -uint32_t MDNSResponder::clsHost::stcQuery::stcAnswer::IPv6AddressCount(void) const -{ - uint32_t u32Count = 0; - - stcIPAddress* pIPv6Address = m_pIPv6Addresses; - while (pIPv6Address) - { - ++u32Count; - pIPv6Address = pIPv6Address->m_pNext; - } - return u32Count; -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::IPv6AddressAtIndex (const) -*/ -const MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* MDNSResponder::clsHost::stcQuery::stcAnswer::IPv6AddressAtIndex(uint32_t p_u32Index) const -{ - return (stcIPAddress*)(((const stcAnswer*)this)->IPv6AddressAtIndex(p_u32Index)); -} - -/* - MDNSResponder::clsHost::stcQuery::stcAnswer::IPv6AddressAtIndex -*/ -MDNSResponder::clsHost::stcQuery::stcAnswer::stcIPAddress* MDNSResponder::clsHost::stcQuery::stcAnswer::IPv6AddressAtIndex(uint32_t p_u32Index) -{ - stcIPAddress* pIPv6Address = 0; - - if (((uint32_t)(-1) != p_u32Index) && - (m_pIPv6Addresses)) - { - - uint32_t u32Index; - for (pIPv6Address = m_pIPv6Addresses, u32Index = 0; ((pIPv6Address) && (u32Index < p_u32Index)); pIPv6Address = pIPv6Address->m_pNext, ++u32Index); - } - return pIPv6Address; -} -#endif - - -/** - MDNSResponder::clsHost::stcQuery - - 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::clsHost::stcQuery::stcQuery constructor -*/ -MDNSResponder::clsHost::stcQuery::stcQuery(const enuQueryType p_QueryType) - : m_pNext(0), - m_QueryType(p_QueryType), - m_fnCallback(0), - m_bLegacyQuery(false), - m_u8SentCount(0), - m_ResendTimeout(std::numeric_limits::max()), - m_bAwaitingAnswers(true), - m_pAnswers(0) -{ - clear(); - m_QueryType = p_QueryType; -} - -/* - MDNSResponder::clsHost::stcQuery::~stcQuery destructor -*/ -MDNSResponder::clsHost::stcQuery::~stcQuery(void) -{ - clear(); -} - -/* - MDNSResponder::clsHost::stcQuery::clear -*/ -bool MDNSResponder::clsHost::stcQuery::clear(void) -{ - m_pNext = 0; - m_QueryType = enuQueryType::None; - m_fnCallback = 0; - m_bLegacyQuery = false; - m_u8SentCount = 0; - m_ResendTimeout.reset(std::numeric_limits::max()); - m_bAwaitingAnswers = true; - while (m_pAnswers) - { - stcAnswer* pNext = m_pAnswers->m_pNext; - delete m_pAnswers; - m_pAnswers = pNext; - } - return true; -} - -/* - MDNSResponder::clsHost::stcQuery::answerCount -*/ -uint32_t MDNSResponder::clsHost::stcQuery::answerCount(void) const -{ - uint32_t u32Count = 0; - - stcAnswer* pAnswer = m_pAnswers; - while (pAnswer) - { - ++u32Count; - pAnswer = pAnswer->m_pNext; - } - return u32Count; -} - -/* - MDNSResponder::clsHost::stcQuery::answerAtIndex -*/ -const MDNSResponder::clsHost::stcQuery::stcAnswer* MDNSResponder::clsHost::stcQuery::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) && (u32Index < p_u32Index)); pAnswer = pAnswer->m_pNext, ++u32Index); - } - return pAnswer; -} - -/* - MDNSResponder::clsHost::stcQuery::answerAtIndex -*/ -MDNSResponder::clsHost::stcQuery::stcAnswer* MDNSResponder::clsHost::stcQuery::answerAtIndex(uint32_t p_u32Index) -{ - return (stcAnswer*)(((const stcQuery*)this)->answerAtIndex(p_u32Index)); -} - -/* - MDNSResponder::clsHost::stcQuery::indexOfAnswer -*/ -uint32_t MDNSResponder::clsHost::stcQuery::indexOfAnswer(const MDNSResponder::clsHost::stcQuery::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::clsHost::stcQuery::addAnswer -*/ -bool MDNSResponder::clsHost::stcQuery::addAnswer(MDNSResponder::clsHost::stcQuery::stcAnswer* p_pAnswer) -{ - bool bResult = false; - - if (p_pAnswer) - { - p_pAnswer->m_pNext = m_pAnswers; - m_pAnswers = p_pAnswer; - bResult = true; - } - return bResult; -} - -/* - MDNSResponder::clsHost::stcQuery::removeAnswer -*/ -bool MDNSResponder::clsHost::stcQuery::removeAnswer(MDNSResponder::clsHost::stcQuery::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::clsHost::stcQuery::findAnswerForServiceDomain -*/ -MDNSResponder::clsHost::stcQuery::stcAnswer* MDNSResponder::clsHost::stcQuery::findAnswerForServiceDomain(const MDNSResponder::clsHost::stcRRDomain& p_ServiceDomain) -{ - stcAnswer* pAnswer = m_pAnswers; - while (pAnswer) - { - if (pAnswer->m_ServiceDomain == p_ServiceDomain) - { - break; - } - pAnswer = pAnswer->m_pNext; - } - return pAnswer; -} - -/* - MDNSResponder::clsHost::stcQuery::findAnswerForHostDomain -*/ -MDNSResponder::clsHost::stcQuery::stcAnswer* MDNSResponder::clsHost::stcQuery::findAnswerForHostDomain(const MDNSResponder::clsHost::stcRRDomain& p_HostDomain) -{ - stcAnswer* pAnswer = m_pAnswers; - while (pAnswer) - { - if (pAnswer->m_HostDomain == p_HostDomain) - { - break; - } - pAnswer = pAnswer->m_pNext; - } - return pAnswer; -} - - -} // namespace MDNSImplementation - -} // namespace esp8266 - - - diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp new file mode 100644 index 0000000000..040d915905 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -0,0 +1,1493 @@ +/* + * LEAmDNS2_Legacy.cpp + * + * + */ + +#include "LEAmDNS2_Legacy.h" + + +namespace esp8266 +{ + +/** + * LEAmDNS + */ +namespace MDNSImplementation +{ + +/** + STRINGIZE +*/ +#ifndef STRINGIZE +#define STRINGIZE(x) #x +#endif +#ifndef STRINGIZE_VALUE_OF +#define STRINGIZE_VALUE_OF(x) STRINGIZE(x) +#endif + + +/* + * clsLEAMDNSHost_Legacy::clsLEAMDNSHost_Legacy constructor + * + */ +clsLEAMDNSHost_Legacy::clsLEAMDNSHost_Legacy(void) +{ +} + +/* + * clsLEAMDNSHost_Legacy::clsLEAMDNSHost_Legacy destructor + * + */ +clsLEAMDNSHost_Legacy::~clsLEAMDNSHost_Legacy(void) +{ +} + +/* + * + * HOST SETUP + * + */ + +/* + * clsLEAMDNSHost_Legacy::begin + * + */ +bool clsLEAMDNSHost_Legacy::begin(const char* p_pcHostname) +{ + bool bResult = ( ( (!(WIFI_STA & (WiFiMode_t)wifi_get_opmode())) + || (addHostForNetIf(p_pcHostname, netif_get_by_index(WIFI_STA)))) + && ( (!(WIFI_AP & (WiFiMode_t)wifi_get_opmode())) + || (addHostForNetIf(p_pcHostname, netif_get_by_index(WIFI_AP))))); + return ( (bResult) + && (0 != m_HostInformations.size())); +} + +/* + * clsLEAMDNSHost_Legacy::begin (String) + * + */ +bool clsLEAMDNSHost_Legacy::begin(const String& p_strHostname) +{ + return begin(p_strHostname.c_str()); +} + +/* + * clsLEAMDNSHost_Legacy::begin (Ignored Options) + * + */ +bool clsLEAMDNSHost_Legacy::begin(const char* p_pcHostname, + IPAddress /*p_IPAddress = INADDR_ANY*/, // ignored + uint32_t /*p_u32TTL = 120*/) // ignored +{ + return begin(p_pcHostname); +} + +/* + * clsLEAMDNSHost_Legacy::begin (String & Ignored Options) + * + */ +bool clsLEAMDNSHost_Legacy::begin(const String& p_strHostname, + IPAddress /*p_IPAddress = INADDR_ANY*/, // ignored + uint32_t /*p_u32TTL = 120*/) // ignored +{ + return begin(p_strHostname.c_str()); +} + +/* + * clsLEAMDNSHost_Legacy::close + * + */ +bool clsLEAMDNSHost_Legacy::close(void) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + if ((bResult = (*it).m_pHost->close())) + { + delete (*it).m_pHost; + (*it).m_pHost = 0; + } + } + return ( (bResult) + && (m_HostInformations.clear(), true)); +} + +/* + * clsLEAMDNSHost_Legacy::end + * + */ +bool clsLEAMDNSHost_Legacy::end(void) +{ + return close(); +} + +/* + * clsLEAMDNSHost_Legacy::addHostForNetIf + * + * NEW! + * + */ +bool clsLEAMDNSHost_Legacy::addHostForNetIf(const char* p_pcHostname, + netif* p_pNetIf) +{ + clsLEAMDNSHost* pHost = 0; + + if ( ((pHost = new esp8266::experimental::clsLEAMDNSHost)) + && (!( (pHost->begin(p_pcHostname, p_pNetIf /*, default callback*/)) + && (m_HostInformations.push_back(stcHostInformation(pHost)), true)))) + { + delete pHost; + pHost = 0; + } + return (0 != pHost); +} + +/* + * clsLEAMDNSHost_Legacy::setHostname + * + */ +bool clsLEAMDNSHost_Legacy::setHostname(const char* p_pcHostname) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + bResult = (*it).m_pHost->setHostName(p_pcHostname); + } + return bResult; +} + +/* + * clsLEAMDNSHost_Legacy::setHostname + * + */ +bool clsLEAMDNSHost_Legacy::setHostname(String p_strHostname) +{ + return setHostname(p_strHostname.c_str()); +} + +/* + * clsLEAMDNSHost_Legacy::hostname + * + */ +const char* clsLEAMDNSHost_Legacy::hostname(void) const +{ + return (m_HostInformations.empty() + ? 0 + : m_HostInformations.front().m_pHost->hostName()); +} + +/* + * clsLEAMDNSHost_Legacy::status + * + */ +bool clsLEAMDNSHost_Legacy::status(void) const +{ + bool bStatus = true; + + for (const stcHostInformation& hostInformation : m_HostInformations) + { + if (!((bStatus = hostInformation.m_pHost->probeStatus()))) + { + break; + } + } + return bStatus; +} + + +/* + * + * SERVICE MANAGEMENT + * + */ + +/* + * clsLEAMDNSHost_Legacy::addService + * + */ +clsLEAMDNSHost_Legacy::hMDNSService clsLEAMDNSHost_Legacy::addService(const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol, + uint16_t p_u16Port) +{ + hMDNSService hResult = 0; + + for (stcHostInformation& hostInformation : m_HostInformations) + { + clsLEAMDNSHost::clsService* pService = hostInformation.m_pHost->addService(p_pcName, p_pcService, p_pcProtocol, p_u16Port /*, default callback*/); + if (pService) + { + if (!hResult) + { // Store first service handle as result and key + hResult = (hMDNSService)pService; + } + hostInformation.m_HandleToPtr[hResult] = pService; + } + } + return hResult; +} + +/* + * clsLEAMDNSHost_Legacy::addService (String) + * + */ +bool clsLEAMDNSHost_Legacy::addService(String p_strServiceName, + String p_strProtocol, + uint16_t p_u16Port) +{ + return (0 != addService(0, p_strServiceName.c_str(), p_strProtocol.c_str(), p_u16Port)); +} + +/* + * clsLEAMDNSHost_Legacy::removeService (hService) + * + */ +bool clsLEAMDNSHost_Legacy::removeService(const hMDNSService p_hService) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService]; + if ((bResult = ( (pService) + && ((*it).m_pHost->removeService(pService))))) + { + (*it).m_HandleToPtr.erase(p_hService); + } + } + return bResult; +} + +/* + * clsLEAMDNSHost_Legacy::removeService (name) + * + */ +bool clsLEAMDNSHost_Legacy::removeService(const char* p_pcInstanceName, + const char* p_pcServiceName, + const char* p_pcProtocol) +{ + hMDNSService hService = 0; + return ( ((hService = (m_HostInformations.empty() + ? 0 + : (hMDNSService)m_HostInformations.front().m_pHost->findService(p_pcInstanceName, p_pcServiceName, p_pcProtocol)))) + && (removeService(hService))); +} + +/* + * clsLEAMDNSHost_Legacy::setServiceName + * + */ +bool clsLEAMDNSHost_Legacy::setServiceName(const hMDNSService p_hService, + const char* p_pcInstanceName) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService]; + bResult = ( (pService) + && (pService->setInstanceName(p_pcInstanceName))); + } + return bResult; +} + +/* + * clsLEAMDNSHost_Legacy::setInstanceName + * + */ +void clsLEAMDNSHost_Legacy::setInstanceName(const char* p_pcInstanceName) +{ + for (stcHostInformation& hostInformation : m_HostInformations) + { + hostInformation.m_pHost->setDefaultInstanceName(p_pcInstanceName); + } +} + +/* + * clsLEAMDNSHost_Legacy::setInstanceName (String) + * + */ +void clsLEAMDNSHost_Legacy::setInstanceName(const String& p_strHostname) +{ + setInstanceName(p_strHostname.c_str()); +} + +/* + * clsLEAMDNSHost_Legacy::serviceName + * + */ +const char* clsLEAMDNSHost_Legacy::serviceName(const hMDNSService p_hService) const +{ + const clsLEAMDNSHost::clsService* pService = 0; + return (m_HostInformations.empty() + ? 0 + : (((pService = (const clsLEAMDNSHost::clsService*)(m_HostInformations.front().m_HandleToPtr.at(p_hService)))) + ? pService->instanceName() + : 0)); +} + +/* + * clsLEAMDNSHost_Legacy::service + * + */ +const char* clsLEAMDNSHost_Legacy::service(const hMDNSService p_hService) const +{ + const clsLEAMDNSHost::clsService* pService = 0; + return (m_HostInformations.empty() + ? 0 + : (((pService = (const clsLEAMDNSHost::clsService*)m_HostInformations.front().m_HandleToPtr.at(p_hService))) + ? pService->type() + : 0)); +} + +/* + * clsLEAMDNSHost_Legacy::serviceProtocol + * + */ +const char* clsLEAMDNSHost_Legacy::serviceProtocol(const hMDNSService p_hService) const +{ + const clsLEAMDNSHost::clsService* pService = 0; + return (m_HostInformations.empty() + ? 0 + : (((pService = (const clsLEAMDNSHost::clsService*)m_HostInformations.front().m_HandleToPtr.at(p_hService))) + ? pService->protocol() + : 0)); +} + +/* + * clsLEAMDNSHost_Legacy::serviceStatus + * + */ +bool clsLEAMDNSHost_Legacy::serviceStatus(const hMDNSService p_hService) const +{ + const clsLEAMDNSHost::clsService* pService = 0; + return (m_HostInformations.empty() + ? false + : (((pService = (const clsLEAMDNSHost::clsService*)m_HostInformations.front().m_HandleToPtr.at(p_hService))) + ? pService->probeStatus() + : false)); +} + + +/* + * + * SERVICE TXT MANAGEMENT + * + */ + +/* + * clsLEAMDNSHost_Legacy::addServiceTxt (char*) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + const char* p_pcValue) +{ + return _addServiceTxt(p_hService, p_pcKey, p_pcValue, false); +} + +/* + * clsLEAMDNSHost_Legacy::addServiceTxt (uint32_t) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint32_t p_u32Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_u32Value, false); +} + +/* + * clsLEAMDNSHost_Legacy::addServiceTxt (uint16_t) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint16_t p_u16Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_u16Value, false); +} + +/* + * clsLEAMDNSHost_Legacy::addServiceTxt (uint8_t) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint8_t p_u8Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_u8Value, false); +} + +/* + * clsLEAMDNSHost_Legacy::addServiceTxt (int32_t) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int32_t p_i32Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_i32Value, false); +} + +/* + * clsLEAMDNSHost_Legacy::addServiceTxt (int16_t) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int16_t p_i16Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_i16Value, false); +} + +/* + * clsLEAMDNSHost_Legacy::addServiceTxt (int8_t) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int8_t p_i8Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_i8Value, false); +} + +/* + * clsLEAMDNSHost_Legacy::addServiceTxt (legacy) + * + */ +bool clsLEAMDNSHost_Legacy::addServiceTxt(const char* p_pcService, + const char* p_pcProtocol, + const char* p_pcKey, + const char* p_pcValue) +{ + hMDNSService hService = 0; + return ( ((hService = (m_HostInformations.empty() + ? 0 + : (hMDNSService)m_HostInformations.front().m_pHost->findService(0, p_pcService, p_pcProtocol)))) + && (_addServiceTxt(hService, p_pcKey, p_pcValue, false))); +} + +/* + * clsLEAMDNSHost_Legacy::addServiceTxt (legacy, String) + * + */ +bool clsLEAMDNSHost_Legacy::addServiceTxt(String p_strService, + String p_strProtocol, + String p_strKey, + String p_strValue) +{ + return addServiceTxt(p_strService.c_str(), p_strProtocol.c_str(), p_strKey.c_str(), p_strValue.c_str()); +} + +/* + * clsLEAMDNSHost_Legacy::removeServiceTxt (hTxt) + * + */ +bool clsLEAMDNSHost_Legacy::removeServiceTxt(const hMDNSService p_hService, + const hMDNSTxt p_hTxt) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService]; + clsLEAMDNSHost::clsServiceTxt* pTxt = (clsLEAMDNSHost::clsServiceTxt*)(*it).m_HandleToPtr[p_hTxt]; + if ((bResult = ( (pService) + && (pTxt) + && (pService->removeServiceTxt(pTxt))))) + { + (*it).m_HandleToPtr.erase(p_hTxt); + } + } + return bResult; +} + +/* + * clsLEAMDNSHost_Legacy::removeServiceTxt (char*) + * + */ +bool clsLEAMDNSHost_Legacy::removeServiceTxt(const hMDNSService p_hService, + const char* p_pcKey) +{ + clsLEAMDNSHost::clsService* pService = 0; + clsLEAMDNSHost::clsServiceTxt* pTxt = 0; + return ( ((pService = (m_HostInformations.empty() + ? 0 + : (clsLEAMDNSHost::clsService*)m_HostInformations.front().m_HandleToPtr[p_hService]))) + && ((pTxt = pService->findServiceTxt(p_pcKey))) + && (removeServiceTxt(p_hService, (const hMDNSTxt)pTxt))); +} + +/* + * clsLEAMDNSHost_Legacy::removeServiceTxt (char*) + * + */ +bool clsLEAMDNSHost_Legacy::removeServiceTxt(const char* p_pcInstanceName, + const char* p_pcServiceName, + const char* p_pcProtocol, + const char* p_pcKey) +{ + hMDNSService hService = 0; + return ( ((hService = (m_HostInformations.empty() + ? 0 + : (hMDNSService)m_HostInformations.front().m_pHost->findService(p_pcInstanceName, p_pcServiceName, p_pcProtocol)))) + && (removeServiceTxt(hService, p_pcKey))); +} + +/* + * clsLEAMDNSHost_Legacy::setDynamicServiceTxtCallback (global) + * + */ +bool clsLEAMDNSHost_Legacy::setDynamicServiceTxtCallback(MDNSDynamicServiceTxtCallbackFn p_fnCallback) +{ + bool bResult = true; + + if ((bResult = m_HostInformations.empty())) + { + // The service handles of the first host are the keys in the HostInformations.HandleToPtr map + for (const clsLEAMDNSHost::clsService* pService : m_HostInformations.front().m_pHost->services()) + { + if (!((bResult = setDynamicServiceTxtCallback((hMDNSService)pService, p_fnCallback)))) + { + break; + } + } + } + return bResult; +} + +/* + * clsLEAMDNSHost_Legacy::setDynamicServiceTxtCallback (service) + * + */ +bool clsLEAMDNSHost_Legacy::setDynamicServiceTxtCallback(const hMDNSService p_hService, + MDNSDynamicServiceTxtCallbackFn p_fnCallback) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService]; + bResult = pService->setDynamicServiceTxtCallback([p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/)->void + { + if (p_fnCallback) // void(const hMDNSService p_hService) + { + p_fnCallback(p_hService); + } + }); + } + return bResult; +} + +/* + * clsLEAMDNSHost_Legacy::addDynamicServiceTxt (char*) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + const char* p_pcValue) +{ + return _addServiceTxt(p_hService, p_pcKey, p_pcValue, true); +} + +/* + * clsLEAMDNSHost_Legacy::addDynamicServiceTxt (uint32) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + uint32_t p_u32Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_u32Value, true); +} + +/* + * clsLEAMDNSHost_Legacy::addDynamicServiceTxt (uint16) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + uint16_t p_u16Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_u16Value, true); +} + +/* + * clsLEAMDNSHost_Legacy::addDynamicServiceTxt (uint8) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + uint8_t p_u8Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_u8Value, true); +} + +/* + * clsLEAMDNSHost_Legacy::addDynamicServiceTxt (int32) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + int32_t p_i32Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_i32Value, true); +} + +/* + * clsLEAMDNSHost_Legacy::addDynamicServiceTxt (int16) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + int16_t p_i16Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_i16Value, true); +} + +/* + * clsLEAMDNSHost_Legacy::addDynamicServiceTxt (int8) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + int8_t p_i8Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_i8Value, true); +} + + +/* + * + * STATIC QUERY + * + */ + + /* + * clsLEAMDNSHost_Legacy::queryService + * + * This will take p_u16Timeout millisec for every host! + * + */ +uint32_t clsLEAMDNSHost_Legacy::queryService(const char* p_pcService, + const char* p_pcProtocol, + const uint16_t p_u16Timeout /*= MDNS_QUERYSERVICES_WAIT_TIME*/) +{ + uint32_t u32Answers = 0; + + for (stcHostInformation& hostInformation : m_HostInformations) + { + u32Answers += (hostInformation.m_pHost->queryService(p_pcService, p_pcProtocol, p_u16Timeout)).size(); + } + return u32Answers; +} + +/* + * clsLEAMDNSHost_Legacy::removeQuery + * + */ +bool clsLEAMDNSHost_Legacy::removeQuery(void) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + bResult = (*it).m_pHost->removeQuery(); + } + return bResult; +} + +/* + * clsLEAMDNSHost_Legacy::queryService + * + */ +uint32_t clsLEAMDNSHost_Legacy::queryService(String p_strService, + String p_strProtocol) +{ + return queryService(p_strService.c_str(), p_strProtocol.c_str()); +} + +/* + * clsLEAMDNSHost_Legacy::answerHostname + * + */ +const char* clsLEAMDNSHost_Legacy::answerHostname(const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_u32AnswerIndex); + return (answerAccessor.serviceDomainAvailable() + ? answerAccessor.serviceDomain() + : 0); +} + +/* + * clsLEAMDNSHost_Legacy::answerIP + * + */ +IPAddress clsLEAMDNSHost_Legacy::answerIP(const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_u32AnswerIndex); + return (answerAccessor.IPv4AddressAvailable() + ? answerAccessor.IPv4Addresses()[0] + : IPAddress()); +} + +/* + * clsLEAMDNSHost_Legacy::answerPort + * + */ +uint16_t clsLEAMDNSHost_Legacy::answerPort(const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_u32AnswerIndex); + return (answerAccessor.hostPortAvailable() + ? answerAccessor.hostPort() + : 0); +} + +/* + * clsLEAMDNSHost_Legacy::hostname + * + */ +String clsLEAMDNSHost_Legacy::hostname(const uint32_t p_u32AnswerIndex) +{ + return String(answerHostname(p_u32AnswerIndex)); +} + +/* + * clsLEAMDNSHost_Legacy::IP + * + */ +IPAddress clsLEAMDNSHost_Legacy::IP(const uint32_t p_u32AnswerIndex) +{ + return answerIP(p_u32AnswerIndex); +} + +/* + * clsLEAMDNSHost_Legacy::port + * + */ +uint16_t clsLEAMDNSHost_Legacy::port(const uint32_t p_u32AnswerIndex) +{ + return answerPort(p_u32AnswerIndex); +} + + +/* + * + * DYNAMIC QUERY + * + */ + +/* + * clsLEAMDNSHost_Legacy::installServiceQuery + * + */ +clsLEAMDNSHost_Legacy::hMDNSServiceQuery clsLEAMDNSHost_Legacy::installServiceQuery(const char* p_pcService, + const char* p_pcProtocol, + MDNSServiceQueryCallbackFn p_fnCallback) +{ + hMDNSServiceQuery hResult = 0; + + for (stcHostInformation& hostInformation : m_HostInformations) + { + clsLEAMDNSHost::clsQuery* pQuery = hostInformation.m_pHost->installServiceQuery(p_pcService, p_pcProtocol, [this, p_fnCallback](const clsLEAMDNSHost::clsQuery& /*p_Query*/, + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor& p_AnswerAccessor, + clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, // flags for the updated answer item + bool p_bSetContent)->void + { + if (p_fnCallback) // void(const stcMDNSServiceInfo& p_MDNSServiceInfo, MDNSResponder::AnswerType p_AnswerType, bool p_bSetContent) + { + p_fnCallback(stcMDNSServiceInfo(p_AnswerAccessor), _answerFlagsToAnswerType(p_QueryAnswerTypeFlags), p_bSetContent); + } + }); + if (pQuery) + { + if (!hResult) + { // Store first query as result and key + hResult = (hMDNSServiceQuery)pQuery; + } + hostInformation.m_HandleToPtr[hResult] = pQuery; + } + } + return hResult; +} + +/* + * clsLEAMDNSHost_Legacy::removeServiceQuery + * + */ +bool clsLEAMDNSHost_Legacy::removeServiceQuery(clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + if ((bResult = (*it).m_pHost->removeQuery((clsLEAMDNSHost::clsQuery*)(*it).m_HandleToPtr[p_hServiceQuery]))) + { + (*it).m_HandleToPtr.erase(p_hServiceQuery); + } + } + return bResult; +} + +/* + * clsLEAMDNSHost_Legacy::answerCount + * + */ +uint32_t clsLEAMDNSHost_Legacy::answerCount(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery) +{ + uint32_t u32AnswerCount = 0; + + for (stcHostInformation& hostInformation : m_HostInformations) + { + clsLEAMDNSHost::clsQuery* pQuery = (clsLEAMDNSHost::clsQuery*)hostInformation.m_HandleToPtr[p_hServiceQuery]; + if (pQuery) + { + u32AnswerCount += pQuery->answerCount(); + } + else + { + u32AnswerCount = 0; + break; + } + } + return u32AnswerCount; +} + +/* + * clsLEAMDNSHost_Legacy::answerInfo + * + */ +std::vector clsLEAMDNSHost_Legacy::answerInfo(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery) +{ + std::vector serviceInfos; + + for (stcHostInformation& hostInformation : m_HostInformations) + { + clsLEAMDNSHost::clsQuery* pQuery = (clsLEAMDNSHost::clsQuery*)hostInformation.m_HandleToPtr[p_hServiceQuery]; + if (pQuery) + { + for (clsLEAMDNSHost::clsQuery::clsAnswerAccessor& answerAccessor : pQuery->answerAccessors()) + { + serviceInfos.push_back(stcMDNSServiceInfo(answerAccessor)); + } + } + else + { + serviceInfos.clear(); + break; + } + } + return serviceInfos; +} + +/* + * clsLEAMDNSHost_Legacy::answerServiceDomain + * + */ +const char* clsLEAMDNSHost_Legacy::answerServiceDomain(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); + return (answerAccessor.serviceDomainAvailable() + ? answerAccessor.serviceDomain() + : 0); +} + +/* + * clsLEAMDNSHost_Legacy::hasAnswerHostDomain + * + */ +bool clsLEAMDNSHost_Legacy::hasAnswerHostDomain(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).hostDomainAvailable(); +} + +/* + * clsLEAMDNSHost_Legacy::answerHostDomain + * + */ +const char* clsLEAMDNSHost_Legacy::answerHostDomain(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); + return (answerAccessor.hostDomainAvailable() + ? answerAccessor.hostDomain() + : 0); +} + +#ifdef MDNS_IP4_SUPPORT +/* + * clsLEAMDNSHost_Legacy::hasAnswerIP4Address + * + */ +bool clsLEAMDNSHost_Legacy::hasAnswerIP4Address(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).IPv4AddressAvailable(); +} + +/* + * clsLEAMDNSHost_Legacy::answerIP4AddressCount + * + */ +uint32_t clsLEAMDNSHost_Legacy::answerIP4AddressCount(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); + return (answerAccessor.IPv4AddressAvailable() + ? answerAccessor.IPv4Addresses().size() + : 0); +} + +/* + * clsLEAMDNSHost_Legacy::answerIP4Address + * + */ +IPAddress clsLEAMDNSHost_Legacy::answerIP4Address(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); + return (answerAccessor.IPv4AddressAvailable() + ? answerAccessor.IPv4Addresses()[p_u32AddressIndex] + : IPAddress()); +} +#endif +#ifdef MDNS_IP6_SUPPORT +/* + * clsLEAMDNSHost_Legacy::hasAnswerIP6Address + * + */ +bool clsLEAMDNSHost_Legacy::hasAnswerIP6Address(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).IPv6AddressAvailable(); +} + +/* + * clsLEAMDNSHost_Legacy::answerIP6AddressCount + * + */ +uint32_t clsLEAMDNSHost_Legacy::answerIP6AddressCount(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); + return (answerAccessor.IPv6AddressAvailable() + ? answerAccessor.IPv6Addresses().size() + : 0); +} + +/* + * clsLEAMDNSHost_Legacy::answerIP6Address + * + */ +IPAddress clsLEAMDNSHost_Legacy::answerIP6Address(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); + return (answerAccessor.IPv6AddressAvailable() + ? answerAccessor.IPv6Addresses()[p_u32AddressIndex] + : IPAddress()); +} +#endif + +/* + * clsLEAMDNSHost_Legacy::hasAnswerPort + * + */ +bool clsLEAMDNSHost_Legacy::hasAnswerPort(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).hostPortAvailable(); +} + +/* + * clsLEAMDNSHost_Legacy::answerPort + * + */ +uint16_t clsLEAMDNSHost_Legacy::answerPort(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); + return (answerAccessor.hostPortAvailable() + ? answerAccessor.hostPort() + : 0); +} + +/* + * clsLEAMDNSHost_Legacy::hasAnswerTxts + * + */ +bool clsLEAMDNSHost_Legacy::hasAnswerTxts(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).txtsAvailable(); +} + +/* + * clsLEAMDNSHost_Legacy::answerHostDomain + * + * Get the TXT items as a ';'-separated string + */ +const char* clsLEAMDNSHost_Legacy::answerTxts(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); + return (answerAccessor.txtsAvailable() + ? answerAccessor.txts() + : 0); +} + + +/* + * + * HOST/SERVICE PROBE CALLBACKS + * + */ + +/* + * clsLEAMDNSHost_Legacy::setHostProbeResultCallback + * + */ +bool clsLEAMDNSHost_Legacy::setHostProbeResultCallback(clsLEAMDNSHost_Legacy::MDNSHostProbeResultCallbackFn p_fnCallback) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + bResult = (*it).m_pHost->setProbeResultCallback([p_fnCallback](clsLEAMDNSHost& /*p_rHost*/, + const char* p_pcDomainName, + bool p_bProbeResult)->void + { + if (p_fnCallback) // void(const char* p_pcDomainName, bool p_bProbeResult) + { + p_fnCallback(p_pcDomainName, p_bProbeResult); + } + }); + } + return bResult; +} + +/* + * clsLEAMDNSHost_Legacy::setHostProbeResultCallback + * + */ +bool clsLEAMDNSHost_Legacy::setHostProbeResultCallback(clsLEAMDNSHost_Legacy::MDNSHostProbeResultCallbackFn2 p_fnCallback) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + bResult = (*it).m_pHost->setProbeResultCallback([this, p_fnCallback](clsLEAMDNSHost& /*p_rHost*/, + const char* p_pcDomainName, + bool p_bProbeResult)->void + { + if (p_fnCallback) // void(clsLEAMDNSHost_Legacy* p_pMDNSResponder, const char* p_pcDomainName, bool p_bProbeResult) + { + p_fnCallback(this, p_pcDomainName, p_bProbeResult); + } + }); + } + return bResult; +} + +/* + * clsLEAMDNSHost_Legacy::setServiceProbeResultCallback + * + */ +bool clsLEAMDNSHost_Legacy::setServiceProbeResultCallback(const clsLEAMDNSHost_Legacy::hMDNSService p_hService, + clsLEAMDNSHost_Legacy::MDNSServiceProbeResultCallbackFn p_fnCallback) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + clsLEAMDNSHost::clsService* pService = 0; + bResult = ( ((pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService])) + && (pService->setProbeResultCallback([this, p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/, + const char* p_pcInstanceName, + bool p_bProbeResult)->void + { + if (p_fnCallback) // void(const char* p_pcServiceName, const hMDNSService p_hMDNSService, bool p_bProbeResult) + { + p_fnCallback(p_pcInstanceName, p_hService, p_bProbeResult); + } + }))); + } + return bResult; +} + +/* + * clsLEAMDNSHost_Legacy::setServiceProbeResultCallback + * + */ +bool clsLEAMDNSHost_Legacy::setServiceProbeResultCallback(const clsLEAMDNSHost_Legacy::hMDNSService p_hService, + clsLEAMDNSHost_Legacy::MDNSServiceProbeResultCallbackFn2 p_fnCallback) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + clsLEAMDNSHost::clsService* pService = 0; + bResult = ( ((pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService])) + && (pService->setProbeResultCallback([this, p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/, + const char* p_pcInstanceName, + bool p_bProbeResult)->void + { + if (p_fnCallback) // void((clsLEAMDNSHost_Legacy* p_pMDNSResponder, const char* p_pcServiceName, const hMDNSService p_hMDNSService, bool p_bProbeResult) + { + p_fnCallback(this, p_pcInstanceName, p_hService, p_bProbeResult); + } + }))); + } + return bResult; +} + + +/* + * + * PROCESS + * + */ + +/* + * clsLEAMDNSHost_Legacy::notifyAPChange + * + */ +bool clsLEAMDNSHost_Legacy::notifyAPChange(void) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + bResult = (*it).m_pHost->restart(); + } + return bResult; +} + +/* + * clsLEAMDNSHost_Legacy::update + * + */ +bool clsLEAMDNSHost_Legacy::update(void) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + bResult = (*it).m_pHost->update(); + } + return bResult; +} + +/* + * clsLEAMDNSHost_Legacy::announce + * + */ +bool clsLEAMDNSHost_Legacy::announce(void) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + bResult = (*it).m_pHost->announce(true, true); + } + return bResult; +} + +/* + * clsLEAMDNSHost_Legacy::enableArduino + * + */ +clsLEAMDNSHost_Legacy::hMDNSService clsLEAMDNSHost_Legacy::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; +} + +/* + * clsLEAMDNSHost_Legacy::indexDomain + * + */ +bool clsLEAMDNSHost_Legacy::indexDomain(char*& p_rpcDomain, + const char* p_pcDivider /*= "-"*/, + const char* p_pcDefaultDomain /*= 0*/) +{ + bool bResult = false; + + const char* cpcDomainName = clsLEAMDNSHost::indexDomainName(p_rpcDomain, p_pcDivider, p_pcDefaultDomain); + delete[] p_rpcDomain; + p_rpcDomain = 0; + if ( (cpcDomainName) + && ((p_rpcDomain = new char[strlen(cpcDomainName) + 1]))) + { + strcpy(p_rpcDomain, cpcDomainName); + bResult = true; + } + return bResult; +} + + +/* + * + * INTERNAL HELPERS + * + */ + +/* + * clsLEAMDNSHost_Legacy::_addServiceTxt (char*) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bDynamic) +{ + hMDNSTxt hResult = 0; + + for (stcHostInformation& hostInformation : m_HostInformations) + { + clsLEAMDNSHost::clsService* pService = 0; + clsLEAMDNSHost::clsServiceTxt* pTxt = 0; + if ( ((pService = (clsLEAMDNSHost::clsService*)hostInformation.m_HandleToPtr[p_hService])) + && ((pTxt = (p_bDynamic + ? pService->addDynamicServiceTxt(p_pcKey, p_pcValue) + : pService->addServiceTxt(p_pcKey, p_pcValue))))) + { + if (!hResult) + { + hResult = (hMDNSTxt)pTxt; + } + hostInformation.m_HandleToPtr[hResult] = pTxt; + } + } + return hResult; +} + +/* + * clsLEAMDNSHost_Legacy::_addServiceTxt (uint32_t) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint32_t p_u32Value, + bool p_bDynamic) +{ + char acValueBuffer[16]; // 32-bit max 10 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%u", p_u32Value); + + return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); +} + +/* + * clsLEAMDNSHost_Legacy::_addServiceTxt (uint16_t) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint16_t p_u16Value, + bool p_bDynamic) +{ + char acValueBuffer[8]; // 16-bit max 5 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%hu", p_u16Value); + + return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); +} + +/* + * clsLEAMDNSHost_Legacy::_addServiceTxt (uint8_t) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint8_t p_u8Value, + bool p_bDynamic) +{ + char acValueBuffer[8]; // 8-bit max 3 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%hhu", p_u8Value); + + return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); +} + +/* + * clsLEAMDNSHost_Legacy::_addServiceTxt (int32_t) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int32_t p_i32Value, + bool p_bDynamic) +{ + char acValueBuffer[16]; // 32-bit max 11 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%i", p_i32Value); + + return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); +} + +/* + * clsLEAMDNSHost_Legacy::_addServiceTxt (int16_t) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int16_t p_i16Value, + bool p_bDynamic) +{ + char acValueBuffer[8]; // 16-bit max 6 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%hi", p_i16Value); + + return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); +} + +/* + * clsLEAMDNSHost_Legacy::_addServiceTxt (int8_t) + * + */ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int8_t p_i8Value, + bool p_bDynamic) +{ + char acValueBuffer[8]; // 8-bit max 4 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%hhi", p_i8Value); + + return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); +} + +/* + * clsLEAMDNSHost_Legacy::_answerFlagsToAnswerType + * + */ +clsLEAMDNSHost_Legacy::AnswerType clsLEAMDNSHost_Legacy::_answerFlagsToAnswerType(clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags) const +{ + AnswerType answerType = AnswerType::Unknown; + + if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Unknown) & p_QueryAnswerTypeFlags) + { + answerType = AnswerType::Unknown; + } + else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain) & p_QueryAnswerTypeFlags) + { + answerType = AnswerType::ServiceDomain; + } + else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::HostDomain) & p_QueryAnswerTypeFlags) + { + answerType = AnswerType::HostDomainAndPort; + } + else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Port) & p_QueryAnswerTypeFlags) + { + answerType = AnswerType::HostDomainAndPort; + } + else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Txts) & p_QueryAnswerTypeFlags) + { + answerType = AnswerType::Txt; + } +#ifdef MDNS_IP4_SUPPORT + else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address) & p_QueryAnswerTypeFlags) + { + answerType = AnswerType::IP4Address; + } +#endif +#ifdef MDNS_IP6_SUPPORT + else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address) & p_QueryAnswerTypeFlags) + { + answerType = AnswerType::IP6Address; + } +#endif + return answerType; +} + +/* + * clsLEAMDNSHost_Legacy::_getAnswerAccessor + * + */ +clsLEAMDNSHost_Legacy::clsLEAMDNSHost::clsQuery::clsAnswerAccessor clsLEAMDNSHost_Legacy::_getAnswerAccessor(const uint32_t p_u32AnswerIndex) +{ + uint32_t u32AnswerIndexWithoutOffset = p_u32AnswerIndex; + for (stcHostInformation& hostInformation : m_HostInformations) + { + clsLEAMDNSHost::clsQuery* pQuery = hostInformation.m_pHost->getQuery(); + if (pQuery) + { + if (pQuery->answerCount() > u32AnswerIndexWithoutOffset) + { + return pQuery->answerAccessor(u32AnswerIndexWithoutOffset); + } + else + { + u32AnswerIndexWithoutOffset -= pQuery->answerCount(); + } + } + else + { + break; + } + } + return clsLEAMDNSHost::clsQuery::clsAnswerAccessor(0); +} + +/* + * clsLEAMDNSHost_Legacy::_getAnswerAccessor + * + */ +clsLEAMDNSHost_Legacy::clsLEAMDNSHost::clsQuery::clsAnswerAccessor clsLEAMDNSHost_Legacy::_getAnswerAccessor(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + uint32_t u32AnswerIndexWithoutOffset = p_u32AnswerIndex; + for (stcHostInformation& hostInformation : m_HostInformations) + { + clsLEAMDNSHost::clsQuery* pQuery = (clsLEAMDNSHost::clsQuery*)hostInformation.m_HandleToPtr[p_hServiceQuery]; + if (pQuery) + { + if (pQuery->answerCount() > u32AnswerIndexWithoutOffset) + { + return pQuery->answerAccessor(u32AnswerIndexWithoutOffset); + } + else + { + u32AnswerIndexWithoutOffset -= pQuery->answerCount(); + } + } + else + { + break; + } + } + return clsLEAMDNSHost::clsQuery::clsAnswerAccessor(0); +} + + +} // namespace MDNSImplementation + + +} // namespace esp8266 + + + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h new file mode 100644 index 0000000000..3b37dd5451 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h @@ -0,0 +1,679 @@ +/* + * LEAmDNS2_Legacy.h + * (c) 2020, 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 'clsLEAMDNSHost_Legacy::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(clsLEAMDNSHost_Legacy* p_MDNSResponder, const char* p_pcDomain, clsLEAMDNSHost_Legacy: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(clsLEAMDNSHost_Legacy* 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 MDNSDynamicServiceTxtCallbackFn; + + // 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); + + // 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); + + // 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 = clsConsts::u16StaticQueryWaitTime); + 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_Unknown = 0, + 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; + + /** + * AnswerType (std::map compatible version) + */ + enum class AnswerType : uint32_t { + Unknown = ServiceQueryAnswerType_Unknown, + ServiceDomain = ServiceQueryAnswerType_ServiceDomain, + HostDomainAndPort = ServiceQueryAnswerType_HostDomainAndPort, + Txt = ServiceQueryAnswerType_Txts, +#ifdef MDNS_IP4_SUPPORT + IP4Address = ServiceQueryAnswerType_IP4Address, +#endif +#ifdef MDNS_IP6_SUPPORT + IP6Address = ServiceQueryAnswerType_IP6Address +#endif + }; + + /** + * stcMDNSServiceInfo + */ + struct stcMDNSServiceInfo { + stcMDNSServiceInfo(const clsLEAMDNSHost::clsQuery::clsAnswerAccessor& p_rAnswerAccessor) + : m_rAnswerAccessor(p_rAnswerAccessor) {}; + /** + * stcCompareKey + */ + struct stcCompareKey { + /* + * operator () + */ + bool operator()(char const* p_pA, char const* p_pB) const { + return (0 > strcmp(p_pA, p_pB)); + } + }; + /** + * clsKeyValueMap + */ + using clsKeyValueMap = std::map; + + protected: + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor& m_rAnswerAccessor; + clsKeyValueMap m_KeyValueMap; + + public: + /* + * serviceDomain + */ + const char* serviceDomain(void) const { + return (m_rAnswerAccessor.serviceDomainAvailable() + ? m_rAnswerAccessor.serviceDomain() + : nullptr); + } + /* + * hostDomainAvailable + */ + bool hostDomainAvailable(void) const { + return m_rAnswerAccessor.serviceDomainAvailable(); + } + /* + * hostDomain + */ + const char* hostDomain(void) const { + return (hostDomainAvailable() + ? m_rAnswerAccessor.hostDomain() + : nullptr); + } + /* + * hostPortAvailable + */ + bool hostPortAvailable(void) const { + return m_rAnswerAccessor.hostPortAvailable(); + } + /* + * hostPort + */ + uint16_t hostPort(void) const { + return (hostPortAvailable() + ? m_rAnswerAccessor.hostPort() + : 0); + } +#ifdef MDNS_IP4_SUPPORT + /* + * IP4AddressAvailable + */ + bool IP4AddressAvailable(void) const { + return m_rAnswerAccessor.IPv4AddressAvailable(); + } + /* + * IP4Addresses + */ + std::vector IP4Adresses(void) const { + return (IP4AddressAvailable() + ? m_rAnswerAccessor.IPv4Addresses() + : std::vector()); + } +#endif +#ifdef MDNS_IP6_SUPPORT + /* + * IP6AddressAvailable + */ + bool IP6AddressAvailable(void) const { + return m_rAnswerAccessor.IPv6AddressAvailable(); + } + /* + * IP6Addresses + */ + std::vector IP6Adresses(void) const { + return (IP6AddressAvailable() + ? m_rAnswerAccessor.IPv6Addresses() + : std::vector()); + } +#endif + /* + * txtAvailable + */ + bool txtAvailable(void) const { + return m_rAnswerAccessor.txtsAvailable(); + } + /* + * strKeyValue -> abc=def;hij=klm; + */ + const char* strKeyValue (void) const { + // TODO + return nullptr; + } + /* + * keyValues -> abc=def hij=klm ... + */ + const clsKeyValueMap& keyValues(void) { + if ((txtAvailable()) && + (0 == m_KeyValueMap.size())) { + for (auto kv : m_rAnswerAccessor.txtKeyValues()) + { + m_KeyValueMap.emplace(std::pair(kv.first, kv.second)); + } + //for (auto kv=m_rMDNSResponder._answerKeyValue(m_hServiceQuery, m_u32AnswerIndex); kv!=nullptr; kv=kv->m_pNext) { + // m_KeyValueMap.emplace(std::pair(kv->m_pcKey, kv->m_pcValue)); + //} + } + return m_KeyValueMap; + } + /* + * value (abc)->def + */ + const char* value(const char* p_pcKey) const { + return m_rAnswerAccessor.txtValue(p_pcKey); + } + }; + + /** + * MDNSServiceQueryCallbackFn + * + * Callback function for received answers for dynamic service queries + */ + typedef std::function MDNSServiceQueryCallbackFn; + + // 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); + // Remove a dynamic service query + bool removeServiceQuery(hMDNSServiceQuery p_hServiceQuery); + + uint32_t answerCount(const hMDNSServiceQuery p_hServiceQuery); + std::vector answerInfo(const clsLEAMDNSHost_Legacy::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); + + /** + * MDNSHostProbeResultCallbackFn/2 + * Callback function for host domain probe results + */ + typedef std::function MDNSHostProbeResultCallbackFn; + + typedef std::function MDNSHostProbeResultCallbackFn2; + + // Set a callback function for host probe results + // The callback function is called, when the probeing for the host domain + // succeededs or fails. + // In case of failure, the failed domain name should be changed. + bool setHostProbeResultCallback(MDNSHostProbeResultCallbackFn p_fnCallback); + bool setHostProbeResultCallback(MDNSHostProbeResultCallbackFn2 p_fnCallback); + + /** + * MDNSServiceProbeResultCallbackFn/2 + * Callback function for service domain probe results + */ + typedef std::function MDNSServiceProbeResultCallbackFn; + + typedef std::function MDNSServiceProbeResultCallbackFn2; + + // Set a service specific probe result callcack + bool setServiceProbeResultCallback(const hMDNSService p_hService, + MDNSServiceProbeResultCallbackFn p_fnCallback); + bool setServiceProbeResultCallback(const hMDNSService p_hService, + MDNSServiceProbeResultCallbackFn2 p_fnCallback); + + // 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: + /** + * stcHostInformation + */ + struct stcHostInformation + { + /** + * clsHandleToPtrMap + */ + using clsHandleToPtrMap = std::map; + + clsLEAMDNSHost* m_pHost; + clsHandleToPtrMap m_HandleToPtr; + + stcHostInformation(clsLEAMDNSHost* p_pHost) + : m_pHost(p_pHost) + {} + + /** + * list + */ + using list = std::list; + }; + + stcHostInformation::list m_HostInformations; + + // HELPERS + hMDNSTxt _addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bDynamic); + hMDNSTxt _addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint32_t p_u32Value, + bool p_bDynamic); + hMDNSTxt _addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint16_t p_u16Value, + bool p_bDynamic); + hMDNSTxt _addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint8_t p_u8Value, + bool p_bDynamic); + hMDNSTxt _addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int32_t p_i32Value, + bool p_bDynamic); + hMDNSTxt _addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int16_t p_i16Value, + bool p_bDynamic); + hMDNSTxt _addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int8_t p_i8Value, + bool p_bDynamic); + + AnswerType _answerFlagsToAnswerType(clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags) const; + + clsLEAMDNSHost::clsQuery::clsAnswerAccessor _getAnswerAccessor(const uint32_t p_u32AnswerIndex); + clsLEAMDNSHost::clsQuery::clsAnswerAccessor _getAnswerAccessor(const hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex); + +}; + + +} // namespace MDNSImplementation + + +} // namespace esp8266 + + +#endif // __LEAMDNS2HOST_LEGACY_H__ + + + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h deleted file mode 100755 index 240700b7cf..0000000000 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h +++ /dev/null @@ -1,169 +0,0 @@ -/* - LEAmDNS2_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 LEAMDNS2_PRIV_H -#define LEAMDNS2_PRIV_H - -namespace esp8266 -{ - -/* - LEAmDNS -*/ - -namespace experimental -{ - -// Enable class debug functions -#define ESP_8266_MDNS_INCLUDE -#define DEBUG_ESP_MDNS_RESPONDER - - -#ifndef LWIP_OPEN_SRC -#define LWIP_OPEN_SRC -#endif - -// -// If ENABLE_ESP_MDNS_RESPONDER_PASSIV_MODE is defined, the mDNS responder ignores a successful probing -// This allows to drive the responder in a environment, where 'update()' isn't called in the loop -//#define ENABLE_ESP_MDNS_RESPONDER_PASSIV_MODE - -// 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 - -/* - This is NOT the TTL (Time-To-Live) for MDNS records, but the - subnet level distance MDNS records should travel. - 1 sets the subnet distance to 'local', which is default for MDNS. - (Btw.: 255 would set it to 'as far as possible' -> internet) - - However, RFC 3171 seems to force 255 instead -*/ -#define MDNS_MULTICAST_TTL 255 /* some say 1 is right*/ - -/* - This is the MDNS record TTL - Host level records are set to 2min (120s) - service level records are set to 75min (4500s) -*/ -#define MDNS_LEGACY_TTL 10 -#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 - Delay between and number of announces for host and service domains - Delay between and number of queries; the delay is multiplied by the resent number in '_checkQueryCache' -*/ -#define MDNS_PROBE_DELAY 250 -#define MDNS_PROBE_COUNT 3 -#define MDNS_ANNOUNCE_DELAY 1000 -#define MDNS_ANNOUNCE_COUNT 3 -#define MDNS_DYNAMIC_QUERY_RESEND_DELAY 1000 - - -/* - 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 -#else -#ifdef F -#undef F -#endif -#define F(A) A -#endif - -} // namespace MDNSImplementation - -} // namespace esp8266 - -// Include the main header, so the submodlues only need to include this header -#include "LEAmDNS2.h" - - -#endif // LEAMDNS2_PRIV_H diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h b/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h old mode 100755 new mode 100644 From d47f6f07e412659da52396ed6435ab059390a206 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 8 May 2020 11:29:26 +0200 Subject: [PATCH 004/152] style --- .../LEAmDNS/TwoInterfaces/TwoInterfaces.ino | 246 +- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 144 +- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 28 +- .../ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 260 +- .../ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp | 4 +- .../ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 192 +- .../ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 180 +- .../ESP8266mDNS/src/LEAmDNS2_Backbone.cpp | 14 +- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 2988 +++++++++-------- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h | 1377 ++++---- 10 files changed, 2729 insertions(+), 2704 deletions(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/TwoInterfaces/TwoInterfaces.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/TwoInterfaces/TwoInterfaces.ino index 3434454491..3f06f543b7 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/TwoInterfaces/TwoInterfaces.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/TwoInterfaces/TwoInterfaces.ino @@ -12,151 +12,123 @@ ESP8266WebServer server(80); void connectToWiFi(const char* p_pcSSID, const char* p_pcPWD, - uint32_t p_u32Timeout = 20) -{ - WiFi.begin(p_pcSSID, p_pcPWD); + uint32_t p_u32Timeout = 20) { + WiFi.begin(p_pcSSID, p_pcPWD); + Serial.println(""); + + // Wait for connection + uint8 u8Tries = p_u32Timeout; + while ((WiFi.status() != WL_CONNECTED) && + (u8Tries--)) { + delay(500); + Serial.print("."); + } + if (WiFi.status() == WL_CONNECTED) { Serial.println(""); - - // Wait for connection - uint8 u8Tries = p_u32Timeout; - while ((WiFi.status() != WL_CONNECTED) && - (u8Tries--)) { - delay(500); - Serial.print("."); - } - if (WiFi.status() == WL_CONNECTED) - { - Serial.println(""); - Serial.print("Connected to "); - Serial.println(p_pcSSID); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - } - else - { - Serial.printf("FAILED to connect to '%s'!\n", p_pcSSID); - } + Serial.print("Connected to "); + Serial.println(p_pcSSID); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + } else { + Serial.printf("FAILED to connect to '%s'!\n", p_pcSSID); + } } -void setup(void) -{ - Serial.begin(115200); - Serial.setDebugOutput(false); - delay(2000); - Serial.printf("\nStart\n"); - - // Setup WiFi and AP - WiFi.setAutoConnect(false); - WiFi.mode(WIFI_AP_STA); - WiFi.softAP("ESP8266", "12345678"); - Serial.print("Created AP "); - Serial.println("ESP8266"); - Serial.print("AP-IP address: "); - Serial.println(WiFi.softAPIP()); - - if (mDNSHost_AP.begin("ESP8266", WIFI_AP, [](clsMDNSHost& p_rMDNSHost, - const char* p_pcDomainName, - bool p_bProbeResult)->void - { - Serial.printf("mDNSHost_AP::ProbeResultCallback: '%s' is %s\n", p_pcDomainName, (p_bProbeResult ? "FREE" : "USED!")); - - // Unattended added service - p_rMDNSHost.addService(0, "http", "tcp", 80); - })) - { - Serial.println("mDNS-AP started"); - } - else - { - Serial.println("FAILED to start mDNS-AP"); - } - - // Connect to WiFi network, with WRONG password - connectToWiFi("AP8", "WRONG_PW", 5); - - if (mDNSHost_STA.begin("esp8266", WIFI_STA, [](clsMDNSHost& p_rMDNSHost, - const char* p_pcDomainName, - bool p_bProbeResult)->void - { - Serial.printf("mDNSHost_STA::ProbeResultCallback: '%s' is %s\n", p_pcDomainName, (p_bProbeResult ? "FREE" : "USED!")); - if (p_bProbeResult) - { - p_rMDNSHost.addService("LEA_Weather", "http", "tcp", 80, [](clsMDNSHost::clsService& p_rService, - const char* p_pcInstanceName, - bool p_bProbeResult)->void - { - Serial.printf("mDNSHost_STA::HTTP-Service::ProbeResultCallback: '%s' is %s\n", p_pcInstanceName, (p_bProbeResult ? "FREE" : "USED!")); - if (p_bProbeResult) - { - if (!p_rService.addServiceTxt("path", "/")) - { - Serial.println("FAILED to add service TXT item!"); - } - p_rService.setDynamicServiceTxtCallback([](clsMDNSHost::clsService& p_rService)->void - { - Serial.printf("mDNSHost_STA::HTTP-Service::DynamicTXTCallback\n"); - - p_rService.addDynamicServiceTxt("user", "admin"); - static uint32_t u32Counter = 0; - p_rService.addDynamicServiceTxt("cnt", ++u32Counter); - }); - } - else - { - if (p_rService.indexInstanceName()) - { - Serial.printf("Changed service instance name to '%s'\n", p_rService.instanceName()); - } - else - { - Serial.println("FAILED to index service instance name!"); - } - } - }); - - // Unattended added service - p_rMDNSHost.addService("MQTTInstance", "mqtt", "tcp", 1883); - } - else - { - p_rMDNSHost.indexHostName(); - } - })) - { - Serial.println("mDNS-STA started"); - } - else - { - Serial.println("FAILED to start mDNS-STA"); +void setup(void) { + Serial.begin(115200); + Serial.setDebugOutput(false); + delay(2000); + Serial.printf("\nStart\n"); + + // Setup WiFi and AP + WiFi.setAutoConnect(false); + WiFi.mode(WIFI_AP_STA); + WiFi.softAP("ESP8266", "12345678"); + Serial.print("Created AP "); + Serial.println("ESP8266"); + Serial.print("AP-IP address: "); + Serial.println(WiFi.softAPIP()); + + if (mDNSHost_AP.begin("ESP8266", WIFI_AP, [](clsMDNSHost & p_rMDNSHost, + const char* p_pcDomainName, + bool p_bProbeResult)->void { + Serial.printf("mDNSHost_AP::ProbeResultCallback: '%s' is %s\n", p_pcDomainName, (p_bProbeResult ? "FREE" : "USED!")); + + // Unattended added service + p_rMDNSHost.addService(0, "http", "tcp", 80); + })) { + Serial.println("mDNS-AP started"); + } else { + Serial.println("FAILED to start mDNS-AP"); + } + + // Connect to WiFi network, with WRONG password + connectToWiFi("AP8", "WRONG_PW", 5); + + if (mDNSHost_STA.begin("esp8266", WIFI_STA, [](clsMDNSHost & p_rMDNSHost, + const char* p_pcDomainName, + bool p_bProbeResult)->void { + Serial.printf("mDNSHost_STA::ProbeResultCallback: '%s' is %s\n", p_pcDomainName, (p_bProbeResult ? "FREE" : "USED!")); + if (p_bProbeResult) { + p_rMDNSHost.addService("LEA_Weather", "http", "tcp", 80, [](clsMDNSHost::clsService & p_rService, + const char* p_pcInstanceName, + bool p_bProbeResult)->void { + Serial.printf("mDNSHost_STA::HTTP-Service::ProbeResultCallback: '%s' is %s\n", p_pcInstanceName, (p_bProbeResult ? "FREE" : "USED!")); + if (p_bProbeResult) { + if (!p_rService.addServiceTxt("path", "/")) { + Serial.println("FAILED to add service TXT item!"); + } + p_rService.setDynamicServiceTxtCallback([](clsMDNSHost::clsService & p_rService)->void { + Serial.printf("mDNSHost_STA::HTTP-Service::DynamicTXTCallback\n"); + + p_rService.addDynamicServiceTxt("user", "admin"); + static uint32_t u32Counter = 0; + p_rService.addDynamicServiceTxt("cnt", ++u32Counter); + }); + } else { + if (p_rService.indexInstanceName()) { + Serial.printf("Changed service instance name to '%s'\n", p_rService.instanceName()); + } else { + Serial.println("FAILED to index service instance name!"); + } + } + }); + + // Unattended added service + p_rMDNSHost.addService("MQTTInstance", "mqtt", "tcp", 1883); + } else { + p_rMDNSHost.indexHostName(); } - - // Non-synchronized added service - mDNSHost_STA.addService(0, "test", "tcp", 999); - - // Setup HTTP server - server.on("/", [](void) - { - Serial.println("server.on"); - server.send(200, "text/plain", "test"); - }); - server.begin(); - Serial.println("HTTP server started"); + })) { + Serial.println("mDNS-STA started"); + } else { + Serial.println("FAILED to start mDNS-STA"); + } + + // Non-synchronized added service + mDNSHost_STA.addService(0, "test", "tcp", 999); + + // Setup HTTP server + server.on("/", [](void) { + Serial.println("server.on"); + server.send(200, "text/plain", "test"); + }); + server.begin(); + Serial.println("HTTP server started"); } -void loop(void) -{ - server.handleClient(); - mDNSHost_AP.update(); - mDNSHost_STA.update(); +void loop(void) { + server.handleClient(); + mDNSHost_AP.update(); + mDNSHost_STA.update(); - static esp8266::polledTimeout::oneShotMs timer2(esp8266::polledTimeout::oneShotMs::alwaysExpired); - if (timer2) - { - Serial.println("FIX PASSWORD"); - connectToWiFi("AP8", "_______"); + static esp8266::polledTimeout::oneShotMs timer2(esp8266::polledTimeout::oneShotMs::alwaysExpired); + if (timer2) { + Serial.println("FIX PASSWORD"); + connectToWiFi("AP8", "_______"); - timer2.reset(esp8266::polledTimeout::oneShotMs::neverExpires); - } + timer2.reset(esp8266::polledTimeout::oneShotMs::neverExpires); + } } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index dd46d09ad3..cee90c907c 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -51,8 +51,8 @@ const char* strrstr(const char*__restrict p_pcString, size_t stPatternLength = (p_pcPattern ? strlen(p_pcPattern) : 0); if ((stStringLength) && - (stPatternLength) && - (stPatternLength <= stStringLength)) + (stPatternLength) && + (stPatternLength <= stStringLength)) { // Pattern is shorter or has the same length than the string for (const char* s = (p_pcString + stStringLength - stPatternLength); s >= p_pcString; --s) @@ -99,8 +99,8 @@ namespace experimental */ //static const char* clsLEAMDNSHost::indexDomainName(const char* p_pcDomainName, - const char* p_pcDivider /*= "-"*/, - const char* p_pcDefaultDomainName /*= 0*/) + const char* p_pcDivider /*= "-"*/, + const char* p_pcDefaultDomainName /*= 0*/) { static char acResultDomainName[clsConsts::stDomainLabelMaxLength]; *acResultDomainName = 0; @@ -117,8 +117,8 @@ const char* clsLEAMDNSHost::indexDomainName(const char* p_pcDomainName, char* pEnd = 0; unsigned long ulIndex = strtoul((pFoundDivider + strlen(pcDivider)), &pEnd, 10); if ((ulIndex) && - ((pEnd - p_pcDomainName) == (ptrdiff_t)strlen(p_pcDomainName)) && - (!*pEnd)) + ((pEnd - p_pcDomainName) == (ptrdiff_t)strlen(p_pcDomainName)) && + (!*pEnd)) { // Valid (old) index found char acIndexBuffer[16]; @@ -165,7 +165,7 @@ bool clsLEAMDNSHost::setNetIfHostName(netif* p_pNetIf, const char* p_pcHostName) { if ((p_pNetIf) && - (p_pcHostName)) + (p_pcHostName)) { netif_set_hostname(p_pNetIf, (char*)p_pcHostName); // LWIP 1.x forces 'char*' instead of 'const char*' DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[mDNS] setNetIfHostName host name: %s!\n"), p_pcHostName);); @@ -332,7 +332,7 @@ bool clsLEAMDNSHost::setHostName(const char* p_pcHostName) for (clsService* pService : m_Services) { if ((pService->m_bAutoName) && - (!m_pcDefaultInstanceName)) + (!m_pcDefaultInstanceName)) { if (!((bResult = pService->setInstanceName(p_pcHostName)))) { @@ -426,10 +426,10 @@ const char* clsLEAMDNSHost::defaultInstanceName(void) const */ clsLEAMDNSHost::clsService* clsLEAMDNSHost::addService(const char* p_pcInstanceName, - const char* p_pcType, - const char* p_pcProtocol, - uint16_t p_u16Port, - clsLEAMDNSHost::clsService::fnProbeResultCallback p_fnCallback /*= 0*/) + const char* p_pcType, + const char* p_pcProtocol, + uint16_t p_u16Port, + clsLEAMDNSHost::clsService::fnProbeResultCallback p_fnCallback /*= 0*/) { clsService* pService = 0; @@ -439,10 +439,10 @@ clsLEAMDNSHost::clsService* clsLEAMDNSHost::addService(const char* p_pcInstanceN if ((pService = new clsService)) { if ((pService->setInstanceName(_instanceName(p_pcInstanceName))) && - (pService->setType(p_pcType)) && - (pService->setProtocol(p_pcProtocol)) && - (pService->setPort(p_u16Port)) && - (p_fnCallback ? pService->setProbeResultCallback(p_fnCallback) : true)) + (pService->setType(p_pcType)) && + (pService->setProtocol(p_pcProtocol)) && + (pService->setPort(p_u16Port)) && + (p_fnCallback ? pService->setProbeResultCallback(p_fnCallback) : true)) { m_Services.push_back(pService); } @@ -482,19 +482,19 @@ bool clsLEAMDNSHost::removeService(clsLEAMDNSHost::clsService* p_pService) */ const clsLEAMDNSHost::clsService* clsLEAMDNSHost::findService(const char* p_pcInstanceName, - const char* p_pcType, - const char* p_pcProtocol, - uint16_t p_u16Port/*= (uint16_t)(-1)*/) const + const char* p_pcType, + const char* p_pcProtocol, + uint16_t p_u16Port/*= (uint16_t)(-1)*/) const { clsService* pFoundService = 0; for (clsService* pService : m_Services) { if ((0 == strcmp(pService->instanceName(), _instanceName(p_pcInstanceName))) && - (0 == strcmp(pService->type(), p_pcType)) && - (0 == strcmp(pService->protocol(), p_pcProtocol)) && - (((uint16_t)(-1) == p_u16Port) || - (pService->port() == p_u16Port))) + (0 == strcmp(pService->type(), p_pcType)) && + (0 == strcmp(pService->protocol(), p_pcProtocol)) && + (((uint16_t)(-1) == p_u16Port) || + (pService->port() == p_u16Port))) { pFoundService = pService; break; @@ -508,20 +508,20 @@ const clsLEAMDNSHost::clsService* clsLEAMDNSHost::findService(const char* p_pcIn */ clsLEAMDNSHost::clsService* clsLEAMDNSHost::findService(const char* p_pcInstanceName, - const char* p_pcType, - const char* p_pcProtocol, - uint16_t p_u16Port /*= (uint16_t)(-1)*/) + const char* p_pcType, + const char* p_pcProtocol, + uint16_t p_u16Port /*= (uint16_t)(-1)*/) { return (clsService*)((const clsLEAMDNSHost*)this)->findService(p_pcInstanceName, p_pcType, p_pcProtocol, p_u16Port); } /* clsLEAMDNSHost::services - + */ const clsLEAMDNSHost::clsService::list& clsLEAMDNSHost::services(void) const { - return m_Services; + return m_Services; } @@ -536,21 +536,21 @@ const clsLEAMDNSHost::clsService::list& clsLEAMDNSHost::services(void) const */ clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryService(const char* p_pcService, - const char* p_pcProtocol, - const uint16_t p_u16Timeout) + const char* p_pcProtocol, + const uint16_t p_u16Timeout) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryService '_%s._%s.local'\n"), _DH(), p_pcService, p_pcProtocol);); clsQuery* pQuery = 0; if ((p_pcService) && (*p_pcService) && - (p_pcProtocol) && (*p_pcProtocol) && - (p_u16Timeout) && - ((pQuery = _allocQuery(clsQuery::enuQueryType::Service))) && - (_buildDomainForService(p_pcService, p_pcProtocol, pQuery->m_Domain))) + (p_pcProtocol) && (*p_pcProtocol) && + (p_u16Timeout) && + ((pQuery = _allocQuery(clsQuery::enuQueryType::Service))) && + (_buildDomainForService(p_pcService, p_pcProtocol, pQuery->m_Domain))) { if ((_removeLegacyQuery()) && - ((pQuery->m_bStaticQuery = true)) && - (_sendQuery(*pQuery))) + ((pQuery->m_bStaticQuery = true)) && + (_sendQuery(*pQuery))) { // Wait for answers to arrive DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryService: Waiting %u ms for answers...\n"), _DH(), p_u16Timeout);); @@ -579,19 +579,19 @@ clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryService */ clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryHost(const char* p_pcHostName, - const uint16_t p_u16Timeout) + const uint16_t p_u16Timeout) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost '%s.local'\n"), _DH(), p_pcHostName);); clsQuery* pQuery = 0; if ((p_pcHostName) && (*p_pcHostName) && - (p_u16Timeout) && - ((pQuery = _allocQuery(clsQuery::enuQueryType::Host))) && - (_buildDomainForHost(p_pcHostName, pQuery->m_Domain))) + (p_u16Timeout) && + ((pQuery = _allocQuery(clsQuery::enuQueryType::Host))) && + (_buildDomainForHost(p_pcHostName, pQuery->m_Domain))) { if ((_removeLegacyQuery()) && - ((pQuery->m_bStaticQuery = true)) && - (_sendQuery(*pQuery))) + ((pQuery->m_bStaticQuery = true)) && + (_sendQuery(*pQuery))) { // Wait for answers to arrive DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost: Waiting %u ms for answers...\n"), _DH(), p_u16Timeout);); @@ -647,8 +647,8 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::getQuery(void) */ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcService, - const char* p_pcProtocol, - clsLEAMDNSHost::clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer) + const char* p_pcProtocol, + clsLEAMDNSHost::clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer) { clsQuery* pQuery = 0; if ((pQuery = _installServiceQuery(p_pcService, p_pcProtocol))) @@ -663,8 +663,8 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcSe */ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcService, - const char* p_pcProtocol, - clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor) + const char* p_pcProtocol, + clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor) { clsQuery* pQuery = 0; if ((pQuery = _installServiceQuery(p_pcService, p_pcProtocol))) @@ -678,7 +678,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcSe clsLEAmDNS2_Host::installHostQuery (answer) */ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installHostQuery(const char* p_pcHostName, - clsLEAMDNSHost::clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer) + clsLEAMDNSHost::clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer) { clsQuery* pQuery = 0; if ((p_pcHostName) && (*p_pcHostName)) @@ -698,7 +698,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installHostQuery(const char* p_pcHostN clsLEAmDNS2_Host::installHostQuery (accessor) */ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installHostQuery(const char* p_pcHostName, - clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor) + clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor) { clsQuery* pQuery = 0; if ((p_pcHostName) && (*p_pcHostName)) @@ -803,7 +803,7 @@ UdpContext* clsLEAMDNSHost::_allocBackbone(void) DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _allocBackbone: Created backbone.\n"), _DH());); if ((clsBackbone::sm_pBackbone) && - (!clsBackbone::sm_pBackbone->init())) + (!clsBackbone::sm_pBackbone->init())) { DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _allocBackbone: FAILED to init backbone!\n"), _DH());); @@ -828,8 +828,8 @@ bool clsLEAMDNSHost::_releaseBackbone(void) bool bResult = false; if ((clsBackbone::sm_pBackbone) && - ((bResult = clsBackbone::sm_pBackbone->removeHost(this))) && - (0 == clsBackbone::sm_pBackbone->hostCount())) + ((bResult = clsBackbone::sm_pBackbone->removeHost(this))) && + (0 == clsBackbone::sm_pBackbone->hostCount())) { delete clsBackbone::sm_pBackbone; clsBackbone::sm_pBackbone = 0; @@ -945,14 +945,14 @@ clsLEAMDNSHost::typeNetIfState clsLEAMDNSHost::_getNetIfState(void) const typeNetIfState curNetIfState = static_cast(enuNetIfState::None); if ((m_pNetIf) && - (netif_is_up(m_pNetIf))) + (netif_is_up(m_pNetIf))) { curNetIfState |= static_cast(enuNetIfState::IsUp); // Check if netif link is up if ((netif_is_link_up(m_pNetIf)) && - ((m_pNetIf != netif_get_by_index(WIFI_STA)) || - (STATION_GOT_IP == wifi_station_get_connect_status()))) + ((m_pNetIf != netif_get_by_index(WIFI_STA)) || + (STATION_GOT_IP == wifi_station_get_connect_status()))) { curNetIfState |= static_cast(enuNetIfState::LinkIsUp); } @@ -1084,7 +1084,7 @@ bool clsLEAMDNSHost::_allocDomainName(const char* p_pcNewDomainName, size_t stLength = 0; if ((p_pcNewDomainName) && - (clsConsts::stDomainLabelMaxLength >= (stLength = strlen(p_pcNewDomainName)))) // char max size for a single label + (clsConsts::stDomainLabelMaxLength >= (stLength = strlen(p_pcNewDomainName)))) // char max size for a single label { // Copy in hostname characters as lowercase if ((bResult = (0 != (p_rpcDomainName = new char[stLength + 1])))) @@ -1156,7 +1156,7 @@ bool clsLEAMDNSHost::_releaseDefaultInstanceName(void) clsLEAmDNS2_Host::_instanceName */ const char* clsLEAMDNSHost::_instanceName(const char* p_pcInstanceName, - bool p_bReturnZero /*= true*/) const + bool p_bReturnZero /*= true*/) const { return (p_pcInstanceName ? : (m_pcDefaultInstanceName ? : (m_pcHostName ? : (p_bReturnZero ? 0 : "-")))); } @@ -1283,8 +1283,8 @@ bool clsLEAMDNSHost::_releaseQueries(void) */ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_findNextQueryByDomain(const clsLEAMDNSHost::clsRRDomain& p_Domain, - const clsLEAMDNSHost::clsQuery::enuQueryType p_QueryType, - const clsQuery* p_pPrevQuery) + const clsLEAMDNSHost::clsQuery::enuQueryType p_QueryType, + const clsQuery* p_pPrevQuery) { clsQuery* pMatchingQuery = 0; @@ -1292,17 +1292,21 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_findNextQueryByDomain(const clsLEAMDN if (p_pPrevQuery) { if (m_Queries.end() != ((it = std::find(m_Queries.begin(), m_Queries.end(), p_pPrevQuery)))) - { // Found previous object + { + // Found previous object it++; } - DEBUG_EX_ERR(else DEBUG_OUTPUT.printf_P(PSTR("%s _findNextQueryByDomain: FAILED to find 'previous' object!\n"), _DH());); // if not prev was found -> 'cancel' + DEBUG_EX_ERR(else + { + DEBUG_OUTPUT.printf_P(PSTR("%s _findNextQueryByDomain: FAILED to find 'previous' object!\n"), _DH()); + }); // if not prev was found -> 'cancel' } for (; it != m_Queries.end(); it++) { if (((clsQuery::enuQueryType::None == p_QueryType) || - ((*it)->m_QueryType == p_QueryType)) && - (p_Domain == (*it)->m_Domain)) + ((*it)->m_QueryType == p_QueryType)) && + (p_Domain == (*it)->m_Domain)) { pMatchingQuery = *it; break; @@ -1316,14 +1320,14 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_findNextQueryByDomain(const clsLEAMDN */ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installServiceQuery(const char* p_pcService, - const char* p_pcProtocol) + const char* p_pcProtocol) { clsQuery* pMDNSQuery = 0; if ((p_pcService) && (*p_pcService) && - (p_pcProtocol) && (*p_pcProtocol) && - ((pMDNSQuery = _allocQuery(clsQuery::enuQueryType::Service))) && - (_buildDomainForService(p_pcService, p_pcProtocol, pMDNSQuery->m_Domain))) + (p_pcProtocol) && (*p_pcProtocol) && + ((pMDNSQuery = _allocQuery(clsQuery::enuQueryType::Service))) && + (_buildDomainForService(p_pcService, p_pcProtocol, pMDNSQuery->m_Domain))) { pMDNSQuery->m_bStaticQuery = false; @@ -1346,7 +1350,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installServiceQuery(const char* p_pcS clsLEAmDNS2_Host::_installDomainQuery */ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installDomainQuery(clsLEAMDNSHost::clsRRDomain& p_Domain, - clsLEAMDNSHost::clsQuery::enuQueryType p_QueryType) + clsLEAMDNSHost::clsQuery::enuQueryType p_QueryType) { clsQuery* pQuery = 0; @@ -1402,9 +1406,9 @@ bool clsLEAMDNSHost::_hasQueriesWaitingForAnswers(void) const clsLEAmDNS2_Host::_executeQueryCallback */ bool clsLEAMDNSHost::_executeQueryCallback(const clsQuery& p_Query, - const clsQuery::clsAnswer& p_Answer, - clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, - bool p_bSetContent) + const clsQuery::clsAnswer& p_Answer, + clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, + bool p_bSetContent) { if (p_Query.m_fnCallbackAnswer) { diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index 5a63d1f59e..ff590473c7 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -465,8 +465,8 @@ class clsLEAMDNSHost Callback function for host domain probe results */ using fnProbeResultCallback = std::function; + const char* p_pcDomainName, + bool p_bProbeResult)>; protected: /** @@ -498,8 +498,8 @@ class clsLEAMDNSHost fnProbeResultCallback */ using fnProbeResultCallback = std::function; + const char* p_pcInstanceName, + bool p_bProbeResult)>; protected: friend clsLEAMDNSHost; @@ -1188,16 +1188,16 @@ class clsLEAMDNSHost QueryCallbackAnswerFn */ using QueryCallbackAnswerFn = std::function; // true: Answer component set, false: component deleted + const clsAnswer& p_Answer, + clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, // flags for the updated answer item + bool p_bSetContent)>; // true: Answer component set, false: component deleted /** QueryCallbackAccessorFn */ using QueryCallbackAccessorFn = std::function; // true: Answer component set, false: component deleted + const clsAnswerAccessor& p_AnswerAccessor, + clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, // flags for the updated answer item + bool p_bSetContent)>; // true: Answer component set, false: component deleted protected: friend clsLEAMDNSHost; @@ -1292,7 +1292,7 @@ class clsLEAMDNSHost const char* p_pcType, const char* p_pcProtocol, uint16_t p_u16Port = (uint16_t)(-1)); - const clsService::list& services(void) const; + const clsService::list& services(void) const; // QUERIES @@ -1303,10 +1303,10 @@ class clsLEAMDNSHost // - answerIP (or IP) // - answerPort (or port) clsQuery::clsAnswerAccessor::vector queryService(const char* p_pcService, - const char* p_pcProtocol, - const uint16_t p_u16Timeout); + const char* p_pcProtocol, + const uint16_t p_u16Timeout); clsQuery::clsAnswerAccessor::vector queryHost(const char* p_pcHostName, - const uint16_t p_u16Timeout); + const uint16_t p_u16Timeout); bool removeQuery(void); bool hasQuery(void); clsQuery* getQuery(void); diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index 11fece09c1..efc9d1879e 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -133,8 +133,8 @@ bool clsLEAMDNSHost::_parseQuery(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader // Define host replies, BUT only answer queries after probing is done u32HostOrServiceReplies = sendParameter.m_u32HostReplyMask |= ((probeStatus()) - ? _replyMaskForHost(questionRR.m_Header, 0) - : 0); + ? _replyMaskForHost(questionRR.m_Header, 0) + : 0); DEBUG_EX_INFO(if (u32HostOrServiceReplies) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Host reply needed %s\n"), _DH(), _replyFlags2String(u32HostOrServiceReplies));); // Check tiebreak need for host domain @@ -142,7 +142,7 @@ bool clsLEAMDNSHost::_parseQuery(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader { bool bFullNameMatch = false; if ((_replyMaskForHost(questionRR.m_Header, &bFullNameMatch)) && - (bFullNameMatch)) + (bFullNameMatch)) { // We're in 'probing' state and someone is asking for our host domain: this might be // a race-condition: Two hosts with the same domain names try simutanously to probe their domains @@ -172,7 +172,7 @@ bool clsLEAMDNSHost::_parseQuery(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader { bool bFullNameMatch = false; if ((_replyMaskForService(questionRR.m_Header, *pService, &bFullNameMatch)) && - (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 @@ -189,8 +189,8 @@ bool clsLEAMDNSHost::_parseQuery(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader // 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)) + (questionRR.m_bUnicast)) && // Expressivly unicast query + (!sendParameter.m_bUnicast)) { sendParameter.m_bUnicast = true; //sendParameter.m_bCacheFlush = false; @@ -198,9 +198,9 @@ bool clsLEAMDNSHost::_parseQuery(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader //Serial.printf_P(PSTR("%s _parseQuery: Ignored Unicast response asked for by %s!\n"), _DH(), IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str()); if ((DNS_MQUERY_PORT != m_pUDPContext->getRemotePort()) && // Unicast (maybe legacy) query AND - (1 == p_MsgHeader.m_u16QDCount) && // Only one question AND - ((sendParameter.m_u32HostReplyMask) || // Host replies OR - (u32HostOrServiceReplies))) // Host or service replies available + (1 == p_MsgHeader.m_u16QDCount) && // Only one question AND + ((sendParameter.m_u32HostReplyMask) || // Host replies OR + (u32HostOrServiceReplies))) // Host or service replies available { // Local host check // We're a match for this legacy query, BUT @@ -209,20 +209,20 @@ bool clsLEAMDNSHost::_parseQuery(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader ip_info IPInfo_Local; #endif if ((m_pNetIf) && - (m_pUDPContext) && + (m_pUDPContext) && #ifdef MDNS_IPV4_SUPPORT - (m_pUDPContext->getRemoteAddress().isV4()) && - ((wifi_get_ip_info(netif_get_index(m_pNetIf), &IPInfo_Local))) && - (ip4_addr_netcmp(ip_2_ip4((const ip_addr_t*)m_pUDPContext->getRemoteAddress()), &IPInfo_Local.ip, &IPInfo_Local.netmask)) + (m_pUDPContext->getRemoteAddress().isV4()) && + ((wifi_get_ip_info(netif_get_index(m_pNetIf), &IPInfo_Local))) && + (ip4_addr_netcmp(ip_2_ip4((const ip_addr_t*)m_pUDPContext->getRemoteAddress()), &IPInfo_Local.ip, &IPInfo_Local.netmask)) #else - (true) + (true) #endif - && + && #ifdef MDNS_IPV6_SUPPORT - (m_pUDPContext->getRemoteAddress().isV6()) && - (ip6_addr_islinklocal(ip_2_ip6((const ip_addr_t*)m_pUDPContext->getRemoteAddress()))) + (m_pUDPContext->getRemoteAddress().isV6()) && + (ip6_addr_islinklocal(ip_2_ip6((const ip_addr_t*)m_pUDPContext->getRemoteAddress()))) #else - (true) + (true) #endif ) { @@ -275,8 +275,8 @@ bool clsLEAMDNSHost::_parseQuery(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader // Handle known answers uint32_t u32Answers = (p_MsgHeader.m_u16ANCount + p_MsgHeader.m_u16NSCount + p_MsgHeader.m_u16ARCount); if (((u32HostOrServiceReplies) || - (bHostOrServiceTiebreakNeeded)) && - (u32Answers)) + (bHostOrServiceTiebreakNeeded)) && + (u32Answers)) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Reading known answers(%u):\n"), _DH(), u32Answers);); @@ -284,10 +284,10 @@ bool clsLEAMDNSHost::_parseQuery(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader { clsRRAnswer* pKnownRRAnswer = 0; if (((bResult = _readRRAnswer(pKnownRRAnswer))) && - (pKnownRRAnswer)) + (pKnownRRAnswer)) { if ((DNS_RRTYPE_ANY != pKnownRRAnswer->m_Header.m_Attributes.m_u16Type) && // No ANY type answer - (DNS_RRCLASS_ANY != (pKnownRRAnswer->m_Header.m_Attributes.m_u16Class & (~0x8000)))) // No ANY class answer + (DNS_RRCLASS_ANY != (pKnownRRAnswer->m_Header.m_Attributes.m_u16Class & (~0x8000)))) // No ANY class answer { /* - RFC6762 7.1 Suppression only for 'Shared Records' - // Find match between planned answer (sendParameter.m_u8HostReplyMask) and this 'known answer' @@ -357,7 +357,7 @@ bool clsLEAMDNSHost::_parseQuery(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader { clsRRDomain hostDomain; if ((_buildDomainForHost(m_pcHostName, hostDomain)) && - (pKnownRRAnswer->m_Header.m_Domain == hostDomain)) + (pKnownRRAnswer->m_Header.m_Domain == hostDomain)) { // Host domain match #ifdef MDNS_IPV4_SUPPORT @@ -429,21 +429,21 @@ bool clsLEAMDNSHost::_parseQuery(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader uint32_t u32ServiceMatchMask = (pService->m_u32ReplyMask & _replyMaskForService(pKnownRRAnswer->m_Header, *pService)); if ((u32ServiceMatchMask) && // The RR in the known answer matches an RR we are planning to send, AND - ((clsConsts::u32ServiceTTL / 2) <= pKnownRRAnswer->m_u32TTL)) // The TTL of the known answer is longer than half of the new service TTL (4500s) + ((clsConsts::u32ServiceTTL / 2) <= pKnownRRAnswer->m_u32TTL)) // The TTL of the known answer is longer than half of the new service TTL (4500s) { if (enuAnswerType::PTR == pKnownRRAnswer->answerType()) { clsRRDomain serviceDomain; if ((u32ServiceMatchMask & static_cast(enuContentFlag::PTR_TYPE)) && - (_buildDomainForService(*pService, false, serviceDomain)) && - (serviceDomain == ((clsRRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain)) + (_buildDomainForService(*pService, false, serviceDomain)) && + (serviceDomain == ((clsRRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain)) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service type PTR already known (TTL:%u)... skipping!\n"), _DH(pService), pKnownRRAnswer->m_u32TTL);); pService->m_u32ReplyMask &= ~static_cast(enuContentFlag::PTR_TYPE); } if ((u32ServiceMatchMask & static_cast(enuContentFlag::PTR_NAME)) && - (_buildDomainForService(*pService, true, serviceDomain)) && - (serviceDomain == ((clsRRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain)) + (_buildDomainForService(*pService, true, serviceDomain)) && + (serviceDomain == ((clsRRAnswerPTR*)pKnownRRAnswer)->m_PTRDomain)) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Service name PTR already known (TTL:%u)... skipping!\n"), _DH(pService), pKnownRRAnswer->m_u32TTL);); pService->m_u32ReplyMask &= ~static_cast(enuContentFlag::PTR_NAME); @@ -488,14 +488,14 @@ bool clsLEAMDNSHost::_parseQuery(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader { clsRRDomain serviceDomain; if ((_buildDomainForService(*pService, true, serviceDomain)) && - (pKnownRRAnswer->m_Header.m_Domain == serviceDomain)) + (pKnownRRAnswer->m_Header.m_Domain == serviceDomain)) { // Service domain match if (enuAnswerType::SRV == pKnownRRAnswer->answerType()) { clsRRDomain hostDomain; if ((_buildDomainForHost(m_pcHostName, hostDomain)) && - (hostDomain == ((clsRRAnswerSRV*)pKnownRRAnswer)->m_SRVDomain)) // Host domain match + (hostDomain == ((clsRRAnswerSRV*)pKnownRRAnswer)->m_SRVDomain)) // Host domain match { // We've received an old message from ourselfs (same SRV) DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Tiebreak (SRV) won (was an old message)!\n"), _DH(pService));); @@ -626,7 +626,7 @@ bool clsLEAMDNSHost::_parseResponse(const clsLEAMDNSHost::clsMsgHeader& p_MsgHea // A response should be the result of a query or a probe if ((_hasQueriesWaitingForAnswers()) || // Waiting for query answers OR - (_hasProbesWaitingForAnswers())) // Probe responses + (_hasProbesWaitingForAnswers())) // Probe responses { DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: Received a response\n"), _DH()); @@ -651,7 +651,7 @@ bool clsLEAMDNSHost::_parseResponse(const clsLEAMDNSHost::clsMsgHeader& p_MsgHea { clsRRAnswer* pRRAnswer = 0; if (((bResult = _readRRAnswer(pRRAnswer))) && - (pRRAnswer)) + (pRRAnswer)) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse: ADDING answer!\n"));); pRRAnswer->m_pNext = pCollectedRRAnswers; @@ -753,36 +753,41 @@ bool clsLEAMDNSHost::_processAnswers(const clsLEAMDNSHost::clsRRAnswer* p_pAnswe const clsRRAnswer* pRRAnswer = p_pAnswers; while ((pRRAnswer) && - (bResult)) + (bResult)) { // 1. level answer (PTR) if (enuAnswerType::PTR == pRRAnswer->answerType()) - { // eg. _http._tcp.local PTR xxxx xx MyESP._http._tcp.local + { + // eg. _http._tcp.local PTR xxxx xx MyESP._http._tcp.local bResult = _processPTRAnswer((clsRRAnswerPTR*)pRRAnswer, bFoundNewKeyAnswer); // May 'enable' new SRV or TXT answers to be linked to queries } // 2. level answers // SRV -> host domain and port else if (enuAnswerType::SRV == pRRAnswer->answerType()) - { // eg. MyESP._http._tcp.local SRV xxxx xx yy zz 5000 esp8266.local + { + // eg. MyESP._http._tcp.local SRV xxxx xx yy zz 5000 esp8266.local bResult = _processSRVAnswer((clsRRAnswerSRV*)pRRAnswer, bFoundNewKeyAnswer); // May 'enable' new A/AAAA answers to be linked to queries } // TXT -> Txts else if (enuAnswerType::TXT == pRRAnswer->answerType()) - { // eg. MyESP_http._tcp.local TXT xxxx xx c#=1 + { + // eg. MyESP_http._tcp.local TXT xxxx xx c#=1 bResult = _processTXTAnswer((clsRRAnswerTXT*)pRRAnswer); } // 3. level answers #ifdef MDNS_IPV4_SUPPORT // A -> IPv4Address else if (enuAnswerType::A == pRRAnswer->answerType()) - { // eg. esp8266.local A xxxx xx 192.168.2.120 + { + // eg. esp8266.local A xxxx xx 192.168.2.120 bResult = _processAAnswer((clsRRAnswerA*)pRRAnswer); } #endif #ifdef MDNS_IPV6_SUPPORT // AAAA -> IPv6Address else if (enuAnswerType::AAAA == pRRAnswer->answerType()) - { // eg. esp8266.local AAAA xxxx xx 09cf::0c + { + // eg. esp8266.local AAAA xxxx xx 09cf::0c bResult = _processAAAAAnswer((clsRRAnswerAAAA*)pRRAnswer); } #endif @@ -790,24 +795,24 @@ bool clsLEAMDNSHost::_processAnswers(const clsLEAMDNSHost::clsRRAnswer* p_pAnswe // Finally check for probing conflicts // Host domain if ((clsProbeInformation_Base::enuProbingStatus::InProgress == m_ProbeInformation.m_ProbingStatus) && - ((enuAnswerType::A == pRRAnswer->answerType()) || - (enuAnswerType::AAAA == pRRAnswer->answerType()))) + ((enuAnswerType::A == pRRAnswer->answerType()) || + (enuAnswerType::AAAA == pRRAnswer->answerType()))) { clsRRDomain hostDomain; if ((_buildDomainForHost(m_pcHostName, hostDomain)) && - (pRRAnswer->m_Header.m_Domain == hostDomain)) + (pRRAnswer->m_Header.m_Domain == hostDomain)) { bool bPossibleEcho = false; #ifdef MDNS_IPV4_SUPPORT if ((enuAnswerType::A == pRRAnswer->answerType()) && - (((clsRRAnswerA*)pRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V4))) + (((clsRRAnswerA*)pRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V4))) { bPossibleEcho = true; } #endif #ifdef MDNS_IPV6_SUPPORT if ((enuAnswerType::AAAA == pRRAnswer->answerType()) && - (((clsRRAnswerAAAA*)pRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V6))) + (((clsRRAnswerAAAA*)pRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V6))) { bPossibleEcho = true; } @@ -828,12 +833,12 @@ bool clsLEAMDNSHost::_processAnswers(const clsLEAMDNSHost::clsRRAnswer* p_pAnswe for (clsService* pService : m_Services) { if ((clsProbeInformation_Base::enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) && - ((enuAnswerType::TXT == pRRAnswer->answerType()) || - (enuAnswerType::SRV == pRRAnswer->answerType()))) + ((enuAnswerType::TXT == pRRAnswer->answerType()) || + (enuAnswerType::SRV == pRRAnswer->answerType()))) { clsRRDomain serviceDomain; if ((_buildDomainForService(*pService, true, serviceDomain)) && - (pRRAnswer->m_Header.m_Domain == serviceDomain)) + (pRRAnswer->m_Header.m_Domain == serviceDomain)) { // TODO: Echo management needed? DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _processAnswers: Probing CONFLICT found with '%s'\n"), _DH(), _service2String(pService));); @@ -870,12 +875,15 @@ bool clsLEAMDNSHost::_processPTRAnswer(const clsLEAMDNSHost::clsRRAnswerPTR* p_p while (pQuery) { if (pQuery->m_bAwaitingAnswers) - { // Find answer for service domain (eg. MyESP._http._tcp.local) + { + // Find answer for service domain (eg. MyESP._http._tcp.local) clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForServiceDomain(p_pPTRAnswer->m_PTRDomain); if (pSQAnswer) - { // existing answer + { + // existing answer if (p_pPTRAnswer->m_u32TTL) - { // Received update message + { + // Received update message pSQAnswer->m_TTLServiceDomain.set(p_pPTRAnswer->m_u32TTL); // Update TTL tag DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processPTRAnswer: Updated TTL(%lu) for "), _DH(), p_pPTRAnswer->m_u32TTL); @@ -884,7 +892,8 @@ bool clsLEAMDNSHost::_processPTRAnswer(const clsLEAMDNSHost::clsRRAnswerPTR* p_p ); } else - { // received goodbye-message + { + // received goodbye-message pSQAnswer->m_TTLServiceDomain.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processPTRAnswer: 'Goodbye' received for "), _DH()); @@ -942,7 +951,8 @@ bool clsLEAMDNSHost::_processSRVAnswer(const clsLEAMDNSHost::clsRRAnswerSRV* p_p { // Answer for this service domain (eg. MyESP._http._tcp.local) available if (p_pSRVAnswer->m_u32TTL) - { // First or update message (TTL != 0) + { + // First or update message (TTL != 0) pSQAnswer->m_TTLHostDomainAndPort.set(p_pSRVAnswer->m_u32TTL); // Update TTL tag DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processSRVAnswer: Updated TTL(%lu) for "), _DH(), p_pSRVAnswer->m_u32TTL); @@ -951,7 +961,7 @@ bool clsLEAMDNSHost::_processSRVAnswer(const clsLEAMDNSHost::clsRRAnswerSRV* p_p ); // Host domain & Port if ((pSQAnswer->m_HostDomain != p_pSRVAnswer->m_SRVDomain) || - (pSQAnswer->m_u16Port != p_pSRVAnswer->m_u16Port)) + (pSQAnswer->m_u16Port != p_pSRVAnswer->m_u16Port)) { pSQAnswer->m_HostDomain = p_pSRVAnswer->m_SRVDomain; @@ -972,7 +982,8 @@ bool clsLEAMDNSHost::_processSRVAnswer(const clsLEAMDNSHost::clsRRAnswerSRV* p_p } } else - { // Goodby message + { + // Goodby message pSQAnswer->m_TTLHostDomainAndPort.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processSRVAnswer: 'Goodbye' received for "), _DH()); @@ -1006,9 +1017,11 @@ bool clsLEAMDNSHost::_processTXTAnswer(const clsLEAMDNSHost::clsRRAnswerTXT* p_p { clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForServiceDomain(p_pTXTAnswer->m_Header.m_Domain); if (pSQAnswer) - { // Answer for this service domain (eg. MyESP._http._tcp.local) available + { + // Answer for this service domain (eg. MyESP._http._tcp.local) available if (p_pTXTAnswer->m_u32TTL) - { // First or update message + { + // First or update message pSQAnswer->m_TTLTxts.set(p_pTXTAnswer->m_u32TTL); // Update TTL tag DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processTXTAnswer: Updated TTL(%lu) for "), _DH(), p_pTXTAnswer->m_u32TTL); @@ -1031,7 +1044,8 @@ bool clsLEAMDNSHost::_processTXTAnswer(const clsLEAMDNSHost::clsRRAnswerTXT* p_p } } else - { // Goodby message + { + // Goodby message pSQAnswer->m_TTLTxts.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processTXTAnswer: 'Goodbye' received for "), _DH()); @@ -1063,15 +1077,17 @@ bool clsLEAMDNSHost::_processAAnswer(const clsLEAMDNSHost::clsRRAnswerA* p_pAAns clsQuery* pQuery = *it; if (pQuery->m_bAwaitingAnswers) - { // Look for answers to host queries + { + // Look for answers to host queries if ((p_pAAnswer->m_u32TTL) && // NOT just a goodbye message - (clsQuery::enuQueryType::Host == pQuery->m_QueryType) && // AND a host query - (pQuery->m_Domain == p_pAAnswer->m_Header.m_Domain)) // AND a matching host domain + (clsQuery::enuQueryType::Host == pQuery->m_QueryType) && // AND a host query + (pQuery->m_Domain == p_pAAnswer->m_Header.m_Domain)) // AND a matching host domain { clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAnswer->m_Header.m_Domain); if ((!pSQAnswer) && - ((pSQAnswer = new clsQuery::clsAnswer))) - { // Add not yet included answer + ((pSQAnswer = new clsQuery::clsAnswer))) + { + // Add not yet included answer pSQAnswer->m_HostDomain = p_pAAnswer->m_Header.m_Domain; //pSQAnswer->releaseHostDomain(); @@ -1090,12 +1106,15 @@ bool clsLEAMDNSHost::_processAAnswer(const clsLEAMDNSHost::clsRRAnswerA* p_pAAns // Look for answers to service queries clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAnswer->m_Header.m_Domain); if (pSQAnswer) - { // Answer for this host domain (eg. esp8266.local) available + { + // Answer for this host domain (eg. esp8266.local) available clsQuery::clsAnswer::clsIPAddressWithTTL* pIPAddress = pSQAnswer->findIPv4Address(p_pAAnswer->m_IPAddress); if (pIPAddress) - { // Already known IPv4 address + { + // Already known IPv4 address if (p_pAAnswer->m_u32TTL) - { // Valid TTL -> Update answers TTL + { + // Valid TTL -> Update answers TTL pIPAddress->m_TTL.set(p_pAAnswer->m_u32TTL); DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: Updated TTL(%lu) for "), _DH(), p_pAAnswer->m_u32TTL); @@ -1104,7 +1123,8 @@ bool clsLEAMDNSHost::_processAAnswer(const clsLEAMDNSHost::clsRRAnswerA* p_pAAns ); } else - { // 'Goodbye' message for known IPv4 address + { + // 'Goodbye' message for known IPv4 address pIPAddress->m_TTL.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: 'Goodbye' received for "), _DH()); @@ -1114,12 +1134,14 @@ bool clsLEAMDNSHost::_processAAnswer(const clsLEAMDNSHost::clsRRAnswerA* p_pAAns } } else - { // Until now unknown IPv4 address -> Add (if the message isn't just a 'Goodbye' note) + { + // Until now unknown IPv4 address -> Add (if the message isn't just a 'Goodbye' note) if (p_pAAnswer->m_u32TTL) - { // NOT just a 'Goodbye' message + { + // NOT just a 'Goodbye' message pIPAddress = new clsQuery::clsAnswer::clsIPAddressWithTTL(p_pAAnswer->m_IPAddress, p_pAAnswer->m_u32TTL); if ((pIPAddress) && - (pSQAnswer->addIPv4Address(pIPAddress))) + (pSQAnswer->addIPv4Address(pIPAddress))) { DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processAAnswer: Added IPv4 address to "), _DH()); @@ -1161,15 +1183,17 @@ bool clsLEAMDNSHost::_processAAAAAnswer(const clsLEAMDNSHost::clsRRAnswerAAAA* p clsQuery* pQuery = *it; if (pQuery->m_bAwaitingAnswers) - { // Look for answers to host queries + { + // Look for answers to host queries if ((p_pAAAAAnswer->m_u32TTL) && // NOT just a goodbye message - (clsQuery::enuQueryType::Host == pQuery->m_QueryType) && // AND a host query - (pQuery->m_Domain == p_pAAAAAnswer->m_Header.m_Domain)) // AND a matching host domain + (clsQuery::enuQueryType::Host == pQuery->m_QueryType) && // AND a host query + (pQuery->m_Domain == p_pAAAAAnswer->m_Header.m_Domain)) // AND a matching host domain { clsQuery::clsAnswer* pSQAnswer = pQuery->findAnswerForHostDomain(p_pAAAAAnswer->m_Header.m_Domain); if ((!pSQAnswer) && - ((pSQAnswer = new clsQuery::clsAnswer))) - { // Add not yet included answer + ((pSQAnswer = new clsQuery::clsAnswer))) + { + // Add not yet included answer pSQAnswer->m_HostDomain = p_pAAAAAnswer->m_Header.m_Domain; //pSQAnswer->releaseHostDomain(); @@ -1191,9 +1215,11 @@ bool clsLEAMDNSHost::_processAAAAAnswer(const clsLEAMDNSHost::clsRRAnswerAAAA* p { clsQuery::clsAnswer::clsIPAddressWithTTL* pIPAddress = pSQAnswer->findIPv6Address(p_pAAAAAnswer->m_IPAddress); if (pIPAddress) - { // Already known IPv6 address + { + // Already known IPv6 address if (p_pAAAAAnswer->m_u32TTL) - { // Valid TTL -> Update answers TTL + { + // Valid TTL -> Update answers TTL pIPAddress->m_TTL.set(p_pAAAAAnswer->m_u32TTL); DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processAAAAAnswer: Updated TTL(%lu) for "), _DH(), p_pAAAAAnswer->m_u32TTL); @@ -1202,7 +1228,8 @@ bool clsLEAMDNSHost::_processAAAAAnswer(const clsLEAMDNSHost::clsRRAnswerAAAA* p ); } else - { // 'Goodbye' message for known IPv6 address + { + // 'Goodbye' message for known IPv6 address pIPAddress->m_TTL.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processAAAAAnswer: 'Goodbye' received for "), _DH()); @@ -1212,13 +1239,14 @@ bool clsLEAMDNSHost::_processAAAAAnswer(const clsLEAMDNSHost::clsRRAnswerAAAA* p } } else - { // Until now unknown IPv6 address -> Add (if the message isn't just a 'Goodbye' note) + { + // Until now unknown IPv6 address -> Add (if the message isn't just a 'Goodbye' note) if (p_pAAAAAnswer->m_u32TTL) { // NOT just a 'Goodbye' message pIPAddress = new clsQuery::clsAnswer::clsIPAddressWithTTL(p_pAAAAAnswer->m_IPAddress, p_pAAAAAnswer->m_u32TTL); if ((pIPAddress) && - (pSQAnswer->addIPv6Address(pIPAddress))) + (pSQAnswer->addIPv6Address(pIPAddress))) { DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _processAAAAAnswer: Added IPv6 address to "), _DH()); @@ -1270,19 +1298,19 @@ bool clsLEAMDNSHost::_updateProbeStatus(void) // // Probe host domain if ((clsProbeInformation_Base::enuProbingStatus::ReadyToStart == m_ProbeInformation.m_ProbingStatus) && // Ready to get started AND - (( + (( #ifdef MDNS_IPV4_SUPPORT - _getResponderIPAddress(enuIPProtocolType::V4).isSet() // AND has IPv4 address + _getResponderIPAddress(enuIPProtocolType::V4).isSet() // AND has IPv4 address #else - true + true #endif - ) || ( + ) || ( #ifdef MDNS_IPV6_SUPPORT - _getResponderIPAddress(enuIPProtocolType::V6).isSet() // OR has IPv6 address + _getResponderIPAddress(enuIPProtocolType::V6).isSet() // OR has IPv6 address #else - true + true #endif - ))) // Has IP address + ))) // Has IP address { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Starting host probing...\n"), _DH());); @@ -1476,7 +1504,7 @@ bool clsLEAMDNSHost::_sendHostProbe(void) clsRRQuestion* pNewRRQuestion = new clsRRQuestion; if (((bResult = (0 != pNewRRQuestion))) && - ((bResult = _buildDomainForHost(m_pcHostName, pNewRRQuestion->m_Header.m_Domain)))) + ((bResult = _buildDomainForHost(m_pcHostName, pNewRRQuestion->m_Header.m_Domain)))) { //sendParameter.m_pQuestions->m_bUnicast = true; pNewRRQuestion->m_Header.m_Attributes.m_u16Type = DNS_RRTYPE_ANY; @@ -1531,7 +1559,7 @@ bool clsLEAMDNSHost::_sendServiceProbe(clsService& p_rService) clsRRQuestion* pNewRRQuestion = new clsRRQuestion; if (((bResult = (0 != pNewRRQuestion))) && - ((bResult = _buildDomainForService(p_rService, true, pNewRRQuestion->m_Header.m_Domain)))) + ((bResult = _buildDomainForService(p_rService, true, pNewRRQuestion->m_Header.m_Domain)))) { pNewRRQuestion->m_bUnicast = true; pNewRRQuestion->m_Header.m_Attributes.m_u16Type = DNS_RRTYPE_ANY; @@ -1612,7 +1640,7 @@ bool clsLEAMDNSHost::_callHostProbeResultCallback(bool p_bResult) */ bool clsLEAMDNSHost::_callServiceProbeResultCallback(clsLEAMDNSHost::clsService& p_rService, - bool p_bResult) + bool p_bResult) { if (p_rService.m_ProbeInformation.m_fnProbeResultCallback) { @@ -1763,7 +1791,7 @@ bool clsLEAMDNSHost::_checkQueryCache(void) // // Resend dynamic queries, if not already done often enough if ((!pQuery->m_bStaticQuery) && - (pQuery->m_ResendTimeout.expired())) + (pQuery->m_ResendTimeout.expired())) { if ((bResult = _sendQuery(*pQuery))) { @@ -1794,7 +1822,7 @@ bool clsLEAMDNSHost::_checkQueryCache(void) // 1. level answer if ((bResult) && - (pQAnswer->m_TTLServiceDomain.flagged())) + (pQAnswer->m_TTLServiceDomain.flagged())) { if (!pQAnswer->m_TTLServiceDomain.finalTimeoutLevel()) { @@ -1826,7 +1854,7 @@ bool clsLEAMDNSHost::_checkQueryCache(void) // 2. level answers // HostDomain & Port (from SRV) if ((bResult) && - (pQAnswer->m_TTLHostDomainAndPort.flagged())) + (pQAnswer->m_TTLHostDomainAndPort.flagged())) { if (!pQAnswer->m_TTLHostDomainAndPort.finalTimeoutLevel()) { @@ -1872,7 +1900,7 @@ bool clsLEAMDNSHost::_checkQueryCache(void) // Txts (from TXT) if ((bResult) && - (pQAnswer->m_TTLTxts.flagged())) + (pQAnswer->m_TTLTxts.flagged())) { if (!pQAnswer->m_TTLTxts.finalTimeoutLevel()) { @@ -1919,7 +1947,7 @@ bool clsLEAMDNSHost::_checkQueryCache(void) { // Needs update if ((bAUpdateQuerySent) || - ((bResult = _sendQuery(pQAnswer->m_HostDomain, DNS_RRTYPE_A)))) + ((bResult = _sendQuery(pQAnswer->m_HostDomain, DNS_RRTYPE_A)))) { pIPv4Address->m_TTL.restart(); bAUpdateQuerySent = true; @@ -1972,7 +2000,7 @@ bool clsLEAMDNSHost::_checkQueryCache(void) { // Needs update if ((bAAAAUpdateQuerySent) || - ((bResult = _sendQuery(pQAnswer->m_HostDomain, DNS_RRTYPE_AAAA)))) + ((bResult = _sendQuery(pQAnswer->m_HostDomain, DNS_RRTYPE_AAAA)))) { pIPv6Address->m_TTL.restart(); bAAAAUpdateQuerySent = true; @@ -2036,7 +2064,7 @@ bool clsLEAMDNSHost::_checkQueryCache(void) In addition, a full name match (question domain == host domain) is marked. */ uint32_t clsLEAMDNSHost::_replyMaskForHost(const clsLEAMDNSHost::clsRRHeader& p_RRHeader, - bool* p_pbFullNameMatch /*= 0*/) const + bool* p_pbFullNameMatch /*= 0*/) const { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _replyMaskForHost\n"));); @@ -2044,18 +2072,18 @@ uint32_t clsLEAMDNSHost::_replyMaskForHost(const clsLEAMDNSHost::clsRRHeader& p_ (p_pbFullNameMatch ? *p_pbFullNameMatch = false : 0); if ((DNS_RRCLASS_IN == (p_RRHeader.m_Attributes.m_u16Class & (~0x8000))) || - (DNS_RRCLASS_ANY == (p_RRHeader.m_Attributes.m_u16Class & (~0x8000)))) + (DNS_RRCLASS_ANY == (p_RRHeader.m_Attributes.m_u16Class & (~0x8000)))) { if ((DNS_RRTYPE_PTR == p_RRHeader.m_Attributes.m_u16Type) || - (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) { // PTR request #ifdef MDNS_IPV4_SUPPORT clsRRDomain reverseIPv4Domain; if ((_getResponderIPAddress(enuIPProtocolType::V4).isSet()) && - (_buildDomainForReverseIPv4(_getResponderIPAddress(enuIPProtocolType::V4), reverseIPv4Domain)) && - (p_RRHeader.m_Domain == reverseIPv4Domain)) + (_buildDomainForReverseIPv4(_getResponderIPAddress(enuIPProtocolType::V4), reverseIPv4Domain)) && + (p_RRHeader.m_Domain == reverseIPv4Domain)) { // Reverse domain match u32ReplyMask |= static_cast(enuContentFlag::PTR_IPv4); @@ -2064,8 +2092,8 @@ uint32_t clsLEAMDNSHost::_replyMaskForHost(const clsLEAMDNSHost::clsRRHeader& p_ #ifdef MDNS_IPV6_SUPPORT clsRRDomain reverseIPv6Domain; if ((_getResponderIPAddress(enuIPProtocolType::V6).isSet()) && - (_buildDomainForReverseIPv6(_getResponderIPAddress(enuIPProtocolType::V6), reverseIPv6Domain)) && - (p_RRHeader.m_Domain == reverseIPv6Domain)) + (_buildDomainForReverseIPv6(_getResponderIPAddress(enuIPProtocolType::V6), reverseIPv6Domain)) && + (p_RRHeader.m_Domain == reverseIPv6Domain)) { // Reverse domain match u32ReplyMask |= static_cast(enuContentFlag::PTR_IPv6); @@ -2075,13 +2103,13 @@ uint32_t clsLEAMDNSHost::_replyMaskForHost(const clsLEAMDNSHost::clsRRHeader& p_ clsRRDomain hostDomain; if ((_buildDomainForHost(m_pcHostName, hostDomain)) && - (p_RRHeader.m_Domain == hostDomain)) // Host domain match + (p_RRHeader.m_Domain == hostDomain)) // Host domain match { (p_pbFullNameMatch ? (*p_pbFullNameMatch = true) : (0)); #ifdef MDNS_IPV4_SUPPORT if ((DNS_RRTYPE_A == p_RRHeader.m_Attributes.m_u16Type) || - (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) { // IPv4 address request u32ReplyMask |= static_cast(enuContentFlag::A); @@ -2089,7 +2117,7 @@ uint32_t clsLEAMDNSHost::_replyMaskForHost(const clsLEAMDNSHost::clsRRHeader& p_ #endif #ifdef MDNS_IPV6_SUPPORT if ((DNS_RRTYPE_AAAA == p_RRHeader.m_Attributes.m_u16Type) || - (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) { // IPv6 address request u32ReplyMask |= static_cast(enuContentFlag::AAAA); @@ -2119,20 +2147,20 @@ uint32_t clsLEAMDNSHost::_replyMaskForHost(const clsLEAMDNSHost::clsRRHeader& p_ */ uint32_t clsLEAMDNSHost::_replyMaskForService(const clsLEAMDNSHost::clsRRHeader& p_RRHeader, - clsLEAMDNSHost::clsService& p_rService, - bool* p_pbFullNameMatch /*= 0*/) + clsLEAMDNSHost::clsService& p_rService, + bool* p_pbFullNameMatch /*= 0*/) { uint32_t u32ReplyMask = 0; (p_pbFullNameMatch ? *p_pbFullNameMatch = false : 0); if ((DNS_RRCLASS_IN == (p_RRHeader.m_Attributes.m_u16Class & (~0x8000))) || - (DNS_RRCLASS_ANY == (p_RRHeader.m_Attributes.m_u16Class & (~0x8000)))) + (DNS_RRCLASS_ANY == (p_RRHeader.m_Attributes.m_u16Class & (~0x8000)))) { clsRRDomain DNSSDDomain; if ((_buildDomainForDNSSD(DNSSDDomain)) && // _services._dns-sd._udp.local - (p_RRHeader.m_Domain == DNSSDDomain) && - ((DNS_RRTYPE_PTR == p_RRHeader.m_Attributes.m_u16Type) || - (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type))) + (p_RRHeader.m_Domain == DNSSDDomain) && + ((DNS_RRTYPE_PTR == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type))) { // Common service info requested u32ReplyMask |= static_cast(enuContentFlag::PTR_TYPE); @@ -2140,27 +2168,27 @@ uint32_t clsLEAMDNSHost::_replyMaskForService(const clsLEAMDNSHost::clsRRHeader& clsRRDomain serviceDomain; if ((_buildDomainForService(p_rService, false, serviceDomain)) && // eg. _http._tcp.local - (p_RRHeader.m_Domain == serviceDomain) && - ((DNS_RRTYPE_PTR == p_RRHeader.m_Attributes.m_u16Type) || - (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type))) + (p_RRHeader.m_Domain == serviceDomain) && + ((DNS_RRTYPE_PTR == p_RRHeader.m_Attributes.m_u16Type) || + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type))) { // Special service info requested u32ReplyMask |= static_cast(enuContentFlag::PTR_NAME); } if ((_buildDomainForService(p_rService, true, serviceDomain)) && // eg. MyESP._http._tcp.local - (p_RRHeader.m_Domain == serviceDomain)) + (p_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)) + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) { // Instance info SRV requested u32ReplyMask |= static_cast(enuContentFlag::SRV); } if ((DNS_RRTYPE_TXT == p_RRHeader.m_Attributes.m_u16Type) || - (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) + (DNS_RRTYPE_ANY == p_RRHeader.m_Attributes.m_u16Type)) { // Instance info TXT requested u32ReplyMask |= static_cast(enuContentFlag::TXT); diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp index 434f61a0ea..b449d1cd08 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp @@ -197,7 +197,7 @@ const char* clsLEAMDNSHost::_RRType2Name(uint16_t p_u16RRType) const */ const char* clsLEAMDNSHost::_RRClass2String(uint16_t p_u16RRClass, - bool p_bIsQuery) const + bool p_bIsQuery) const { static char acClassString[16]; *acClassString = 0; @@ -267,7 +267,7 @@ const char* clsLEAMDNSHost::_replyFlags2String(uint32_t p_u32ReplyFlags) const // Remove trailing spaces while ((*acFlagsString) && - (' ' == acFlagsString[strlen(acFlagsString) - 1])) + (' ' == acFlagsString[strlen(acFlagsString) - 1])) { acFlagsString[strlen(acFlagsString) - 1] = 0; } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp index b4db3c0531..b2fe64072a 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -52,8 +52,8 @@ namespace experimental */ clsLEAMDNSHost::clsServiceTxt::clsServiceTxt(const char* p_pcKey /*= 0*/, - const char* p_pcValue /*= 0*/, - bool p_bTemp /*= false*/) + const char* p_pcValue /*= 0*/, + bool p_bTemp /*= false*/) : m_pcKey(0), m_pcValue(0), m_bTemp(p_bTemp) @@ -127,7 +127,7 @@ char* clsLEAMDNSHost::clsServiceTxt::allocKey(size_t p_stLength) */ bool clsLEAMDNSHost::clsServiceTxt::setKey(const char* p_pcKey, - size_t p_stLength) + size_t p_stLength) { bool bResult = false; @@ -186,7 +186,7 @@ char* clsLEAMDNSHost::clsServiceTxt::allocValue(size_t p_stLength) */ bool clsLEAMDNSHost::clsServiceTxt::setValue(const char* p_pcValue, - size_t p_stLength) + size_t p_stLength) { bool bResult = false; @@ -446,7 +446,7 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsServiceTxts::find(const char* for (clsServiceTxt* pTxt : m_Txts) { if ((p_pcKey) && - (0 == strcmp(pTxt->m_pcKey, p_pcKey))) + (0 == strcmp(pTxt->m_pcKey, p_pcKey))) { pResult = pTxt; break; @@ -466,7 +466,7 @@ const clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsServiceTxts::find(const for (const clsServiceTxt* pTxt : m_Txts) { if ((p_pcKey) && - (0 == strcmp(pTxt->m_pcKey, p_pcKey))) + (0 == strcmp(pTxt->m_pcKey, p_pcKey))) { pResult = pTxt; break; @@ -571,8 +571,8 @@ const char* clsLEAMDNSHost::clsServiceTxts::c_str(void) const { if ((!m_pcCache) && - (m_Txts.size()) && - ((((clsServiceTxts*)this)->m_pcCache = new char[c_strLength()]))) // TRANSPARENT caching + (m_Txts.size()) && + ((((clsServiceTxts*)this)->m_pcCache = new char[c_strLength()]))) // TRANSPARENT caching { ((clsServiceTxts*)this)->c_str(m_pcCache); } @@ -825,7 +825,7 @@ bool clsLEAMDNSHost::clsService::setInstanceName(const char* p_pcInstanceName) _releaseInstanceName(); size_t stLength = (p_pcInstanceName ? strlen(p_pcInstanceName) : 0); if ((stLength) && - (stLength <= clsConsts::stDomainLabelMaxLength)) + (stLength <= clsConsts::stDomainLabelMaxLength)) { if ((bResult = (0 != (m_pcInstanceName = new char[stLength + 1])))) { @@ -887,7 +887,7 @@ bool clsLEAMDNSHost::clsService::setType(const char* p_pcType) _releaseType(); size_t stLength = (p_pcType ? strlen(p_pcType) : 0); if ((stLength) && - (stLength <= clsConsts::stServiceTypeMaxLength)) + (stLength <= clsConsts::stServiceTypeMaxLength)) { if ((bResult = (0 != (m_pcType = new char[stLength + 1])))) { @@ -934,7 +934,7 @@ bool clsLEAMDNSHost::clsService::setProtocol(const char* p_pcProtocol) _releaseProtocol(); size_t stLength = (p_pcProtocol ? strlen(p_pcProtocol) : 0); if ((stLength) && - (stLength <= clsConsts::stServiceProtocolMaxLength)) + (stLength <= clsConsts::stServiceProtocolMaxLength)) { if ((bResult = (0 != (m_pcProtocol = new char[stLength + 1])))) { @@ -1030,7 +1030,7 @@ void clsLEAMDNSHost::clsService::_resetProbeStatus(void) */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, - const char* p_pcValue) + const char* p_pcValue) { return _addServiceTxt(p_pcKey, p_pcValue, false); } @@ -1040,7 +1040,7 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const c */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, - uint32_t p_u32Value) + uint32_t p_u32Value) { return _addServiceTxt(p_pcKey, p_u32Value, false); } @@ -1050,7 +1050,7 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const c */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, - uint16_t p_u16Value) + uint16_t p_u16Value) { return _addServiceTxt(p_pcKey, p_u16Value, false); } @@ -1060,7 +1060,7 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const c */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, - uint8_t p_u8Value) + uint8_t p_u8Value) { return _addServiceTxt(p_pcKey, p_u8Value, false); } @@ -1070,7 +1070,7 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const c */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, - int32_t p_i32Value) + int32_t p_i32Value) { return _addServiceTxt(p_pcKey, p_i32Value, false); } @@ -1080,7 +1080,7 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const c */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, - int16_t p_i16Value) + int16_t p_i16Value) { return _addServiceTxt(p_pcKey, p_i16Value, false); } @@ -1090,7 +1090,7 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const c */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const char* p_pcKey, - int8_t p_i8Value) + int8_t p_i8Value) { return _addServiceTxt(p_pcKey, p_i8Value, false); } @@ -1100,7 +1100,7 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addServiceTxt(const c */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, - const char* p_pcValue) + const char* p_pcValue) { return _addServiceTxt(p_pcKey, p_pcValue, true); } @@ -1110,7 +1110,7 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt( */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, - uint32_t p_u32Value) + uint32_t p_u32Value) { return _addServiceTxt(p_pcKey, p_u32Value, true); } @@ -1120,7 +1120,7 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt( */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, - uint16_t p_u16Value) + uint16_t p_u16Value) { return _addServiceTxt(p_pcKey, p_u16Value, true); } @@ -1130,7 +1130,7 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt( */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, - uint8_t p_u8Value) + uint8_t p_u8Value) { return _addServiceTxt(p_pcKey, p_u8Value, true); } @@ -1140,7 +1140,7 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt( */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, - int32_t p_i32Value) + int32_t p_i32Value) { return _addServiceTxt(p_pcKey, p_i32Value, true); } @@ -1150,7 +1150,7 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt( */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, - int16_t p_i16Value) + int16_t p_i16Value) { return _addServiceTxt(p_pcKey, p_i16Value, true); } @@ -1160,7 +1160,7 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt( */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::addDynamicServiceTxt(const char* p_pcKey, - int8_t p_i8Value) + int8_t p_i8Value) { return _addServiceTxt(p_pcKey, p_i8Value, true); } @@ -1180,13 +1180,13 @@ bool clsLEAMDNSHost::clsService::setDynamicServiceTxtCallback(fnDynamicServiceTx */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, - const char* p_pcValue, - bool p_bTemp) + const char* p_pcValue, + bool p_bTemp) { clsServiceTxt* pServiceTxt = 0; if ((p_pcKey) && - (*p_pcKey)) + (*p_pcKey)) { if ((pServiceTxt = m_Txts.find(p_pcKey))) { @@ -1220,8 +1220,8 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const (p_pcValue ? strlen(p_pcValue) : 0))) { if (!(((pServiceTxt = new clsServiceTxt)) && - (pServiceTxt->set(p_pcKey, p_pcValue, p_bTemp)) && - (m_Txts.add(pServiceTxt)))) + (pServiceTxt->set(p_pcKey, p_pcValue, p_bTemp)) && + (m_Txts.add(pServiceTxt)))) { DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[LEAmDNS2_Host] clsService::_addServiceTxt: FAILED to add TXT item '%s'!\n"), p_pcKey)); if (pServiceTxt) @@ -1247,8 +1247,8 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, - uint32_t p_u32Value, - bool p_bTemp) + uint32_t p_u32Value, + bool p_bTemp) { char acValueBuffer[16]; // 32-bit max 10 digits *acValueBuffer = 0; @@ -1262,8 +1262,8 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, - uint16_t p_u16Value, - bool p_bTemp) + uint16_t p_u16Value, + bool p_bTemp) { char acValueBuffer[8]; // 16-bit max 5 digits *acValueBuffer = 0; @@ -1277,8 +1277,8 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, - uint8_t p_u8Value, - bool p_bTemp) + uint8_t p_u8Value, + bool p_bTemp) { char acValueBuffer[8]; // 8-bit max 3 digits *acValueBuffer = 0; @@ -1292,8 +1292,8 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, - int32_t p_i32Value, - bool p_bTemp) + int32_t p_i32Value, + bool p_bTemp) { char acValueBuffer[16]; // 32-bit max 10 digits *acValueBuffer = 0; @@ -1307,8 +1307,8 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, - int16_t p_i16Value, - bool p_bTemp) + int16_t p_i16Value, + bool p_bTemp) { char acValueBuffer[8]; // 16-bit max 5 digits *acValueBuffer = 0; @@ -1322,8 +1322,8 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const */ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, - int8_t p_i8Value, - bool p_bTemp) + int8_t p_i8Value, + bool p_bTemp) { char acValueBuffer[8]; // 8-bit max 3 digits *acValueBuffer = 0; @@ -1345,17 +1345,17 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const */ clsLEAMDNSHost::clsMsgHeader::clsMsgHeader(uint16_t p_u16ID /*= 0*/, - bool p_bQR /*= false*/, - uint8_t p_u8Opcode /*= 0*/, - bool p_bAA /*= false*/, - bool p_bTC /*= false*/, - bool p_bRD /*= false*/, - bool p_bRA /*= false*/, - uint8_t p_u8RCode /*= 0*/, - uint16_t p_u16QDCount /*= 0*/, - uint16_t p_u16ANCount /*= 0*/, - uint16_t p_u16NSCount /*= 0*/, - uint16_t p_u16ARCount /*= 0*/) + bool p_bQR /*= false*/, + uint8_t p_u8Opcode /*= 0*/, + bool p_bAA /*= false*/, + bool p_bTC /*= false*/, + bool p_bRD /*= false*/, + bool p_bRA /*= false*/, + uint8_t p_u8RCode /*= 0*/, + uint16_t p_u16QDCount /*= 0*/, + uint16_t p_u16ANCount /*= 0*/, + uint16_t p_u16NSCount /*= 0*/, + uint16_t p_u16ARCount /*= 0*/) : m_u16ID(p_u16ID), m_1bQR(p_bQR), m_4bOpcode(p_u8Opcode), m_1bAA(p_bAA), m_1bTC(p_bTC), m_1bRD(p_bRD), m_1bRA(p_bRA), m_3bZ(0), m_4bRCode(p_u8RCode), @@ -1456,7 +1456,7 @@ bool clsLEAMDNSHost::clsRRDomain::clearNameCache(void) */ bool clsLEAMDNSHost::clsRRDomain::addLabel(const char* p_pcLabel, - bool p_bPrependUnderline /*= false*/) + bool p_bPrependUnderline /*= false*/) { bool bResult = false; @@ -1464,7 +1464,7 @@ bool clsLEAMDNSHost::clsRRDomain::addLabel(const char* p_pcLabel, ? (strlen(p_pcLabel) + (p_bPrependUnderline ? 1 : 0)) : 0); if ((clsConsts::stDomainLabelMaxLength >= stLength) && - (clsConsts::stDomainMaxLength >= (m_u16NameLength + (1 + stLength)))) + (clsConsts::stDomainMaxLength >= (m_u16NameLength + (1 + stLength)))) { // Length byte m_acName[m_u16NameLength] = (unsigned char)stLength; // Might be 0! @@ -1498,9 +1498,9 @@ bool clsLEAMDNSHost::clsRRDomain::compare(const clsRRDomain& p_Other) const const char* pT = m_acName; const char* pO = p_Other.m_acName; while ((pT) && - (pO) && - (*((unsigned char*)pT) == *((unsigned char*)pO)) && // Same length AND - (0 == strncasecmp((pT + 1), (pO + 1), *((unsigned char*)pT)))) // Same content + (pO) && + (*((unsigned char*)pT) == *((unsigned char*)pO)) && // Same length AND + (0 == strncasecmp((pT + 1), (pO + 1), *((unsigned char*)pT)))) // Same content { if (*((unsigned char*)pT)) // Not 0 { @@ -1593,8 +1593,8 @@ bool clsLEAMDNSHost::clsRRDomain::c_str(char* p_pcBuffer) const const char* clsLEAMDNSHost::clsRRDomain::c_str(void) const { if ((!m_pcDecodedName) && - (m_u16NameLength) && - ((((clsRRDomain*)this)->m_pcDecodedName = new char[c_strLength()]))) // TRANSPARENT caching + (m_u16NameLength) && + ((((clsRRDomain*)this)->m_pcDecodedName = new char[c_strLength()]))) // TRANSPARENT caching { ((clsRRDomain*)this)->c_str(m_pcDecodedName); } @@ -1614,7 +1614,7 @@ const char* clsLEAMDNSHost::clsRRDomain::c_str(void) const */ clsLEAMDNSHost::clsRRAttributes::clsRRAttributes(uint16_t p_u16Type /*= 0*/, - uint16_t p_u16Class /*= 1 DNS_RRCLASS_IN Internet*/) + uint16_t p_u16Class /*= 1 DNS_RRCLASS_IN Internet*/) : m_u16Type(p_u16Type), m_u16Class(p_u16Class) { @@ -1753,7 +1753,7 @@ bool clsLEAMDNSHost::clsNSECBitmap::setBit(uint16_t p_u16Bit) bool bResult = false; if ((p_u16Bit) && - (length() > (p_u16Bit / 8))) // bit between 0..47(2F) + (length() > (p_u16Bit / 8))) // bit between 0..47(2F) { uint8_t& ru8Byte = m_au8BitmapData[p_u16Bit / 8]; @@ -1775,7 +1775,7 @@ bool clsLEAMDNSHost::clsNSECBitmap::getBit(uint16_t p_u16Bit) const bool bResult = false; if ((p_u16Bit) && - (length() > (p_u16Bit / 8))) // bit between 0..47(2F) + (length() > (p_u16Bit / 8))) // bit between 0..47(2F) { uint8_t u8Byte = m_au8BitmapData[p_u16Bit / 8]; @@ -1800,8 +1800,8 @@ bool clsLEAMDNSHost::clsNSECBitmap::getBit(uint16_t p_u16Bit) const */ clsLEAMDNSHost::clsRRAnswer::clsRRAnswer(enuAnswerType p_AnswerType, - const clsLEAMDNSHost::clsRRHeader& p_Header, - uint32_t p_u32TTL) + const clsLEAMDNSHost::clsRRHeader& p_Header, + uint32_t p_u32TTL) : m_pNext(0), m_AnswerType(p_AnswerType), m_Header(p_Header), @@ -1855,7 +1855,7 @@ bool clsLEAMDNSHost::clsRRAnswer::clear(void) */ clsLEAMDNSHost::clsRRAnswerA::clsRRAnswerA(const clsLEAMDNSHost::clsRRHeader& p_Header, - uint32_t p_u32TTL) + uint32_t p_u32TTL) : clsRRAnswer(enuAnswerType::A, p_Header, p_u32TTL), m_IPAddress() { @@ -1895,7 +1895,7 @@ bool clsLEAMDNSHost::clsRRAnswerA::clear(void) */ clsLEAMDNSHost::clsRRAnswerPTR::clsRRAnswerPTR(const clsLEAMDNSHost::clsRRHeader& p_Header, - uint32_t p_u32TTL) + uint32_t p_u32TTL) : clsRRAnswer(enuAnswerType::PTR, p_Header, p_u32TTL) { } @@ -1933,7 +1933,7 @@ bool clsLEAMDNSHost::clsRRAnswerPTR::clear(void) */ clsLEAMDNSHost::clsRRAnswerTXT::clsRRAnswerTXT(const clsLEAMDNSHost::clsRRHeader& p_Header, - uint32_t p_u32TTL) + uint32_t p_u32TTL) : clsRRAnswer(enuAnswerType::TXT, p_Header, p_u32TTL) { } @@ -1972,7 +1972,7 @@ bool clsLEAMDNSHost::clsRRAnswerTXT::clear(void) */ clsLEAMDNSHost::clsRRAnswerAAAA::clsRRAnswerAAAA(const clsLEAMDNSHost::clsRRHeader& p_Header, - uint32_t p_u32TTL) + uint32_t p_u32TTL) : clsRRAnswer(enuAnswerType::AAAA, p_Header, p_u32TTL), m_IPAddress() { @@ -2012,7 +2012,7 @@ bool clsLEAMDNSHost::clsRRAnswerAAAA::clear(void) */ clsLEAMDNSHost::clsRRAnswerSRV::clsRRAnswerSRV(const clsLEAMDNSHost::clsRRHeader& p_Header, - uint32_t p_u32TTL) + uint32_t p_u32TTL) : clsRRAnswer(enuAnswerType::SRV, p_Header, p_u32TTL), m_u16Priority(0), m_u16Weight(0), @@ -2056,7 +2056,7 @@ bool clsLEAMDNSHost::clsRRAnswerSRV::clear(void) */ clsLEAMDNSHost::clsRRAnswerGeneric::clsRRAnswerGeneric(const clsRRHeader& p_Header, - uint32_t p_u32TTL) + uint32_t p_u32TTL) : clsRRAnswer(enuAnswerType::Generic, p_Header, p_u32TTL), m_u16RDLength(0), m_pu8RDData(0) @@ -2111,8 +2111,8 @@ bool clsLEAMDNSHost::clsRRAnswerGeneric::clear(void) */ clsLEAMDNSHost::clsSendParameter::clsDomainCacheItem::clsDomainCacheItem(const void* p_pHostNameOrService, - bool p_bAdditionalData, - uint32_t p_u16Offset) + bool p_bAdditionalData, + uint32_t p_u16Offset) : m_pHostNameOrService(p_pHostNameOrService), m_bAdditionalData(p_bAdditionalData), m_u16Offset(p_u16Offset) @@ -2227,15 +2227,15 @@ bool clsLEAMDNSHost::clsSendParameter::shiftOffset(uint16_t p_u16Shift) */ bool clsLEAMDNSHost::clsSendParameter::addDomainCacheItem(const void* p_pHostNameOrService, - bool p_bAdditionalData, - uint16_t p_u16Offset) + bool p_bAdditionalData, + uint16_t p_u16Offset) { bool bResult = false; clsDomainCacheItem* pNewItem = 0; if ((p_pHostNameOrService) && - (p_u16Offset) && - ((pNewItem = new clsDomainCacheItem(p_pHostNameOrService, p_bAdditionalData, p_u16Offset)))) + (p_u16Offset) && + ((pNewItem = new clsDomainCacheItem(p_pHostNameOrService, p_bAdditionalData, p_u16Offset)))) { m_DomainCacheItems.push_back(pNewItem); bResult = true; @@ -2248,14 +2248,14 @@ bool clsLEAMDNSHost::clsSendParameter::addDomainCacheItem(const void* p_pHostNam */ uint16_t clsLEAMDNSHost::clsSendParameter::findCachedDomainOffset(const void* p_pHostNameOrService, - bool p_bAdditionalData) const + bool p_bAdditionalData) const { const clsDomainCacheItem* pMatchingCacheItem = 0; for (const clsDomainCacheItem* pCacheItem : m_DomainCacheItems) { if ((pCacheItem->m_pHostNameOrService == p_pHostNameOrService) && - (pCacheItem->m_bAdditionalData == p_bAdditionalData)) // Found cache item + (pCacheItem->m_bAdditionalData == p_bAdditionalData)) // Found cache item { pMatchingCacheItem = pCacheItem; break; @@ -2356,7 +2356,7 @@ bool clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::restart(void) bool bResult = true; if ((static_cast(enuTimeoutLevel::Base) <= m_TimeoutLevel) && // >= 80% AND - (static_cast(enuTimeoutLevel::Final) > m_TimeoutLevel)) // < 100% + (static_cast(enuTimeoutLevel::Final) > m_TimeoutLevel)) // < 100% { m_TimeoutLevel += static_cast(enuTimeoutLevel::Interval); // increment by 5% m_TTLTimeout.reset(timeout()); @@ -2422,7 +2422,7 @@ unsigned long clsLEAMDNSHost::clsQuery::clsAnswer::clsTTL::timeout(void) const */ clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL::clsIPAddressWithTTL(IPAddress p_IPAddress, - uint32_t p_u32TTL /*= 0*/) + uint32_t p_u32TTL /*= 0*/) : m_IPAddress(p_IPAddress) { m_TTL.set(p_u32TTL); @@ -2514,8 +2514,8 @@ bool clsLEAMDNSHost::clsQuery::clsAnswer::removeIPv4Address(clsLEAMDNSHost::clsQ bool bResult = false; clsIPAddressWithTTL::list::iterator it(p_pIPv4Address - ? std::find(m_IPv4Addresses.begin(), m_IPv4Addresses.end(), p_pIPv4Address) - : m_IPv4Addresses.end()); + ? std::find(m_IPv4Addresses.begin(), m_IPv4Addresses.end(), p_pIPv4Address) + : m_IPv4Addresses.end()); if (m_IPv4Addresses.end() != it) { m_IPv4Addresses.erase(it); @@ -2583,8 +2583,8 @@ const clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost:: uint32_t u32CurIndex = 0; for (clsIPAddressWithTTL::list::const_iterator it = m_IPv4Addresses.begin(); - (((uint32_t)(-1) != p_u32Index) && (u32CurIndex <= p_u32Index) && (it != m_IPv4Addresses.end())); - it++, u32CurIndex++) + (((uint32_t)(-1) != p_u32Index) && (u32CurIndex <= p_u32Index) && (it != m_IPv4Addresses.end())); + it++, u32CurIndex++) { if (p_u32Index == u32CurIndex++) { @@ -2636,8 +2636,8 @@ bool clsLEAMDNSHost::clsQuery::clsAnswer::removeIPv6Address(clsLEAMDNSHost::clsQ bool bResult = false; clsIPAddressWithTTL::list::iterator it(p_pIPv6Address - ? std::find(m_IPv6Addresses.begin(), m_IPv6Addresses.end(), p_pIPv6Address) - : m_IPv6Addresses.end()); + ? std::find(m_IPv6Addresses.begin(), m_IPv6Addresses.end(), p_pIPv6Address) + : m_IPv6Addresses.end()); if (m_IPv6Addresses.end() != it) { m_IPv6Addresses.erase(it); @@ -2705,8 +2705,8 @@ const clsLEAMDNSHost::clsQuery::clsAnswer::clsIPAddressWithTTL* clsLEAMDNSHost:: uint32_t u32CurIndex = 0; for (clsIPAddressWithTTL::list::const_iterator it = m_IPv6Addresses.begin(); - (((uint32_t)(-1) != p_u32Index) && (u32CurIndex <= p_u32Index) && (it != m_IPv6Addresses.end())); - it++, u32CurIndex++) + (((uint32_t)(-1) != p_u32Index) && (u32CurIndex <= p_u32Index) && (it != m_IPv6Addresses.end())); + it++, u32CurIndex++) { if (p_u32Index == u32CurIndex++) { @@ -2732,7 +2732,7 @@ clsLEAMDNSHost::clsQuery::clsAnswerAccessor::clsAnswerAccessor(const clsLEAMDNSH : m_pAnswer(p_pAnswer) { if ((m_pAnswer) && - (txtsAvailable())) + (txtsAvailable())) { // Prepare m_TxtKeyValueMap for (const clsLEAMDNSHost::clsServiceTxt* pTxt : m_pAnswer->m_Txts.m_Txts) @@ -2759,7 +2759,7 @@ clsLEAMDNSHost::clsQuery::clsAnswerAccessor::~clsAnswerAccessor(void) */ bool clsLEAMDNSHost::clsQuery::clsAnswerAccessor::stcCompareTxtKey::operator()(char const* p_pA, - char const* p_pB) const + char const* p_pB) const { return (0 > strcasecmp(p_pA, p_pB)); } @@ -2846,7 +2846,7 @@ clsLEAMDNSHost::clsQuery::clsAnswerAccessor::clsIPAddressVector clsLEAMDNSHost:: { clsIPAddressVector internalIP; if ((m_pAnswer) && - (IPv4AddressAvailable())) + (IPv4AddressAvailable())) { for (uint32_t u = 0; u < m_pAnswer->IPv4AddressCount(); ++u) { @@ -2880,7 +2880,7 @@ clsLEAMDNSHost::clsQuery::clsAnswerAccessor::clsIPAddressVector clsLEAMDNSHost:: { clsIPAddressVector internalIP; if ((m_pAnswer) && - (IPv6AddressAvailable())) + (IPv6AddressAvailable())) { for (uint32_t u = 0; u < m_pAnswer->IPv6AddressCount(); ++u) { @@ -2941,7 +2941,7 @@ const char* clsLEAMDNSHost::clsQuery::clsAnswerAccessor::txtValue(const char* p_ for (const clsLEAMDNSHost::clsServiceTxt* pTxt : m_pAnswer->m_Txts.m_Txts) { if ((p_pcKey) && - (0 == strcasecmp(pTxt->m_pcKey, p_pcKey))) + (0 == strcasecmp(pTxt->m_pcKey, p_pcKey))) { pcResult = pTxt->m_pcValue; break; @@ -3110,8 +3110,8 @@ const clsLEAMDNSHost::clsQuery::clsAnswer* clsLEAMDNSHost::clsQuery::answer(uint uint32_t u32CurIndex = 0; for (clsAnswer::list::const_iterator it = m_Answers.begin(); - (((uint32_t)(-1) != p_u32Index) && (u32CurIndex <= p_u32Index) && (it != m_Answers.end())); - it++, u32CurIndex++) + (((uint32_t)(-1) != p_u32Index) && (u32CurIndex <= p_u32Index) && (it != m_Answers.end())); + it++, u32CurIndex++) { if (p_u32Index == u32CurIndex++) { diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 50880585cc..f4a187b667 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -100,7 +100,7 @@ bool clsLEAMDNSHost::_sendMessage(clsLEAMDNSHost::clsSendParameter& p_rSendParam DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage (V4): FAILED!\n"), _DH());); if ((clsConsts::u32SendCooldown) && - (can_yield())) + (can_yield())) { delay(clsConsts::u32SendCooldown); } @@ -110,16 +110,16 @@ bool clsLEAMDNSHost::_sendMessage(clsLEAMDNSHost::clsSendParameter& p_rSendParam // Multicast response -> Send via the same network interface, that received the query #ifdef MDNS_IPV4_SUPPORT if (((!ipRemote.isSet()) || // NO remote IP - (ipRemote.isV4())) && // OR IPv4 - (u8AvailableProtocols & static_cast(enuIPProtocolType::V4))) // AND IPv4 protocol available + (ipRemote.isV4())) && // OR IPv4 + (u8AvailableProtocols & static_cast(enuIPProtocolType::V4))) // AND IPv4 protocol available { bResult = _sendMessage_Multicast(p_rSendParameter, static_cast(enuIPProtocolType::V4)); } #endif #ifdef MDNS_IPV6_SUPPORT if (((!ipRemote.isSet()) || // NO remote IP - (ipRemote.isV6())) && // OR IPv6 - (u8AvailableProtocols & static_cast(enuIPProtocolType::V6))) // AND IPv6 protocol available + (ipRemote.isV6())) && // OR IPv6 + (u8AvailableProtocols & static_cast(enuIPProtocolType::V6))) // AND IPv6 protocol available { bResult = _sendMessage_Multicast(p_rSendParameter, static_cast(enuIPProtocolType::V6)); } @@ -153,7 +153,7 @@ bool clsLEAMDNSHost::_sendMessage(clsLEAMDNSHost::clsSendParameter& p_rSendParam via the selected WiFi protocols */ bool clsLEAMDNSHost::_sendMessage_Multicast(clsLEAMDNSHost::clsSendParameter& p_rSendParameter, - uint8_t p_IPProtocolTypes) + uint8_t p_IPProtocolTypes) { bool bIPv4Result = true; bool bIPv6Result = true; @@ -173,7 +173,7 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(clsLEAMDNSHost::clsSendParameter& p_ DEBUG_EX_ERR(if (!bIPv4Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast (V4): FAILED!\n"), _DH());); if ((clsConsts::u32SendCooldown) && - (can_yield())) + (can_yield())) { delay(clsConsts::u32SendCooldown); } @@ -199,7 +199,7 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(clsLEAMDNSHost::clsSendParameter& p_ DEBUG_EX_ERR(if (!bIPv6Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast (IPv6): FAILED! (%s, %s, %s)\n"), _DH(), (_getResponderIPAddress(enuIPProtocolType::V6).isSet() ? "1" : "0"), (bPrepareMessage ? "1" : "0"), (bUDPContextSend ? "1" : "0"));); if ((clsConsts::u32SendCooldown) && - (can_yield())) + (can_yield())) { delay(clsConsts::u32SendCooldown); } @@ -287,8 +287,8 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa #ifdef MDNS_IPV4_SUPPORT // A if ((bResult) && - (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::A)) && - (_getResponderIPAddress(enuIPProtocolType::V4).isSet())) + (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::A)) && + (_getResponderIPAddress(enuIPProtocolType::V4).isSet())) { u32NSECContent |= static_cast(enuContentFlag::A); @@ -299,8 +299,8 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa } // PTR_IPv4 if ((bResult) && - (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::PTR_IPv4)) && - (_getResponderIPAddress(enuIPProtocolType::V4).isSet())) + (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::PTR_IPv4)) && + (_getResponderIPAddress(enuIPProtocolType::V4).isSet())) { u32NSECContent |= static_cast(enuContentFlag::PTR_IPv4); @@ -313,8 +313,8 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa #ifdef MDNS_IPV6_SUPPORT // AAAA if ((bResult) && - (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::AAAA)) && - (_getResponderIPAddress(enuIPProtocolType::V6).isSet())) + (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::AAAA)) && + (_getResponderIPAddress(enuIPProtocolType::V6).isSet())) { u32NSECContent |= static_cast(enuContentFlag::AAAA); @@ -325,8 +325,8 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa } // PTR_IPv6 if ((bResult) && - (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::PTR_IPv6)) && - (_getResponderIPAddress(enuIPProtocolType::V6).isSet())) + (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::PTR_IPv6)) && + (_getResponderIPAddress(enuIPProtocolType::V6).isSet())) { u32NSECContent |= static_cast(enuContentFlag::PTR_IPv6); @@ -343,7 +343,7 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa // PTR_TYPE if ((bResult) && - (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_TYPE))) + (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_TYPE))) { ((static_cast(enuSequence::Count) == sequence) ? ++ru16Answers @@ -352,7 +352,7 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa } // PTR_NAME if ((bResult) && - (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_NAME))) + (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_NAME))) { ((static_cast(enuSequence::Count) == sequence) ? ++ru16Answers @@ -361,7 +361,7 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa } // SRV if ((bResult) && - (pService->m_u32ReplyMask & static_cast(enuContentFlag::SRV))) + (pService->m_u32ReplyMask & static_cast(enuContentFlag::SRV))) { ((static_cast(enuSequence::Count) == sequence) ? ++ru16Answers @@ -370,7 +370,7 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa } // TXT if ((bResult) && - (pService->m_u32ReplyMask & static_cast(enuContentFlag::TXT))) + (pService->m_u32ReplyMask & static_cast(enuContentFlag::TXT))) { ((static_cast(enuSequence::Count) == sequence) ? ++ru16Answers @@ -393,8 +393,8 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa clsService* pService = *it; if ((bResult) && - (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_NAME)) && // If PTR_NAME is requested, AND - (!(pService->m_u32ReplyMask & static_cast(enuContentFlag::SRV)))) // NOT SRV -> add SRV as additional answer + (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_NAME)) && // If PTR_NAME is requested, AND + (!(pService->m_u32ReplyMask & static_cast(enuContentFlag::SRV)))) // NOT SRV -> add SRV as additional answer { ((static_cast(enuSequence::Count) == sequence) @@ -413,18 +413,18 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa } */ if ((pService->m_u32ReplyMask & (static_cast(enuContentFlag::PTR_NAME) | static_cast(enuContentFlag::SRV))) || // If service instance name or SRV OR - (p_rSendParameter.m_u32HostReplyMask & (static_cast(enuContentFlag::A) | static_cast(enuContentFlag::AAAA)))) // any host IP address is requested + (p_rSendParameter.m_u32HostReplyMask & (static_cast(enuContentFlag::A) | static_cast(enuContentFlag::AAAA)))) // any host IP address is requested { #ifdef MDNS_IPV4_SUPPORT if ((bResult) && - (!(p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::A)))) // Add IPv4 address + (!(p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::A)))) // Add IPv4 address { bNeedsAdditionalAnswerA = true; } #endif #ifdef MDNS_IPV6_SUPPORT if ((bResult) && - (!(p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::AAAA)))) // Add IPv6 address + (!(p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::AAAA)))) // Add IPv6 address { bNeedsAdditionalAnswerAAAA = true; } @@ -432,8 +432,8 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa } // NSEC record for service if ((bResult) && - (pService->m_u32ReplyMask) && - ((clsSendParameter::enuResponseType::None != p_rSendParameter.m_Response))) + (pService->m_u32ReplyMask) && + ((clsSendParameter::enuResponseType::None != p_rSendParameter.m_Response))) { ((static_cast(enuSequence::Count) == sequence) ? ++ru16AdditionalAnswers @@ -445,8 +445,8 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa #ifdef MDNS_IPV4_SUPPORT // Answer A needed? if ((bResult) && - (bNeedsAdditionalAnswerA) && - (_getResponderIPAddress(enuIPProtocolType::V4).isSet())) + (bNeedsAdditionalAnswerA) && + (_getResponderIPAddress(enuIPProtocolType::V4).isSet())) { // Additional A u32NSECContent |= static_cast(enuContentFlag::A); @@ -460,8 +460,8 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa #ifdef MDNS_IPV6_SUPPORT // Answer AAAA needed? if ((bResult) && - (bNeedsAdditionalAnswerAAAA) && - (_getResponderIPAddress(enuIPProtocolType::V6).isSet())) + (bNeedsAdditionalAnswerAAAA) && + (_getResponderIPAddress(enuIPProtocolType::V6).isSet())) { // Additional AAAA u32NSECContent |= static_cast(enuContentFlag::AAAA); @@ -475,8 +475,8 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa // NSEC host (part 2) if ((bResult) && - ((clsSendParameter::enuResponseType::None != p_rSendParameter.m_Response)) && - (u32NSECContent)) + ((clsSendParameter::enuResponseType::None != p_rSendParameter.m_Response)) && + (u32NSECContent)) { // NSEC PTR IPv4/IPv6 are separate answers; make sure, that this is counted for #ifdef MDNS_IPV4_SUPPORT @@ -640,8 +640,8 @@ IPAddress clsLEAMDNSHost::_getResponderIPAddress(enuIPProtocolType p_IPProtocolT { //DEBUG_EX_INFO(if ip6_addr_isvalid(netif_ip6_addr_state(&m_rNetIf, idx)) DEBUG_OUTPUT.printf_P(PSTR("%s _getResponderIPAddress: Checking IPv6 address %s (LL: %s)\n"), _DH(), IPAddress(netif_ip_addr6(m_pNetIf, idx)).toString().c_str(), (bCheckLinkLocal ? "YES" : "NO"));); if ((ip6_addr_isvalid(netif_ip6_addr_state(m_pNetIf, idx))) && - (((!bCheckLinkLocal) || - (ip6_addr_islinklocal(netif_ip6_addr(m_pNetIf, idx)))))) + (((!bCheckLinkLocal) || + (ip6_addr_islinklocal(netif_ip6_addr(m_pNetIf, idx)))))) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _getResponderIPAddress: Selected IPv6 address %s (LL: %s)\n"), _DH(), IPAddress(netif_ip_addr6(m_pNetIf, idx)).toString().c_str(), (bCheckLinkLocal ? "YES" : "NO"));); ipResponder = netif_ip_addr6(m_pNetIf, idx); @@ -713,8 +713,8 @@ bool clsLEAMDNSHost::_readRRAnswer(clsLEAMDNSHost::clsRRAnswer*& p_rpRRAnswer) uint32_t u32TTL; uint16_t u16RDLength; if ((_readRRHeader(header)) && - (_udpRead32(u32TTL)) && - (_udpRead16(u16RDLength))) + (_udpRead32(u32TTL)) && + (_udpRead16(u16RDLength))) { /* DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswer: Reading 0x%04X answer (class:0x%04X, TTL:%u, RDLength:%u) for "), header.m_Attributes.m_u16Type, header.m_Attributes.m_u16Class, u32TTL, u16RDLength); @@ -876,7 +876,7 @@ bool clsLEAMDNSHost::_readRRAnswerTXT(clsLEAMDNSHost::clsRRAnswerTXT& p_rRRAnswe const unsigned char* pucCursor = pucBuffer; while ((pucCursor < (pucBuffer + p_u16RDLength)) && - (bResult)) + (bResult)) { bResult = false; @@ -894,7 +894,7 @@ bool clsLEAMDNSHost::_readRRAnswerTXT(clsLEAMDNSHost::clsRRAnswerTXT& p_rRRAnswe unsigned char* pucEqualSign = (unsigned char*)os_strchr((const char*)pucCursor, '='); // Position of the '=' sign unsigned char ucKeyLength; if ((pucEqualSign) && - ((ucKeyLength = (pucEqualSign - pucCursor)))) + ((ucKeyLength = (pucEqualSign - pucCursor)))) { unsigned char ucValueLength = (ucLength - (pucEqualSign - pucCursor + 1)); bResult = (((pTxt = new clsServiceTxt)) && @@ -914,7 +914,7 @@ bool clsLEAMDNSHost::_readRRAnswerTXT(clsLEAMDNSHost::clsRRAnswerTXT& p_rRRAnswe } if ((bResult) && - (pTxt)) + (pTxt)) { // Everythings fine so far // Link TXT item to answer TXTs @@ -1012,13 +1012,13 @@ bool clsLEAMDNSHost::_readRRAnswerSRV(clsLEAMDNSHost::clsRRAnswerSRV& p_rRRAnswe MDNSResponder::_readRRAnswerGeneric */ bool clsLEAMDNSHost::_readRRAnswerGeneric(clsLEAMDNSHost::clsRRAnswerGeneric& p_rRRAnswerGeneric, - uint16_t p_u16RDLength) + 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]))) + ((p_rRRAnswerGeneric.m_pu8RDData = new unsigned char[p_rRRAnswerGeneric.m_u16RDLength]))) { bResult = _udpReadBuffer(p_rRRAnswerGeneric.m_pu8RDData, p_rRRAnswerGeneric.m_u16RDLength); } @@ -1178,7 +1178,7 @@ bool clsLEAMDNSHost::_readRRAttributes(clsLEAMDNSHost::clsRRAttributes& p_rRRAtt */ bool clsLEAMDNSHost::_buildDomainForHost(const char* p_pcHostName, - clsLEAMDNSHost::clsRRDomain& p_rHostDomain) const + clsLEAMDNSHost::clsRRDomain& p_rHostDomain) const { p_rHostDomain.clear(); @@ -1218,8 +1218,8 @@ bool clsLEAMDNSHost::_buildDomainForDNSSD(clsLEAMDNSHost::clsRRDomain& p_rDNSSDD */ bool clsLEAMDNSHost::_buildDomainForService(const clsLEAMDNSHost::clsService& p_Service, - bool p_bIncludeName, - clsLEAMDNSHost::clsRRDomain& p_rServiceDomain) const + bool p_bIncludeName, + clsLEAMDNSHost::clsRRDomain& p_rServiceDomain) const { p_rServiceDomain.clear(); bool bResult = (((!p_bIncludeName) || @@ -1240,8 +1240,8 @@ bool clsLEAMDNSHost::_buildDomainForService(const clsLEAMDNSHost::clsService& p_ */ bool clsLEAMDNSHost::_buildDomainForService(const char* p_pcServiceType, - const char* p_pcProtocol, - clsLEAMDNSHost::clsRRDomain& p_rServiceDomain) const + const char* p_pcProtocol, + clsLEAMDNSHost::clsRRDomain& p_rServiceDomain) const { p_rServiceDomain.clear(); bool bResult = ((p_pcServiceType) && @@ -1264,7 +1264,7 @@ bool clsLEAMDNSHost::_buildDomainForService(const char* p_pcServiceType, */ bool clsLEAMDNSHost::_buildDomainForReverseIPv4(IPAddress p_IPv4Address, - clsLEAMDNSHost::clsRRDomain& p_rReverseIPv4Domain) const + clsLEAMDNSHost::clsRRDomain& p_rReverseIPv4Domain) const { bool bResult = ((p_IPv4Address.isSet()) && (p_IPv4Address.isV4())); @@ -1296,7 +1296,7 @@ bool clsLEAMDNSHost::_buildDomainForReverseIPv4(IPAddress p_IPv4Address, */ bool clsLEAMDNSHost::_buildDomainForReverseIPv6(IPAddress p_IPv6Address, - clsLEAMDNSHost::clsRRDomain& p_rReverseIPv6Domain) const + clsLEAMDNSHost::clsRRDomain& p_rReverseIPv6Domain) const { bool bResult = ((p_IPv6Address.isSet()) && (p_IPv6Address.isV6())); @@ -1507,12 +1507,12 @@ bool clsLEAMDNSHost::_readMDNSMsgHeader(clsLEAMDNSHost::clsMsgHeader& p_rMsgHead 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))) + (_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 @@ -1584,7 +1584,7 @@ bool clsLEAMDNSHost::_write32(uint32_t p_u32Value, */ bool clsLEAMDNSHost::_writeMDNSMsgHeader(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader, - clsLEAMDNSHost::clsSendParameter& p_rSendParameter) + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { /* DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSMsgHeader: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), _DH(), @@ -1615,7 +1615,7 @@ bool clsLEAMDNSHost::_writeMDNSMsgHeader(const clsLEAMDNSHost::clsMsgHeader& p_M */ bool clsLEAMDNSHost::_writeMDNSRRAttributes(const clsLEAMDNSHost::clsRRAttributes& p_Attributes, - clsLEAMDNSHost::clsSendParameter& p_rSendParameter) + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { bool bResult = ((_write16(p_Attributes.m_u16Type, p_rSendParameter)) && (_write16(p_Attributes.m_u16Class, p_rSendParameter))); @@ -1654,9 +1654,9 @@ bool clsLEAMDNSHost::_writeMDNSRRDomain(const clsLEAMDNSHost::clsRRDomain& p_Dom */ bool clsLEAMDNSHost::_writeMDNSHostDomain(const char* p_pcHostName, - bool p_bPrependRDLength, - uint16_t p_u16AdditionalLength, - clsLEAMDNSHost::clsSendParameter& p_rSendParameter) + bool p_bPrependRDLength, + uint16_t p_u16AdditionalLength, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV' uint16_t u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)p_pcHostName, false); @@ -1694,10 +1694,10 @@ bool clsLEAMDNSHost::_writeMDNSHostDomain(const char* p_pcHostName, */ bool clsLEAMDNSHost::_writeMDNSServiceDomain(const clsLEAMDNSHost::clsService& p_Service, - bool p_bIncludeName, - bool p_bPrependRDLength, - uint16_t p_u16AdditionalLength, - clsLEAMDNSHost::clsSendParameter& p_rSendParameter) + bool p_bIncludeName, + bool p_bPrependRDLength, + uint16_t p_u16AdditionalLength, + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV' uint16_t u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)&p_Service, p_bIncludeName); @@ -1815,7 +1815,7 @@ bool clsLEAMDNSHost::_writeMDNSAnswer_A(IPAddress p_IPAddress, */ bool clsLEAMDNSHost::_writeMDNSAnswer_PTR_IPv4(IPAddress p_IPAddress, - clsLEAMDNSHost::clsSendParameter& p_rSendParameter) + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv4 (%s)%s\n"), p_IPAddress.toString().c_str(), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); @@ -1860,7 +1860,7 @@ bool clsLEAMDNSHost::_writeMDNSAnswer_PTR_IPv4(IPAddress p_IPAddress, */ bool clsLEAMDNSHost::_writeMDNSAnswer_PTR_TYPE(clsLEAMDNSHost::clsService& p_rService, - clsLEAMDNSHost::clsSendParameter& p_rSendParameter) + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_TYPE\n"));); @@ -1903,7 +1903,7 @@ bool clsLEAMDNSHost::_writeMDNSAnswer_PTR_TYPE(clsLEAMDNSHost::clsService& p_rSe */ bool clsLEAMDNSHost::_writeMDNSAnswer_PTR_NAME(clsLEAMDNSHost::clsService& p_rService, - clsLEAMDNSHost::clsSendParameter& p_rSendParameter) + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_NAME\n"), _DH());); @@ -1947,7 +1947,7 @@ bool clsLEAMDNSHost::_writeMDNSAnswer_PTR_NAME(clsLEAMDNSHost::clsService& p_rSe */ bool clsLEAMDNSHost::_writeMDNSAnswer_TXT(clsLEAMDNSHost::clsService& p_rService, - clsLEAMDNSHost::clsSendParameter& p_rSendParameter) + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_TXT%s\n"), (p_rSendParameter.m_bCacheFlush ? "" : " nF"), _DH());); @@ -1957,16 +1957,16 @@ bool clsLEAMDNSHost::_writeMDNSAnswer_TXT(clsLEAMDNSHost::clsService& p_rService ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet if ((_collectServiceTxts(p_rService)) && - (_writeMDNSServiceDomain(p_rService, true, false, 0, p_rSendParameter)) && // MyESP._http._tcp.local - (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS - (_write32((p_rSendParameter.m_bUnannounce - ? 0 - : (p_rSendParameter.m_bLegacyDNSQuery // TTL - ? clsConsts::u32LegacyTTL - : clsConsts::u32ServiceTTL)), p_rSendParameter)) && - (_write16((p_rService.m_Txts.count() // RDLength - ? p_rService.m_Txts.length() // default case - : 1), p_rSendParameter))) // If no TXT records exist, a single 0 byte is sent + (_writeMDNSServiceDomain(p_rService, true, false, 0, p_rSendParameter)) && // MyESP._http._tcp.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce + ? 0 + : (p_rSendParameter.m_bLegacyDNSQuery // TTL + ? clsConsts::u32LegacyTTL + : clsConsts::u32ServiceTTL)), p_rSendParameter)) && + (_write16((p_rService.m_Txts.count() // RDLength + ? p_rService.m_Txts.length() // default case + : 1), p_rSendParameter))) // If no TXT records exist, a single 0 byte is sent { bResult = true; // RData Txts @@ -2035,7 +2035,7 @@ bool clsLEAMDNSHost::_writeMDNSAnswer_TXT(clsLEAMDNSHost::clsService& p_rService */ bool clsLEAMDNSHost::_writeMDNSAnswer_AAAA(IPAddress p_IPAddress, - clsLEAMDNSHost::clsSendParameter& p_rSendParameter) + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_AAAA (%s)%s\n"), p_IPAddress.toString().c_str(), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); @@ -2077,7 +2077,7 @@ bool clsLEAMDNSHost::_writeMDNSAnswer_AAAA(IPAddress p_IPAddress, */ bool clsLEAMDNSHost::_writeMDNSAnswer_PTR_IPv6(IPAddress p_IPAddress, - clsLEAMDNSHost::clsSendParameter& p_rSendParameter) + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_PTR_IPv6%s\n"), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); @@ -2117,13 +2117,13 @@ bool clsLEAMDNSHost::_writeMDNSAnswer_PTR_IPv6(IPAddress p_IPAddress, */ bool clsLEAMDNSHost::_writeMDNSAnswer_SRV(clsLEAMDNSHost::clsService& p_rService, - clsLEAMDNSHost::clsSendParameter& p_rSendParameter) + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _writeMDNSAnswer_SRV%s\n"), (p_rSendParameter.m_bCacheFlush ? "" : " nF"));); uint16_t u16CachedDomainOffset = (p_rSendParameter.m_bLegacyDNSQuery - ? 0 - : p_rSendParameter.findCachedDomainOffset((const void*)m_pcHostName, false)); + ? 0 + : p_rSendParameter.findCachedDomainOffset((const void*)m_pcHostName, false)); clsRRAttributes attributes(DNS_RRTYPE_SRV, ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet @@ -2194,7 +2194,7 @@ clsLEAMDNSHost::clsNSECBitmap* clsLEAMDNSHost::_createNSECBitmap(uint32_t p_u32N } #endif if ((p_u32NSECContent & static_cast(enuContentFlag::PTR_IPv4)) || - (p_u32NSECContent & static_cast(enuContentFlag::PTR_IPv6))) + (p_u32NSECContent & static_cast(enuContentFlag::PTR_IPv6))) { pNSECBitmap->setBit(DNS_RRTYPE_PTR); // 12/0x0C } @@ -2225,7 +2225,7 @@ clsLEAMDNSHost::clsNSECBitmap* clsLEAMDNSHost::_createNSECBitmap(uint32_t p_u32N */ bool clsLEAMDNSHost::_writeMDNSNSECBitmap(const clsLEAMDNSHost::clsNSECBitmap& p_NSECBitmap, - clsLEAMDNSHost::clsSendParameter& p_rSendParameter) + clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { /* DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("_writeMDNSNSECBitmap: ")); for (uint16_t u=0; unext())) + (m_pUDPContext->next())) { netif* pNetIf = m_pUDPContext->getInputNetif();//ip_current_input_netif(); // Probably changed inbetween!!!! clsLEAMDNSHost* pHost = 0; if ((pNetIf) && - ((pHost = _findHost(pNetIf)))) + ((pHost = _findHost(pNetIf)))) { DEBUG_EX_INFO( if (u32LoopCounter++) { DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Multi-Loop (%u)!\n"), _DH(), u32LoopCounter); if ((remoteIPAddr.isSet()) && - (remoteIPAddr != m_pUDPContext->getRemoteAddress())) + (remoteIPAddr != m_pUDPContext->getRemoteAddress())) { DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Changed IP address %s->%s!\n"), _DH(), remoteIPAddr.toString().c_str(), m_pUDPContext->getRemoteAddress().toString().c_str()); } @@ -264,7 +264,7 @@ const clsLEAMDNSHost* clsLEAMDNSHost::clsBackbone::_findHost(netif* p_pNetIf) co for (const clsLEAMDNSHost* pHost : m_HostList) { if ((p_pNetIf) && - (pHost->m_pNetIf == p_pNetIf)) + (pHost->m_pNetIf == p_pNetIf)) { pResult = pHost; break; diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index 040d915905..896018f81d 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -1,1493 +1,1495 @@ -/* - * LEAmDNS2_Legacy.cpp - * - * - */ - -#include "LEAmDNS2_Legacy.h" - - -namespace esp8266 -{ - -/** - * LEAmDNS - */ -namespace MDNSImplementation -{ - -/** - STRINGIZE -*/ -#ifndef STRINGIZE -#define STRINGIZE(x) #x -#endif -#ifndef STRINGIZE_VALUE_OF -#define STRINGIZE_VALUE_OF(x) STRINGIZE(x) -#endif - - -/* - * clsLEAMDNSHost_Legacy::clsLEAMDNSHost_Legacy constructor - * - */ -clsLEAMDNSHost_Legacy::clsLEAMDNSHost_Legacy(void) -{ -} - -/* - * clsLEAMDNSHost_Legacy::clsLEAMDNSHost_Legacy destructor - * - */ -clsLEAMDNSHost_Legacy::~clsLEAMDNSHost_Legacy(void) -{ -} - -/* - * - * HOST SETUP - * - */ - -/* - * clsLEAMDNSHost_Legacy::begin - * - */ -bool clsLEAMDNSHost_Legacy::begin(const char* p_pcHostname) -{ - bool bResult = ( ( (!(WIFI_STA & (WiFiMode_t)wifi_get_opmode())) - || (addHostForNetIf(p_pcHostname, netif_get_by_index(WIFI_STA)))) - && ( (!(WIFI_AP & (WiFiMode_t)wifi_get_opmode())) - || (addHostForNetIf(p_pcHostname, netif_get_by_index(WIFI_AP))))); - return ( (bResult) - && (0 != m_HostInformations.size())); -} - -/* - * clsLEAMDNSHost_Legacy::begin (String) - * - */ -bool clsLEAMDNSHost_Legacy::begin(const String& p_strHostname) -{ - return begin(p_strHostname.c_str()); -} - -/* - * clsLEAMDNSHost_Legacy::begin (Ignored Options) - * - */ -bool clsLEAMDNSHost_Legacy::begin(const char* p_pcHostname, - IPAddress /*p_IPAddress = INADDR_ANY*/, // ignored - uint32_t /*p_u32TTL = 120*/) // ignored -{ - return begin(p_pcHostname); -} - -/* - * clsLEAMDNSHost_Legacy::begin (String & Ignored Options) - * - */ -bool clsLEAMDNSHost_Legacy::begin(const String& p_strHostname, - IPAddress /*p_IPAddress = INADDR_ANY*/, // ignored - uint32_t /*p_u32TTL = 120*/) // ignored -{ - return begin(p_strHostname.c_str()); -} - -/* - * clsLEAMDNSHost_Legacy::close - * - */ -bool clsLEAMDNSHost_Legacy::close(void) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - if ((bResult = (*it).m_pHost->close())) - { - delete (*it).m_pHost; - (*it).m_pHost = 0; - } - } - return ( (bResult) - && (m_HostInformations.clear(), true)); -} - -/* - * clsLEAMDNSHost_Legacy::end - * - */ -bool clsLEAMDNSHost_Legacy::end(void) -{ - return close(); -} - -/* - * clsLEAMDNSHost_Legacy::addHostForNetIf - * - * NEW! - * - */ -bool clsLEAMDNSHost_Legacy::addHostForNetIf(const char* p_pcHostname, - netif* p_pNetIf) -{ - clsLEAMDNSHost* pHost = 0; - - if ( ((pHost = new esp8266::experimental::clsLEAMDNSHost)) - && (!( (pHost->begin(p_pcHostname, p_pNetIf /*, default callback*/)) - && (m_HostInformations.push_back(stcHostInformation(pHost)), true)))) - { - delete pHost; - pHost = 0; - } - return (0 != pHost); -} - -/* - * clsLEAMDNSHost_Legacy::setHostname - * - */ -bool clsLEAMDNSHost_Legacy::setHostname(const char* p_pcHostname) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - bResult = (*it).m_pHost->setHostName(p_pcHostname); - } - return bResult; -} - -/* - * clsLEAMDNSHost_Legacy::setHostname - * - */ -bool clsLEAMDNSHost_Legacy::setHostname(String p_strHostname) -{ - return setHostname(p_strHostname.c_str()); -} - -/* - * clsLEAMDNSHost_Legacy::hostname - * - */ -const char* clsLEAMDNSHost_Legacy::hostname(void) const -{ - return (m_HostInformations.empty() - ? 0 - : m_HostInformations.front().m_pHost->hostName()); -} - -/* - * clsLEAMDNSHost_Legacy::status - * - */ -bool clsLEAMDNSHost_Legacy::status(void) const -{ - bool bStatus = true; - - for (const stcHostInformation& hostInformation : m_HostInformations) - { - if (!((bStatus = hostInformation.m_pHost->probeStatus()))) - { - break; - } - } - return bStatus; -} - - -/* - * - * SERVICE MANAGEMENT - * - */ - -/* - * clsLEAMDNSHost_Legacy::addService - * - */ -clsLEAMDNSHost_Legacy::hMDNSService clsLEAMDNSHost_Legacy::addService(const char* p_pcName, - const char* p_pcService, - const char* p_pcProtocol, - uint16_t p_u16Port) -{ - hMDNSService hResult = 0; - - for (stcHostInformation& hostInformation : m_HostInformations) - { - clsLEAMDNSHost::clsService* pService = hostInformation.m_pHost->addService(p_pcName, p_pcService, p_pcProtocol, p_u16Port /*, default callback*/); - if (pService) - { - if (!hResult) - { // Store first service handle as result and key - hResult = (hMDNSService)pService; - } - hostInformation.m_HandleToPtr[hResult] = pService; - } - } - return hResult; -} - -/* - * clsLEAMDNSHost_Legacy::addService (String) - * - */ -bool clsLEAMDNSHost_Legacy::addService(String p_strServiceName, - String p_strProtocol, - uint16_t p_u16Port) -{ - return (0 != addService(0, p_strServiceName.c_str(), p_strProtocol.c_str(), p_u16Port)); -} - -/* - * clsLEAMDNSHost_Legacy::removeService (hService) - * - */ -bool clsLEAMDNSHost_Legacy::removeService(const hMDNSService p_hService) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService]; - if ((bResult = ( (pService) - && ((*it).m_pHost->removeService(pService))))) - { - (*it).m_HandleToPtr.erase(p_hService); - } - } - return bResult; -} - -/* - * clsLEAMDNSHost_Legacy::removeService (name) - * - */ -bool clsLEAMDNSHost_Legacy::removeService(const char* p_pcInstanceName, - const char* p_pcServiceName, - const char* p_pcProtocol) -{ - hMDNSService hService = 0; - return ( ((hService = (m_HostInformations.empty() - ? 0 - : (hMDNSService)m_HostInformations.front().m_pHost->findService(p_pcInstanceName, p_pcServiceName, p_pcProtocol)))) - && (removeService(hService))); -} - -/* - * clsLEAMDNSHost_Legacy::setServiceName - * - */ -bool clsLEAMDNSHost_Legacy::setServiceName(const hMDNSService p_hService, - const char* p_pcInstanceName) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService]; - bResult = ( (pService) - && (pService->setInstanceName(p_pcInstanceName))); - } - return bResult; -} - -/* - * clsLEAMDNSHost_Legacy::setInstanceName - * - */ -void clsLEAMDNSHost_Legacy::setInstanceName(const char* p_pcInstanceName) -{ - for (stcHostInformation& hostInformation : m_HostInformations) - { - hostInformation.m_pHost->setDefaultInstanceName(p_pcInstanceName); - } -} - -/* - * clsLEAMDNSHost_Legacy::setInstanceName (String) - * - */ -void clsLEAMDNSHost_Legacy::setInstanceName(const String& p_strHostname) -{ - setInstanceName(p_strHostname.c_str()); -} - -/* - * clsLEAMDNSHost_Legacy::serviceName - * - */ -const char* clsLEAMDNSHost_Legacy::serviceName(const hMDNSService p_hService) const -{ - const clsLEAMDNSHost::clsService* pService = 0; - return (m_HostInformations.empty() - ? 0 - : (((pService = (const clsLEAMDNSHost::clsService*)(m_HostInformations.front().m_HandleToPtr.at(p_hService)))) - ? pService->instanceName() - : 0)); -} - -/* - * clsLEAMDNSHost_Legacy::service - * - */ -const char* clsLEAMDNSHost_Legacy::service(const hMDNSService p_hService) const -{ - const clsLEAMDNSHost::clsService* pService = 0; - return (m_HostInformations.empty() - ? 0 - : (((pService = (const clsLEAMDNSHost::clsService*)m_HostInformations.front().m_HandleToPtr.at(p_hService))) - ? pService->type() - : 0)); -} - -/* - * clsLEAMDNSHost_Legacy::serviceProtocol - * - */ -const char* clsLEAMDNSHost_Legacy::serviceProtocol(const hMDNSService p_hService) const -{ - const clsLEAMDNSHost::clsService* pService = 0; - return (m_HostInformations.empty() - ? 0 - : (((pService = (const clsLEAMDNSHost::clsService*)m_HostInformations.front().m_HandleToPtr.at(p_hService))) - ? pService->protocol() - : 0)); -} - -/* - * clsLEAMDNSHost_Legacy::serviceStatus - * - */ -bool clsLEAMDNSHost_Legacy::serviceStatus(const hMDNSService p_hService) const -{ - const clsLEAMDNSHost::clsService* pService = 0; - return (m_HostInformations.empty() - ? false - : (((pService = (const clsLEAMDNSHost::clsService*)m_HostInformations.front().m_HandleToPtr.at(p_hService))) - ? pService->probeStatus() - : false)); -} - - -/* - * - * SERVICE TXT MANAGEMENT - * - */ - -/* - * clsLEAMDNSHost_Legacy::addServiceTxt (char*) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - const char* p_pcValue) -{ - return _addServiceTxt(p_hService, p_pcKey, p_pcValue, false); -} - -/* - * clsLEAMDNSHost_Legacy::addServiceTxt (uint32_t) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint32_t p_u32Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_u32Value, false); -} - -/* - * clsLEAMDNSHost_Legacy::addServiceTxt (uint16_t) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint16_t p_u16Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_u16Value, false); -} - -/* - * clsLEAMDNSHost_Legacy::addServiceTxt (uint8_t) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint8_t p_u8Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_u8Value, false); -} - -/* - * clsLEAMDNSHost_Legacy::addServiceTxt (int32_t) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int32_t p_i32Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_i32Value, false); -} - -/* - * clsLEAMDNSHost_Legacy::addServiceTxt (int16_t) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int16_t p_i16Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_i16Value, false); -} - -/* - * clsLEAMDNSHost_Legacy::addServiceTxt (int8_t) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int8_t p_i8Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_i8Value, false); -} - -/* - * clsLEAMDNSHost_Legacy::addServiceTxt (legacy) - * - */ -bool clsLEAMDNSHost_Legacy::addServiceTxt(const char* p_pcService, - const char* p_pcProtocol, - const char* p_pcKey, - const char* p_pcValue) -{ - hMDNSService hService = 0; - return ( ((hService = (m_HostInformations.empty() - ? 0 - : (hMDNSService)m_HostInformations.front().m_pHost->findService(0, p_pcService, p_pcProtocol)))) - && (_addServiceTxt(hService, p_pcKey, p_pcValue, false))); -} - -/* - * clsLEAMDNSHost_Legacy::addServiceTxt (legacy, String) - * - */ -bool clsLEAMDNSHost_Legacy::addServiceTxt(String p_strService, - String p_strProtocol, - String p_strKey, - String p_strValue) -{ - return addServiceTxt(p_strService.c_str(), p_strProtocol.c_str(), p_strKey.c_str(), p_strValue.c_str()); -} - -/* - * clsLEAMDNSHost_Legacy::removeServiceTxt (hTxt) - * - */ -bool clsLEAMDNSHost_Legacy::removeServiceTxt(const hMDNSService p_hService, - const hMDNSTxt p_hTxt) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService]; - clsLEAMDNSHost::clsServiceTxt* pTxt = (clsLEAMDNSHost::clsServiceTxt*)(*it).m_HandleToPtr[p_hTxt]; - if ((bResult = ( (pService) - && (pTxt) - && (pService->removeServiceTxt(pTxt))))) - { - (*it).m_HandleToPtr.erase(p_hTxt); - } - } - return bResult; -} - -/* - * clsLEAMDNSHost_Legacy::removeServiceTxt (char*) - * - */ -bool clsLEAMDNSHost_Legacy::removeServiceTxt(const hMDNSService p_hService, - const char* p_pcKey) -{ - clsLEAMDNSHost::clsService* pService = 0; - clsLEAMDNSHost::clsServiceTxt* pTxt = 0; - return ( ((pService = (m_HostInformations.empty() - ? 0 - : (clsLEAMDNSHost::clsService*)m_HostInformations.front().m_HandleToPtr[p_hService]))) - && ((pTxt = pService->findServiceTxt(p_pcKey))) - && (removeServiceTxt(p_hService, (const hMDNSTxt)pTxt))); -} - -/* - * clsLEAMDNSHost_Legacy::removeServiceTxt (char*) - * - */ -bool clsLEAMDNSHost_Legacy::removeServiceTxt(const char* p_pcInstanceName, - const char* p_pcServiceName, - const char* p_pcProtocol, - const char* p_pcKey) -{ - hMDNSService hService = 0; - return ( ((hService = (m_HostInformations.empty() - ? 0 - : (hMDNSService)m_HostInformations.front().m_pHost->findService(p_pcInstanceName, p_pcServiceName, p_pcProtocol)))) - && (removeServiceTxt(hService, p_pcKey))); -} - -/* - * clsLEAMDNSHost_Legacy::setDynamicServiceTxtCallback (global) - * - */ -bool clsLEAMDNSHost_Legacy::setDynamicServiceTxtCallback(MDNSDynamicServiceTxtCallbackFn p_fnCallback) -{ - bool bResult = true; - - if ((bResult = m_HostInformations.empty())) - { - // The service handles of the first host are the keys in the HostInformations.HandleToPtr map - for (const clsLEAMDNSHost::clsService* pService : m_HostInformations.front().m_pHost->services()) - { - if (!((bResult = setDynamicServiceTxtCallback((hMDNSService)pService, p_fnCallback)))) - { - break; - } - } - } - return bResult; -} - -/* - * clsLEAMDNSHost_Legacy::setDynamicServiceTxtCallback (service) - * - */ -bool clsLEAMDNSHost_Legacy::setDynamicServiceTxtCallback(const hMDNSService p_hService, - MDNSDynamicServiceTxtCallbackFn p_fnCallback) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService]; - bResult = pService->setDynamicServiceTxtCallback([p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/)->void - { - if (p_fnCallback) // void(const hMDNSService p_hService) - { - p_fnCallback(p_hService); - } - }); - } - return bResult; -} - -/* - * clsLEAMDNSHost_Legacy::addDynamicServiceTxt (char*) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, - const char* p_pcKey, - const char* p_pcValue) -{ - return _addServiceTxt(p_hService, p_pcKey, p_pcValue, true); -} - -/* - * clsLEAMDNSHost_Legacy::addDynamicServiceTxt (uint32) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, - const char* p_pcKey, - uint32_t p_u32Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_u32Value, true); -} - -/* - * clsLEAMDNSHost_Legacy::addDynamicServiceTxt (uint16) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, - const char* p_pcKey, - uint16_t p_u16Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_u16Value, true); -} - -/* - * clsLEAMDNSHost_Legacy::addDynamicServiceTxt (uint8) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, - const char* p_pcKey, - uint8_t p_u8Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_u8Value, true); -} - -/* - * clsLEAMDNSHost_Legacy::addDynamicServiceTxt (int32) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, - const char* p_pcKey, - int32_t p_i32Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_i32Value, true); -} - -/* - * clsLEAMDNSHost_Legacy::addDynamicServiceTxt (int16) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, - const char* p_pcKey, - int16_t p_i16Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_i16Value, true); -} - -/* - * clsLEAMDNSHost_Legacy::addDynamicServiceTxt (int8) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, - const char* p_pcKey, - int8_t p_i8Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_i8Value, true); -} - - -/* - * - * STATIC QUERY - * - */ - - /* - * clsLEAMDNSHost_Legacy::queryService - * - * This will take p_u16Timeout millisec for every host! - * - */ -uint32_t clsLEAMDNSHost_Legacy::queryService(const char* p_pcService, - const char* p_pcProtocol, - const uint16_t p_u16Timeout /*= MDNS_QUERYSERVICES_WAIT_TIME*/) -{ - uint32_t u32Answers = 0; - - for (stcHostInformation& hostInformation : m_HostInformations) - { - u32Answers += (hostInformation.m_pHost->queryService(p_pcService, p_pcProtocol, p_u16Timeout)).size(); - } - return u32Answers; -} - -/* - * clsLEAMDNSHost_Legacy::removeQuery - * - */ -bool clsLEAMDNSHost_Legacy::removeQuery(void) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - bResult = (*it).m_pHost->removeQuery(); - } - return bResult; -} - -/* - * clsLEAMDNSHost_Legacy::queryService - * - */ -uint32_t clsLEAMDNSHost_Legacy::queryService(String p_strService, - String p_strProtocol) -{ - return queryService(p_strService.c_str(), p_strProtocol.c_str()); -} - -/* - * clsLEAMDNSHost_Legacy::answerHostname - * - */ -const char* clsLEAMDNSHost_Legacy::answerHostname(const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_u32AnswerIndex); - return (answerAccessor.serviceDomainAvailable() - ? answerAccessor.serviceDomain() - : 0); -} - -/* - * clsLEAMDNSHost_Legacy::answerIP - * - */ -IPAddress clsLEAMDNSHost_Legacy::answerIP(const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_u32AnswerIndex); - return (answerAccessor.IPv4AddressAvailable() - ? answerAccessor.IPv4Addresses()[0] - : IPAddress()); -} - -/* - * clsLEAMDNSHost_Legacy::answerPort - * - */ -uint16_t clsLEAMDNSHost_Legacy::answerPort(const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_u32AnswerIndex); - return (answerAccessor.hostPortAvailable() - ? answerAccessor.hostPort() - : 0); -} - -/* - * clsLEAMDNSHost_Legacy::hostname - * - */ -String clsLEAMDNSHost_Legacy::hostname(const uint32_t p_u32AnswerIndex) -{ - return String(answerHostname(p_u32AnswerIndex)); -} - -/* - * clsLEAMDNSHost_Legacy::IP - * - */ -IPAddress clsLEAMDNSHost_Legacy::IP(const uint32_t p_u32AnswerIndex) -{ - return answerIP(p_u32AnswerIndex); -} - -/* - * clsLEAMDNSHost_Legacy::port - * - */ -uint16_t clsLEAMDNSHost_Legacy::port(const uint32_t p_u32AnswerIndex) -{ - return answerPort(p_u32AnswerIndex); -} - - -/* - * - * DYNAMIC QUERY - * - */ - -/* - * clsLEAMDNSHost_Legacy::installServiceQuery - * - */ -clsLEAMDNSHost_Legacy::hMDNSServiceQuery clsLEAMDNSHost_Legacy::installServiceQuery(const char* p_pcService, - const char* p_pcProtocol, - MDNSServiceQueryCallbackFn p_fnCallback) -{ - hMDNSServiceQuery hResult = 0; - - for (stcHostInformation& hostInformation : m_HostInformations) - { - clsLEAMDNSHost::clsQuery* pQuery = hostInformation.m_pHost->installServiceQuery(p_pcService, p_pcProtocol, [this, p_fnCallback](const clsLEAMDNSHost::clsQuery& /*p_Query*/, - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor& p_AnswerAccessor, - clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, // flags for the updated answer item - bool p_bSetContent)->void - { - if (p_fnCallback) // void(const stcMDNSServiceInfo& p_MDNSServiceInfo, MDNSResponder::AnswerType p_AnswerType, bool p_bSetContent) - { - p_fnCallback(stcMDNSServiceInfo(p_AnswerAccessor), _answerFlagsToAnswerType(p_QueryAnswerTypeFlags), p_bSetContent); - } - }); - if (pQuery) - { - if (!hResult) - { // Store first query as result and key - hResult = (hMDNSServiceQuery)pQuery; - } - hostInformation.m_HandleToPtr[hResult] = pQuery; - } - } - return hResult; -} - -/* - * clsLEAMDNSHost_Legacy::removeServiceQuery - * - */ -bool clsLEAMDNSHost_Legacy::removeServiceQuery(clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - if ((bResult = (*it).m_pHost->removeQuery((clsLEAMDNSHost::clsQuery*)(*it).m_HandleToPtr[p_hServiceQuery]))) - { - (*it).m_HandleToPtr.erase(p_hServiceQuery); - } - } - return bResult; -} - -/* - * clsLEAMDNSHost_Legacy::answerCount - * - */ -uint32_t clsLEAMDNSHost_Legacy::answerCount(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery) -{ - uint32_t u32AnswerCount = 0; - - for (stcHostInformation& hostInformation : m_HostInformations) - { - clsLEAMDNSHost::clsQuery* pQuery = (clsLEAMDNSHost::clsQuery*)hostInformation.m_HandleToPtr[p_hServiceQuery]; - if (pQuery) - { - u32AnswerCount += pQuery->answerCount(); - } - else - { - u32AnswerCount = 0; - break; - } - } - return u32AnswerCount; -} - -/* - * clsLEAMDNSHost_Legacy::answerInfo - * - */ -std::vector clsLEAMDNSHost_Legacy::answerInfo(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery) -{ - std::vector serviceInfos; - - for (stcHostInformation& hostInformation : m_HostInformations) - { - clsLEAMDNSHost::clsQuery* pQuery = (clsLEAMDNSHost::clsQuery*)hostInformation.m_HandleToPtr[p_hServiceQuery]; - if (pQuery) - { - for (clsLEAMDNSHost::clsQuery::clsAnswerAccessor& answerAccessor : pQuery->answerAccessors()) - { - serviceInfos.push_back(stcMDNSServiceInfo(answerAccessor)); - } - } - else - { - serviceInfos.clear(); - break; - } - } - return serviceInfos; -} - -/* - * clsLEAMDNSHost_Legacy::answerServiceDomain - * - */ -const char* clsLEAMDNSHost_Legacy::answerServiceDomain(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); - return (answerAccessor.serviceDomainAvailable() - ? answerAccessor.serviceDomain() - : 0); -} - -/* - * clsLEAMDNSHost_Legacy::hasAnswerHostDomain - * - */ -bool clsLEAMDNSHost_Legacy::hasAnswerHostDomain(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).hostDomainAvailable(); -} - -/* - * clsLEAMDNSHost_Legacy::answerHostDomain - * - */ -const char* clsLEAMDNSHost_Legacy::answerHostDomain(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); - return (answerAccessor.hostDomainAvailable() - ? answerAccessor.hostDomain() - : 0); -} - -#ifdef MDNS_IP4_SUPPORT -/* - * clsLEAMDNSHost_Legacy::hasAnswerIP4Address - * - */ -bool clsLEAMDNSHost_Legacy::hasAnswerIP4Address(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).IPv4AddressAvailable(); -} - -/* - * clsLEAMDNSHost_Legacy::answerIP4AddressCount - * - */ -uint32_t clsLEAMDNSHost_Legacy::answerIP4AddressCount(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); - return (answerAccessor.IPv4AddressAvailable() - ? answerAccessor.IPv4Addresses().size() - : 0); -} - -/* - * clsLEAMDNSHost_Legacy::answerIP4Address - * - */ -IPAddress clsLEAMDNSHost_Legacy::answerIP4Address(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex, - const uint32_t p_u32AddressIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); - return (answerAccessor.IPv4AddressAvailable() - ? answerAccessor.IPv4Addresses()[p_u32AddressIndex] - : IPAddress()); -} -#endif -#ifdef MDNS_IP6_SUPPORT -/* - * clsLEAMDNSHost_Legacy::hasAnswerIP6Address - * - */ -bool clsLEAMDNSHost_Legacy::hasAnswerIP6Address(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).IPv6AddressAvailable(); -} - -/* - * clsLEAMDNSHost_Legacy::answerIP6AddressCount - * - */ -uint32_t clsLEAMDNSHost_Legacy::answerIP6AddressCount(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); - return (answerAccessor.IPv6AddressAvailable() - ? answerAccessor.IPv6Addresses().size() - : 0); -} - -/* - * clsLEAMDNSHost_Legacy::answerIP6Address - * - */ -IPAddress clsLEAMDNSHost_Legacy::answerIP6Address(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex, - const uint32_t p_u32AddressIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); - return (answerAccessor.IPv6AddressAvailable() - ? answerAccessor.IPv6Addresses()[p_u32AddressIndex] - : IPAddress()); -} -#endif - -/* - * clsLEAMDNSHost_Legacy::hasAnswerPort - * - */ -bool clsLEAMDNSHost_Legacy::hasAnswerPort(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).hostPortAvailable(); -} - -/* - * clsLEAMDNSHost_Legacy::answerPort - * - */ -uint16_t clsLEAMDNSHost_Legacy::answerPort(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); - return (answerAccessor.hostPortAvailable() - ? answerAccessor.hostPort() - : 0); -} - -/* - * clsLEAMDNSHost_Legacy::hasAnswerTxts - * - */ -bool clsLEAMDNSHost_Legacy::hasAnswerTxts(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).txtsAvailable(); -} - -/* - * clsLEAMDNSHost_Legacy::answerHostDomain - * - * Get the TXT items as a ';'-separated string - */ -const char* clsLEAMDNSHost_Legacy::answerTxts(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); - return (answerAccessor.txtsAvailable() - ? answerAccessor.txts() - : 0); -} - - -/* - * - * HOST/SERVICE PROBE CALLBACKS - * - */ - -/* - * clsLEAMDNSHost_Legacy::setHostProbeResultCallback - * - */ -bool clsLEAMDNSHost_Legacy::setHostProbeResultCallback(clsLEAMDNSHost_Legacy::MDNSHostProbeResultCallbackFn p_fnCallback) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - bResult = (*it).m_pHost->setProbeResultCallback([p_fnCallback](clsLEAMDNSHost& /*p_rHost*/, - const char* p_pcDomainName, - bool p_bProbeResult)->void - { - if (p_fnCallback) // void(const char* p_pcDomainName, bool p_bProbeResult) - { - p_fnCallback(p_pcDomainName, p_bProbeResult); - } - }); - } - return bResult; -} - -/* - * clsLEAMDNSHost_Legacy::setHostProbeResultCallback - * - */ -bool clsLEAMDNSHost_Legacy::setHostProbeResultCallback(clsLEAMDNSHost_Legacy::MDNSHostProbeResultCallbackFn2 p_fnCallback) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - bResult = (*it).m_pHost->setProbeResultCallback([this, p_fnCallback](clsLEAMDNSHost& /*p_rHost*/, - const char* p_pcDomainName, - bool p_bProbeResult)->void - { - if (p_fnCallback) // void(clsLEAMDNSHost_Legacy* p_pMDNSResponder, const char* p_pcDomainName, bool p_bProbeResult) - { - p_fnCallback(this, p_pcDomainName, p_bProbeResult); - } - }); - } - return bResult; -} - -/* - * clsLEAMDNSHost_Legacy::setServiceProbeResultCallback - * - */ -bool clsLEAMDNSHost_Legacy::setServiceProbeResultCallback(const clsLEAMDNSHost_Legacy::hMDNSService p_hService, - clsLEAMDNSHost_Legacy::MDNSServiceProbeResultCallbackFn p_fnCallback) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - clsLEAMDNSHost::clsService* pService = 0; - bResult = ( ((pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService])) - && (pService->setProbeResultCallback([this, p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/, - const char* p_pcInstanceName, - bool p_bProbeResult)->void - { - if (p_fnCallback) // void(const char* p_pcServiceName, const hMDNSService p_hMDNSService, bool p_bProbeResult) - { - p_fnCallback(p_pcInstanceName, p_hService, p_bProbeResult); - } - }))); - } - return bResult; -} - -/* - * clsLEAMDNSHost_Legacy::setServiceProbeResultCallback - * - */ -bool clsLEAMDNSHost_Legacy::setServiceProbeResultCallback(const clsLEAMDNSHost_Legacy::hMDNSService p_hService, - clsLEAMDNSHost_Legacy::MDNSServiceProbeResultCallbackFn2 p_fnCallback) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - clsLEAMDNSHost::clsService* pService = 0; - bResult = ( ((pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService])) - && (pService->setProbeResultCallback([this, p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/, - const char* p_pcInstanceName, - bool p_bProbeResult)->void - { - if (p_fnCallback) // void((clsLEAMDNSHost_Legacy* p_pMDNSResponder, const char* p_pcServiceName, const hMDNSService p_hMDNSService, bool p_bProbeResult) - { - p_fnCallback(this, p_pcInstanceName, p_hService, p_bProbeResult); - } - }))); - } - return bResult; -} - - -/* - * - * PROCESS - * - */ - -/* - * clsLEAMDNSHost_Legacy::notifyAPChange - * - */ -bool clsLEAMDNSHost_Legacy::notifyAPChange(void) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - bResult = (*it).m_pHost->restart(); - } - return bResult; -} - -/* - * clsLEAMDNSHost_Legacy::update - * - */ -bool clsLEAMDNSHost_Legacy::update(void) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - bResult = (*it).m_pHost->update(); - } - return bResult; -} - -/* - * clsLEAMDNSHost_Legacy::announce - * - */ -bool clsLEAMDNSHost_Legacy::announce(void) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - bResult = (*it).m_pHost->announce(true, true); - } - return bResult; -} - -/* - * clsLEAMDNSHost_Legacy::enableArduino - * - */ -clsLEAMDNSHost_Legacy::hMDNSService clsLEAMDNSHost_Legacy::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; -} - -/* - * clsLEAMDNSHost_Legacy::indexDomain - * - */ -bool clsLEAMDNSHost_Legacy::indexDomain(char*& p_rpcDomain, - const char* p_pcDivider /*= "-"*/, - const char* p_pcDefaultDomain /*= 0*/) -{ - bool bResult = false; - - const char* cpcDomainName = clsLEAMDNSHost::indexDomainName(p_rpcDomain, p_pcDivider, p_pcDefaultDomain); - delete[] p_rpcDomain; - p_rpcDomain = 0; - if ( (cpcDomainName) - && ((p_rpcDomain = new char[strlen(cpcDomainName) + 1]))) - { - strcpy(p_rpcDomain, cpcDomainName); - bResult = true; - } - return bResult; -} - - -/* - * - * INTERNAL HELPERS - * - */ - -/* - * clsLEAMDNSHost_Legacy::_addServiceTxt (char*) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - const char* p_pcValue, - bool p_bDynamic) -{ - hMDNSTxt hResult = 0; - - for (stcHostInformation& hostInformation : m_HostInformations) - { - clsLEAMDNSHost::clsService* pService = 0; - clsLEAMDNSHost::clsServiceTxt* pTxt = 0; - if ( ((pService = (clsLEAMDNSHost::clsService*)hostInformation.m_HandleToPtr[p_hService])) - && ((pTxt = (p_bDynamic - ? pService->addDynamicServiceTxt(p_pcKey, p_pcValue) - : pService->addServiceTxt(p_pcKey, p_pcValue))))) - { - if (!hResult) - { - hResult = (hMDNSTxt)pTxt; - } - hostInformation.m_HandleToPtr[hResult] = pTxt; - } - } - return hResult; -} - -/* - * clsLEAMDNSHost_Legacy::_addServiceTxt (uint32_t) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint32_t p_u32Value, - bool p_bDynamic) -{ - char acValueBuffer[16]; // 32-bit max 10 digits - *acValueBuffer = 0; - sprintf(acValueBuffer, "%u", p_u32Value); - - return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); -} - -/* - * clsLEAMDNSHost_Legacy::_addServiceTxt (uint16_t) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint16_t p_u16Value, - bool p_bDynamic) -{ - char acValueBuffer[8]; // 16-bit max 5 digits - *acValueBuffer = 0; - sprintf(acValueBuffer, "%hu", p_u16Value); - - return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); -} - -/* - * clsLEAMDNSHost_Legacy::_addServiceTxt (uint8_t) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint8_t p_u8Value, - bool p_bDynamic) -{ - char acValueBuffer[8]; // 8-bit max 3 digits - *acValueBuffer = 0; - sprintf(acValueBuffer, "%hhu", p_u8Value); - - return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); -} - -/* - * clsLEAMDNSHost_Legacy::_addServiceTxt (int32_t) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int32_t p_i32Value, - bool p_bDynamic) -{ - char acValueBuffer[16]; // 32-bit max 11 digits - *acValueBuffer = 0; - sprintf(acValueBuffer, "%i", p_i32Value); - - return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); -} - -/* - * clsLEAMDNSHost_Legacy::_addServiceTxt (int16_t) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int16_t p_i16Value, - bool p_bDynamic) -{ - char acValueBuffer[8]; // 16-bit max 6 digits - *acValueBuffer = 0; - sprintf(acValueBuffer, "%hi", p_i16Value); - - return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); -} - -/* - * clsLEAMDNSHost_Legacy::_addServiceTxt (int8_t) - * - */ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int8_t p_i8Value, - bool p_bDynamic) -{ - char acValueBuffer[8]; // 8-bit max 4 digits - *acValueBuffer = 0; - sprintf(acValueBuffer, "%hhi", p_i8Value); - - return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); -} - -/* - * clsLEAMDNSHost_Legacy::_answerFlagsToAnswerType - * - */ -clsLEAMDNSHost_Legacy::AnswerType clsLEAMDNSHost_Legacy::_answerFlagsToAnswerType(clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags) const -{ - AnswerType answerType = AnswerType::Unknown; - - if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Unknown) & p_QueryAnswerTypeFlags) - { - answerType = AnswerType::Unknown; - } - else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain) & p_QueryAnswerTypeFlags) - { - answerType = AnswerType::ServiceDomain; - } - else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::HostDomain) & p_QueryAnswerTypeFlags) - { - answerType = AnswerType::HostDomainAndPort; - } - else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Port) & p_QueryAnswerTypeFlags) - { - answerType = AnswerType::HostDomainAndPort; - } - else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Txts) & p_QueryAnswerTypeFlags) - { - answerType = AnswerType::Txt; - } -#ifdef MDNS_IP4_SUPPORT - else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address) & p_QueryAnswerTypeFlags) - { - answerType = AnswerType::IP4Address; - } -#endif -#ifdef MDNS_IP6_SUPPORT - else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address) & p_QueryAnswerTypeFlags) - { - answerType = AnswerType::IP6Address; - } -#endif - return answerType; -} - -/* - * clsLEAMDNSHost_Legacy::_getAnswerAccessor - * - */ -clsLEAMDNSHost_Legacy::clsLEAMDNSHost::clsQuery::clsAnswerAccessor clsLEAMDNSHost_Legacy::_getAnswerAccessor(const uint32_t p_u32AnswerIndex) -{ - uint32_t u32AnswerIndexWithoutOffset = p_u32AnswerIndex; - for (stcHostInformation& hostInformation : m_HostInformations) - { - clsLEAMDNSHost::clsQuery* pQuery = hostInformation.m_pHost->getQuery(); - if (pQuery) - { - if (pQuery->answerCount() > u32AnswerIndexWithoutOffset) - { - return pQuery->answerAccessor(u32AnswerIndexWithoutOffset); - } - else - { - u32AnswerIndexWithoutOffset -= pQuery->answerCount(); - } - } - else - { - break; - } - } - return clsLEAMDNSHost::clsQuery::clsAnswerAccessor(0); -} - -/* - * clsLEAMDNSHost_Legacy::_getAnswerAccessor - * - */ -clsLEAMDNSHost_Legacy::clsLEAMDNSHost::clsQuery::clsAnswerAccessor clsLEAMDNSHost_Legacy::_getAnswerAccessor(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - uint32_t u32AnswerIndexWithoutOffset = p_u32AnswerIndex; - for (stcHostInformation& hostInformation : m_HostInformations) - { - clsLEAMDNSHost::clsQuery* pQuery = (clsLEAMDNSHost::clsQuery*)hostInformation.m_HandleToPtr[p_hServiceQuery]; - if (pQuery) - { - if (pQuery->answerCount() > u32AnswerIndexWithoutOffset) - { - return pQuery->answerAccessor(u32AnswerIndexWithoutOffset); - } - else - { - u32AnswerIndexWithoutOffset -= pQuery->answerCount(); - } - } - else - { - break; - } - } - return clsLEAMDNSHost::clsQuery::clsAnswerAccessor(0); -} - - -} // namespace MDNSImplementation - - -} // namespace esp8266 - - - - - - +/* + LEAmDNS2_Legacy.cpp + + +*/ + +#include "LEAmDNS2_Legacy.h" + + +namespace esp8266 +{ + +/** + LEAmDNS +*/ +namespace MDNSImplementation +{ + +/** + STRINGIZE +*/ +#ifndef STRINGIZE +#define STRINGIZE(x) #x +#endif +#ifndef STRINGIZE_VALUE_OF +#define STRINGIZE_VALUE_OF(x) STRINGIZE(x) +#endif + + +/* + clsLEAMDNSHost_Legacy::clsLEAMDNSHost_Legacy constructor + +*/ +clsLEAMDNSHost_Legacy::clsLEAMDNSHost_Legacy(void) +{ +} + +/* + clsLEAMDNSHost_Legacy::clsLEAMDNSHost_Legacy destructor + +*/ +clsLEAMDNSHost_Legacy::~clsLEAMDNSHost_Legacy(void) +{ +} + +/* + + HOST SETUP + +*/ + +/* + clsLEAMDNSHost_Legacy::begin + +*/ +bool clsLEAMDNSHost_Legacy::begin(const char* p_pcHostname) +{ + bool bResult = (((!(WIFI_STA & (WiFiMode_t)wifi_get_opmode())) + || (addHostForNetIf(p_pcHostname, netif_get_by_index(WIFI_STA)))) + && ((!(WIFI_AP & (WiFiMode_t)wifi_get_opmode())) + || (addHostForNetIf(p_pcHostname, netif_get_by_index(WIFI_AP))))); + return ((bResult) + && (0 != m_HostInformations.size())); +} + +/* + clsLEAMDNSHost_Legacy::begin (String) + +*/ +bool clsLEAMDNSHost_Legacy::begin(const String& p_strHostname) +{ + return begin(p_strHostname.c_str()); +} + +/* + clsLEAMDNSHost_Legacy::begin (Ignored Options) + +*/ +bool clsLEAMDNSHost_Legacy::begin(const char* p_pcHostname, + IPAddress /*p_IPAddress = INADDR_ANY*/, // ignored + uint32_t /*p_u32TTL = 120*/) // ignored +{ + return begin(p_pcHostname); +} + +/* + clsLEAMDNSHost_Legacy::begin (String & Ignored Options) + +*/ +bool clsLEAMDNSHost_Legacy::begin(const String& p_strHostname, + IPAddress /*p_IPAddress = INADDR_ANY*/, // ignored + uint32_t /*p_u32TTL = 120*/) // ignored +{ + return begin(p_strHostname.c_str()); +} + +/* + clsLEAMDNSHost_Legacy::close + +*/ +bool clsLEAMDNSHost_Legacy::close(void) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + if ((bResult = (*it).m_pHost->close())) + { + delete (*it).m_pHost; + (*it).m_pHost = 0; + } + } + return ((bResult) + && (m_HostInformations.clear(), true)); +} + +/* + clsLEAMDNSHost_Legacy::end + +*/ +bool clsLEAMDNSHost_Legacy::end(void) +{ + return close(); +} + +/* + clsLEAMDNSHost_Legacy::addHostForNetIf + + NEW! + +*/ +bool clsLEAMDNSHost_Legacy::addHostForNetIf(const char* p_pcHostname, + netif* p_pNetIf) +{ + clsLEAMDNSHost* pHost = 0; + + if (((pHost = new esp8266::experimental::clsLEAMDNSHost)) + && (!((pHost->begin(p_pcHostname, p_pNetIf /*, default callback*/)) + && (m_HostInformations.push_back(stcHostInformation(pHost)), true)))) + { + delete pHost; + pHost = 0; + } + return (0 != pHost); +} + +/* + clsLEAMDNSHost_Legacy::setHostname + +*/ +bool clsLEAMDNSHost_Legacy::setHostname(const char* p_pcHostname) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + bResult = (*it).m_pHost->setHostName(p_pcHostname); + } + return bResult; +} + +/* + clsLEAMDNSHost_Legacy::setHostname + +*/ +bool clsLEAMDNSHost_Legacy::setHostname(String p_strHostname) +{ + return setHostname(p_strHostname.c_str()); +} + +/* + clsLEAMDNSHost_Legacy::hostname + +*/ +const char* clsLEAMDNSHost_Legacy::hostname(void) const +{ + return (m_HostInformations.empty() + ? 0 + : m_HostInformations.front().m_pHost->hostName()); +} + +/* + clsLEAMDNSHost_Legacy::status + +*/ +bool clsLEAMDNSHost_Legacy::status(void) const +{ + bool bStatus = true; + + for (const stcHostInformation& hostInformation : m_HostInformations) + { + if (!((bStatus = hostInformation.m_pHost->probeStatus()))) + { + break; + } + } + return bStatus; +} + + +/* + + SERVICE MANAGEMENT + +*/ + +/* + clsLEAMDNSHost_Legacy::addService + +*/ +clsLEAMDNSHost_Legacy::hMDNSService clsLEAMDNSHost_Legacy::addService(const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol, + uint16_t p_u16Port) +{ + hMDNSService hResult = 0; + + for (stcHostInformation& hostInformation : m_HostInformations) + { + clsLEAMDNSHost::clsService* pService = hostInformation.m_pHost->addService(p_pcName, p_pcService, p_pcProtocol, p_u16Port /*, default callback*/); + if (pService) + { + if (!hResult) + { + // Store first service handle as result and key + hResult = (hMDNSService)pService; + } + hostInformation.m_HandleToPtr[hResult] = pService; + } + } + return hResult; +} + +/* + clsLEAMDNSHost_Legacy::addService (String) + +*/ +bool clsLEAMDNSHost_Legacy::addService(String p_strServiceName, + String p_strProtocol, + uint16_t p_u16Port) +{ + return (0 != addService(0, p_strServiceName.c_str(), p_strProtocol.c_str(), p_u16Port)); +} + +/* + clsLEAMDNSHost_Legacy::removeService (hService) + +*/ +bool clsLEAMDNSHost_Legacy::removeService(const hMDNSService p_hService) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService]; + if ((bResult = ((pService) + && ((*it).m_pHost->removeService(pService))))) + { + (*it).m_HandleToPtr.erase(p_hService); + } + } + return bResult; +} + +/* + clsLEAMDNSHost_Legacy::removeService (name) + +*/ +bool clsLEAMDNSHost_Legacy::removeService(const char* p_pcInstanceName, + const char* p_pcServiceName, + const char* p_pcProtocol) +{ + hMDNSService hService = 0; + return (((hService = (m_HostInformations.empty() + ? 0 + : (hMDNSService)m_HostInformations.front().m_pHost->findService(p_pcInstanceName, p_pcServiceName, p_pcProtocol)))) + && (removeService(hService))); +} + +/* + clsLEAMDNSHost_Legacy::setServiceName + +*/ +bool clsLEAMDNSHost_Legacy::setServiceName(const hMDNSService p_hService, + const char* p_pcInstanceName) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService]; + bResult = ((pService) + && (pService->setInstanceName(p_pcInstanceName))); + } + return bResult; +} + +/* + clsLEAMDNSHost_Legacy::setInstanceName + +*/ +void clsLEAMDNSHost_Legacy::setInstanceName(const char* p_pcInstanceName) +{ + for (stcHostInformation& hostInformation : m_HostInformations) + { + hostInformation.m_pHost->setDefaultInstanceName(p_pcInstanceName); + } +} + +/* + clsLEAMDNSHost_Legacy::setInstanceName (String) + +*/ +void clsLEAMDNSHost_Legacy::setInstanceName(const String& p_strHostname) +{ + setInstanceName(p_strHostname.c_str()); +} + +/* + clsLEAMDNSHost_Legacy::serviceName + +*/ +const char* clsLEAMDNSHost_Legacy::serviceName(const hMDNSService p_hService) const +{ + const clsLEAMDNSHost::clsService* pService = 0; + return (m_HostInformations.empty() + ? 0 + : (((pService = (const clsLEAMDNSHost::clsService*)(m_HostInformations.front().m_HandleToPtr.at(p_hService)))) + ? pService->instanceName() + : 0)); +} + +/* + clsLEAMDNSHost_Legacy::service + +*/ +const char* clsLEAMDNSHost_Legacy::service(const hMDNSService p_hService) const +{ + const clsLEAMDNSHost::clsService* pService = 0; + return (m_HostInformations.empty() + ? 0 + : (((pService = (const clsLEAMDNSHost::clsService*)m_HostInformations.front().m_HandleToPtr.at(p_hService))) + ? pService->type() + : 0)); +} + +/* + clsLEAMDNSHost_Legacy::serviceProtocol + +*/ +const char* clsLEAMDNSHost_Legacy::serviceProtocol(const hMDNSService p_hService) const +{ + const clsLEAMDNSHost::clsService* pService = 0; + return (m_HostInformations.empty() + ? 0 + : (((pService = (const clsLEAMDNSHost::clsService*)m_HostInformations.front().m_HandleToPtr.at(p_hService))) + ? pService->protocol() + : 0)); +} + +/* + clsLEAMDNSHost_Legacy::serviceStatus + +*/ +bool clsLEAMDNSHost_Legacy::serviceStatus(const hMDNSService p_hService) const +{ + const clsLEAMDNSHost::clsService* pService = 0; + return (m_HostInformations.empty() + ? false + : (((pService = (const clsLEAMDNSHost::clsService*)m_HostInformations.front().m_HandleToPtr.at(p_hService))) + ? pService->probeStatus() + : false)); +} + + +/* + + SERVICE TXT MANAGEMENT + +*/ + +/* + clsLEAMDNSHost_Legacy::addServiceTxt (char*) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + const char* p_pcValue) +{ + return _addServiceTxt(p_hService, p_pcKey, p_pcValue, false); +} + +/* + clsLEAMDNSHost_Legacy::addServiceTxt (uint32_t) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint32_t p_u32Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_u32Value, false); +} + +/* + clsLEAMDNSHost_Legacy::addServiceTxt (uint16_t) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint16_t p_u16Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_u16Value, false); +} + +/* + clsLEAMDNSHost_Legacy::addServiceTxt (uint8_t) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint8_t p_u8Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_u8Value, false); +} + +/* + clsLEAMDNSHost_Legacy::addServiceTxt (int32_t) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int32_t p_i32Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_i32Value, false); +} + +/* + clsLEAMDNSHost_Legacy::addServiceTxt (int16_t) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int16_t p_i16Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_i16Value, false); +} + +/* + clsLEAMDNSHost_Legacy::addServiceTxt (int8_t) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int8_t p_i8Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_i8Value, false); +} + +/* + clsLEAMDNSHost_Legacy::addServiceTxt (legacy) + +*/ +bool clsLEAMDNSHost_Legacy::addServiceTxt(const char* p_pcService, + const char* p_pcProtocol, + const char* p_pcKey, + const char* p_pcValue) +{ + hMDNSService hService = 0; + return (((hService = (m_HostInformations.empty() + ? 0 + : (hMDNSService)m_HostInformations.front().m_pHost->findService(0, p_pcService, p_pcProtocol)))) + && (_addServiceTxt(hService, p_pcKey, p_pcValue, false))); +} + +/* + clsLEAMDNSHost_Legacy::addServiceTxt (legacy, String) + +*/ +bool clsLEAMDNSHost_Legacy::addServiceTxt(String p_strService, + String p_strProtocol, + String p_strKey, + String p_strValue) +{ + return addServiceTxt(p_strService.c_str(), p_strProtocol.c_str(), p_strKey.c_str(), p_strValue.c_str()); +} + +/* + clsLEAMDNSHost_Legacy::removeServiceTxt (hTxt) + +*/ +bool clsLEAMDNSHost_Legacy::removeServiceTxt(const hMDNSService p_hService, + const hMDNSTxt p_hTxt) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService]; + clsLEAMDNSHost::clsServiceTxt* pTxt = (clsLEAMDNSHost::clsServiceTxt*)(*it).m_HandleToPtr[p_hTxt]; + if ((bResult = ((pService) + && (pTxt) + && (pService->removeServiceTxt(pTxt))))) + { + (*it).m_HandleToPtr.erase(p_hTxt); + } + } + return bResult; +} + +/* + clsLEAMDNSHost_Legacy::removeServiceTxt (char*) + +*/ +bool clsLEAMDNSHost_Legacy::removeServiceTxt(const hMDNSService p_hService, + const char* p_pcKey) +{ + clsLEAMDNSHost::clsService* pService = 0; + clsLEAMDNSHost::clsServiceTxt* pTxt = 0; + return (((pService = (m_HostInformations.empty() + ? 0 + : (clsLEAMDNSHost::clsService*)m_HostInformations.front().m_HandleToPtr[p_hService]))) + && ((pTxt = pService->findServiceTxt(p_pcKey))) + && (removeServiceTxt(p_hService, (const hMDNSTxt)pTxt))); +} + +/* + clsLEAMDNSHost_Legacy::removeServiceTxt (char*) + +*/ +bool clsLEAMDNSHost_Legacy::removeServiceTxt(const char* p_pcInstanceName, + const char* p_pcServiceName, + const char* p_pcProtocol, + const char* p_pcKey) +{ + hMDNSService hService = 0; + return (((hService = (m_HostInformations.empty() + ? 0 + : (hMDNSService)m_HostInformations.front().m_pHost->findService(p_pcInstanceName, p_pcServiceName, p_pcProtocol)))) + && (removeServiceTxt(hService, p_pcKey))); +} + +/* + clsLEAMDNSHost_Legacy::setDynamicServiceTxtCallback (global) + +*/ +bool clsLEAMDNSHost_Legacy::setDynamicServiceTxtCallback(MDNSDynamicServiceTxtCallbackFn p_fnCallback) +{ + bool bResult = true; + + if ((bResult = m_HostInformations.empty())) + { + // The service handles of the first host are the keys in the HostInformations.HandleToPtr map + for (const clsLEAMDNSHost::clsService* pService : m_HostInformations.front().m_pHost->services()) + { + if (!((bResult = setDynamicServiceTxtCallback((hMDNSService)pService, p_fnCallback)))) + { + break; + } + } + } + return bResult; +} + +/* + clsLEAMDNSHost_Legacy::setDynamicServiceTxtCallback (service) + +*/ +bool clsLEAMDNSHost_Legacy::setDynamicServiceTxtCallback(const hMDNSService p_hService, + MDNSDynamicServiceTxtCallbackFn p_fnCallback) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService]; + bResult = pService->setDynamicServiceTxtCallback([p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/)->void + { + if (p_fnCallback) // void(const hMDNSService p_hService) + { + p_fnCallback(p_hService); + } + }); + } + return bResult; +} + +/* + clsLEAMDNSHost_Legacy::addDynamicServiceTxt (char*) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + const char* p_pcValue) +{ + return _addServiceTxt(p_hService, p_pcKey, p_pcValue, true); +} + +/* + clsLEAMDNSHost_Legacy::addDynamicServiceTxt (uint32) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + uint32_t p_u32Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_u32Value, true); +} + +/* + clsLEAMDNSHost_Legacy::addDynamicServiceTxt (uint16) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + uint16_t p_u16Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_u16Value, true); +} + +/* + clsLEAMDNSHost_Legacy::addDynamicServiceTxt (uint8) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + uint8_t p_u8Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_u8Value, true); +} + +/* + clsLEAMDNSHost_Legacy::addDynamicServiceTxt (int32) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + int32_t p_i32Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_i32Value, true); +} + +/* + clsLEAMDNSHost_Legacy::addDynamicServiceTxt (int16) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + int16_t p_i16Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_i16Value, true); +} + +/* + clsLEAMDNSHost_Legacy::addDynamicServiceTxt (int8) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, + const char* p_pcKey, + int8_t p_i8Value) +{ + return _addServiceTxt(p_hService, p_pcKey, p_i8Value, true); +} + + +/* + + STATIC QUERY + +*/ + +/* + clsLEAMDNSHost_Legacy::queryService + + This will take p_u16Timeout millisec for every host! + +*/ +uint32_t clsLEAMDNSHost_Legacy::queryService(const char* p_pcService, + const char* p_pcProtocol, + const uint16_t p_u16Timeout /*= MDNS_QUERYSERVICES_WAIT_TIME*/) +{ + uint32_t u32Answers = 0; + + for (stcHostInformation& hostInformation : m_HostInformations) + { + u32Answers += (hostInformation.m_pHost->queryService(p_pcService, p_pcProtocol, p_u16Timeout)).size(); + } + return u32Answers; +} + +/* + clsLEAMDNSHost_Legacy::removeQuery + +*/ +bool clsLEAMDNSHost_Legacy::removeQuery(void) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + bResult = (*it).m_pHost->removeQuery(); + } + return bResult; +} + +/* + clsLEAMDNSHost_Legacy::queryService + +*/ +uint32_t clsLEAMDNSHost_Legacy::queryService(String p_strService, + String p_strProtocol) +{ + return queryService(p_strService.c_str(), p_strProtocol.c_str()); +} + +/* + clsLEAMDNSHost_Legacy::answerHostname + +*/ +const char* clsLEAMDNSHost_Legacy::answerHostname(const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_u32AnswerIndex); + return (answerAccessor.serviceDomainAvailable() + ? answerAccessor.serviceDomain() + : 0); +} + +/* + clsLEAMDNSHost_Legacy::answerIP + +*/ +IPAddress clsLEAMDNSHost_Legacy::answerIP(const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_u32AnswerIndex); + return (answerAccessor.IPv4AddressAvailable() + ? answerAccessor.IPv4Addresses()[0] + : IPAddress()); +} + +/* + clsLEAMDNSHost_Legacy::answerPort + +*/ +uint16_t clsLEAMDNSHost_Legacy::answerPort(const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_u32AnswerIndex); + return (answerAccessor.hostPortAvailable() + ? answerAccessor.hostPort() + : 0); +} + +/* + clsLEAMDNSHost_Legacy::hostname + +*/ +String clsLEAMDNSHost_Legacy::hostname(const uint32_t p_u32AnswerIndex) +{ + return String(answerHostname(p_u32AnswerIndex)); +} + +/* + clsLEAMDNSHost_Legacy::IP + +*/ +IPAddress clsLEAMDNSHost_Legacy::IP(const uint32_t p_u32AnswerIndex) +{ + return answerIP(p_u32AnswerIndex); +} + +/* + clsLEAMDNSHost_Legacy::port + +*/ +uint16_t clsLEAMDNSHost_Legacy::port(const uint32_t p_u32AnswerIndex) +{ + return answerPort(p_u32AnswerIndex); +} + + +/* + + DYNAMIC QUERY + +*/ + +/* + clsLEAMDNSHost_Legacy::installServiceQuery + +*/ +clsLEAMDNSHost_Legacy::hMDNSServiceQuery clsLEAMDNSHost_Legacy::installServiceQuery(const char* p_pcService, + const char* p_pcProtocol, + MDNSServiceQueryCallbackFn p_fnCallback) +{ + hMDNSServiceQuery hResult = 0; + + for (stcHostInformation& hostInformation : m_HostInformations) + { + clsLEAMDNSHost::clsQuery* pQuery = hostInformation.m_pHost->installServiceQuery(p_pcService, p_pcProtocol, [this, p_fnCallback](const clsLEAMDNSHost::clsQuery& /*p_Query*/, + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor & p_AnswerAccessor, + clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, // flags for the updated answer item + bool p_bSetContent)->void + { + if (p_fnCallback) // void(const stcMDNSServiceInfo& p_MDNSServiceInfo, MDNSResponder::AnswerType p_AnswerType, bool p_bSetContent) + { + p_fnCallback(stcMDNSServiceInfo(p_AnswerAccessor), _answerFlagsToAnswerType(p_QueryAnswerTypeFlags), p_bSetContent); + } + }); + if (pQuery) + { + if (!hResult) + { + // Store first query as result and key + hResult = (hMDNSServiceQuery)pQuery; + } + hostInformation.m_HandleToPtr[hResult] = pQuery; + } + } + return hResult; +} + +/* + clsLEAMDNSHost_Legacy::removeServiceQuery + +*/ +bool clsLEAMDNSHost_Legacy::removeServiceQuery(clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + if ((bResult = (*it).m_pHost->removeQuery((clsLEAMDNSHost::clsQuery*)(*it).m_HandleToPtr[p_hServiceQuery]))) + { + (*it).m_HandleToPtr.erase(p_hServiceQuery); + } + } + return bResult; +} + +/* + clsLEAMDNSHost_Legacy::answerCount + +*/ +uint32_t clsLEAMDNSHost_Legacy::answerCount(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery) +{ + uint32_t u32AnswerCount = 0; + + for (stcHostInformation& hostInformation : m_HostInformations) + { + clsLEAMDNSHost::clsQuery* pQuery = (clsLEAMDNSHost::clsQuery*)hostInformation.m_HandleToPtr[p_hServiceQuery]; + if (pQuery) + { + u32AnswerCount += pQuery->answerCount(); + } + else + { + u32AnswerCount = 0; + break; + } + } + return u32AnswerCount; +} + +/* + clsLEAMDNSHost_Legacy::answerInfo + +*/ +std::vector clsLEAMDNSHost_Legacy::answerInfo(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery) +{ + std::vector serviceInfos; + + for (stcHostInformation& hostInformation : m_HostInformations) + { + clsLEAMDNSHost::clsQuery* pQuery = (clsLEAMDNSHost::clsQuery*)hostInformation.m_HandleToPtr[p_hServiceQuery]; + if (pQuery) + { + for (clsLEAMDNSHost::clsQuery::clsAnswerAccessor& answerAccessor : pQuery->answerAccessors()) + { + serviceInfos.push_back(stcMDNSServiceInfo(answerAccessor)); + } + } + else + { + serviceInfos.clear(); + break; + } + } + return serviceInfos; +} + +/* + clsLEAMDNSHost_Legacy::answerServiceDomain + +*/ +const char* clsLEAMDNSHost_Legacy::answerServiceDomain(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); + return (answerAccessor.serviceDomainAvailable() + ? answerAccessor.serviceDomain() + : 0); +} + +/* + clsLEAMDNSHost_Legacy::hasAnswerHostDomain + +*/ +bool clsLEAMDNSHost_Legacy::hasAnswerHostDomain(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).hostDomainAvailable(); +} + +/* + clsLEAMDNSHost_Legacy::answerHostDomain + +*/ +const char* clsLEAMDNSHost_Legacy::answerHostDomain(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); + return (answerAccessor.hostDomainAvailable() + ? answerAccessor.hostDomain() + : 0); +} + +#ifdef MDNS_IP4_SUPPORT +/* + clsLEAMDNSHost_Legacy::hasAnswerIP4Address + +*/ +bool clsLEAMDNSHost_Legacy::hasAnswerIP4Address(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).IPv4AddressAvailable(); +} + +/* + clsLEAMDNSHost_Legacy::answerIP4AddressCount + +*/ +uint32_t clsLEAMDNSHost_Legacy::answerIP4AddressCount(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); + return (answerAccessor.IPv4AddressAvailable() + ? answerAccessor.IPv4Addresses().size() + : 0); +} + +/* + clsLEAMDNSHost_Legacy::answerIP4Address + +*/ +IPAddress clsLEAMDNSHost_Legacy::answerIP4Address(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); + return (answerAccessor.IPv4AddressAvailable() + ? answerAccessor.IPv4Addresses()[p_u32AddressIndex] + : IPAddress()); +} +#endif +#ifdef MDNS_IP6_SUPPORT +/* + clsLEAMDNSHost_Legacy::hasAnswerIP6Address + +*/ +bool clsLEAMDNSHost_Legacy::hasAnswerIP6Address(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).IPv6AddressAvailable(); +} + +/* + clsLEAMDNSHost_Legacy::answerIP6AddressCount + +*/ +uint32_t clsLEAMDNSHost_Legacy::answerIP6AddressCount(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); + return (answerAccessor.IPv6AddressAvailable() + ? answerAccessor.IPv6Addresses().size() + : 0); +} + +/* + clsLEAMDNSHost_Legacy::answerIP6Address + +*/ +IPAddress clsLEAMDNSHost_Legacy::answerIP6Address(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex, + const uint32_t p_u32AddressIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); + return (answerAccessor.IPv6AddressAvailable() + ? answerAccessor.IPv6Addresses()[p_u32AddressIndex] + : IPAddress()); +} +#endif + +/* + clsLEAMDNSHost_Legacy::hasAnswerPort + +*/ +bool clsLEAMDNSHost_Legacy::hasAnswerPort(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).hostPortAvailable(); +} + +/* + clsLEAMDNSHost_Legacy::answerPort + +*/ +uint16_t clsLEAMDNSHost_Legacy::answerPort(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); + return (answerAccessor.hostPortAvailable() + ? answerAccessor.hostPort() + : 0); +} + +/* + clsLEAMDNSHost_Legacy::hasAnswerTxts + +*/ +bool clsLEAMDNSHost_Legacy::hasAnswerTxts(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).txtsAvailable(); +} + +/* + clsLEAMDNSHost_Legacy::answerHostDomain + + Get the TXT items as a ';'-separated string +*/ +const char* clsLEAMDNSHost_Legacy::answerTxts(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); + return (answerAccessor.txtsAvailable() + ? answerAccessor.txts() + : 0); +} + + +/* + + HOST/SERVICE PROBE CALLBACKS + +*/ + +/* + clsLEAMDNSHost_Legacy::setHostProbeResultCallback + +*/ +bool clsLEAMDNSHost_Legacy::setHostProbeResultCallback(clsLEAMDNSHost_Legacy::MDNSHostProbeResultCallbackFn p_fnCallback) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + bResult = (*it).m_pHost->setProbeResultCallback([p_fnCallback](clsLEAMDNSHost& /*p_rHost*/, + const char* p_pcDomainName, + bool p_bProbeResult)->void + { + if (p_fnCallback) // void(const char* p_pcDomainName, bool p_bProbeResult) + { + p_fnCallback(p_pcDomainName, p_bProbeResult); + } + }); + } + return bResult; +} + +/* + clsLEAMDNSHost_Legacy::setHostProbeResultCallback + +*/ +bool clsLEAMDNSHost_Legacy::setHostProbeResultCallback(clsLEAMDNSHost_Legacy::MDNSHostProbeResultCallbackFn2 p_fnCallback) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + bResult = (*it).m_pHost->setProbeResultCallback([this, p_fnCallback](clsLEAMDNSHost& /*p_rHost*/, + const char* p_pcDomainName, + bool p_bProbeResult)->void + { + if (p_fnCallback) // void(clsLEAMDNSHost_Legacy* p_pMDNSResponder, const char* p_pcDomainName, bool p_bProbeResult) + { + p_fnCallback(this, p_pcDomainName, p_bProbeResult); + } + }); + } + return bResult; +} + +/* + clsLEAMDNSHost_Legacy::setServiceProbeResultCallback + +*/ +bool clsLEAMDNSHost_Legacy::setServiceProbeResultCallback(const clsLEAMDNSHost_Legacy::hMDNSService p_hService, + clsLEAMDNSHost_Legacy::MDNSServiceProbeResultCallbackFn p_fnCallback) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + clsLEAMDNSHost::clsService* pService = 0; + bResult = (((pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService])) + && (pService->setProbeResultCallback([this, p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/, + const char* p_pcInstanceName, + bool p_bProbeResult)->void + { + if (p_fnCallback) // void(const char* p_pcServiceName, const hMDNSService p_hMDNSService, bool p_bProbeResult) + { + p_fnCallback(p_pcInstanceName, p_hService, p_bProbeResult); + } + }))); + } + return bResult; +} + +/* + clsLEAMDNSHost_Legacy::setServiceProbeResultCallback + +*/ +bool clsLEAMDNSHost_Legacy::setServiceProbeResultCallback(const clsLEAMDNSHost_Legacy::hMDNSService p_hService, + clsLEAMDNSHost_Legacy::MDNSServiceProbeResultCallbackFn2 p_fnCallback) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + clsLEAMDNSHost::clsService* pService = 0; + bResult = (((pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService])) + && (pService->setProbeResultCallback([this, p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/, + const char* p_pcInstanceName, + bool p_bProbeResult)->void + { + if (p_fnCallback) // void((clsLEAMDNSHost_Legacy* p_pMDNSResponder, const char* p_pcServiceName, const hMDNSService p_hMDNSService, bool p_bProbeResult) + { + p_fnCallback(this, p_pcInstanceName, p_hService, p_bProbeResult); + } + }))); + } + return bResult; +} + + +/* + + PROCESS + +*/ + +/* + clsLEAMDNSHost_Legacy::notifyAPChange + +*/ +bool clsLEAMDNSHost_Legacy::notifyAPChange(void) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + bResult = (*it).m_pHost->restart(); + } + return bResult; +} + +/* + clsLEAMDNSHost_Legacy::update + +*/ +bool clsLEAMDNSHost_Legacy::update(void) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + bResult = (*it).m_pHost->update(); + } + return bResult; +} + +/* + clsLEAMDNSHost_Legacy::announce + +*/ +bool clsLEAMDNSHost_Legacy::announce(void) +{ + bool bResult = true; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) + { + bResult = (*it).m_pHost->announce(true, true); + } + return bResult; +} + +/* + clsLEAMDNSHost_Legacy::enableArduino + +*/ +clsLEAMDNSHost_Legacy::hMDNSService clsLEAMDNSHost_Legacy::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; +} + +/* + clsLEAMDNSHost_Legacy::indexDomain + +*/ +bool clsLEAMDNSHost_Legacy::indexDomain(char*& p_rpcDomain, + const char* p_pcDivider /*= "-"*/, + const char* p_pcDefaultDomain /*= 0*/) +{ + bool bResult = false; + + const char* cpcDomainName = clsLEAMDNSHost::indexDomainName(p_rpcDomain, p_pcDivider, p_pcDefaultDomain); + delete[] p_rpcDomain; + p_rpcDomain = 0; + if ((cpcDomainName) + && ((p_rpcDomain = new char[strlen(cpcDomainName) + 1]))) + { + strcpy(p_rpcDomain, cpcDomainName); + bResult = true; + } + return bResult; +} + + +/* + + INTERNAL HELPERS + +*/ + +/* + clsLEAMDNSHost_Legacy::_addServiceTxt (char*) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bDynamic) +{ + hMDNSTxt hResult = 0; + + for (stcHostInformation& hostInformation : m_HostInformations) + { + clsLEAMDNSHost::clsService* pService = 0; + clsLEAMDNSHost::clsServiceTxt* pTxt = 0; + if (((pService = (clsLEAMDNSHost::clsService*)hostInformation.m_HandleToPtr[p_hService])) + && ((pTxt = (p_bDynamic + ? pService->addDynamicServiceTxt(p_pcKey, p_pcValue) + : pService->addServiceTxt(p_pcKey, p_pcValue))))) + { + if (!hResult) + { + hResult = (hMDNSTxt)pTxt; + } + hostInformation.m_HandleToPtr[hResult] = pTxt; + } + } + return hResult; +} + +/* + clsLEAMDNSHost_Legacy::_addServiceTxt (uint32_t) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint32_t p_u32Value, + bool p_bDynamic) +{ + char acValueBuffer[16]; // 32-bit max 10 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%u", p_u32Value); + + return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); +} + +/* + clsLEAMDNSHost_Legacy::_addServiceTxt (uint16_t) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint16_t p_u16Value, + bool p_bDynamic) +{ + char acValueBuffer[8]; // 16-bit max 5 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%hu", p_u16Value); + + return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); +} + +/* + clsLEAMDNSHost_Legacy::_addServiceTxt (uint8_t) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint8_t p_u8Value, + bool p_bDynamic) +{ + char acValueBuffer[8]; // 8-bit max 3 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%hhu", p_u8Value); + + return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); +} + +/* + clsLEAMDNSHost_Legacy::_addServiceTxt (int32_t) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int32_t p_i32Value, + bool p_bDynamic) +{ + char acValueBuffer[16]; // 32-bit max 11 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%i", p_i32Value); + + return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); +} + +/* + clsLEAMDNSHost_Legacy::_addServiceTxt (int16_t) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int16_t p_i16Value, + bool p_bDynamic) +{ + char acValueBuffer[8]; // 16-bit max 6 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%hi", p_i16Value); + + return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); +} + +/* + clsLEAMDNSHost_Legacy::_addServiceTxt (int8_t) + +*/ +clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int8_t p_i8Value, + bool p_bDynamic) +{ + char acValueBuffer[8]; // 8-bit max 4 digits + *acValueBuffer = 0; + sprintf(acValueBuffer, "%hhi", p_i8Value); + + return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); +} + +/* + clsLEAMDNSHost_Legacy::_answerFlagsToAnswerType + +*/ +clsLEAMDNSHost_Legacy::AnswerType clsLEAMDNSHost_Legacy::_answerFlagsToAnswerType(clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags) const +{ + AnswerType answerType = AnswerType::Unknown; + + if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Unknown) & p_QueryAnswerTypeFlags) + { + answerType = AnswerType::Unknown; + } + else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain) & p_QueryAnswerTypeFlags) + { + answerType = AnswerType::ServiceDomain; + } + else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::HostDomain) & p_QueryAnswerTypeFlags) + { + answerType = AnswerType::HostDomainAndPort; + } + else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Port) & p_QueryAnswerTypeFlags) + { + answerType = AnswerType::HostDomainAndPort; + } + else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Txts) & p_QueryAnswerTypeFlags) + { + answerType = AnswerType::Txt; + } +#ifdef MDNS_IP4_SUPPORT + else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address) & p_QueryAnswerTypeFlags) + { + answerType = AnswerType::IP4Address; + } +#endif +#ifdef MDNS_IP6_SUPPORT + else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address) & p_QueryAnswerTypeFlags) + { + answerType = AnswerType::IP6Address; + } +#endif + return answerType; +} + +/* + clsLEAMDNSHost_Legacy::_getAnswerAccessor + +*/ +clsLEAMDNSHost_Legacy::clsLEAMDNSHost::clsQuery::clsAnswerAccessor clsLEAMDNSHost_Legacy::_getAnswerAccessor(const uint32_t p_u32AnswerIndex) +{ + uint32_t u32AnswerIndexWithoutOffset = p_u32AnswerIndex; + for (stcHostInformation& hostInformation : m_HostInformations) + { + clsLEAMDNSHost::clsQuery* pQuery = hostInformation.m_pHost->getQuery(); + if (pQuery) + { + if (pQuery->answerCount() > u32AnswerIndexWithoutOffset) + { + return pQuery->answerAccessor(u32AnswerIndexWithoutOffset); + } + else + { + u32AnswerIndexWithoutOffset -= pQuery->answerCount(); + } + } + else + { + break; + } + } + return clsLEAMDNSHost::clsQuery::clsAnswerAccessor(0); +} + +/* + clsLEAMDNSHost_Legacy::_getAnswerAccessor + +*/ +clsLEAMDNSHost_Legacy::clsLEAMDNSHost::clsQuery::clsAnswerAccessor clsLEAMDNSHost_Legacy::_getAnswerAccessor(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + uint32_t u32AnswerIndexWithoutOffset = p_u32AnswerIndex; + for (stcHostInformation& hostInformation : m_HostInformations) + { + clsLEAMDNSHost::clsQuery* pQuery = (clsLEAMDNSHost::clsQuery*)hostInformation.m_HandleToPtr[p_hServiceQuery]; + if (pQuery) + { + if (pQuery->answerCount() > u32AnswerIndexWithoutOffset) + { + return pQuery->answerAccessor(u32AnswerIndexWithoutOffset); + } + else + { + u32AnswerIndexWithoutOffset -= pQuery->answerCount(); + } + } + else + { + break; + } + } + return clsLEAMDNSHost::clsQuery::clsAnswerAccessor(0); +} + + +} // namespace MDNSImplementation + + +} // namespace esp8266 + + + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h index 3b37dd5451..be4a731216 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h @@ -1,679 +1,698 @@ -/* - * LEAmDNS2_Legacy.h - * (c) 2020, 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 'clsLEAMDNSHost_Legacy::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(clsLEAMDNSHost_Legacy* p_MDNSResponder, const char* p_pcDomain, clsLEAMDNSHost_Legacy: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(clsLEAMDNSHost_Legacy* 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 MDNSDynamicServiceTxtCallbackFn; - - // 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); - - // 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); - - // 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 = clsConsts::u16StaticQueryWaitTime); - 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_Unknown = 0, - 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; - - /** - * AnswerType (std::map compatible version) - */ - enum class AnswerType : uint32_t { - Unknown = ServiceQueryAnswerType_Unknown, - ServiceDomain = ServiceQueryAnswerType_ServiceDomain, - HostDomainAndPort = ServiceQueryAnswerType_HostDomainAndPort, - Txt = ServiceQueryAnswerType_Txts, -#ifdef MDNS_IP4_SUPPORT - IP4Address = ServiceQueryAnswerType_IP4Address, -#endif -#ifdef MDNS_IP6_SUPPORT - IP6Address = ServiceQueryAnswerType_IP6Address -#endif - }; - - /** - * stcMDNSServiceInfo - */ - struct stcMDNSServiceInfo { - stcMDNSServiceInfo(const clsLEAMDNSHost::clsQuery::clsAnswerAccessor& p_rAnswerAccessor) - : m_rAnswerAccessor(p_rAnswerAccessor) {}; - /** - * stcCompareKey - */ - struct stcCompareKey { - /* - * operator () - */ - bool operator()(char const* p_pA, char const* p_pB) const { - return (0 > strcmp(p_pA, p_pB)); - } - }; - /** - * clsKeyValueMap - */ - using clsKeyValueMap = std::map; - - protected: - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor& m_rAnswerAccessor; - clsKeyValueMap m_KeyValueMap; - - public: - /* - * serviceDomain - */ - const char* serviceDomain(void) const { - return (m_rAnswerAccessor.serviceDomainAvailable() - ? m_rAnswerAccessor.serviceDomain() - : nullptr); - } - /* - * hostDomainAvailable - */ - bool hostDomainAvailable(void) const { - return m_rAnswerAccessor.serviceDomainAvailable(); - } - /* - * hostDomain - */ - const char* hostDomain(void) const { - return (hostDomainAvailable() - ? m_rAnswerAccessor.hostDomain() - : nullptr); - } - /* - * hostPortAvailable - */ - bool hostPortAvailable(void) const { - return m_rAnswerAccessor.hostPortAvailable(); - } - /* - * hostPort - */ - uint16_t hostPort(void) const { - return (hostPortAvailable() - ? m_rAnswerAccessor.hostPort() - : 0); - } -#ifdef MDNS_IP4_SUPPORT - /* - * IP4AddressAvailable - */ - bool IP4AddressAvailable(void) const { - return m_rAnswerAccessor.IPv4AddressAvailable(); - } - /* - * IP4Addresses - */ - std::vector IP4Adresses(void) const { - return (IP4AddressAvailable() - ? m_rAnswerAccessor.IPv4Addresses() - : std::vector()); - } -#endif -#ifdef MDNS_IP6_SUPPORT - /* - * IP6AddressAvailable - */ - bool IP6AddressAvailable(void) const { - return m_rAnswerAccessor.IPv6AddressAvailable(); - } - /* - * IP6Addresses - */ - std::vector IP6Adresses(void) const { - return (IP6AddressAvailable() - ? m_rAnswerAccessor.IPv6Addresses() - : std::vector()); - } -#endif - /* - * txtAvailable - */ - bool txtAvailable(void) const { - return m_rAnswerAccessor.txtsAvailable(); - } - /* - * strKeyValue -> abc=def;hij=klm; - */ - const char* strKeyValue (void) const { - // TODO - return nullptr; - } - /* - * keyValues -> abc=def hij=klm ... - */ - const clsKeyValueMap& keyValues(void) { - if ((txtAvailable()) && - (0 == m_KeyValueMap.size())) { - for (auto kv : m_rAnswerAccessor.txtKeyValues()) - { - m_KeyValueMap.emplace(std::pair(kv.first, kv.second)); - } - //for (auto kv=m_rMDNSResponder._answerKeyValue(m_hServiceQuery, m_u32AnswerIndex); kv!=nullptr; kv=kv->m_pNext) { - // m_KeyValueMap.emplace(std::pair(kv->m_pcKey, kv->m_pcValue)); - //} - } - return m_KeyValueMap; - } - /* - * value (abc)->def - */ - const char* value(const char* p_pcKey) const { - return m_rAnswerAccessor.txtValue(p_pcKey); - } - }; - - /** - * MDNSServiceQueryCallbackFn - * - * Callback function for received answers for dynamic service queries - */ - typedef std::function MDNSServiceQueryCallbackFn; - - // 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); - // Remove a dynamic service query - bool removeServiceQuery(hMDNSServiceQuery p_hServiceQuery); - - uint32_t answerCount(const hMDNSServiceQuery p_hServiceQuery); - std::vector answerInfo(const clsLEAMDNSHost_Legacy::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); - - /** - * MDNSHostProbeResultCallbackFn/2 - * Callback function for host domain probe results - */ - typedef std::function MDNSHostProbeResultCallbackFn; - - typedef std::function MDNSHostProbeResultCallbackFn2; - - // Set a callback function for host probe results - // The callback function is called, when the probeing for the host domain - // succeededs or fails. - // In case of failure, the failed domain name should be changed. - bool setHostProbeResultCallback(MDNSHostProbeResultCallbackFn p_fnCallback); - bool setHostProbeResultCallback(MDNSHostProbeResultCallbackFn2 p_fnCallback); - - /** - * MDNSServiceProbeResultCallbackFn/2 - * Callback function for service domain probe results - */ - typedef std::function MDNSServiceProbeResultCallbackFn; - - typedef std::function MDNSServiceProbeResultCallbackFn2; - - // Set a service specific probe result callcack - bool setServiceProbeResultCallback(const hMDNSService p_hService, - MDNSServiceProbeResultCallbackFn p_fnCallback); - bool setServiceProbeResultCallback(const hMDNSService p_hService, - MDNSServiceProbeResultCallbackFn2 p_fnCallback); - - // 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: - /** - * stcHostInformation - */ - struct stcHostInformation - { - /** - * clsHandleToPtrMap - */ - using clsHandleToPtrMap = std::map; - - clsLEAMDNSHost* m_pHost; - clsHandleToPtrMap m_HandleToPtr; - - stcHostInformation(clsLEAMDNSHost* p_pHost) - : m_pHost(p_pHost) - {} - - /** - * list - */ - using list = std::list; - }; - - stcHostInformation::list m_HostInformations; - - // HELPERS - hMDNSTxt _addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - const char* p_pcValue, - bool p_bDynamic); - hMDNSTxt _addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint32_t p_u32Value, - bool p_bDynamic); - hMDNSTxt _addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint16_t p_u16Value, - bool p_bDynamic); - hMDNSTxt _addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint8_t p_u8Value, - bool p_bDynamic); - hMDNSTxt _addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int32_t p_i32Value, - bool p_bDynamic); - hMDNSTxt _addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int16_t p_i16Value, - bool p_bDynamic); - hMDNSTxt _addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int8_t p_i8Value, - bool p_bDynamic); - - AnswerType _answerFlagsToAnswerType(clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags) const; - - clsLEAMDNSHost::clsQuery::clsAnswerAccessor _getAnswerAccessor(const uint32_t p_u32AnswerIndex); - clsLEAMDNSHost::clsQuery::clsAnswerAccessor _getAnswerAccessor(const hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex); - -}; - - -} // namespace MDNSImplementation - - -} // namespace esp8266 - - -#endif // __LEAMDNS2HOST_LEGACY_H__ - - - - - - +/* + LEAmDNS2_Legacy.h + (c) 2020, 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 'clsLEAMDNSHost_Legacy::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(clsLEAMDNSHost_Legacy* p_MDNSResponder, const char* p_pcDomain, clsLEAMDNSHost_Legacy: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(clsLEAMDNSHost_Legacy* 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 MDNSDynamicServiceTxtCallbackFn; + + // 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); + + // 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); + + // 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 = clsConsts::u16StaticQueryWaitTime); + 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_Unknown = 0, + 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; + + /** + AnswerType (std::map compatible version) + */ + enum class AnswerType : uint32_t + { + Unknown = ServiceQueryAnswerType_Unknown, + ServiceDomain = ServiceQueryAnswerType_ServiceDomain, + HostDomainAndPort = ServiceQueryAnswerType_HostDomainAndPort, + Txt = ServiceQueryAnswerType_Txts, +#ifdef MDNS_IP4_SUPPORT + IP4Address = ServiceQueryAnswerType_IP4Address, +#endif +#ifdef MDNS_IP6_SUPPORT + IP6Address = ServiceQueryAnswerType_IP6Address +#endif + }; + + /** + stcMDNSServiceInfo + */ + struct stcMDNSServiceInfo + { + stcMDNSServiceInfo(const clsLEAMDNSHost::clsQuery::clsAnswerAccessor& p_rAnswerAccessor) + : m_rAnswerAccessor(p_rAnswerAccessor) {}; + /** + stcCompareKey + */ + struct stcCompareKey + { + /* + operator () + */ + bool operator()(char const* p_pA, char const* p_pB) const + { + return (0 > strcmp(p_pA, p_pB)); + } + }; + /** + clsKeyValueMap + */ + using clsKeyValueMap = std::map; + + protected: + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor& m_rAnswerAccessor; + clsKeyValueMap m_KeyValueMap; + + public: + /* + serviceDomain + */ + const char* serviceDomain(void) const + { + return (m_rAnswerAccessor.serviceDomainAvailable() + ? m_rAnswerAccessor.serviceDomain() + : nullptr); + } + /* + hostDomainAvailable + */ + bool hostDomainAvailable(void) const + { + return m_rAnswerAccessor.serviceDomainAvailable(); + } + /* + hostDomain + */ + const char* hostDomain(void) const + { + return (hostDomainAvailable() + ? m_rAnswerAccessor.hostDomain() + : nullptr); + } + /* + hostPortAvailable + */ + bool hostPortAvailable(void) const + { + return m_rAnswerAccessor.hostPortAvailable(); + } + /* + hostPort + */ + uint16_t hostPort(void) const + { + return (hostPortAvailable() + ? m_rAnswerAccessor.hostPort() + : 0); + } +#ifdef MDNS_IP4_SUPPORT + /* + IP4AddressAvailable + */ + bool IP4AddressAvailable(void) const + { + return m_rAnswerAccessor.IPv4AddressAvailable(); + } + /* + IP4Addresses + */ + std::vector IP4Adresses(void) const + { + return (IP4AddressAvailable() + ? m_rAnswerAccessor.IPv4Addresses() + : std::vector()); + } +#endif +#ifdef MDNS_IP6_SUPPORT + /* + IP6AddressAvailable + */ + bool IP6AddressAvailable(void) const + { + return m_rAnswerAccessor.IPv6AddressAvailable(); + } + /* + IP6Addresses + */ + std::vector IP6Adresses(void) const + { + return (IP6AddressAvailable() + ? m_rAnswerAccessor.IPv6Addresses() + : std::vector()); + } +#endif + /* + txtAvailable + */ + bool txtAvailable(void) const + { + return m_rAnswerAccessor.txtsAvailable(); + } + /* + strKeyValue -> abc=def;hij=klm; + */ + const char* strKeyValue(void) const + { + // TODO + return nullptr; + } + /* + keyValues -> abc=def hij=klm ... + */ + const clsKeyValueMap& keyValues(void) + { + if ((txtAvailable()) && + (0 == m_KeyValueMap.size())) + { + for (auto kv : m_rAnswerAccessor.txtKeyValues()) + { + m_KeyValueMap.emplace(std::pair(kv.first, kv.second)); + } + //for (auto kv=m_rMDNSResponder._answerKeyValue(m_hServiceQuery, m_u32AnswerIndex); kv!=nullptr; kv=kv->m_pNext) { + // m_KeyValueMap.emplace(std::pair(kv->m_pcKey, kv->m_pcValue)); + //} + } + return m_KeyValueMap; + } + /* + value (abc)->def + */ + const char* value(const char* p_pcKey) const + { + return m_rAnswerAccessor.txtValue(p_pcKey); + } + }; + + /** + MDNSServiceQueryCallbackFn + + Callback function for received answers for dynamic service queries + */ + typedef std::function MDNSServiceQueryCallbackFn; + + // 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); + // Remove a dynamic service query + bool removeServiceQuery(hMDNSServiceQuery p_hServiceQuery); + + uint32_t answerCount(const hMDNSServiceQuery p_hServiceQuery); + std::vector answerInfo(const clsLEAMDNSHost_Legacy::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); + + /** + MDNSHostProbeResultCallbackFn/2 + Callback function for host domain probe results + */ + typedef std::function MDNSHostProbeResultCallbackFn; + + typedef std::function MDNSHostProbeResultCallbackFn2; + + // Set a callback function for host probe results + // The callback function is called, when the probeing for the host domain + // succeededs or fails. + // In case of failure, the failed domain name should be changed. + bool setHostProbeResultCallback(MDNSHostProbeResultCallbackFn p_fnCallback); + bool setHostProbeResultCallback(MDNSHostProbeResultCallbackFn2 p_fnCallback); + + /** + MDNSServiceProbeResultCallbackFn/2 + Callback function for service domain probe results + */ + typedef std::function MDNSServiceProbeResultCallbackFn; + + typedef std::function MDNSServiceProbeResultCallbackFn2; + + // Set a service specific probe result callcack + bool setServiceProbeResultCallback(const hMDNSService p_hService, + MDNSServiceProbeResultCallbackFn p_fnCallback); + bool setServiceProbeResultCallback(const hMDNSService p_hService, + MDNSServiceProbeResultCallbackFn2 p_fnCallback); + + // 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: + /** + stcHostInformation + */ + struct stcHostInformation + { + /** + clsHandleToPtrMap + */ + using clsHandleToPtrMap = std::map; + + clsLEAMDNSHost* m_pHost; + clsHandleToPtrMap m_HandleToPtr; + + stcHostInformation(clsLEAMDNSHost* p_pHost) + : m_pHost(p_pHost) + {} + + /** + list + */ + using list = std::list; + }; + + stcHostInformation::list m_HostInformations; + + // HELPERS + hMDNSTxt _addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bDynamic); + hMDNSTxt _addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint32_t p_u32Value, + bool p_bDynamic); + hMDNSTxt _addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint16_t p_u16Value, + bool p_bDynamic); + hMDNSTxt _addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + uint8_t p_u8Value, + bool p_bDynamic); + hMDNSTxt _addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int32_t p_i32Value, + bool p_bDynamic); + hMDNSTxt _addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int16_t p_i16Value, + bool p_bDynamic); + hMDNSTxt _addServiceTxt(const hMDNSService p_hService, + const char* p_pcKey, + int8_t p_i8Value, + bool p_bDynamic); + + AnswerType _answerFlagsToAnswerType(clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags) const; + + clsLEAMDNSHost::clsQuery::clsAnswerAccessor _getAnswerAccessor(const uint32_t p_u32AnswerIndex); + clsLEAMDNSHost::clsQuery::clsAnswerAccessor _getAnswerAccessor(const hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex); + +}; + + +} // namespace MDNSImplementation + + +} // namespace esp8266 + + +#endif // __LEAMDNS2HOST_LEGACY_H__ + + + + + + From 1910848a029db8b72994a95dd9b3e298b34741df Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 8 May 2020 15:49:38 +0200 Subject: [PATCH 005/152] two interface example is working (also with IPv6) --- .../{src => OLDmDNS}/ESP8266mDNS_Legacy.cpp | 0 .../{src => OLDmDNS}/ESP8266mDNS_Legacy.h | 0 .../ESP8266mDNS/{src => OLDmDNS}/LEAmDNS.cpp | 0 .../ESP8266mDNS/{src => OLDmDNS}/LEAmDNS.h | 0 .../{src => OLDmDNS}/LEAmDNS_Control.cpp | 0 .../{src => OLDmDNS}/LEAmDNS_Helpers.cpp | 0 .../{src => OLDmDNS}/LEAmDNS_Priv.h | 0 .../{src => OLDmDNS}/LEAmDNS_Structs.cpp | 0 .../{src => OLDmDNS}/LEAmDNS_Transfer.cpp | 0 .../{src => OLDmDNS}/LEAmDNS_lwIPdefs.h | 0 .../LEAmDNS/TwoInterfaces/TwoInterfaces.ino | 24 +++++++++++++------ libraries/ESP8266mDNS/src/ESP8266mDNS.h | 5 ++-- 12 files changed, 20 insertions(+), 9 deletions(-) rename libraries/ESP8266mDNS/{src => OLDmDNS}/ESP8266mDNS_Legacy.cpp (100%) rename libraries/ESP8266mDNS/{src => OLDmDNS}/ESP8266mDNS_Legacy.h (100%) rename libraries/ESP8266mDNS/{src => OLDmDNS}/LEAmDNS.cpp (100%) rename libraries/ESP8266mDNS/{src => OLDmDNS}/LEAmDNS.h (100%) rename libraries/ESP8266mDNS/{src => OLDmDNS}/LEAmDNS_Control.cpp (100%) rename libraries/ESP8266mDNS/{src => OLDmDNS}/LEAmDNS_Helpers.cpp (100%) rename libraries/ESP8266mDNS/{src => OLDmDNS}/LEAmDNS_Priv.h (100%) rename libraries/ESP8266mDNS/{src => OLDmDNS}/LEAmDNS_Structs.cpp (100%) rename libraries/ESP8266mDNS/{src => OLDmDNS}/LEAmDNS_Transfer.cpp (100%) rename libraries/ESP8266mDNS/{src => OLDmDNS}/LEAmDNS_lwIPdefs.h (100%) diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp b/libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.cpp similarity index 100% rename from libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp rename to libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.cpp diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.h b/libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.h similarity index 100% rename from libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.h rename to libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.h diff --git a/libraries/ESP8266mDNS/src/LEAmDNS.cpp b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.cpp similarity index 100% rename from libraries/ESP8266mDNS/src/LEAmDNS.cpp rename to libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.cpp diff --git a/libraries/ESP8266mDNS/src/LEAmDNS.h b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.h similarity index 100% rename from libraries/ESP8266mDNS/src/LEAmDNS.h rename to libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.h diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Control.cpp similarity index 100% rename from libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp rename to libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Control.cpp diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Helpers.cpp similarity index 100% rename from libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp rename to libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Helpers.cpp diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Priv.h b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Priv.h similarity index 100% rename from libraries/ESP8266mDNS/src/LEAmDNS_Priv.h rename to libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Priv.h diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Structs.cpp similarity index 100% rename from libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp rename to libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Structs.cpp diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Transfer.cpp b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Transfer.cpp similarity index 100% rename from libraries/ESP8266mDNS/src/LEAmDNS_Transfer.cpp rename to libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Transfer.cpp diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_lwIPdefs.h b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_lwIPdefs.h similarity index 100% rename from libraries/ESP8266mDNS/src/LEAmDNS_lwIPdefs.h rename to libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_lwIPdefs.h diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/TwoInterfaces/TwoInterfaces.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/TwoInterfaces/TwoInterfaces.ino index 3f06f543b7..4bbdbd157b 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/TwoInterfaces/TwoInterfaces.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/TwoInterfaces/TwoInterfaces.ino @@ -5,9 +5,18 @@ #include #include - -clsMDNSHost mDNSHost_AP; -clsMDNSHost mDNSHost_STA; +#ifndef STASSID +#define STASSID "ssid" +#define STAPSK "12345678" +#endif + +#ifndef APSSID +#define APSSID "espap" +#define APPSK "12345678" +#endif + +clsMDNSHost mDNSHost_AP; +clsMDNSHost mDNSHost_STA; ESP8266WebServer server(80); void connectToWiFi(const char* p_pcSSID, @@ -43,7 +52,7 @@ void setup(void) { // Setup WiFi and AP WiFi.setAutoConnect(false); WiFi.mode(WIFI_AP_STA); - WiFi.softAP("ESP8266", "12345678"); + WiFi.softAP(APSSID, APPSK); Serial.print("Created AP "); Serial.println("ESP8266"); Serial.print("AP-IP address: "); @@ -62,8 +71,9 @@ void setup(void) { Serial.println("FAILED to start mDNS-AP"); } - // Connect to WiFi network, with WRONG password - connectToWiFi("AP8", "WRONG_PW", 5); + // Connect to WiFi network, with WRONG password (timeout 5 seconds) + // (good password will be given in `loop()`) + connectToWiFi(STASSID, "WRONG_PW", 5); if (mDNSHost_STA.begin("esp8266", WIFI_STA, [](clsMDNSHost & p_rMDNSHost, const char* p_pcDomainName, @@ -125,7 +135,7 @@ void loop(void) { static esp8266::polledTimeout::oneShotMs timer2(esp8266::polledTimeout::oneShotMs::alwaysExpired); if (timer2) { Serial.println("FIX PASSWORD"); - connectToWiFi("AP8", "_______"); + connectToWiFi(STASSID, STAPSK); timer2.reset(esp8266::polledTimeout::oneShotMs::neverExpires); } diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS.h b/libraries/ESP8266mDNS/src/ESP8266mDNS.h index fdcf489fb0..467367b564 100644 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS.h +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS.h @@ -42,8 +42,8 @@ */ -#include "ESP8266mDNS_Legacy.h" -#include "LEAmDNS.h" +//#include "ESP8266mDNS_Legacy.h" +//#include "LEAmDNS.h" #include "LEAmDNS2Host.h" @@ -53,6 +53,7 @@ //using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; //new //using MDNSResponder = esp8266::experimental::MDNSResponder; //new^2 not compatible using clsMDNSHost = esp8266::experimental::clsLEAMDNSHost; +using MDNSResponder = esp8266::experimental::clsLEAMDNSHost; //extern MDNSResponder MDNS; #endif From c19266751dc14c6b402f5782157e02f3d1268d5d Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 8 May 2020 18:43:47 +0200 Subject: [PATCH 006/152] restored former versions (legacy, LEAv1), clock example for new API --- .../LEAmDNS/TwoInterfaces/TwoInterfaces.ino | 2 + .../LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino | 241 ++ libraries/ESP8266mDNS/src/ESP8266mDNS.h | 18 +- .../ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp | 1523 ++++++++++ .../ESP8266mDNS/src/ESP8266mDNS_Legacy.h | 169 ++ libraries/ESP8266mDNS/src/LEAmDNS.cpp | 1382 +++++++++ libraries/ESP8266mDNS/src/LEAmDNS.h | 1464 ++++++++++ libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 1 + libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 12 +- .../ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 1 + .../ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp | 1 + .../ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 1 + .../ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 2 +- .../ESP8266mDNS/src/LEAmDNS2_Backbone.cpp | 1 + libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 1 + libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp | 2135 ++++++++++++++ libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp | 851 ++++++ libraries/ESP8266mDNS/src/LEAmDNS_Priv.h | 182 ++ libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp | 2477 +++++++++++++++++ .../ESP8266mDNS/src/LEAmDNS_Transfer.cpp | 1780 ++++++++++++ libraries/ESP8266mDNS/src/LEAmDNS_lwIPdefs.h | 44 + 21 files changed, 12274 insertions(+), 14 deletions(-) create mode 100644 libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino create mode 100644 libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp create mode 100644 libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.h create mode 100644 libraries/ESP8266mDNS/src/LEAmDNS.cpp create mode 100644 libraries/ESP8266mDNS/src/LEAmDNS.h create mode 100644 libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp create mode 100644 libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp create mode 100644 libraries/ESP8266mDNS/src/LEAmDNS_Priv.h create mode 100644 libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp create mode 100644 libraries/ESP8266mDNS/src/LEAmDNS_Transfer.cpp create mode 100644 libraries/ESP8266mDNS/src/LEAmDNS_lwIPdefs.h diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/TwoInterfaces/TwoInterfaces.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/TwoInterfaces/TwoInterfaces.ino index 4bbdbd157b..f561b867f6 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/TwoInterfaces/TwoInterfaces.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/TwoInterfaces/TwoInterfaces.ino @@ -15,6 +15,8 @@ #define APPSK "12345678" #endif +// uses API MDNSApiVersion::LEAv2 + clsMDNSHost mDNSHost_AP; clsMDNSHost mDNSHost_STA; ESP8266WebServer server(80); diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino new file mode 100644 index 0000000000..5c429226f4 --- /dev/null +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino @@ -0,0 +1,241 @@ +/* + ESP8266 mDNS responder clock + + This example demonstrates two features of the LEA clsMDNSHost: + 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 + +// uses API MDNSApiVersion::LEAv2 + +/* + Include the clsMDNSHost (the library needs to be included also) + As LEA clsMDNSHost is experimantal in the ESP8266 environment currently, the + legacy clsMDNSHost is defaulted in th include file. + There are two ways to access LEA clsMDNSHost: + 1. Prepend every declaration and call to global declarations or functions with the namespace, like: + 'LEAmDNS::clsMDNSHost::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 + +#ifndef STASSID +#define STASSID "your-ssid" +#define STAPSK "your-password" +#endif + +const char* ssid = STASSID; +const char* password = STAPSK; + +clsMDNSHost responder; // MDNS responder +bool bHostDomainConfirmed = false; // Flags the confirmation of the host domain +clsMDNSHost::clsService* hMDNSService = 0; // The handle of the clock service in the MDNS responder + +// HTTP server at port 'SERVICE_PORT' will respond to HTTP requests +ESP8266WebServer server(SERVICE_PORT); + +/* + getTimeString +*/ +const char* getTimeString(void) { + + static char acTimeString[32]; + time_t now = time(nullptr); + ctime_r(&now, acTimeString); + size_t stLength; + while (((stLength = strlen(acTimeString))) && + ('\n' == acTimeString[stLength - 1])) { + acTimeString[stLength - 1] = 0; // Remove trailing line break... + } + return acTimeString; +} + + +/* + setClock + + Set time via NTP +*/ +void setClock(void) { + configTime((TIMEZONE_OFFSET * 3600), (DST_OFFSET * 3600), "pool.ntp.org", "time.nist.gov", "time.windows.com"); + + Serial.print("Waiting for NTP time sync: "); + time_t now = time(nullptr); // Secs since 01.01.1970 (when uninitalized starts with (8 * 3600 = 28800) + while (now < 8 * 3600 * 2) { // Wait for realistic value + delay(500); + Serial.print("."); + now = time(nullptr); + } + Serial.println(""); + Serial.printf("Current time: %s\n", getTimeString()); +} + + +/* + setStationHostname +*/ +bool setStationHostname(const char* p_pcHostname) { + + if (p_pcHostname) { + WiFi.hostname(p_pcHostname); + Serial.printf("setDeviceHostname: Station hostname is set to '%s'\n", p_pcHostname); + } + return true; +} + + +/* + MDNSDynamicServiceTxtCallback + + Add a dynamic MDNS TXT item 'ct' to the clock service. + The callback function is called every time, the TXT items for the clock service + are needed. + This can be triggered by calling responder.announce(). + +*/ +void MDNSDynamicServiceTxtCallback(const clsMDNSHost::hMDNSService& p_hService) { + Serial.println("MDNSDynamicServiceTxtCallback"); + + if (hMDNSService == &p_hService) { + Serial.printf("Updating curtime TXT item to: %s\n", getTimeString()); + hMDNSService->addDynamicServiceTxt("curtime", getTimeString()); + } +} + + +/* + handleHTTPClient +*/ + +void handleHTTPRequest() { + Serial.println(""); + Serial.println("HTTP Request"); + + // Get current time + time_t now = time(nullptr);; + struct tm timeinfo; + gmtime_r(&now, &timeinfo); + + String s; + + s = "\r\nHello from "; + s += WiFi.hostname() + " at " + WiFi.localIP().toString(); + // Simple addition of the current time + s += "\r\nCurrent time is: "; + s += getTimeString(); + // done :-) + s += "\r\n\r\n"; + Serial.println("Sending 200"); + server.send(200, "text/html", s); +} + +/* + setup +*/ +void setup(void) { + Serial.begin(115200); + + // Connect to WiFi network + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + Serial.println(""); + + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + // Sync clock + setClock(); + + // Setup MDNS responder + // Init the (currently empty) host domain string with 'esp8266' + if (responder.begin("ESP8266", WIFI_STA, [](clsMDNSHost & p_rMDNSHost, + const char* p_pcDomainName, + bool p_bProbeResult)->void { + Serial.printf("mDNSHost_AP::ProbeResultCallback: '%s' is %s\n", p_pcDomainName, (p_bProbeResult ? "FREE" : "USED!")); + // Unattended added service + p_rMDNSHost.addService(0, "http", "tcp", 80); + })) { + Serial.println("mDNS-AP started"); + } else { + Serial.println("FAILED to start mDNS-AP"); + } + + // Setup HTTP server + server.on("/", handleHTTPRequest); + server.begin(); + Serial.println("HTTP server started"); +} + +/* + loop +*/ +void loop(void) { + + // Check if a request has come in + server.handleClient(); + // Allow MDNS processing + responder.update(); + + static esp8266::polledTimeout::periodicMs timeout(UPDATE_CYCLE); + if (timeout.expired()) { + + if (hMDNSService) { + // Just trigger a new MDNS announcement, this will lead to a call to + // 'MDNSDynamicServiceTxtCallback', which will update the time TXT item + responder.announce(); + } + } +} diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS.h b/libraries/ESP8266mDNS/src/ESP8266mDNS.h index 467367b564..c3713c6689 100644 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS.h +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS.h @@ -42,19 +42,19 @@ */ -//#include "ESP8266mDNS_Legacy.h" -//#include "LEAmDNS.h" -#include "LEAmDNS2Host.h" +enum class MDNSApiVersion { Legacy, LEA, LEAv2 }; +#include "ESP8266mDNS_Legacy.h" +#include "LEAmDNS.h" +#include "LEAmDNS2Host.h" +using clsMDNSHost = esp8266::experimental::clsLEAMDNSHost; #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) // Maps the implementation to use to the global namespace type -//using MDNSResponder = Legacy_MDNSResponder::MDNSResponder; //legacy -//using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; //new -//using MDNSResponder = esp8266::experimental::MDNSResponder; //new^2 not compatible -using clsMDNSHost = esp8266::experimental::clsLEAMDNSHost; -using MDNSResponder = esp8266::experimental::clsLEAMDNSHost; +//using MDNSResponder = Legacy_MDNSResponder::MDNSResponder; // Legacy +using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; // LEA +//using MDNSResponder = clsMDNSHost; // LEAv2 -//extern MDNSResponder MDNS; +extern MDNSResponder MDNS; #endif diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp b/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp new file mode 100644 index 0000000000..8791195523 --- /dev/null +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp @@ -0,0 +1,1523 @@ +/* + + ESP8266 Multicast DNS (port of CC3000 Multicast DNS library) + Version 1.1 + Copyright (c) 2013 Tony DiCola (tony@tonydicola.com) + ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com) + MDNS-SD Suport 2015 Hristo Gochkov + Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com) + + + License (MIT license): + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +// Important RFC's for reference: +// - DNS request and response: http://www.ietf.org/rfc/rfc1035.txt +// - Multicast DNS: http://www.ietf.org/rfc/rfc6762.txt +// - MDNS-SD: https://tools.ietf.org/html/rfc6763 + +#ifndef LWIP_OPEN_SRC +#define LWIP_OPEN_SRC +#endif + +#include "ESP8266mDNS.h" +#include + +#include "debug.h" + +extern "C" { +#include "osapi.h" +#include "ets_sys.h" +#include "user_interface.h" +} + +#include "WiFiUdp.h" +#include "lwip/opt.h" +#include "lwip/udp.h" +#include "lwip/inet.h" +#include "lwip/igmp.h" +#include "lwip/mem.h" +#include "include/UdpContext.h" + + + +namespace Legacy_MDNSResponder +{ + + +#ifdef DEBUG_ESP_MDNS +#define DEBUG_ESP_MDNS_ERR +#define DEBUG_ESP_MDNS_TX +#define DEBUG_ESP_MDNS_RX +#endif + +#define MDNS_NAME_REF 0xC000 + +#define MDNS_TYPE_AAAA 0x001C +#define MDNS_TYPE_A 0x0001 +#define MDNS_TYPE_PTR 0x000C +#define MDNS_TYPE_SRV 0x0021 +#define MDNS_TYPE_TXT 0x0010 + +#define MDNS_CLASS_IN 0x0001 +#define MDNS_CLASS_IN_FLUSH_CACHE 0x8001 + +#define MDNS_ANSWERS_ALL 0x0F +#define MDNS_ANSWER_PTR 0x08 +#define MDNS_ANSWER_TXT 0x04 +#define MDNS_ANSWER_SRV 0x02 +#define MDNS_ANSWER_A 0x01 + +#define _conn_read32() (((uint32_t)_conn->read() << 24) | ((uint32_t)_conn->read() << 16) | ((uint32_t)_conn->read() << 8) | _conn->read()) +#define _conn_read16() (((uint16_t)_conn->read() << 8) | _conn->read()) +#define _conn_read8() _conn->read() +#define _conn_readS(b,l) _conn->read((char*)(b),l); + +static const IPAddress MDNS_MULTICAST_ADDR(224, 0, 0, 251); +static const int MDNS_MULTICAST_TTL = 1; +static const int MDNS_PORT = 5353; + +struct MDNSService +{ + MDNSService* _next; + char _name[32]; + char _proto[4]; + uint16_t _port; + uint16_t _txtLen; // length of all txts + struct MDNSTxt * _txts; +}; + +struct MDNSTxt +{ + MDNSTxt * _next; + String _txt; +}; + +struct MDNSAnswer +{ + MDNSAnswer* next; + uint8_t ip[4]; + uint16_t port; + char *hostname; +}; + +struct MDNSQuery +{ + char _service[32]; + char _proto[4]; +}; + + +MDNSResponder::MDNSResponder() : _conn(0) +{ + _services = 0; + _instanceName = ""; + _answers = 0; + _query = 0; + _newQuery = false; + _waitingForAnswers = false; +} +MDNSResponder::~MDNSResponder() +{ + if (_query != 0) + { + os_free(_query); + _query = 0; + } + + // Clear answer list + MDNSAnswer *answer; + int numAnswers = _getNumAnswers(); + for (int n = numAnswers - 1; n >= 0; n--) + { + answer = _getAnswerFromIdx(n); + os_free(answer->hostname); + os_free(answer); + answer = 0; + } + _answers = 0; + + if (_conn) + { + _conn->unref(); + } +} + +bool MDNSResponder::begin(const char* hostname) +{ + size_t n = strlen(hostname); + if (n > 63) // max size for a single label. + { + return false; + } + + // Copy in hostname characters as lowercase + _hostName = hostname; + _hostName.toLowerCase(); + + // If instance name is not already set copy hostname to instance name + if (_instanceName.equals("")) + { + _instanceName = hostname; + } + + _gotIPHandler = WiFi.onStationModeGotIP([this](const WiFiEventStationModeGotIP & event) + { + (void) event; + _restart(); + }); + + _disconnectedHandler = WiFi.onStationModeDisconnected([this](const WiFiEventStationModeDisconnected & event) + { + (void) event; + _restart(); + }); + + return _listen(); +} + +void MDNSResponder::notifyAPChange() +{ + _restart(); +} + +void MDNSResponder::_restart() +{ + if (_conn) + { + _conn->unref(); + _conn = nullptr; + } + _listen(); +} + +bool MDNSResponder::_listen() +{ + // Open the MDNS socket if it isn't already open. + if (!_conn) + { +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.println("MDNS listening"); +#endif + + IPAddress mdns(MDNS_MULTICAST_ADDR); + + if (igmp_joingroup(IP4_ADDR_ANY4, mdns) != ERR_OK) + { + return false; + } + + _conn = new UdpContext; + _conn->ref(); + + if (!_conn->listen(IP_ADDR_ANY, MDNS_PORT)) + { + return false; + } + _conn->setMulticastTTL(MDNS_MULTICAST_TTL); + _conn->onRx(std::bind(&MDNSResponder::update, this)); + _conn->connect(mdns, MDNS_PORT); + } + return true; +} + +void MDNSResponder::update() +{ + if (!_conn || !_conn->next()) + { + return; + } + _parsePacket(); +} + + +void MDNSResponder::setInstanceName(String name) +{ + if (name.length() > 63) + { + return; + } + _instanceName = name; +} + + +bool MDNSResponder::addServiceTxt(char *name, char *proto, char *key, char *value) +{ + MDNSService* servicePtr; + + uint8_t txtLen = os_strlen(key) + os_strlen(value) + 1; // Add one for equals sign + txtLen += 1; //accounts for length byte added when building the txt responce + //Find the service + for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) + { + //Checking Service names + if (strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) + { + //found a service name match + if (servicePtr->_txtLen + txtLen > 1300) + { + return false; //max txt record size + } + MDNSTxt *newtxt = new MDNSTxt; + newtxt->_txt = String(key) + '=' + String(value); + newtxt->_next = 0; + if (servicePtr->_txts == 0) //no services have been added + { + //Adding First TXT to service + servicePtr->_txts = newtxt; + servicePtr->_txtLen += txtLen; + return true; + } + else + { + MDNSTxt * txtPtr = servicePtr->_txts; + while (txtPtr->_next != 0) + { + txtPtr = txtPtr->_next; + } + //adding another TXT to service + txtPtr->_next = newtxt; + servicePtr->_txtLen += txtLen; + return true; + } + } + } + return false; +} + +void MDNSResponder::addService(char *name, char *proto, uint16_t port) +{ + if (_getServicePort(name, proto) != 0) + { + return; + } + if (os_strlen(name) > 32 || os_strlen(proto) != 3) + { + return; //bad arguments + } + struct MDNSService *srv = (struct MDNSService*)(os_malloc(sizeof(struct MDNSService))); + os_strcpy(srv->_name, name); + os_strcpy(srv->_proto, proto); + srv->_port = port; + srv->_next = 0; + srv->_txts = 0; + srv->_txtLen = 0; + + if (_services == 0) + { + _services = srv; + } + else + { + MDNSService* servicePtr = _services; + while (servicePtr->_next != 0) + { + servicePtr = servicePtr->_next; + } + servicePtr->_next = srv; + } + +} + +int MDNSResponder::queryService(char *service, char *proto) +{ +#ifdef DEBUG_ESP_MDNS_TX + DEBUG_ESP_PORT.printf("queryService %s %s\n", service, proto); +#endif + while (_answers != 0) + { + MDNSAnswer *currAnswer = _answers; + _answers = _answers->next; + os_free(currAnswer->hostname); + os_free(currAnswer); + currAnswer = 0; + } + if (_query != 0) + { + os_free(_query); + _query = 0; + } + _query = (struct MDNSQuery*)(os_malloc(sizeof(struct MDNSQuery))); + os_strcpy(_query->_service, service); + os_strcpy(_query->_proto, proto); + _newQuery = true; + + char underscore[] = "_"; + + // build service name with _ + char serviceName[os_strlen(service) + 2]; + os_strcpy(serviceName, underscore); + os_strcat(serviceName, service); + size_t serviceNameLen = os_strlen(serviceName); + + //build proto name with _ + char protoName[5]; + os_strcpy(protoName, underscore); + os_strcat(protoName, proto); + size_t protoNameLen = 4; + + //local string + char localName[] = "local"; + size_t localNameLen = 5; + + //terminator + char terminator[] = "\0"; + + // Only supports sending one PTR query + uint8_t questionCount = 1; + + _waitingForAnswers = true; + for (int itfn = 0; itfn < 2; itfn++) + { + struct ip_info ip_info; + + wifi_get_ip_info((!itfn) ? SOFTAP_IF : STATION_IF, &ip_info); + if (!ip_info.ip.addr) + { + continue; + } + _conn->setMulticastInterface(IPAddress(ip_info.ip.addr)); + + // Write the header + _conn->flush(); + uint8_t head[12] = + { + 0x00, 0x00, //ID = 0 + 0x00, 0x00, //Flags = response + authoritative answer + 0x00, questionCount, //Question count + 0x00, 0x00, //Answer count + 0x00, 0x00, //Name server records + 0x00, 0x00 //Additional records + }; + _conn->append(reinterpret_cast(head), 12); + + // Only supports sending one PTR query + // Send the Name field (eg. "_http._tcp.local") + _conn->append(reinterpret_cast(&serviceNameLen), 1); // lenght of "_" + service + _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_" + service + _conn->append(reinterpret_cast(&protoNameLen), 1); // lenght of "_" + proto + _conn->append(reinterpret_cast(protoName), protoNameLen); // "_" + proto + _conn->append(reinterpret_cast(&localNameLen), 1); // lenght of "local" + _conn->append(reinterpret_cast(localName), localNameLen); // "local" + _conn->append(reinterpret_cast(&terminator), 1); // terminator + + //Send the type and class + uint8_t ptrAttrs[4] = + { + 0x00, 0x0c, //PTR record query + 0x00, 0x01 //Class IN + }; + _conn->append(reinterpret_cast(ptrAttrs), 4); + _conn->send(); + } + +#ifdef DEBUG_ESP_MDNS_TX + DEBUG_ESP_PORT.println("Waiting for answers.."); +#endif + delay(1000); + + _waitingForAnswers = false; + + return _getNumAnswers(); +} + +String MDNSResponder::hostname(int idx) +{ + MDNSAnswer *answer = _getAnswerFromIdx(idx); + if (answer == 0) + { + return String(); + } + return answer->hostname; +} + +IPAddress MDNSResponder::IP(int idx) +{ + MDNSAnswer *answer = _getAnswerFromIdx(idx); + if (answer == 0) + { + return IPAddress(); + } + return IPAddress(answer->ip); +} + +uint16_t MDNSResponder::port(int idx) +{ + MDNSAnswer *answer = _getAnswerFromIdx(idx); + if (answer == 0) + { + return 0; + } + return answer->port; +} + +MDNSAnswer* MDNSResponder::_getAnswerFromIdx(int idx) +{ + MDNSAnswer *answer = _answers; + while (answer != 0 && idx-- > 0) + { + answer = answer->next; + } + if (idx > 0) + { + return 0; + } + return answer; +} + +int MDNSResponder::_getNumAnswers() +{ + int numAnswers = 0; + MDNSAnswer *answer = _answers; + while (answer != 0) + { + numAnswers++; + answer = answer->next; + } + return numAnswers; +} + +MDNSTxt * MDNSResponder::_getServiceTxt(char *name, char *proto) +{ + MDNSService* servicePtr; + for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) + { + if (servicePtr->_port > 0 && strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) + { + if (servicePtr->_txts == 0) + { + return nullptr; + } + return servicePtr->_txts; + } + } + return nullptr; +} + +uint16_t MDNSResponder::_getServiceTxtLen(char *name, char *proto) +{ + MDNSService* servicePtr; + for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) + { + if (servicePtr->_port > 0 && strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) + { + if (servicePtr->_txts == 0) + { + return false; + } + return servicePtr->_txtLen; + } + } + return 0; +} + +uint16_t MDNSResponder::_getServicePort(char *name, char *proto) +{ + MDNSService* servicePtr; + for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) + { + if (servicePtr->_port > 0 && strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) + { + return servicePtr->_port; + } + } + return 0; +} + +IPAddress MDNSResponder::_getRequestMulticastInterface() +{ + struct ip_info ip_info; + bool match_ap = false; + if (wifi_get_opmode() & SOFTAP_MODE) + { + const IPAddress& remote_ip = _conn->getRemoteAddress(); + wifi_get_ip_info(SOFTAP_IF, &ip_info); + IPAddress infoIp(ip_info.ip); + IPAddress infoMask(ip_info.netmask); + if (ip_info.ip.addr && ip_addr_netcmp((const ip_addr_t*)remote_ip, (const ip_addr_t*)infoIp, ip_2_ip4((const ip_addr_t*)infoMask))) + { + match_ap = true; + } + } + if (!match_ap) + { + wifi_get_ip_info(STATION_IF, &ip_info); + } + return IPAddress(ip_info.ip.addr); +} + +void MDNSResponder::_parsePacket() +{ + int i; + char tmp; + bool serviceParsed = false; + bool protoParsed = false; + bool localParsed = false; + + char hostName[255]; + uint8_t hostNameLen; + + char serviceName[32]; + uint8_t serviceNameLen; + uint16_t servicePort = 0; + + char protoName[32]; + protoName[0] = 0; + uint8_t protoNameLen = 0; + + uint16_t packetHeader[6]; + + for (i = 0; i < 6; i++) + { + packetHeader[i] = _conn_read16(); + } + + if ((packetHeader[1] & 0x8000) != 0) // Read answers + { +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.printf("Reading answers RX: REQ, ID:%u, Q:%u, A:%u, NS:%u, ADD:%u\n", packetHeader[0], packetHeader[2], packetHeader[3], packetHeader[4], packetHeader[5]); +#endif + + if (!_waitingForAnswers) + { +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.println("Not expecting any answers right now, returning"); +#endif + _conn->flush(); + return; + } + + int numAnswers = packetHeader[3] + packetHeader[5]; + // Assume that the PTR answer always comes first and that it is always accompanied by a TXT, SRV, AAAA (optional) and A answer in the same packet. + if (numAnswers < 4) + { +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.printf("Expected a packet with 4 or more answers, got %u\n", numAnswers); +#endif + _conn->flush(); + return; + } + + uint8_t tmp8; + uint16_t answerPort = 0; + uint8_t answerIp[4] = { 0, 0, 0, 0 }; + char answerHostName[255]; + bool serviceMatch = false; + MDNSAnswer *answer; + uint8_t partsCollected = 0; + uint8_t stringsRead = 0; + + answerHostName[0] = '\0'; + + // Clear answer list + if (_newQuery) + { + int oldAnswers = _getNumAnswers(); + for (int n = oldAnswers - 1; n >= 0; n--) + { + answer = _getAnswerFromIdx(n); + os_free(answer->hostname); + os_free(answer); + answer = 0; + } + _answers = 0; + _newQuery = false; + } + + while (numAnswers--) + { + // Read name + stringsRead = 0; + size_t last_bufferpos = 0; + do + { + tmp8 = _conn_read8(); + if (tmp8 == 0x00) // End of name + { + break; + } + if (tmp8 & 0xC0) // Compressed pointer + { + uint16_t offset = ((((uint16_t)tmp8) & ~0xC0) << 8) | _conn_read8(); + if (_conn->isValidOffset(offset)) + { + if (0 == last_bufferpos) + { + last_bufferpos = _conn->tell(); + } +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.print("Compressed pointer, jumping from "); + DEBUG_ESP_PORT.print(last_bufferpos); + DEBUG_ESP_PORT.print(" to "); + DEBUG_ESP_PORT.println(offset); +#endif + _conn->seek(offset); + tmp8 = _conn_read8(); + } + else + { +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.print("Skipping malformed compressed pointer"); +#endif + tmp8 = _conn_read8(); + break; + } + } + if (stringsRead > 3) + { +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.println("failed to read the response name"); +#endif + _conn->flush(); + return; + } + _conn_readS(serviceName, tmp8); + serviceName[tmp8] = '\0'; +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.printf(" %d ", tmp8); + for (int n = 0; n < tmp8; n++) + { + DEBUG_ESP_PORT.printf("%c", serviceName[n]); + } + DEBUG_ESP_PORT.println(); +#endif + if (serviceName[0] == '_') + { + if (strcmp(&serviceName[1], _query->_service) == 0) + { + serviceMatch = true; +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.printf("found matching service: %s\n", _query->_service); +#endif + } + } + stringsRead++; + } while (true); + if (last_bufferpos > 0) + { + _conn->seek(last_bufferpos); +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.print("Compressed pointer, jumping back to "); + DEBUG_ESP_PORT.println(last_bufferpos); +#endif + } + + uint16_t answerType = _conn_read16(); // Read type + uint16_t answerClass = _conn_read16(); // Read class + uint32_t answerTtl = _conn_read32(); // Read ttl + uint16_t answerRdlength = _conn_read16(); // Read rdlength + + (void) answerClass; + (void) answerTtl; + + if (answerRdlength > 255) + { + if (answerType == MDNS_TYPE_TXT && answerRdlength < 1460) + { + while (--answerRdlength) + { + _conn->read(); + } + } + else + { +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.printf("Data len too long! %u\n", answerRdlength); +#endif + _conn->flush(); + return; + } + } + +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.printf("type: %04x rdlength: %d\n", answerType, answerRdlength); +#endif + + if (answerType == MDNS_TYPE_PTR) + { + partsCollected |= 0x01; + _conn_readS(hostName, answerRdlength); // Read rdata + if (hostName[answerRdlength - 2] & 0xc0) + { + memcpy(answerHostName, hostName + 1, answerRdlength - 3); + answerHostName[answerRdlength - 3] = '\0'; + } +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.printf("PTR %d ", answerRdlength); + for (int n = 0; n < answerRdlength; n++) + { + DEBUG_ESP_PORT.printf("%c", hostName[n]); + } + DEBUG_ESP_PORT.println(); +#endif + } + + else if (answerType == MDNS_TYPE_TXT) + { + partsCollected |= 0x02; + _conn_readS(hostName, answerRdlength); // Read rdata +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.printf("TXT %d ", answerRdlength); + for (int n = 0; n < answerRdlength; n++) + { + DEBUG_ESP_PORT.printf("%c", hostName[n]); + } + DEBUG_ESP_PORT.println(); +#endif + } + + else if (answerType == MDNS_TYPE_SRV) + { + partsCollected |= 0x04; + uint16_t answerPrio = _conn_read16(); // Read priority + uint16_t answerWeight = _conn_read16(); // Read weight + answerPort = _conn_read16(); // Read port + last_bufferpos = 0; + + (void) answerPrio; + (void) answerWeight; + + // Read hostname + tmp8 = _conn_read8(); + if (tmp8 & 0xC0) // Compressed pointer + { + uint16_t offset = ((((uint16_t)tmp8) & ~0xC0) << 8) | _conn_read8(); + if (_conn->isValidOffset(offset)) + { + last_bufferpos = _conn->tell(); +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.print("Compressed pointer, jumping from "); + DEBUG_ESP_PORT.print(last_bufferpos); + DEBUG_ESP_PORT.print(" to "); + DEBUG_ESP_PORT.println(offset); +#endif + _conn->seek(offset); + tmp8 = _conn_read8(); + } + else + { +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.print("Skipping malformed compressed pointer"); +#endif + tmp8 = _conn_read8(); + break; + } + } + _conn_readS(answerHostName, tmp8); + answerHostName[tmp8] = '\0'; +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.printf("SRV %d ", tmp8); + for (int n = 0; n < tmp8; n++) + { + DEBUG_ESP_PORT.printf("%02x ", answerHostName[n]); + } + DEBUG_ESP_PORT.printf("\n%s\n", answerHostName); +#endif + if (last_bufferpos > 0) + { + _conn->seek(last_bufferpos); + tmp8 = 2; // Size of compression octets +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.print("Compressed pointer, jumping back to "); + DEBUG_ESP_PORT.println(last_bufferpos); +#endif + } + if (answerRdlength - (6 + 1 + tmp8) > 0) // Skip any remaining rdata + { + _conn_readS(hostName, answerRdlength - (6 + 1 + tmp8)); + } + } + + else if (answerType == MDNS_TYPE_A) + { + partsCollected |= 0x08; + for (int i = 0; i < 4; i++) + { + answerIp[i] = _conn_read8(); + } + } + else + { +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.printf("Ignoring unsupported type %02x\n", tmp8); +#endif + for (int n = 0; n < answerRdlength; n++) + { + (void)_conn_read8(); + } + } + + if ((partsCollected == 0x0F) && serviceMatch) + { +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.println("All answers parsed, adding to _answers list.."); +#endif + // Add new answer to answer list + if (_answers == 0) + { + _answers = (struct MDNSAnswer*)(os_malloc(sizeof(struct MDNSAnswer))); + answer = _answers; + } + else + { + answer = _answers; + while (answer->next != 0) + { + answer = answer->next; + } + answer->next = (struct MDNSAnswer*)(os_malloc(sizeof(struct MDNSAnswer))); + answer = answer->next; + } + answer->next = 0; + answer->hostname = 0; + + // Populate new answer + answer->port = answerPort; + for (int i = 0; i < 4; i++) + { + answer->ip[i] = answerIp[i]; + } + answer->hostname = (char *)os_malloc(strlen(answerHostName) + 1); + os_strcpy(answer->hostname, answerHostName); + _conn->flush(); + return; + } + } + + _conn->flush(); + return; + } + + // PARSE REQUEST NAME + + hostNameLen = _conn_read8() % 255; + _conn_readS(hostName, hostNameLen); + hostName[hostNameLen] = '\0'; + + if (hostName[0] == '_') + { + serviceParsed = true; + memcpy(serviceName, hostName + 1, hostNameLen); + serviceNameLen = hostNameLen - 1; + hostNameLen = 0; + } + + if (hostNameLen > 0 && !_hostName.equals(hostName) && !_instanceName.equals(hostName)) + { +#ifdef DEBUG_ESP_MDNS_ERR + DEBUG_ESP_PORT.printf("ERR_NO_HOST: %s\n", hostName); + DEBUG_ESP_PORT.printf("hostname: %s\n", _hostName.c_str()); + DEBUG_ESP_PORT.printf("instance: %s\n", _instanceName.c_str()); +#endif + _conn->flush(); + return; + } + + if (!serviceParsed) + { + serviceNameLen = _conn_read8() % 255; + _conn_readS(serviceName, serviceNameLen); + serviceName[serviceNameLen] = '\0'; + + if (serviceName[0] == '_') + { + memmove(serviceName, serviceName + 1, serviceNameLen); + serviceNameLen--; + serviceParsed = true; + } + else if (serviceNameLen == 5 && strcmp("local", serviceName) == 0) + { + tmp = _conn_read8(); + if (tmp == 0) + { + serviceParsed = true; + serviceNameLen = 0; + protoParsed = true; + protoNameLen = 0; + localParsed = true; + } + else + { +#ifdef DEBUG_ESP_MDNS_ERR + DEBUG_ESP_PORT.printf("ERR_FQDN: %s\n", serviceName); +#endif + _conn->flush(); + return; + } + } + else + { +#ifdef DEBUG_ESP_MDNS_ERR + DEBUG_ESP_PORT.printf("ERR_SERVICE: %s\n", serviceName); +#endif + _conn->flush(); + return; + } + } + + if (!protoParsed) + { + protoNameLen = _conn_read8() % 255; + _conn_readS(protoName, protoNameLen); + protoName[protoNameLen] = '\0'; + if (protoNameLen == 4 && protoName[0] == '_') + { + memmove(protoName, protoName + 1, protoNameLen); + protoNameLen--; + protoParsed = true; + } + else if (strcmp("services", serviceName) == 0 && strcmp("_dns-sd", protoName) == 0) + { + _conn->flush(); + IPAddress interface = _getRequestMulticastInterface(); + _replyToTypeEnumRequest(interface); + return; + } + else + { +#ifdef DEBUG_ESP_MDNS_ERR + DEBUG_ESP_PORT.printf("ERR_PROTO: %s\n", protoName); +#endif + _conn->flush(); + return; + } + } + + if (!localParsed) + { + char localName[32]; + uint8_t localNameLen = _conn_read8() % 31; + _conn_readS(localName, localNameLen); + localName[localNameLen] = '\0'; + tmp = _conn_read8(); + if (localNameLen == 5 && strcmp("local", localName) == 0 && tmp == 0) + { + localParsed = true; + } + else + { +#ifdef DEBUG_ESP_MDNS_ERR + DEBUG_ESP_PORT.printf("ERR_FQDN: %s\n", localName); +#endif + _conn->flush(); + return; + } + } + + if (serviceNameLen > 0 && protoNameLen > 0) + { + servicePort = _getServicePort(serviceName, protoName); + if (servicePort == 0) + { +#ifdef DEBUG_ESP_MDNS_ERR + DEBUG_ESP_PORT.printf("ERR_NO_SERVICE: %s\n", serviceName); +#endif + _conn->flush(); + return; + } + } + else if (serviceNameLen > 0 || protoNameLen > 0) + { +#ifdef DEBUG_ESP_MDNS_ERR + DEBUG_ESP_PORT.printf("ERR_SERVICE_PROTO: %s\n", serviceName); +#endif + _conn->flush(); + return; + } + + // RESPOND + +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.printf("RX: REQ, ID:%u, Q:%u, A:%u, NS:%u, ADD:%u\n", packetHeader[0], packetHeader[2], packetHeader[3], packetHeader[4], packetHeader[5]); +#endif + + uint16_t currentType; + uint16_t currentClass; + + int numQuestions = packetHeader[2]; + if (numQuestions > 4) + { + numQuestions = 4; + } + uint16_t questions[4]; + int question = 0; + + while (numQuestions--) + { + currentType = _conn_read16(); + if (currentType & MDNS_NAME_REF) //new header handle it better! + { + currentType = _conn_read16(); + } + currentClass = _conn_read16(); + if (currentClass & MDNS_CLASS_IN) + { + questions[question++] = currentType; + } + + if (numQuestions > 0) + { + if (_conn_read16() != 0xC00C) //new question but for another host/service + { + _conn->flush(); + numQuestions = 0; + } + } + +#ifdef DEBUG_ESP_MDNS_RX + DEBUG_ESP_PORT.printf("REQ: "); + if (hostNameLen > 0) + { + DEBUG_ESP_PORT.printf("%s.", hostName); + } + if (serviceNameLen > 0) + { + DEBUG_ESP_PORT.printf("_%s.", serviceName); + } + if (protoNameLen > 0) + { + DEBUG_ESP_PORT.printf("_%s.", protoName); + } + DEBUG_ESP_PORT.printf("local. "); + + if (currentType == MDNS_TYPE_AAAA) + { + DEBUG_ESP_PORT.printf(" AAAA "); + } + else if (currentType == MDNS_TYPE_A) + { + DEBUG_ESP_PORT.printf(" A "); + } + else if (currentType == MDNS_TYPE_PTR) + { + DEBUG_ESP_PORT.printf(" PTR "); + } + else if (currentType == MDNS_TYPE_SRV) + { + DEBUG_ESP_PORT.printf(" SRV "); + } + else if (currentType == MDNS_TYPE_TXT) + { + DEBUG_ESP_PORT.printf(" TXT "); + } + else + { + DEBUG_ESP_PORT.printf(" 0x%04X ", currentType); + } + + if (currentClass == MDNS_CLASS_IN) + { + DEBUG_ESP_PORT.printf(" IN "); + } + else if (currentClass == MDNS_CLASS_IN_FLUSH_CACHE) + { + DEBUG_ESP_PORT.printf(" IN[F] "); + } + else + { + DEBUG_ESP_PORT.printf(" 0x%04X ", currentClass); + } + + DEBUG_ESP_PORT.printf("\n"); +#endif + } + uint8_t questionMask = 0; + uint8_t responseMask = 0; + for (i = 0; i < question; i++) + { + if (questions[i] == MDNS_TYPE_A) + { + questionMask |= 0x1; + responseMask |= 0x1; + } + else if (questions[i] == MDNS_TYPE_SRV) + { + questionMask |= 0x2; + responseMask |= 0x3; + } + else if (questions[i] == MDNS_TYPE_TXT) + { + questionMask |= 0x4; + responseMask |= 0x4; + } + else if (questions[i] == MDNS_TYPE_PTR) + { + questionMask |= 0x8; + responseMask |= 0xF; + } + } + + IPAddress interface = _getRequestMulticastInterface(); + return _replyToInstanceRequest(questionMask, responseMask, serviceName, protoName, servicePort, interface); +} + + +/** + STRINGIZE +*/ +#ifndef STRINGIZE +#define STRINGIZE(x) #x +#endif +#ifndef STRINGIZE_VALUE_OF +#define STRINGIZE_VALUE_OF(x) STRINGIZE(x) +#endif + + +void MDNSResponder::enableArduino(uint16_t port, bool auth) +{ + + addService("arduino", "tcp", port); + addServiceTxt("arduino", "tcp", "tcp_check", "no"); + addServiceTxt("arduino", "tcp", "ssh_upload", "no"); + addServiceTxt("arduino", "tcp", "board", STRINGIZE_VALUE_OF(ARDUINO_BOARD)); + addServiceTxt("arduino", "tcp", "auth_upload", (auth) ? "yes" : "no"); +} + +void MDNSResponder::_replyToTypeEnumRequest(IPAddress multicastInterface) +{ + MDNSService* servicePtr; + for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) + { + if (servicePtr->_port > 0) + { + char *service = servicePtr->_name; + char *proto = servicePtr->_proto; + //uint16_t port = servicePtr->_port; + +#ifdef DEBUG_ESP_MDNS_TX + DEBUG_ESP_PORT.printf("TX: service:%s, proto:%s\n", service, proto); +#endif + + char sdHostName[] = "_services"; + size_t sdHostNameLen = 9; + char sdServiceName[] = "_dns-sd"; + size_t sdServiceNameLen = 7; + char sdProtoName[] = "_udp"; + size_t sdProtoNameLen = 4; + + char underscore[] = "_"; + + // build service name with _ + char serviceName[os_strlen(service) + 2]; + os_strcpy(serviceName, underscore); + os_strcat(serviceName, service); + size_t serviceNameLen = os_strlen(serviceName); + + //build proto name with _ + char protoName[5]; + os_strcpy(protoName, underscore); + os_strcat(protoName, proto); + size_t protoNameLen = 4; + + //local string + char localName[] = "local"; + size_t localNameLen = 5; + + //terminator + char terminator[] = "\0"; + + //Write the header + _conn->flush(); + uint8_t head[12] = + { + 0x00, 0x00, //ID = 0 + 0x84, 0x00, //Flags = response + authoritative answer + 0x00, 0x00, //Question count + 0x00, 0x01, //Answer count + 0x00, 0x00, //Name server records + 0x00, 0x00, //Additional records + }; + _conn->append(reinterpret_cast(head), 12); + + // Send the Name field (ie. "_services._dns-sd._udp.local") + _conn->append(reinterpret_cast(&sdHostNameLen), 1); // length of "_services" + _conn->append(reinterpret_cast(sdHostName), sdHostNameLen); // "_services" + _conn->append(reinterpret_cast(&sdServiceNameLen), 1); // length of "_dns-sd" + _conn->append(reinterpret_cast(sdServiceName), sdServiceNameLen);// "_dns-sd" + _conn->append(reinterpret_cast(&sdProtoNameLen), 1); // length of "_udp" + _conn->append(reinterpret_cast(sdProtoName), sdProtoNameLen); // "_udp" + _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" + _conn->append(reinterpret_cast(localName), localNameLen); // "local" + _conn->append(reinterpret_cast(&terminator), 1); // terminator + + //Send the type, class, ttl and rdata length + uint8_t ptrDataLen = serviceNameLen + protoNameLen + localNameLen + 4; // 4 is three label sizes and the terminator + uint8_t ptrAttrs[10] = + { + 0x00, 0x0c, //PTR record query + 0x00, 0x01, //Class IN + 0x00, 0x00, 0x11, 0x94, //TTL 4500 + 0x00, ptrDataLen, //RData length + }; + _conn->append(reinterpret_cast(ptrAttrs), 10); + + //Send the RData (ie. "_http._tcp.local") + _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" + _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" + _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" + _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" + _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" + _conn->append(reinterpret_cast(localName), localNameLen); // "local" + _conn->append(reinterpret_cast(&terminator), 1); // terminator + + _conn->setMulticastInterface(multicastInterface); + _conn->send(); + } + } +} + +void MDNSResponder::_replyToInstanceRequest(uint8_t questionMask, uint8_t responseMask, char * service, char *proto, uint16_t port, IPAddress multicastInterface) +{ + int i; + if (questionMask == 0) + { + return; + } + if (responseMask == 0) + { + return; + } + +#ifdef DEBUG_ESP_MDNS_TX + DEBUG_ESP_PORT.printf("TX: qmask:%01X, rmask:%01X, service:%s, proto:%s, port:%u\n", questionMask, responseMask, service, proto, port); +#endif + + + String instanceName = _instanceName; + size_t instanceNameLen = instanceName.length(); + + String hostName = _hostName; + size_t hostNameLen = hostName.length(); + + char underscore[] = "_"; + + // build service name with _ + char serviceName[os_strlen(service) + 2]; + os_strcpy(serviceName, underscore); + os_strcat(serviceName, service); + size_t serviceNameLen = os_strlen(serviceName); + + //build proto name with _ + char protoName[5]; + os_strcpy(protoName, underscore); + os_strcat(protoName, proto); + size_t protoNameLen = 4; + + //local string + char localName[] = "local"; + size_t localNameLen = 5; + + //terminator + char terminator[] = "\0"; + + uint8_t answerMask = responseMask & questionMask; + uint8_t answerCount = 0; + uint8_t additionalMask = responseMask & ~questionMask; + uint8_t additionalCount = 0; + for (i = 0; i < 4; i++) + { + if (answerMask & (1 << i)) + { + answerCount++; + } + if (additionalMask & (1 << i)) + { + additionalCount++; + } + } + + + //Write the header + _conn->flush(); + uint8_t head[12] = + { + 0x00, 0x00, //ID = 0 + 0x84, 0x00, //Flags = response + authoritative answer + 0x00, 0x00, //Question count + 0x00, answerCount, //Answer count + 0x00, 0x00, //Name server records + 0x00, additionalCount, //Additional records + }; + _conn->append(reinterpret_cast(head), 12); + + for (int responseSection = 0; responseSection < 2; ++responseSection) + { + + // PTR Response + if ((responseSection == 0 ? answerMask : additionalMask) & 0x8) + { + // Send the Name field (ie. "_http._tcp.local") + _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" + _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" + _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" + _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" + _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" + _conn->append(reinterpret_cast(localName), localNameLen); // "local" + _conn->append(reinterpret_cast(&terminator), 1); // terminator + + //Send the type, class, ttl and rdata length + uint8_t ptrDataLen = instanceNameLen + serviceNameLen + protoNameLen + localNameLen + 5; // 5 is four label sizes and the terminator + uint8_t ptrAttrs[10] = + { + 0x00, 0x0c, //PTR record query + 0x00, 0x01, //Class IN + 0x00, 0x00, 0x00, 0x78, //TTL 120 + 0x00, ptrDataLen, //RData length + }; + _conn->append(reinterpret_cast(ptrAttrs), 10); + + //Send the RData (ie. "My IOT device._http._tcp.local") + _conn->append(reinterpret_cast(&instanceNameLen), 1); // length of "My IOT device" + _conn->append(reinterpret_cast(instanceName.c_str()), instanceNameLen);// "My IOT device" + _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" + _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" + _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" + _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" + _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" + _conn->append(reinterpret_cast(localName), localNameLen); // "local" + _conn->append(reinterpret_cast(&terminator), 1); // terminator + } + + //TXT Responce + if ((responseSection == 0 ? answerMask : additionalMask) & 0x4) + { + //Send the name field (ie. "My IOT device._http._tcp.local") + _conn->append(reinterpret_cast(&instanceNameLen), 1); // length of "My IOT device" + _conn->append(reinterpret_cast(instanceName.c_str()), instanceNameLen);// "My IOT device" + _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" + _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" + _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" + _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" + _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" + _conn->append(reinterpret_cast(localName), localNameLen); // "local" + _conn->append(reinterpret_cast(&terminator), 1); // terminator + + //Send the type, class, ttl and rdata length + uint8_t txtDataLen = _getServiceTxtLen(service, proto); + uint8_t txtAttrs[10] = + { + 0x00, 0x10, //TXT record query + 0x80, 0x01, //Class IN, with cache flush + 0x00, 0x00, 0x11, 0x94, //TTL 4500 + 0x00, txtDataLen, //RData length + }; + _conn->append(reinterpret_cast(txtAttrs), 10); + + //Send the RData + MDNSTxt * txtPtr = _getServiceTxt(service, proto); + while (txtPtr != 0) + { + uint8_t txtLen = txtPtr->_txt.length(); + _conn->append(reinterpret_cast(&txtLen), 1); // length of txt + _conn->append(reinterpret_cast(txtPtr->_txt.c_str()), txtLen);// the txt + txtPtr = txtPtr->_next; + } + } + + + //SRV Responce + if ((responseSection == 0 ? answerMask : additionalMask) & 0x2) + { + //Send the name field (ie. "My IOT device._http._tcp.local") + _conn->append(reinterpret_cast(&instanceNameLen), 1); // length of "My IOT device" + _conn->append(reinterpret_cast(instanceName.c_str()), instanceNameLen);// "My IOT device" + _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" + _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" + _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" + _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" + _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" + _conn->append(reinterpret_cast(localName), localNameLen); // "local" + _conn->append(reinterpret_cast(&terminator), 1); // terminator + + //Send the type, class, ttl, rdata length, priority and weight + uint8_t srvDataSize = hostNameLen + localNameLen + 3; // 3 is 2 lable size bytes and the terminator + srvDataSize += 6; // Size of Priority, weight and port + uint8_t srvAttrs[10] = + { + 0x00, 0x21, //Type SRV + 0x80, 0x01, //Class IN, with cache flush + 0x00, 0x00, 0x00, 0x78, //TTL 120 + 0x00, srvDataSize, //RData length + }; + _conn->append(reinterpret_cast(srvAttrs), 10); + + //Send the RData Priority weight and port + uint8_t srvRData[6] = + { + 0x00, 0x00, //Priority 0 + 0x00, 0x00, //Weight 0 + (uint8_t)((port >> 8) & 0xFF), (uint8_t)(port & 0xFF) + }; + _conn->append(reinterpret_cast(srvRData), 6); + //Send the RData (ie. "esp8266.local") + _conn->append(reinterpret_cast(&hostNameLen), 1); // length of "esp8266" + _conn->append(reinterpret_cast(hostName.c_str()), hostNameLen);// "esp8266" + _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" + _conn->append(reinterpret_cast(localName), localNameLen); // "local" + _conn->append(reinterpret_cast(&terminator), 1); // terminator + + } + + // A Response + if ((responseSection == 0 ? answerMask : additionalMask) & 0x1) + { + //Send the RData (ie. "esp8266.local") + _conn->append(reinterpret_cast(&hostNameLen), 1); // length of "esp8266" + _conn->append(reinterpret_cast(hostName.c_str()), hostNameLen);// "esp8266" + _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" + _conn->append(reinterpret_cast(localName), localNameLen); // "local" + _conn->append(reinterpret_cast(&terminator), 1); // terminator + + uint8_t aaaAttrs[10] = + { + 0x00, 0x01, //TYPE A + 0x80, 0x01, //Class IN, with cache flush + 0x00, 0x00, 0x00, 0x78, //TTL 120 + 0x00, 0x04, //DATA LEN + }; + _conn->append(reinterpret_cast(aaaAttrs), 10); + + // Send RData + uint32_t ip = multicastInterface; + uint8_t aaaRData[4] = + { + (uint8_t)(ip & 0xFF), //IP first octet + (uint8_t)((ip >> 8) & 0xFF), //IP second octet + (uint8_t)((ip >> 16) & 0xFF), //IP third octet + (uint8_t)((ip >> 24) & 0xFF) //IP fourth octet + }; + _conn->append(reinterpret_cast(aaaRData), 4); + } + } + + _conn->setMulticastInterface(multicastInterface); + _conn->send(); +} + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) +MDNSResponder MDNS; +#endif + +} // namespace Legacy_MDNSResponder + + + + diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.h b/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.h new file mode 100644 index 0000000000..0a66a7fd32 --- /dev/null +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.h @@ -0,0 +1,169 @@ +/* + ESP8266 Multicast DNS (port of CC3000 Multicast DNS library) + Version 1.1 + Copyright (c) 2013 Tony DiCola (tony@tonydicola.com) + ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com) + Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com) + + This is a simple implementation of multicast DNS query support for an Arduino + running on ESP8266 chip. Only support for resolving address queries is currently + implemented. + + Requirements: + - ESP8266WiFi library + + Usage: + - Include the ESP8266 Multicast DNS library in the sketch. + - Call the begin method in the sketch's setup and provide a domain name (without + the '.local' suffix, i.e. just provide 'foo' to resolve 'foo.local'), and the + Adafruit CC3000 class instance. Optionally provide a time to live (in seconds) + for the DNS record--the default is 1 hour. + - Call the update method in each iteration of the sketch's loop function. + + License (MIT license): + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ +#ifndef ESP8266MDNS_LEGACY_H +#define ESP8266MDNS_LEGACY_H + +#include "ESP8266WiFi.h" +#include "WiFiUdp.h" + +//this should be defined at build time +#ifndef ARDUINO_BOARD +#define ARDUINO_BOARD "generic" +#endif + +class UdpContext; + + +namespace Legacy_MDNSResponder +{ + + +struct MDNSService; +struct MDNSTxt; +struct MDNSAnswer; + +class MDNSResponder +{ +public: + + static constexpr auto ApiVersion = MDNSApiVersion::Legacy; + + MDNSResponder(); + ~MDNSResponder(); + bool begin(const char* hostName); + bool begin(const String& hostName) + { + return begin(hostName.c_str()); + } + //for compatibility + bool begin(const char* hostName, IPAddress ip, uint32_t ttl = 120) + { + (void) ip; + (void) ttl; + return begin(hostName); + } + bool begin(const String& hostName, IPAddress ip, uint32_t ttl = 120) + { + return begin(hostName.c_str(), ip, ttl); + } + /* Application should call this whenever AP is configured/disabled */ + void notifyAPChange(); + void update(); + + void addService(char *service, char *proto, uint16_t port); + void addService(const char *service, const char *proto, uint16_t port) + { + addService((char *)service, (char *)proto, port); + } + void addService(const String& service, const String& proto, uint16_t port) + { + addService(service.c_str(), proto.c_str(), port); + } + + bool addServiceTxt(char *name, char *proto, char * key, char * value); + bool addServiceTxt(const char *name, const char *proto, const char *key, const char * value) + { + return addServiceTxt((char *)name, (char *)proto, (char *)key, (char *)value); + } + bool addServiceTxt(const String& name, const String& proto, const String& key, const String& value) + { + return addServiceTxt(name.c_str(), proto.c_str(), key.c_str(), value.c_str()); + } + + int queryService(char *service, char *proto); + int queryService(const char *service, const char *proto) + { + return queryService((char *)service, (char *)proto); + } + int queryService(const String& service, const String& proto) + { + return queryService(service.c_str(), proto.c_str()); + } + String hostname(int idx); + IPAddress IP(int idx); + uint16_t port(int idx); + + void enableArduino(uint16_t port, bool auth = false); + + void setInstanceName(String name); + void setInstanceName(const char * name) + { + setInstanceName(String(name)); + } + void setInstanceName(char * name) + { + setInstanceName(String(name)); + } + +private: + struct MDNSService * _services; + UdpContext* _conn; + String _hostName; + String _instanceName; + struct MDNSAnswer * _answers; + struct MDNSQuery * _query; + bool _newQuery; + bool _waitingForAnswers; + WiFiEventHandler _disconnectedHandler; + WiFiEventHandler _gotIPHandler; + + + uint16_t _getServicePort(char *service, char *proto); + MDNSTxt * _getServiceTxt(char *name, char *proto); + uint16_t _getServiceTxtLen(char *name, char *proto); + IPAddress _getRequestMulticastInterface(); + void _parsePacket(); + void _replyToTypeEnumRequest(IPAddress multicastInterface); + void _replyToInstanceRequest(uint8_t questionMask, uint8_t responseMask, char * service, char *proto, uint16_t port, IPAddress multicastInterface); + MDNSAnswer* _getAnswerFromIdx(int idx); + int _getNumAnswers(); + bool _listen(); + void _restart(); +}; + +} // namespace Legacy_MDNSResponder + +#endif //ESP8266MDNS_H + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS.cpp b/libraries/ESP8266mDNS/src/LEAmDNS.cpp new file mode 100644 index 0000000000..9e784bfe97 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS.cpp @@ -0,0 +1,1382 @@ +/* + 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 +#include + +#include "ESP8266mDNS.h" +#include "LEAmDNS_Priv.h" + + +namespace esp8266 +{ + +/* + LEAmDNS +*/ +namespace MDNSImplementation +{ + +/** + 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), +#ifdef ENABLE_ESP_MDNS_RESPONDER_PASSIV_MODE + m_bPassivModeEnabled(true), +#else + m_bPassivModeEnabled(false), +#endif + m_netif(nullptr) +{ +} + +/* + 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, const IPAddress& p_IPAddress, uint32_t p_u32TTL) +{ + + (void)p_u32TTL; // ignored + bool bResult = false; + + if (0 == m_pUDPContext) + { + if (_setHostname(p_pcHostname)) + { + + //// select interface + + m_netif = nullptr; + IPAddress ipAddress = p_IPAddress; + + if (!ipAddress.isSet()) + { + + IPAddress sta = WiFi.localIP(); + IPAddress ap = WiFi.softAPIP(); + + if (sta.isSet()) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] STA interface selected\n"))); + ipAddress = sta; + } + else if (ap.isSet()) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] AP interface selected\n"))); + ipAddress = ap; + } + else + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] standard interfaces are not up, please specify one in ::begin()\n"))); + return false; + } + + // continue to ensure interface is UP + } + + // check existence of this IP address in the interface list + bool found = false; + m_netif = nullptr; + for (auto a : addrList) + if (ipAddress == a.addr()) + { + if (a.ifUp()) + { + found = true; + m_netif = a.interface(); + break; + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] found interface for IP '%s' but it is not UP\n"), ipAddress.toString().c_str());); + } + if (!found) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] interface defined by IP '%s' not found\n"), ipAddress.toString().c_str());); + return false; + } + + //// done selecting the interface + + if (m_netif->num == STATION_IF) + { + + m_GotIPHandler = WiFi.onStationModeGotIP([this](const WiFiEventStationModeGotIP & pEvent) + { + (void) pEvent; + // Ensure that _restart() runs in USER context + schedule_function([this]() + { + MDNSResponder::_restart(); + }); + }); + + m_DisconnectedHandler = WiFi.onStationModeDisconnected([this](const WiFiEventStationModeDisconnected & pEvent) + { + (void) pEvent; + // Ensure that _restart() runs in USER context + schedule_function([this]() + { + MDNSResponder::_restart(); + }); + }); + } + + bResult = _restart(); + } + DEBUG_EX_ERR(if (!bResult) + { + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] begin: FAILED for '%s'!\n"), (p_pcHostname ? : "-")); + }); + } + else + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] begin: Ignoring multiple calls to begin (Ignored host domain: '%s')!\n"), (p_pcHostname ? : "-"));); + } + return bResult; +} + +/* + MDNSResponder::close + + Ends the MDNS responder. + Announced services are unannounced (by multicasting a goodbye message) + +*/ +bool MDNSResponder::close(void) +{ + bool bResult = false; + + if (0 != m_pUDPContext) + { + m_GotIPHandler.reset(); // reset WiFi event callbacks. + m_DisconnectedHandler.reset(); + + _announce(false, true); + _resetProbeStatus(false); // Stop probing + _releaseServiceQueries(); + _releaseUDPContext(); + _releaseHostname(); + + bResult = true; + } + else + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] close: Ignoring call to close!\n"));); + } + return bResult; +} + +/* + MDNSResponder::end + + Ends the MDNS responder. + for compatibility with esp32 + +*/ + +bool MDNSResponder::end(void) +{ + return close(); +} + +/* + 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("[MDNSResponder] setHostname: FAILED for '%s'!\n"), (p_pcHostname ? : "-")); + }); + return bResult; +} + +/* + MDNSResponder::setHostname (LEGACY) +*/ +bool MDNSResponder::setHostname(const 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_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] addService: %s to add '%s.%s.%s'!\n"), (hResult ? "Succeeded" : "FAILED"), (p_pcName ? : "-"), p_pcService, p_pcProtocol);); + DEBUG_EX_ERR(if (!hResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[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("[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(const String& p_strService, + const 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("[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("[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("[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("[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(const String& p_strService, + const String& p_strProtocol, + const String& p_strKey, + const 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::MDNSDynamicServiceTxtCallbackFunc p_fnCallback) +{ + + m_fnServiceTxtCallback = p_fnCallback; + + 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::MDNSDynamicServiceTxtCallbackFunc p_fnCallback) +{ + + bool bResult = false; + + stcMDNSService* pService = _findService(p_hService); + if (pService) + { + pService->m_fnTxtCallback = p_fnCallback; + + bResult = true; + } + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[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("[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("[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*/) +{ + if (0 == m_pUDPContext) + { + // safeguard against misuse + return 0; + } + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[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("[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("[MDNSResponder] queryService: INVALID input data!\n"));); + } + 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(const String& p_strService, + const 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::MDNSServiceQueryCallbackFunc p_fnCallback) +{ + 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_bLegacyQuery = false; + + if (_sendMDNSServiceQuery(*pServiceQuery)) + { + pServiceQuery->m_u8SentCount = 1; + pServiceQuery->m_ResendTimeout.reset(MDNS_DYNAMIC_QUERY_RESEND_DELAY); + + hResult = (hMDNSServiceQuery)pServiceQuery; + } + else + { + _removeServiceQuery(pServiceQuery); + } + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] installServiceQuery: %s for '%s.%s'!\n\n"), (hResult ? "Succeeded" : "FAILED"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); + DEBUG_EX_ERR(if (!hResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] installServiceQuery: FAILED for '%s.%s'!\n\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("[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); +} + +std::vector MDNSResponder::answerInfo(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery) +{ + std::vector tempVector; + for (uint32_t i = 0; i < answerCount(p_hServiceQuery); i++) + { + tempVector.emplace_back(*this, p_hServiceQuery, i); + } + return tempVector; +} + +/* + 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::setHostProbeResultCallback(MDNSResponder::MDNSHostProbeFn p_fnCallback) +{ + + m_HostProbeInformation.m_fnHostProbeResultCallback = p_fnCallback; + + return true; +} + +bool MDNSResponder::setHostProbeResultCallback(MDNSHostProbeFn1 pfn) +{ + using namespace std::placeholders; + return setHostProbeResultCallback([this, pfn](const char* p_pcDomainName, bool p_bProbeResult) + { + pfn(*this, p_pcDomainName, p_bProbeResult); + }); +} + +/* + 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::MDNSServiceProbeFn p_fnCallback) +{ + + bool bResult = false; + + stcMDNSService* pService = _findService(p_hService); + if (pService) + { + pService->m_ProbeInformation.m_fnServiceProbeResultCallback = p_fnCallback; + + bResult = true; + } + return bResult; +} + +bool MDNSResponder::setServiceProbeResultCallback(const MDNSResponder::hMDNSService p_hService, + MDNSResponder::MDNSServiceProbeFn1 p_fnCallback) +{ + using namespace std::placeholders; + return setServiceProbeResultCallback(p_hService, [this, p_fnCallback](const char* p_pcServiceName, const hMDNSService p_hMDNSService, bool p_bProbeResult) + { + p_fnCallback(*this, p_pcServiceName, p_hMDNSService, p_bProbeResult); + }); +} + + +/* + 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) +{ + + if (m_bPassivModeEnabled) + { + m_bPassivModeEnabled = false; + } + 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(true, true)); +} + +/* + 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; +} + + +} //namespace MDNSImplementation + +} //namespace esp8266 + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS.h b/libraries/ESP8266mDNS/src/LEAmDNS.h new file mode 100644 index 0000000000..e10080adf3 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS.h @@ -0,0 +1,1464 @@ +/* + 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 +#include +#include + + +#include "ESP8266WiFi.h" + + +namespace esp8266 +{ + +/** + LEAmDNS +*/ +namespace MDNSImplementation +{ + +//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 */ + + static constexpr auto ApiVersion = MDNSApiVersion::LEA; + + 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, ...) + // if interfaceAddress is not specified, default interface is STA, or AP when STA is not set + bool begin(const char* p_pcHostname, const IPAddress& p_IPAddress = INADDR_ANY, uint32_t p_u32TTL = 120 /*ignored*/); + bool begin(const String& p_strHostname, const IPAddress& p_IPAddress = INADDR_ANY, uint32_t p_u32TTL = 120 /*ignored*/) + { + return begin(p_strHostname.c_str(), p_IPAddress, p_u32TTL); + } + + // Finish MDNS processing + bool close(void); + // for esp32 compatability + bool end(void); + // Change hostname (probing is restarted) + bool setHostname(const char* p_pcHostname); + // for compatibility... + bool setHostname(const String& p_strHostname); + + bool isRunning(void) + { + return (m_pUDPContext != 0); + } + + /** + 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(const String& p_strServiceName, + const 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); + //for compatibility + //Warning: this has the side effect of changing the hostname. + //TODO: implement instancename different from hostname + void setInstanceName(const char* p_pcHostname) + { + setHostname(p_pcHostname); + } + // for esp32 compatibilty + void setInstanceName(const String& s_pcHostname) + { + setInstanceName(s_pcHostname.c_str()); + } + + /** + 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(const String& p_strService, + const String& p_strProtocol, + const String& p_strKey, + const String& p_strValue); + + /** + MDNSDynamicServiceTxtCallbackFn + Callback function for dynamic MDNS TXT items + */ + + typedef std::function MDNSDynamicServiceTxtCallbackFunc; + + // 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(MDNSDynamicServiceTxtCallbackFunc p_fnCallback); + // 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, + MDNSDynamicServiceTxtCallbackFunc p_fnCallback); + + // 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(const String& p_strService, + const 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; + + enum class AnswerType : uint32_t + { + Unknown = 0, + ServiceDomain = ServiceQueryAnswerType_ServiceDomain, + HostDomainAndPort = ServiceQueryAnswerType_HostDomainAndPort, + Txt = ServiceQueryAnswerType_Txts, +#ifdef MDNS_IP4_SUPPORT + IP4Address = ServiceQueryAnswerType_IP4Address, +#endif +#ifdef MDNS_IP6_SUPPORT + IP6Address = ServiceQueryAnswerType_IP6Address, +#endif + }; + + /** + MDNSServiceQueryCallbackFn + Callback function for received answers for dynamic service queries + */ + struct MDNSServiceInfo; // forward declaration + typedef std::function MDNSServiceQueryCallbackFunc; + + // 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, + MDNSServiceQueryCallbackFunc p_fnCallback); + // Remove a dynamic service query + bool removeServiceQuery(hMDNSServiceQuery p_hServiceQuery); + + uint32_t answerCount(const hMDNSServiceQuery p_hServiceQuery); + std::vector answerInfo(const MDNSResponder::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 std::function MDNSHostProbeFn; + + typedef std::function MDNSHostProbeFn1; + + typedef std::function MDNSServiceProbeFn; + + typedef std::function MDNSServiceProbeFn1; + + // Set a global callback function for host and service probe results + // The callback function is called, when the probing for the host domain + // (or a service domain, which hasn't got a service specific callback) + // Succeeds or fails. + // In case of failure, the failed domain name should be changed. + bool setHostProbeResultCallback(MDNSHostProbeFn p_fnCallback); + bool setHostProbeResultCallback(MDNSHostProbeFn1 p_fnCallback); + + // Set a service specific probe result callback + bool setServiceProbeResultCallback(const MDNSResponder::hMDNSService p_hService, + MDNSServiceProbeFn p_fnCallback); + bool setServiceProbeResultCallback(const MDNSResponder::hMDNSService p_hService, + MDNSServiceProbeFn1 p_fnCallback); + + // 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); + + /** STRUCTS **/ + +public: + /** + MDNSServiceInfo, used in application callbacks + */ + struct MDNSServiceInfo + { + MDNSServiceInfo(MDNSResponder& p_pM, MDNSResponder::hMDNSServiceQuery p_hS, uint32_t p_u32A) + : p_pMDNSResponder(p_pM), + p_hServiceQuery(p_hS), + p_u32AnswerIndex(p_u32A) + {}; + struct CompareKey + { + bool operator()(char const *a, char const *b) const + { + return strcmp(a, b) < 0; + } + }; + using KeyValueMap = std::map; + protected: + MDNSResponder& p_pMDNSResponder; + MDNSResponder::hMDNSServiceQuery p_hServiceQuery; + uint32_t p_u32AnswerIndex; + KeyValueMap keyValueMap; + public: + const char* serviceDomain() + { + return p_pMDNSResponder.answerServiceDomain(p_hServiceQuery, p_u32AnswerIndex); + }; + bool hostDomainAvailable() + { + return (p_pMDNSResponder.hasAnswerHostDomain(p_hServiceQuery, p_u32AnswerIndex)); + } + const char* hostDomain() + { + return (hostDomainAvailable()) ? + p_pMDNSResponder.answerHostDomain(p_hServiceQuery, p_u32AnswerIndex) : nullptr; + }; + bool hostPortAvailable() + { + return (p_pMDNSResponder.hasAnswerPort(p_hServiceQuery, p_u32AnswerIndex)); + } + uint16_t hostPort() + { + return (hostPortAvailable()) ? + p_pMDNSResponder.answerPort(p_hServiceQuery, p_u32AnswerIndex) : 0; + }; + bool IP4AddressAvailable() + { + return (p_pMDNSResponder.hasAnswerIP4Address(p_hServiceQuery, p_u32AnswerIndex)); + } + std::vector IP4Adresses() + { + std::vector internalIP; + if (IP4AddressAvailable()) + { + uint16_t cntIP4Adress = p_pMDNSResponder.answerIP4AddressCount(p_hServiceQuery, p_u32AnswerIndex); + for (uint32_t u2 = 0; u2 < cntIP4Adress; ++u2) + { + internalIP.emplace_back(p_pMDNSResponder.answerIP4Address(p_hServiceQuery, p_u32AnswerIndex, u2)); + } + } + return internalIP; + }; + bool txtAvailable() + { + return (p_pMDNSResponder.hasAnswerTxts(p_hServiceQuery, p_u32AnswerIndex)); + } + const char* strKeyValue() + { + return (txtAvailable()) ? + p_pMDNSResponder.answerTxts(p_hServiceQuery, p_u32AnswerIndex) : nullptr; + }; + const KeyValueMap& keyValues() + { + if (txtAvailable() && keyValueMap.size() == 0) + { + for (auto kv = p_pMDNSResponder._answerKeyValue(p_hServiceQuery, p_u32AnswerIndex); kv != nullptr; kv = kv->m_pNext) + { + keyValueMap.emplace(std::pair(kv->m_pcKey, kv->m_pcValue)); + } + } + return keyValueMap; + } + const char* value(const char* key) + { + char* result = nullptr; + + for (stcMDNSServiceTxt* pTxt = p_pMDNSResponder._answerKeyValue(p_hServiceQuery, p_u32AnswerIndex); pTxt; pTxt = pTxt->m_pNext) + { + if ((key) && + (0 == strcmp(pTxt->m_pcKey, key))) + { + result = pTxt->m_pcValue; + break; + } + } + return result; + } + }; +protected: + + /** + stcMDNSServiceTxt + */ + 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; + }; + + /** + stcMDNSTxts + */ + 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; + }; + + /** + 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 + */ + 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_RRDomain + */ + 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_RRAttributes + */ + 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_RRHeader + */ + 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_RRQuestion + */ + struct stcMDNS_RRQuestion + { + stcMDNS_RRQuestion* m_pNext; + stcMDNS_RRHeader m_Header; + bool m_bUnicast; // Unicast reply requested + + stcMDNS_RRQuestion(void); + }; + + /** + enuAnswerType + */ + typedef enum _enuAnswerType + { + AnswerType_A, + AnswerType_PTR, + AnswerType_TXT, + AnswerType_AAAA, + AnswerType_SRV, + AnswerType_Generic + } enuAnswerType; + + /** + stcMDNS_RRAnswer + */ + 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); + }; + +#ifdef MDNS_IP4_SUPPORT + /** + stcMDNS_RRAnswerA + */ + 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); + }; +#endif + + /** + stcMDNS_RRAnswerPTR + */ + 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_RRAnswerTXT + */ + 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); + }; + +#ifdef MDNS_IP6_SUPPORT + /** + stcMDNS_RRAnswerAAAA + */ + 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); + }; +#endif + + /** + stcMDNS_RRAnswerSRV + */ + 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_RRAnswerGeneric + */ + 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); + }; + + + /** + enuProbingStatus + */ + typedef enum _enuProbingStatus + { + ProbingStatus_WaitingForData, + ProbingStatus_ReadyToStart, + ProbingStatus_InProgress, + ProbingStatus_Done + } enuProbingStatus; + + /** + stcProbeInformation + */ + struct stcProbeInformation + { + enuProbingStatus m_ProbingStatus; + uint8_t m_u8SentCount; // Used for probes and announcements + esp8266::polledTimeout::oneShotMs m_Timeout; // Used for probes and announcements + //clsMDNSTimeFlag m_TimeFlag; // Used for probes and announcements + bool m_bConflict; + bool m_bTiebreakNeeded; + MDNSHostProbeFn m_fnHostProbeResultCallback; + MDNSServiceProbeFn m_fnServiceProbeResultCallback; + + stcProbeInformation(void); + + bool clear(bool p_bClearUserdata = false); + }; + + + /** + stcMDNSService + */ + 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; + MDNSDynamicServiceTxtCallbackFunc m_fnTxtCallback; + 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); + }; + + /** + stcMDNSServiceQuery + */ + struct stcMDNSServiceQuery + { + /** + stcAnswer + */ + struct stcAnswer + { + /** + stcTTL + */ + struct stcTTL + { + /** + timeoutLevel_t + */ + typedef uint8_t timeoutLevel_t; + /** + TIMEOUTLEVELs + */ + const timeoutLevel_t TIMEOUTLEVEL_UNSET = 0; + const timeoutLevel_t TIMEOUTLEVEL_BASE = 80; + const timeoutLevel_t TIMEOUTLEVEL_INTERVAL = 5; + const timeoutLevel_t TIMEOUTLEVEL_FINAL = 100; + + uint32_t m_u32TTL; + esp8266::polledTimeout::oneShotMs m_TTLTimeout; + timeoutLevel_t m_timeoutLevel; + + stcTTL(void); + bool set(uint32_t p_u32TTL); + + bool flagged(void); + bool restart(void); + + bool prepareDeletion(void); + bool finalTimeoutLevel(void) const; + + unsigned long timeout(void) const; + }; +#ifdef MDNS_IP4_SUPPORT + /** + stcIP4Address + */ + struct stcIP4Address + { + stcIP4Address* m_pNext; + IPAddress m_IPAddress; + stcTTL m_TTL; + + stcIP4Address(IPAddress p_IPAddress, + uint32_t p_u32TTL = 0); + }; +#endif +#ifdef MDNS_IP6_SUPPORT + /** + stcIP6Address + */ + struct stcIP6Address + { + stcIP6Address* m_pNext; + IP6Address m_IPAddress; + stcTTL m_TTL; + + stcIP6Address(IPAddress p_IPAddress, + uint32_t p_u32TTL = 0); + }; +#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 + }; + + stcMDNSServiceQuery* m_pNext; + stcMDNS_RRDomain m_ServiceTypeDomain; // eg. _http._tcp.local + MDNSServiceQueryCallbackFunc m_fnCallback; + bool m_bLegacyQuery; + uint8_t m_u8SentCount; + esp8266::polledTimeout::oneShotMs m_ResendTimeout; + 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); + }; + + /** + stcMDNSSendParameter + */ + struct stcMDNSSendParameter + { + protected: + /** + stcDomainCacheItem + */ + 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); + }; + + 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; + }; + + // Instance variables + stcMDNSService* m_pServices; + UdpContext* m_pUDPContext; + char* m_pcHostname; + stcMDNSServiceQuery* m_pServiceQueries; + WiFiEventHandler m_DisconnectedHandler; + WiFiEventHandler m_GotIPHandler; + MDNSDynamicServiceTxtCallbackFunc m_fnServiceTxtCallback; + bool m_bPassivModeEnabled; + stcProbeInformation m_HostProbeInformation; + CONST netif* m_netif; // network interface to run on + + /** 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, + bool p_bIncludeServices); + 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); + 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, + stcMDNSServiceQuery::stcAnswer* p_pKnownAnswers = 0); + + const IPAddress _getResponseMulticastInterface() const + { + return IPAddress(m_netif->ip_addr); + } + + 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); + + stcMDNSServiceTxt* _answerKeyValue(const hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex); + + 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 +}; + +}// namespace MDNSImplementation + +}// namespace esp8266 + +#endif // MDNS_H diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index cee90c907c..740b77db6f 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -22,6 +22,7 @@ */ +#include "ESP8266mDNS.h" #include "LEAmDNS2Host.h" #ifdef MDNS_IPV4_SUPPORT diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index ff590473c7..ceee11834a 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -129,9 +129,9 @@ /* Enable/disable debug trace macros */ -#ifdef DEBUG_ESP_MDNS_RESPONDER -//#define DEBUG_ESP_MDNS_INFO -//#define DEBUG_ESP_MDNS_INFO2 +#if defined(DEBUG_ESP_PORT) && defined(DEBUG_ESP_MDNS_RESPONDER) +#define DEBUG_ESP_MDNS_INFO +#define DEBUG_ESP_MDNS_INFO2 #define DEBUG_ESP_MDNS_ERR #define DEBUG_ESP_MDNS_TX #define DEBUG_ESP_MDNS_RX @@ -144,7 +144,7 @@ #define DEBUG_EX_INFO(A) #endif #ifdef DEBUG_ESP_MDNS_INFO2 -#define DEBUG_EX_INFO2(A) A +#define DEBUG_EX_INFO2(A) A #else #define DEBUG_EX_INFO2(A) #endif @@ -340,6 +340,9 @@ class clsLEAMDNSHost }; public: + + static constexpr auto ApiVersion = MDNSApiVersion::LEAv2; + /** clsServiceTxt */ @@ -621,6 +624,7 @@ class clsLEAMDNSHost */ using list = std::list; }; + using hMDNSService = clsService; // backward compatibility with LEAmDNS protected: /** diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index efc9d1879e..a061d77da8 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -22,6 +22,7 @@ */ +#include "ESP8266mDNS.h" #include "LEAmDNS2Host.h" diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp index b449d1cd08..bc7316be88 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp @@ -22,6 +22,7 @@ */ +#include "ESP8266mDNS.h" #include "LEAmDNS2Host.h" diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp index b2fe64072a..e027f494ac 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -22,6 +22,7 @@ */ +#include "ESP8266mDNS.h" #include "LEAmDNS2Host.h" diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index f4a187b667..36b5ece554 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -23,7 +23,7 @@ */ #include // for can_yield() - +#include "ESP8266mDNS.h" #include "LEAmDNS2Host.h" diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp index 83ecf0c8a4..d3d4849a54 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp @@ -22,6 +22,7 @@ */ +#include "ESP8266mDNS.h" #include "LEAmDNS2Host.h" diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index 896018f81d..151555d15c 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -4,6 +4,7 @@ */ +#include "ESP8266mDNS.h" #include "LEAmDNS2_Legacy.h" diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp new file mode 100644 index 0000000000..0e46651d5f --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp @@ -0,0 +1,2135 @@ +/* + 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 "ESP8266mDNS.h" +#include "LEAmDNS_lwIPdefs.h" +#include "LEAmDNS_Priv.h" + +namespace esp8266 +{ +/* + LEAmDNS +*/ +namespace MDNSImplementation +{ + +/** + CONTROL +*/ + + +/** + MAINTENANCE +*/ + +/* + MDNSResponder::_process + + Run the MDNS process. + Is called, every time the UDPContext receives data AND + should be called in every 'loop' by calling 'MDNS::update()'. + +*/ +bool MDNSResponder::_process(bool p_bUserContext) +{ + + bool bResult = true; + + if (!p_bUserContext) + { + + if ((m_pUDPContext) && // UDPContext available AND + (m_pUDPContext->next())) // has content + { + + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _update: Calling _parseMessage\n"));); + bResult = _parseMessage(); + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parsePacket %s\n"), (bResult ? "succeeded" : "FAILED"));); + } + } + else + { + bResult = (m_netif != nullptr) && + (m_netif->flags & NETIF_FLAG_UP) && // network interface is up and running + _updateProbeStatus() && // Probing + _checkServiceQueryCache(); // Service query cache check + } + return bResult; +} + +/* + MDNSResponder::_restart +*/ +bool MDNSResponder::_restart(void) +{ + + return ((m_netif != nullptr) && + (m_netif->flags & NETIF_FLAG_UP) && // network interface is up and running + (_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("[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("[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("[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("[MDNSResponder] _parseMessage: Received UNEXPECTED opcode:%u. Ignoring message!\n"), header.m_4bOpcode);); + m_pUDPContext->flush(); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseMessage: FAILED to read header\n"));); + m_pUDPContext->flush(); + } + DEBUG_EX_INFO( + unsigned uFreeHeap = ESP.getFreeHeap(); + DEBUG_OUTPUT.printf_P(PSTR("[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) && (qd < p_MsgHeader.m_u16QDCount)); ++qd) + { + + stcMDNS_RRQuestion questionRR; + if ((bResult = _readRRQuestion(questionRR))) + { + // Define host replies, BUT only answer queries after probing is done + u8HostOrServiceReplies = + sendParameter.m_u8HostReplyMask |= (((m_bPassivModeEnabled) || + (ProbingStatus_Done == m_HostProbeInformation.m_ProbingStatus)) + ? _replyMaskForHost(questionRR.m_Header, 0) + : 0); + DEBUG_EX_INFO(if (u8HostOrServiceReplies) + { + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: Host reply needed 0x%X\n"), u8HostOrServiceReplies); + }); + + // Check tiebreak need for host domain + if (ProbingStatus_InProgress == m_HostProbeInformation.m_ProbingStatus) + { + bool bFullNameMatch = false; + if ((_replyMaskForHost(questionRR.m_Header, &bFullNameMatch)) && + (bFullNameMatch)) + { + // We're in 'probing' state and someone is asking for our host domain: this might be + // a race-condition: Two host with the same domain names try simutanously to probe their domains + // See: RFC 6762, 8.2 (Tiebraking) + // However, we're using a max. reduced approach for tiebreaking here: The higher IP-address wins! + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: Possible race-condition for host domain detected while probing.\n"));); + + m_HostProbeInformation.m_bTiebreakNeeded = true; + } + } + + // Define service replies + for (stcMDNSService* pService = m_pServices; pService; pService = pService->m_pNext) + { + // Define service replies, BUT only answer queries after probing is done + uint8_t u8ReplyMaskForQuestion = (((m_bPassivModeEnabled) || + (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("[MDNSResponder] _parseQuery: Service reply needed for (%s.%s.%s): 0x%X (%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("[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("[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)) && + (ip4_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)) && + (ip4_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("[MDNSResponder] _parseQuery: Legacy query from local host %s, id %u!\n"), IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str(), p_MsgHeader.m_u16ID);); + + 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("[MDNSResponder] _parseQuery: FAILED to add legacy question!\n"));); + } + } + else + { + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: Legacy query from NON-LOCAL host!\n"));); + bResult = false; + } + } + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: FAILED to read question!\n"));); + } + } // for questions + + //DEBUG_EX_INFO(if (u8HostOrServiceReplies) { DEBUG_OUTPUT.printf_P(PSTR("[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("[MDNSResponder] _parseQuery: Known answers(%u):\n"), u32Answers); + }); + + for (uint32_t an = 0; ((bResult) && (an < u32Answers)); ++an) + { + stcMDNS_RRAnswer* pKnownRRAnswer = 0; + if (((bResult = _readRRAnswer(pKnownRRAnswer))) && + (pKnownRRAnswer)) + { + + if ((DNS_RRTYPE_ANY != pKnownRRAnswer->m_Header.m_Attributes.m_u16Type) && // No ANY type answer + (DNS_RRCLASS_ANY != pKnownRRAnswer->m_Header.m_Attributes.m_u16Class)) // 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("[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("[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())) + { + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[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())) + { + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[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()); + 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("[MDNSResponder] _parseQuery: Tiebreak (IP4) WON (was an old message)!\n"));); + m_HostProbeInformation.m_bTiebreakNeeded = false; + } + else + { + if ((uint32_t)(((stcMDNS_RRAnswerA*)pKnownRRAnswer)->m_IPAddress) > (uint32_t)localIPAddress) // The OTHER IP is 'higher' -> LOST + { + // LOST tiebreak + DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("[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("[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 (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("[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("[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("[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("[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("[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("[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("[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("[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("[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("[MDNSResponder] _parseQuery: FAILED to read known answer!\n"));); + } + + if (pKnownRRAnswer) + { + delete pKnownRRAnswer; + pKnownRRAnswer = 0; + } + } // 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) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: Sending answer(0x%X)...\n"), u8ReplyNeeded);); + + sendParameter.m_bResponse = true; + sendParameter.m_bAuthorative = true; + + bResult = _sendMDNSMessage(sendParameter); + } + DEBUG_EX_INFO( + else + { + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: No reply needed\n")); + } + ); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[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("[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("[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("[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("[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("[MDNSResponder] _parseResponse: Received a response\n")); + //_udpDump(); + ); + + bResult = true; + // + // Ignore questions here + stcMDNS_RRQuestion dummyRRQ; + for (uint16_t qd = 0; ((bResult) && (qd < p_MsgHeader.m_u16QDCount)); ++qd) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseResponse: Received a response containing a question... ignoring!\n"));); + bResult = _readRRQuestion(dummyRRQ); + } // for queries + + // + // Read and collect answers + stcMDNS_RRAnswer* pCollectedRRAnswers = 0; + uint32_t u32NumberOfAnswerRRs = (p_MsgHeader.m_u16ANCount + p_MsgHeader.m_u16NSCount + p_MsgHeader.m_u16ARCount); + for (uint32_t an = 0; ((bResult) && (an < u32NumberOfAnswerRRs)); ++an) + { + stcMDNS_RRAnswer* pRRAnswer = 0; + if (((bResult = _readRRAnswer(pRRAnswer))) && + (pRRAnswer)) + { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseResponse: ADDING answer!\n"));); + pRRAnswer->m_pNext = pCollectedRRAnswers; + pCollectedRRAnswers = pRRAnswer; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseResponse: FAILED to read answer!\n"));); + if (pRRAnswer) + { + delete pRRAnswer; + pRRAnswer = 0; + } + bResult = false; + } + } // for answers + + // + // Process answers + if (bResult) + { + bResult = ((!pCollectedRRAnswers) || + (_processAnswers(pCollectedRRAnswers))); + } + else // Some failure while reading answers + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseResponse: FAILED to read answers!\n"));); + m_pUDPContext->flush(); + } + + // Delete collected answers + while (pCollectedRRAnswers) + { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[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("[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("[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("[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("[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("[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) + } while ((bFoundNewKeyAnswer) && + (bResult)); + } // else: No answers provided + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[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("[MDNSResponder] _processPTRAnswer: Processing PTR answers...\n"));); + // eg. _http._tcp.local PTR xxxx xx MyESP._http._tcp.local + // Check pending service queries for eg. '_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); // Update TTL tag + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processPTRAnswer: Updated TTL(%d) for "), (int)p_pPTRAnswer->m_u32TTL); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR("\n")); + ); + } + else // received goodbye-message + { + pSQAnswer->m_TTLServiceDomain.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processPTRAnswer: 'Goodbye' received for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR("\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); + pSQAnswer->releaseServiceDomain(); + + bResult = pServiceQuery->addAnswer(pSQAnswer); + p_rbFoundNewKeyAnswer = true; + if (pServiceQuery->m_fnCallback) + { + MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); + pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_ServiceDomain), true); + } + } + } + pServiceQuery = _findNextServiceQueryByServiceType(p_pPTRAnswer->m_Header.m_Domain, pServiceQuery); + } + } // else: No p_pPTRAnswer + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[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); // Update TTL tag + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processSRVAnswer: Updated TTL(%d) for "), (int)p_pSRVAnswer->m_u32TTL); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" host domain and port\n")); + ); + // Host domain & Port + if ((pSQAnswer->m_HostDomain != p_pSRVAnswer->m_SRVDomain) || + (pSQAnswer->m_u16Port != p_pSRVAnswer->m_u16Port)) + { + + pSQAnswer->m_HostDomain = p_pSRVAnswer->m_SRVDomain; + pSQAnswer->releaseHostDomain(); + pSQAnswer->m_u16Port = p_pSRVAnswer->m_u16Port; + pSQAnswer->m_u32ContentFlags |= ServiceQueryAnswerType_HostDomainAndPort; + + p_rbFoundNewKeyAnswer = true; + if (pServiceQuery->m_fnCallback) + { + MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); + pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_HostDomainAndPort), true); + } + } + } + else // Goodby message + { + pSQAnswer->m_TTLHostDomainAndPort.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processSRVAnswer: 'Goodbye' received for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" 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("[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); // Update TTL tag + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processTXTAnswer: Updated TTL(%d) for "), (int)p_pTXTAnswer->m_u32TTL); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" TXTs\n")); + ); + if (!pSQAnswer->m_Txts.compare(p_pTXTAnswer->m_Txts)) + { + pSQAnswer->m_Txts = p_pTXTAnswer->m_Txts; + pSQAnswer->m_u32ContentFlags |= ServiceQueryAnswerType_Txts; + pSQAnswer->releaseTxts(); + + if (pServiceQuery->m_fnCallback) + { + MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); + pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_Txts), true); + } + } + } + else // Goodby message + { + pSQAnswer->m_TTLTxts.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processTXTAnswer: 'Goodbye' received for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" TXTs\n")); + ); + } + } + pServiceQuery = pServiceQuery->m_pNext; + } // while(service query) + } // else: No p_pTXTAnswer + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[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); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processAAnswer: Updated TTL(%d) for "), (int)p_pAAnswer->m_u32TTL); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IP4Address (%s)\n"), pIP4Address->m_IPAddress.toString().c_str()); + ); + } + else // 'Goodbye' message for known IP4 address + { + pIP4Address->m_TTL.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processAAnswer: 'Goodbye' received for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" 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); + if ((pIP4Address) && + (pSQAnswer->addIP4Address(pIP4Address))) + { + + pSQAnswer->m_u32ContentFlags |= ServiceQueryAnswerType_IP4Address; + if (pServiceQuery->m_fnCallback) + { + MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); + pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_IP4Address), true); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[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("[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); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processAAnswer: Updated TTL(%lu) for "), p_pAAAAAnswer->m_u32TTL); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IP6 address (%s)\n"), pIP6Address->m_IPAddress.toString().c_str()); + ); + } + else // 'Goodbye' message for known IP6 address + { + pIP6Address->m_TTL.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processAAnswer: 'Goodbye' received for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" 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); + 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("[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 + //TODO: Fix the following to allow Ethernet shield or other interfaces + (_getResponseMulticastInterface() != IPAddress())) // Has IP address + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Starting host probing...\n"));); + + // First probe delay SHOULD be random 0-250 ms + m_HostProbeInformation.m_Timeout.reset(rand() % MDNS_PROBE_DELAY); + m_HostProbeInformation.m_ProbingStatus = ProbingStatus_InProgress; + } + else if ((ProbingStatus_InProgress == m_HostProbeInformation.m_ProbingStatus) && // Probing AND + (m_HostProbeInformation.m_Timeout.expired())) // Time for next probe + { + + if (MDNS_PROBE_COUNT > m_HostProbeInformation.m_u8SentCount) // Send next probe + { + if ((bResult = _sendHostProbe())) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Did sent host probe\n\n"));); + m_HostProbeInformation.m_Timeout.reset(MDNS_PROBE_DELAY); + ++m_HostProbeInformation.m_u8SentCount; + } + } + else // Probing finished + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Done host probing.\n"));); + m_HostProbeInformation.m_ProbingStatus = ProbingStatus_Done; + m_HostProbeInformation.m_Timeout.resetToNeverExpires(); + if (m_HostProbeInformation.m_fnHostProbeResultCallback) + { + m_HostProbeInformation.m_fnHostProbeResultCallback(m_pcHostname, true); + } + + // Prepare to announce host + m_HostProbeInformation.m_u8SentCount = 0; + m_HostProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Prepared host announcing.\n\n"));); + } + } // else: Probing already finished OR waiting for next time slot + else if ((ProbingStatus_Done == m_HostProbeInformation.m_ProbingStatus) && + (m_HostProbeInformation.m_Timeout.expired())) + { + + if ((bResult = _announce(true, false))) // Don't announce services here + { + ++m_HostProbeInformation.m_u8SentCount; + + if (MDNS_ANNOUNCE_COUNT > m_HostProbeInformation.m_u8SentCount) + { + m_HostProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Announcing host (%d).\n\n"), m_HostProbeInformation.m_u8SentCount);); + } + else + { + m_HostProbeInformation.m_Timeout.resetToNeverExpires(); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Done host announcing.\n\n"));); + } + } + } + + // + // 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_Timeout.reset(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_Timeout.expired())) // Time for next probe + { + + if (MDNS_PROBE_COUNT > pService->m_ProbeInformation.m_u8SentCount) // Send next probe + { + if ((bResult = _sendServiceProbe(*pService))) + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Did sent service probe (%u)\n\n"), (pService->m_ProbeInformation.m_u8SentCount + 1));); + pService->m_ProbeInformation.m_Timeout.reset(MDNS_PROBE_DELAY); + ++pService->m_ProbeInformation.m_u8SentCount; + } + } + else // Probing finished + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Done service probing %s.%s.%s\n\n"), (pService->m_pcName ? : m_pcHostname), pService->m_pcService, pService->m_pcProtocol);); + pService->m_ProbeInformation.m_ProbingStatus = ProbingStatus_Done; + pService->m_ProbeInformation.m_Timeout.resetToNeverExpires(); + if (pService->m_ProbeInformation.m_fnServiceProbeResultCallback) + { + pService->m_ProbeInformation.m_fnServiceProbeResultCallback(pService->m_pcName, pService, true); + } + // Prepare to announce service + pService->m_ProbeInformation.m_u8SentCount = 0; + pService->m_ProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Prepared service announcing.\n\n"));); + } + } // else: Probing already finished OR waiting for next time slot + else if ((ProbingStatus_Done == pService->m_ProbeInformation.m_ProbingStatus) && + (pService->m_ProbeInformation.m_Timeout.expired())) + { + + if ((bResult = _announceService(*pService))) // Announce service + { + ++pService->m_ProbeInformation.m_u8SentCount; + + if (MDNS_ANNOUNCE_COUNT > pService->m_ProbeInformation.m_u8SentCount) + { + pService->m_ProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Announcing service %s.%s.%s (%d)\n\n"), (pService->m_pcName ? : m_pcHostname), pService->m_pcService, pService->m_pcProtocol, pService->m_ProbeInformation.m_u8SentCount);); + } + else + { + pService->m_ProbeInformation.m_Timeout.resetToNeverExpires(); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Done service announcing for %s.%s.%s\n\n"), (pService->m_pcName ? : m_pcHostname), pService->m_pcService, pService->m_pcProtocol);); + } + } + } + } + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: FAILED!\n\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_u8SentCount)); // 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_u8SentCount)); // 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("[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("[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("[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("[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("[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("[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_fnHostProbeResultCallback) + { + m_HostProbeInformation.m_fnHostProbeResultCallback(m_pcHostname, false); + + 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 + if (p_rService.m_ProbeInformation.m_fnServiceProbeResultCallback) + { + p_rService.m_ProbeInformation.m_fnServiceProbeResultCallback(p_rService.m_pcName, &p_rService, false); + 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, + bool p_bIncludeServices) +{ + + 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("[MDNSResponder] _announce: Announcing host %s (content 0x%X)\n"), m_pcHostname, sendParameter.m_u8HostReplyMask);); + + if (p_bIncludeServices) + { + // Announce services (service type, name, SRV (location) and TXTs) + for (stcMDNSService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) + { + 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("[MDNSResponder] _announce: Announcing service %s.%s.%s (content %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("[MDNSResponder] _announce: FAILED!\n")); + }); + return ((bResult) && + (_sendMDNSMessage(sendParameter))); +} + +/* + 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("[MDNSResponder] _announceService: Announcing service %s.%s.%s (content 0x%X)\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("[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; + + DEBUG_EX_INFO( + bool printedInfo = false; + ); + for (stcMDNSServiceQuery* pServiceQuery = m_pServiceQueries; ((bResult) && (pServiceQuery)); pServiceQuery = pServiceQuery->m_pNext) + { + + // + // Resend dynamic service queries, if not already done often enough + if ((!pServiceQuery->m_bLegacyQuery) && + (MDNS_DYNAMIC_QUERY_RESEND_COUNT > pServiceQuery->m_u8SentCount) && + (pServiceQuery->m_ResendTimeout.expired())) + { + + if ((bResult = _sendMDNSServiceQuery(*pServiceQuery))) + { + ++pServiceQuery->m_u8SentCount; + pServiceQuery->m_ResendTimeout.reset((MDNS_DYNAMIC_QUERY_RESEND_COUNT > pServiceQuery->m_u8SentCount) + ? (MDNS_DYNAMIC_QUERY_RESEND_DELAY * (pServiceQuery->m_u8SentCount - 1)) + : esp8266::polledTimeout::oneShotMs::neverExpires); + } + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: %s to resend service query!"), (bResult ? "Succeeded" : "FAILED")); + printedInfo = true; + ); + } + + // + // Schedule updates for cached answers + 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.flagged())) + { + + if (!pSQAnswer->m_TTLServiceDomain.finalTimeoutLevel()) + { + + bResult = ((_sendMDNSServiceQuery(*pServiceQuery)) && + (pSQAnswer->m_TTLServiceDomain.restart())); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: PTR update scheduled for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" %s\n"), (bResult ? "OK" : "FAILURE")); + printedInfo = true; + ); + } + else + { + // Timed out! -> Delete + if (pServiceQuery->m_fnCallback) + { + MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); + pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_ServiceDomain), false); + } + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: Will remove PTR answer for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR("\n")); + printedInfo = true; + ); + + bResult = pServiceQuery->removeAnswer(pSQAnswer); + pSQAnswer = 0; + continue; // Don't use this answer anymore + } + } // ServiceDomain flagged + + // 2. level answers + // HostDomain & Port (from SRV) + if ((bResult) && + (pSQAnswer->m_TTLHostDomainAndPort.flagged())) + { + + if (!pSQAnswer->m_TTLHostDomainAndPort.finalTimeoutLevel()) + { + + bResult = ((_sendMDNSQuery(pSQAnswer->m_ServiceDomain, DNS_RRTYPE_SRV)) && + (pSQAnswer->m_TTLHostDomainAndPort.restart())); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: SRV update scheduled for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" host domain and port %s\n"), (bResult ? "OK" : "FAILURE")); + printedInfo = true; + ); + } + else + { + // Timed out! -> Delete + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: Will remove SRV answer for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" host domain and port\n")); + printedInfo = true; + ); + // Delete + pSQAnswer->m_HostDomain.clear(); + pSQAnswer->releaseHostDomain(); + pSQAnswer->m_u16Port = 0; + pSQAnswer->m_TTLHostDomainAndPort.set(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) + { + MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); + pServiceQuery->m_fnCallback(serviceInfo, static_cast(u32ContentFlags), false); + } + } + } // HostDomainAndPort flagged + + // Txts (from TXT) + if ((bResult) && + (pSQAnswer->m_TTLTxts.flagged())) + { + + if (!pSQAnswer->m_TTLTxts.finalTimeoutLevel()) + { + + bResult = ((_sendMDNSQuery(pSQAnswer->m_ServiceDomain, DNS_RRTYPE_TXT)) && + (pSQAnswer->m_TTLTxts.restart())); + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: TXT update scheduled for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" TXTs %s\n"), (bResult ? "OK" : "FAILURE")); + printedInfo = true; + ); + } + else + { + // Timed out! -> Delete + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: Will remove TXT answer for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" TXTs\n")); + printedInfo = true; + ); + // Delete + pSQAnswer->m_Txts.clear(); + pSQAnswer->m_TTLTxts.set(0); + + // Remove content flags for deleted answer parts + pSQAnswer->m_u32ContentFlags &= ~ServiceQueryAnswerType_Txts; + + if (pServiceQuery->m_fnCallback) + { + MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); + pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_Txts), false); + } + } + } // TXTs flagged + + // 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.flagged()) + { + + if (!pIP4Address->m_TTL.finalTimeoutLevel()) // Needs update + { + + if ((bAUpdateQuerySent) || + ((bResult = _sendMDNSQuery(pSQAnswer->m_HostDomain, DNS_RRTYPE_A)))) + { + + pIP4Address->m_TTL.restart(); + bAUpdateQuerySent = true; + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: IP4 update scheduled for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IP4 address (%s)\n"), (pIP4Address->m_IPAddress.toString().c_str())); + printedInfo = true; + ); + } + } + else + { + // Timed out! -> Delete + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: Will remove IP4 answer for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IP4 address\n")); + printedInfo = true; + ); + 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) + { + MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); + pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_IP4Address), false); + } + } + } // IP4 flagged + + 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.flagged()) + { + + if (!pIP6Address->m_TTL.finalTimeoutLevel()) // Needs update + { + + if ((bAAAAUpdateQuerySent) || + ((bResult = _sendMDNSQuery(pSQAnswer->m_HostDomain, DNS_RRTYPE_AAAA)))) + { + + pIP6Address->m_TTL.restart(); + bAAAAUpdateQuerySent = true; + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: IP6 update scheduled for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IP6 address (%s)\n"), (pIP6Address->m_IPAddress.toString().c_str())); + printedInfo = true; + ); + } + } + else + { + // Timed out! -> Delete + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: Will remove answer for ")); + _printRRDomain(pSQAnswer->m_ServiceDomain); + DEBUG_OUTPUT.printf_P(PSTR(" IP6Address\n")); + printedInfo = true; + ); + 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); + } + } + } // IP6 flagged + + pIP6Address = pNextIP6Address; // Next + } // while +#endif + pSQAnswer = pNextSQAnswer; + } + } + } + DEBUG_EX_INFO( + if (printedInfo) +{ + DEBUG_OUTPUT.printf_P(PSTR("\n")); + } + ); + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[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("[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(), 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("[MDNSResponder] _replyMaskForHost: INVALID RR-class (0x%04X)!\n"), p_RRHeader.m_Attributes.m_u16Class);); + } + DEBUG_EX_INFO(if (u8ReplyMask) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _replyMaskForHost: 0x%X\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("[MDNSResponder] _replyMaskForService: INVALID RR-class (0x%04X)!\n"), p_RRHeader.m_Attributes.m_u16Class);); + } + DEBUG_EX_INFO(if (u8ReplyMask) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _replyMaskForService(%s.%s.%s): 0x%X\n"), p_Service.m_pcName, p_Service.m_pcService, p_Service.m_pcProtocol, u8ReplyMask); + }); + return u8ReplyMask; +} + +} // namespace MDNSImplementation + +} // namespace esp8266 diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp b/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp new file mode 100644 index 0000000000..d5a0ccd762 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp @@ -0,0 +1,851 @@ +/* + 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 + +#include "ESP8266mDNS.h" +#include "LEAmDNS_lwIPdefs.h" +#include "LEAmDNS_Priv.h" + + +namespace +{ + +/* + strrstr (static) + + Backwards search for p_pcPattern in p_pcString + Based on: https://stackoverflow.com/a/1634398/2778898 + +*/ +const char* strrstr(const char*__restrict p_pcString, const char*__restrict p_pcPattern) +{ + + const char* pcResult = 0; + + size_t stStringLength = (p_pcString ? strlen(p_pcString) : 0); + size_t stPatternLength = (p_pcPattern ? strlen(p_pcPattern) : 0); + + if ((stStringLength) && + (stPatternLength) && + (stPatternLength <= stStringLength)) + { + // Pattern is shorter or has the same length tham the string + + for (const char* s = (p_pcString + stStringLength - stPatternLength); s >= p_pcString; --s) + { + if (0 == strncmp(s, p_pcPattern, stPatternLength)) + { + pcResult = s; + break; + } + } + } + return pcResult; +} + + +} // anonymous + + + + + +namespace esp8266 +{ + +/* + LEAmDNS +*/ +namespace MDNSImplementation +{ + +/** + HELPERS +*/ + +/* + MDNSResponder::indexDomain (static) + + Updates the given domain 'p_rpcHostname' by appending a delimiter and an index number. + + If the given domain already hasa numeric index (after the given delimiter), this index + incremented. If not, the delimiter and index '2' is added. + + If 'p_rpcHostname' is empty (==0), the given default name 'p_pcDefaultHostname' is used, + if no default is given, 'esp8266' is used. + +*/ +/*static*/ bool MDNSResponder::indexDomain(char*& p_rpcDomain, + const char* p_pcDivider /*= "-"*/, + const char* p_pcDefaultDomain /*= 0*/) +{ + + bool bResult = false; + + // Ensure a divider exists; use '-' as default + const char* pcDivider = (p_pcDivider ? : "-"); + + if (p_rpcDomain) + { + const char* pFoundDivider = strrstr(p_rpcDomain, pcDivider); + if (pFoundDivider) // maybe already extended + { + char* pEnd = 0; + unsigned long ulIndex = strtoul((pFoundDivider + strlen(pcDivider)), &pEnd, 10); + if ((ulIndex) && + ((pEnd - p_rpcDomain) == (ptrdiff_t)strlen(p_rpcDomain)) && + (!*pEnd)) // Valid (old) index found + { + + char acIndexBuffer[16]; + sprintf(acIndexBuffer, "%lu", (++ulIndex)); + size_t stLength = ((pFoundDivider - p_rpcDomain + strlen(pcDivider)) + strlen(acIndexBuffer) + 1); + char* pNewHostname = new char[stLength]; + if (pNewHostname) + { + memcpy(pNewHostname, p_rpcDomain, (pFoundDivider - p_rpcDomain + strlen(pcDivider))); + pNewHostname[pFoundDivider - p_rpcDomain + strlen(pcDivider)] = 0; + strcat(pNewHostname, acIndexBuffer); + + delete[] p_rpcDomain; + p_rpcDomain = pNewHostname; + + bResult = true; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.println(F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!"));); + } + } + else + { + pFoundDivider = 0; // Flag the need to (base) extend the hostname + } + } + + if (!pFoundDivider) // not yet extended (or failed to increment extension) -> start indexing + { + size_t stLength = strlen(p_rpcDomain) + (strlen(pcDivider) + 1 + 1); // Name + Divider + '2' + '\0' + char* pNewHostname = new char[stLength]; + if (pNewHostname) + { + sprintf(pNewHostname, "%s%s2", p_rpcDomain, pcDivider); + + delete[] p_rpcDomain; + p_rpcDomain = pNewHostname; + + bResult = true; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.println(F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!"));); + } + } + } + else + { + // No given host domain, use base or default + const char* cpcDefaultName = (p_pcDefaultDomain ? : "esp8266"); + + size_t stLength = strlen(cpcDefaultName) + 1; // '\0' + p_rpcDomain = new char[stLength]; + if (p_rpcDomain) + { + strncpy(p_rpcDomain, cpcDefaultName, stLength); + bResult = true; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.println(F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!"));); + } + } + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] indexDomain: %s\n"), p_rpcDomain);); + return bResult; +} + + +/* + UDP CONTEXT +*/ + +bool MDNSResponder::_callProcess(void) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf("[MDNSResponder] _callProcess (%lu, triggered by: %s)\n", millis(), IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str());); + + return _process(false); +} + +/* + MDNSResponder::_allocUDPContext + + (Re-)Creates the one-and-only UDP context for the MDNS responder. + The context is added to the 'multicast'-group and listens to the MDNS port (5353). + The travel-distance for multicast messages is set to 1 (local, via MDNS_MULTICAST_TTL). + Messages are received via the MDNSResponder '_update' function. CAUTION: This function + is called from the WiFi stack side of the ESP stack system. + +*/ +bool MDNSResponder::_allocUDPContext(void) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.println("[MDNSResponder] _allocUDPContext");); + + bool bResult = false; + + _releaseUDPContext(); + +#ifdef MDNS_IP4_SUPPORT + ip_addr_t multicast_addr = DNS_MQUERY_IPV4_GROUP_INIT; +#endif +#ifdef MDNS_IP6_SUPPORT + //TODO: set multicast address (lwip_joingroup() is IPv4 only at the time of writing) + multicast_addr.addr = DNS_MQUERY_IPV6_GROUP_INIT; +#endif + if (ERR_OK == igmp_joingroup(ip_2_ip4(&m_netif->ip_addr), ip_2_ip4(&multicast_addr))) + { + m_pUDPContext = new UdpContext; + m_pUDPContext->ref(); + + if (m_pUDPContext->listen(IP4_ADDR_ANY, DNS_MQUERY_PORT)) + { + m_pUDPContext->setMulticastTTL(MDNS_MULTICAST_TTL); + m_pUDPContext->onRx(std::bind(&MDNSResponder::_callProcess, this)); + + bResult = m_pUDPContext->connect(&multicast_addr, DNS_MQUERY_PORT); + } + } + return bResult; +} + +/* + MDNSResponder::_releaseUDPContext +*/ +bool MDNSResponder::_releaseUDPContext(void) +{ + + if (m_pUDPContext) + { + m_pUDPContext->unref(); + m_pUDPContext = 0; + } + return true; +} + + +/* + SERVICE QUERY +*/ + +/* + MDNSResponder::_allocServiceQuery +*/ +MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_allocServiceQuery(void) +{ + + stcMDNSServiceQuery* pServiceQuery = new stcMDNSServiceQuery; + if (pServiceQuery) + { + // Link to query list + pServiceQuery->m_pNext = m_pServiceQueries; + m_pServiceQueries = pServiceQuery; + } + return m_pServiceQueries; +} + +/* + MDNSResponder::_removeServiceQuery +*/ +bool MDNSResponder::_removeServiceQuery(MDNSResponder::stcMDNSServiceQuery* p_pServiceQuery) +{ + + bool bResult = false; + + if (p_pServiceQuery) + { + stcMDNSServiceQuery* pPred = m_pServiceQueries; + while ((pPred) && + (pPred->m_pNext != p_pServiceQuery)) + { + pPred = pPred->m_pNext; + } + if (pPred) + { + pPred->m_pNext = p_pServiceQuery->m_pNext; + delete p_pServiceQuery; + bResult = true; + } + else // No predecesor + { + if (m_pServiceQueries == p_pServiceQuery) + { + m_pServiceQueries = p_pServiceQuery->m_pNext; + delete p_pServiceQuery; + bResult = true; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.println("[MDNSResponder] _releaseServiceQuery: INVALID service query!");); + } + } + } + return bResult; +} + +/* + MDNSResponder::_removeLegacyServiceQuery +*/ +bool MDNSResponder::_removeLegacyServiceQuery(void) +{ + + stcMDNSServiceQuery* pLegacyServiceQuery = _findLegacyServiceQuery(); + return (pLegacyServiceQuery ? _removeServiceQuery(pLegacyServiceQuery) : true); +} + +/* + MDNSResponder::_findServiceQuery + + 'Convert' hMDNSServiceQuery to stcMDNSServiceQuery* (ensure existance) + +*/ +MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_findServiceQuery(MDNSResponder::hMDNSServiceQuery p_hServiceQuery) +{ + + stcMDNSServiceQuery* pServiceQuery = m_pServiceQueries; + while (pServiceQuery) + { + if ((hMDNSServiceQuery)pServiceQuery == p_hServiceQuery) + { + break; + } + pServiceQuery = pServiceQuery->m_pNext; + } + return pServiceQuery; +} + +/* + MDNSResponder::_findLegacyServiceQuery +*/ +MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_findLegacyServiceQuery(void) +{ + + stcMDNSServiceQuery* pServiceQuery = m_pServiceQueries; + while (pServiceQuery) + { + if (pServiceQuery->m_bLegacyQuery) + { + break; + } + pServiceQuery = pServiceQuery->m_pNext; + } + return pServiceQuery; +} + +/* + MDNSResponder::_releaseServiceQueries +*/ +bool MDNSResponder::_releaseServiceQueries(void) +{ + while (m_pServiceQueries) + { + stcMDNSServiceQuery* pNext = m_pServiceQueries->m_pNext; + delete m_pServiceQueries; + m_pServiceQueries = pNext; + } + return true; +} + +/* + MDNSResponder::_findNextServiceQueryByServiceType +*/ +MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_findNextServiceQueryByServiceType(const stcMDNS_RRDomain& p_ServiceTypeDomain, + const stcMDNSServiceQuery* p_pPrevServiceQuery) +{ + stcMDNSServiceQuery* pMatchingServiceQuery = 0; + + stcMDNSServiceQuery* pServiceQuery = (p_pPrevServiceQuery ? p_pPrevServiceQuery->m_pNext : m_pServiceQueries); + while (pServiceQuery) + { + if (p_ServiceTypeDomain == pServiceQuery->m_ServiceTypeDomain) + { + pMatchingServiceQuery = pServiceQuery; + break; + } + pServiceQuery = pServiceQuery->m_pNext; + } + return pMatchingServiceQuery; +} + + +/* + HOSTNAME +*/ + +/* + MDNSResponder::_setHostname +*/ +bool MDNSResponder::_setHostname(const char* p_pcHostname) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _allocHostname (%s)\n"), p_pcHostname);); + + bool bResult = false; + + _releaseHostname(); + + size_t stLength = 0; + if ((p_pcHostname) && + (MDNS_DOMAIN_LABEL_MAXLENGTH >= (stLength = strlen(p_pcHostname)))) // char max size for a single label + { + // Copy in hostname characters as lowercase + if ((bResult = (0 != (m_pcHostname = new char[stLength + 1])))) + { +#ifdef MDNS_FORCE_LOWERCASE_HOSTNAME + size_t i = 0; + for (; i < stLength; ++i) + { + m_pcHostname[i] = (isupper(p_pcHostname[i]) ? tolower(p_pcHostname[i]) : p_pcHostname[i]); + } + m_pcHostname[i] = 0; +#else + strncpy(m_pcHostname, p_pcHostname, (stLength + 1)); +#endif + } + } + return bResult; +} + +/* + MDNSResponder::_releaseHostname +*/ +bool MDNSResponder::_releaseHostname(void) +{ + + if (m_pcHostname) + { + delete[] m_pcHostname; + m_pcHostname = 0; + } + return true; +} + + +/* + SERVICE +*/ + +/* + MDNSResponder::_allocService +*/ +MDNSResponder::stcMDNSService* MDNSResponder::_allocService(const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol, + uint16_t p_u16Port) +{ + + stcMDNSService* pService = 0; + if (((!p_pcName) || + (MDNS_DOMAIN_LABEL_MAXLENGTH >= strlen(p_pcName))) && + (p_pcService) && + (MDNS_SERVICE_NAME_LENGTH >= strlen(p_pcService)) && + (p_pcProtocol) && + (MDNS_SERVICE_PROTOCOL_LENGTH >= strlen(p_pcProtocol)) && + (p_u16Port) && + (0 != (pService = new stcMDNSService)) && + (pService->setName(p_pcName ? : m_pcHostname)) && + (pService->setService(p_pcService)) && + (pService->setProtocol(p_pcProtocol))) + { + + pService->m_bAutoName = (0 == p_pcName); + pService->m_u16Port = p_u16Port; + + // Add to list (or start list) + pService->m_pNext = m_pServices; + m_pServices = pService; + } + return pService; +} + +/* + MDNSResponder::_releaseService +*/ +bool MDNSResponder::_releaseService(MDNSResponder::stcMDNSService* p_pService) +{ + + bool bResult = false; + + if (p_pService) + { + stcMDNSService* pPred = m_pServices; + while ((pPred) && + (pPred->m_pNext != p_pService)) + { + pPred = pPred->m_pNext; + } + if (pPred) + { + pPred->m_pNext = p_pService->m_pNext; + delete p_pService; + bResult = true; + } + else // No predecesor + { + if (m_pServices == p_pService) + { + m_pServices = p_pService->m_pNext; + delete p_pService; + bResult = true; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.println("[MDNSResponder] _releaseService: INVALID service!");); + } + } + } + return bResult; +} + +/* + MDNSResponder::_releaseServices +*/ +bool MDNSResponder::_releaseServices(void) +{ + + stcMDNSService* pService = m_pServices; + while (pService) + { + _releaseService(pService); + pService = m_pServices; + } + return true; +} + +/* + MDNSResponder::_findService +*/ +MDNSResponder::stcMDNSService* MDNSResponder::_findService(const char* p_pcName, + const char* p_pcService, + const char* p_pcProtocol) +{ + + stcMDNSService* pService = m_pServices; + while (pService) + { + if ((0 == strcmp(pService->m_pcName, p_pcName)) && + (0 == strcmp(pService->m_pcService, p_pcService)) && + (0 == strcmp(pService->m_pcProtocol, p_pcProtocol))) + { + + break; + } + pService = pService->m_pNext; + } + return pService; +} + +/* + MDNSResponder::_findService +*/ +MDNSResponder::stcMDNSService* MDNSResponder::_findService(const MDNSResponder::hMDNSService p_hService) +{ + + stcMDNSService* pService = m_pServices; + while (pService) + { + if (p_hService == (hMDNSService)pService) + { + break; + } + pService = pService->m_pNext; + } + return pService; +} + + +/* + SERVICE TXT +*/ + +/* + MDNSResponder::_allocServiceTxt +*/ +MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_allocServiceTxt(MDNSResponder::stcMDNSService* p_pService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp) +{ + + stcMDNSServiceTxt* pTxt = 0; + + if ((p_pService) && + (p_pcKey) && + (MDNS_SERVICE_TXT_MAXLENGTH > (p_pService->m_Txts.length() + + 1 + // Length byte + (p_pcKey ? strlen(p_pcKey) : 0) + + 1 + // '=' + (p_pcValue ? strlen(p_pcValue) : 0)))) + { + + pTxt = new stcMDNSServiceTxt; + if (pTxt) + { + size_t stLength = (p_pcKey ? strlen(p_pcKey) : 0); + pTxt->m_pcKey = new char[stLength + 1]; + if (pTxt->m_pcKey) + { + strncpy(pTxt->m_pcKey, p_pcKey, stLength); pTxt->m_pcKey[stLength] = 0; + } + + if (p_pcValue) + { + stLength = (p_pcValue ? strlen(p_pcValue) : 0); + pTxt->m_pcValue = new char[stLength + 1]; + if (pTxt->m_pcValue) + { + strncpy(pTxt->m_pcValue, p_pcValue, stLength); pTxt->m_pcValue[stLength] = 0; + } + } + pTxt->m_bTemp = p_bTemp; + + // Add to list (or start list) + p_pService->m_Txts.add(pTxt); + } + } + return pTxt; +} + +/* + MDNSResponder::_releaseServiceTxt +*/ +bool MDNSResponder::_releaseServiceTxt(MDNSResponder::stcMDNSService* p_pService, + MDNSResponder::stcMDNSServiceTxt* p_pTxt) +{ + + return ((p_pService) && + (p_pTxt) && + (p_pService->m_Txts.remove(p_pTxt))); +} + +/* + MDNSResponder::_updateServiceTxt +*/ +MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_updateServiceTxt(MDNSResponder::stcMDNSService* p_pService, + MDNSResponder::stcMDNSServiceTxt* p_pTxt, + const char* p_pcValue, + bool p_bTemp) +{ + + if ((p_pService) && + (p_pTxt) && + (MDNS_SERVICE_TXT_MAXLENGTH > (p_pService->m_Txts.length() - + (p_pTxt->m_pcValue ? strlen(p_pTxt->m_pcValue) : 0) + + (p_pcValue ? strlen(p_pcValue) : 0)))) + { + p_pTxt->update(p_pcValue); + p_pTxt->m_bTemp = p_bTemp; + } + return p_pTxt; +} + +/* + MDNSResponder::_findServiceTxt +*/ +MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_findServiceTxt(MDNSResponder::stcMDNSService* p_pService, + const char* p_pcKey) +{ + + return (p_pService ? p_pService->m_Txts.find(p_pcKey) : 0); +} + +/* + MDNSResponder::_findServiceTxt +*/ +MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_findServiceTxt(MDNSResponder::stcMDNSService* p_pService, + const hMDNSTxt p_hTxt) +{ + + return (((p_pService) && (p_hTxt)) ? p_pService->m_Txts.find((stcMDNSServiceTxt*)p_hTxt) : 0); +} + +/* + MDNSResponder::_addServiceTxt +*/ +MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_addServiceTxt(MDNSResponder::stcMDNSService* p_pService, + const char* p_pcKey, + const char* p_pcValue, + bool p_bTemp) +{ + stcMDNSServiceTxt* pResult = 0; + + if ((p_pService) && + (p_pcKey) && + (strlen(p_pcKey))) + { + + stcMDNSServiceTxt* pTxt = p_pService->m_Txts.find(p_pcKey); + if (pTxt) + { + pResult = _updateServiceTxt(p_pService, pTxt, p_pcValue, p_bTemp); + } + else + { + pResult = _allocServiceTxt(p_pService, p_pcKey, p_pcValue, p_bTemp); + } + } + return pResult; +} + +MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_answerKeyValue(const hMDNSServiceQuery p_hServiceQuery, + const uint32_t p_u32AnswerIndex) +{ + stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery); + stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); + // Fill m_pcTxts (if not already done) + return (pSQAnswer) ? pSQAnswer->m_Txts.m_pTxts : 0; +} + +/* + MDNSResponder::_collectServiceTxts +*/ +bool MDNSResponder::_collectServiceTxts(MDNSResponder::stcMDNSService& p_rService) +{ + + // Call Dynamic service callbacks + if (m_fnServiceTxtCallback) + { + m_fnServiceTxtCallback((hMDNSService)&p_rService); + } + if (p_rService.m_fnTxtCallback) + { + p_rService.m_fnTxtCallback((hMDNSService)&p_rService); + } + return true; +} + +/* + MDNSResponder::_releaseTempServiceTxts +*/ +bool MDNSResponder::_releaseTempServiceTxts(MDNSResponder::stcMDNSService& p_rService) +{ + + return (p_rService.m_Txts.removeTempTxts()); +} + + +/* + MISC +*/ + +#ifdef DEBUG_ESP_MDNS_RESPONDER +/* + MDNSResponder::_printRRDomain +*/ +bool MDNSResponder::_printRRDomain(const MDNSResponder::stcMDNS_RRDomain& p_RRDomain) const +{ + + //DEBUG_OUTPUT.printf_P(PSTR("Domain: ")); + + const char* pCursor = p_RRDomain.m_acName; + uint8_t u8Length = *pCursor++; + if (u8Length) + { + while (u8Length) + { + for (uint8_t u = 0; u < u8Length; ++u) + { + DEBUG_OUTPUT.printf_P(PSTR("%c"), *(pCursor++)); + } + u8Length = *pCursor++; + if (u8Length) + { + DEBUG_OUTPUT.printf_P(PSTR(".")); + } + } + } + else // empty domain + { + DEBUG_OUTPUT.printf_P(PSTR("-empty-")); + } + //DEBUG_OUTPUT.printf_P(PSTR("\n")); + + return true; +} + +/* + MDNSResponder::_printRRAnswer +*/ +bool MDNSResponder::_printRRAnswer(const MDNSResponder::stcMDNS_RRAnswer& p_RRAnswer) const +{ + + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] RRAnswer: ")); + _printRRDomain(p_RRAnswer.m_Header.m_Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:0x%04X Class:0x%04X TTL:%u, "), p_RRAnswer.m_Header.m_Attributes.m_u16Type, p_RRAnswer.m_Header.m_Attributes.m_u16Class, p_RRAnswer.m_u32TTL); + switch (p_RRAnswer.m_Header.m_Attributes.m_u16Type & (~0x8000)) // Topmost bit might carry 'cache flush' flag + { +#ifdef MDNS_IP4_SUPPORT + case DNS_RRTYPE_A: + DEBUG_OUTPUT.printf_P(PSTR("A IP:%s"), ((const stcMDNS_RRAnswerA*)&p_RRAnswer)->m_IPAddress.toString().c_str()); + break; +#endif + case DNS_RRTYPE_PTR: + DEBUG_OUTPUT.printf_P(PSTR("PTR ")); + _printRRDomain(((const stcMDNS_RRAnswerPTR*)&p_RRAnswer)->m_PTRDomain); + break; + case DNS_RRTYPE_TXT: + { + size_t stTxtLength = ((const stcMDNS_RRAnswerTXT*)&p_RRAnswer)->m_Txts.c_strLength(); + char* pTxts = new char[stTxtLength]; + if (pTxts) + { + ((/*const c_str()!!*/stcMDNS_RRAnswerTXT*)&p_RRAnswer)->m_Txts.c_str(pTxts); + DEBUG_OUTPUT.printf_P(PSTR("TXT(%u) %s"), stTxtLength, pTxts); + delete[] pTxts; + } + break; + } +#ifdef MDNS_IP6_SUPPORT + case DNS_RRTYPE_AAAA: + DEBUG_OUTPUT.printf_P(PSTR("AAAA IP:%s"), ((stcMDNS_RRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); + break; +#endif + case DNS_RRTYPE_SRV: + DEBUG_OUTPUT.printf_P(PSTR("SRV Port:%u "), ((const stcMDNS_RRAnswerSRV*)&p_RRAnswer)->m_u16Port); + _printRRDomain(((const stcMDNS_RRAnswerSRV*)&p_RRAnswer)->m_SRVDomain); + break; + default: + DEBUG_OUTPUT.printf_P(PSTR("generic ")); + break; + } + DEBUG_OUTPUT.printf_P(PSTR("\n")); + + return true; +} +#endif + +} // namespace MDNSImplementation + +} // namespace esp8266 + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Priv.h b/libraries/ESP8266mDNS/src/LEAmDNS_Priv.h new file mode 100644 index 0000000000..4750669d38 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Priv.h @@ -0,0 +1,182 @@ +/* + 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 MDNS_PRIV_H +#define MDNS_PRIV_H + +namespace esp8266 +{ + +/* + LEAmDNS +*/ + +namespace MDNSImplementation +{ + +// Enable class debug functions +#define ESP_8266_MDNS_INCLUDE +//#define DEBUG_ESP_MDNS_RESPONDER + +#if !defined(DEBUG_ESP_MDNS_RESPONDER) && defined(DEBUG_ESP_MDNS) +#define DEBUG_ESP_MDNS_RESPONDER +#endif + +#ifndef LWIP_OPEN_SRC +#define LWIP_OPEN_SRC +#endif + +// +// If ENABLE_ESP_MDNS_RESPONDER_PASSIV_MODE is defined, the mDNS responder ignores a successful probing +// This allows to drive the responder in a environment, where 'update()' isn't called in the loop +//#define ENABLE_ESP_MDNS_RESPONDER_PASSIV_MODE + +// Enable/disable debug trace macros +#if defined(DEBUG_ESP_PORT) && defined(DEBUG_ESP_MDNS_RESPONDER) +#define DEBUG_ESP_MDNS_INFO +#define DEBUG_ESP_MDNS_ERR +#define DEBUG_ESP_MDNS_TX +#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) do { (void)0; } while (0) +#define DEBUG_EX_ERR(A) do { (void)0; } while (0) +#define DEBUG_EX_TX(A) do { (void)0; } while (0) +#define DEBUG_EX_RX(A) do { (void)0; } while (0) +#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) + + However, RFC 3171 seems to force 255 instead +*/ +#define MDNS_MULTICAST_TTL 255/*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 + Delay between and number of announces for host and service domains + Delay between and number of service queries; the delay is multiplied by the resent number in '_checkServiceQueryCache' +*/ +#define MDNS_PROBE_DELAY 250 +#define MDNS_PROBE_COUNT 3 +#define MDNS_ANNOUNCE_DELAY 1000 +#define MDNS_ANNOUNCE_COUNT 8 +#define MDNS_DYNAMIC_QUERY_RESEND_COUNT 5 +#define MDNS_DYNAMIC_QUERY_RESEND_DELAY 5000 + + +/* + 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 +#else +#ifdef F +#undef F +#endif +#define F(A) A +#endif + +} // namespace MDNSImplementation + +} // namespace esp8266 + +// Include the main header, so the submodlues only need to include this header +#include "LEAmDNS.h" + + +#endif // MDNS_PRIV_H diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp new file mode 100644 index 0000000000..18c4a95eb3 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp @@ -0,0 +1,2477 @@ +/* + 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 "ESP8266mDNS.h" +#include "LEAmDNS_Priv.h" +#include "LEAmDNS_lwIPdefs.h" + +namespace esp8266 +{ + +/* + LEAmDNS +*/ +namespace MDNSImplementation +{ + +/** + 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)) + { + 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 ? 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)) + { + 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 ? 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 += strlen(m_pcKey); // Key + stLength += 1; // '=' + stLength += (m_pcValue ? 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 ? strlen(pTxt->m_pcKey) : 0))))) + { + if (pTxt != m_pTxts) + { + *p_pcBuffer++ = ';'; + } + strncpy(p_pcBuffer, pTxt->m_pcKey, stLength); p_pcBuffer[stLength] = 0; + p_pcBuffer += stLength; + *p_pcBuffer++ = '='; + if ((stLength = (pTxt->m_pcValue ? strlen(pTxt->m_pcValue) : 0))) + { + 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 ? strlen(pTxt->m_pcKey) : 0))))) + { + memcpy(p_pcBuffer, pTxt->m_pcKey, stLength); + p_pcBuffer += stLength; + *p_pcBuffer++ = '='; + if ((stLength = (pTxt->m_pcValue ? strlen(pTxt->m_pcValue) : 0))) + { + 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) && + (strlen(pTxt->m_pcValue) == 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) && + (strlen(pOtherTxt->m_pcValue) == 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 + ? (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; + } + 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 == strncasecmp((pT + 1), (pO + 1), *((unsigned char*)pT)))) // Same content + { + if (*((unsigned char*)pT)) // Not 0 + { + pT += (1 + * ((unsigned char*)pT)); // Shift by length byte and 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_u8SentCount(0), + m_Timeout(esp8266::polledTimeout::oneShotMs::neverExpires), + m_bConflict(false), + m_bTiebreakNeeded(false), + m_fnHostProbeResultCallback(0), + m_fnServiceProbeResultCallback(0) +{ +} + +/* + MDNSResponder::stcProbeInformation::clear +*/ +bool MDNSResponder::stcProbeInformation::clear(bool p_bClearUserdata /*= false*/) +{ + + m_ProbingStatus = ProbingStatus_WaitingForData; + m_u8SentCount = 0; + m_Timeout.resetToNeverExpires(); + m_bConflict = false; + m_bTiebreakNeeded = false; + if (p_bClearUserdata) + { + m_fnHostProbeResultCallback = 0; + m_fnServiceProbeResultCallback = 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) +{ + + 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 ? strlen(p_pcName) : 0); + if (stLength) + { + if ((bResult = (0 != (m_pcName = new char[stLength + 1])))) + { + 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 ? strlen(p_pcService) : 0); + if (stLength) + { + if ((bResult = (0 != (m_pcService = new char[stLength + 1])))) + { + 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 ? strlen(p_pcProtocol) : 0); + if (stLength) + { + if ((bResult = (0 != (m_pcProtocol = new char[stLength + 1])))) + { + 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* /) + : m_bUpdateScheduled(false) { + + set(p_u32TTL * 1000); + } + + / * + MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::set + / + bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::set(uint32_t p_u32TTL) { + + m_TTLTimeFlag.restart(p_u32TTL * 1000); + m_bUpdateScheduled = false; + + return true; + } + + / * + MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::has80Percent + / + bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::has80Percent(void) const { + + return ((m_TTLTimeFlag.getTimeout()) && + (!m_bUpdateScheduled) && + (m_TTLTimeFlag.hypotheticalTimeout((m_TTLTimeFlag.getTimeout() * 800) / 1000))); + } + + / * + MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::isOutdated + / + bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::isOutdated(void) const { + + return ((m_TTLTimeFlag.getTimeout()) && + (m_TTLTimeFlag.flagged())); + }*/ + + +/** + 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(void) + : m_u32TTL(0), + m_TTLTimeout(esp8266::polledTimeout::oneShotMs::neverExpires), + m_timeoutLevel(TIMEOUTLEVEL_UNSET) +{ + +} + +/* + MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::set +*/ +bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::set(uint32_t p_u32TTL) +{ + + m_u32TTL = p_u32TTL; + if (m_u32TTL) + { + m_timeoutLevel = TIMEOUTLEVEL_BASE; // Set to 80% + m_TTLTimeout.reset(timeout()); + } + else + { + m_timeoutLevel = TIMEOUTLEVEL_UNSET; // undef + m_TTLTimeout.resetToNeverExpires(); + } + return true; +} + +/* + MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::flagged +*/ +bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::flagged(void) +{ + + return ((m_u32TTL) && + (TIMEOUTLEVEL_UNSET != m_timeoutLevel) && + (m_TTLTimeout.expired())); +} + +/* + MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::restart +*/ +bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::restart(void) +{ + + bool bResult = true; + + if ((TIMEOUTLEVEL_BASE <= m_timeoutLevel) && // >= 80% AND + (TIMEOUTLEVEL_FINAL > m_timeoutLevel)) // < 100% + { + + m_timeoutLevel += TIMEOUTLEVEL_INTERVAL; // increment by 5% + m_TTLTimeout.reset(timeout()); + } + else + { + bResult = false; + m_TTLTimeout.resetToNeverExpires(); + m_timeoutLevel = TIMEOUTLEVEL_UNSET; + } + return bResult; +} + +/* + MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::prepareDeletion +*/ +bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::prepareDeletion(void) +{ + + m_timeoutLevel = TIMEOUTLEVEL_FINAL; + m_TTLTimeout.reset(1 * 1000); // See RFC 6762, 10.1 + + return true; +} + +/* + MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::finalTimeoutLevel +*/ +bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::finalTimeoutLevel(void) const +{ + + return (TIMEOUTLEVEL_FINAL == m_timeoutLevel); +} + +/* + MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::timeout +*/ +unsigned long MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::timeout(void) const +{ + + uint32_t u32Timeout = esp8266::polledTimeout::oneShotMs::neverExpires; + + if (TIMEOUTLEVEL_BASE == m_timeoutLevel) // 80% + { + u32Timeout = (m_u32TTL * 800); // to milliseconds + } + else if ((TIMEOUTLEVEL_BASE < m_timeoutLevel) && // >80% AND + (TIMEOUTLEVEL_FINAL >= m_timeoutLevel)) // <= 100% + { + + u32Timeout = (m_u32TTL * 50); + } // else: invalid + return u32Timeout; +} + + +#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*/) + : m_pNext(0), + m_IPAddress(p_IPAddress) +{ + + m_TTL.set(p_u32TTL); +} +#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) && (u32Index < p_u32Index)); pIP4Address = pIP4Address->m_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) && (u32Index < p_u32Index)); pIP6Address = pIP6Address->m_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_bLegacyQuery(false), + m_u8SentCount(0), + m_ResendTimeout(esp8266::polledTimeout::oneShotMs::neverExpires), + 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_bLegacyQuery = false; + m_u8SentCount = 0; + m_ResendTimeout.resetToNeverExpires(); + 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) && (u32Index < p_u32Index)); pAnswer = pAnswer->m_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 MDNSImplementation + +} // namespace esp8266 + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS_Transfer.cpp new file mode 100644 index 0000000000..c5858690a5 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Transfer.cpp @@ -0,0 +1,1780 @@ +/* + 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 "ESP8266mDNS.h" +#include "LEAmDNS_lwIPdefs.h" +#include "LEAmDNS_Priv.h" + + +namespace esp8266 +{ + +/* + LEAmDNS +*/ +namespace MDNSImplementation +{ + +/** + CONST STRINGS +*/ +static const char* scpcLocal = "local"; +static const char* scpcServices = "services"; +static const char* scpcDNSSD = "dns-sd"; +static const char* scpcUDP = "udp"; +//static const char* scpcTCP = "tcp"; + +#ifdef MDNS_IP4_SUPPORT +static const char* scpcReverseIP4Domain = "in-addr"; +#endif +#ifdef MDNS_IP6_SUPPORT +static const char* scpcReverseIP6Domain = "ip6"; +#endif +static const char* scpcReverseTopDomain = "arpa"; + +/** + TRANSFER +*/ + + +/** + SENDING +*/ + +/* + MDNSResponder::_sendMDNSMessage + + Unicast responses are prepared and sent directly to the querier. + Multicast responses or queries are transferred to _sendMDNSMessage_Multicast + + Any reply flags in installed services are removed at the end! + +*/ +bool MDNSResponder::_sendMDNSMessage(MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + + bool bResult = true; + + if (p_rSendParameter.m_bResponse && + p_rSendParameter.m_bUnicast) // Unicast response -> Send to querier + { + DEBUG_EX_ERR(if (!m_pUDPContext->getRemoteAddress()) + { + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSMessage: MISSING remote address for response!\n")); + }); + IPAddress ipRemote; + ipRemote = m_pUDPContext->getRemoteAddress(); + bResult = ((_prepareMDNSMessage(p_rSendParameter, _getResponseMulticastInterface())) && + (m_pUDPContext->send(ipRemote, m_pUDPContext->getRemotePort()))); + } + else // Multicast response + { + bResult = _sendMDNSMessage_Multicast(p_rSendParameter); + } + + // Finally clear service reply masks + for (stcMDNSService* pService = m_pServices; pService; pService = pService->m_pNext) + { + pService->m_u8ReplyMask = 0; + } + + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSMessage: FAILED!\n")); + }); + return bResult; +} + +/* + MDNSResponder::_sendMDNSMessage_Multicast + + Fills the UDP output buffer (via _prepareMDNSMessage) and sends the buffer + via the selected WiFi interface (Station or AP) +*/ +bool MDNSResponder::_sendMDNSMessage_Multicast(MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + + bool bResult = false; + + IPAddress fromIPAddress; + fromIPAddress = _getResponseMulticastInterface(); + m_pUDPContext->setMulticastInterface(fromIPAddress); + +#ifdef MDNS_IP4_SUPPORT + IPAddress toMulticastAddress(DNS_MQUERY_IPV4_GROUP_INIT); +#endif +#ifdef MDNS_IP6_SUPPORT + //TODO: set multicast address + IPAddress toMulticastAddress(DNS_MQUERY_IPV6_GROUP_INIT); +#endif + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSMessage_Multicast: Will send to '%s'.\n"), toMulticastAddress.toString().c_str());); + bResult = ((_prepareMDNSMessage(p_rSendParameter, fromIPAddress)) && + (m_pUDPContext->send(toMulticastAddress, DNS_MQUERY_PORT))); + + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSMessage_Multicast: FAILED!\n")); + }); + return bResult; +} + +/* + MDNSResponder::_prepareMDNSMessage + + The MDNS message is composed in a two-step process. + In the first loop 'only' the header informations (mainly number of answers) are collected, + while in the seconds loop, the header and all queries and answers are written to the UDP + output buffer. + +*/ +bool MDNSResponder::_prepareMDNSMessage(MDNSResponder::stcMDNSSendParameter& p_rSendParameter, + IPAddress p_IPAddress) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage\n"));); + bool bResult = true; + + // Prepare header; count answers + stcMDNS_MsgHeader msgHeader(p_rSendParameter.m_u16ID, p_rSendParameter.m_bResponse, 0, p_rSendParameter.m_bAuthorative); + // If this is a response, the answers are anwers, + // else this is a query or probe and the answers go into auth section + uint16_t& ru16Answers = (p_rSendParameter.m_bResponse + ? msgHeader.m_u16ANCount + : msgHeader.m_u16NSCount); + + /** + enuSequence + */ + enum enuSequence + { + Sequence_Count = 0, + Sequence_Send = 1 + }; + + // Two step sequence: 'Count' and 'Send' + for (uint32_t sequence = Sequence_Count; ((bResult) && (sequence <= Sequence_Send)); ++sequence) + { + DEBUG_EX_INFO( + if (Sequence_Send == sequence) + { + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), + (unsigned)msgHeader.m_u16ID, + (unsigned)msgHeader.m_1bQR, (unsigned)msgHeader.m_4bOpcode, (unsigned)msgHeader.m_1bAA, (unsigned)msgHeader.m_1bTC, (unsigned)msgHeader.m_1bRD, + (unsigned)msgHeader.m_1bRA, (unsigned)msgHeader.m_4bRCode, + (unsigned)msgHeader.m_u16QDCount, + (unsigned)msgHeader.m_u16ANCount, + (unsigned)msgHeader.m_u16NSCount, + (unsigned)msgHeader.m_u16ARCount); + } + ); + // Count/send + // Header + bResult = ((Sequence_Count == sequence) + ? true + : _writeMDNSMsgHeader(msgHeader, p_rSendParameter)); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSMsgHeader FAILED!\n"));); + // Questions + for (stcMDNS_RRQuestion* pQuestion = p_rSendParameter.m_pQuestions; ((bResult) && (pQuestion)); pQuestion = pQuestion->m_pNext) + { + ((Sequence_Count == sequence) + ? ++msgHeader.m_u16QDCount + : (bResult = _writeMDNSQuestion(*pQuestion, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSQuestion FAILED!\n"));); + } + + // Answers and authorative answers +#ifdef MDNS_IP4_SUPPORT + if ((bResult) && + (p_rSendParameter.m_u8HostReplyMask & ContentFlag_A)) + { + ((Sequence_Count == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_A(p_IPAddress, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_A(A) FAILED!\n"));); + } + if ((bResult) && + (p_rSendParameter.m_u8HostReplyMask & ContentFlag_PTR_IP4)) + { + ((Sequence_Count == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_PTR_IP4(p_IPAddress, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_IP4 FAILED!\n"));); + } +#endif +#ifdef MDNS_IP6_SUPPORT + if ((bResult) && + (p_rSendParameter.m_u8HostReplyMask & ContentFlag_AAAA)) + { + ((Sequence_Count == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_AAAA(p_IPAddress, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_AAAA(A) FAILED!\n"));); + } + if ((bResult) && + (p_rSendParameter.m_u8HostReplyMask & ContentFlag_PTR_IP6)) + { + ((Sequence_Count == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_PTR_IP6(p_IPAddress, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_IP6 FAILED!\n"));); + } +#endif + + for (stcMDNSService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) + { + if ((bResult) && + (pService->m_u8ReplyMask & ContentFlag_PTR_TYPE)) + { + ((Sequence_Count == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_PTR_TYPE(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_TYPE FAILED!\n"));); + } + if ((bResult) && + (pService->m_u8ReplyMask & ContentFlag_PTR_NAME)) + { + ((Sequence_Count == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_PTR_NAME(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_NAME FAILED!\n"));); + } + if ((bResult) && + (pService->m_u8ReplyMask & ContentFlag_SRV)) + { + ((Sequence_Count == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_SRV(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_SRV(A) FAILED!\n"));); + } + if ((bResult) && + (pService->m_u8ReplyMask & ContentFlag_TXT)) + { + ((Sequence_Count == sequence) + ? ++ru16Answers + : (bResult = _writeMDNSAnswer_TXT(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_TXT(A) FAILED!\n"));); + } + } // for services + + // Additional answers +#ifdef MDNS_IP4_SUPPORT + bool bNeedsAdditionalAnswerA = false; +#endif +#ifdef MDNS_IP6_SUPPORT + bool bNeedsAdditionalAnswerAAAA = false; +#endif + for (stcMDNSService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) + { + if ((bResult) && + (pService->m_u8ReplyMask & ContentFlag_PTR_NAME) && // If PTR_NAME is requested, AND + (!(pService->m_u8ReplyMask & ContentFlag_SRV))) // NOT SRV -> add SRV as additional answer + { + ((Sequence_Count == sequence) + ? ++msgHeader.m_u16ARCount + : (bResult = _writeMDNSAnswer_SRV(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_SRV(B) FAILED!\n"));); + } + if ((bResult) && + (pService->m_u8ReplyMask & ContentFlag_PTR_NAME) && // If PTR_NAME is requested, AND + (!(pService->m_u8ReplyMask & ContentFlag_TXT))) // NOT TXT -> add TXT as additional answer + { + ((Sequence_Count == sequence) + ? ++msgHeader.m_u16ARCount + : (bResult = _writeMDNSAnswer_TXT(*pService, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_TXT(B) FAILED!\n"));); + } + if ((pService->m_u8ReplyMask & (ContentFlag_PTR_NAME | ContentFlag_SRV)) || // If service instance name or SRV OR + (p_rSendParameter.m_u8HostReplyMask & (ContentFlag_A | ContentFlag_AAAA))) // any host IP address is requested + { +#ifdef MDNS_IP4_SUPPORT + if ((bResult) && + (!(p_rSendParameter.m_u8HostReplyMask & ContentFlag_A))) // Add IP4 address + { + bNeedsAdditionalAnswerA = true; + } +#endif +#ifdef MDNS_IP6_SUPPORT + if ((bResult) && + (!(p_rSendParameter.m_u8HostReplyMask & ContentFlag_AAAA))) // Add IP6 address + { + bNeedsAdditionalAnswerAAAA = true; + } +#endif + } + } // for services + + // Answer A needed? +#ifdef MDNS_IP4_SUPPORT + if ((bResult) && + (bNeedsAdditionalAnswerA)) + { + ((Sequence_Count == sequence) + ? ++msgHeader.m_u16ARCount + : (bResult = _writeMDNSAnswer_A(p_IPAddress, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_A(B) FAILED!\n"));); + } +#endif +#ifdef MDNS_IP6_SUPPORT + // Answer AAAA needed? + if ((bResult) && + (bNeedsAdditionalAnswerAAAA)) + { + ((Sequence_Count == sequence) + ? ++msgHeader.m_u16ARCount + : (bResult = _writeMDNSAnswer_AAAA(p_IPAddress, p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_AAAA(B) FAILED!\n"));); + } +#endif + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: Loop %i FAILED!\n"), sequence);); + } // for sequence + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: FAILED!\n"));); + return bResult; +} + +/* + MDNSResponder::_sendMDNSServiceQuery + + Creates and sends a PTR query for the given service domain. + +*/ +bool MDNSResponder::_sendMDNSServiceQuery(const MDNSResponder::stcMDNSServiceQuery& p_ServiceQuery) +{ + + return _sendMDNSQuery(p_ServiceQuery.m_ServiceTypeDomain, DNS_RRTYPE_PTR); +} + +/* + MDNSResponder::_sendMDNSQuery + + Creates and sends a query for the given domain and query type. + +*/ +bool MDNSResponder::_sendMDNSQuery(const MDNSResponder::stcMDNS_RRDomain& p_QueryDomain, + uint16_t p_u16QueryType, + stcMDNSServiceQuery::stcAnswer* p_pKnownAnswers /*= 0*/) +{ + + 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_Header.m_Attributes.m_u16Type = p_u16QueryType; + // It seems, that some mDNS implementations don't support 'unicast response' questions... + sendParameter.m_pQuestions->m_Header.m_Attributes.m_u16Class = (/*0x8000 |*/ DNS_RRCLASS_IN); // /*Unicast &*/ INternet + + // TODO: Add knwon answer to the query + (void)p_pKnownAnswers; + + bResult = _sendMDNSMessage(sendParameter); + } // else: FAILED to alloc question + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSQuery: FAILED to alloc question!\n"));); + return bResult; +} + +/** + HELPERS +*/ + +/** + RESOURCE RECORDS +*/ + +/* + MDNSResponder::_readRRQuestion + + Reads a question (eg. MyESP._http._tcp.local ANY IN) from the UPD input buffer. + +*/ +bool MDNSResponder::_readRRQuestion(MDNSResponder::stcMDNS_RRQuestion& p_rRRQuestion) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRQuestion\n"));); + + bool bResult = false; + + if ((bResult = _readRRHeader(p_rRRQuestion.m_Header))) + { + // Extract unicast flag from class field + p_rRRQuestion.m_bUnicast = (p_rRRQuestion.m_Header.m_Attributes.m_u16Class & 0x8000); + p_rRRQuestion.m_Header.m_Attributes.m_u16Class &= (~0x8000); + + DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRQuestion ")); + _printRRDomain(p_rRRQuestion.m_Header.m_Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:0x%04X Class:0x%04X %s\n"), (unsigned)p_rRRQuestion.m_Header.m_Attributes.m_u16Type, (unsigned)p_rRRQuestion.m_Header.m_Attributes.m_u16Class, (p_rRRQuestion.m_bUnicast ? "Unicast" : "Multicast")); + ); + } + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRQuestion: FAILED!\n")); + }); + return bResult; +} + +/* + MDNSResponder::_readRRAnswer + + Reads an answer (eg. _http._tcp.local PTR OP TTL MyESP._http._tcp.local) + from the UDP input buffer. + After reading the domain and type info, the further processing of the answer + is transferred the answer specific reading functions. + Unknown answer types are processed by the generic answer reader (to remove them + from the input buffer). + +*/ +bool MDNSResponder::_readRRAnswer(MDNSResponder::stcMDNS_RRAnswer*& p_rpRRAnswer) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer\n"));); + + bool bResult = false; + + stcMDNS_RRHeader header; + uint32_t u32TTL; + uint16_t u16RDLength; + if ((_readRRHeader(header)) && + (_udpRead32(u32TTL)) && + (_udpRead16(u16RDLength))) + { + + /* DEBUG_EX_INFO( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer: Reading 0x%04X answer (class:0x%04X, TTL:%u, RDLength:%u) for "), header.m_Attributes.m_u16Type, header.m_Attributes.m_u16Class, u32TTL, u16RDLength); + _printRRDomain(header.m_Domain); + DEBUG_OUTPUT.printf_P(PSTR("\n")); + );*/ + + switch (header.m_Attributes.m_u16Type & (~0x8000)) // Topmost bit might carry 'cache flush' flag + { +#ifdef MDNS_IP4_SUPPORT + case DNS_RRTYPE_A: + p_rpRRAnswer = new stcMDNS_RRAnswerA(header, u32TTL); + bResult = _readRRAnswerA(*(stcMDNS_RRAnswerA*&)p_rpRRAnswer, u16RDLength); + break; +#endif + case DNS_RRTYPE_PTR: + p_rpRRAnswer = new stcMDNS_RRAnswerPTR(header, u32TTL); + bResult = _readRRAnswerPTR(*(stcMDNS_RRAnswerPTR*&)p_rpRRAnswer, u16RDLength); + break; + case DNS_RRTYPE_TXT: + p_rpRRAnswer = new stcMDNS_RRAnswerTXT(header, u32TTL); + bResult = _readRRAnswerTXT(*(stcMDNS_RRAnswerTXT*&)p_rpRRAnswer, u16RDLength); + break; +#ifdef MDNS_IP6_SUPPORT + case DNS_RRTYPE_AAAA: + p_rpRRAnswer = new stcMDNS_RRAnswerAAAA(header, u32TTL); + bResult = _readRRAnswerAAAA(*(stcMDNS_RRAnswerAAAA*&)p_rpRRAnswer, u16RDLength); + break; +#endif + case DNS_RRTYPE_SRV: + p_rpRRAnswer = new stcMDNS_RRAnswerSRV(header, u32TTL); + bResult = _readRRAnswerSRV(*(stcMDNS_RRAnswerSRV*&)p_rpRRAnswer, u16RDLength); + break; + default: + p_rpRRAnswer = new stcMDNS_RRAnswerGeneric(header, u32TTL); + bResult = _readRRAnswerGeneric(*(stcMDNS_RRAnswerGeneric*&)p_rpRRAnswer, u16RDLength); + break; + } + DEBUG_EX_INFO( + if ((bResult) && + (p_rpRRAnswer)) + { + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer: ")); + _printRRDomain(p_rpRRAnswer->m_Header.m_Domain); + DEBUG_OUTPUT.printf_P(PSTR(" Type:0x%04X Class:0x%04X TTL:%u, RDLength:%u "), p_rpRRAnswer->m_Header.m_Attributes.m_u16Type, p_rpRRAnswer->m_Header.m_Attributes.m_u16Class, p_rpRRAnswer->m_u32TTL, u16RDLength); + switch (header.m_Attributes.m_u16Type & (~0x8000)) // Topmost bit might carry 'cache flush' flag + { +#ifdef MDNS_IP4_SUPPORT + case DNS_RRTYPE_A: + DEBUG_OUTPUT.printf_P(PSTR("A IP:%s"), ((stcMDNS_RRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); + break; +#endif + case DNS_RRTYPE_PTR: + DEBUG_OUTPUT.printf_P(PSTR("PTR ")); + _printRRDomain(((stcMDNS_RRAnswerPTR*&)p_rpRRAnswer)->m_PTRDomain); + break; + case DNS_RRTYPE_TXT: + { + size_t stTxtLength = ((stcMDNS_RRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_strLength(); + char* pTxts = new char[stTxtLength]; + if (pTxts) + { + ((stcMDNS_RRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_str(pTxts); + DEBUG_OUTPUT.printf_P(PSTR("TXT(%u) %s"), stTxtLength, pTxts); + delete[] pTxts; + } + break; + } +#ifdef MDNS_IP6_SUPPORT + case DNS_RRTYPE_AAAA: + DEBUG_OUTPUT.printf_P(PSTR("AAAA IP:%s"), ((stcMDNS_RRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); + break; +#endif + case DNS_RRTYPE_SRV: + DEBUG_OUTPUT.printf_P(PSTR("SRV Port:%u "), ((stcMDNS_RRAnswerSRV*&)p_rpRRAnswer)->m_u16Port); + _printRRDomain(((stcMDNS_RRAnswerSRV*&)p_rpRRAnswer)->m_SRVDomain); + break; + default: + DEBUG_OUTPUT.printf_P(PSTR("generic ")); + break; + } + DEBUG_OUTPUT.printf_P(PSTR("\n")); + } + else + { + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer: FAILED to read specific answer of type 0x%04X!\n"), p_rpRRAnswer->m_Header.m_Attributes.m_u16Type); + } + ); // DEBUG_EX_INFO + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer: FAILED!\n"));); + return bResult; +} + +#ifdef MDNS_IP4_SUPPORT +/* + MDNSResponder::_readRRAnswerA +*/ +bool MDNSResponder::_readRRAnswerA(MDNSResponder::stcMDNS_RRAnswerA& p_rRRAnswerA, + uint16_t p_u16RDLength) +{ + + uint32_t u32IP4Address; + bool bResult = ((MDNS_IP4_SIZE == p_u16RDLength) && + (_udpReadBuffer((unsigned char*)&u32IP4Address, MDNS_IP4_SIZE)) && + ((p_rRRAnswerA.m_IPAddress = IPAddress(u32IP4Address)))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerA: FAILED!\n"));); + return bResult; +} +#endif + +/* + MDNSResponder::_readRRAnswerPTR +*/ +bool MDNSResponder::_readRRAnswerPTR(MDNSResponder::stcMDNS_RRAnswerPTR& p_rRRAnswerPTR, + uint16_t p_u16RDLength) +{ + + bool bResult = ((p_u16RDLength) && + (_readRRDomain(p_rRRAnswerPTR.m_PTRDomain))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerPTR: FAILED!\n"));); + return bResult; +} + +/* + MDNSResponder::_readRRAnswerTXT + + Read TXT items from a buffer like 4c#=15ff=20 +*/ +bool MDNSResponder::_readRRAnswerTXT(MDNSResponder::stcMDNS_RRAnswerTXT& p_rRRAnswerTXT, + uint16_t p_u16RDLength) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: RDLength:%u\n"), p_u16RDLength);); + bool bResult = true; + + p_rRRAnswerTXT.clear(); + if (p_u16RDLength) + { + bResult = false; + + unsigned char* pucBuffer = new unsigned char[p_u16RDLength]; + if (pucBuffer) + { + if (_udpReadBuffer(pucBuffer, p_u16RDLength)) + { + bResult = true; + + const unsigned char* pucCursor = pucBuffer; + while ((pucCursor < (pucBuffer + p_u16RDLength)) && + (bResult)) + { + bResult = false; + + stcMDNSServiceTxt* pTxt = 0; + unsigned char ucLength = *pucCursor++; // Length of the next txt item + if (ucLength) + { + DEBUG_EX_INFO( + static char sacBuffer[64]; *sacBuffer = 0; + uint8_t u8MaxLength = ((ucLength > (sizeof(sacBuffer) - 1)) ? (sizeof(sacBuffer) - 1) : ucLength); + os_strncpy(sacBuffer, (const char*)pucCursor, u8MaxLength); sacBuffer[u8MaxLength] = 0; + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: Item(%u): %s\n"), ucLength, sacBuffer); + ); + + unsigned char* pucEqualSign = (unsigned char*)os_strchr((const char*)pucCursor, '='); // Position of the '=' sign + unsigned char ucKeyLength; + if ((pucEqualSign) && + ((ucKeyLength = (pucEqualSign - pucCursor)))) + { + unsigned char ucValueLength = (ucLength - (pucEqualSign - pucCursor + 1)); + bResult = (((pTxt = new stcMDNSServiceTxt)) && + (pTxt->setKey((const char*)pucCursor, ucKeyLength)) && + (pTxt->setValue((const char*)(pucEqualSign + 1), ucValueLength))); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: INVALID TXT format (No '=')!\n"));); + } + pucCursor += ucLength; + } + else // no/zero length TXT + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: TXT answer contains no items.\n"));); + bResult = true; + } + + if ((bResult) && + (pTxt)) // Everythings fine so far + { + // Link TXT item to answer TXTs + pTxt->m_pNext = p_rRRAnswerTXT.m_Txts.m_pTxts; + p_rRRAnswerTXT.m_Txts.m_pTxts = pTxt; + } + else // At least no TXT (migth be OK, if length was 0) OR an error + { + if (!bResult) + { + DEBUG_EX_ERR( + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: FAILED to read TXT item!\n")); + DEBUG_OUTPUT.printf_P(PSTR("RData dump:\n")); + _udpDump((m_pUDPContext->tell() - p_u16RDLength), p_u16RDLength); + DEBUG_OUTPUT.printf_P(PSTR("\n")); + ); + } + if (pTxt) + { + delete pTxt; + pTxt = 0; + } + p_rRRAnswerTXT.clear(); + } + } // while + + DEBUG_EX_ERR( + if (!bResult) // Some failure + { + DEBUG_OUTPUT.printf_P(PSTR("RData dump:\n")); + _udpDump((m_pUDPContext->tell() - p_u16RDLength), p_u16RDLength); + DEBUG_OUTPUT.printf_P(PSTR("\n")); + } + ); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: FAILED to read TXT content!\n"));); + } + // Clean up + delete[] pucBuffer; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: FAILED to alloc buffer for TXT content!\n"));); + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: WARNING! No content!\n"));); + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: FAILED!\n"));); + return bResult; +} + +#ifdef MDNS_IP6_SUPPORT +bool MDNSResponder::_readRRAnswerAAAA(MDNSResponder::stcMDNS_RRAnswerAAAA& p_rRRAnswerAAAA, + uint16_t p_u16RDLength) +{ + bool bResult = false; + // TODO: Implement + return bResult; +} +#endif + +/* + MDNSResponder::_readRRAnswerSRV +*/ +bool MDNSResponder::_readRRAnswerSRV(MDNSResponder::stcMDNS_RRAnswerSRV& p_rRRAnswerSRV, + uint16_t p_u16RDLength) +{ + + bool bResult = (((3 * sizeof(uint16_t)) < p_u16RDLength) && + (_udpRead16(p_rRRAnswerSRV.m_u16Priority)) && + (_udpRead16(p_rRRAnswerSRV.m_u16Weight)) && + (_udpRead16(p_rRRAnswerSRV.m_u16Port)) && + (_readRRDomain(p_rRRAnswerSRV.m_SRVDomain))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerSRV: FAILED!\n"));); + return bResult; +} + +/* + MDNSResponder::_readRRAnswerGeneric +*/ +bool MDNSResponder::_readRRAnswerGeneric(MDNSResponder::stcMDNS_RRAnswerGeneric& p_rRRAnswerGeneric, + uint16_t p_u16RDLength) +{ + bool bResult = (0 == p_u16RDLength); + + p_rRRAnswerGeneric.clear(); + if (((p_rRRAnswerGeneric.m_u16RDLength = p_u16RDLength)) && + ((p_rRRAnswerGeneric.m_pu8RDData = new unsigned char[p_rRRAnswerGeneric.m_u16RDLength]))) + { + + bResult = _udpReadBuffer(p_rRRAnswerGeneric.m_pu8RDData, p_rRRAnswerGeneric.m_u16RDLength); + } + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerGeneric: FAILED!\n"));); + return bResult; +} + +/* + MDNSResponder::_readRRHeader +*/ +bool MDNSResponder::_readRRHeader(MDNSResponder::stcMDNS_RRHeader& p_rRRHeader) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRHeader\n"));); + + bool bResult = ((_readRRDomain(p_rRRHeader.m_Domain)) && + (_readRRAttributes(p_rRRHeader.m_Attributes))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRHeader: FAILED!\n"));); + return bResult; +} + +/* + MDNSResponder::_readRRDomain + + Reads a (maybe multilevel compressed) domain from the UDP input buffer. + +*/ +bool MDNSResponder::_readRRDomain(MDNSResponder::stcMDNS_RRDomain& p_rRRDomain) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain\n"));); + + bool bResult = ((p_rRRDomain.clear()) && + (_readRRDomain_Loop(p_rRRDomain, 0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain: FAILED!\n"));); + return bResult; +} + +/* + MDNSResponder::_readRRDomain_Loop + + Reads a domain from the UDP input buffer. For every compression level, the functions + calls itself recursively. To avoid endless recursion because of malformed MDNS records, + the maximum recursion depth is set by MDNS_DOMAIN_MAX_REDIRCTION. + +*/ +bool MDNSResponder::_readRRDomain_Loop(MDNSResponder::stcMDNS_RRDomain& p_rRRDomain, + uint8_t p_u8Depth) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u)\n"), p_u8Depth);); + + bool bResult = false; + + if (MDNS_DOMAIN_MAX_REDIRCTION >= p_u8Depth) + { + bResult = true; + + uint8_t u8Len = 0; + do + { + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): Offset:%u p0:%02x\n"), p_u8Depth, m_pUDPContext->tell(), m_pUDPContext->peek());); + _udpRead8(u8Len); + + if (u8Len & MDNS_DOMAIN_COMPRESS_MARK) + { + // Compressed label(s) + uint16_t u16Offset = ((u8Len & ~MDNS_DOMAIN_COMPRESS_MARK) << 8); // Implicit BE to LE conversion! + _udpRead8(u8Len); + u16Offset |= u8Len; + + if (m_pUDPContext->isValidOffset(u16Offset)) + { + size_t stCurrentPosition = m_pUDPContext->tell(); // Prepare return from recursion + + //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): Redirecting from %u to %u!\n"), p_u8Depth, stCurrentPosition, u16Offset);); + m_pUDPContext->seek(u16Offset); + if (_readRRDomain_Loop(p_rRRDomain, p_u8Depth + 1)) // Do recursion + { + //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): Succeeded to read redirected label! Returning to %u\n"), p_u8Depth, stCurrentPosition);); + m_pUDPContext->seek(stCurrentPosition); // Restore after recursion + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): FAILED to read redirected label!\n"), p_u8Depth);); + bResult = false; + } + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): INVALID offset in redirection!\n"), p_u8Depth);); + bResult = false; + } + break; + } + else + { + // Normal (uncompressed) label (maybe '\0' only) + if (MDNS_DOMAIN_MAXLENGTH > (p_rRRDomain.m_u16NameLength + u8Len)) + { + // Add length byte + p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength] = u8Len; + ++(p_rRRDomain.m_u16NameLength); + if (u8Len) // Add name + { + if ((bResult = _udpReadBuffer((unsigned char*) & (p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength]), u8Len))) + { + /* DEBUG_EX_INFO( + p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength + u8Len] = 0; // Closing '\0' for printing + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): Domain label (%u): %s\n"), p_u8Depth, (unsigned)(p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength - 1]), &(p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength])); + );*/ + + p_rRRDomain.m_u16NameLength += u8Len; + } + } + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(2) offset:%u p0:%x\n"), m_pUDPContext->tell(), m_pUDPContext->peek());); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): ERROR! Domain name too long (%u + %u)!\n"), p_u8Depth, p_rRRDomain.m_u16NameLength, u8Len);); + bResult = false; + break; + } + } + } while ((bResult) && + (0 != u8Len)); + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): ERROR! Too many redirections!\n"), p_u8Depth);); + } + return bResult; +} + +/* + MDNSResponder::_readRRAttributes +*/ +bool MDNSResponder::_readRRAttributes(MDNSResponder::stcMDNS_RRAttributes& p_rRRAttributes) +{ + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAttributes\n"));); + + bool bResult = ((_udpRead16(p_rRRAttributes.m_u16Type)) && + (_udpRead16(p_rRRAttributes.m_u16Class))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAttributes: FAILED!\n"));); + return bResult; +} + + +/* + DOMAIN NAMES +*/ + +/* + MDNSResponder::_buildDomainForHost + + Builds a MDNS host domain (eg. esp8266.local) for the given hostname. + +*/ +bool MDNSResponder::_buildDomainForHost(const char* p_pcHostname, + MDNSResponder::stcMDNS_RRDomain& p_rHostDomain) const +{ + + p_rHostDomain.clear(); + bool bResult = ((p_pcHostname) && + (*p_pcHostname) && + (p_rHostDomain.addLabel(p_pcHostname)) && + (p_rHostDomain.addLabel(scpcLocal)) && + (p_rHostDomain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForHost: FAILED!\n"));); + return bResult; +} + +/* + MDNSResponder::_buildDomainForDNSSD + + Builds the '_services._dns-sd._udp.local' domain. + Used while detecting generic service enum question (DNS-SD) and answering these questions. + +*/ +bool MDNSResponder::_buildDomainForDNSSD(MDNSResponder::stcMDNS_RRDomain& p_rDNSSDDomain) const +{ + + p_rDNSSDDomain.clear(); + bool bResult = ((p_rDNSSDDomain.addLabel(scpcServices, true)) && + (p_rDNSSDDomain.addLabel(scpcDNSSD, true)) && + (p_rDNSSDDomain.addLabel(scpcUDP, true)) && + (p_rDNSSDDomain.addLabel(scpcLocal)) && + (p_rDNSSDDomain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForDNSSD: FAILED!\n"));); + return bResult; +} + +/* + MDNSResponder::_buildDomainForService + + Builds the domain for the given service (eg. _http._tcp.local or + MyESP._http._tcp.local (if p_bIncludeName is set)). + +*/ +bool MDNSResponder::_buildDomainForService(const MDNSResponder::stcMDNSService& p_Service, + bool p_bIncludeName, + MDNSResponder::stcMDNS_RRDomain& p_rServiceDomain) const +{ + + p_rServiceDomain.clear(); + bool bResult = (((!p_bIncludeName) || + (p_rServiceDomain.addLabel(p_Service.m_pcName))) && + (p_rServiceDomain.addLabel(p_Service.m_pcService, true)) && + (p_rServiceDomain.addLabel(p_Service.m_pcProtocol, true)) && + (p_rServiceDomain.addLabel(scpcLocal)) && + (p_rServiceDomain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForService: FAILED!\n"));); + return bResult; +} + +/* + MDNSResponder::_buildDomainForService + + Builds the domain for the given service properties (eg. _http._tcp.local). + The usual prepended '_' are added, if missing in the input strings. + +*/ +bool MDNSResponder::_buildDomainForService(const char* p_pcService, + const char* p_pcProtocol, + MDNSResponder::stcMDNS_RRDomain& p_rServiceDomain) const +{ + + p_rServiceDomain.clear(); + bool bResult = ((p_pcService) && + (p_pcProtocol) && + (p_rServiceDomain.addLabel(p_pcService, ('_' != *p_pcService))) && + (p_rServiceDomain.addLabel(p_pcProtocol, ('_' != *p_pcProtocol))) && + (p_rServiceDomain.addLabel(scpcLocal)) && + (p_rServiceDomain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForService: FAILED for (%s.%s)!\n"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); + return bResult; +} + +#ifdef MDNS_IP4_SUPPORT +/* + MDNSResponder::_buildDomainForReverseIP4 + + The IP4 address is stringized by printing the four address bytes into a char buffer in reverse order + and adding 'in-addr.arpa' (eg. 012.789.456.123.in-addr.arpa). + Used while detecting reverse IP4 questions and answering these +*/ +bool MDNSResponder::_buildDomainForReverseIP4(IPAddress p_IP4Address, + MDNSResponder::stcMDNS_RRDomain& p_rReverseIP4Domain) const +{ + + bool bResult = true; + + p_rReverseIP4Domain.clear(); + + char acBuffer[32]; + for (int i = MDNS_IP4_SIZE; ((bResult) && (i >= 1)); --i) + { + itoa(p_IP4Address[i - 1], acBuffer, 10); + bResult = p_rReverseIP4Domain.addLabel(acBuffer); + } + bResult = ((bResult) && + (p_rReverseIP4Domain.addLabel(scpcReverseIP4Domain)) && + (p_rReverseIP4Domain.addLabel(scpcReverseTopDomain)) && + (p_rReverseIP4Domain.addLabel(0))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForReverseIP4: FAILED!\n"));); + return bResult; +} +#endif + +#ifdef MDNS_IP6_SUPPORT +/* + MDNSResponder::_buildDomainForReverseIP6 + + Used while detecting reverse IP6 questions and answering these +*/ +bool MDNSResponder::_buildDomainForReverseIP6(IPAddress p_IP4Address, + MDNSResponder::stcMDNS_RRDomain& p_rReverseIP6Domain) const +{ + // TODO: Implement + return false; +} +#endif + + +/* + UDP +*/ + +/* + MDNSResponder::_udpReadBuffer +*/ +bool MDNSResponder::_udpReadBuffer(unsigned char* p_pBuffer, + size_t p_stLength) +{ + + bool bResult = ((m_pUDPContext) && + (true/*m_pUDPContext->getSize() > p_stLength*/) && + (p_pBuffer) && + (p_stLength) && + ((p_stLength == m_pUDPContext->read((char*)p_pBuffer, p_stLength)))); + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _udpReadBuffer: FAILED!\n")); + }); + return bResult; +} + +/* + MDNSResponder::_udpRead8 +*/ +bool MDNSResponder::_udpRead8(uint8_t& p_ru8Value) +{ + + return _udpReadBuffer((unsigned char*)&p_ru8Value, sizeof(p_ru8Value)); +} + +/* + MDNSResponder::_udpRead16 +*/ +bool MDNSResponder::_udpRead16(uint16_t& p_ru16Value) +{ + + bool bResult = false; + + if (_udpReadBuffer((unsigned char*)&p_ru16Value, sizeof(p_ru16Value))) + { + p_ru16Value = lwip_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 = lwip_ntohl(p_ru32Value); + bResult = true; + } + return bResult; +} + +/* + MDNSResponder::_udpAppendBuffer +*/ +bool MDNSResponder::_udpAppendBuffer(const unsigned char* p_pcBuffer, + size_t p_stLength) +{ + + bool bResult = ((m_pUDPContext) && + (p_pcBuffer) && + (p_stLength) && + (p_stLength == m_pUDPContext->append((const char*)p_pcBuffer, p_stLength))); + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _udpAppendBuffer: FAILED!\n")); + }); + return bResult; +} + +/* + MDNSResponder::_udpAppend8 +*/ +bool MDNSResponder::_udpAppend8(uint8_t p_u8Value) +{ + + return (_udpAppendBuffer((unsigned char*)&p_u8Value, sizeof(p_u8Value))); +} + +/* + MDNSResponder::_udpAppend16 +*/ +bool MDNSResponder::_udpAppend16(uint16_t p_u16Value) +{ + + p_u16Value = lwip_htons(p_u16Value); + return (_udpAppendBuffer((unsigned char*)&p_u16Value, sizeof(p_u16Value))); +} + +/* + MDNSResponder::_udpAppend32 +*/ +bool MDNSResponder::_udpAppend32(uint32_t p_u32Value) +{ + + p_u32Value = lwip_htonl(p_u32Value); + return (_udpAppendBuffer((unsigned char*)&p_u32Value, sizeof(p_u32Value))); +} + +#ifdef DEBUG_ESP_MDNS_RESPONDER +/* + MDNSResponder::_udpDump +*/ +bool MDNSResponder::_udpDump(bool p_bMovePointer /*= false*/) +{ + + const uint8_t cu8BytesPerLine = 16; + + uint32_t u32StartPosition = m_pUDPContext->tell(); + DEBUG_OUTPUT.println("UDP Context Dump:"); + uint32_t u32Counter = 0; + uint8_t u8Byte = 0; + + while (_udpRead8(u8Byte)) + { + DEBUG_OUTPUT.printf_P(PSTR("%02x %s"), u8Byte, ((++u32Counter % cu8BytesPerLine) ? "" : "\n")); + } + DEBUG_OUTPUT.printf_P(PSTR("%sDone: %u bytes\n"), (((u32Counter) && (u32Counter % cu8BytesPerLine)) ? "\n" : ""), u32Counter); + + if (!p_bMovePointer) // Restore + { + m_pUDPContext->seek(u32StartPosition); + } + return true; +} + +/* + MDNSResponder::_udpDump +*/ +bool MDNSResponder::_udpDump(unsigned p_uOffset, + unsigned p_uLength) +{ + + if ((m_pUDPContext) && + (m_pUDPContext->isValidOffset(p_uOffset))) + { + unsigned uCurrentPosition = m_pUDPContext->tell(); // Remember start position + + m_pUDPContext->seek(p_uOffset); + uint8_t u8Byte; + for (unsigned u = 0; ((u < p_uLength) && (_udpRead8(u8Byte))); ++u) + { + DEBUG_OUTPUT.printf_P(PSTR("%02x "), u8Byte); + } + // Return to start position + m_pUDPContext->seek(uCurrentPosition); + } + return true; +} +#endif + + +/** + READ/WRITE MDNS STRUCTS +*/ + +/* + MDNSResponder::_readMDNSMsgHeader + + Read a MDNS header from the UDP input buffer. + | 8 | 8 | 8 | 8 | + 00| Identifier | Flags & Codes | + 01| Question count | Answer count | + 02| NS answer count | Ad answer count | + + All 16-bit and 32-bit elements need to be translated form network coding to host coding (done in _udpRead16 and _udpRead32) + In addition, bitfield memory order is undefined in C standard (GCC doesn't order them in the coded direction...), so they + need some mapping here +*/ +bool MDNSResponder::_readMDNSMsgHeader(MDNSResponder::stcMDNS_MsgHeader& p_rMsgHeader) +{ + + bool bResult = false; + + uint8_t u8B1; + uint8_t u8B2; + if ((_udpRead16(p_rMsgHeader.m_u16ID)) && + (_udpRead8(u8B1)) && + (_udpRead8(u8B2)) && + (_udpRead16(p_rMsgHeader.m_u16QDCount)) && + (_udpRead16(p_rMsgHeader.m_u16ANCount)) && + (_udpRead16(p_rMsgHeader.m_u16NSCount)) && + (_udpRead16(p_rMsgHeader.m_u16ARCount))) + { + + p_rMsgHeader.m_1bQR = (u8B1 & 0x80); // Query/Responde flag + p_rMsgHeader.m_4bOpcode = (u8B1 & 0x78); // Operation code (0: Standard query, others ignored) + p_rMsgHeader.m_1bAA = (u8B1 & 0x04); // Authorative answer + p_rMsgHeader.m_1bTC = (u8B1 & 0x02); // Truncation flag + p_rMsgHeader.m_1bRD = (u8B1 & 0x01); // Recursion desired + + p_rMsgHeader.m_1bRA = (u8B2 & 0x80); // Recursion available + p_rMsgHeader.m_3bZ = (u8B2 & 0x70); // Zero + p_rMsgHeader.m_4bRCode = (u8B2 & 0x0F); // Response code + + /* DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readMDNSMsgHeader: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), + (unsigned)p_rMsgHeader.m_u16ID, + (unsigned)p_rMsgHeader.m_1bQR, (unsigned)p_rMsgHeader.m_4bOpcode, (unsigned)p_rMsgHeader.m_1bAA, (unsigned)p_rMsgHeader.m_1bTC, (unsigned)p_rMsgHeader.m_1bRD, + (unsigned)p_rMsgHeader.m_1bRA, (unsigned)p_rMsgHeader.m_4bRCode, + (unsigned)p_rMsgHeader.m_u16QDCount, + (unsigned)p_rMsgHeader.m_u16ANCount, + (unsigned)p_rMsgHeader.m_u16NSCount, + (unsigned)p_rMsgHeader.m_u16ARCount););*/ + bResult = true; + } + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readMDNSMsgHeader: FAILED!\n")); + }); + return bResult; +} + +/* + MDNSResponder::_write8 +*/ +bool MDNSResponder::_write8(uint8_t p_u8Value, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + + return ((_udpAppend8(p_u8Value)) && + (p_rSendParameter.shiftOffset(sizeof(p_u8Value)))); +} + +/* + MDNSResponder::_write16 +*/ +bool MDNSResponder::_write16(uint16_t p_u16Value, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + + return ((_udpAppend16(p_u16Value)) && + (p_rSendParameter.shiftOffset(sizeof(p_u16Value)))); +} + +/* + MDNSResponder::_write32 +*/ +bool MDNSResponder::_write32(uint32_t p_u32Value, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + + return ((_udpAppend32(p_u32Value)) && + (p_rSendParameter.shiftOffset(sizeof(p_u32Value)))); +} + +/* + MDNSResponder::_writeMDNSMsgHeader + + Write MDNS header to the UDP output buffer. + + All 16-bit and 32-bit elements need to be translated form host coding to network coding (done in _udpAppend16 and _udpAppend32) + In addition, bitfield memory order is undefined in C standard (GCC doesn't order them in the coded direction...), so they + need some mapping here +*/ +bool MDNSResponder::_writeMDNSMsgHeader(const MDNSResponder::stcMDNS_MsgHeader& p_MsgHeader, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + /* DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSMsgHeader: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), + (unsigned)p_MsgHeader.m_u16ID, + (unsigned)p_MsgHeader.m_1bQR, (unsigned)p_MsgHeader.m_4bOpcode, (unsigned)p_MsgHeader.m_1bAA, (unsigned)p_MsgHeader.m_1bTC, (unsigned)p_MsgHeader.m_1bRD, + (unsigned)p_MsgHeader.m_1bRA, (unsigned)p_MsgHeader.m_4bRCode, + (unsigned)p_MsgHeader.m_u16QDCount, + (unsigned)p_MsgHeader.m_u16ANCount, + (unsigned)p_MsgHeader.m_u16NSCount, + (unsigned)p_MsgHeader.m_u16ARCount););*/ + + uint8_t u8B1((p_MsgHeader.m_1bQR << 7) | (p_MsgHeader.m_4bOpcode << 3) | (p_MsgHeader.m_1bAA << 2) | (p_MsgHeader.m_1bTC << 1) | (p_MsgHeader.m_1bRD)); + uint8_t u8B2((p_MsgHeader.m_1bRA << 7) | (p_MsgHeader.m_3bZ << 4) | (p_MsgHeader.m_4bRCode)); + bool bResult = ((_write16(p_MsgHeader.m_u16ID, p_rSendParameter)) && + (_write8(u8B1, p_rSendParameter)) && + (_write8(u8B2, p_rSendParameter)) && + (_write16(p_MsgHeader.m_u16QDCount, p_rSendParameter)) && + (_write16(p_MsgHeader.m_u16ANCount, p_rSendParameter)) && + (_write16(p_MsgHeader.m_u16NSCount, p_rSendParameter)) && + (_write16(p_MsgHeader.m_u16ARCount, p_rSendParameter))); + + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSMsgHeader: FAILED!\n")); + }); + return bResult; +} + +/* + MDNSResponder::_writeRRAttributes +*/ +bool MDNSResponder::_writeMDNSRRAttributes(const MDNSResponder::stcMDNS_RRAttributes& p_Attributes, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + + bool bResult = ((_write16(p_Attributes.m_u16Type, p_rSendParameter)) && + (_write16(p_Attributes.m_u16Class, p_rSendParameter))); + + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSRRAttributes: FAILED!\n")); + }); + return bResult; +} + +/* + MDNSResponder::_writeMDNSRRDomain +*/ +bool MDNSResponder::_writeMDNSRRDomain(const MDNSResponder::stcMDNS_RRDomain& p_Domain, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + + bool bResult = ((_udpAppendBuffer((const unsigned char*)p_Domain.m_acName, p_Domain.m_u16NameLength)) && + (p_rSendParameter.shiftOffset(p_Domain.m_u16NameLength))); + + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSRRDomain: FAILED!\n")); + }); + return bResult; +} + +/* + MDNSResponder::_writeMDNSHostDomain + + Write a host domain to the UDP output buffer. + If the domain record is part of the answer, the records length is + prepended (p_bPrependRDLength is set). + + A very simple form of name compression is applied here: + If the domain is written to the UDP output buffer, the write offset is stored + together with a domain id (the pointer) in a p_rSendParameter substructure (cache). + If the same domain (pointer) should be written to the UDP output later again, + the old offset is retrieved from the cache, marked as a compressed domain offset + and written to the output buffer. + +*/ +bool MDNSResponder::_writeMDNSHostDomain(const char* p_pcHostname, + bool p_bPrependRDLength, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + + // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV' + uint16_t u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)p_pcHostname, false); + + stcMDNS_RRDomain hostDomain; + bool bResult = (u16CachedDomainOffset + // Found cached domain -> mark as compressed domain + ? ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset + ((!p_bPrependRDLength) || + (_write16(2, p_rSendParameter))) && // Length of 'Cxxx' + (_write8(((u16CachedDomainOffset >> 8) | MDNS_DOMAIN_COMPRESS_MARK), p_rSendParameter)) && // Compression mark (and offset) + (_write8((uint8_t)(u16CachedDomainOffset & 0xFF), p_rSendParameter))) + // No cached domain -> add this domain to cache and write full domain name + : ((_buildDomainForHost(p_pcHostname, hostDomain)) && // eg. esp8266.local + ((!p_bPrependRDLength) || + (_write16(hostDomain.m_u16NameLength, p_rSendParameter))) && // RDLength (if needed) + (p_rSendParameter.addDomainCacheItem((const void*)p_pcHostname, false, p_rSendParameter.m_u16Offset)) && + (_writeMDNSRRDomain(hostDomain, p_rSendParameter)))); + + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSHostDomain: FAILED!\n")); + }); + return bResult; + +} + +/* + MDNSResponder::_writeMDNSServiceDomain + + Write a service domain to the UDP output buffer. + If the domain record is part of the answer, the records length is + prepended (p_bPrependRDLength is set). + + A very simple form of name compression is applied here: see '_writeMDNSHostDomain' + The cache differentiates of course between service domains which includes + the instance name (p_bIncludeName is set) and thoose who don't. + +*/ +bool MDNSResponder::_writeMDNSServiceDomain(const MDNSResponder::stcMDNSService& p_Service, + bool p_bIncludeName, + bool p_bPrependRDLength, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + + // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV' + uint16_t u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)&p_Service, p_bIncludeName); + + stcMDNS_RRDomain serviceDomain; + bool bResult = (u16CachedDomainOffset + // Found cached domain -> mark as compressed domain + ? ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset + ((!p_bPrependRDLength) || + (_write16(2, p_rSendParameter))) && // Lenght of 'Cxxx' + (_write8(((u16CachedDomainOffset >> 8) | MDNS_DOMAIN_COMPRESS_MARK), p_rSendParameter)) && // Compression mark (and offset) + (_write8((uint8_t)(u16CachedDomainOffset & 0xFF), p_rSendParameter))) + // No cached domain -> add this domain to cache and write full domain name + : ((_buildDomainForService(p_Service, p_bIncludeName, serviceDomain)) && // eg. MyESP._http._tcp.local + ((!p_bPrependRDLength) || + (_write16(serviceDomain.m_u16NameLength, p_rSendParameter))) && // RDLength (if needed) + (p_rSendParameter.addDomainCacheItem((const void*)&p_Service, p_bIncludeName, p_rSendParameter.m_u16Offset)) && + (_writeMDNSRRDomain(serviceDomain, p_rSendParameter)))); + + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSServiceDomain: FAILED!\n")); + }); + return bResult; + +} + +/* + MDNSResponder::_writeMDNSQuestion + + Write a MDNS question to the UDP output buffer + + QNAME (host/service domain, eg. esp8266.local) + QTYPE (16bit, eg. ANY) + QCLASS (16bit, eg. IN) + +*/ +bool MDNSResponder::_writeMDNSQuestion(MDNSResponder::stcMDNS_RRQuestion& p_Question, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSQuestion\n"));); + + bool bResult = ((_writeMDNSRRDomain(p_Question.m_Header.m_Domain, p_rSendParameter)) && + (_writeMDNSRRAttributes(p_Question.m_Header.m_Attributes, p_rSendParameter))); + + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSQuestion: FAILED!\n")); + }); + return bResult; + +} + + +#ifdef MDNS_IP4_SUPPORT +/* + MDNSResponder::_writeMDNSAnswer_A + + Write a MDNS A answer to the UDP output buffer. + + NAME (var, host/service domain, eg. esp8266.local + TYPE (16bit, eg. A) + CLASS (16bit, eg. IN) + TTL (32bit, eg. 120) + RDLENGTH (16bit, eg 4) + RDATA (var, eg. 123.456.789.012) + + eg. esp8266.local A 0x8001 120 4 123.456.789.012 + Ref: http://www.zytrax.com/books/dns/ch8/a.html +*/ +bool MDNSResponder::_writeMDNSAnswer_A(IPAddress p_IPAddress, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_A (%s)\n"), p_IPAddress.toString().c_str());); + + stcMDNS_RRAttributes attributes(DNS_RRTYPE_A, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + const unsigned char aucIPAddress[MDNS_IP4_SIZE] = { p_IPAddress[0], p_IPAddress[1], p_IPAddress[2], p_IPAddress[3] }; + bool bResult = ((_writeMDNSHostDomain(m_pcHostname, false, p_rSendParameter)) && + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) && // TTL + (_write16(MDNS_IP4_SIZE, p_rSendParameter)) && // RDLength + (_udpAppendBuffer(aucIPAddress, MDNS_IP4_SIZE)) && // RData + (p_rSendParameter.shiftOffset(MDNS_IP4_SIZE))); + + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_A: FAILED!\n")); + }); + return bResult; + +} + +/* + MDNSResponder::_writeMDNSAnswer_PTR_IP4 + + Write a MDNS reverse IP4 PTR answer to the UDP output buffer. + See: '_writeMDNSAnswer_A' + + eg. 012.789.456.123.in-addr.arpa PTR 0x8001 120 15 esp8266.local + Used while answering reverse IP4 questions +*/ +bool MDNSResponder::_writeMDNSAnswer_PTR_IP4(IPAddress p_IPAddress, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_IP4 (%s)\n"), p_IPAddress.toString().c_str());); + + stcMDNS_RRDomain reverseIP4Domain; + stcMDNS_RRAttributes attributes(DNS_RRTYPE_PTR, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + stcMDNS_RRDomain hostDomain; + bool bResult = ((_buildDomainForReverseIP4(p_IPAddress, reverseIP4Domain)) && // 012.789.456.123.in-addr.arpa + (_writeMDNSRRDomain(reverseIP4Domain, p_rSendParameter)) && + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) && // TTL + (_writeMDNSHostDomain(m_pcHostname, true, p_rSendParameter))); // RDLength & RData (host domain, eg. esp8266.local) + + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_IP4: FAILED!\n")); + }); + return bResult; +} +#endif + +/* + MDNSResponder::_writeMDNSAnswer_PTR_TYPE + + Write a MDNS PTR answer to the UDP output buffer. + See: '_writeMDNSAnswer_A' + + PTR all-services -> service type + eg. _services._dns-sd._udp.local PTR 0x8001 5400 xx _http._tcp.local + http://www.zytrax.com/books/dns/ch8/ptr.html +*/ +bool MDNSResponder::_writeMDNSAnswer_PTR_TYPE(MDNSResponder::stcMDNSService& p_rService, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_TYPE\n"));); + + stcMDNS_RRDomain dnssdDomain; + stcMDNS_RRDomain serviceDomain; + stcMDNS_RRAttributes attributes(DNS_RRTYPE_PTR, DNS_RRCLASS_IN); // No cache flush! only INternet + bool bResult = ((_buildDomainForDNSSD(dnssdDomain)) && // _services._dns-sd._udp.local + (_writeMDNSRRDomain(dnssdDomain, p_rSendParameter)) && + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL + (_writeMDNSServiceDomain(p_rService, false, true, p_rSendParameter))); // RDLength & RData (service domain, eg. _http._tcp.local) + + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_TYPE: FAILED!\n")); + }); + return bResult; +} + +/* + MDNSResponder::_writeMDNSAnswer_PTR_NAME + + Write a MDNS PTR answer to the UDP output buffer. + See: '_writeMDNSAnswer_A' + + PTR service type -> service name + eg. _http.tcp.local PTR 0x8001 120 xx myESP._http._tcp.local + http://www.zytrax.com/books/dns/ch8/ptr.html +*/ +bool MDNSResponder::_writeMDNSAnswer_PTR_NAME(MDNSResponder::stcMDNSService& p_rService, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_NAME\n"));); + + stcMDNS_RRAttributes attributes(DNS_RRTYPE_PTR, DNS_RRCLASS_IN); // No cache flush! only INternet + bool bResult = ((_writeMDNSServiceDomain(p_rService, false, false, p_rSendParameter)) && // _http._tcp.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL + (_writeMDNSServiceDomain(p_rService, true, true, p_rSendParameter))); // RDLength & RData (service domain, eg. MyESP._http._tcp.local) + + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_NAME: FAILED!\n")); + }); + return bResult; +} + + +/* + MDNSResponder::_writeMDNSAnswer_TXT + + Write a MDNS TXT answer to the UDP output buffer. + See: '_writeMDNSAnswer_A' + + The TXT items in the RDATA block are 'length byte encoded': [len]vardata + + eg. myESP._http._tcp.local TXT 0x8001 120 4 c#=1 + http://www.zytrax.com/books/dns/ch8/txt.html +*/ +bool MDNSResponder::_writeMDNSAnswer_TXT(MDNSResponder::stcMDNSService& p_rService, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_TXT\n"));); + + bool bResult = false; + + stcMDNS_RRAttributes attributes(DNS_RRTYPE_TXT, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + + if ((_collectServiceTxts(p_rService)) && + (_writeMDNSServiceDomain(p_rService, true, false, p_rSendParameter)) && // MyESP._http._tcp.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL + (_write16(p_rService.m_Txts.length(), p_rSendParameter))) // RDLength + { + + bResult = true; + // RData Txts + for (stcMDNSServiceTxt* pTxt = p_rService.m_Txts.m_pTxts; ((bResult) && (pTxt)); pTxt = pTxt->m_pNext) + { + unsigned char ucLengthByte = pTxt->length(); + bResult = ((_udpAppendBuffer((unsigned char*)&ucLengthByte, sizeof(ucLengthByte))) && // Length + (p_rSendParameter.shiftOffset(sizeof(ucLengthByte))) && + ((size_t)os_strlen(pTxt->m_pcKey) == m_pUDPContext->append(pTxt->m_pcKey, os_strlen(pTxt->m_pcKey))) && // Key + (p_rSendParameter.shiftOffset((size_t)os_strlen(pTxt->m_pcKey))) && + (1 == m_pUDPContext->append("=", 1)) && // = + (p_rSendParameter.shiftOffset(1)) && + ((!pTxt->m_pcValue) || + (((size_t)os_strlen(pTxt->m_pcValue) == m_pUDPContext->append(pTxt->m_pcValue, os_strlen(pTxt->m_pcValue))) && // Value + (p_rSendParameter.shiftOffset((size_t)os_strlen(pTxt->m_pcValue)))))); + + DEBUG_EX_ERR(if (!bResult) + { + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_TXT: FAILED to write %sTxt %s=%s!\n"), (pTxt->m_bTemp ? "temp. " : ""), (pTxt->m_pcKey ? : "?"), (pTxt->m_pcValue ? : "?")); + }); + } + } + _releaseTempServiceTxts(p_rService); + + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_TXT: FAILED!\n")); + }); + return bResult; +} + +#ifdef MDNS_IP6_SUPPORT +/* + MDNSResponder::_writeMDNSAnswer_AAAA + + Write a MDNS AAAA answer to the UDP output buffer. + See: '_writeMDNSAnswer_A' + + eg. esp8266.local AAAA 0x8001 120 16 xxxx::xx + http://www.zytrax.com/books/dns/ch8/aaaa.html +*/ +bool MDNSResponder::_writeMDNSAnswer_AAAA(IPAddress p_IPAddress, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_AAAA\n"));); + + stcMDNS_RRAttributes attributes(DNS_RRTYPE_AAAA, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + bool bResult = ((_writeMDNSHostDomain(m_pcHostname, false, p_rSendParameter)) && // esp8266.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) && // TTL + (_write16(MDNS_IP6_SIZE, p_rSendParameter)) && // RDLength + (false /*TODO: IP6 version of: _udpAppendBuffer((uint32_t)p_IPAddress, MDNS_IP4_SIZE)*/)); // RData + + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_AAAA: FAILED!\n")); + }); + return bResult; +} + +/* + MDNSResponder::_writeMDNSAnswer_PTR_IP6 + + Write a MDNS reverse IP6 PTR answer to the UDP output buffer. + See: '_writeMDNSAnswer_A' + + eg. xxxx::xx.in6.arpa PTR 0x8001 120 15 esp8266.local + Used while answering reverse IP6 questions +*/ +bool MDNSResponder::_writeMDNSAnswer_PTR_IP6(IPAddress p_IPAddress, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_IP6\n"));); + + stcMDNS_RRDomain reverseIP6Domain; + stcMDNS_RRAttributes attributes(DNS_RRTYPE_PTR, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + bool bResult = ((_buildDomainForReverseIP6(p_IPAddress, reverseIP6Domain)) && // xxxx::xx.ip6.arpa + (_writeMDNSRRDomain(reverseIP6Domain, p_rSendParameter)) && + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) && // TTL + (_writeMDNSHostDomain(m_pcHostname, true, p_rSendParameter))); // RDLength & RData (host domain, eg. esp8266.local) + + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_IP6: FAILED!\n")); + }); + return bResult; +} +#endif + +/* + MDNSResponder::_writeMDNSAnswer_SRV + + eg. MyESP._http.tcp.local SRV 0x8001 120 0 0 60068 esp8266.local + http://www.zytrax.com/books/dns/ch8/srv.html ???? Include instance name ???? +*/ +bool MDNSResponder::_writeMDNSAnswer_SRV(MDNSResponder::stcMDNSService& p_rService, + MDNSResponder::stcMDNSSendParameter& p_rSendParameter) +{ + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_SRV\n"));); + + uint16_t u16CachedDomainOffset = (p_rSendParameter.m_bLegacyQuery + ? 0 + : p_rSendParameter.findCachedDomainOffset((const void*)m_pcHostname, false)); + + stcMDNS_RRAttributes attributes(DNS_RRTYPE_SRV, + ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet + stcMDNS_RRDomain hostDomain; + bool bResult = ((_writeMDNSServiceDomain(p_rService, true, false, p_rSendParameter)) && // MyESP._http._tcp.local + (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS + (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL + (!u16CachedDomainOffset + // No cache for domain name (or no compression allowed) + ? ((_buildDomainForHost(m_pcHostname, hostDomain)) && + (_write16((sizeof(uint16_t /*Prio*/) + // RDLength + sizeof(uint16_t /*Weight*/) + + sizeof(uint16_t /*Port*/) + + hostDomain.m_u16NameLength), p_rSendParameter)) && // Domain length + (_write16(MDNS_SRV_PRIORITY, p_rSendParameter)) && // Priority + (_write16(MDNS_SRV_WEIGHT, p_rSendParameter)) && // Weight + (_write16(p_rService.m_u16Port, p_rSendParameter)) && // Port + (p_rSendParameter.addDomainCacheItem((const void*)m_pcHostname, false, p_rSendParameter.m_u16Offset)) && + (_writeMDNSRRDomain(hostDomain, p_rSendParameter))) // Host, eg. esp8266.local + // Cache available for domain + : ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset + (_write16((sizeof(uint16_t /*Prio*/) + // RDLength + sizeof(uint16_t /*Weight*/) + + sizeof(uint16_t /*Port*/) + + 2), p_rSendParameter)) && // Length of 'C0xx' + (_write16(MDNS_SRV_PRIORITY, p_rSendParameter)) && // Priority + (_write16(MDNS_SRV_WEIGHT, p_rSendParameter)) && // Weight + (_write16(p_rService.m_u16Port, p_rSendParameter)) && // Port + (_write8(((u16CachedDomainOffset >> 8) | MDNS_DOMAIN_COMPRESS_MARK), p_rSendParameter)) && // Compression mark (and offset) + (_write8((uint8_t)u16CachedDomainOffset, p_rSendParameter))))); // Offset + + DEBUG_EX_ERR(if (!bResult) +{ + DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_SRV: FAILED!\n")); + }); + return bResult; +} + +} // namespace MDNSImplementation + +} // namespace esp8266 + + + + + + diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_lwIPdefs.h b/libraries/ESP8266mDNS/src/LEAmDNS_lwIPdefs.h new file mode 100644 index 0000000000..a3bcc4b370 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS_lwIPdefs.h @@ -0,0 +1,44 @@ +/* + 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 MDNS_LWIPDEFS_H +#define MDNS_LWIPDEFS_H + +#include +#if LWIP_VERSION_MAJOR == 1 + +#include // DNS_RRTYPE_xxx + +// cherry pick from lwip1 dns.c/mdns.c source files: +#define DNS_MQUERY_PORT 5353 +#define DNS_MQUERY_IPV4_GROUP_INIT IPAddress(224,0,0,251) /* resolver1.opendns.com */ +#define DNS_RRCLASS_ANY 255 /* any class */ + +#else // lwIP > 1 + +#include // DNS_RRTYPE_xxx, DNS_MQUERY_PORT + +#endif + +#endif // MDNS_LWIPDEFS_H From 0e692c3993ac222cd41bc2f33df63350ed2245ec Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 8 May 2020 21:45:55 +0200 Subject: [PATCH 007/152] add enableArduino() into LEAmDNSv2 --- .../OLDmDNS/ESP8266mDNS_Legacy.cpp | 1523 ---------- .../ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.h | 166 -- libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.cpp | 1381 --------- libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.h | 1461 ---------- .../ESP8266mDNS/OLDmDNS/LEAmDNS_Control.cpp | 2134 -------------- .../ESP8266mDNS/OLDmDNS/LEAmDNS_Helpers.cpp | 850 ------ libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Priv.h | 182 -- .../ESP8266mDNS/OLDmDNS/LEAmDNS_Structs.cpp | 2476 ----------------- .../ESP8266mDNS/OLDmDNS/LEAmDNS_Transfer.cpp | 1779 ------------ .../ESP8266mDNS/OLDmDNS/LEAmDNS_lwIPdefs.h | 44 - ...-mDNS-SPIFFS.ino => OTA-mDNS-LittleFS.ino} | 0 libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 30 + libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 2 + 13 files changed, 32 insertions(+), 11996 deletions(-) delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.cpp delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.h delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.cpp delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.h delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Control.cpp delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Helpers.cpp delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Priv.h delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Structs.cpp delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Transfer.cpp delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_lwIPdefs.h rename libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/{OTA-mDNS-SPIFFS.ino => OTA-mDNS-LittleFS.ino} (100%) diff --git a/libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.cpp b/libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.cpp deleted file mode 100644 index 8791195523..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.cpp +++ /dev/null @@ -1,1523 +0,0 @@ -/* - - ESP8266 Multicast DNS (port of CC3000 Multicast DNS library) - Version 1.1 - Copyright (c) 2013 Tony DiCola (tony@tonydicola.com) - ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com) - MDNS-SD Suport 2015 Hristo Gochkov - Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com) - - - License (MIT license): - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -*/ - -// Important RFC's for reference: -// - DNS request and response: http://www.ietf.org/rfc/rfc1035.txt -// - Multicast DNS: http://www.ietf.org/rfc/rfc6762.txt -// - MDNS-SD: https://tools.ietf.org/html/rfc6763 - -#ifndef LWIP_OPEN_SRC -#define LWIP_OPEN_SRC -#endif - -#include "ESP8266mDNS.h" -#include - -#include "debug.h" - -extern "C" { -#include "osapi.h" -#include "ets_sys.h" -#include "user_interface.h" -} - -#include "WiFiUdp.h" -#include "lwip/opt.h" -#include "lwip/udp.h" -#include "lwip/inet.h" -#include "lwip/igmp.h" -#include "lwip/mem.h" -#include "include/UdpContext.h" - - - -namespace Legacy_MDNSResponder -{ - - -#ifdef DEBUG_ESP_MDNS -#define DEBUG_ESP_MDNS_ERR -#define DEBUG_ESP_MDNS_TX -#define DEBUG_ESP_MDNS_RX -#endif - -#define MDNS_NAME_REF 0xC000 - -#define MDNS_TYPE_AAAA 0x001C -#define MDNS_TYPE_A 0x0001 -#define MDNS_TYPE_PTR 0x000C -#define MDNS_TYPE_SRV 0x0021 -#define MDNS_TYPE_TXT 0x0010 - -#define MDNS_CLASS_IN 0x0001 -#define MDNS_CLASS_IN_FLUSH_CACHE 0x8001 - -#define MDNS_ANSWERS_ALL 0x0F -#define MDNS_ANSWER_PTR 0x08 -#define MDNS_ANSWER_TXT 0x04 -#define MDNS_ANSWER_SRV 0x02 -#define MDNS_ANSWER_A 0x01 - -#define _conn_read32() (((uint32_t)_conn->read() << 24) | ((uint32_t)_conn->read() << 16) | ((uint32_t)_conn->read() << 8) | _conn->read()) -#define _conn_read16() (((uint16_t)_conn->read() << 8) | _conn->read()) -#define _conn_read8() _conn->read() -#define _conn_readS(b,l) _conn->read((char*)(b),l); - -static const IPAddress MDNS_MULTICAST_ADDR(224, 0, 0, 251); -static const int MDNS_MULTICAST_TTL = 1; -static const int MDNS_PORT = 5353; - -struct MDNSService -{ - MDNSService* _next; - char _name[32]; - char _proto[4]; - uint16_t _port; - uint16_t _txtLen; // length of all txts - struct MDNSTxt * _txts; -}; - -struct MDNSTxt -{ - MDNSTxt * _next; - String _txt; -}; - -struct MDNSAnswer -{ - MDNSAnswer* next; - uint8_t ip[4]; - uint16_t port; - char *hostname; -}; - -struct MDNSQuery -{ - char _service[32]; - char _proto[4]; -}; - - -MDNSResponder::MDNSResponder() : _conn(0) -{ - _services = 0; - _instanceName = ""; - _answers = 0; - _query = 0; - _newQuery = false; - _waitingForAnswers = false; -} -MDNSResponder::~MDNSResponder() -{ - if (_query != 0) - { - os_free(_query); - _query = 0; - } - - // Clear answer list - MDNSAnswer *answer; - int numAnswers = _getNumAnswers(); - for (int n = numAnswers - 1; n >= 0; n--) - { - answer = _getAnswerFromIdx(n); - os_free(answer->hostname); - os_free(answer); - answer = 0; - } - _answers = 0; - - if (_conn) - { - _conn->unref(); - } -} - -bool MDNSResponder::begin(const char* hostname) -{ - size_t n = strlen(hostname); - if (n > 63) // max size for a single label. - { - return false; - } - - // Copy in hostname characters as lowercase - _hostName = hostname; - _hostName.toLowerCase(); - - // If instance name is not already set copy hostname to instance name - if (_instanceName.equals("")) - { - _instanceName = hostname; - } - - _gotIPHandler = WiFi.onStationModeGotIP([this](const WiFiEventStationModeGotIP & event) - { - (void) event; - _restart(); - }); - - _disconnectedHandler = WiFi.onStationModeDisconnected([this](const WiFiEventStationModeDisconnected & event) - { - (void) event; - _restart(); - }); - - return _listen(); -} - -void MDNSResponder::notifyAPChange() -{ - _restart(); -} - -void MDNSResponder::_restart() -{ - if (_conn) - { - _conn->unref(); - _conn = nullptr; - } - _listen(); -} - -bool MDNSResponder::_listen() -{ - // Open the MDNS socket if it isn't already open. - if (!_conn) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.println("MDNS listening"); -#endif - - IPAddress mdns(MDNS_MULTICAST_ADDR); - - if (igmp_joingroup(IP4_ADDR_ANY4, mdns) != ERR_OK) - { - return false; - } - - _conn = new UdpContext; - _conn->ref(); - - if (!_conn->listen(IP_ADDR_ANY, MDNS_PORT)) - { - return false; - } - _conn->setMulticastTTL(MDNS_MULTICAST_TTL); - _conn->onRx(std::bind(&MDNSResponder::update, this)); - _conn->connect(mdns, MDNS_PORT); - } - return true; -} - -void MDNSResponder::update() -{ - if (!_conn || !_conn->next()) - { - return; - } - _parsePacket(); -} - - -void MDNSResponder::setInstanceName(String name) -{ - if (name.length() > 63) - { - return; - } - _instanceName = name; -} - - -bool MDNSResponder::addServiceTxt(char *name, char *proto, char *key, char *value) -{ - MDNSService* servicePtr; - - uint8_t txtLen = os_strlen(key) + os_strlen(value) + 1; // Add one for equals sign - txtLen += 1; //accounts for length byte added when building the txt responce - //Find the service - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - //Checking Service names - if (strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) - { - //found a service name match - if (servicePtr->_txtLen + txtLen > 1300) - { - return false; //max txt record size - } - MDNSTxt *newtxt = new MDNSTxt; - newtxt->_txt = String(key) + '=' + String(value); - newtxt->_next = 0; - if (servicePtr->_txts == 0) //no services have been added - { - //Adding First TXT to service - servicePtr->_txts = newtxt; - servicePtr->_txtLen += txtLen; - return true; - } - else - { - MDNSTxt * txtPtr = servicePtr->_txts; - while (txtPtr->_next != 0) - { - txtPtr = txtPtr->_next; - } - //adding another TXT to service - txtPtr->_next = newtxt; - servicePtr->_txtLen += txtLen; - return true; - } - } - } - return false; -} - -void MDNSResponder::addService(char *name, char *proto, uint16_t port) -{ - if (_getServicePort(name, proto) != 0) - { - return; - } - if (os_strlen(name) > 32 || os_strlen(proto) != 3) - { - return; //bad arguments - } - struct MDNSService *srv = (struct MDNSService*)(os_malloc(sizeof(struct MDNSService))); - os_strcpy(srv->_name, name); - os_strcpy(srv->_proto, proto); - srv->_port = port; - srv->_next = 0; - srv->_txts = 0; - srv->_txtLen = 0; - - if (_services == 0) - { - _services = srv; - } - else - { - MDNSService* servicePtr = _services; - while (servicePtr->_next != 0) - { - servicePtr = servicePtr->_next; - } - servicePtr->_next = srv; - } - -} - -int MDNSResponder::queryService(char *service, char *proto) -{ -#ifdef DEBUG_ESP_MDNS_TX - DEBUG_ESP_PORT.printf("queryService %s %s\n", service, proto); -#endif - while (_answers != 0) - { - MDNSAnswer *currAnswer = _answers; - _answers = _answers->next; - os_free(currAnswer->hostname); - os_free(currAnswer); - currAnswer = 0; - } - if (_query != 0) - { - os_free(_query); - _query = 0; - } - _query = (struct MDNSQuery*)(os_malloc(sizeof(struct MDNSQuery))); - os_strcpy(_query->_service, service); - os_strcpy(_query->_proto, proto); - _newQuery = true; - - char underscore[] = "_"; - - // build service name with _ - char serviceName[os_strlen(service) + 2]; - os_strcpy(serviceName, underscore); - os_strcat(serviceName, service); - size_t serviceNameLen = os_strlen(serviceName); - - //build proto name with _ - char protoName[5]; - os_strcpy(protoName, underscore); - os_strcat(protoName, proto); - size_t protoNameLen = 4; - - //local string - char localName[] = "local"; - size_t localNameLen = 5; - - //terminator - char terminator[] = "\0"; - - // Only supports sending one PTR query - uint8_t questionCount = 1; - - _waitingForAnswers = true; - for (int itfn = 0; itfn < 2; itfn++) - { - struct ip_info ip_info; - - wifi_get_ip_info((!itfn) ? SOFTAP_IF : STATION_IF, &ip_info); - if (!ip_info.ip.addr) - { - continue; - } - _conn->setMulticastInterface(IPAddress(ip_info.ip.addr)); - - // Write the header - _conn->flush(); - uint8_t head[12] = - { - 0x00, 0x00, //ID = 0 - 0x00, 0x00, //Flags = response + authoritative answer - 0x00, questionCount, //Question count - 0x00, 0x00, //Answer count - 0x00, 0x00, //Name server records - 0x00, 0x00 //Additional records - }; - _conn->append(reinterpret_cast(head), 12); - - // Only supports sending one PTR query - // Send the Name field (eg. "_http._tcp.local") - _conn->append(reinterpret_cast(&serviceNameLen), 1); // lenght of "_" + service - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_" + service - _conn->append(reinterpret_cast(&protoNameLen), 1); // lenght of "_" + proto - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_" + proto - _conn->append(reinterpret_cast(&localNameLen), 1); // lenght of "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type and class - uint8_t ptrAttrs[4] = - { - 0x00, 0x0c, //PTR record query - 0x00, 0x01 //Class IN - }; - _conn->append(reinterpret_cast(ptrAttrs), 4); - _conn->send(); - } - -#ifdef DEBUG_ESP_MDNS_TX - DEBUG_ESP_PORT.println("Waiting for answers.."); -#endif - delay(1000); - - _waitingForAnswers = false; - - return _getNumAnswers(); -} - -String MDNSResponder::hostname(int idx) -{ - MDNSAnswer *answer = _getAnswerFromIdx(idx); - if (answer == 0) - { - return String(); - } - return answer->hostname; -} - -IPAddress MDNSResponder::IP(int idx) -{ - MDNSAnswer *answer = _getAnswerFromIdx(idx); - if (answer == 0) - { - return IPAddress(); - } - return IPAddress(answer->ip); -} - -uint16_t MDNSResponder::port(int idx) -{ - MDNSAnswer *answer = _getAnswerFromIdx(idx); - if (answer == 0) - { - return 0; - } - return answer->port; -} - -MDNSAnswer* MDNSResponder::_getAnswerFromIdx(int idx) -{ - MDNSAnswer *answer = _answers; - while (answer != 0 && idx-- > 0) - { - answer = answer->next; - } - if (idx > 0) - { - return 0; - } - return answer; -} - -int MDNSResponder::_getNumAnswers() -{ - int numAnswers = 0; - MDNSAnswer *answer = _answers; - while (answer != 0) - { - numAnswers++; - answer = answer->next; - } - return numAnswers; -} - -MDNSTxt * MDNSResponder::_getServiceTxt(char *name, char *proto) -{ - MDNSService* servicePtr; - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - if (servicePtr->_port > 0 && strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) - { - if (servicePtr->_txts == 0) - { - return nullptr; - } - return servicePtr->_txts; - } - } - return nullptr; -} - -uint16_t MDNSResponder::_getServiceTxtLen(char *name, char *proto) -{ - MDNSService* servicePtr; - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - if (servicePtr->_port > 0 && strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) - { - if (servicePtr->_txts == 0) - { - return false; - } - return servicePtr->_txtLen; - } - } - return 0; -} - -uint16_t MDNSResponder::_getServicePort(char *name, char *proto) -{ - MDNSService* servicePtr; - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - if (servicePtr->_port > 0 && strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) - { - return servicePtr->_port; - } - } - return 0; -} - -IPAddress MDNSResponder::_getRequestMulticastInterface() -{ - struct ip_info ip_info; - bool match_ap = false; - if (wifi_get_opmode() & SOFTAP_MODE) - { - const IPAddress& remote_ip = _conn->getRemoteAddress(); - wifi_get_ip_info(SOFTAP_IF, &ip_info); - IPAddress infoIp(ip_info.ip); - IPAddress infoMask(ip_info.netmask); - if (ip_info.ip.addr && ip_addr_netcmp((const ip_addr_t*)remote_ip, (const ip_addr_t*)infoIp, ip_2_ip4((const ip_addr_t*)infoMask))) - { - match_ap = true; - } - } - if (!match_ap) - { - wifi_get_ip_info(STATION_IF, &ip_info); - } - return IPAddress(ip_info.ip.addr); -} - -void MDNSResponder::_parsePacket() -{ - int i; - char tmp; - bool serviceParsed = false; - bool protoParsed = false; - bool localParsed = false; - - char hostName[255]; - uint8_t hostNameLen; - - char serviceName[32]; - uint8_t serviceNameLen; - uint16_t servicePort = 0; - - char protoName[32]; - protoName[0] = 0; - uint8_t protoNameLen = 0; - - uint16_t packetHeader[6]; - - for (i = 0; i < 6; i++) - { - packetHeader[i] = _conn_read16(); - } - - if ((packetHeader[1] & 0x8000) != 0) // Read answers - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("Reading answers RX: REQ, ID:%u, Q:%u, A:%u, NS:%u, ADD:%u\n", packetHeader[0], packetHeader[2], packetHeader[3], packetHeader[4], packetHeader[5]); -#endif - - if (!_waitingForAnswers) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.println("Not expecting any answers right now, returning"); -#endif - _conn->flush(); - return; - } - - int numAnswers = packetHeader[3] + packetHeader[5]; - // Assume that the PTR answer always comes first and that it is always accompanied by a TXT, SRV, AAAA (optional) and A answer in the same packet. - if (numAnswers < 4) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("Expected a packet with 4 or more answers, got %u\n", numAnswers); -#endif - _conn->flush(); - return; - } - - uint8_t tmp8; - uint16_t answerPort = 0; - uint8_t answerIp[4] = { 0, 0, 0, 0 }; - char answerHostName[255]; - bool serviceMatch = false; - MDNSAnswer *answer; - uint8_t partsCollected = 0; - uint8_t stringsRead = 0; - - answerHostName[0] = '\0'; - - // Clear answer list - if (_newQuery) - { - int oldAnswers = _getNumAnswers(); - for (int n = oldAnswers - 1; n >= 0; n--) - { - answer = _getAnswerFromIdx(n); - os_free(answer->hostname); - os_free(answer); - answer = 0; - } - _answers = 0; - _newQuery = false; - } - - while (numAnswers--) - { - // Read name - stringsRead = 0; - size_t last_bufferpos = 0; - do - { - tmp8 = _conn_read8(); - if (tmp8 == 0x00) // End of name - { - break; - } - if (tmp8 & 0xC0) // Compressed pointer - { - uint16_t offset = ((((uint16_t)tmp8) & ~0xC0) << 8) | _conn_read8(); - if (_conn->isValidOffset(offset)) - { - if (0 == last_bufferpos) - { - last_bufferpos = _conn->tell(); - } -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Compressed pointer, jumping from "); - DEBUG_ESP_PORT.print(last_bufferpos); - DEBUG_ESP_PORT.print(" to "); - DEBUG_ESP_PORT.println(offset); -#endif - _conn->seek(offset); - tmp8 = _conn_read8(); - } - else - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Skipping malformed compressed pointer"); -#endif - tmp8 = _conn_read8(); - break; - } - } - if (stringsRead > 3) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.println("failed to read the response name"); -#endif - _conn->flush(); - return; - } - _conn_readS(serviceName, tmp8); - serviceName[tmp8] = '\0'; -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf(" %d ", tmp8); - for (int n = 0; n < tmp8; n++) - { - DEBUG_ESP_PORT.printf("%c", serviceName[n]); - } - DEBUG_ESP_PORT.println(); -#endif - if (serviceName[0] == '_') - { - if (strcmp(&serviceName[1], _query->_service) == 0) - { - serviceMatch = true; -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("found matching service: %s\n", _query->_service); -#endif - } - } - stringsRead++; - } while (true); - if (last_bufferpos > 0) - { - _conn->seek(last_bufferpos); -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Compressed pointer, jumping back to "); - DEBUG_ESP_PORT.println(last_bufferpos); -#endif - } - - uint16_t answerType = _conn_read16(); // Read type - uint16_t answerClass = _conn_read16(); // Read class - uint32_t answerTtl = _conn_read32(); // Read ttl - uint16_t answerRdlength = _conn_read16(); // Read rdlength - - (void) answerClass; - (void) answerTtl; - - if (answerRdlength > 255) - { - if (answerType == MDNS_TYPE_TXT && answerRdlength < 1460) - { - while (--answerRdlength) - { - _conn->read(); - } - } - else - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("Data len too long! %u\n", answerRdlength); -#endif - _conn->flush(); - return; - } - } - -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("type: %04x rdlength: %d\n", answerType, answerRdlength); -#endif - - if (answerType == MDNS_TYPE_PTR) - { - partsCollected |= 0x01; - _conn_readS(hostName, answerRdlength); // Read rdata - if (hostName[answerRdlength - 2] & 0xc0) - { - memcpy(answerHostName, hostName + 1, answerRdlength - 3); - answerHostName[answerRdlength - 3] = '\0'; - } -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("PTR %d ", answerRdlength); - for (int n = 0; n < answerRdlength; n++) - { - DEBUG_ESP_PORT.printf("%c", hostName[n]); - } - DEBUG_ESP_PORT.println(); -#endif - } - - else if (answerType == MDNS_TYPE_TXT) - { - partsCollected |= 0x02; - _conn_readS(hostName, answerRdlength); // Read rdata -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("TXT %d ", answerRdlength); - for (int n = 0; n < answerRdlength; n++) - { - DEBUG_ESP_PORT.printf("%c", hostName[n]); - } - DEBUG_ESP_PORT.println(); -#endif - } - - else if (answerType == MDNS_TYPE_SRV) - { - partsCollected |= 0x04; - uint16_t answerPrio = _conn_read16(); // Read priority - uint16_t answerWeight = _conn_read16(); // Read weight - answerPort = _conn_read16(); // Read port - last_bufferpos = 0; - - (void) answerPrio; - (void) answerWeight; - - // Read hostname - tmp8 = _conn_read8(); - if (tmp8 & 0xC0) // Compressed pointer - { - uint16_t offset = ((((uint16_t)tmp8) & ~0xC0) << 8) | _conn_read8(); - if (_conn->isValidOffset(offset)) - { - last_bufferpos = _conn->tell(); -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Compressed pointer, jumping from "); - DEBUG_ESP_PORT.print(last_bufferpos); - DEBUG_ESP_PORT.print(" to "); - DEBUG_ESP_PORT.println(offset); -#endif - _conn->seek(offset); - tmp8 = _conn_read8(); - } - else - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Skipping malformed compressed pointer"); -#endif - tmp8 = _conn_read8(); - break; - } - } - _conn_readS(answerHostName, tmp8); - answerHostName[tmp8] = '\0'; -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("SRV %d ", tmp8); - for (int n = 0; n < tmp8; n++) - { - DEBUG_ESP_PORT.printf("%02x ", answerHostName[n]); - } - DEBUG_ESP_PORT.printf("\n%s\n", answerHostName); -#endif - if (last_bufferpos > 0) - { - _conn->seek(last_bufferpos); - tmp8 = 2; // Size of compression octets -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Compressed pointer, jumping back to "); - DEBUG_ESP_PORT.println(last_bufferpos); -#endif - } - if (answerRdlength - (6 + 1 + tmp8) > 0) // Skip any remaining rdata - { - _conn_readS(hostName, answerRdlength - (6 + 1 + tmp8)); - } - } - - else if (answerType == MDNS_TYPE_A) - { - partsCollected |= 0x08; - for (int i = 0; i < 4; i++) - { - answerIp[i] = _conn_read8(); - } - } - else - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("Ignoring unsupported type %02x\n", tmp8); -#endif - for (int n = 0; n < answerRdlength; n++) - { - (void)_conn_read8(); - } - } - - if ((partsCollected == 0x0F) && serviceMatch) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.println("All answers parsed, adding to _answers list.."); -#endif - // Add new answer to answer list - if (_answers == 0) - { - _answers = (struct MDNSAnswer*)(os_malloc(sizeof(struct MDNSAnswer))); - answer = _answers; - } - else - { - answer = _answers; - while (answer->next != 0) - { - answer = answer->next; - } - answer->next = (struct MDNSAnswer*)(os_malloc(sizeof(struct MDNSAnswer))); - answer = answer->next; - } - answer->next = 0; - answer->hostname = 0; - - // Populate new answer - answer->port = answerPort; - for (int i = 0; i < 4; i++) - { - answer->ip[i] = answerIp[i]; - } - answer->hostname = (char *)os_malloc(strlen(answerHostName) + 1); - os_strcpy(answer->hostname, answerHostName); - _conn->flush(); - return; - } - } - - _conn->flush(); - return; - } - - // PARSE REQUEST NAME - - hostNameLen = _conn_read8() % 255; - _conn_readS(hostName, hostNameLen); - hostName[hostNameLen] = '\0'; - - if (hostName[0] == '_') - { - serviceParsed = true; - memcpy(serviceName, hostName + 1, hostNameLen); - serviceNameLen = hostNameLen - 1; - hostNameLen = 0; - } - - if (hostNameLen > 0 && !_hostName.equals(hostName) && !_instanceName.equals(hostName)) - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_NO_HOST: %s\n", hostName); - DEBUG_ESP_PORT.printf("hostname: %s\n", _hostName.c_str()); - DEBUG_ESP_PORT.printf("instance: %s\n", _instanceName.c_str()); -#endif - _conn->flush(); - return; - } - - if (!serviceParsed) - { - serviceNameLen = _conn_read8() % 255; - _conn_readS(serviceName, serviceNameLen); - serviceName[serviceNameLen] = '\0'; - - if (serviceName[0] == '_') - { - memmove(serviceName, serviceName + 1, serviceNameLen); - serviceNameLen--; - serviceParsed = true; - } - else if (serviceNameLen == 5 && strcmp("local", serviceName) == 0) - { - tmp = _conn_read8(); - if (tmp == 0) - { - serviceParsed = true; - serviceNameLen = 0; - protoParsed = true; - protoNameLen = 0; - localParsed = true; - } - else - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_FQDN: %s\n", serviceName); -#endif - _conn->flush(); - return; - } - } - else - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_SERVICE: %s\n", serviceName); -#endif - _conn->flush(); - return; - } - } - - if (!protoParsed) - { - protoNameLen = _conn_read8() % 255; - _conn_readS(protoName, protoNameLen); - protoName[protoNameLen] = '\0'; - if (protoNameLen == 4 && protoName[0] == '_') - { - memmove(protoName, protoName + 1, protoNameLen); - protoNameLen--; - protoParsed = true; - } - else if (strcmp("services", serviceName) == 0 && strcmp("_dns-sd", protoName) == 0) - { - _conn->flush(); - IPAddress interface = _getRequestMulticastInterface(); - _replyToTypeEnumRequest(interface); - return; - } - else - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_PROTO: %s\n", protoName); -#endif - _conn->flush(); - return; - } - } - - if (!localParsed) - { - char localName[32]; - uint8_t localNameLen = _conn_read8() % 31; - _conn_readS(localName, localNameLen); - localName[localNameLen] = '\0'; - tmp = _conn_read8(); - if (localNameLen == 5 && strcmp("local", localName) == 0 && tmp == 0) - { - localParsed = true; - } - else - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_FQDN: %s\n", localName); -#endif - _conn->flush(); - return; - } - } - - if (serviceNameLen > 0 && protoNameLen > 0) - { - servicePort = _getServicePort(serviceName, protoName); - if (servicePort == 0) - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_NO_SERVICE: %s\n", serviceName); -#endif - _conn->flush(); - return; - } - } - else if (serviceNameLen > 0 || protoNameLen > 0) - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_SERVICE_PROTO: %s\n", serviceName); -#endif - _conn->flush(); - return; - } - - // RESPOND - -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("RX: REQ, ID:%u, Q:%u, A:%u, NS:%u, ADD:%u\n", packetHeader[0], packetHeader[2], packetHeader[3], packetHeader[4], packetHeader[5]); -#endif - - uint16_t currentType; - uint16_t currentClass; - - int numQuestions = packetHeader[2]; - if (numQuestions > 4) - { - numQuestions = 4; - } - uint16_t questions[4]; - int question = 0; - - while (numQuestions--) - { - currentType = _conn_read16(); - if (currentType & MDNS_NAME_REF) //new header handle it better! - { - currentType = _conn_read16(); - } - currentClass = _conn_read16(); - if (currentClass & MDNS_CLASS_IN) - { - questions[question++] = currentType; - } - - if (numQuestions > 0) - { - if (_conn_read16() != 0xC00C) //new question but for another host/service - { - _conn->flush(); - numQuestions = 0; - } - } - -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("REQ: "); - if (hostNameLen > 0) - { - DEBUG_ESP_PORT.printf("%s.", hostName); - } - if (serviceNameLen > 0) - { - DEBUG_ESP_PORT.printf("_%s.", serviceName); - } - if (protoNameLen > 0) - { - DEBUG_ESP_PORT.printf("_%s.", protoName); - } - DEBUG_ESP_PORT.printf("local. "); - - if (currentType == MDNS_TYPE_AAAA) - { - DEBUG_ESP_PORT.printf(" AAAA "); - } - else if (currentType == MDNS_TYPE_A) - { - DEBUG_ESP_PORT.printf(" A "); - } - else if (currentType == MDNS_TYPE_PTR) - { - DEBUG_ESP_PORT.printf(" PTR "); - } - else if (currentType == MDNS_TYPE_SRV) - { - DEBUG_ESP_PORT.printf(" SRV "); - } - else if (currentType == MDNS_TYPE_TXT) - { - DEBUG_ESP_PORT.printf(" TXT "); - } - else - { - DEBUG_ESP_PORT.printf(" 0x%04X ", currentType); - } - - if (currentClass == MDNS_CLASS_IN) - { - DEBUG_ESP_PORT.printf(" IN "); - } - else if (currentClass == MDNS_CLASS_IN_FLUSH_CACHE) - { - DEBUG_ESP_PORT.printf(" IN[F] "); - } - else - { - DEBUG_ESP_PORT.printf(" 0x%04X ", currentClass); - } - - DEBUG_ESP_PORT.printf("\n"); -#endif - } - uint8_t questionMask = 0; - uint8_t responseMask = 0; - for (i = 0; i < question; i++) - { - if (questions[i] == MDNS_TYPE_A) - { - questionMask |= 0x1; - responseMask |= 0x1; - } - else if (questions[i] == MDNS_TYPE_SRV) - { - questionMask |= 0x2; - responseMask |= 0x3; - } - else if (questions[i] == MDNS_TYPE_TXT) - { - questionMask |= 0x4; - responseMask |= 0x4; - } - else if (questions[i] == MDNS_TYPE_PTR) - { - questionMask |= 0x8; - responseMask |= 0xF; - } - } - - IPAddress interface = _getRequestMulticastInterface(); - return _replyToInstanceRequest(questionMask, responseMask, serviceName, protoName, servicePort, interface); -} - - -/** - STRINGIZE -*/ -#ifndef STRINGIZE -#define STRINGIZE(x) #x -#endif -#ifndef STRINGIZE_VALUE_OF -#define STRINGIZE_VALUE_OF(x) STRINGIZE(x) -#endif - - -void MDNSResponder::enableArduino(uint16_t port, bool auth) -{ - - addService("arduino", "tcp", port); - addServiceTxt("arduino", "tcp", "tcp_check", "no"); - addServiceTxt("arduino", "tcp", "ssh_upload", "no"); - addServiceTxt("arduino", "tcp", "board", STRINGIZE_VALUE_OF(ARDUINO_BOARD)); - addServiceTxt("arduino", "tcp", "auth_upload", (auth) ? "yes" : "no"); -} - -void MDNSResponder::_replyToTypeEnumRequest(IPAddress multicastInterface) -{ - MDNSService* servicePtr; - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - if (servicePtr->_port > 0) - { - char *service = servicePtr->_name; - char *proto = servicePtr->_proto; - //uint16_t port = servicePtr->_port; - -#ifdef DEBUG_ESP_MDNS_TX - DEBUG_ESP_PORT.printf("TX: service:%s, proto:%s\n", service, proto); -#endif - - char sdHostName[] = "_services"; - size_t sdHostNameLen = 9; - char sdServiceName[] = "_dns-sd"; - size_t sdServiceNameLen = 7; - char sdProtoName[] = "_udp"; - size_t sdProtoNameLen = 4; - - char underscore[] = "_"; - - // build service name with _ - char serviceName[os_strlen(service) + 2]; - os_strcpy(serviceName, underscore); - os_strcat(serviceName, service); - size_t serviceNameLen = os_strlen(serviceName); - - //build proto name with _ - char protoName[5]; - os_strcpy(protoName, underscore); - os_strcat(protoName, proto); - size_t protoNameLen = 4; - - //local string - char localName[] = "local"; - size_t localNameLen = 5; - - //terminator - char terminator[] = "\0"; - - //Write the header - _conn->flush(); - uint8_t head[12] = - { - 0x00, 0x00, //ID = 0 - 0x84, 0x00, //Flags = response + authoritative answer - 0x00, 0x00, //Question count - 0x00, 0x01, //Answer count - 0x00, 0x00, //Name server records - 0x00, 0x00, //Additional records - }; - _conn->append(reinterpret_cast(head), 12); - - // Send the Name field (ie. "_services._dns-sd._udp.local") - _conn->append(reinterpret_cast(&sdHostNameLen), 1); // length of "_services" - _conn->append(reinterpret_cast(sdHostName), sdHostNameLen); // "_services" - _conn->append(reinterpret_cast(&sdServiceNameLen), 1); // length of "_dns-sd" - _conn->append(reinterpret_cast(sdServiceName), sdServiceNameLen);// "_dns-sd" - _conn->append(reinterpret_cast(&sdProtoNameLen), 1); // length of "_udp" - _conn->append(reinterpret_cast(sdProtoName), sdProtoNameLen); // "_udp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type, class, ttl and rdata length - uint8_t ptrDataLen = serviceNameLen + protoNameLen + localNameLen + 4; // 4 is three label sizes and the terminator - uint8_t ptrAttrs[10] = - { - 0x00, 0x0c, //PTR record query - 0x00, 0x01, //Class IN - 0x00, 0x00, 0x11, 0x94, //TTL 4500 - 0x00, ptrDataLen, //RData length - }; - _conn->append(reinterpret_cast(ptrAttrs), 10); - - //Send the RData (ie. "_http._tcp.local") - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - _conn->setMulticastInterface(multicastInterface); - _conn->send(); - } - } -} - -void MDNSResponder::_replyToInstanceRequest(uint8_t questionMask, uint8_t responseMask, char * service, char *proto, uint16_t port, IPAddress multicastInterface) -{ - int i; - if (questionMask == 0) - { - return; - } - if (responseMask == 0) - { - return; - } - -#ifdef DEBUG_ESP_MDNS_TX - DEBUG_ESP_PORT.printf("TX: qmask:%01X, rmask:%01X, service:%s, proto:%s, port:%u\n", questionMask, responseMask, service, proto, port); -#endif - - - String instanceName = _instanceName; - size_t instanceNameLen = instanceName.length(); - - String hostName = _hostName; - size_t hostNameLen = hostName.length(); - - char underscore[] = "_"; - - // build service name with _ - char serviceName[os_strlen(service) + 2]; - os_strcpy(serviceName, underscore); - os_strcat(serviceName, service); - size_t serviceNameLen = os_strlen(serviceName); - - //build proto name with _ - char protoName[5]; - os_strcpy(protoName, underscore); - os_strcat(protoName, proto); - size_t protoNameLen = 4; - - //local string - char localName[] = "local"; - size_t localNameLen = 5; - - //terminator - char terminator[] = "\0"; - - uint8_t answerMask = responseMask & questionMask; - uint8_t answerCount = 0; - uint8_t additionalMask = responseMask & ~questionMask; - uint8_t additionalCount = 0; - for (i = 0; i < 4; i++) - { - if (answerMask & (1 << i)) - { - answerCount++; - } - if (additionalMask & (1 << i)) - { - additionalCount++; - } - } - - - //Write the header - _conn->flush(); - uint8_t head[12] = - { - 0x00, 0x00, //ID = 0 - 0x84, 0x00, //Flags = response + authoritative answer - 0x00, 0x00, //Question count - 0x00, answerCount, //Answer count - 0x00, 0x00, //Name server records - 0x00, additionalCount, //Additional records - }; - _conn->append(reinterpret_cast(head), 12); - - for (int responseSection = 0; responseSection < 2; ++responseSection) - { - - // PTR Response - if ((responseSection == 0 ? answerMask : additionalMask) & 0x8) - { - // Send the Name field (ie. "_http._tcp.local") - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type, class, ttl and rdata length - uint8_t ptrDataLen = instanceNameLen + serviceNameLen + protoNameLen + localNameLen + 5; // 5 is four label sizes and the terminator - uint8_t ptrAttrs[10] = - { - 0x00, 0x0c, //PTR record query - 0x00, 0x01, //Class IN - 0x00, 0x00, 0x00, 0x78, //TTL 120 - 0x00, ptrDataLen, //RData length - }; - _conn->append(reinterpret_cast(ptrAttrs), 10); - - //Send the RData (ie. "My IOT device._http._tcp.local") - _conn->append(reinterpret_cast(&instanceNameLen), 1); // length of "My IOT device" - _conn->append(reinterpret_cast(instanceName.c_str()), instanceNameLen);// "My IOT device" - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - } - - //TXT Responce - if ((responseSection == 0 ? answerMask : additionalMask) & 0x4) - { - //Send the name field (ie. "My IOT device._http._tcp.local") - _conn->append(reinterpret_cast(&instanceNameLen), 1); // length of "My IOT device" - _conn->append(reinterpret_cast(instanceName.c_str()), instanceNameLen);// "My IOT device" - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type, class, ttl and rdata length - uint8_t txtDataLen = _getServiceTxtLen(service, proto); - uint8_t txtAttrs[10] = - { - 0x00, 0x10, //TXT record query - 0x80, 0x01, //Class IN, with cache flush - 0x00, 0x00, 0x11, 0x94, //TTL 4500 - 0x00, txtDataLen, //RData length - }; - _conn->append(reinterpret_cast(txtAttrs), 10); - - //Send the RData - MDNSTxt * txtPtr = _getServiceTxt(service, proto); - while (txtPtr != 0) - { - uint8_t txtLen = txtPtr->_txt.length(); - _conn->append(reinterpret_cast(&txtLen), 1); // length of txt - _conn->append(reinterpret_cast(txtPtr->_txt.c_str()), txtLen);// the txt - txtPtr = txtPtr->_next; - } - } - - - //SRV Responce - if ((responseSection == 0 ? answerMask : additionalMask) & 0x2) - { - //Send the name field (ie. "My IOT device._http._tcp.local") - _conn->append(reinterpret_cast(&instanceNameLen), 1); // length of "My IOT device" - _conn->append(reinterpret_cast(instanceName.c_str()), instanceNameLen);// "My IOT device" - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type, class, ttl, rdata length, priority and weight - uint8_t srvDataSize = hostNameLen + localNameLen + 3; // 3 is 2 lable size bytes and the terminator - srvDataSize += 6; // Size of Priority, weight and port - uint8_t srvAttrs[10] = - { - 0x00, 0x21, //Type SRV - 0x80, 0x01, //Class IN, with cache flush - 0x00, 0x00, 0x00, 0x78, //TTL 120 - 0x00, srvDataSize, //RData length - }; - _conn->append(reinterpret_cast(srvAttrs), 10); - - //Send the RData Priority weight and port - uint8_t srvRData[6] = - { - 0x00, 0x00, //Priority 0 - 0x00, 0x00, //Weight 0 - (uint8_t)((port >> 8) & 0xFF), (uint8_t)(port & 0xFF) - }; - _conn->append(reinterpret_cast(srvRData), 6); - //Send the RData (ie. "esp8266.local") - _conn->append(reinterpret_cast(&hostNameLen), 1); // length of "esp8266" - _conn->append(reinterpret_cast(hostName.c_str()), hostNameLen);// "esp8266" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - } - - // A Response - if ((responseSection == 0 ? answerMask : additionalMask) & 0x1) - { - //Send the RData (ie. "esp8266.local") - _conn->append(reinterpret_cast(&hostNameLen), 1); // length of "esp8266" - _conn->append(reinterpret_cast(hostName.c_str()), hostNameLen);// "esp8266" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - uint8_t aaaAttrs[10] = - { - 0x00, 0x01, //TYPE A - 0x80, 0x01, //Class IN, with cache flush - 0x00, 0x00, 0x00, 0x78, //TTL 120 - 0x00, 0x04, //DATA LEN - }; - _conn->append(reinterpret_cast(aaaAttrs), 10); - - // Send RData - uint32_t ip = multicastInterface; - uint8_t aaaRData[4] = - { - (uint8_t)(ip & 0xFF), //IP first octet - (uint8_t)((ip >> 8) & 0xFF), //IP second octet - (uint8_t)((ip >> 16) & 0xFF), //IP third octet - (uint8_t)((ip >> 24) & 0xFF) //IP fourth octet - }; - _conn->append(reinterpret_cast(aaaRData), 4); - } - } - - _conn->setMulticastInterface(multicastInterface); - _conn->send(); -} - -#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) -MDNSResponder MDNS; -#endif - -} // namespace Legacy_MDNSResponder - - - - diff --git a/libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.h b/libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.h deleted file mode 100644 index 9d3cfd2f62..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.h +++ /dev/null @@ -1,166 +0,0 @@ -/* - ESP8266 Multicast DNS (port of CC3000 Multicast DNS library) - Version 1.1 - Copyright (c) 2013 Tony DiCola (tony@tonydicola.com) - ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com) - Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com) - - This is a simple implementation of multicast DNS query support for an Arduino - running on ESP8266 chip. Only support for resolving address queries is currently - implemented. - - Requirements: - - ESP8266WiFi library - - Usage: - - Include the ESP8266 Multicast DNS library in the sketch. - - Call the begin method in the sketch's setup and provide a domain name (without - the '.local' suffix, i.e. just provide 'foo' to resolve 'foo.local'), and the - Adafruit CC3000 class instance. Optionally provide a time to live (in seconds) - for the DNS record--the default is 1 hour. - - Call the update method in each iteration of the sketch's loop function. - - License (MIT license): - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -*/ -#ifndef ESP8266MDNS_LEGACY_H -#define ESP8266MDNS_LEGACY_H - -#include "ESP8266WiFi.h" -#include "WiFiUdp.h" - -//this should be defined at build time -#ifndef ARDUINO_BOARD -#define ARDUINO_BOARD "generic" -#endif - -class UdpContext; - - -namespace Legacy_MDNSResponder -{ - - -struct MDNSService; -struct MDNSTxt; -struct MDNSAnswer; - -class MDNSResponder -{ -public: - MDNSResponder(); - ~MDNSResponder(); - bool begin(const char* hostName); - bool begin(const String& hostName) - { - return begin(hostName.c_str()); - } - //for compatibility - bool begin(const char* hostName, IPAddress ip, uint32_t ttl = 120) - { - (void) ip; - (void) ttl; - return begin(hostName); - } - bool begin(const String& hostName, IPAddress ip, uint32_t ttl = 120) - { - return begin(hostName.c_str(), ip, ttl); - } - /* Application should call this whenever AP is configured/disabled */ - void notifyAPChange(); - void update(); - - void addService(char *service, char *proto, uint16_t port); - void addService(const char *service, const char *proto, uint16_t port) - { - addService((char *)service, (char *)proto, port); - } - void addService(const String& service, const String& proto, uint16_t port) - { - addService(service.c_str(), proto.c_str(), port); - } - - bool addServiceTxt(char *name, char *proto, char * key, char * value); - bool addServiceTxt(const char *name, const char *proto, const char *key, const char * value) - { - return addServiceTxt((char *)name, (char *)proto, (char *)key, (char *)value); - } - bool addServiceTxt(const String& name, const String& proto, const String& key, const String& value) - { - return addServiceTxt(name.c_str(), proto.c_str(), key.c_str(), value.c_str()); - } - - int queryService(char *service, char *proto); - int queryService(const char *service, const char *proto) - { - return queryService((char *)service, (char *)proto); - } - int queryService(const String& service, const String& proto) - { - return queryService(service.c_str(), proto.c_str()); - } - String hostname(int idx); - IPAddress IP(int idx); - uint16_t port(int idx); - - void enableArduino(uint16_t port, bool auth = false); - - void setInstanceName(String name); - void setInstanceName(const char * name) - { - setInstanceName(String(name)); - } - void setInstanceName(char * name) - { - setInstanceName(String(name)); - } - -private: - struct MDNSService * _services; - UdpContext* _conn; - String _hostName; - String _instanceName; - struct MDNSAnswer * _answers; - struct MDNSQuery * _query; - bool _newQuery; - bool _waitingForAnswers; - WiFiEventHandler _disconnectedHandler; - WiFiEventHandler _gotIPHandler; - - - uint16_t _getServicePort(char *service, char *proto); - MDNSTxt * _getServiceTxt(char *name, char *proto); - uint16_t _getServiceTxtLen(char *name, char *proto); - IPAddress _getRequestMulticastInterface(); - void _parsePacket(); - void _replyToTypeEnumRequest(IPAddress multicastInterface); - void _replyToInstanceRequest(uint8_t questionMask, uint8_t responseMask, char * service, char *proto, uint16_t port, IPAddress multicastInterface); - MDNSAnswer* _getAnswerFromIdx(int idx); - int _getNumAnswers(); - bool _listen(); - void _restart(); -}; - -} // namespace Legacy_MDNSResponder - -#endif //ESP8266MDNS_H - - - diff --git a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.cpp b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.cpp deleted file mode 100644 index 87ff5167ff..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.cpp +++ /dev/null @@ -1,1381 +0,0 @@ -/* - 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 -#include - -#include "LEAmDNS_Priv.h" - - -namespace esp8266 -{ - -/* - LEAmDNS -*/ -namespace MDNSImplementation -{ - -/** - 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), -#ifdef ENABLE_ESP_MDNS_RESPONDER_PASSIV_MODE - m_bPassivModeEnabled(true), -#else - m_bPassivModeEnabled(false), -#endif - m_netif(nullptr) -{ -} - -/* - 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, const IPAddress& p_IPAddress, uint32_t p_u32TTL) -{ - - (void)p_u32TTL; // ignored - bool bResult = false; - - if (0 == m_pUDPContext) - { - if (_setHostname(p_pcHostname)) - { - - //// select interface - - m_netif = nullptr; - IPAddress ipAddress = p_IPAddress; - - if (!ipAddress.isSet()) - { - - IPAddress sta = WiFi.localIP(); - IPAddress ap = WiFi.softAPIP(); - - if (sta.isSet()) - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] STA interface selected\n"))); - ipAddress = sta; - } - else if (ap.isSet()) - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] AP interface selected\n"))); - ipAddress = ap; - } - else - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] standard interfaces are not up, please specify one in ::begin()\n"))); - return false; - } - - // continue to ensure interface is UP - } - - // check existence of this IP address in the interface list - bool found = false; - m_netif = nullptr; - for (auto a : addrList) - if (ipAddress == a.addr()) - { - if (a.ifUp()) - { - found = true; - m_netif = a.interface(); - break; - } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] found interface for IP '%s' but it is not UP\n"), ipAddress.toString().c_str());); - } - if (!found) - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] interface defined by IP '%s' not found\n"), ipAddress.toString().c_str());); - return false; - } - - //// done selecting the interface - - if (m_netif->num == STATION_IF) - { - - m_GotIPHandler = WiFi.onStationModeGotIP([this](const WiFiEventStationModeGotIP & pEvent) - { - (void) pEvent; - // Ensure that _restart() runs in USER context - schedule_function([this]() - { - MDNSResponder::_restart(); - }); - }); - - m_DisconnectedHandler = WiFi.onStationModeDisconnected([this](const WiFiEventStationModeDisconnected & pEvent) - { - (void) pEvent; - // Ensure that _restart() runs in USER context - schedule_function([this]() - { - MDNSResponder::_restart(); - }); - }); - } - - bResult = _restart(); - } - DEBUG_EX_ERR(if (!bResult) - { - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] begin: FAILED for '%s'!\n"), (p_pcHostname ? : "-")); - }); - } - else - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] begin: Ignoring multiple calls to begin (Ignored host domain: '%s')!\n"), (p_pcHostname ? : "-"));); - } - return bResult; -} - -/* - MDNSResponder::close - - Ends the MDNS responder. - Announced services are unannounced (by multicasting a goodbye message) - -*/ -bool MDNSResponder::close(void) -{ - bool bResult = false; - - if (0 != m_pUDPContext) - { - m_GotIPHandler.reset(); // reset WiFi event callbacks. - m_DisconnectedHandler.reset(); - - _announce(false, true); - _resetProbeStatus(false); // Stop probing - _releaseServiceQueries(); - _releaseUDPContext(); - _releaseHostname(); - - bResult = true; - } - else - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] close: Ignoring call to close!\n"));); - } - return bResult; -} - -/* - MDNSResponder::end - - Ends the MDNS responder. - for compatibility with esp32 - -*/ - -bool MDNSResponder::end(void) -{ - return close(); -} - -/* - 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("[MDNSResponder] setHostname: FAILED for '%s'!\n"), (p_pcHostname ? : "-")); - }); - return bResult; -} - -/* - MDNSResponder::setHostname (LEGACY) -*/ -bool MDNSResponder::setHostname(const 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_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] addService: %s to add '%s.%s.%s'!\n"), (hResult ? "Succeeded" : "FAILED"), (p_pcName ? : "-"), p_pcService, p_pcProtocol);); - DEBUG_EX_ERR(if (!hResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[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("[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(const String& p_strService, - const 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("[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("[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("[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("[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(const String& p_strService, - const String& p_strProtocol, - const String& p_strKey, - const 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::MDNSDynamicServiceTxtCallbackFunc p_fnCallback) -{ - - m_fnServiceTxtCallback = p_fnCallback; - - 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::MDNSDynamicServiceTxtCallbackFunc p_fnCallback) -{ - - bool bResult = false; - - stcMDNSService* pService = _findService(p_hService); - if (pService) - { - pService->m_fnTxtCallback = p_fnCallback; - - bResult = true; - } - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[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("[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("[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*/) -{ - if (0 == m_pUDPContext) - { - // safeguard against misuse - return 0; - } - - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[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("[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("[MDNSResponder] queryService: INVALID input data!\n"));); - } - 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(const String& p_strService, - const 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::MDNSServiceQueryCallbackFunc p_fnCallback) -{ - 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_bLegacyQuery = false; - - if (_sendMDNSServiceQuery(*pServiceQuery)) - { - pServiceQuery->m_u8SentCount = 1; - pServiceQuery->m_ResendTimeout.reset(MDNS_DYNAMIC_QUERY_RESEND_DELAY); - - hResult = (hMDNSServiceQuery)pServiceQuery; - } - else - { - _removeServiceQuery(pServiceQuery); - } - } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] installServiceQuery: %s for '%s.%s'!\n\n"), (hResult ? "Succeeded" : "FAILED"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); - DEBUG_EX_ERR(if (!hResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] installServiceQuery: FAILED for '%s.%s'!\n\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("[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); -} - -std::vector MDNSResponder::answerInfo(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery) -{ - std::vector tempVector; - for (uint32_t i = 0; i < answerCount(p_hServiceQuery); i++) - { - tempVector.emplace_back(*this, p_hServiceQuery, i); - } - return tempVector; -} - -/* - 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::setHostProbeResultCallback(MDNSResponder::MDNSHostProbeFn p_fnCallback) -{ - - m_HostProbeInformation.m_fnHostProbeResultCallback = p_fnCallback; - - return true; -} - -bool MDNSResponder::setHostProbeResultCallback(MDNSHostProbeFn1 pfn) -{ - using namespace std::placeholders; - return setHostProbeResultCallback([this, pfn](const char* p_pcDomainName, bool p_bProbeResult) - { - pfn(*this, p_pcDomainName, p_bProbeResult); - }); -} - -/* - 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::MDNSServiceProbeFn p_fnCallback) -{ - - bool bResult = false; - - stcMDNSService* pService = _findService(p_hService); - if (pService) - { - pService->m_ProbeInformation.m_fnServiceProbeResultCallback = p_fnCallback; - - bResult = true; - } - return bResult; -} - -bool MDNSResponder::setServiceProbeResultCallback(const MDNSResponder::hMDNSService p_hService, - MDNSResponder::MDNSServiceProbeFn1 p_fnCallback) -{ - using namespace std::placeholders; - return setServiceProbeResultCallback(p_hService, [this, p_fnCallback](const char* p_pcServiceName, const hMDNSService p_hMDNSService, bool p_bProbeResult) - { - p_fnCallback(*this, p_pcServiceName, p_hMDNSService, p_bProbeResult); - }); -} - - -/* - 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) -{ - - if (m_bPassivModeEnabled) - { - m_bPassivModeEnabled = false; - } - 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(true, true)); -} - -/* - 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; -} - - -} //namespace MDNSImplementation - -} //namespace esp8266 - - diff --git a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.h b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.h deleted file mode 100644 index fe02560aec..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.h +++ /dev/null @@ -1,1461 +0,0 @@ -/* - 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 -#include -#include - - -#include "ESP8266WiFi.h" - - -namespace esp8266 -{ - -/** - LEAmDNS -*/ -namespace MDNSImplementation -{ - -//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, ...) - // if interfaceAddress is not specified, default interface is STA, or AP when STA is not set - bool begin(const char* p_pcHostname, const IPAddress& p_IPAddress = INADDR_ANY, uint32_t p_u32TTL = 120 /*ignored*/); - bool begin(const String& p_strHostname, const IPAddress& p_IPAddress = INADDR_ANY, uint32_t p_u32TTL = 120 /*ignored*/) - { - return begin(p_strHostname.c_str(), p_IPAddress, p_u32TTL); - } - - // Finish MDNS processing - bool close(void); - // for esp32 compatability - bool end(void); - // Change hostname (probing is restarted) - bool setHostname(const char* p_pcHostname); - // for compatibility... - bool setHostname(const String& p_strHostname); - - bool isRunning(void) - { - return (m_pUDPContext != 0); - } - - /** - 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(const String& p_strServiceName, - const 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); - //for compatibility - //Warning: this has the side effect of changing the hostname. - //TODO: implement instancename different from hostname - void setInstanceName(const char* p_pcHostname) - { - setHostname(p_pcHostname); - } - // for esp32 compatibilty - void setInstanceName(const String& s_pcHostname) - { - setInstanceName(s_pcHostname.c_str()); - } - - /** - 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(const String& p_strService, - const String& p_strProtocol, - const String& p_strKey, - const String& p_strValue); - - /** - MDNSDynamicServiceTxtCallbackFn - Callback function for dynamic MDNS TXT items - */ - - typedef std::function MDNSDynamicServiceTxtCallbackFunc; - - // 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(MDNSDynamicServiceTxtCallbackFunc p_fnCallback); - // 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, - MDNSDynamicServiceTxtCallbackFunc p_fnCallback); - - // 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(const String& p_strService, - const 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; - - enum class AnswerType : uint32_t - { - Unknown = 0, - ServiceDomain = ServiceQueryAnswerType_ServiceDomain, - HostDomainAndPort = ServiceQueryAnswerType_HostDomainAndPort, - Txt = ServiceQueryAnswerType_Txts, -#ifdef MDNS_IP4_SUPPORT - IP4Address = ServiceQueryAnswerType_IP4Address, -#endif -#ifdef MDNS_IP6_SUPPORT - IP6Address = ServiceQueryAnswerType_IP6Address, -#endif - }; - - /** - MDNSServiceQueryCallbackFn - Callback function for received answers for dynamic service queries - */ - struct MDNSServiceInfo; // forward declaration - typedef std::function MDNSServiceQueryCallbackFunc; - - // 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, - MDNSServiceQueryCallbackFunc p_fnCallback); - // Remove a dynamic service query - bool removeServiceQuery(hMDNSServiceQuery p_hServiceQuery); - - uint32_t answerCount(const hMDNSServiceQuery p_hServiceQuery); - std::vector answerInfo(const MDNSResponder::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 std::function MDNSHostProbeFn; - - typedef std::function MDNSHostProbeFn1; - - typedef std::function MDNSServiceProbeFn; - - typedef std::function MDNSServiceProbeFn1; - - // Set a global callback function for host and service probe results - // The callback function is called, when the probing for the host domain - // (or a service domain, which hasn't got a service specific callback) - // Succeeds or fails. - // In case of failure, the failed domain name should be changed. - bool setHostProbeResultCallback(MDNSHostProbeFn p_fnCallback); - bool setHostProbeResultCallback(MDNSHostProbeFn1 p_fnCallback); - - // Set a service specific probe result callback - bool setServiceProbeResultCallback(const MDNSResponder::hMDNSService p_hService, - MDNSServiceProbeFn p_fnCallback); - bool setServiceProbeResultCallback(const MDNSResponder::hMDNSService p_hService, - MDNSServiceProbeFn1 p_fnCallback); - - // 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); - - /** STRUCTS **/ - -public: - /** - MDNSServiceInfo, used in application callbacks - */ - struct MDNSServiceInfo - { - MDNSServiceInfo(MDNSResponder& p_pM, MDNSResponder::hMDNSServiceQuery p_hS, uint32_t p_u32A) - : p_pMDNSResponder(p_pM), - p_hServiceQuery(p_hS), - p_u32AnswerIndex(p_u32A) - {}; - struct CompareKey - { - bool operator()(char const *a, char const *b) const - { - return strcmp(a, b) < 0; - } - }; - using KeyValueMap = std::map; - protected: - MDNSResponder& p_pMDNSResponder; - MDNSResponder::hMDNSServiceQuery p_hServiceQuery; - uint32_t p_u32AnswerIndex; - KeyValueMap keyValueMap; - public: - const char* serviceDomain() - { - return p_pMDNSResponder.answerServiceDomain(p_hServiceQuery, p_u32AnswerIndex); - }; - bool hostDomainAvailable() - { - return (p_pMDNSResponder.hasAnswerHostDomain(p_hServiceQuery, p_u32AnswerIndex)); - } - const char* hostDomain() - { - return (hostDomainAvailable()) ? - p_pMDNSResponder.answerHostDomain(p_hServiceQuery, p_u32AnswerIndex) : nullptr; - }; - bool hostPortAvailable() - { - return (p_pMDNSResponder.hasAnswerPort(p_hServiceQuery, p_u32AnswerIndex)); - } - uint16_t hostPort() - { - return (hostPortAvailable()) ? - p_pMDNSResponder.answerPort(p_hServiceQuery, p_u32AnswerIndex) : 0; - }; - bool IP4AddressAvailable() - { - return (p_pMDNSResponder.hasAnswerIP4Address(p_hServiceQuery, p_u32AnswerIndex)); - } - std::vector IP4Adresses() - { - std::vector internalIP; - if (IP4AddressAvailable()) - { - uint16_t cntIP4Adress = p_pMDNSResponder.answerIP4AddressCount(p_hServiceQuery, p_u32AnswerIndex); - for (uint32_t u2 = 0; u2 < cntIP4Adress; ++u2) - { - internalIP.emplace_back(p_pMDNSResponder.answerIP4Address(p_hServiceQuery, p_u32AnswerIndex, u2)); - } - } - return internalIP; - }; - bool txtAvailable() - { - return (p_pMDNSResponder.hasAnswerTxts(p_hServiceQuery, p_u32AnswerIndex)); - } - const char* strKeyValue() - { - return (txtAvailable()) ? - p_pMDNSResponder.answerTxts(p_hServiceQuery, p_u32AnswerIndex) : nullptr; - }; - const KeyValueMap& keyValues() - { - if (txtAvailable() && keyValueMap.size() == 0) - { - for (auto kv = p_pMDNSResponder._answerKeyValue(p_hServiceQuery, p_u32AnswerIndex); kv != nullptr; kv = kv->m_pNext) - { - keyValueMap.emplace(std::pair(kv->m_pcKey, kv->m_pcValue)); - } - } - return keyValueMap; - } - const char* value(const char* key) - { - char* result = nullptr; - - for (stcMDNSServiceTxt* pTxt = p_pMDNSResponder._answerKeyValue(p_hServiceQuery, p_u32AnswerIndex); pTxt; pTxt = pTxt->m_pNext) - { - if ((key) && - (0 == strcmp(pTxt->m_pcKey, key))) - { - result = pTxt->m_pcValue; - break; - } - } - return result; - } - }; -protected: - - /** - stcMDNSServiceTxt - */ - 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; - }; - - /** - stcMDNSTxts - */ - 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; - }; - - /** - 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 - */ - 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_RRDomain - */ - 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_RRAttributes - */ - 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_RRHeader - */ - 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_RRQuestion - */ - struct stcMDNS_RRQuestion - { - stcMDNS_RRQuestion* m_pNext; - stcMDNS_RRHeader m_Header; - bool m_bUnicast; // Unicast reply requested - - stcMDNS_RRQuestion(void); - }; - - /** - enuAnswerType - */ - typedef enum _enuAnswerType - { - AnswerType_A, - AnswerType_PTR, - AnswerType_TXT, - AnswerType_AAAA, - AnswerType_SRV, - AnswerType_Generic - } enuAnswerType; - - /** - stcMDNS_RRAnswer - */ - 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); - }; - -#ifdef MDNS_IP4_SUPPORT - /** - stcMDNS_RRAnswerA - */ - 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); - }; -#endif - - /** - stcMDNS_RRAnswerPTR - */ - 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_RRAnswerTXT - */ - 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); - }; - -#ifdef MDNS_IP6_SUPPORT - /** - stcMDNS_RRAnswerAAAA - */ - 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); - }; -#endif - - /** - stcMDNS_RRAnswerSRV - */ - 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_RRAnswerGeneric - */ - 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); - }; - - - /** - enuProbingStatus - */ - typedef enum _enuProbingStatus - { - ProbingStatus_WaitingForData, - ProbingStatus_ReadyToStart, - ProbingStatus_InProgress, - ProbingStatus_Done - } enuProbingStatus; - - /** - stcProbeInformation - */ - struct stcProbeInformation - { - enuProbingStatus m_ProbingStatus; - uint8_t m_u8SentCount; // Used for probes and announcements - esp8266::polledTimeout::oneShotMs m_Timeout; // Used for probes and announcements - //clsMDNSTimeFlag m_TimeFlag; // Used for probes and announcements - bool m_bConflict; - bool m_bTiebreakNeeded; - MDNSHostProbeFn m_fnHostProbeResultCallback; - MDNSServiceProbeFn m_fnServiceProbeResultCallback; - - stcProbeInformation(void); - - bool clear(bool p_bClearUserdata = false); - }; - - - /** - stcMDNSService - */ - 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; - MDNSDynamicServiceTxtCallbackFunc m_fnTxtCallback; - 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); - }; - - /** - stcMDNSServiceQuery - */ - struct stcMDNSServiceQuery - { - /** - stcAnswer - */ - struct stcAnswer - { - /** - stcTTL - */ - struct stcTTL - { - /** - timeoutLevel_t - */ - typedef uint8_t timeoutLevel_t; - /** - TIMEOUTLEVELs - */ - const timeoutLevel_t TIMEOUTLEVEL_UNSET = 0; - const timeoutLevel_t TIMEOUTLEVEL_BASE = 80; - const timeoutLevel_t TIMEOUTLEVEL_INTERVAL = 5; - const timeoutLevel_t TIMEOUTLEVEL_FINAL = 100; - - uint32_t m_u32TTL; - esp8266::polledTimeout::oneShotMs m_TTLTimeout; - timeoutLevel_t m_timeoutLevel; - - stcTTL(void); - bool set(uint32_t p_u32TTL); - - bool flagged(void); - bool restart(void); - - bool prepareDeletion(void); - bool finalTimeoutLevel(void) const; - - unsigned long timeout(void) const; - }; -#ifdef MDNS_IP4_SUPPORT - /** - stcIP4Address - */ - struct stcIP4Address - { - stcIP4Address* m_pNext; - IPAddress m_IPAddress; - stcTTL m_TTL; - - stcIP4Address(IPAddress p_IPAddress, - uint32_t p_u32TTL = 0); - }; -#endif -#ifdef MDNS_IP6_SUPPORT - /** - stcIP6Address - */ - struct stcIP6Address - { - stcIP6Address* m_pNext; - IP6Address m_IPAddress; - stcTTL m_TTL; - - stcIP6Address(IPAddress p_IPAddress, - uint32_t p_u32TTL = 0); - }; -#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 - }; - - stcMDNSServiceQuery* m_pNext; - stcMDNS_RRDomain m_ServiceTypeDomain; // eg. _http._tcp.local - MDNSServiceQueryCallbackFunc m_fnCallback; - bool m_bLegacyQuery; - uint8_t m_u8SentCount; - esp8266::polledTimeout::oneShotMs m_ResendTimeout; - 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); - }; - - /** - stcMDNSSendParameter - */ - struct stcMDNSSendParameter - { - protected: - /** - stcDomainCacheItem - */ - 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); - }; - - 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; - }; - - // Instance variables - stcMDNSService* m_pServices; - UdpContext* m_pUDPContext; - char* m_pcHostname; - stcMDNSServiceQuery* m_pServiceQueries; - WiFiEventHandler m_DisconnectedHandler; - WiFiEventHandler m_GotIPHandler; - MDNSDynamicServiceTxtCallbackFunc m_fnServiceTxtCallback; - bool m_bPassivModeEnabled; - stcProbeInformation m_HostProbeInformation; - CONST netif* m_netif; // network interface to run on - - /** 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, - bool p_bIncludeServices); - 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); - 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, - stcMDNSServiceQuery::stcAnswer* p_pKnownAnswers = 0); - - const IPAddress _getResponseMulticastInterface() const - { - return IPAddress(m_netif->ip_addr); - } - - 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); - - stcMDNSServiceTxt* _answerKeyValue(const hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex); - - 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 -}; - -}// namespace MDNSImplementation - -}// namespace esp8266 - -#endif // MDNS_H diff --git a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Control.cpp b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Control.cpp deleted file mode 100644 index 41e9524aba..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Control.cpp +++ /dev/null @@ -1,2134 +0,0 @@ -/* - 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 "LEAmDNS_lwIPdefs.h" -#include "LEAmDNS_Priv.h" - -namespace esp8266 -{ -/* - LEAmDNS -*/ -namespace MDNSImplementation -{ - -/** - CONTROL -*/ - - -/** - MAINTENANCE -*/ - -/* - MDNSResponder::_process - - Run the MDNS process. - Is called, every time the UDPContext receives data AND - should be called in every 'loop' by calling 'MDNS::update()'. - -*/ -bool MDNSResponder::_process(bool p_bUserContext) -{ - - bool bResult = true; - - if (!p_bUserContext) - { - - if ((m_pUDPContext) && // UDPContext available AND - (m_pUDPContext->next())) // has content - { - - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _update: Calling _parseMessage\n"));); - bResult = _parseMessage(); - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parsePacket %s\n"), (bResult ? "succeeded" : "FAILED"));); - } - } - else - { - bResult = (m_netif != nullptr) && - (m_netif->flags & NETIF_FLAG_UP) && // network interface is up and running - _updateProbeStatus() && // Probing - _checkServiceQueryCache(); // Service query cache check - } - return bResult; -} - -/* - MDNSResponder::_restart -*/ -bool MDNSResponder::_restart(void) -{ - - return ((m_netif != nullptr) && - (m_netif->flags & NETIF_FLAG_UP) && // network interface is up and running - (_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("[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("[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("[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("[MDNSResponder] _parseMessage: Received UNEXPECTED opcode:%u. Ignoring message!\n"), header.m_4bOpcode);); - m_pUDPContext->flush(); - } - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseMessage: FAILED to read header\n"));); - m_pUDPContext->flush(); - } - DEBUG_EX_INFO( - unsigned uFreeHeap = ESP.getFreeHeap(); - DEBUG_OUTPUT.printf_P(PSTR("[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) && (qd < p_MsgHeader.m_u16QDCount)); ++qd) - { - - stcMDNS_RRQuestion questionRR; - if ((bResult = _readRRQuestion(questionRR))) - { - // Define host replies, BUT only answer queries after probing is done - u8HostOrServiceReplies = - sendParameter.m_u8HostReplyMask |= (((m_bPassivModeEnabled) || - (ProbingStatus_Done == m_HostProbeInformation.m_ProbingStatus)) - ? _replyMaskForHost(questionRR.m_Header, 0) - : 0); - DEBUG_EX_INFO(if (u8HostOrServiceReplies) - { - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: Host reply needed 0x%X\n"), u8HostOrServiceReplies); - }); - - // Check tiebreak need for host domain - if (ProbingStatus_InProgress == m_HostProbeInformation.m_ProbingStatus) - { - bool bFullNameMatch = false; - if ((_replyMaskForHost(questionRR.m_Header, &bFullNameMatch)) && - (bFullNameMatch)) - { - // We're in 'probing' state and someone is asking for our host domain: this might be - // a race-condition: Two host with the same domain names try simutanously to probe their domains - // See: RFC 6762, 8.2 (Tiebraking) - // However, we're using a max. reduced approach for tiebreaking here: The higher IP-address wins! - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: Possible race-condition for host domain detected while probing.\n"));); - - m_HostProbeInformation.m_bTiebreakNeeded = true; - } - } - - // Define service replies - for (stcMDNSService* pService = m_pServices; pService; pService = pService->m_pNext) - { - // Define service replies, BUT only answer queries after probing is done - uint8_t u8ReplyMaskForQuestion = (((m_bPassivModeEnabled) || - (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("[MDNSResponder] _parseQuery: Service reply needed for (%s.%s.%s): 0x%X (%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("[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("[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)) && - (ip4_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)) && - (ip4_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("[MDNSResponder] _parseQuery: Legacy query from local host %s, id %u!\n"), IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str(), p_MsgHeader.m_u16ID);); - - 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("[MDNSResponder] _parseQuery: FAILED to add legacy question!\n"));); - } - } - else - { - DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: Legacy query from NON-LOCAL host!\n"));); - bResult = false; - } - } - } - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: FAILED to read question!\n"));); - } - } // for questions - - //DEBUG_EX_INFO(if (u8HostOrServiceReplies) { DEBUG_OUTPUT.printf_P(PSTR("[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("[MDNSResponder] _parseQuery: Known answers(%u):\n"), u32Answers); - }); - - for (uint32_t an = 0; ((bResult) && (an < u32Answers)); ++an) - { - stcMDNS_RRAnswer* pKnownRRAnswer = 0; - if (((bResult = _readRRAnswer(pKnownRRAnswer))) && - (pKnownRRAnswer)) - { - - if ((DNS_RRTYPE_ANY != pKnownRRAnswer->m_Header.m_Attributes.m_u16Type) && // No ANY type answer - (DNS_RRCLASS_ANY != pKnownRRAnswer->m_Header.m_Attributes.m_u16Class)) // 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("[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("[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())) - { - - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[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())) - { - - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[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()); - 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("[MDNSResponder] _parseQuery: Tiebreak (IP4) WON (was an old message)!\n"));); - m_HostProbeInformation.m_bTiebreakNeeded = false; - } - else - { - if ((uint32_t)(((stcMDNS_RRAnswerA*)pKnownRRAnswer)->m_IPAddress) > (uint32_t)localIPAddress) // The OTHER IP is 'higher' -> LOST - { - // LOST tiebreak - DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("[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("[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 (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("[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("[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("[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("[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("[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("[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("[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("[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("[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("[MDNSResponder] _parseQuery: FAILED to read known answer!\n"));); - } - - if (pKnownRRAnswer) - { - delete pKnownRRAnswer; - pKnownRRAnswer = 0; - } - } // 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) - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: Sending answer(0x%X)...\n"), u8ReplyNeeded);); - - sendParameter.m_bResponse = true; - sendParameter.m_bAuthorative = true; - - bResult = _sendMDNSMessage(sendParameter); - } - DEBUG_EX_INFO( - else - { - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: No reply needed\n")); - } - ); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[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("[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("[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("[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("[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("[MDNSResponder] _parseResponse: Received a response\n")); - //_udpDump(); - ); - - bResult = true; - // - // Ignore questions here - stcMDNS_RRQuestion dummyRRQ; - for (uint16_t qd = 0; ((bResult) && (qd < p_MsgHeader.m_u16QDCount)); ++qd) - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseResponse: Received a response containing a question... ignoring!\n"));); - bResult = _readRRQuestion(dummyRRQ); - } // for queries - - // - // Read and collect answers - stcMDNS_RRAnswer* pCollectedRRAnswers = 0; - uint32_t u32NumberOfAnswerRRs = (p_MsgHeader.m_u16ANCount + p_MsgHeader.m_u16NSCount + p_MsgHeader.m_u16ARCount); - for (uint32_t an = 0; ((bResult) && (an < u32NumberOfAnswerRRs)); ++an) - { - stcMDNS_RRAnswer* pRRAnswer = 0; - if (((bResult = _readRRAnswer(pRRAnswer))) && - (pRRAnswer)) - { - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseResponse: ADDING answer!\n"));); - pRRAnswer->m_pNext = pCollectedRRAnswers; - pCollectedRRAnswers = pRRAnswer; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseResponse: FAILED to read answer!\n"));); - if (pRRAnswer) - { - delete pRRAnswer; - pRRAnswer = 0; - } - bResult = false; - } - } // for answers - - // - // Process answers - if (bResult) - { - bResult = ((!pCollectedRRAnswers) || - (_processAnswers(pCollectedRRAnswers))); - } - else // Some failure while reading answers - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseResponse: FAILED to read answers!\n"));); - m_pUDPContext->flush(); - } - - // Delete collected answers - while (pCollectedRRAnswers) - { - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[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("[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("[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("[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("[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("[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) - } while ((bFoundNewKeyAnswer) && - (bResult)); - } // else: No answers provided - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[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("[MDNSResponder] _processPTRAnswer: Processing PTR answers...\n"));); - // eg. _http._tcp.local PTR xxxx xx MyESP._http._tcp.local - // Check pending service queries for eg. '_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); // Update TTL tag - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processPTRAnswer: Updated TTL(%d) for "), (int)p_pPTRAnswer->m_u32TTL); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR("\n")); - ); - } - else // received goodbye-message - { - pSQAnswer->m_TTLServiceDomain.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processPTRAnswer: 'Goodbye' received for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR("\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); - pSQAnswer->releaseServiceDomain(); - - bResult = pServiceQuery->addAnswer(pSQAnswer); - p_rbFoundNewKeyAnswer = true; - if (pServiceQuery->m_fnCallback) - { - MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); - pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_ServiceDomain), true); - } - } - } - pServiceQuery = _findNextServiceQueryByServiceType(p_pPTRAnswer->m_Header.m_Domain, pServiceQuery); - } - } // else: No p_pPTRAnswer - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[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); // Update TTL tag - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processSRVAnswer: Updated TTL(%d) for "), (int)p_pSRVAnswer->m_u32TTL); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" host domain and port\n")); - ); - // Host domain & Port - if ((pSQAnswer->m_HostDomain != p_pSRVAnswer->m_SRVDomain) || - (pSQAnswer->m_u16Port != p_pSRVAnswer->m_u16Port)) - { - - pSQAnswer->m_HostDomain = p_pSRVAnswer->m_SRVDomain; - pSQAnswer->releaseHostDomain(); - pSQAnswer->m_u16Port = p_pSRVAnswer->m_u16Port; - pSQAnswer->m_u32ContentFlags |= ServiceQueryAnswerType_HostDomainAndPort; - - p_rbFoundNewKeyAnswer = true; - if (pServiceQuery->m_fnCallback) - { - MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); - pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_HostDomainAndPort), true); - } - } - } - else // Goodby message - { - pSQAnswer->m_TTLHostDomainAndPort.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processSRVAnswer: 'Goodbye' received for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" 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("[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); // Update TTL tag - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processTXTAnswer: Updated TTL(%d) for "), (int)p_pTXTAnswer->m_u32TTL); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" TXTs\n")); - ); - if (!pSQAnswer->m_Txts.compare(p_pTXTAnswer->m_Txts)) - { - pSQAnswer->m_Txts = p_pTXTAnswer->m_Txts; - pSQAnswer->m_u32ContentFlags |= ServiceQueryAnswerType_Txts; - pSQAnswer->releaseTxts(); - - if (pServiceQuery->m_fnCallback) - { - MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); - pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_Txts), true); - } - } - } - else // Goodby message - { - pSQAnswer->m_TTLTxts.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processTXTAnswer: 'Goodbye' received for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" TXTs\n")); - ); - } - } - pServiceQuery = pServiceQuery->m_pNext; - } // while(service query) - } // else: No p_pTXTAnswer - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[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); - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processAAnswer: Updated TTL(%d) for "), (int)p_pAAnswer->m_u32TTL); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" IP4Address (%s)\n"), pIP4Address->m_IPAddress.toString().c_str()); - ); - } - else // 'Goodbye' message for known IP4 address - { - pIP4Address->m_TTL.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processAAnswer: 'Goodbye' received for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" 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); - if ((pIP4Address) && - (pSQAnswer->addIP4Address(pIP4Address))) - { - - pSQAnswer->m_u32ContentFlags |= ServiceQueryAnswerType_IP4Address; - if (pServiceQuery->m_fnCallback) - { - MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); - pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_IP4Address), true); - } - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[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("[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); - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processAAnswer: Updated TTL(%lu) for "), p_pAAAAAnswer->m_u32TTL); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" IP6 address (%s)\n"), pIP6Address->m_IPAddress.toString().c_str()); - ); - } - else // 'Goodbye' message for known IP6 address - { - pIP6Address->m_TTL.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processAAnswer: 'Goodbye' received for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" 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); - 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("[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 - //TODO: Fix the following to allow Ethernet shield or other interfaces - (_getResponseMulticastInterface() != IPAddress())) // Has IP address - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Starting host probing...\n"));); - - // First probe delay SHOULD be random 0-250 ms - m_HostProbeInformation.m_Timeout.reset(rand() % MDNS_PROBE_DELAY); - m_HostProbeInformation.m_ProbingStatus = ProbingStatus_InProgress; - } - else if ((ProbingStatus_InProgress == m_HostProbeInformation.m_ProbingStatus) && // Probing AND - (m_HostProbeInformation.m_Timeout.expired())) // Time for next probe - { - - if (MDNS_PROBE_COUNT > m_HostProbeInformation.m_u8SentCount) // Send next probe - { - if ((bResult = _sendHostProbe())) - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Did sent host probe\n\n"));); - m_HostProbeInformation.m_Timeout.reset(MDNS_PROBE_DELAY); - ++m_HostProbeInformation.m_u8SentCount; - } - } - else // Probing finished - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Done host probing.\n"));); - m_HostProbeInformation.m_ProbingStatus = ProbingStatus_Done; - m_HostProbeInformation.m_Timeout.resetToNeverExpires(); - if (m_HostProbeInformation.m_fnHostProbeResultCallback) - { - m_HostProbeInformation.m_fnHostProbeResultCallback(m_pcHostname, true); - } - - // Prepare to announce host - m_HostProbeInformation.m_u8SentCount = 0; - m_HostProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Prepared host announcing.\n\n"));); - } - } // else: Probing already finished OR waiting for next time slot - else if ((ProbingStatus_Done == m_HostProbeInformation.m_ProbingStatus) && - (m_HostProbeInformation.m_Timeout.expired())) - { - - if ((bResult = _announce(true, false))) // Don't announce services here - { - ++m_HostProbeInformation.m_u8SentCount; - - if (MDNS_ANNOUNCE_COUNT > m_HostProbeInformation.m_u8SentCount) - { - m_HostProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Announcing host (%d).\n\n"), m_HostProbeInformation.m_u8SentCount);); - } - else - { - m_HostProbeInformation.m_Timeout.resetToNeverExpires(); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Done host announcing.\n\n"));); - } - } - } - - // - // 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_Timeout.reset(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_Timeout.expired())) // Time for next probe - { - - if (MDNS_PROBE_COUNT > pService->m_ProbeInformation.m_u8SentCount) // Send next probe - { - if ((bResult = _sendServiceProbe(*pService))) - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Did sent service probe (%u)\n\n"), (pService->m_ProbeInformation.m_u8SentCount + 1));); - pService->m_ProbeInformation.m_Timeout.reset(MDNS_PROBE_DELAY); - ++pService->m_ProbeInformation.m_u8SentCount; - } - } - else // Probing finished - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Done service probing %s.%s.%s\n\n"), (pService->m_pcName ? : m_pcHostname), pService->m_pcService, pService->m_pcProtocol);); - pService->m_ProbeInformation.m_ProbingStatus = ProbingStatus_Done; - pService->m_ProbeInformation.m_Timeout.resetToNeverExpires(); - if (pService->m_ProbeInformation.m_fnServiceProbeResultCallback) - { - pService->m_ProbeInformation.m_fnServiceProbeResultCallback(pService->m_pcName, pService, true); - } - // Prepare to announce service - pService->m_ProbeInformation.m_u8SentCount = 0; - pService->m_ProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Prepared service announcing.\n\n"));); - } - } // else: Probing already finished OR waiting for next time slot - else if ((ProbingStatus_Done == pService->m_ProbeInformation.m_ProbingStatus) && - (pService->m_ProbeInformation.m_Timeout.expired())) - { - - if ((bResult = _announceService(*pService))) // Announce service - { - ++pService->m_ProbeInformation.m_u8SentCount; - - if (MDNS_ANNOUNCE_COUNT > pService->m_ProbeInformation.m_u8SentCount) - { - pService->m_ProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Announcing service %s.%s.%s (%d)\n\n"), (pService->m_pcName ? : m_pcHostname), pService->m_pcService, pService->m_pcProtocol, pService->m_ProbeInformation.m_u8SentCount);); - } - else - { - pService->m_ProbeInformation.m_Timeout.resetToNeverExpires(); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Done service announcing for %s.%s.%s\n\n"), (pService->m_pcName ? : m_pcHostname), pService->m_pcService, pService->m_pcProtocol);); - } - } - } - } - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: FAILED!\n\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_u8SentCount)); // 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_u8SentCount)); // 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("[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("[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("[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("[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("[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("[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_fnHostProbeResultCallback) - { - m_HostProbeInformation.m_fnHostProbeResultCallback(m_pcHostname, false); - - 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 - if (p_rService.m_ProbeInformation.m_fnServiceProbeResultCallback) - { - p_rService.m_ProbeInformation.m_fnServiceProbeResultCallback(p_rService.m_pcName, &p_rService, false); - 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, - bool p_bIncludeServices) -{ - - 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("[MDNSResponder] _announce: Announcing host %s (content 0x%X)\n"), m_pcHostname, sendParameter.m_u8HostReplyMask);); - - if (p_bIncludeServices) - { - // Announce services (service type, name, SRV (location) and TXTs) - for (stcMDNSService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) - { - 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("[MDNSResponder] _announce: Announcing service %s.%s.%s (content %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("[MDNSResponder] _announce: FAILED!\n")); - }); - return ((bResult) && - (_sendMDNSMessage(sendParameter))); -} - -/* - 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("[MDNSResponder] _announceService: Announcing service %s.%s.%s (content 0x%X)\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("[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; - - DEBUG_EX_INFO( - bool printedInfo = false; - ); - for (stcMDNSServiceQuery* pServiceQuery = m_pServiceQueries; ((bResult) && (pServiceQuery)); pServiceQuery = pServiceQuery->m_pNext) - { - - // - // Resend dynamic service queries, if not already done often enough - if ((!pServiceQuery->m_bLegacyQuery) && - (MDNS_DYNAMIC_QUERY_RESEND_COUNT > pServiceQuery->m_u8SentCount) && - (pServiceQuery->m_ResendTimeout.expired())) - { - - if ((bResult = _sendMDNSServiceQuery(*pServiceQuery))) - { - ++pServiceQuery->m_u8SentCount; - pServiceQuery->m_ResendTimeout.reset((MDNS_DYNAMIC_QUERY_RESEND_COUNT > pServiceQuery->m_u8SentCount) - ? (MDNS_DYNAMIC_QUERY_RESEND_DELAY * (pServiceQuery->m_u8SentCount - 1)) - : esp8266::polledTimeout::oneShotMs::neverExpires); - } - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: %s to resend service query!"), (bResult ? "Succeeded" : "FAILED")); - printedInfo = true; - ); - } - - // - // Schedule updates for cached answers - 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.flagged())) - { - - if (!pSQAnswer->m_TTLServiceDomain.finalTimeoutLevel()) - { - - bResult = ((_sendMDNSServiceQuery(*pServiceQuery)) && - (pSQAnswer->m_TTLServiceDomain.restart())); - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: PTR update scheduled for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" %s\n"), (bResult ? "OK" : "FAILURE")); - printedInfo = true; - ); - } - else - { - // Timed out! -> Delete - if (pServiceQuery->m_fnCallback) - { - MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); - pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_ServiceDomain), false); - } - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: Will remove PTR answer for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR("\n")); - printedInfo = true; - ); - - bResult = pServiceQuery->removeAnswer(pSQAnswer); - pSQAnswer = 0; - continue; // Don't use this answer anymore - } - } // ServiceDomain flagged - - // 2. level answers - // HostDomain & Port (from SRV) - if ((bResult) && - (pSQAnswer->m_TTLHostDomainAndPort.flagged())) - { - - if (!pSQAnswer->m_TTLHostDomainAndPort.finalTimeoutLevel()) - { - - bResult = ((_sendMDNSQuery(pSQAnswer->m_ServiceDomain, DNS_RRTYPE_SRV)) && - (pSQAnswer->m_TTLHostDomainAndPort.restart())); - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: SRV update scheduled for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" host domain and port %s\n"), (bResult ? "OK" : "FAILURE")); - printedInfo = true; - ); - } - else - { - // Timed out! -> Delete - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: Will remove SRV answer for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" host domain and port\n")); - printedInfo = true; - ); - // Delete - pSQAnswer->m_HostDomain.clear(); - pSQAnswer->releaseHostDomain(); - pSQAnswer->m_u16Port = 0; - pSQAnswer->m_TTLHostDomainAndPort.set(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) - { - MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); - pServiceQuery->m_fnCallback(serviceInfo, static_cast(u32ContentFlags), false); - } - } - } // HostDomainAndPort flagged - - // Txts (from TXT) - if ((bResult) && - (pSQAnswer->m_TTLTxts.flagged())) - { - - if (!pSQAnswer->m_TTLTxts.finalTimeoutLevel()) - { - - bResult = ((_sendMDNSQuery(pSQAnswer->m_ServiceDomain, DNS_RRTYPE_TXT)) && - (pSQAnswer->m_TTLTxts.restart())); - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: TXT update scheduled for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" TXTs %s\n"), (bResult ? "OK" : "FAILURE")); - printedInfo = true; - ); - } - else - { - // Timed out! -> Delete - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: Will remove TXT answer for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" TXTs\n")); - printedInfo = true; - ); - // Delete - pSQAnswer->m_Txts.clear(); - pSQAnswer->m_TTLTxts.set(0); - - // Remove content flags for deleted answer parts - pSQAnswer->m_u32ContentFlags &= ~ServiceQueryAnswerType_Txts; - - if (pServiceQuery->m_fnCallback) - { - MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); - pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_Txts), false); - } - } - } // TXTs flagged - - // 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.flagged()) - { - - if (!pIP4Address->m_TTL.finalTimeoutLevel()) // Needs update - { - - if ((bAUpdateQuerySent) || - ((bResult = _sendMDNSQuery(pSQAnswer->m_HostDomain, DNS_RRTYPE_A)))) - { - - pIP4Address->m_TTL.restart(); - bAUpdateQuerySent = true; - - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: IP4 update scheduled for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" IP4 address (%s)\n"), (pIP4Address->m_IPAddress.toString().c_str())); - printedInfo = true; - ); - } - } - else - { - // Timed out! -> Delete - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: Will remove IP4 answer for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" IP4 address\n")); - printedInfo = true; - ); - 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) - { - MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); - pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_IP4Address), false); - } - } - } // IP4 flagged - - 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.flagged()) - { - - if (!pIP6Address->m_TTL.finalTimeoutLevel()) // Needs update - { - - if ((bAAAAUpdateQuerySent) || - ((bResult = _sendMDNSQuery(pSQAnswer->m_HostDomain, DNS_RRTYPE_AAAA)))) - { - - pIP6Address->m_TTL.restart(); - bAAAAUpdateQuerySent = true; - - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: IP6 update scheduled for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" IP6 address (%s)\n"), (pIP6Address->m_IPAddress.toString().c_str())); - printedInfo = true; - ); - } - } - else - { - // Timed out! -> Delete - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: Will remove answer for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" IP6Address\n")); - printedInfo = true; - ); - 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); - } - } - } // IP6 flagged - - pIP6Address = pNextIP6Address; // Next - } // while -#endif - pSQAnswer = pNextSQAnswer; - } - } - } - DEBUG_EX_INFO( - if (printedInfo) -{ - DEBUG_OUTPUT.printf_P(PSTR("\n")); - } - ); - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[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("[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(), 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("[MDNSResponder] _replyMaskForHost: INVALID RR-class (0x%04X)!\n"), p_RRHeader.m_Attributes.m_u16Class);); - } - DEBUG_EX_INFO(if (u8ReplyMask) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _replyMaskForHost: 0x%X\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("[MDNSResponder] _replyMaskForService: INVALID RR-class (0x%04X)!\n"), p_RRHeader.m_Attributes.m_u16Class);); - } - DEBUG_EX_INFO(if (u8ReplyMask) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _replyMaskForService(%s.%s.%s): 0x%X\n"), p_Service.m_pcName, p_Service.m_pcService, p_Service.m_pcProtocol, u8ReplyMask); - }); - return u8ReplyMask; -} - -} // namespace MDNSImplementation - -} // namespace esp8266 diff --git a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Helpers.cpp b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Helpers.cpp deleted file mode 100644 index d23941ce53..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Helpers.cpp +++ /dev/null @@ -1,850 +0,0 @@ -/* - LEAmDNS_Helpers.cpp - - License (MIT license): - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -*/ - -#include "lwip/igmp.h" - -#include "LEAmDNS_lwIPdefs.h" -#include "LEAmDNS_Priv.h" - - -namespace -{ - -/* - strrstr (static) - - Backwards search for p_pcPattern in p_pcString - Based on: https://stackoverflow.com/a/1634398/2778898 - -*/ -const char* strrstr(const char*__restrict p_pcString, const char*__restrict p_pcPattern) -{ - - const char* pcResult = 0; - - size_t stStringLength = (p_pcString ? strlen(p_pcString) : 0); - size_t stPatternLength = (p_pcPattern ? strlen(p_pcPattern) : 0); - - if ((stStringLength) && - (stPatternLength) && - (stPatternLength <= stStringLength)) - { - // Pattern is shorter or has the same length tham the string - - for (const char* s = (p_pcString + stStringLength - stPatternLength); s >= p_pcString; --s) - { - if (0 == strncmp(s, p_pcPattern, stPatternLength)) - { - pcResult = s; - break; - } - } - } - return pcResult; -} - - -} // anonymous - - - - - -namespace esp8266 -{ - -/* - LEAmDNS -*/ -namespace MDNSImplementation -{ - -/** - HELPERS -*/ - -/* - MDNSResponder::indexDomain (static) - - Updates the given domain 'p_rpcHostname' by appending a delimiter and an index number. - - If the given domain already hasa numeric index (after the given delimiter), this index - incremented. If not, the delimiter and index '2' is added. - - If 'p_rpcHostname' is empty (==0), the given default name 'p_pcDefaultHostname' is used, - if no default is given, 'esp8266' is used. - -*/ -/*static*/ bool MDNSResponder::indexDomain(char*& p_rpcDomain, - const char* p_pcDivider /*= "-"*/, - const char* p_pcDefaultDomain /*= 0*/) -{ - - bool bResult = false; - - // Ensure a divider exists; use '-' as default - const char* pcDivider = (p_pcDivider ? : "-"); - - if (p_rpcDomain) - { - const char* pFoundDivider = strrstr(p_rpcDomain, pcDivider); - if (pFoundDivider) // maybe already extended - { - char* pEnd = 0; - unsigned long ulIndex = strtoul((pFoundDivider + strlen(pcDivider)), &pEnd, 10); - if ((ulIndex) && - ((pEnd - p_rpcDomain) == (ptrdiff_t)strlen(p_rpcDomain)) && - (!*pEnd)) // Valid (old) index found - { - - char acIndexBuffer[16]; - sprintf(acIndexBuffer, "%lu", (++ulIndex)); - size_t stLength = ((pFoundDivider - p_rpcDomain + strlen(pcDivider)) + strlen(acIndexBuffer) + 1); - char* pNewHostname = new char[stLength]; - if (pNewHostname) - { - memcpy(pNewHostname, p_rpcDomain, (pFoundDivider - p_rpcDomain + strlen(pcDivider))); - pNewHostname[pFoundDivider - p_rpcDomain + strlen(pcDivider)] = 0; - strcat(pNewHostname, acIndexBuffer); - - delete[] p_rpcDomain; - p_rpcDomain = pNewHostname; - - bResult = true; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.println(F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!"));); - } - } - else - { - pFoundDivider = 0; // Flag the need to (base) extend the hostname - } - } - - if (!pFoundDivider) // not yet extended (or failed to increment extension) -> start indexing - { - size_t stLength = strlen(p_rpcDomain) + (strlen(pcDivider) + 1 + 1); // Name + Divider + '2' + '\0' - char* pNewHostname = new char[stLength]; - if (pNewHostname) - { - sprintf(pNewHostname, "%s%s2", p_rpcDomain, pcDivider); - - delete[] p_rpcDomain; - p_rpcDomain = pNewHostname; - - bResult = true; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.println(F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!"));); - } - } - } - else - { - // No given host domain, use base or default - const char* cpcDefaultName = (p_pcDefaultDomain ? : "esp8266"); - - size_t stLength = strlen(cpcDefaultName) + 1; // '\0' - p_rpcDomain = new char[stLength]; - if (p_rpcDomain) - { - strncpy(p_rpcDomain, cpcDefaultName, stLength); - bResult = true; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.println(F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!"));); - } - } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] indexDomain: %s\n"), p_rpcDomain);); - return bResult; -} - - -/* - UDP CONTEXT -*/ - -bool MDNSResponder::_callProcess(void) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf("[MDNSResponder] _callProcess (%lu, triggered by: %s)\n", millis(), IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str());); - - return _process(false); -} - -/* - MDNSResponder::_allocUDPContext - - (Re-)Creates the one-and-only UDP context for the MDNS responder. - The context is added to the 'multicast'-group and listens to the MDNS port (5353). - The travel-distance for multicast messages is set to 1 (local, via MDNS_MULTICAST_TTL). - Messages are received via the MDNSResponder '_update' function. CAUTION: This function - is called from the WiFi stack side of the ESP stack system. - -*/ -bool MDNSResponder::_allocUDPContext(void) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.println("[MDNSResponder] _allocUDPContext");); - - bool bResult = false; - - _releaseUDPContext(); - -#ifdef MDNS_IP4_SUPPORT - ip_addr_t multicast_addr = DNS_MQUERY_IPV4_GROUP_INIT; -#endif -#ifdef MDNS_IP6_SUPPORT - //TODO: set multicast address (lwip_joingroup() is IPv4 only at the time of writing) - multicast_addr.addr = DNS_MQUERY_IPV6_GROUP_INIT; -#endif - if (ERR_OK == igmp_joingroup(ip_2_ip4(&m_netif->ip_addr), ip_2_ip4(&multicast_addr))) - { - m_pUDPContext = new UdpContext; - m_pUDPContext->ref(); - - if (m_pUDPContext->listen(IP4_ADDR_ANY, DNS_MQUERY_PORT)) - { - m_pUDPContext->setMulticastTTL(MDNS_MULTICAST_TTL); - m_pUDPContext->onRx(std::bind(&MDNSResponder::_callProcess, this)); - - bResult = m_pUDPContext->connect(&multicast_addr, DNS_MQUERY_PORT); - } - } - return bResult; -} - -/* - MDNSResponder::_releaseUDPContext -*/ -bool MDNSResponder::_releaseUDPContext(void) -{ - - if (m_pUDPContext) - { - m_pUDPContext->unref(); - m_pUDPContext = 0; - } - return true; -} - - -/* - SERVICE QUERY -*/ - -/* - MDNSResponder::_allocServiceQuery -*/ -MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_allocServiceQuery(void) -{ - - stcMDNSServiceQuery* pServiceQuery = new stcMDNSServiceQuery; - if (pServiceQuery) - { - // Link to query list - pServiceQuery->m_pNext = m_pServiceQueries; - m_pServiceQueries = pServiceQuery; - } - return m_pServiceQueries; -} - -/* - MDNSResponder::_removeServiceQuery -*/ -bool MDNSResponder::_removeServiceQuery(MDNSResponder::stcMDNSServiceQuery* p_pServiceQuery) -{ - - bool bResult = false; - - if (p_pServiceQuery) - { - stcMDNSServiceQuery* pPred = m_pServiceQueries; - while ((pPred) && - (pPred->m_pNext != p_pServiceQuery)) - { - pPred = pPred->m_pNext; - } - if (pPred) - { - pPred->m_pNext = p_pServiceQuery->m_pNext; - delete p_pServiceQuery; - bResult = true; - } - else // No predecesor - { - if (m_pServiceQueries == p_pServiceQuery) - { - m_pServiceQueries = p_pServiceQuery->m_pNext; - delete p_pServiceQuery; - bResult = true; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.println("[MDNSResponder] _releaseServiceQuery: INVALID service query!");); - } - } - } - return bResult; -} - -/* - MDNSResponder::_removeLegacyServiceQuery -*/ -bool MDNSResponder::_removeLegacyServiceQuery(void) -{ - - stcMDNSServiceQuery* pLegacyServiceQuery = _findLegacyServiceQuery(); - return (pLegacyServiceQuery ? _removeServiceQuery(pLegacyServiceQuery) : true); -} - -/* - MDNSResponder::_findServiceQuery - - 'Convert' hMDNSServiceQuery to stcMDNSServiceQuery* (ensure existance) - -*/ -MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_findServiceQuery(MDNSResponder::hMDNSServiceQuery p_hServiceQuery) -{ - - stcMDNSServiceQuery* pServiceQuery = m_pServiceQueries; - while (pServiceQuery) - { - if ((hMDNSServiceQuery)pServiceQuery == p_hServiceQuery) - { - break; - } - pServiceQuery = pServiceQuery->m_pNext; - } - return pServiceQuery; -} - -/* - MDNSResponder::_findLegacyServiceQuery -*/ -MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_findLegacyServiceQuery(void) -{ - - stcMDNSServiceQuery* pServiceQuery = m_pServiceQueries; - while (pServiceQuery) - { - if (pServiceQuery->m_bLegacyQuery) - { - break; - } - pServiceQuery = pServiceQuery->m_pNext; - } - return pServiceQuery; -} - -/* - MDNSResponder::_releaseServiceQueries -*/ -bool MDNSResponder::_releaseServiceQueries(void) -{ - while (m_pServiceQueries) - { - stcMDNSServiceQuery* pNext = m_pServiceQueries->m_pNext; - delete m_pServiceQueries; - m_pServiceQueries = pNext; - } - return true; -} - -/* - MDNSResponder::_findNextServiceQueryByServiceType -*/ -MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_findNextServiceQueryByServiceType(const stcMDNS_RRDomain& p_ServiceTypeDomain, - const stcMDNSServiceQuery* p_pPrevServiceQuery) -{ - stcMDNSServiceQuery* pMatchingServiceQuery = 0; - - stcMDNSServiceQuery* pServiceQuery = (p_pPrevServiceQuery ? p_pPrevServiceQuery->m_pNext : m_pServiceQueries); - while (pServiceQuery) - { - if (p_ServiceTypeDomain == pServiceQuery->m_ServiceTypeDomain) - { - pMatchingServiceQuery = pServiceQuery; - break; - } - pServiceQuery = pServiceQuery->m_pNext; - } - return pMatchingServiceQuery; -} - - -/* - HOSTNAME -*/ - -/* - MDNSResponder::_setHostname -*/ -bool MDNSResponder::_setHostname(const char* p_pcHostname) -{ - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _allocHostname (%s)\n"), p_pcHostname);); - - bool bResult = false; - - _releaseHostname(); - - size_t stLength = 0; - if ((p_pcHostname) && - (MDNS_DOMAIN_LABEL_MAXLENGTH >= (stLength = strlen(p_pcHostname)))) // char max size for a single label - { - // Copy in hostname characters as lowercase - if ((bResult = (0 != (m_pcHostname = new char[stLength + 1])))) - { -#ifdef MDNS_FORCE_LOWERCASE_HOSTNAME - size_t i = 0; - for (; i < stLength; ++i) - { - m_pcHostname[i] = (isupper(p_pcHostname[i]) ? tolower(p_pcHostname[i]) : p_pcHostname[i]); - } - m_pcHostname[i] = 0; -#else - strncpy(m_pcHostname, p_pcHostname, (stLength + 1)); -#endif - } - } - return bResult; -} - -/* - MDNSResponder::_releaseHostname -*/ -bool MDNSResponder::_releaseHostname(void) -{ - - if (m_pcHostname) - { - delete[] m_pcHostname; - m_pcHostname = 0; - } - return true; -} - - -/* - SERVICE -*/ - -/* - MDNSResponder::_allocService -*/ -MDNSResponder::stcMDNSService* MDNSResponder::_allocService(const char* p_pcName, - const char* p_pcService, - const char* p_pcProtocol, - uint16_t p_u16Port) -{ - - stcMDNSService* pService = 0; - if (((!p_pcName) || - (MDNS_DOMAIN_LABEL_MAXLENGTH >= strlen(p_pcName))) && - (p_pcService) && - (MDNS_SERVICE_NAME_LENGTH >= strlen(p_pcService)) && - (p_pcProtocol) && - (MDNS_SERVICE_PROTOCOL_LENGTH >= strlen(p_pcProtocol)) && - (p_u16Port) && - (0 != (pService = new stcMDNSService)) && - (pService->setName(p_pcName ? : m_pcHostname)) && - (pService->setService(p_pcService)) && - (pService->setProtocol(p_pcProtocol))) - { - - pService->m_bAutoName = (0 == p_pcName); - pService->m_u16Port = p_u16Port; - - // Add to list (or start list) - pService->m_pNext = m_pServices; - m_pServices = pService; - } - return pService; -} - -/* - MDNSResponder::_releaseService -*/ -bool MDNSResponder::_releaseService(MDNSResponder::stcMDNSService* p_pService) -{ - - bool bResult = false; - - if (p_pService) - { - stcMDNSService* pPred = m_pServices; - while ((pPred) && - (pPred->m_pNext != p_pService)) - { - pPred = pPred->m_pNext; - } - if (pPred) - { - pPred->m_pNext = p_pService->m_pNext; - delete p_pService; - bResult = true; - } - else // No predecesor - { - if (m_pServices == p_pService) - { - m_pServices = p_pService->m_pNext; - delete p_pService; - bResult = true; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.println("[MDNSResponder] _releaseService: INVALID service!");); - } - } - } - return bResult; -} - -/* - MDNSResponder::_releaseServices -*/ -bool MDNSResponder::_releaseServices(void) -{ - - stcMDNSService* pService = m_pServices; - while (pService) - { - _releaseService(pService); - pService = m_pServices; - } - return true; -} - -/* - MDNSResponder::_findService -*/ -MDNSResponder::stcMDNSService* MDNSResponder::_findService(const char* p_pcName, - const char* p_pcService, - const char* p_pcProtocol) -{ - - stcMDNSService* pService = m_pServices; - while (pService) - { - if ((0 == strcmp(pService->m_pcName, p_pcName)) && - (0 == strcmp(pService->m_pcService, p_pcService)) && - (0 == strcmp(pService->m_pcProtocol, p_pcProtocol))) - { - - break; - } - pService = pService->m_pNext; - } - return pService; -} - -/* - MDNSResponder::_findService -*/ -MDNSResponder::stcMDNSService* MDNSResponder::_findService(const MDNSResponder::hMDNSService p_hService) -{ - - stcMDNSService* pService = m_pServices; - while (pService) - { - if (p_hService == (hMDNSService)pService) - { - break; - } - pService = pService->m_pNext; - } - return pService; -} - - -/* - SERVICE TXT -*/ - -/* - MDNSResponder::_allocServiceTxt -*/ -MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_allocServiceTxt(MDNSResponder::stcMDNSService* p_pService, - const char* p_pcKey, - const char* p_pcValue, - bool p_bTemp) -{ - - stcMDNSServiceTxt* pTxt = 0; - - if ((p_pService) && - (p_pcKey) && - (MDNS_SERVICE_TXT_MAXLENGTH > (p_pService->m_Txts.length() + - 1 + // Length byte - (p_pcKey ? strlen(p_pcKey) : 0) + - 1 + // '=' - (p_pcValue ? strlen(p_pcValue) : 0)))) - { - - pTxt = new stcMDNSServiceTxt; - if (pTxt) - { - size_t stLength = (p_pcKey ? strlen(p_pcKey) : 0); - pTxt->m_pcKey = new char[stLength + 1]; - if (pTxt->m_pcKey) - { - strncpy(pTxt->m_pcKey, p_pcKey, stLength); pTxt->m_pcKey[stLength] = 0; - } - - if (p_pcValue) - { - stLength = (p_pcValue ? strlen(p_pcValue) : 0); - pTxt->m_pcValue = new char[stLength + 1]; - if (pTxt->m_pcValue) - { - strncpy(pTxt->m_pcValue, p_pcValue, stLength); pTxt->m_pcValue[stLength] = 0; - } - } - pTxt->m_bTemp = p_bTemp; - - // Add to list (or start list) - p_pService->m_Txts.add(pTxt); - } - } - return pTxt; -} - -/* - MDNSResponder::_releaseServiceTxt -*/ -bool MDNSResponder::_releaseServiceTxt(MDNSResponder::stcMDNSService* p_pService, - MDNSResponder::stcMDNSServiceTxt* p_pTxt) -{ - - return ((p_pService) && - (p_pTxt) && - (p_pService->m_Txts.remove(p_pTxt))); -} - -/* - MDNSResponder::_updateServiceTxt -*/ -MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_updateServiceTxt(MDNSResponder::stcMDNSService* p_pService, - MDNSResponder::stcMDNSServiceTxt* p_pTxt, - const char* p_pcValue, - bool p_bTemp) -{ - - if ((p_pService) && - (p_pTxt) && - (MDNS_SERVICE_TXT_MAXLENGTH > (p_pService->m_Txts.length() - - (p_pTxt->m_pcValue ? strlen(p_pTxt->m_pcValue) : 0) + - (p_pcValue ? strlen(p_pcValue) : 0)))) - { - p_pTxt->update(p_pcValue); - p_pTxt->m_bTemp = p_bTemp; - } - return p_pTxt; -} - -/* - MDNSResponder::_findServiceTxt -*/ -MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_findServiceTxt(MDNSResponder::stcMDNSService* p_pService, - const char* p_pcKey) -{ - - return (p_pService ? p_pService->m_Txts.find(p_pcKey) : 0); -} - -/* - MDNSResponder::_findServiceTxt -*/ -MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_findServiceTxt(MDNSResponder::stcMDNSService* p_pService, - const hMDNSTxt p_hTxt) -{ - - return (((p_pService) && (p_hTxt)) ? p_pService->m_Txts.find((stcMDNSServiceTxt*)p_hTxt) : 0); -} - -/* - MDNSResponder::_addServiceTxt -*/ -MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_addServiceTxt(MDNSResponder::stcMDNSService* p_pService, - const char* p_pcKey, - const char* p_pcValue, - bool p_bTemp) -{ - stcMDNSServiceTxt* pResult = 0; - - if ((p_pService) && - (p_pcKey) && - (strlen(p_pcKey))) - { - - stcMDNSServiceTxt* pTxt = p_pService->m_Txts.find(p_pcKey); - if (pTxt) - { - pResult = _updateServiceTxt(p_pService, pTxt, p_pcValue, p_bTemp); - } - else - { - pResult = _allocServiceTxt(p_pService, p_pcKey, p_pcValue, p_bTemp); - } - } - return pResult; -} - -MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_answerKeyValue(const hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery); - stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); - // Fill m_pcTxts (if not already done) - return (pSQAnswer) ? pSQAnswer->m_Txts.m_pTxts : 0; -} - -/* - MDNSResponder::_collectServiceTxts -*/ -bool MDNSResponder::_collectServiceTxts(MDNSResponder::stcMDNSService& p_rService) -{ - - // Call Dynamic service callbacks - if (m_fnServiceTxtCallback) - { - m_fnServiceTxtCallback((hMDNSService)&p_rService); - } - if (p_rService.m_fnTxtCallback) - { - p_rService.m_fnTxtCallback((hMDNSService)&p_rService); - } - return true; -} - -/* - MDNSResponder::_releaseTempServiceTxts -*/ -bool MDNSResponder::_releaseTempServiceTxts(MDNSResponder::stcMDNSService& p_rService) -{ - - return (p_rService.m_Txts.removeTempTxts()); -} - - -/* - MISC -*/ - -#ifdef DEBUG_ESP_MDNS_RESPONDER -/* - MDNSResponder::_printRRDomain -*/ -bool MDNSResponder::_printRRDomain(const MDNSResponder::stcMDNS_RRDomain& p_RRDomain) const -{ - - //DEBUG_OUTPUT.printf_P(PSTR("Domain: ")); - - const char* pCursor = p_RRDomain.m_acName; - uint8_t u8Length = *pCursor++; - if (u8Length) - { - while (u8Length) - { - for (uint8_t u = 0; u < u8Length; ++u) - { - DEBUG_OUTPUT.printf_P(PSTR("%c"), *(pCursor++)); - } - u8Length = *pCursor++; - if (u8Length) - { - DEBUG_OUTPUT.printf_P(PSTR(".")); - } - } - } - else // empty domain - { - DEBUG_OUTPUT.printf_P(PSTR("-empty-")); - } - //DEBUG_OUTPUT.printf_P(PSTR("\n")); - - return true; -} - -/* - MDNSResponder::_printRRAnswer -*/ -bool MDNSResponder::_printRRAnswer(const MDNSResponder::stcMDNS_RRAnswer& p_RRAnswer) const -{ - - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] RRAnswer: ")); - _printRRDomain(p_RRAnswer.m_Header.m_Domain); - DEBUG_OUTPUT.printf_P(PSTR(" Type:0x%04X Class:0x%04X TTL:%u, "), p_RRAnswer.m_Header.m_Attributes.m_u16Type, p_RRAnswer.m_Header.m_Attributes.m_u16Class, p_RRAnswer.m_u32TTL); - switch (p_RRAnswer.m_Header.m_Attributes.m_u16Type & (~0x8000)) // Topmost bit might carry 'cache flush' flag - { -#ifdef MDNS_IP4_SUPPORT - case DNS_RRTYPE_A: - DEBUG_OUTPUT.printf_P(PSTR("A IP:%s"), ((const stcMDNS_RRAnswerA*)&p_RRAnswer)->m_IPAddress.toString().c_str()); - break; -#endif - case DNS_RRTYPE_PTR: - DEBUG_OUTPUT.printf_P(PSTR("PTR ")); - _printRRDomain(((const stcMDNS_RRAnswerPTR*)&p_RRAnswer)->m_PTRDomain); - break; - case DNS_RRTYPE_TXT: - { - size_t stTxtLength = ((const stcMDNS_RRAnswerTXT*)&p_RRAnswer)->m_Txts.c_strLength(); - char* pTxts = new char[stTxtLength]; - if (pTxts) - { - ((/*const c_str()!!*/stcMDNS_RRAnswerTXT*)&p_RRAnswer)->m_Txts.c_str(pTxts); - DEBUG_OUTPUT.printf_P(PSTR("TXT(%u) %s"), stTxtLength, pTxts); - delete[] pTxts; - } - break; - } -#ifdef MDNS_IP6_SUPPORT - case DNS_RRTYPE_AAAA: - DEBUG_OUTPUT.printf_P(PSTR("AAAA IP:%s"), ((stcMDNS_RRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); - break; -#endif - case DNS_RRTYPE_SRV: - DEBUG_OUTPUT.printf_P(PSTR("SRV Port:%u "), ((const stcMDNS_RRAnswerSRV*)&p_RRAnswer)->m_u16Port); - _printRRDomain(((const stcMDNS_RRAnswerSRV*)&p_RRAnswer)->m_SRVDomain); - break; - default: - DEBUG_OUTPUT.printf_P(PSTR("generic ")); - break; - } - DEBUG_OUTPUT.printf_P(PSTR("\n")); - - return true; -} -#endif - -} // namespace MDNSImplementation - -} // namespace esp8266 - - - - diff --git a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Priv.h b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Priv.h deleted file mode 100644 index cc56b133a9..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Priv.h +++ /dev/null @@ -1,182 +0,0 @@ -/* - 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 MDNS_PRIV_H -#define MDNS_PRIV_H - -namespace esp8266 -{ - -/* - LEAmDNS -*/ - -namespace MDNSImplementation -{ - -// Enable class debug functions -#define ESP_8266_MDNS_INCLUDE -//#define DEBUG_ESP_MDNS_RESPONDER - -#if !defined(DEBUG_ESP_MDNS_RESPONDER) && defined(DEBUG_ESP_MDNS) -#define DEBUG_ESP_MDNS_RESPONDER -#endif - -#ifndef LWIP_OPEN_SRC -#define LWIP_OPEN_SRC -#endif - -// -// If ENABLE_ESP_MDNS_RESPONDER_PASSIV_MODE is defined, the mDNS responder ignores a successful probing -// This allows to drive the responder in a environment, where 'update()' isn't called in the loop -//#define ENABLE_ESP_MDNS_RESPONDER_PASSIV_MODE - -// 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) do { (void)0; } while (0) -#endif -#ifdef DEBUG_ESP_MDNS_ERR -#define DEBUG_EX_ERR(A) A -#else -#define DEBUG_EX_ERR(A) do { (void)0; } while (0) -#endif -#ifdef DEBUG_ESP_MDNS_TX -#define DEBUG_EX_TX(A) A -#else -#define DEBUG_EX_TX(A) do { (void)0; } while (0) -#endif -#ifdef DEBUG_ESP_MDNS_RX -#define DEBUG_EX_RX(A) A -#else -#define DEBUG_EX_RX(A) do { (void)0; } while (0) -#endif - -#ifdef DEBUG_ESP_PORT -#define DEBUG_OUTPUT DEBUG_ESP_PORT -#else -#define DEBUG_OUTPUT Serial -#endif -#else -#define DEBUG_EX_INFO(A) do { (void)0; } while (0) -#define DEBUG_EX_ERR(A) do { (void)0; } while (0) -#define DEBUG_EX_TX(A) do { (void)0; } while (0) -#define DEBUG_EX_RX(A) do { (void)0; } while (0) -#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) - - However, RFC 3171 seems to force 255 instead -*/ -#define MDNS_MULTICAST_TTL 255/*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 - Delay between and number of announces for host and service domains - Delay between and number of service queries; the delay is multiplied by the resent number in '_checkServiceQueryCache' -*/ -#define MDNS_PROBE_DELAY 250 -#define MDNS_PROBE_COUNT 3 -#define MDNS_ANNOUNCE_DELAY 1000 -#define MDNS_ANNOUNCE_COUNT 8 -#define MDNS_DYNAMIC_QUERY_RESEND_COUNT 5 -#define MDNS_DYNAMIC_QUERY_RESEND_DELAY 5000 - - -/* - 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 -#else -#ifdef F -#undef F -#endif -#define F(A) A -#endif - -} // namespace MDNSImplementation - -} // namespace esp8266 - -// Include the main header, so the submodlues only need to include this header -#include "LEAmDNS.h" - - -#endif // MDNS_PRIV_H diff --git a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Structs.cpp b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Structs.cpp deleted file mode 100644 index ce475de3ba..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Structs.cpp +++ /dev/null @@ -1,2476 +0,0 @@ -/* - 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" -#include "LEAmDNS_lwIPdefs.h" - -namespace esp8266 -{ - -/* - LEAmDNS -*/ -namespace MDNSImplementation -{ - -/** - 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)) - { - 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 ? 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)) - { - 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 ? 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 += strlen(m_pcKey); // Key - stLength += 1; // '=' - stLength += (m_pcValue ? 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 ? strlen(pTxt->m_pcKey) : 0))))) - { - if (pTxt != m_pTxts) - { - *p_pcBuffer++ = ';'; - } - strncpy(p_pcBuffer, pTxt->m_pcKey, stLength); p_pcBuffer[stLength] = 0; - p_pcBuffer += stLength; - *p_pcBuffer++ = '='; - if ((stLength = (pTxt->m_pcValue ? strlen(pTxt->m_pcValue) : 0))) - { - 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 ? strlen(pTxt->m_pcKey) : 0))))) - { - memcpy(p_pcBuffer, pTxt->m_pcKey, stLength); - p_pcBuffer += stLength; - *p_pcBuffer++ = '='; - if ((stLength = (pTxt->m_pcValue ? strlen(pTxt->m_pcValue) : 0))) - { - 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) && - (strlen(pTxt->m_pcValue) == 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) && - (strlen(pOtherTxt->m_pcValue) == 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 - ? (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; - } - 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 == strncasecmp((pT + 1), (pO + 1), *((unsigned char*)pT)))) // Same content - { - if (*((unsigned char*)pT)) // Not 0 - { - pT += (1 + * ((unsigned char*)pT)); // Shift by length byte and 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_u8SentCount(0), - m_Timeout(esp8266::polledTimeout::oneShotMs::neverExpires), - m_bConflict(false), - m_bTiebreakNeeded(false), - m_fnHostProbeResultCallback(0), - m_fnServiceProbeResultCallback(0) -{ -} - -/* - MDNSResponder::stcProbeInformation::clear -*/ -bool MDNSResponder::stcProbeInformation::clear(bool p_bClearUserdata /*= false*/) -{ - - m_ProbingStatus = ProbingStatus_WaitingForData; - m_u8SentCount = 0; - m_Timeout.resetToNeverExpires(); - m_bConflict = false; - m_bTiebreakNeeded = false; - if (p_bClearUserdata) - { - m_fnHostProbeResultCallback = 0; - m_fnServiceProbeResultCallback = 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) -{ - - 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 ? strlen(p_pcName) : 0); - if (stLength) - { - if ((bResult = (0 != (m_pcName = new char[stLength + 1])))) - { - 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 ? strlen(p_pcService) : 0); - if (stLength) - { - if ((bResult = (0 != (m_pcService = new char[stLength + 1])))) - { - 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 ? strlen(p_pcProtocol) : 0); - if (stLength) - { - if ((bResult = (0 != (m_pcProtocol = new char[stLength + 1])))) - { - 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* /) - : m_bUpdateScheduled(false) { - - set(p_u32TTL * 1000); - } - - / * - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::set - / - bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::set(uint32_t p_u32TTL) { - - m_TTLTimeFlag.restart(p_u32TTL * 1000); - m_bUpdateScheduled = false; - - return true; - } - - / * - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::has80Percent - / - bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::has80Percent(void) const { - - return ((m_TTLTimeFlag.getTimeout()) && - (!m_bUpdateScheduled) && - (m_TTLTimeFlag.hypotheticalTimeout((m_TTLTimeFlag.getTimeout() * 800) / 1000))); - } - - / * - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::isOutdated - / - bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::isOutdated(void) const { - - return ((m_TTLTimeFlag.getTimeout()) && - (m_TTLTimeFlag.flagged())); - }*/ - - -/** - 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(void) - : m_u32TTL(0), - m_TTLTimeout(esp8266::polledTimeout::oneShotMs::neverExpires), - m_timeoutLevel(TIMEOUTLEVEL_UNSET) -{ - -} - -/* - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::set -*/ -bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::set(uint32_t p_u32TTL) -{ - - m_u32TTL = p_u32TTL; - if (m_u32TTL) - { - m_timeoutLevel = TIMEOUTLEVEL_BASE; // Set to 80% - m_TTLTimeout.reset(timeout()); - } - else - { - m_timeoutLevel = TIMEOUTLEVEL_UNSET; // undef - m_TTLTimeout.resetToNeverExpires(); - } - return true; -} - -/* - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::flagged -*/ -bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::flagged(void) -{ - - return ((m_u32TTL) && - (TIMEOUTLEVEL_UNSET != m_timeoutLevel) && - (m_TTLTimeout.expired())); -} - -/* - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::restart -*/ -bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::restart(void) -{ - - bool bResult = true; - - if ((TIMEOUTLEVEL_BASE <= m_timeoutLevel) && // >= 80% AND - (TIMEOUTLEVEL_FINAL > m_timeoutLevel)) // < 100% - { - - m_timeoutLevel += TIMEOUTLEVEL_INTERVAL; // increment by 5% - m_TTLTimeout.reset(timeout()); - } - else - { - bResult = false; - m_TTLTimeout.resetToNeverExpires(); - m_timeoutLevel = TIMEOUTLEVEL_UNSET; - } - return bResult; -} - -/* - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::prepareDeletion -*/ -bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::prepareDeletion(void) -{ - - m_timeoutLevel = TIMEOUTLEVEL_FINAL; - m_TTLTimeout.reset(1 * 1000); // See RFC 6762, 10.1 - - return true; -} - -/* - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::finalTimeoutLevel -*/ -bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::finalTimeoutLevel(void) const -{ - - return (TIMEOUTLEVEL_FINAL == m_timeoutLevel); -} - -/* - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::timeout -*/ -unsigned long MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::timeout(void) const -{ - - uint32_t u32Timeout = esp8266::polledTimeout::oneShotMs::neverExpires; - - if (TIMEOUTLEVEL_BASE == m_timeoutLevel) // 80% - { - u32Timeout = (m_u32TTL * 800); // to milliseconds - } - else if ((TIMEOUTLEVEL_BASE < m_timeoutLevel) && // >80% AND - (TIMEOUTLEVEL_FINAL >= m_timeoutLevel)) // <= 100% - { - - u32Timeout = (m_u32TTL * 50); - } // else: invalid - return u32Timeout; -} - - -#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*/) - : m_pNext(0), - m_IPAddress(p_IPAddress) -{ - - m_TTL.set(p_u32TTL); -} -#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) && (u32Index < p_u32Index)); pIP4Address = pIP4Address->m_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) && (u32Index < p_u32Index)); pIP6Address = pIP6Address->m_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_bLegacyQuery(false), - m_u8SentCount(0), - m_ResendTimeout(esp8266::polledTimeout::oneShotMs::neverExpires), - 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_bLegacyQuery = false; - m_u8SentCount = 0; - m_ResendTimeout.resetToNeverExpires(); - 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) && (u32Index < p_u32Index)); pAnswer = pAnswer->m_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 MDNSImplementation - -} // namespace esp8266 - - - diff --git a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Transfer.cpp b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Transfer.cpp deleted file mode 100644 index 7400abec42..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Transfer.cpp +++ /dev/null @@ -1,1779 +0,0 @@ -/* - LEAmDNS_Transfer.cpp - - License (MIT license): - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -*/ - -extern "C" { -#include "user_interface.h" -} - -#include "LEAmDNS_lwIPdefs.h" -#include "LEAmDNS_Priv.h" - - -namespace esp8266 -{ - -/* - LEAmDNS -*/ -namespace MDNSImplementation -{ - -/** - CONST STRINGS -*/ -static const char* scpcLocal = "local"; -static const char* scpcServices = "services"; -static const char* scpcDNSSD = "dns-sd"; -static const char* scpcUDP = "udp"; -//static const char* scpcTCP = "tcp"; - -#ifdef MDNS_IP4_SUPPORT -static const char* scpcReverseIP4Domain = "in-addr"; -#endif -#ifdef MDNS_IP6_SUPPORT -static const char* scpcReverseIP6Domain = "ip6"; -#endif -static const char* scpcReverseTopDomain = "arpa"; - -/** - TRANSFER -*/ - - -/** - SENDING -*/ - -/* - MDNSResponder::_sendMDNSMessage - - Unicast responses are prepared and sent directly to the querier. - Multicast responses or queries are transferred to _sendMDNSMessage_Multicast - - Any reply flags in installed services are removed at the end! - -*/ -bool MDNSResponder::_sendMDNSMessage(MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - bool bResult = true; - - if (p_rSendParameter.m_bResponse && - p_rSendParameter.m_bUnicast) // Unicast response -> Send to querier - { - DEBUG_EX_ERR(if (!m_pUDPContext->getRemoteAddress()) - { - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSMessage: MISSING remote address for response!\n")); - }); - IPAddress ipRemote; - ipRemote = m_pUDPContext->getRemoteAddress(); - bResult = ((_prepareMDNSMessage(p_rSendParameter, _getResponseMulticastInterface())) && - (m_pUDPContext->send(ipRemote, m_pUDPContext->getRemotePort()))); - } - else // Multicast response - { - bResult = _sendMDNSMessage_Multicast(p_rSendParameter); - } - - // Finally clear service reply masks - for (stcMDNSService* pService = m_pServices; pService; pService = pService->m_pNext) - { - pService->m_u8ReplyMask = 0; - } - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSMessage: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_sendMDNSMessage_Multicast - - Fills the UDP output buffer (via _prepareMDNSMessage) and sends the buffer - via the selected WiFi interface (Station or AP) -*/ -bool MDNSResponder::_sendMDNSMessage_Multicast(MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - bool bResult = false; - - IPAddress fromIPAddress; - fromIPAddress = _getResponseMulticastInterface(); - m_pUDPContext->setMulticastInterface(fromIPAddress); - -#ifdef MDNS_IP4_SUPPORT - IPAddress toMulticastAddress(DNS_MQUERY_IPV4_GROUP_INIT); -#endif -#ifdef MDNS_IP6_SUPPORT - //TODO: set multicast address - IPAddress toMulticastAddress(DNS_MQUERY_IPV6_GROUP_INIT); -#endif - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSMessage_Multicast: Will send to '%s'.\n"), toMulticastAddress.toString().c_str());); - bResult = ((_prepareMDNSMessage(p_rSendParameter, fromIPAddress)) && - (m_pUDPContext->send(toMulticastAddress, DNS_MQUERY_PORT))); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSMessage_Multicast: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_prepareMDNSMessage - - The MDNS message is composed in a two-step process. - In the first loop 'only' the header informations (mainly number of answers) are collected, - while in the seconds loop, the header and all queries and answers are written to the UDP - output buffer. - -*/ -bool MDNSResponder::_prepareMDNSMessage(MDNSResponder::stcMDNSSendParameter& p_rSendParameter, - IPAddress p_IPAddress) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage\n"));); - bool bResult = true; - - // Prepare header; count answers - stcMDNS_MsgHeader msgHeader(p_rSendParameter.m_u16ID, p_rSendParameter.m_bResponse, 0, p_rSendParameter.m_bAuthorative); - // If this is a response, the answers are anwers, - // else this is a query or probe and the answers go into auth section - uint16_t& ru16Answers = (p_rSendParameter.m_bResponse - ? msgHeader.m_u16ANCount - : msgHeader.m_u16NSCount); - - /** - enuSequence - */ - enum enuSequence - { - Sequence_Count = 0, - Sequence_Send = 1 - }; - - // Two step sequence: 'Count' and 'Send' - for (uint32_t sequence = Sequence_Count; ((bResult) && (sequence <= Sequence_Send)); ++sequence) - { - DEBUG_EX_INFO( - if (Sequence_Send == sequence) - { - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), - (unsigned)msgHeader.m_u16ID, - (unsigned)msgHeader.m_1bQR, (unsigned)msgHeader.m_4bOpcode, (unsigned)msgHeader.m_1bAA, (unsigned)msgHeader.m_1bTC, (unsigned)msgHeader.m_1bRD, - (unsigned)msgHeader.m_1bRA, (unsigned)msgHeader.m_4bRCode, - (unsigned)msgHeader.m_u16QDCount, - (unsigned)msgHeader.m_u16ANCount, - (unsigned)msgHeader.m_u16NSCount, - (unsigned)msgHeader.m_u16ARCount); - } - ); - // Count/send - // Header - bResult = ((Sequence_Count == sequence) - ? true - : _writeMDNSMsgHeader(msgHeader, p_rSendParameter)); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSMsgHeader FAILED!\n"));); - // Questions - for (stcMDNS_RRQuestion* pQuestion = p_rSendParameter.m_pQuestions; ((bResult) && (pQuestion)); pQuestion = pQuestion->m_pNext) - { - ((Sequence_Count == sequence) - ? ++msgHeader.m_u16QDCount - : (bResult = _writeMDNSQuestion(*pQuestion, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSQuestion FAILED!\n"));); - } - - // Answers and authorative answers -#ifdef MDNS_IP4_SUPPORT - if ((bResult) && - (p_rSendParameter.m_u8HostReplyMask & ContentFlag_A)) - { - ((Sequence_Count == sequence) - ? ++ru16Answers - : (bResult = _writeMDNSAnswer_A(p_IPAddress, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_A(A) FAILED!\n"));); - } - if ((bResult) && - (p_rSendParameter.m_u8HostReplyMask & ContentFlag_PTR_IP4)) - { - ((Sequence_Count == sequence) - ? ++ru16Answers - : (bResult = _writeMDNSAnswer_PTR_IP4(p_IPAddress, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_IP4 FAILED!\n"));); - } -#endif -#ifdef MDNS_IP6_SUPPORT - if ((bResult) && - (p_rSendParameter.m_u8HostReplyMask & ContentFlag_AAAA)) - { - ((Sequence_Count == sequence) - ? ++ru16Answers - : (bResult = _writeMDNSAnswer_AAAA(p_IPAddress, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_AAAA(A) FAILED!\n"));); - } - if ((bResult) && - (p_rSendParameter.m_u8HostReplyMask & ContentFlag_PTR_IP6)) - { - ((Sequence_Count == sequence) - ? ++ru16Answers - : (bResult = _writeMDNSAnswer_PTR_IP6(p_IPAddress, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_IP6 FAILED!\n"));); - } -#endif - - for (stcMDNSService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) - { - if ((bResult) && - (pService->m_u8ReplyMask & ContentFlag_PTR_TYPE)) - { - ((Sequence_Count == sequence) - ? ++ru16Answers - : (bResult = _writeMDNSAnswer_PTR_TYPE(*pService, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_TYPE FAILED!\n"));); - } - if ((bResult) && - (pService->m_u8ReplyMask & ContentFlag_PTR_NAME)) - { - ((Sequence_Count == sequence) - ? ++ru16Answers - : (bResult = _writeMDNSAnswer_PTR_NAME(*pService, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_NAME FAILED!\n"));); - } - if ((bResult) && - (pService->m_u8ReplyMask & ContentFlag_SRV)) - { - ((Sequence_Count == sequence) - ? ++ru16Answers - : (bResult = _writeMDNSAnswer_SRV(*pService, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_SRV(A) FAILED!\n"));); - } - if ((bResult) && - (pService->m_u8ReplyMask & ContentFlag_TXT)) - { - ((Sequence_Count == sequence) - ? ++ru16Answers - : (bResult = _writeMDNSAnswer_TXT(*pService, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_TXT(A) FAILED!\n"));); - } - } // for services - - // Additional answers -#ifdef MDNS_IP4_SUPPORT - bool bNeedsAdditionalAnswerA = false; -#endif -#ifdef MDNS_IP6_SUPPORT - bool bNeedsAdditionalAnswerAAAA = false; -#endif - for (stcMDNSService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) - { - if ((bResult) && - (pService->m_u8ReplyMask & ContentFlag_PTR_NAME) && // If PTR_NAME is requested, AND - (!(pService->m_u8ReplyMask & ContentFlag_SRV))) // NOT SRV -> add SRV as additional answer - { - ((Sequence_Count == sequence) - ? ++msgHeader.m_u16ARCount - : (bResult = _writeMDNSAnswer_SRV(*pService, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_SRV(B) FAILED!\n"));); - } - if ((bResult) && - (pService->m_u8ReplyMask & ContentFlag_PTR_NAME) && // If PTR_NAME is requested, AND - (!(pService->m_u8ReplyMask & ContentFlag_TXT))) // NOT TXT -> add TXT as additional answer - { - ((Sequence_Count == sequence) - ? ++msgHeader.m_u16ARCount - : (bResult = _writeMDNSAnswer_TXT(*pService, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_TXT(B) FAILED!\n"));); - } - if ((pService->m_u8ReplyMask & (ContentFlag_PTR_NAME | ContentFlag_SRV)) || // If service instance name or SRV OR - (p_rSendParameter.m_u8HostReplyMask & (ContentFlag_A | ContentFlag_AAAA))) // any host IP address is requested - { -#ifdef MDNS_IP4_SUPPORT - if ((bResult) && - (!(p_rSendParameter.m_u8HostReplyMask & ContentFlag_A))) // Add IP4 address - { - bNeedsAdditionalAnswerA = true; - } -#endif -#ifdef MDNS_IP6_SUPPORT - if ((bResult) && - (!(p_rSendParameter.m_u8HostReplyMask & ContentFlag_AAAA))) // Add IP6 address - { - bNeedsAdditionalAnswerAAAA = true; - } -#endif - } - } // for services - - // Answer A needed? -#ifdef MDNS_IP4_SUPPORT - if ((bResult) && - (bNeedsAdditionalAnswerA)) - { - ((Sequence_Count == sequence) - ? ++msgHeader.m_u16ARCount - : (bResult = _writeMDNSAnswer_A(p_IPAddress, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_A(B) FAILED!\n"));); - } -#endif -#ifdef MDNS_IP6_SUPPORT - // Answer AAAA needed? - if ((bResult) && - (bNeedsAdditionalAnswerAAAA)) - { - ((Sequence_Count == sequence) - ? ++msgHeader.m_u16ARCount - : (bResult = _writeMDNSAnswer_AAAA(p_IPAddress, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_AAAA(B) FAILED!\n"));); - } -#endif - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: Loop %i FAILED!\n"), sequence);); - } // for sequence - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_sendMDNSServiceQuery - - Creates and sends a PTR query for the given service domain. - -*/ -bool MDNSResponder::_sendMDNSServiceQuery(const MDNSResponder::stcMDNSServiceQuery& p_ServiceQuery) -{ - - return _sendMDNSQuery(p_ServiceQuery.m_ServiceTypeDomain, DNS_RRTYPE_PTR); -} - -/* - MDNSResponder::_sendMDNSQuery - - Creates and sends a query for the given domain and query type. - -*/ -bool MDNSResponder::_sendMDNSQuery(const MDNSResponder::stcMDNS_RRDomain& p_QueryDomain, - uint16_t p_u16QueryType, - stcMDNSServiceQuery::stcAnswer* p_pKnownAnswers /*= 0*/) -{ - - 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_Header.m_Attributes.m_u16Type = p_u16QueryType; - // It seems, that some mDNS implementations don't support 'unicast response' questions... - sendParameter.m_pQuestions->m_Header.m_Attributes.m_u16Class = (/*0x8000 |*/ DNS_RRCLASS_IN); // /*Unicast &*/ INternet - - // TODO: Add knwon answer to the query - (void)p_pKnownAnswers; - - bResult = _sendMDNSMessage(sendParameter); - } // else: FAILED to alloc question - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSQuery: FAILED to alloc question!\n"));); - return bResult; -} - -/** - HELPERS -*/ - -/** - RESOURCE RECORDS -*/ - -/* - MDNSResponder::_readRRQuestion - - Reads a question (eg. MyESP._http._tcp.local ANY IN) from the UPD input buffer. - -*/ -bool MDNSResponder::_readRRQuestion(MDNSResponder::stcMDNS_RRQuestion& p_rRRQuestion) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRQuestion\n"));); - - bool bResult = false; - - if ((bResult = _readRRHeader(p_rRRQuestion.m_Header))) - { - // Extract unicast flag from class field - p_rRRQuestion.m_bUnicast = (p_rRRQuestion.m_Header.m_Attributes.m_u16Class & 0x8000); - p_rRRQuestion.m_Header.m_Attributes.m_u16Class &= (~0x8000); - - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRQuestion ")); - _printRRDomain(p_rRRQuestion.m_Header.m_Domain); - DEBUG_OUTPUT.printf_P(PSTR(" Type:0x%04X Class:0x%04X %s\n"), (unsigned)p_rRRQuestion.m_Header.m_Attributes.m_u16Type, (unsigned)p_rRRQuestion.m_Header.m_Attributes.m_u16Class, (p_rRRQuestion.m_bUnicast ? "Unicast" : "Multicast")); - ); - } - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRQuestion: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_readRRAnswer - - Reads an answer (eg. _http._tcp.local PTR OP TTL MyESP._http._tcp.local) - from the UDP input buffer. - After reading the domain and type info, the further processing of the answer - is transferred the answer specific reading functions. - Unknown answer types are processed by the generic answer reader (to remove them - from the input buffer). - -*/ -bool MDNSResponder::_readRRAnswer(MDNSResponder::stcMDNS_RRAnswer*& p_rpRRAnswer) -{ - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer\n"));); - - bool bResult = false; - - stcMDNS_RRHeader header; - uint32_t u32TTL; - uint16_t u16RDLength; - if ((_readRRHeader(header)) && - (_udpRead32(u32TTL)) && - (_udpRead16(u16RDLength))) - { - - /* DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer: Reading 0x%04X answer (class:0x%04X, TTL:%u, RDLength:%u) for "), header.m_Attributes.m_u16Type, header.m_Attributes.m_u16Class, u32TTL, u16RDLength); - _printRRDomain(header.m_Domain); - DEBUG_OUTPUT.printf_P(PSTR("\n")); - );*/ - - switch (header.m_Attributes.m_u16Type & (~0x8000)) // Topmost bit might carry 'cache flush' flag - { -#ifdef MDNS_IP4_SUPPORT - case DNS_RRTYPE_A: - p_rpRRAnswer = new stcMDNS_RRAnswerA(header, u32TTL); - bResult = _readRRAnswerA(*(stcMDNS_RRAnswerA*&)p_rpRRAnswer, u16RDLength); - break; -#endif - case DNS_RRTYPE_PTR: - p_rpRRAnswer = new stcMDNS_RRAnswerPTR(header, u32TTL); - bResult = _readRRAnswerPTR(*(stcMDNS_RRAnswerPTR*&)p_rpRRAnswer, u16RDLength); - break; - case DNS_RRTYPE_TXT: - p_rpRRAnswer = new stcMDNS_RRAnswerTXT(header, u32TTL); - bResult = _readRRAnswerTXT(*(stcMDNS_RRAnswerTXT*&)p_rpRRAnswer, u16RDLength); - break; -#ifdef MDNS_IP6_SUPPORT - case DNS_RRTYPE_AAAA: - p_rpRRAnswer = new stcMDNS_RRAnswerAAAA(header, u32TTL); - bResult = _readRRAnswerAAAA(*(stcMDNS_RRAnswerAAAA*&)p_rpRRAnswer, u16RDLength); - break; -#endif - case DNS_RRTYPE_SRV: - p_rpRRAnswer = new stcMDNS_RRAnswerSRV(header, u32TTL); - bResult = _readRRAnswerSRV(*(stcMDNS_RRAnswerSRV*&)p_rpRRAnswer, u16RDLength); - break; - default: - p_rpRRAnswer = new stcMDNS_RRAnswerGeneric(header, u32TTL); - bResult = _readRRAnswerGeneric(*(stcMDNS_RRAnswerGeneric*&)p_rpRRAnswer, u16RDLength); - break; - } - DEBUG_EX_INFO( - if ((bResult) && - (p_rpRRAnswer)) - { - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer: ")); - _printRRDomain(p_rpRRAnswer->m_Header.m_Domain); - DEBUG_OUTPUT.printf_P(PSTR(" Type:0x%04X Class:0x%04X TTL:%u, RDLength:%u "), p_rpRRAnswer->m_Header.m_Attributes.m_u16Type, p_rpRRAnswer->m_Header.m_Attributes.m_u16Class, p_rpRRAnswer->m_u32TTL, u16RDLength); - switch (header.m_Attributes.m_u16Type & (~0x8000)) // Topmost bit might carry 'cache flush' flag - { -#ifdef MDNS_IP4_SUPPORT - case DNS_RRTYPE_A: - DEBUG_OUTPUT.printf_P(PSTR("A IP:%s"), ((stcMDNS_RRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); - break; -#endif - case DNS_RRTYPE_PTR: - DEBUG_OUTPUT.printf_P(PSTR("PTR ")); - _printRRDomain(((stcMDNS_RRAnswerPTR*&)p_rpRRAnswer)->m_PTRDomain); - break; - case DNS_RRTYPE_TXT: - { - size_t stTxtLength = ((stcMDNS_RRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_strLength(); - char* pTxts = new char[stTxtLength]; - if (pTxts) - { - ((stcMDNS_RRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_str(pTxts); - DEBUG_OUTPUT.printf_P(PSTR("TXT(%u) %s"), stTxtLength, pTxts); - delete[] pTxts; - } - break; - } -#ifdef MDNS_IP6_SUPPORT - case DNS_RRTYPE_AAAA: - DEBUG_OUTPUT.printf_P(PSTR("AAAA IP:%s"), ((stcMDNS_RRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); - break; -#endif - case DNS_RRTYPE_SRV: - DEBUG_OUTPUT.printf_P(PSTR("SRV Port:%u "), ((stcMDNS_RRAnswerSRV*&)p_rpRRAnswer)->m_u16Port); - _printRRDomain(((stcMDNS_RRAnswerSRV*&)p_rpRRAnswer)->m_SRVDomain); - break; - default: - DEBUG_OUTPUT.printf_P(PSTR("generic ")); - break; - } - DEBUG_OUTPUT.printf_P(PSTR("\n")); - } - else - { - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer: FAILED to read specific answer of type 0x%04X!\n"), p_rpRRAnswer->m_Header.m_Attributes.m_u16Type); - } - ); // DEBUG_EX_INFO - } - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer: FAILED!\n"));); - return bResult; -} - -#ifdef MDNS_IP4_SUPPORT -/* - MDNSResponder::_readRRAnswerA -*/ -bool MDNSResponder::_readRRAnswerA(MDNSResponder::stcMDNS_RRAnswerA& p_rRRAnswerA, - uint16_t p_u16RDLength) -{ - - uint32_t u32IP4Address; - bool bResult = ((MDNS_IP4_SIZE == p_u16RDLength) && - (_udpReadBuffer((unsigned char*)&u32IP4Address, MDNS_IP4_SIZE)) && - ((p_rRRAnswerA.m_IPAddress = IPAddress(u32IP4Address)))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerA: FAILED!\n"));); - return bResult; -} -#endif - -/* - MDNSResponder::_readRRAnswerPTR -*/ -bool MDNSResponder::_readRRAnswerPTR(MDNSResponder::stcMDNS_RRAnswerPTR& p_rRRAnswerPTR, - uint16_t p_u16RDLength) -{ - - bool bResult = ((p_u16RDLength) && - (_readRRDomain(p_rRRAnswerPTR.m_PTRDomain))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerPTR: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_readRRAnswerTXT - - Read TXT items from a buffer like 4c#=15ff=20 -*/ -bool MDNSResponder::_readRRAnswerTXT(MDNSResponder::stcMDNS_RRAnswerTXT& p_rRRAnswerTXT, - uint16_t p_u16RDLength) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: RDLength:%u\n"), p_u16RDLength);); - bool bResult = true; - - p_rRRAnswerTXT.clear(); - if (p_u16RDLength) - { - bResult = false; - - unsigned char* pucBuffer = new unsigned char[p_u16RDLength]; - if (pucBuffer) - { - if (_udpReadBuffer(pucBuffer, p_u16RDLength)) - { - bResult = true; - - const unsigned char* pucCursor = pucBuffer; - while ((pucCursor < (pucBuffer + p_u16RDLength)) && - (bResult)) - { - bResult = false; - - stcMDNSServiceTxt* pTxt = 0; - unsigned char ucLength = *pucCursor++; // Length of the next txt item - if (ucLength) - { - DEBUG_EX_INFO( - static char sacBuffer[64]; *sacBuffer = 0; - uint8_t u8MaxLength = ((ucLength > (sizeof(sacBuffer) - 1)) ? (sizeof(sacBuffer) - 1) : ucLength); - os_strncpy(sacBuffer, (const char*)pucCursor, u8MaxLength); sacBuffer[u8MaxLength] = 0; - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: Item(%u): %s\n"), ucLength, sacBuffer); - ); - - unsigned char* pucEqualSign = (unsigned char*)os_strchr((const char*)pucCursor, '='); // Position of the '=' sign - unsigned char ucKeyLength; - if ((pucEqualSign) && - ((ucKeyLength = (pucEqualSign - pucCursor)))) - { - unsigned char ucValueLength = (ucLength - (pucEqualSign - pucCursor + 1)); - bResult = (((pTxt = new stcMDNSServiceTxt)) && - (pTxt->setKey((const char*)pucCursor, ucKeyLength)) && - (pTxt->setValue((const char*)(pucEqualSign + 1), ucValueLength))); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: INVALID TXT format (No '=')!\n"));); - } - pucCursor += ucLength; - } - else // no/zero length TXT - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: TXT answer contains no items.\n"));); - bResult = true; - } - - if ((bResult) && - (pTxt)) // Everythings fine so far - { - // Link TXT item to answer TXTs - pTxt->m_pNext = p_rRRAnswerTXT.m_Txts.m_pTxts; - p_rRRAnswerTXT.m_Txts.m_pTxts = pTxt; - } - else // At least no TXT (migth be OK, if length was 0) OR an error - { - if (!bResult) - { - DEBUG_EX_ERR( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: FAILED to read TXT item!\n")); - DEBUG_OUTPUT.printf_P(PSTR("RData dump:\n")); - _udpDump((m_pUDPContext->tell() - p_u16RDLength), p_u16RDLength); - DEBUG_OUTPUT.printf_P(PSTR("\n")); - ); - } - if (pTxt) - { - delete pTxt; - pTxt = 0; - } - p_rRRAnswerTXT.clear(); - } - } // while - - DEBUG_EX_ERR( - if (!bResult) // Some failure - { - DEBUG_OUTPUT.printf_P(PSTR("RData dump:\n")); - _udpDump((m_pUDPContext->tell() - p_u16RDLength), p_u16RDLength); - DEBUG_OUTPUT.printf_P(PSTR("\n")); - } - ); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: FAILED to read TXT content!\n"));); - } - // Clean up - delete[] pucBuffer; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: FAILED to alloc buffer for TXT content!\n"));); - } - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: WARNING! No content!\n"));); - } - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: FAILED!\n"));); - return bResult; -} - -#ifdef MDNS_IP6_SUPPORT -bool MDNSResponder::_readRRAnswerAAAA(MDNSResponder::stcMDNS_RRAnswerAAAA& p_rRRAnswerAAAA, - uint16_t p_u16RDLength) -{ - bool bResult = false; - // TODO: Implement - return bResult; -} -#endif - -/* - MDNSResponder::_readRRAnswerSRV -*/ -bool MDNSResponder::_readRRAnswerSRV(MDNSResponder::stcMDNS_RRAnswerSRV& p_rRRAnswerSRV, - uint16_t p_u16RDLength) -{ - - bool bResult = (((3 * sizeof(uint16_t)) < p_u16RDLength) && - (_udpRead16(p_rRRAnswerSRV.m_u16Priority)) && - (_udpRead16(p_rRRAnswerSRV.m_u16Weight)) && - (_udpRead16(p_rRRAnswerSRV.m_u16Port)) && - (_readRRDomain(p_rRRAnswerSRV.m_SRVDomain))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerSRV: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_readRRAnswerGeneric -*/ -bool MDNSResponder::_readRRAnswerGeneric(MDNSResponder::stcMDNS_RRAnswerGeneric& p_rRRAnswerGeneric, - uint16_t p_u16RDLength) -{ - bool bResult = (0 == p_u16RDLength); - - p_rRRAnswerGeneric.clear(); - if (((p_rRRAnswerGeneric.m_u16RDLength = p_u16RDLength)) && - ((p_rRRAnswerGeneric.m_pu8RDData = new unsigned char[p_rRRAnswerGeneric.m_u16RDLength]))) - { - - bResult = _udpReadBuffer(p_rRRAnswerGeneric.m_pu8RDData, p_rRRAnswerGeneric.m_u16RDLength); - } - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerGeneric: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_readRRHeader -*/ -bool MDNSResponder::_readRRHeader(MDNSResponder::stcMDNS_RRHeader& p_rRRHeader) -{ - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRHeader\n"));); - - bool bResult = ((_readRRDomain(p_rRRHeader.m_Domain)) && - (_readRRAttributes(p_rRRHeader.m_Attributes))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRHeader: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_readRRDomain - - Reads a (maybe multilevel compressed) domain from the UDP input buffer. - -*/ -bool MDNSResponder::_readRRDomain(MDNSResponder::stcMDNS_RRDomain& p_rRRDomain) -{ - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain\n"));); - - bool bResult = ((p_rRRDomain.clear()) && - (_readRRDomain_Loop(p_rRRDomain, 0))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_readRRDomain_Loop - - Reads a domain from the UDP input buffer. For every compression level, the functions - calls itself recursively. To avoid endless recursion because of malformed MDNS records, - the maximum recursion depth is set by MDNS_DOMAIN_MAX_REDIRCTION. - -*/ -bool MDNSResponder::_readRRDomain_Loop(MDNSResponder::stcMDNS_RRDomain& p_rRRDomain, - uint8_t p_u8Depth) -{ - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u)\n"), p_u8Depth);); - - bool bResult = false; - - if (MDNS_DOMAIN_MAX_REDIRCTION >= p_u8Depth) - { - bResult = true; - - uint8_t u8Len = 0; - do - { - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): Offset:%u p0:%02x\n"), p_u8Depth, m_pUDPContext->tell(), m_pUDPContext->peek());); - _udpRead8(u8Len); - - if (u8Len & MDNS_DOMAIN_COMPRESS_MARK) - { - // Compressed label(s) - uint16_t u16Offset = ((u8Len & ~MDNS_DOMAIN_COMPRESS_MARK) << 8); // Implicit BE to LE conversion! - _udpRead8(u8Len); - u16Offset |= u8Len; - - if (m_pUDPContext->isValidOffset(u16Offset)) - { - size_t stCurrentPosition = m_pUDPContext->tell(); // Prepare return from recursion - - //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): Redirecting from %u to %u!\n"), p_u8Depth, stCurrentPosition, u16Offset);); - m_pUDPContext->seek(u16Offset); - if (_readRRDomain_Loop(p_rRRDomain, p_u8Depth + 1)) // Do recursion - { - //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): Succeeded to read redirected label! Returning to %u\n"), p_u8Depth, stCurrentPosition);); - m_pUDPContext->seek(stCurrentPosition); // Restore after recursion - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): FAILED to read redirected label!\n"), p_u8Depth);); - bResult = false; - } - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): INVALID offset in redirection!\n"), p_u8Depth);); - bResult = false; - } - break; - } - else - { - // Normal (uncompressed) label (maybe '\0' only) - if (MDNS_DOMAIN_MAXLENGTH > (p_rRRDomain.m_u16NameLength + u8Len)) - { - // Add length byte - p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength] = u8Len; - ++(p_rRRDomain.m_u16NameLength); - if (u8Len) // Add name - { - if ((bResult = _udpReadBuffer((unsigned char*) & (p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength]), u8Len))) - { - /* DEBUG_EX_INFO( - p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength + u8Len] = 0; // Closing '\0' for printing - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): Domain label (%u): %s\n"), p_u8Depth, (unsigned)(p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength - 1]), &(p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength])); - );*/ - - p_rRRDomain.m_u16NameLength += u8Len; - } - } - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(2) offset:%u p0:%x\n"), m_pUDPContext->tell(), m_pUDPContext->peek());); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): ERROR! Domain name too long (%u + %u)!\n"), p_u8Depth, p_rRRDomain.m_u16NameLength, u8Len);); - bResult = false; - break; - } - } - } while ((bResult) && - (0 != u8Len)); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): ERROR! Too many redirections!\n"), p_u8Depth);); - } - return bResult; -} - -/* - MDNSResponder::_readRRAttributes -*/ -bool MDNSResponder::_readRRAttributes(MDNSResponder::stcMDNS_RRAttributes& p_rRRAttributes) -{ - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAttributes\n"));); - - bool bResult = ((_udpRead16(p_rRRAttributes.m_u16Type)) && - (_udpRead16(p_rRRAttributes.m_u16Class))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAttributes: FAILED!\n"));); - return bResult; -} - - -/* - DOMAIN NAMES -*/ - -/* - MDNSResponder::_buildDomainForHost - - Builds a MDNS host domain (eg. esp8266.local) for the given hostname. - -*/ -bool MDNSResponder::_buildDomainForHost(const char* p_pcHostname, - MDNSResponder::stcMDNS_RRDomain& p_rHostDomain) const -{ - - p_rHostDomain.clear(); - bool bResult = ((p_pcHostname) && - (*p_pcHostname) && - (p_rHostDomain.addLabel(p_pcHostname)) && - (p_rHostDomain.addLabel(scpcLocal)) && - (p_rHostDomain.addLabel(0))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForHost: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_buildDomainForDNSSD - - Builds the '_services._dns-sd._udp.local' domain. - Used while detecting generic service enum question (DNS-SD) and answering these questions. - -*/ -bool MDNSResponder::_buildDomainForDNSSD(MDNSResponder::stcMDNS_RRDomain& p_rDNSSDDomain) const -{ - - p_rDNSSDDomain.clear(); - bool bResult = ((p_rDNSSDDomain.addLabel(scpcServices, true)) && - (p_rDNSSDDomain.addLabel(scpcDNSSD, true)) && - (p_rDNSSDDomain.addLabel(scpcUDP, true)) && - (p_rDNSSDDomain.addLabel(scpcLocal)) && - (p_rDNSSDDomain.addLabel(0))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForDNSSD: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_buildDomainForService - - Builds the domain for the given service (eg. _http._tcp.local or - MyESP._http._tcp.local (if p_bIncludeName is set)). - -*/ -bool MDNSResponder::_buildDomainForService(const MDNSResponder::stcMDNSService& p_Service, - bool p_bIncludeName, - MDNSResponder::stcMDNS_RRDomain& p_rServiceDomain) const -{ - - p_rServiceDomain.clear(); - bool bResult = (((!p_bIncludeName) || - (p_rServiceDomain.addLabel(p_Service.m_pcName))) && - (p_rServiceDomain.addLabel(p_Service.m_pcService, true)) && - (p_rServiceDomain.addLabel(p_Service.m_pcProtocol, true)) && - (p_rServiceDomain.addLabel(scpcLocal)) && - (p_rServiceDomain.addLabel(0))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForService: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_buildDomainForService - - Builds the domain for the given service properties (eg. _http._tcp.local). - The usual prepended '_' are added, if missing in the input strings. - -*/ -bool MDNSResponder::_buildDomainForService(const char* p_pcService, - const char* p_pcProtocol, - MDNSResponder::stcMDNS_RRDomain& p_rServiceDomain) const -{ - - p_rServiceDomain.clear(); - bool bResult = ((p_pcService) && - (p_pcProtocol) && - (p_rServiceDomain.addLabel(p_pcService, ('_' != *p_pcService))) && - (p_rServiceDomain.addLabel(p_pcProtocol, ('_' != *p_pcProtocol))) && - (p_rServiceDomain.addLabel(scpcLocal)) && - (p_rServiceDomain.addLabel(0))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForService: FAILED for (%s.%s)!\n"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); - return bResult; -} - -#ifdef MDNS_IP4_SUPPORT -/* - MDNSResponder::_buildDomainForReverseIP4 - - The IP4 address is stringized by printing the four address bytes into a char buffer in reverse order - and adding 'in-addr.arpa' (eg. 012.789.456.123.in-addr.arpa). - Used while detecting reverse IP4 questions and answering these -*/ -bool MDNSResponder::_buildDomainForReverseIP4(IPAddress p_IP4Address, - MDNSResponder::stcMDNS_RRDomain& p_rReverseIP4Domain) const -{ - - bool bResult = true; - - p_rReverseIP4Domain.clear(); - - char acBuffer[32]; - for (int i = MDNS_IP4_SIZE; ((bResult) && (i >= 1)); --i) - { - itoa(p_IP4Address[i - 1], acBuffer, 10); - bResult = p_rReverseIP4Domain.addLabel(acBuffer); - } - bResult = ((bResult) && - (p_rReverseIP4Domain.addLabel(scpcReverseIP4Domain)) && - (p_rReverseIP4Domain.addLabel(scpcReverseTopDomain)) && - (p_rReverseIP4Domain.addLabel(0))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForReverseIP4: FAILED!\n"));); - return bResult; -} -#endif - -#ifdef MDNS_IP6_SUPPORT -/* - MDNSResponder::_buildDomainForReverseIP6 - - Used while detecting reverse IP6 questions and answering these -*/ -bool MDNSResponder::_buildDomainForReverseIP6(IPAddress p_IP4Address, - MDNSResponder::stcMDNS_RRDomain& p_rReverseIP6Domain) const -{ - // TODO: Implement - return false; -} -#endif - - -/* - UDP -*/ - -/* - MDNSResponder::_udpReadBuffer -*/ -bool MDNSResponder::_udpReadBuffer(unsigned char* p_pBuffer, - size_t p_stLength) -{ - - bool bResult = ((m_pUDPContext) && - (true/*m_pUDPContext->getSize() > p_stLength*/) && - (p_pBuffer) && - (p_stLength) && - ((p_stLength == m_pUDPContext->read((char*)p_pBuffer, p_stLength)))); - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _udpReadBuffer: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_udpRead8 -*/ -bool MDNSResponder::_udpRead8(uint8_t& p_ru8Value) -{ - - return _udpReadBuffer((unsigned char*)&p_ru8Value, sizeof(p_ru8Value)); -} - -/* - MDNSResponder::_udpRead16 -*/ -bool MDNSResponder::_udpRead16(uint16_t& p_ru16Value) -{ - - bool bResult = false; - - if (_udpReadBuffer((unsigned char*)&p_ru16Value, sizeof(p_ru16Value))) - { - p_ru16Value = lwip_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 = lwip_ntohl(p_ru32Value); - bResult = true; - } - return bResult; -} - -/* - MDNSResponder::_udpAppendBuffer -*/ -bool MDNSResponder::_udpAppendBuffer(const unsigned char* p_pcBuffer, - size_t p_stLength) -{ - - bool bResult = ((m_pUDPContext) && - (p_pcBuffer) && - (p_stLength) && - (p_stLength == m_pUDPContext->append((const char*)p_pcBuffer, p_stLength))); - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _udpAppendBuffer: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_udpAppend8 -*/ -bool MDNSResponder::_udpAppend8(uint8_t p_u8Value) -{ - - return (_udpAppendBuffer((unsigned char*)&p_u8Value, sizeof(p_u8Value))); -} - -/* - MDNSResponder::_udpAppend16 -*/ -bool MDNSResponder::_udpAppend16(uint16_t p_u16Value) -{ - - p_u16Value = lwip_htons(p_u16Value); - return (_udpAppendBuffer((unsigned char*)&p_u16Value, sizeof(p_u16Value))); -} - -/* - MDNSResponder::_udpAppend32 -*/ -bool MDNSResponder::_udpAppend32(uint32_t p_u32Value) -{ - - p_u32Value = lwip_htonl(p_u32Value); - return (_udpAppendBuffer((unsigned char*)&p_u32Value, sizeof(p_u32Value))); -} - -#ifdef DEBUG_ESP_MDNS_RESPONDER -/* - MDNSResponder::_udpDump -*/ -bool MDNSResponder::_udpDump(bool p_bMovePointer /*= false*/) -{ - - const uint8_t cu8BytesPerLine = 16; - - uint32_t u32StartPosition = m_pUDPContext->tell(); - DEBUG_OUTPUT.println("UDP Context Dump:"); - uint32_t u32Counter = 0; - uint8_t u8Byte = 0; - - while (_udpRead8(u8Byte)) - { - DEBUG_OUTPUT.printf_P(PSTR("%02x %s"), u8Byte, ((++u32Counter % cu8BytesPerLine) ? "" : "\n")); - } - DEBUG_OUTPUT.printf_P(PSTR("%sDone: %u bytes\n"), (((u32Counter) && (u32Counter % cu8BytesPerLine)) ? "\n" : ""), u32Counter); - - if (!p_bMovePointer) // Restore - { - m_pUDPContext->seek(u32StartPosition); - } - return true; -} - -/* - MDNSResponder::_udpDump -*/ -bool MDNSResponder::_udpDump(unsigned p_uOffset, - unsigned p_uLength) -{ - - if ((m_pUDPContext) && - (m_pUDPContext->isValidOffset(p_uOffset))) - { - unsigned uCurrentPosition = m_pUDPContext->tell(); // Remember start position - - m_pUDPContext->seek(p_uOffset); - uint8_t u8Byte; - for (unsigned u = 0; ((u < p_uLength) && (_udpRead8(u8Byte))); ++u) - { - DEBUG_OUTPUT.printf_P(PSTR("%02x "), u8Byte); - } - // Return to start position - m_pUDPContext->seek(uCurrentPosition); - } - return true; -} -#endif - - -/** - READ/WRITE MDNS STRUCTS -*/ - -/* - MDNSResponder::_readMDNSMsgHeader - - Read a MDNS header from the UDP input buffer. - | 8 | 8 | 8 | 8 | - 00| Identifier | Flags & Codes | - 01| Question count | Answer count | - 02| NS answer count | Ad answer count | - - All 16-bit and 32-bit elements need to be translated form network coding to host coding (done in _udpRead16 and _udpRead32) - In addition, bitfield memory order is undefined in C standard (GCC doesn't order them in the coded direction...), so they - need some mapping here -*/ -bool MDNSResponder::_readMDNSMsgHeader(MDNSResponder::stcMDNS_MsgHeader& p_rMsgHeader) -{ - - bool bResult = false; - - uint8_t u8B1; - uint8_t u8B2; - if ((_udpRead16(p_rMsgHeader.m_u16ID)) && - (_udpRead8(u8B1)) && - (_udpRead8(u8B2)) && - (_udpRead16(p_rMsgHeader.m_u16QDCount)) && - (_udpRead16(p_rMsgHeader.m_u16ANCount)) && - (_udpRead16(p_rMsgHeader.m_u16NSCount)) && - (_udpRead16(p_rMsgHeader.m_u16ARCount))) - { - - p_rMsgHeader.m_1bQR = (u8B1 & 0x80); // Query/Responde flag - p_rMsgHeader.m_4bOpcode = (u8B1 & 0x78); // Operation code (0: Standard query, others ignored) - p_rMsgHeader.m_1bAA = (u8B1 & 0x04); // Authorative answer - p_rMsgHeader.m_1bTC = (u8B1 & 0x02); // Truncation flag - p_rMsgHeader.m_1bRD = (u8B1 & 0x01); // Recursion desired - - p_rMsgHeader.m_1bRA = (u8B2 & 0x80); // Recursion available - p_rMsgHeader.m_3bZ = (u8B2 & 0x70); // Zero - p_rMsgHeader.m_4bRCode = (u8B2 & 0x0F); // Response code - - /* DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readMDNSMsgHeader: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), - (unsigned)p_rMsgHeader.m_u16ID, - (unsigned)p_rMsgHeader.m_1bQR, (unsigned)p_rMsgHeader.m_4bOpcode, (unsigned)p_rMsgHeader.m_1bAA, (unsigned)p_rMsgHeader.m_1bTC, (unsigned)p_rMsgHeader.m_1bRD, - (unsigned)p_rMsgHeader.m_1bRA, (unsigned)p_rMsgHeader.m_4bRCode, - (unsigned)p_rMsgHeader.m_u16QDCount, - (unsigned)p_rMsgHeader.m_u16ANCount, - (unsigned)p_rMsgHeader.m_u16NSCount, - (unsigned)p_rMsgHeader.m_u16ARCount););*/ - bResult = true; - } - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readMDNSMsgHeader: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_write8 -*/ -bool MDNSResponder::_write8(uint8_t p_u8Value, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - return ((_udpAppend8(p_u8Value)) && - (p_rSendParameter.shiftOffset(sizeof(p_u8Value)))); -} - -/* - MDNSResponder::_write16 -*/ -bool MDNSResponder::_write16(uint16_t p_u16Value, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - return ((_udpAppend16(p_u16Value)) && - (p_rSendParameter.shiftOffset(sizeof(p_u16Value)))); -} - -/* - MDNSResponder::_write32 -*/ -bool MDNSResponder::_write32(uint32_t p_u32Value, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - return ((_udpAppend32(p_u32Value)) && - (p_rSendParameter.shiftOffset(sizeof(p_u32Value)))); -} - -/* - MDNSResponder::_writeMDNSMsgHeader - - Write MDNS header to the UDP output buffer. - - All 16-bit and 32-bit elements need to be translated form host coding to network coding (done in _udpAppend16 and _udpAppend32) - In addition, bitfield memory order is undefined in C standard (GCC doesn't order them in the coded direction...), so they - need some mapping here -*/ -bool MDNSResponder::_writeMDNSMsgHeader(const MDNSResponder::stcMDNS_MsgHeader& p_MsgHeader, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - /* DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSMsgHeader: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), - (unsigned)p_MsgHeader.m_u16ID, - (unsigned)p_MsgHeader.m_1bQR, (unsigned)p_MsgHeader.m_4bOpcode, (unsigned)p_MsgHeader.m_1bAA, (unsigned)p_MsgHeader.m_1bTC, (unsigned)p_MsgHeader.m_1bRD, - (unsigned)p_MsgHeader.m_1bRA, (unsigned)p_MsgHeader.m_4bRCode, - (unsigned)p_MsgHeader.m_u16QDCount, - (unsigned)p_MsgHeader.m_u16ANCount, - (unsigned)p_MsgHeader.m_u16NSCount, - (unsigned)p_MsgHeader.m_u16ARCount););*/ - - uint8_t u8B1((p_MsgHeader.m_1bQR << 7) | (p_MsgHeader.m_4bOpcode << 3) | (p_MsgHeader.m_1bAA << 2) | (p_MsgHeader.m_1bTC << 1) | (p_MsgHeader.m_1bRD)); - uint8_t u8B2((p_MsgHeader.m_1bRA << 7) | (p_MsgHeader.m_3bZ << 4) | (p_MsgHeader.m_4bRCode)); - bool bResult = ((_write16(p_MsgHeader.m_u16ID, p_rSendParameter)) && - (_write8(u8B1, p_rSendParameter)) && - (_write8(u8B2, p_rSendParameter)) && - (_write16(p_MsgHeader.m_u16QDCount, p_rSendParameter)) && - (_write16(p_MsgHeader.m_u16ANCount, p_rSendParameter)) && - (_write16(p_MsgHeader.m_u16NSCount, p_rSendParameter)) && - (_write16(p_MsgHeader.m_u16ARCount, p_rSendParameter))); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSMsgHeader: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_writeRRAttributes -*/ -bool MDNSResponder::_writeMDNSRRAttributes(const MDNSResponder::stcMDNS_RRAttributes& p_Attributes, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - bool bResult = ((_write16(p_Attributes.m_u16Type, p_rSendParameter)) && - (_write16(p_Attributes.m_u16Class, p_rSendParameter))); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSRRAttributes: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_writeMDNSRRDomain -*/ -bool MDNSResponder::_writeMDNSRRDomain(const MDNSResponder::stcMDNS_RRDomain& p_Domain, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - bool bResult = ((_udpAppendBuffer((const unsigned char*)p_Domain.m_acName, p_Domain.m_u16NameLength)) && - (p_rSendParameter.shiftOffset(p_Domain.m_u16NameLength))); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSRRDomain: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_writeMDNSHostDomain - - Write a host domain to the UDP output buffer. - If the domain record is part of the answer, the records length is - prepended (p_bPrependRDLength is set). - - A very simple form of name compression is applied here: - If the domain is written to the UDP output buffer, the write offset is stored - together with a domain id (the pointer) in a p_rSendParameter substructure (cache). - If the same domain (pointer) should be written to the UDP output later again, - the old offset is retrieved from the cache, marked as a compressed domain offset - and written to the output buffer. - -*/ -bool MDNSResponder::_writeMDNSHostDomain(const char* p_pcHostname, - bool p_bPrependRDLength, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV' - uint16_t u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)p_pcHostname, false); - - stcMDNS_RRDomain hostDomain; - bool bResult = (u16CachedDomainOffset - // Found cached domain -> mark as compressed domain - ? ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset - ((!p_bPrependRDLength) || - (_write16(2, p_rSendParameter))) && // Length of 'Cxxx' - (_write8(((u16CachedDomainOffset >> 8) | MDNS_DOMAIN_COMPRESS_MARK), p_rSendParameter)) && // Compression mark (and offset) - (_write8((uint8_t)(u16CachedDomainOffset & 0xFF), p_rSendParameter))) - // No cached domain -> add this domain to cache and write full domain name - : ((_buildDomainForHost(p_pcHostname, hostDomain)) && // eg. esp8266.local - ((!p_bPrependRDLength) || - (_write16(hostDomain.m_u16NameLength, p_rSendParameter))) && // RDLength (if needed) - (p_rSendParameter.addDomainCacheItem((const void*)p_pcHostname, false, p_rSendParameter.m_u16Offset)) && - (_writeMDNSRRDomain(hostDomain, p_rSendParameter)))); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSHostDomain: FAILED!\n")); - }); - return bResult; - -} - -/* - MDNSResponder::_writeMDNSServiceDomain - - Write a service domain to the UDP output buffer. - If the domain record is part of the answer, the records length is - prepended (p_bPrependRDLength is set). - - A very simple form of name compression is applied here: see '_writeMDNSHostDomain' - The cache differentiates of course between service domains which includes - the instance name (p_bIncludeName is set) and thoose who don't. - -*/ -bool MDNSResponder::_writeMDNSServiceDomain(const MDNSResponder::stcMDNSService& p_Service, - bool p_bIncludeName, - bool p_bPrependRDLength, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV' - uint16_t u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)&p_Service, p_bIncludeName); - - stcMDNS_RRDomain serviceDomain; - bool bResult = (u16CachedDomainOffset - // Found cached domain -> mark as compressed domain - ? ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset - ((!p_bPrependRDLength) || - (_write16(2, p_rSendParameter))) && // Lenght of 'Cxxx' - (_write8(((u16CachedDomainOffset >> 8) | MDNS_DOMAIN_COMPRESS_MARK), p_rSendParameter)) && // Compression mark (and offset) - (_write8((uint8_t)(u16CachedDomainOffset & 0xFF), p_rSendParameter))) - // No cached domain -> add this domain to cache and write full domain name - : ((_buildDomainForService(p_Service, p_bIncludeName, serviceDomain)) && // eg. MyESP._http._tcp.local - ((!p_bPrependRDLength) || - (_write16(serviceDomain.m_u16NameLength, p_rSendParameter))) && // RDLength (if needed) - (p_rSendParameter.addDomainCacheItem((const void*)&p_Service, p_bIncludeName, p_rSendParameter.m_u16Offset)) && - (_writeMDNSRRDomain(serviceDomain, p_rSendParameter)))); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSServiceDomain: FAILED!\n")); - }); - return bResult; - -} - -/* - MDNSResponder::_writeMDNSQuestion - - Write a MDNS question to the UDP output buffer - - QNAME (host/service domain, eg. esp8266.local) - QTYPE (16bit, eg. ANY) - QCLASS (16bit, eg. IN) - -*/ -bool MDNSResponder::_writeMDNSQuestion(MDNSResponder::stcMDNS_RRQuestion& p_Question, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSQuestion\n"));); - - bool bResult = ((_writeMDNSRRDomain(p_Question.m_Header.m_Domain, p_rSendParameter)) && - (_writeMDNSRRAttributes(p_Question.m_Header.m_Attributes, p_rSendParameter))); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSQuestion: FAILED!\n")); - }); - return bResult; - -} - - -#ifdef MDNS_IP4_SUPPORT -/* - MDNSResponder::_writeMDNSAnswer_A - - Write a MDNS A answer to the UDP output buffer. - - NAME (var, host/service domain, eg. esp8266.local - TYPE (16bit, eg. A) - CLASS (16bit, eg. IN) - TTL (32bit, eg. 120) - RDLENGTH (16bit, eg 4) - RDATA (var, eg. 123.456.789.012) - - eg. esp8266.local A 0x8001 120 4 123.456.789.012 - Ref: http://www.zytrax.com/books/dns/ch8/a.html -*/ -bool MDNSResponder::_writeMDNSAnswer_A(IPAddress p_IPAddress, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_A (%s)\n"), p_IPAddress.toString().c_str());); - - stcMDNS_RRAttributes attributes(DNS_RRTYPE_A, - ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - const unsigned char aucIPAddress[MDNS_IP4_SIZE] = { p_IPAddress[0], p_IPAddress[1], p_IPAddress[2], p_IPAddress[3] }; - bool bResult = ((_writeMDNSHostDomain(m_pcHostname, false, p_rSendParameter)) && - (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS - (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) && // TTL - (_write16(MDNS_IP4_SIZE, p_rSendParameter)) && // RDLength - (_udpAppendBuffer(aucIPAddress, MDNS_IP4_SIZE)) && // RData - (p_rSendParameter.shiftOffset(MDNS_IP4_SIZE))); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_A: FAILED!\n")); - }); - return bResult; - -} - -/* - MDNSResponder::_writeMDNSAnswer_PTR_IP4 - - Write a MDNS reverse IP4 PTR answer to the UDP output buffer. - See: '_writeMDNSAnswer_A' - - eg. 012.789.456.123.in-addr.arpa PTR 0x8001 120 15 esp8266.local - Used while answering reverse IP4 questions -*/ -bool MDNSResponder::_writeMDNSAnswer_PTR_IP4(IPAddress p_IPAddress, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_IP4 (%s)\n"), p_IPAddress.toString().c_str());); - - stcMDNS_RRDomain reverseIP4Domain; - stcMDNS_RRAttributes attributes(DNS_RRTYPE_PTR, - ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - stcMDNS_RRDomain hostDomain; - bool bResult = ((_buildDomainForReverseIP4(p_IPAddress, reverseIP4Domain)) && // 012.789.456.123.in-addr.arpa - (_writeMDNSRRDomain(reverseIP4Domain, p_rSendParameter)) && - (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS - (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) && // TTL - (_writeMDNSHostDomain(m_pcHostname, true, p_rSendParameter))); // RDLength & RData (host domain, eg. esp8266.local) - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_IP4: FAILED!\n")); - }); - return bResult; -} -#endif - -/* - MDNSResponder::_writeMDNSAnswer_PTR_TYPE - - Write a MDNS PTR answer to the UDP output buffer. - See: '_writeMDNSAnswer_A' - - PTR all-services -> service type - eg. _services._dns-sd._udp.local PTR 0x8001 5400 xx _http._tcp.local - http://www.zytrax.com/books/dns/ch8/ptr.html -*/ -bool MDNSResponder::_writeMDNSAnswer_PTR_TYPE(MDNSResponder::stcMDNSService& p_rService, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_TYPE\n"));); - - stcMDNS_RRDomain dnssdDomain; - stcMDNS_RRDomain serviceDomain; - stcMDNS_RRAttributes attributes(DNS_RRTYPE_PTR, DNS_RRCLASS_IN); // No cache flush! only INternet - bool bResult = ((_buildDomainForDNSSD(dnssdDomain)) && // _services._dns-sd._udp.local - (_writeMDNSRRDomain(dnssdDomain, p_rSendParameter)) && - (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS - (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL - (_writeMDNSServiceDomain(p_rService, false, true, p_rSendParameter))); // RDLength & RData (service domain, eg. _http._tcp.local) - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_TYPE: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_writeMDNSAnswer_PTR_NAME - - Write a MDNS PTR answer to the UDP output buffer. - See: '_writeMDNSAnswer_A' - - PTR service type -> service name - eg. _http.tcp.local PTR 0x8001 120 xx myESP._http._tcp.local - http://www.zytrax.com/books/dns/ch8/ptr.html -*/ -bool MDNSResponder::_writeMDNSAnswer_PTR_NAME(MDNSResponder::stcMDNSService& p_rService, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_NAME\n"));); - - stcMDNS_RRAttributes attributes(DNS_RRTYPE_PTR, DNS_RRCLASS_IN); // No cache flush! only INternet - bool bResult = ((_writeMDNSServiceDomain(p_rService, false, false, p_rSendParameter)) && // _http._tcp.local - (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS - (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL - (_writeMDNSServiceDomain(p_rService, true, true, p_rSendParameter))); // RDLength & RData (service domain, eg. MyESP._http._tcp.local) - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_NAME: FAILED!\n")); - }); - return bResult; -} - - -/* - MDNSResponder::_writeMDNSAnswer_TXT - - Write a MDNS TXT answer to the UDP output buffer. - See: '_writeMDNSAnswer_A' - - The TXT items in the RDATA block are 'length byte encoded': [len]vardata - - eg. myESP._http._tcp.local TXT 0x8001 120 4 c#=1 - http://www.zytrax.com/books/dns/ch8/txt.html -*/ -bool MDNSResponder::_writeMDNSAnswer_TXT(MDNSResponder::stcMDNSService& p_rService, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_TXT\n"));); - - bool bResult = false; - - stcMDNS_RRAttributes attributes(DNS_RRTYPE_TXT, - ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - - if ((_collectServiceTxts(p_rService)) && - (_writeMDNSServiceDomain(p_rService, true, false, p_rSendParameter)) && // MyESP._http._tcp.local - (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS - (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL - (_write16(p_rService.m_Txts.length(), p_rSendParameter))) // RDLength - { - - bResult = true; - // RData Txts - for (stcMDNSServiceTxt* pTxt = p_rService.m_Txts.m_pTxts; ((bResult) && (pTxt)); pTxt = pTxt->m_pNext) - { - unsigned char ucLengthByte = pTxt->length(); - bResult = ((_udpAppendBuffer((unsigned char*)&ucLengthByte, sizeof(ucLengthByte))) && // Length - (p_rSendParameter.shiftOffset(sizeof(ucLengthByte))) && - ((size_t)os_strlen(pTxt->m_pcKey) == m_pUDPContext->append(pTxt->m_pcKey, os_strlen(pTxt->m_pcKey))) && // Key - (p_rSendParameter.shiftOffset((size_t)os_strlen(pTxt->m_pcKey))) && - (1 == m_pUDPContext->append("=", 1)) && // = - (p_rSendParameter.shiftOffset(1)) && - ((!pTxt->m_pcValue) || - (((size_t)os_strlen(pTxt->m_pcValue) == m_pUDPContext->append(pTxt->m_pcValue, os_strlen(pTxt->m_pcValue))) && // Value - (p_rSendParameter.shiftOffset((size_t)os_strlen(pTxt->m_pcValue)))))); - - DEBUG_EX_ERR(if (!bResult) - { - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_TXT: FAILED to write %sTxt %s=%s!\n"), (pTxt->m_bTemp ? "temp. " : ""), (pTxt->m_pcKey ? : "?"), (pTxt->m_pcValue ? : "?")); - }); - } - } - _releaseTempServiceTxts(p_rService); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_TXT: FAILED!\n")); - }); - return bResult; -} - -#ifdef MDNS_IP6_SUPPORT -/* - MDNSResponder::_writeMDNSAnswer_AAAA - - Write a MDNS AAAA answer to the UDP output buffer. - See: '_writeMDNSAnswer_A' - - eg. esp8266.local AAAA 0x8001 120 16 xxxx::xx - http://www.zytrax.com/books/dns/ch8/aaaa.html -*/ -bool MDNSResponder::_writeMDNSAnswer_AAAA(IPAddress p_IPAddress, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_AAAA\n"));); - - stcMDNS_RRAttributes attributes(DNS_RRTYPE_AAAA, - ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - bool bResult = ((_writeMDNSHostDomain(m_pcHostname, false, p_rSendParameter)) && // esp8266.local - (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS - (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) && // TTL - (_write16(MDNS_IP6_SIZE, p_rSendParameter)) && // RDLength - (false /*TODO: IP6 version of: _udpAppendBuffer((uint32_t)p_IPAddress, MDNS_IP4_SIZE)*/)); // RData - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_AAAA: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_writeMDNSAnswer_PTR_IP6 - - Write a MDNS reverse IP6 PTR answer to the UDP output buffer. - See: '_writeMDNSAnswer_A' - - eg. xxxx::xx.in6.arpa PTR 0x8001 120 15 esp8266.local - Used while answering reverse IP6 questions -*/ -bool MDNSResponder::_writeMDNSAnswer_PTR_IP6(IPAddress p_IPAddress, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_IP6\n"));); - - stcMDNS_RRDomain reverseIP6Domain; - stcMDNS_RRAttributes attributes(DNS_RRTYPE_PTR, - ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - bool bResult = ((_buildDomainForReverseIP6(p_IPAddress, reverseIP6Domain)) && // xxxx::xx.ip6.arpa - (_writeMDNSRRDomain(reverseIP6Domain, p_rSendParameter)) && - (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS - (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) && // TTL - (_writeMDNSHostDomain(m_pcHostname, true, p_rSendParameter))); // RDLength & RData (host domain, eg. esp8266.local) - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_IP6: FAILED!\n")); - }); - return bResult; -} -#endif - -/* - MDNSResponder::_writeMDNSAnswer_SRV - - eg. MyESP._http.tcp.local SRV 0x8001 120 0 0 60068 esp8266.local - http://www.zytrax.com/books/dns/ch8/srv.html ???? Include instance name ???? -*/ -bool MDNSResponder::_writeMDNSAnswer_SRV(MDNSResponder::stcMDNSService& p_rService, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_SRV\n"));); - - uint16_t u16CachedDomainOffset = (p_rSendParameter.m_bLegacyQuery - ? 0 - : p_rSendParameter.findCachedDomainOffset((const void*)m_pcHostname, false)); - - stcMDNS_RRAttributes attributes(DNS_RRTYPE_SRV, - ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - stcMDNS_RRDomain hostDomain; - bool bResult = ((_writeMDNSServiceDomain(p_rService, true, false, p_rSendParameter)) && // MyESP._http._tcp.local - (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS - (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL - (!u16CachedDomainOffset - // No cache for domain name (or no compression allowed) - ? ((_buildDomainForHost(m_pcHostname, hostDomain)) && - (_write16((sizeof(uint16_t /*Prio*/) + // RDLength - sizeof(uint16_t /*Weight*/) + - sizeof(uint16_t /*Port*/) + - hostDomain.m_u16NameLength), p_rSendParameter)) && // Domain length - (_write16(MDNS_SRV_PRIORITY, p_rSendParameter)) && // Priority - (_write16(MDNS_SRV_WEIGHT, p_rSendParameter)) && // Weight - (_write16(p_rService.m_u16Port, p_rSendParameter)) && // Port - (p_rSendParameter.addDomainCacheItem((const void*)m_pcHostname, false, p_rSendParameter.m_u16Offset)) && - (_writeMDNSRRDomain(hostDomain, p_rSendParameter))) // Host, eg. esp8266.local - // Cache available for domain - : ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset - (_write16((sizeof(uint16_t /*Prio*/) + // RDLength - sizeof(uint16_t /*Weight*/) + - sizeof(uint16_t /*Port*/) + - 2), p_rSendParameter)) && // Length of 'C0xx' - (_write16(MDNS_SRV_PRIORITY, p_rSendParameter)) && // Priority - (_write16(MDNS_SRV_WEIGHT, p_rSendParameter)) && // Weight - (_write16(p_rService.m_u16Port, p_rSendParameter)) && // Port - (_write8(((u16CachedDomainOffset >> 8) | MDNS_DOMAIN_COMPRESS_MARK), p_rSendParameter)) && // Compression mark (and offset) - (_write8((uint8_t)u16CachedDomainOffset, p_rSendParameter))))); // Offset - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_SRV: FAILED!\n")); - }); - return bResult; -} - -} // namespace MDNSImplementation - -} // namespace esp8266 - - - - - - diff --git a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_lwIPdefs.h b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_lwIPdefs.h deleted file mode 100644 index a3bcc4b370..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_lwIPdefs.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - 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 MDNS_LWIPDEFS_H -#define MDNS_LWIPDEFS_H - -#include -#if LWIP_VERSION_MAJOR == 1 - -#include // DNS_RRTYPE_xxx - -// cherry pick from lwip1 dns.c/mdns.c source files: -#define DNS_MQUERY_PORT 5353 -#define DNS_MQUERY_IPV4_GROUP_INIT IPAddress(224,0,0,251) /* resolver1.opendns.com */ -#define DNS_RRCLASS_ANY 255 /* any class */ - -#else // lwIP > 1 - -#include // DNS_RRTYPE_xxx, DNS_MQUERY_PORT - -#endif - -#endif // MDNS_LWIPDEFS_H diff --git a/libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-SPIFFS.ino b/libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-LittleFS.ino similarity index 100% rename from libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-SPIFFS.ino rename to libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-LittleFS.ino diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 740b77db6f..c8c05082c5 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -32,6 +32,15 @@ #include #endif +/** + STRINGIZE +*/ +#ifndef STRINGIZE +#define STRINGIZE(x) #x +#endif +#ifndef STRINGIZE_VALUE_OF +#define STRINGIZE_VALUE_OF(x) STRINGIZE(x) +#endif namespace // anonymous { @@ -778,6 +787,27 @@ bool clsLEAMDNSHost::restart(void) } +/* + clsLEAMDNSHost_Legacy::enableArduino +*/ +clsLEAMDNSHost::clsService* clsLEAMDNSHost::enableArduino(uint16_t p_u16Port, + bool p_bAuthUpload /*= false*/) +{ + clsLEAMDNSHost::clsService* svc = addService("arduino", "arduino", "tcp", p_u16Port); + if (svc) + { + if ((!svc->addServiceTxt("tcp_check", "no")) + || (!svc->addServiceTxt("ssh_upload", "no")) + || (!svc->addServiceTxt("board", STRINGIZE_VALUE_OF(ARDUINO_BOARD))) + || (!svc->addServiceTxt("auth_upload", (p_bAuthUpload) ? "yes" : "no"))) + { + removeService(svc); + svc = 0; + } + } + return svc; +} + /* P R O T E C T E D diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index ceee11834a..bdcc05e21e 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -1350,6 +1350,8 @@ class clsLEAMDNSHost bool restart(void); + clsService* enableArduino(uint16_t p_u16Port, bool p_bAuthUpload = false); + protected: // File: ..._Host UdpContext* _allocBackbone(void); From 6d22b7073b879668ccdaf0213ec6bdc8252234d1 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 8 May 2020 22:09:05 +0200 Subject: [PATCH 008/152] still using current LEA --- .../OTA-mDNS-LittleFS/OTA-mDNS-LittleFS.ino | 17 +++++++++++------ libraries/ESP8266mDNS/src/ESP8266mDNS.h | 19 +++++++++++-------- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h | 3 +++ 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-LittleFS.ino b/libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-LittleFS.ino index 9d0d861474..34ffcc5c89 100644 --- a/libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-LittleFS.ino +++ b/libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-LittleFS.ino @@ -12,8 +12,13 @@ */ +#ifndef APSSID +#define APSSID "your-apssid" +#define APPSK "your-password" +#endif + #ifndef STASSID -#define STASSID "your-ssid" +#define STASSID "your-sta" #define STAPSK "your-password" #endif @@ -30,15 +35,15 @@ @brief mDNS and OTA Constants @{ */ -#define HOSTNAME "ESP8266-OTA-" ///< Hostename. The setup function adds the Chip ID at the end. +#define HOSTNAME "ESP8266-OTA-" ///< Hostname. The setup function adds the Chip ID at the end. /// @} /** @brief Default WiFi connection information. @{ */ -const char* ap_default_ssid = STASSID; ///< Default SSID. -const char* ap_default_psk = STAPSK; ///< Default PSK. +const char* ap_default_ssid = APSSID; ///< Default SSID. +const char* ap_default_psk = APPSK; ///< Default PSK. /// @} /// Uncomment the next line for verbose output over UART. @@ -166,8 +171,8 @@ void setup() { // Load wifi connection information. if (! loadConfig(&station_ssid, &station_psk)) { - station_ssid = ""; - station_psk = ""; + station_ssid = STASSID; + station_psk = STAPSK; Serial.println("No WiFi connection information available."); } diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS.h b/libraries/ESP8266mDNS/src/ESP8266mDNS.h index c3713c6689..7b921817e7 100644 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS.h +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS.h @@ -42,19 +42,22 @@ */ -enum class MDNSApiVersion { Legacy, LEA, LEAv2 }; +enum class MDNSApiVersion { Legacy, LEA, LEAv2Compat, LEAv2 }; -#include "ESP8266mDNS_Legacy.h" -#include "LEAmDNS.h" -#include "LEAmDNS2Host.h" +#include "ESP8266mDNS_Legacy.h" // Legacy +#include "LEAmDNS.h" // LEA +#include "LEAmDNS2_Legacy.h" // LEAv2Compat - replacement for LEA using v2 +#include "LEAmDNS2Host.h" // LEAv2 - API updated + +// clsMDNSHost replaces MDNSResponder in LEAv2 using clsMDNSHost = esp8266::experimental::clsLEAMDNSHost; #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) // Maps the implementation to use to the global namespace type -//using MDNSResponder = Legacy_MDNSResponder::MDNSResponder; // Legacy -using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; // LEA -//using MDNSResponder = clsMDNSHost; // LEAv2 +//using MDNSResponder = Legacy_MDNSResponder::MDNSResponder; // Legacy +using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; // LEA +//using MDNSResponder = esp8266::MDNSImplementation::clsLEAMDNSHost_Legacy; // LEAv2Compat +//using MDNSResponder = clsMDNSHost; // LEAv2 extern MDNSResponder MDNS; #endif - diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h index be4a731216..9ab1f14d0c 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h @@ -128,6 +128,9 @@ class clsLEAMDNSHost_Legacy }; public: + + static constexpr auto ApiVersion = MDNSApiVersion::LEAv2Compat; + /* INTERFACE */ clsLEAMDNSHost_Legacy(void); virtual ~clsLEAMDNSHost_Legacy(void); From b7c35048c77a632352a35fbbe5f69c4e9ea080d9 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 8 May 2020 23:15:28 +0200 Subject: [PATCH 009/152] remove unwanted change --- libraries/ESP8266WebServer/src/detail/RequestHandler.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/ESP8266WebServer/src/detail/RequestHandler.h b/libraries/ESP8266WebServer/src/detail/RequestHandler.h index c939d573bd..9202f623b3 100644 --- a/libraries/ESP8266WebServer/src/detail/RequestHandler.h +++ b/libraries/ESP8266WebServer/src/detail/RequestHandler.h @@ -7,9 +7,8 @@ template class RequestHandler { -public: using WebServerType = ESP8266WebServerTemplate; - +public: virtual ~RequestHandler() { } virtual bool canHandle(HTTPMethod method, String uri) { (void) method; (void) uri; return false; } virtual bool canUpload(String uri) { (void) uri; return false; } From ff0b10035dca6812a1a24e63890a8c4c5335033b Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 8 May 2020 23:21:47 +0200 Subject: [PATCH 010/152] remove unwanted changes --- .../src/detail/RequestHandler.h | 3 +- .../OLDmDNS/ESP8266mDNS_Legacy.cpp | 1523 ---------- .../ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.h | 166 -- libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.cpp | 1381 --------- libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.h | 1461 ---------- .../ESP8266mDNS/OLDmDNS/LEAmDNS_Control.cpp | 2134 -------------- .../ESP8266mDNS/OLDmDNS/LEAmDNS_Helpers.cpp | 850 ------ libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Priv.h | 182 -- .../ESP8266mDNS/OLDmDNS/LEAmDNS_Structs.cpp | 2476 ----------------- .../ESP8266mDNS/OLDmDNS/LEAmDNS_Transfer.cpp | 1779 ------------ .../ESP8266mDNS/OLDmDNS/LEAmDNS_lwIPdefs.h | 44 - 11 files changed, 1 insertion(+), 11998 deletions(-) delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.cpp delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.h delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.cpp delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.h delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Control.cpp delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Helpers.cpp delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Priv.h delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Structs.cpp delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Transfer.cpp delete mode 100644 libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_lwIPdefs.h diff --git a/libraries/ESP8266WebServer/src/detail/RequestHandler.h b/libraries/ESP8266WebServer/src/detail/RequestHandler.h index c939d573bd..9202f623b3 100644 --- a/libraries/ESP8266WebServer/src/detail/RequestHandler.h +++ b/libraries/ESP8266WebServer/src/detail/RequestHandler.h @@ -7,9 +7,8 @@ template class RequestHandler { -public: using WebServerType = ESP8266WebServerTemplate; - +public: virtual ~RequestHandler() { } virtual bool canHandle(HTTPMethod method, String uri) { (void) method; (void) uri; return false; } virtual bool canUpload(String uri) { (void) uri; return false; } diff --git a/libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.cpp b/libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.cpp deleted file mode 100644 index 8791195523..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.cpp +++ /dev/null @@ -1,1523 +0,0 @@ -/* - - ESP8266 Multicast DNS (port of CC3000 Multicast DNS library) - Version 1.1 - Copyright (c) 2013 Tony DiCola (tony@tonydicola.com) - ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com) - MDNS-SD Suport 2015 Hristo Gochkov - Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com) - - - License (MIT license): - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -*/ - -// Important RFC's for reference: -// - DNS request and response: http://www.ietf.org/rfc/rfc1035.txt -// - Multicast DNS: http://www.ietf.org/rfc/rfc6762.txt -// - MDNS-SD: https://tools.ietf.org/html/rfc6763 - -#ifndef LWIP_OPEN_SRC -#define LWIP_OPEN_SRC -#endif - -#include "ESP8266mDNS.h" -#include - -#include "debug.h" - -extern "C" { -#include "osapi.h" -#include "ets_sys.h" -#include "user_interface.h" -} - -#include "WiFiUdp.h" -#include "lwip/opt.h" -#include "lwip/udp.h" -#include "lwip/inet.h" -#include "lwip/igmp.h" -#include "lwip/mem.h" -#include "include/UdpContext.h" - - - -namespace Legacy_MDNSResponder -{ - - -#ifdef DEBUG_ESP_MDNS -#define DEBUG_ESP_MDNS_ERR -#define DEBUG_ESP_MDNS_TX -#define DEBUG_ESP_MDNS_RX -#endif - -#define MDNS_NAME_REF 0xC000 - -#define MDNS_TYPE_AAAA 0x001C -#define MDNS_TYPE_A 0x0001 -#define MDNS_TYPE_PTR 0x000C -#define MDNS_TYPE_SRV 0x0021 -#define MDNS_TYPE_TXT 0x0010 - -#define MDNS_CLASS_IN 0x0001 -#define MDNS_CLASS_IN_FLUSH_CACHE 0x8001 - -#define MDNS_ANSWERS_ALL 0x0F -#define MDNS_ANSWER_PTR 0x08 -#define MDNS_ANSWER_TXT 0x04 -#define MDNS_ANSWER_SRV 0x02 -#define MDNS_ANSWER_A 0x01 - -#define _conn_read32() (((uint32_t)_conn->read() << 24) | ((uint32_t)_conn->read() << 16) | ((uint32_t)_conn->read() << 8) | _conn->read()) -#define _conn_read16() (((uint16_t)_conn->read() << 8) | _conn->read()) -#define _conn_read8() _conn->read() -#define _conn_readS(b,l) _conn->read((char*)(b),l); - -static const IPAddress MDNS_MULTICAST_ADDR(224, 0, 0, 251); -static const int MDNS_MULTICAST_TTL = 1; -static const int MDNS_PORT = 5353; - -struct MDNSService -{ - MDNSService* _next; - char _name[32]; - char _proto[4]; - uint16_t _port; - uint16_t _txtLen; // length of all txts - struct MDNSTxt * _txts; -}; - -struct MDNSTxt -{ - MDNSTxt * _next; - String _txt; -}; - -struct MDNSAnswer -{ - MDNSAnswer* next; - uint8_t ip[4]; - uint16_t port; - char *hostname; -}; - -struct MDNSQuery -{ - char _service[32]; - char _proto[4]; -}; - - -MDNSResponder::MDNSResponder() : _conn(0) -{ - _services = 0; - _instanceName = ""; - _answers = 0; - _query = 0; - _newQuery = false; - _waitingForAnswers = false; -} -MDNSResponder::~MDNSResponder() -{ - if (_query != 0) - { - os_free(_query); - _query = 0; - } - - // Clear answer list - MDNSAnswer *answer; - int numAnswers = _getNumAnswers(); - for (int n = numAnswers - 1; n >= 0; n--) - { - answer = _getAnswerFromIdx(n); - os_free(answer->hostname); - os_free(answer); - answer = 0; - } - _answers = 0; - - if (_conn) - { - _conn->unref(); - } -} - -bool MDNSResponder::begin(const char* hostname) -{ - size_t n = strlen(hostname); - if (n > 63) // max size for a single label. - { - return false; - } - - // Copy in hostname characters as lowercase - _hostName = hostname; - _hostName.toLowerCase(); - - // If instance name is not already set copy hostname to instance name - if (_instanceName.equals("")) - { - _instanceName = hostname; - } - - _gotIPHandler = WiFi.onStationModeGotIP([this](const WiFiEventStationModeGotIP & event) - { - (void) event; - _restart(); - }); - - _disconnectedHandler = WiFi.onStationModeDisconnected([this](const WiFiEventStationModeDisconnected & event) - { - (void) event; - _restart(); - }); - - return _listen(); -} - -void MDNSResponder::notifyAPChange() -{ - _restart(); -} - -void MDNSResponder::_restart() -{ - if (_conn) - { - _conn->unref(); - _conn = nullptr; - } - _listen(); -} - -bool MDNSResponder::_listen() -{ - // Open the MDNS socket if it isn't already open. - if (!_conn) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.println("MDNS listening"); -#endif - - IPAddress mdns(MDNS_MULTICAST_ADDR); - - if (igmp_joingroup(IP4_ADDR_ANY4, mdns) != ERR_OK) - { - return false; - } - - _conn = new UdpContext; - _conn->ref(); - - if (!_conn->listen(IP_ADDR_ANY, MDNS_PORT)) - { - return false; - } - _conn->setMulticastTTL(MDNS_MULTICAST_TTL); - _conn->onRx(std::bind(&MDNSResponder::update, this)); - _conn->connect(mdns, MDNS_PORT); - } - return true; -} - -void MDNSResponder::update() -{ - if (!_conn || !_conn->next()) - { - return; - } - _parsePacket(); -} - - -void MDNSResponder::setInstanceName(String name) -{ - if (name.length() > 63) - { - return; - } - _instanceName = name; -} - - -bool MDNSResponder::addServiceTxt(char *name, char *proto, char *key, char *value) -{ - MDNSService* servicePtr; - - uint8_t txtLen = os_strlen(key) + os_strlen(value) + 1; // Add one for equals sign - txtLen += 1; //accounts for length byte added when building the txt responce - //Find the service - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - //Checking Service names - if (strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) - { - //found a service name match - if (servicePtr->_txtLen + txtLen > 1300) - { - return false; //max txt record size - } - MDNSTxt *newtxt = new MDNSTxt; - newtxt->_txt = String(key) + '=' + String(value); - newtxt->_next = 0; - if (servicePtr->_txts == 0) //no services have been added - { - //Adding First TXT to service - servicePtr->_txts = newtxt; - servicePtr->_txtLen += txtLen; - return true; - } - else - { - MDNSTxt * txtPtr = servicePtr->_txts; - while (txtPtr->_next != 0) - { - txtPtr = txtPtr->_next; - } - //adding another TXT to service - txtPtr->_next = newtxt; - servicePtr->_txtLen += txtLen; - return true; - } - } - } - return false; -} - -void MDNSResponder::addService(char *name, char *proto, uint16_t port) -{ - if (_getServicePort(name, proto) != 0) - { - return; - } - if (os_strlen(name) > 32 || os_strlen(proto) != 3) - { - return; //bad arguments - } - struct MDNSService *srv = (struct MDNSService*)(os_malloc(sizeof(struct MDNSService))); - os_strcpy(srv->_name, name); - os_strcpy(srv->_proto, proto); - srv->_port = port; - srv->_next = 0; - srv->_txts = 0; - srv->_txtLen = 0; - - if (_services == 0) - { - _services = srv; - } - else - { - MDNSService* servicePtr = _services; - while (servicePtr->_next != 0) - { - servicePtr = servicePtr->_next; - } - servicePtr->_next = srv; - } - -} - -int MDNSResponder::queryService(char *service, char *proto) -{ -#ifdef DEBUG_ESP_MDNS_TX - DEBUG_ESP_PORT.printf("queryService %s %s\n", service, proto); -#endif - while (_answers != 0) - { - MDNSAnswer *currAnswer = _answers; - _answers = _answers->next; - os_free(currAnswer->hostname); - os_free(currAnswer); - currAnswer = 0; - } - if (_query != 0) - { - os_free(_query); - _query = 0; - } - _query = (struct MDNSQuery*)(os_malloc(sizeof(struct MDNSQuery))); - os_strcpy(_query->_service, service); - os_strcpy(_query->_proto, proto); - _newQuery = true; - - char underscore[] = "_"; - - // build service name with _ - char serviceName[os_strlen(service) + 2]; - os_strcpy(serviceName, underscore); - os_strcat(serviceName, service); - size_t serviceNameLen = os_strlen(serviceName); - - //build proto name with _ - char protoName[5]; - os_strcpy(protoName, underscore); - os_strcat(protoName, proto); - size_t protoNameLen = 4; - - //local string - char localName[] = "local"; - size_t localNameLen = 5; - - //terminator - char terminator[] = "\0"; - - // Only supports sending one PTR query - uint8_t questionCount = 1; - - _waitingForAnswers = true; - for (int itfn = 0; itfn < 2; itfn++) - { - struct ip_info ip_info; - - wifi_get_ip_info((!itfn) ? SOFTAP_IF : STATION_IF, &ip_info); - if (!ip_info.ip.addr) - { - continue; - } - _conn->setMulticastInterface(IPAddress(ip_info.ip.addr)); - - // Write the header - _conn->flush(); - uint8_t head[12] = - { - 0x00, 0x00, //ID = 0 - 0x00, 0x00, //Flags = response + authoritative answer - 0x00, questionCount, //Question count - 0x00, 0x00, //Answer count - 0x00, 0x00, //Name server records - 0x00, 0x00 //Additional records - }; - _conn->append(reinterpret_cast(head), 12); - - // Only supports sending one PTR query - // Send the Name field (eg. "_http._tcp.local") - _conn->append(reinterpret_cast(&serviceNameLen), 1); // lenght of "_" + service - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_" + service - _conn->append(reinterpret_cast(&protoNameLen), 1); // lenght of "_" + proto - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_" + proto - _conn->append(reinterpret_cast(&localNameLen), 1); // lenght of "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type and class - uint8_t ptrAttrs[4] = - { - 0x00, 0x0c, //PTR record query - 0x00, 0x01 //Class IN - }; - _conn->append(reinterpret_cast(ptrAttrs), 4); - _conn->send(); - } - -#ifdef DEBUG_ESP_MDNS_TX - DEBUG_ESP_PORT.println("Waiting for answers.."); -#endif - delay(1000); - - _waitingForAnswers = false; - - return _getNumAnswers(); -} - -String MDNSResponder::hostname(int idx) -{ - MDNSAnswer *answer = _getAnswerFromIdx(idx); - if (answer == 0) - { - return String(); - } - return answer->hostname; -} - -IPAddress MDNSResponder::IP(int idx) -{ - MDNSAnswer *answer = _getAnswerFromIdx(idx); - if (answer == 0) - { - return IPAddress(); - } - return IPAddress(answer->ip); -} - -uint16_t MDNSResponder::port(int idx) -{ - MDNSAnswer *answer = _getAnswerFromIdx(idx); - if (answer == 0) - { - return 0; - } - return answer->port; -} - -MDNSAnswer* MDNSResponder::_getAnswerFromIdx(int idx) -{ - MDNSAnswer *answer = _answers; - while (answer != 0 && idx-- > 0) - { - answer = answer->next; - } - if (idx > 0) - { - return 0; - } - return answer; -} - -int MDNSResponder::_getNumAnswers() -{ - int numAnswers = 0; - MDNSAnswer *answer = _answers; - while (answer != 0) - { - numAnswers++; - answer = answer->next; - } - return numAnswers; -} - -MDNSTxt * MDNSResponder::_getServiceTxt(char *name, char *proto) -{ - MDNSService* servicePtr; - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - if (servicePtr->_port > 0 && strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) - { - if (servicePtr->_txts == 0) - { - return nullptr; - } - return servicePtr->_txts; - } - } - return nullptr; -} - -uint16_t MDNSResponder::_getServiceTxtLen(char *name, char *proto) -{ - MDNSService* servicePtr; - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - if (servicePtr->_port > 0 && strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) - { - if (servicePtr->_txts == 0) - { - return false; - } - return servicePtr->_txtLen; - } - } - return 0; -} - -uint16_t MDNSResponder::_getServicePort(char *name, char *proto) -{ - MDNSService* servicePtr; - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - if (servicePtr->_port > 0 && strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) - { - return servicePtr->_port; - } - } - return 0; -} - -IPAddress MDNSResponder::_getRequestMulticastInterface() -{ - struct ip_info ip_info; - bool match_ap = false; - if (wifi_get_opmode() & SOFTAP_MODE) - { - const IPAddress& remote_ip = _conn->getRemoteAddress(); - wifi_get_ip_info(SOFTAP_IF, &ip_info); - IPAddress infoIp(ip_info.ip); - IPAddress infoMask(ip_info.netmask); - if (ip_info.ip.addr && ip_addr_netcmp((const ip_addr_t*)remote_ip, (const ip_addr_t*)infoIp, ip_2_ip4((const ip_addr_t*)infoMask))) - { - match_ap = true; - } - } - if (!match_ap) - { - wifi_get_ip_info(STATION_IF, &ip_info); - } - return IPAddress(ip_info.ip.addr); -} - -void MDNSResponder::_parsePacket() -{ - int i; - char tmp; - bool serviceParsed = false; - bool protoParsed = false; - bool localParsed = false; - - char hostName[255]; - uint8_t hostNameLen; - - char serviceName[32]; - uint8_t serviceNameLen; - uint16_t servicePort = 0; - - char protoName[32]; - protoName[0] = 0; - uint8_t protoNameLen = 0; - - uint16_t packetHeader[6]; - - for (i = 0; i < 6; i++) - { - packetHeader[i] = _conn_read16(); - } - - if ((packetHeader[1] & 0x8000) != 0) // Read answers - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("Reading answers RX: REQ, ID:%u, Q:%u, A:%u, NS:%u, ADD:%u\n", packetHeader[0], packetHeader[2], packetHeader[3], packetHeader[4], packetHeader[5]); -#endif - - if (!_waitingForAnswers) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.println("Not expecting any answers right now, returning"); -#endif - _conn->flush(); - return; - } - - int numAnswers = packetHeader[3] + packetHeader[5]; - // Assume that the PTR answer always comes first and that it is always accompanied by a TXT, SRV, AAAA (optional) and A answer in the same packet. - if (numAnswers < 4) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("Expected a packet with 4 or more answers, got %u\n", numAnswers); -#endif - _conn->flush(); - return; - } - - uint8_t tmp8; - uint16_t answerPort = 0; - uint8_t answerIp[4] = { 0, 0, 0, 0 }; - char answerHostName[255]; - bool serviceMatch = false; - MDNSAnswer *answer; - uint8_t partsCollected = 0; - uint8_t stringsRead = 0; - - answerHostName[0] = '\0'; - - // Clear answer list - if (_newQuery) - { - int oldAnswers = _getNumAnswers(); - for (int n = oldAnswers - 1; n >= 0; n--) - { - answer = _getAnswerFromIdx(n); - os_free(answer->hostname); - os_free(answer); - answer = 0; - } - _answers = 0; - _newQuery = false; - } - - while (numAnswers--) - { - // Read name - stringsRead = 0; - size_t last_bufferpos = 0; - do - { - tmp8 = _conn_read8(); - if (tmp8 == 0x00) // End of name - { - break; - } - if (tmp8 & 0xC0) // Compressed pointer - { - uint16_t offset = ((((uint16_t)tmp8) & ~0xC0) << 8) | _conn_read8(); - if (_conn->isValidOffset(offset)) - { - if (0 == last_bufferpos) - { - last_bufferpos = _conn->tell(); - } -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Compressed pointer, jumping from "); - DEBUG_ESP_PORT.print(last_bufferpos); - DEBUG_ESP_PORT.print(" to "); - DEBUG_ESP_PORT.println(offset); -#endif - _conn->seek(offset); - tmp8 = _conn_read8(); - } - else - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Skipping malformed compressed pointer"); -#endif - tmp8 = _conn_read8(); - break; - } - } - if (stringsRead > 3) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.println("failed to read the response name"); -#endif - _conn->flush(); - return; - } - _conn_readS(serviceName, tmp8); - serviceName[tmp8] = '\0'; -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf(" %d ", tmp8); - for (int n = 0; n < tmp8; n++) - { - DEBUG_ESP_PORT.printf("%c", serviceName[n]); - } - DEBUG_ESP_PORT.println(); -#endif - if (serviceName[0] == '_') - { - if (strcmp(&serviceName[1], _query->_service) == 0) - { - serviceMatch = true; -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("found matching service: %s\n", _query->_service); -#endif - } - } - stringsRead++; - } while (true); - if (last_bufferpos > 0) - { - _conn->seek(last_bufferpos); -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Compressed pointer, jumping back to "); - DEBUG_ESP_PORT.println(last_bufferpos); -#endif - } - - uint16_t answerType = _conn_read16(); // Read type - uint16_t answerClass = _conn_read16(); // Read class - uint32_t answerTtl = _conn_read32(); // Read ttl - uint16_t answerRdlength = _conn_read16(); // Read rdlength - - (void) answerClass; - (void) answerTtl; - - if (answerRdlength > 255) - { - if (answerType == MDNS_TYPE_TXT && answerRdlength < 1460) - { - while (--answerRdlength) - { - _conn->read(); - } - } - else - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("Data len too long! %u\n", answerRdlength); -#endif - _conn->flush(); - return; - } - } - -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("type: %04x rdlength: %d\n", answerType, answerRdlength); -#endif - - if (answerType == MDNS_TYPE_PTR) - { - partsCollected |= 0x01; - _conn_readS(hostName, answerRdlength); // Read rdata - if (hostName[answerRdlength - 2] & 0xc0) - { - memcpy(answerHostName, hostName + 1, answerRdlength - 3); - answerHostName[answerRdlength - 3] = '\0'; - } -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("PTR %d ", answerRdlength); - for (int n = 0; n < answerRdlength; n++) - { - DEBUG_ESP_PORT.printf("%c", hostName[n]); - } - DEBUG_ESP_PORT.println(); -#endif - } - - else if (answerType == MDNS_TYPE_TXT) - { - partsCollected |= 0x02; - _conn_readS(hostName, answerRdlength); // Read rdata -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("TXT %d ", answerRdlength); - for (int n = 0; n < answerRdlength; n++) - { - DEBUG_ESP_PORT.printf("%c", hostName[n]); - } - DEBUG_ESP_PORT.println(); -#endif - } - - else if (answerType == MDNS_TYPE_SRV) - { - partsCollected |= 0x04; - uint16_t answerPrio = _conn_read16(); // Read priority - uint16_t answerWeight = _conn_read16(); // Read weight - answerPort = _conn_read16(); // Read port - last_bufferpos = 0; - - (void) answerPrio; - (void) answerWeight; - - // Read hostname - tmp8 = _conn_read8(); - if (tmp8 & 0xC0) // Compressed pointer - { - uint16_t offset = ((((uint16_t)tmp8) & ~0xC0) << 8) | _conn_read8(); - if (_conn->isValidOffset(offset)) - { - last_bufferpos = _conn->tell(); -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Compressed pointer, jumping from "); - DEBUG_ESP_PORT.print(last_bufferpos); - DEBUG_ESP_PORT.print(" to "); - DEBUG_ESP_PORT.println(offset); -#endif - _conn->seek(offset); - tmp8 = _conn_read8(); - } - else - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Skipping malformed compressed pointer"); -#endif - tmp8 = _conn_read8(); - break; - } - } - _conn_readS(answerHostName, tmp8); - answerHostName[tmp8] = '\0'; -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("SRV %d ", tmp8); - for (int n = 0; n < tmp8; n++) - { - DEBUG_ESP_PORT.printf("%02x ", answerHostName[n]); - } - DEBUG_ESP_PORT.printf("\n%s\n", answerHostName); -#endif - if (last_bufferpos > 0) - { - _conn->seek(last_bufferpos); - tmp8 = 2; // Size of compression octets -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Compressed pointer, jumping back to "); - DEBUG_ESP_PORT.println(last_bufferpos); -#endif - } - if (answerRdlength - (6 + 1 + tmp8) > 0) // Skip any remaining rdata - { - _conn_readS(hostName, answerRdlength - (6 + 1 + tmp8)); - } - } - - else if (answerType == MDNS_TYPE_A) - { - partsCollected |= 0x08; - for (int i = 0; i < 4; i++) - { - answerIp[i] = _conn_read8(); - } - } - else - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("Ignoring unsupported type %02x\n", tmp8); -#endif - for (int n = 0; n < answerRdlength; n++) - { - (void)_conn_read8(); - } - } - - if ((partsCollected == 0x0F) && serviceMatch) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.println("All answers parsed, adding to _answers list.."); -#endif - // Add new answer to answer list - if (_answers == 0) - { - _answers = (struct MDNSAnswer*)(os_malloc(sizeof(struct MDNSAnswer))); - answer = _answers; - } - else - { - answer = _answers; - while (answer->next != 0) - { - answer = answer->next; - } - answer->next = (struct MDNSAnswer*)(os_malloc(sizeof(struct MDNSAnswer))); - answer = answer->next; - } - answer->next = 0; - answer->hostname = 0; - - // Populate new answer - answer->port = answerPort; - for (int i = 0; i < 4; i++) - { - answer->ip[i] = answerIp[i]; - } - answer->hostname = (char *)os_malloc(strlen(answerHostName) + 1); - os_strcpy(answer->hostname, answerHostName); - _conn->flush(); - return; - } - } - - _conn->flush(); - return; - } - - // PARSE REQUEST NAME - - hostNameLen = _conn_read8() % 255; - _conn_readS(hostName, hostNameLen); - hostName[hostNameLen] = '\0'; - - if (hostName[0] == '_') - { - serviceParsed = true; - memcpy(serviceName, hostName + 1, hostNameLen); - serviceNameLen = hostNameLen - 1; - hostNameLen = 0; - } - - if (hostNameLen > 0 && !_hostName.equals(hostName) && !_instanceName.equals(hostName)) - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_NO_HOST: %s\n", hostName); - DEBUG_ESP_PORT.printf("hostname: %s\n", _hostName.c_str()); - DEBUG_ESP_PORT.printf("instance: %s\n", _instanceName.c_str()); -#endif - _conn->flush(); - return; - } - - if (!serviceParsed) - { - serviceNameLen = _conn_read8() % 255; - _conn_readS(serviceName, serviceNameLen); - serviceName[serviceNameLen] = '\0'; - - if (serviceName[0] == '_') - { - memmove(serviceName, serviceName + 1, serviceNameLen); - serviceNameLen--; - serviceParsed = true; - } - else if (serviceNameLen == 5 && strcmp("local", serviceName) == 0) - { - tmp = _conn_read8(); - if (tmp == 0) - { - serviceParsed = true; - serviceNameLen = 0; - protoParsed = true; - protoNameLen = 0; - localParsed = true; - } - else - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_FQDN: %s\n", serviceName); -#endif - _conn->flush(); - return; - } - } - else - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_SERVICE: %s\n", serviceName); -#endif - _conn->flush(); - return; - } - } - - if (!protoParsed) - { - protoNameLen = _conn_read8() % 255; - _conn_readS(protoName, protoNameLen); - protoName[protoNameLen] = '\0'; - if (protoNameLen == 4 && protoName[0] == '_') - { - memmove(protoName, protoName + 1, protoNameLen); - protoNameLen--; - protoParsed = true; - } - else if (strcmp("services", serviceName) == 0 && strcmp("_dns-sd", protoName) == 0) - { - _conn->flush(); - IPAddress interface = _getRequestMulticastInterface(); - _replyToTypeEnumRequest(interface); - return; - } - else - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_PROTO: %s\n", protoName); -#endif - _conn->flush(); - return; - } - } - - if (!localParsed) - { - char localName[32]; - uint8_t localNameLen = _conn_read8() % 31; - _conn_readS(localName, localNameLen); - localName[localNameLen] = '\0'; - tmp = _conn_read8(); - if (localNameLen == 5 && strcmp("local", localName) == 0 && tmp == 0) - { - localParsed = true; - } - else - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_FQDN: %s\n", localName); -#endif - _conn->flush(); - return; - } - } - - if (serviceNameLen > 0 && protoNameLen > 0) - { - servicePort = _getServicePort(serviceName, protoName); - if (servicePort == 0) - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_NO_SERVICE: %s\n", serviceName); -#endif - _conn->flush(); - return; - } - } - else if (serviceNameLen > 0 || protoNameLen > 0) - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_SERVICE_PROTO: %s\n", serviceName); -#endif - _conn->flush(); - return; - } - - // RESPOND - -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("RX: REQ, ID:%u, Q:%u, A:%u, NS:%u, ADD:%u\n", packetHeader[0], packetHeader[2], packetHeader[3], packetHeader[4], packetHeader[5]); -#endif - - uint16_t currentType; - uint16_t currentClass; - - int numQuestions = packetHeader[2]; - if (numQuestions > 4) - { - numQuestions = 4; - } - uint16_t questions[4]; - int question = 0; - - while (numQuestions--) - { - currentType = _conn_read16(); - if (currentType & MDNS_NAME_REF) //new header handle it better! - { - currentType = _conn_read16(); - } - currentClass = _conn_read16(); - if (currentClass & MDNS_CLASS_IN) - { - questions[question++] = currentType; - } - - if (numQuestions > 0) - { - if (_conn_read16() != 0xC00C) //new question but for another host/service - { - _conn->flush(); - numQuestions = 0; - } - } - -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("REQ: "); - if (hostNameLen > 0) - { - DEBUG_ESP_PORT.printf("%s.", hostName); - } - if (serviceNameLen > 0) - { - DEBUG_ESP_PORT.printf("_%s.", serviceName); - } - if (protoNameLen > 0) - { - DEBUG_ESP_PORT.printf("_%s.", protoName); - } - DEBUG_ESP_PORT.printf("local. "); - - if (currentType == MDNS_TYPE_AAAA) - { - DEBUG_ESP_PORT.printf(" AAAA "); - } - else if (currentType == MDNS_TYPE_A) - { - DEBUG_ESP_PORT.printf(" A "); - } - else if (currentType == MDNS_TYPE_PTR) - { - DEBUG_ESP_PORT.printf(" PTR "); - } - else if (currentType == MDNS_TYPE_SRV) - { - DEBUG_ESP_PORT.printf(" SRV "); - } - else if (currentType == MDNS_TYPE_TXT) - { - DEBUG_ESP_PORT.printf(" TXT "); - } - else - { - DEBUG_ESP_PORT.printf(" 0x%04X ", currentType); - } - - if (currentClass == MDNS_CLASS_IN) - { - DEBUG_ESP_PORT.printf(" IN "); - } - else if (currentClass == MDNS_CLASS_IN_FLUSH_CACHE) - { - DEBUG_ESP_PORT.printf(" IN[F] "); - } - else - { - DEBUG_ESP_PORT.printf(" 0x%04X ", currentClass); - } - - DEBUG_ESP_PORT.printf("\n"); -#endif - } - uint8_t questionMask = 0; - uint8_t responseMask = 0; - for (i = 0; i < question; i++) - { - if (questions[i] == MDNS_TYPE_A) - { - questionMask |= 0x1; - responseMask |= 0x1; - } - else if (questions[i] == MDNS_TYPE_SRV) - { - questionMask |= 0x2; - responseMask |= 0x3; - } - else if (questions[i] == MDNS_TYPE_TXT) - { - questionMask |= 0x4; - responseMask |= 0x4; - } - else if (questions[i] == MDNS_TYPE_PTR) - { - questionMask |= 0x8; - responseMask |= 0xF; - } - } - - IPAddress interface = _getRequestMulticastInterface(); - return _replyToInstanceRequest(questionMask, responseMask, serviceName, protoName, servicePort, interface); -} - - -/** - STRINGIZE -*/ -#ifndef STRINGIZE -#define STRINGIZE(x) #x -#endif -#ifndef STRINGIZE_VALUE_OF -#define STRINGIZE_VALUE_OF(x) STRINGIZE(x) -#endif - - -void MDNSResponder::enableArduino(uint16_t port, bool auth) -{ - - addService("arduino", "tcp", port); - addServiceTxt("arduino", "tcp", "tcp_check", "no"); - addServiceTxt("arduino", "tcp", "ssh_upload", "no"); - addServiceTxt("arduino", "tcp", "board", STRINGIZE_VALUE_OF(ARDUINO_BOARD)); - addServiceTxt("arduino", "tcp", "auth_upload", (auth) ? "yes" : "no"); -} - -void MDNSResponder::_replyToTypeEnumRequest(IPAddress multicastInterface) -{ - MDNSService* servicePtr; - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - if (servicePtr->_port > 0) - { - char *service = servicePtr->_name; - char *proto = servicePtr->_proto; - //uint16_t port = servicePtr->_port; - -#ifdef DEBUG_ESP_MDNS_TX - DEBUG_ESP_PORT.printf("TX: service:%s, proto:%s\n", service, proto); -#endif - - char sdHostName[] = "_services"; - size_t sdHostNameLen = 9; - char sdServiceName[] = "_dns-sd"; - size_t sdServiceNameLen = 7; - char sdProtoName[] = "_udp"; - size_t sdProtoNameLen = 4; - - char underscore[] = "_"; - - // build service name with _ - char serviceName[os_strlen(service) + 2]; - os_strcpy(serviceName, underscore); - os_strcat(serviceName, service); - size_t serviceNameLen = os_strlen(serviceName); - - //build proto name with _ - char protoName[5]; - os_strcpy(protoName, underscore); - os_strcat(protoName, proto); - size_t protoNameLen = 4; - - //local string - char localName[] = "local"; - size_t localNameLen = 5; - - //terminator - char terminator[] = "\0"; - - //Write the header - _conn->flush(); - uint8_t head[12] = - { - 0x00, 0x00, //ID = 0 - 0x84, 0x00, //Flags = response + authoritative answer - 0x00, 0x00, //Question count - 0x00, 0x01, //Answer count - 0x00, 0x00, //Name server records - 0x00, 0x00, //Additional records - }; - _conn->append(reinterpret_cast(head), 12); - - // Send the Name field (ie. "_services._dns-sd._udp.local") - _conn->append(reinterpret_cast(&sdHostNameLen), 1); // length of "_services" - _conn->append(reinterpret_cast(sdHostName), sdHostNameLen); // "_services" - _conn->append(reinterpret_cast(&sdServiceNameLen), 1); // length of "_dns-sd" - _conn->append(reinterpret_cast(sdServiceName), sdServiceNameLen);// "_dns-sd" - _conn->append(reinterpret_cast(&sdProtoNameLen), 1); // length of "_udp" - _conn->append(reinterpret_cast(sdProtoName), sdProtoNameLen); // "_udp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type, class, ttl and rdata length - uint8_t ptrDataLen = serviceNameLen + protoNameLen + localNameLen + 4; // 4 is three label sizes and the terminator - uint8_t ptrAttrs[10] = - { - 0x00, 0x0c, //PTR record query - 0x00, 0x01, //Class IN - 0x00, 0x00, 0x11, 0x94, //TTL 4500 - 0x00, ptrDataLen, //RData length - }; - _conn->append(reinterpret_cast(ptrAttrs), 10); - - //Send the RData (ie. "_http._tcp.local") - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - _conn->setMulticastInterface(multicastInterface); - _conn->send(); - } - } -} - -void MDNSResponder::_replyToInstanceRequest(uint8_t questionMask, uint8_t responseMask, char * service, char *proto, uint16_t port, IPAddress multicastInterface) -{ - int i; - if (questionMask == 0) - { - return; - } - if (responseMask == 0) - { - return; - } - -#ifdef DEBUG_ESP_MDNS_TX - DEBUG_ESP_PORT.printf("TX: qmask:%01X, rmask:%01X, service:%s, proto:%s, port:%u\n", questionMask, responseMask, service, proto, port); -#endif - - - String instanceName = _instanceName; - size_t instanceNameLen = instanceName.length(); - - String hostName = _hostName; - size_t hostNameLen = hostName.length(); - - char underscore[] = "_"; - - // build service name with _ - char serviceName[os_strlen(service) + 2]; - os_strcpy(serviceName, underscore); - os_strcat(serviceName, service); - size_t serviceNameLen = os_strlen(serviceName); - - //build proto name with _ - char protoName[5]; - os_strcpy(protoName, underscore); - os_strcat(protoName, proto); - size_t protoNameLen = 4; - - //local string - char localName[] = "local"; - size_t localNameLen = 5; - - //terminator - char terminator[] = "\0"; - - uint8_t answerMask = responseMask & questionMask; - uint8_t answerCount = 0; - uint8_t additionalMask = responseMask & ~questionMask; - uint8_t additionalCount = 0; - for (i = 0; i < 4; i++) - { - if (answerMask & (1 << i)) - { - answerCount++; - } - if (additionalMask & (1 << i)) - { - additionalCount++; - } - } - - - //Write the header - _conn->flush(); - uint8_t head[12] = - { - 0x00, 0x00, //ID = 0 - 0x84, 0x00, //Flags = response + authoritative answer - 0x00, 0x00, //Question count - 0x00, answerCount, //Answer count - 0x00, 0x00, //Name server records - 0x00, additionalCount, //Additional records - }; - _conn->append(reinterpret_cast(head), 12); - - for (int responseSection = 0; responseSection < 2; ++responseSection) - { - - // PTR Response - if ((responseSection == 0 ? answerMask : additionalMask) & 0x8) - { - // Send the Name field (ie. "_http._tcp.local") - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type, class, ttl and rdata length - uint8_t ptrDataLen = instanceNameLen + serviceNameLen + protoNameLen + localNameLen + 5; // 5 is four label sizes and the terminator - uint8_t ptrAttrs[10] = - { - 0x00, 0x0c, //PTR record query - 0x00, 0x01, //Class IN - 0x00, 0x00, 0x00, 0x78, //TTL 120 - 0x00, ptrDataLen, //RData length - }; - _conn->append(reinterpret_cast(ptrAttrs), 10); - - //Send the RData (ie. "My IOT device._http._tcp.local") - _conn->append(reinterpret_cast(&instanceNameLen), 1); // length of "My IOT device" - _conn->append(reinterpret_cast(instanceName.c_str()), instanceNameLen);// "My IOT device" - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - } - - //TXT Responce - if ((responseSection == 0 ? answerMask : additionalMask) & 0x4) - { - //Send the name field (ie. "My IOT device._http._tcp.local") - _conn->append(reinterpret_cast(&instanceNameLen), 1); // length of "My IOT device" - _conn->append(reinterpret_cast(instanceName.c_str()), instanceNameLen);// "My IOT device" - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type, class, ttl and rdata length - uint8_t txtDataLen = _getServiceTxtLen(service, proto); - uint8_t txtAttrs[10] = - { - 0x00, 0x10, //TXT record query - 0x80, 0x01, //Class IN, with cache flush - 0x00, 0x00, 0x11, 0x94, //TTL 4500 - 0x00, txtDataLen, //RData length - }; - _conn->append(reinterpret_cast(txtAttrs), 10); - - //Send the RData - MDNSTxt * txtPtr = _getServiceTxt(service, proto); - while (txtPtr != 0) - { - uint8_t txtLen = txtPtr->_txt.length(); - _conn->append(reinterpret_cast(&txtLen), 1); // length of txt - _conn->append(reinterpret_cast(txtPtr->_txt.c_str()), txtLen);// the txt - txtPtr = txtPtr->_next; - } - } - - - //SRV Responce - if ((responseSection == 0 ? answerMask : additionalMask) & 0x2) - { - //Send the name field (ie. "My IOT device._http._tcp.local") - _conn->append(reinterpret_cast(&instanceNameLen), 1); // length of "My IOT device" - _conn->append(reinterpret_cast(instanceName.c_str()), instanceNameLen);// "My IOT device" - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type, class, ttl, rdata length, priority and weight - uint8_t srvDataSize = hostNameLen + localNameLen + 3; // 3 is 2 lable size bytes and the terminator - srvDataSize += 6; // Size of Priority, weight and port - uint8_t srvAttrs[10] = - { - 0x00, 0x21, //Type SRV - 0x80, 0x01, //Class IN, with cache flush - 0x00, 0x00, 0x00, 0x78, //TTL 120 - 0x00, srvDataSize, //RData length - }; - _conn->append(reinterpret_cast(srvAttrs), 10); - - //Send the RData Priority weight and port - uint8_t srvRData[6] = - { - 0x00, 0x00, //Priority 0 - 0x00, 0x00, //Weight 0 - (uint8_t)((port >> 8) & 0xFF), (uint8_t)(port & 0xFF) - }; - _conn->append(reinterpret_cast(srvRData), 6); - //Send the RData (ie. "esp8266.local") - _conn->append(reinterpret_cast(&hostNameLen), 1); // length of "esp8266" - _conn->append(reinterpret_cast(hostName.c_str()), hostNameLen);// "esp8266" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - } - - // A Response - if ((responseSection == 0 ? answerMask : additionalMask) & 0x1) - { - //Send the RData (ie. "esp8266.local") - _conn->append(reinterpret_cast(&hostNameLen), 1); // length of "esp8266" - _conn->append(reinterpret_cast(hostName.c_str()), hostNameLen);// "esp8266" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - uint8_t aaaAttrs[10] = - { - 0x00, 0x01, //TYPE A - 0x80, 0x01, //Class IN, with cache flush - 0x00, 0x00, 0x00, 0x78, //TTL 120 - 0x00, 0x04, //DATA LEN - }; - _conn->append(reinterpret_cast(aaaAttrs), 10); - - // Send RData - uint32_t ip = multicastInterface; - uint8_t aaaRData[4] = - { - (uint8_t)(ip & 0xFF), //IP first octet - (uint8_t)((ip >> 8) & 0xFF), //IP second octet - (uint8_t)((ip >> 16) & 0xFF), //IP third octet - (uint8_t)((ip >> 24) & 0xFF) //IP fourth octet - }; - _conn->append(reinterpret_cast(aaaRData), 4); - } - } - - _conn->setMulticastInterface(multicastInterface); - _conn->send(); -} - -#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) -MDNSResponder MDNS; -#endif - -} // namespace Legacy_MDNSResponder - - - - diff --git a/libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.h b/libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.h deleted file mode 100644 index 9d3cfd2f62..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/ESP8266mDNS_Legacy.h +++ /dev/null @@ -1,166 +0,0 @@ -/* - ESP8266 Multicast DNS (port of CC3000 Multicast DNS library) - Version 1.1 - Copyright (c) 2013 Tony DiCola (tony@tonydicola.com) - ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com) - Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com) - - This is a simple implementation of multicast DNS query support for an Arduino - running on ESP8266 chip. Only support for resolving address queries is currently - implemented. - - Requirements: - - ESP8266WiFi library - - Usage: - - Include the ESP8266 Multicast DNS library in the sketch. - - Call the begin method in the sketch's setup and provide a domain name (without - the '.local' suffix, i.e. just provide 'foo' to resolve 'foo.local'), and the - Adafruit CC3000 class instance. Optionally provide a time to live (in seconds) - for the DNS record--the default is 1 hour. - - Call the update method in each iteration of the sketch's loop function. - - License (MIT license): - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -*/ -#ifndef ESP8266MDNS_LEGACY_H -#define ESP8266MDNS_LEGACY_H - -#include "ESP8266WiFi.h" -#include "WiFiUdp.h" - -//this should be defined at build time -#ifndef ARDUINO_BOARD -#define ARDUINO_BOARD "generic" -#endif - -class UdpContext; - - -namespace Legacy_MDNSResponder -{ - - -struct MDNSService; -struct MDNSTxt; -struct MDNSAnswer; - -class MDNSResponder -{ -public: - MDNSResponder(); - ~MDNSResponder(); - bool begin(const char* hostName); - bool begin(const String& hostName) - { - return begin(hostName.c_str()); - } - //for compatibility - bool begin(const char* hostName, IPAddress ip, uint32_t ttl = 120) - { - (void) ip; - (void) ttl; - return begin(hostName); - } - bool begin(const String& hostName, IPAddress ip, uint32_t ttl = 120) - { - return begin(hostName.c_str(), ip, ttl); - } - /* Application should call this whenever AP is configured/disabled */ - void notifyAPChange(); - void update(); - - void addService(char *service, char *proto, uint16_t port); - void addService(const char *service, const char *proto, uint16_t port) - { - addService((char *)service, (char *)proto, port); - } - void addService(const String& service, const String& proto, uint16_t port) - { - addService(service.c_str(), proto.c_str(), port); - } - - bool addServiceTxt(char *name, char *proto, char * key, char * value); - bool addServiceTxt(const char *name, const char *proto, const char *key, const char * value) - { - return addServiceTxt((char *)name, (char *)proto, (char *)key, (char *)value); - } - bool addServiceTxt(const String& name, const String& proto, const String& key, const String& value) - { - return addServiceTxt(name.c_str(), proto.c_str(), key.c_str(), value.c_str()); - } - - int queryService(char *service, char *proto); - int queryService(const char *service, const char *proto) - { - return queryService((char *)service, (char *)proto); - } - int queryService(const String& service, const String& proto) - { - return queryService(service.c_str(), proto.c_str()); - } - String hostname(int idx); - IPAddress IP(int idx); - uint16_t port(int idx); - - void enableArduino(uint16_t port, bool auth = false); - - void setInstanceName(String name); - void setInstanceName(const char * name) - { - setInstanceName(String(name)); - } - void setInstanceName(char * name) - { - setInstanceName(String(name)); - } - -private: - struct MDNSService * _services; - UdpContext* _conn; - String _hostName; - String _instanceName; - struct MDNSAnswer * _answers; - struct MDNSQuery * _query; - bool _newQuery; - bool _waitingForAnswers; - WiFiEventHandler _disconnectedHandler; - WiFiEventHandler _gotIPHandler; - - - uint16_t _getServicePort(char *service, char *proto); - MDNSTxt * _getServiceTxt(char *name, char *proto); - uint16_t _getServiceTxtLen(char *name, char *proto); - IPAddress _getRequestMulticastInterface(); - void _parsePacket(); - void _replyToTypeEnumRequest(IPAddress multicastInterface); - void _replyToInstanceRequest(uint8_t questionMask, uint8_t responseMask, char * service, char *proto, uint16_t port, IPAddress multicastInterface); - MDNSAnswer* _getAnswerFromIdx(int idx); - int _getNumAnswers(); - bool _listen(); - void _restart(); -}; - -} // namespace Legacy_MDNSResponder - -#endif //ESP8266MDNS_H - - - diff --git a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.cpp b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.cpp deleted file mode 100644 index 87ff5167ff..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.cpp +++ /dev/null @@ -1,1381 +0,0 @@ -/* - 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 -#include - -#include "LEAmDNS_Priv.h" - - -namespace esp8266 -{ - -/* - LEAmDNS -*/ -namespace MDNSImplementation -{ - -/** - 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), -#ifdef ENABLE_ESP_MDNS_RESPONDER_PASSIV_MODE - m_bPassivModeEnabled(true), -#else - m_bPassivModeEnabled(false), -#endif - m_netif(nullptr) -{ -} - -/* - 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, const IPAddress& p_IPAddress, uint32_t p_u32TTL) -{ - - (void)p_u32TTL; // ignored - bool bResult = false; - - if (0 == m_pUDPContext) - { - if (_setHostname(p_pcHostname)) - { - - //// select interface - - m_netif = nullptr; - IPAddress ipAddress = p_IPAddress; - - if (!ipAddress.isSet()) - { - - IPAddress sta = WiFi.localIP(); - IPAddress ap = WiFi.softAPIP(); - - if (sta.isSet()) - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] STA interface selected\n"))); - ipAddress = sta; - } - else if (ap.isSet()) - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] AP interface selected\n"))); - ipAddress = ap; - } - else - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] standard interfaces are not up, please specify one in ::begin()\n"))); - return false; - } - - // continue to ensure interface is UP - } - - // check existence of this IP address in the interface list - bool found = false; - m_netif = nullptr; - for (auto a : addrList) - if (ipAddress == a.addr()) - { - if (a.ifUp()) - { - found = true; - m_netif = a.interface(); - break; - } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] found interface for IP '%s' but it is not UP\n"), ipAddress.toString().c_str());); - } - if (!found) - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] interface defined by IP '%s' not found\n"), ipAddress.toString().c_str());); - return false; - } - - //// done selecting the interface - - if (m_netif->num == STATION_IF) - { - - m_GotIPHandler = WiFi.onStationModeGotIP([this](const WiFiEventStationModeGotIP & pEvent) - { - (void) pEvent; - // Ensure that _restart() runs in USER context - schedule_function([this]() - { - MDNSResponder::_restart(); - }); - }); - - m_DisconnectedHandler = WiFi.onStationModeDisconnected([this](const WiFiEventStationModeDisconnected & pEvent) - { - (void) pEvent; - // Ensure that _restart() runs in USER context - schedule_function([this]() - { - MDNSResponder::_restart(); - }); - }); - } - - bResult = _restart(); - } - DEBUG_EX_ERR(if (!bResult) - { - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] begin: FAILED for '%s'!\n"), (p_pcHostname ? : "-")); - }); - } - else - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] begin: Ignoring multiple calls to begin (Ignored host domain: '%s')!\n"), (p_pcHostname ? : "-"));); - } - return bResult; -} - -/* - MDNSResponder::close - - Ends the MDNS responder. - Announced services are unannounced (by multicasting a goodbye message) - -*/ -bool MDNSResponder::close(void) -{ - bool bResult = false; - - if (0 != m_pUDPContext) - { - m_GotIPHandler.reset(); // reset WiFi event callbacks. - m_DisconnectedHandler.reset(); - - _announce(false, true); - _resetProbeStatus(false); // Stop probing - _releaseServiceQueries(); - _releaseUDPContext(); - _releaseHostname(); - - bResult = true; - } - else - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] close: Ignoring call to close!\n"));); - } - return bResult; -} - -/* - MDNSResponder::end - - Ends the MDNS responder. - for compatibility with esp32 - -*/ - -bool MDNSResponder::end(void) -{ - return close(); -} - -/* - 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("[MDNSResponder] setHostname: FAILED for '%s'!\n"), (p_pcHostname ? : "-")); - }); - return bResult; -} - -/* - MDNSResponder::setHostname (LEGACY) -*/ -bool MDNSResponder::setHostname(const 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_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] addService: %s to add '%s.%s.%s'!\n"), (hResult ? "Succeeded" : "FAILED"), (p_pcName ? : "-"), p_pcService, p_pcProtocol);); - DEBUG_EX_ERR(if (!hResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[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("[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(const String& p_strService, - const 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("[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("[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("[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("[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(const String& p_strService, - const String& p_strProtocol, - const String& p_strKey, - const 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::MDNSDynamicServiceTxtCallbackFunc p_fnCallback) -{ - - m_fnServiceTxtCallback = p_fnCallback; - - 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::MDNSDynamicServiceTxtCallbackFunc p_fnCallback) -{ - - bool bResult = false; - - stcMDNSService* pService = _findService(p_hService); - if (pService) - { - pService->m_fnTxtCallback = p_fnCallback; - - bResult = true; - } - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[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("[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("[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*/) -{ - if (0 == m_pUDPContext) - { - // safeguard against misuse - return 0; - } - - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[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("[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("[MDNSResponder] queryService: INVALID input data!\n"));); - } - 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(const String& p_strService, - const 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::MDNSServiceQueryCallbackFunc p_fnCallback) -{ - 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_bLegacyQuery = false; - - if (_sendMDNSServiceQuery(*pServiceQuery)) - { - pServiceQuery->m_u8SentCount = 1; - pServiceQuery->m_ResendTimeout.reset(MDNS_DYNAMIC_QUERY_RESEND_DELAY); - - hResult = (hMDNSServiceQuery)pServiceQuery; - } - else - { - _removeServiceQuery(pServiceQuery); - } - } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] installServiceQuery: %s for '%s.%s'!\n\n"), (hResult ? "Succeeded" : "FAILED"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); - DEBUG_EX_ERR(if (!hResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] installServiceQuery: FAILED for '%s.%s'!\n\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("[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); -} - -std::vector MDNSResponder::answerInfo(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery) -{ - std::vector tempVector; - for (uint32_t i = 0; i < answerCount(p_hServiceQuery); i++) - { - tempVector.emplace_back(*this, p_hServiceQuery, i); - } - return tempVector; -} - -/* - 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::setHostProbeResultCallback(MDNSResponder::MDNSHostProbeFn p_fnCallback) -{ - - m_HostProbeInformation.m_fnHostProbeResultCallback = p_fnCallback; - - return true; -} - -bool MDNSResponder::setHostProbeResultCallback(MDNSHostProbeFn1 pfn) -{ - using namespace std::placeholders; - return setHostProbeResultCallback([this, pfn](const char* p_pcDomainName, bool p_bProbeResult) - { - pfn(*this, p_pcDomainName, p_bProbeResult); - }); -} - -/* - 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::MDNSServiceProbeFn p_fnCallback) -{ - - bool bResult = false; - - stcMDNSService* pService = _findService(p_hService); - if (pService) - { - pService->m_ProbeInformation.m_fnServiceProbeResultCallback = p_fnCallback; - - bResult = true; - } - return bResult; -} - -bool MDNSResponder::setServiceProbeResultCallback(const MDNSResponder::hMDNSService p_hService, - MDNSResponder::MDNSServiceProbeFn1 p_fnCallback) -{ - using namespace std::placeholders; - return setServiceProbeResultCallback(p_hService, [this, p_fnCallback](const char* p_pcServiceName, const hMDNSService p_hMDNSService, bool p_bProbeResult) - { - p_fnCallback(*this, p_pcServiceName, p_hMDNSService, p_bProbeResult); - }); -} - - -/* - 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) -{ - - if (m_bPassivModeEnabled) - { - m_bPassivModeEnabled = false; - } - 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(true, true)); -} - -/* - 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; -} - - -} //namespace MDNSImplementation - -} //namespace esp8266 - - diff --git a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.h b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.h deleted file mode 100644 index fe02560aec..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS.h +++ /dev/null @@ -1,1461 +0,0 @@ -/* - 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 -#include -#include - - -#include "ESP8266WiFi.h" - - -namespace esp8266 -{ - -/** - LEAmDNS -*/ -namespace MDNSImplementation -{ - -//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, ...) - // if interfaceAddress is not specified, default interface is STA, or AP when STA is not set - bool begin(const char* p_pcHostname, const IPAddress& p_IPAddress = INADDR_ANY, uint32_t p_u32TTL = 120 /*ignored*/); - bool begin(const String& p_strHostname, const IPAddress& p_IPAddress = INADDR_ANY, uint32_t p_u32TTL = 120 /*ignored*/) - { - return begin(p_strHostname.c_str(), p_IPAddress, p_u32TTL); - } - - // Finish MDNS processing - bool close(void); - // for esp32 compatability - bool end(void); - // Change hostname (probing is restarted) - bool setHostname(const char* p_pcHostname); - // for compatibility... - bool setHostname(const String& p_strHostname); - - bool isRunning(void) - { - return (m_pUDPContext != 0); - } - - /** - 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(const String& p_strServiceName, - const 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); - //for compatibility - //Warning: this has the side effect of changing the hostname. - //TODO: implement instancename different from hostname - void setInstanceName(const char* p_pcHostname) - { - setHostname(p_pcHostname); - } - // for esp32 compatibilty - void setInstanceName(const String& s_pcHostname) - { - setInstanceName(s_pcHostname.c_str()); - } - - /** - 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(const String& p_strService, - const String& p_strProtocol, - const String& p_strKey, - const String& p_strValue); - - /** - MDNSDynamicServiceTxtCallbackFn - Callback function for dynamic MDNS TXT items - */ - - typedef std::function MDNSDynamicServiceTxtCallbackFunc; - - // 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(MDNSDynamicServiceTxtCallbackFunc p_fnCallback); - // 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, - MDNSDynamicServiceTxtCallbackFunc p_fnCallback); - - // 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(const String& p_strService, - const 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; - - enum class AnswerType : uint32_t - { - Unknown = 0, - ServiceDomain = ServiceQueryAnswerType_ServiceDomain, - HostDomainAndPort = ServiceQueryAnswerType_HostDomainAndPort, - Txt = ServiceQueryAnswerType_Txts, -#ifdef MDNS_IP4_SUPPORT - IP4Address = ServiceQueryAnswerType_IP4Address, -#endif -#ifdef MDNS_IP6_SUPPORT - IP6Address = ServiceQueryAnswerType_IP6Address, -#endif - }; - - /** - MDNSServiceQueryCallbackFn - Callback function for received answers for dynamic service queries - */ - struct MDNSServiceInfo; // forward declaration - typedef std::function MDNSServiceQueryCallbackFunc; - - // 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, - MDNSServiceQueryCallbackFunc p_fnCallback); - // Remove a dynamic service query - bool removeServiceQuery(hMDNSServiceQuery p_hServiceQuery); - - uint32_t answerCount(const hMDNSServiceQuery p_hServiceQuery); - std::vector answerInfo(const MDNSResponder::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 std::function MDNSHostProbeFn; - - typedef std::function MDNSHostProbeFn1; - - typedef std::function MDNSServiceProbeFn; - - typedef std::function MDNSServiceProbeFn1; - - // Set a global callback function for host and service probe results - // The callback function is called, when the probing for the host domain - // (or a service domain, which hasn't got a service specific callback) - // Succeeds or fails. - // In case of failure, the failed domain name should be changed. - bool setHostProbeResultCallback(MDNSHostProbeFn p_fnCallback); - bool setHostProbeResultCallback(MDNSHostProbeFn1 p_fnCallback); - - // Set a service specific probe result callback - bool setServiceProbeResultCallback(const MDNSResponder::hMDNSService p_hService, - MDNSServiceProbeFn p_fnCallback); - bool setServiceProbeResultCallback(const MDNSResponder::hMDNSService p_hService, - MDNSServiceProbeFn1 p_fnCallback); - - // 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); - - /** STRUCTS **/ - -public: - /** - MDNSServiceInfo, used in application callbacks - */ - struct MDNSServiceInfo - { - MDNSServiceInfo(MDNSResponder& p_pM, MDNSResponder::hMDNSServiceQuery p_hS, uint32_t p_u32A) - : p_pMDNSResponder(p_pM), - p_hServiceQuery(p_hS), - p_u32AnswerIndex(p_u32A) - {}; - struct CompareKey - { - bool operator()(char const *a, char const *b) const - { - return strcmp(a, b) < 0; - } - }; - using KeyValueMap = std::map; - protected: - MDNSResponder& p_pMDNSResponder; - MDNSResponder::hMDNSServiceQuery p_hServiceQuery; - uint32_t p_u32AnswerIndex; - KeyValueMap keyValueMap; - public: - const char* serviceDomain() - { - return p_pMDNSResponder.answerServiceDomain(p_hServiceQuery, p_u32AnswerIndex); - }; - bool hostDomainAvailable() - { - return (p_pMDNSResponder.hasAnswerHostDomain(p_hServiceQuery, p_u32AnswerIndex)); - } - const char* hostDomain() - { - return (hostDomainAvailable()) ? - p_pMDNSResponder.answerHostDomain(p_hServiceQuery, p_u32AnswerIndex) : nullptr; - }; - bool hostPortAvailable() - { - return (p_pMDNSResponder.hasAnswerPort(p_hServiceQuery, p_u32AnswerIndex)); - } - uint16_t hostPort() - { - return (hostPortAvailable()) ? - p_pMDNSResponder.answerPort(p_hServiceQuery, p_u32AnswerIndex) : 0; - }; - bool IP4AddressAvailable() - { - return (p_pMDNSResponder.hasAnswerIP4Address(p_hServiceQuery, p_u32AnswerIndex)); - } - std::vector IP4Adresses() - { - std::vector internalIP; - if (IP4AddressAvailable()) - { - uint16_t cntIP4Adress = p_pMDNSResponder.answerIP4AddressCount(p_hServiceQuery, p_u32AnswerIndex); - for (uint32_t u2 = 0; u2 < cntIP4Adress; ++u2) - { - internalIP.emplace_back(p_pMDNSResponder.answerIP4Address(p_hServiceQuery, p_u32AnswerIndex, u2)); - } - } - return internalIP; - }; - bool txtAvailable() - { - return (p_pMDNSResponder.hasAnswerTxts(p_hServiceQuery, p_u32AnswerIndex)); - } - const char* strKeyValue() - { - return (txtAvailable()) ? - p_pMDNSResponder.answerTxts(p_hServiceQuery, p_u32AnswerIndex) : nullptr; - }; - const KeyValueMap& keyValues() - { - if (txtAvailable() && keyValueMap.size() == 0) - { - for (auto kv = p_pMDNSResponder._answerKeyValue(p_hServiceQuery, p_u32AnswerIndex); kv != nullptr; kv = kv->m_pNext) - { - keyValueMap.emplace(std::pair(kv->m_pcKey, kv->m_pcValue)); - } - } - return keyValueMap; - } - const char* value(const char* key) - { - char* result = nullptr; - - for (stcMDNSServiceTxt* pTxt = p_pMDNSResponder._answerKeyValue(p_hServiceQuery, p_u32AnswerIndex); pTxt; pTxt = pTxt->m_pNext) - { - if ((key) && - (0 == strcmp(pTxt->m_pcKey, key))) - { - result = pTxt->m_pcValue; - break; - } - } - return result; - } - }; -protected: - - /** - stcMDNSServiceTxt - */ - 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; - }; - - /** - stcMDNSTxts - */ - 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; - }; - - /** - 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 - */ - 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_RRDomain - */ - 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_RRAttributes - */ - 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_RRHeader - */ - 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_RRQuestion - */ - struct stcMDNS_RRQuestion - { - stcMDNS_RRQuestion* m_pNext; - stcMDNS_RRHeader m_Header; - bool m_bUnicast; // Unicast reply requested - - stcMDNS_RRQuestion(void); - }; - - /** - enuAnswerType - */ - typedef enum _enuAnswerType - { - AnswerType_A, - AnswerType_PTR, - AnswerType_TXT, - AnswerType_AAAA, - AnswerType_SRV, - AnswerType_Generic - } enuAnswerType; - - /** - stcMDNS_RRAnswer - */ - 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); - }; - -#ifdef MDNS_IP4_SUPPORT - /** - stcMDNS_RRAnswerA - */ - 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); - }; -#endif - - /** - stcMDNS_RRAnswerPTR - */ - 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_RRAnswerTXT - */ - 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); - }; - -#ifdef MDNS_IP6_SUPPORT - /** - stcMDNS_RRAnswerAAAA - */ - 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); - }; -#endif - - /** - stcMDNS_RRAnswerSRV - */ - 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_RRAnswerGeneric - */ - 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); - }; - - - /** - enuProbingStatus - */ - typedef enum _enuProbingStatus - { - ProbingStatus_WaitingForData, - ProbingStatus_ReadyToStart, - ProbingStatus_InProgress, - ProbingStatus_Done - } enuProbingStatus; - - /** - stcProbeInformation - */ - struct stcProbeInformation - { - enuProbingStatus m_ProbingStatus; - uint8_t m_u8SentCount; // Used for probes and announcements - esp8266::polledTimeout::oneShotMs m_Timeout; // Used for probes and announcements - //clsMDNSTimeFlag m_TimeFlag; // Used for probes and announcements - bool m_bConflict; - bool m_bTiebreakNeeded; - MDNSHostProbeFn m_fnHostProbeResultCallback; - MDNSServiceProbeFn m_fnServiceProbeResultCallback; - - stcProbeInformation(void); - - bool clear(bool p_bClearUserdata = false); - }; - - - /** - stcMDNSService - */ - 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; - MDNSDynamicServiceTxtCallbackFunc m_fnTxtCallback; - 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); - }; - - /** - stcMDNSServiceQuery - */ - struct stcMDNSServiceQuery - { - /** - stcAnswer - */ - struct stcAnswer - { - /** - stcTTL - */ - struct stcTTL - { - /** - timeoutLevel_t - */ - typedef uint8_t timeoutLevel_t; - /** - TIMEOUTLEVELs - */ - const timeoutLevel_t TIMEOUTLEVEL_UNSET = 0; - const timeoutLevel_t TIMEOUTLEVEL_BASE = 80; - const timeoutLevel_t TIMEOUTLEVEL_INTERVAL = 5; - const timeoutLevel_t TIMEOUTLEVEL_FINAL = 100; - - uint32_t m_u32TTL; - esp8266::polledTimeout::oneShotMs m_TTLTimeout; - timeoutLevel_t m_timeoutLevel; - - stcTTL(void); - bool set(uint32_t p_u32TTL); - - bool flagged(void); - bool restart(void); - - bool prepareDeletion(void); - bool finalTimeoutLevel(void) const; - - unsigned long timeout(void) const; - }; -#ifdef MDNS_IP4_SUPPORT - /** - stcIP4Address - */ - struct stcIP4Address - { - stcIP4Address* m_pNext; - IPAddress m_IPAddress; - stcTTL m_TTL; - - stcIP4Address(IPAddress p_IPAddress, - uint32_t p_u32TTL = 0); - }; -#endif -#ifdef MDNS_IP6_SUPPORT - /** - stcIP6Address - */ - struct stcIP6Address - { - stcIP6Address* m_pNext; - IP6Address m_IPAddress; - stcTTL m_TTL; - - stcIP6Address(IPAddress p_IPAddress, - uint32_t p_u32TTL = 0); - }; -#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 - }; - - stcMDNSServiceQuery* m_pNext; - stcMDNS_RRDomain m_ServiceTypeDomain; // eg. _http._tcp.local - MDNSServiceQueryCallbackFunc m_fnCallback; - bool m_bLegacyQuery; - uint8_t m_u8SentCount; - esp8266::polledTimeout::oneShotMs m_ResendTimeout; - 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); - }; - - /** - stcMDNSSendParameter - */ - struct stcMDNSSendParameter - { - protected: - /** - stcDomainCacheItem - */ - 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); - }; - - 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; - }; - - // Instance variables - stcMDNSService* m_pServices; - UdpContext* m_pUDPContext; - char* m_pcHostname; - stcMDNSServiceQuery* m_pServiceQueries; - WiFiEventHandler m_DisconnectedHandler; - WiFiEventHandler m_GotIPHandler; - MDNSDynamicServiceTxtCallbackFunc m_fnServiceTxtCallback; - bool m_bPassivModeEnabled; - stcProbeInformation m_HostProbeInformation; - CONST netif* m_netif; // network interface to run on - - /** 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, - bool p_bIncludeServices); - 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); - 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, - stcMDNSServiceQuery::stcAnswer* p_pKnownAnswers = 0); - - const IPAddress _getResponseMulticastInterface() const - { - return IPAddress(m_netif->ip_addr); - } - - 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); - - stcMDNSServiceTxt* _answerKeyValue(const hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex); - - 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 -}; - -}// namespace MDNSImplementation - -}// namespace esp8266 - -#endif // MDNS_H diff --git a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Control.cpp b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Control.cpp deleted file mode 100644 index 41e9524aba..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Control.cpp +++ /dev/null @@ -1,2134 +0,0 @@ -/* - 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 "LEAmDNS_lwIPdefs.h" -#include "LEAmDNS_Priv.h" - -namespace esp8266 -{ -/* - LEAmDNS -*/ -namespace MDNSImplementation -{ - -/** - CONTROL -*/ - - -/** - MAINTENANCE -*/ - -/* - MDNSResponder::_process - - Run the MDNS process. - Is called, every time the UDPContext receives data AND - should be called in every 'loop' by calling 'MDNS::update()'. - -*/ -bool MDNSResponder::_process(bool p_bUserContext) -{ - - bool bResult = true; - - if (!p_bUserContext) - { - - if ((m_pUDPContext) && // UDPContext available AND - (m_pUDPContext->next())) // has content - { - - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _update: Calling _parseMessage\n"));); - bResult = _parseMessage(); - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parsePacket %s\n"), (bResult ? "succeeded" : "FAILED"));); - } - } - else - { - bResult = (m_netif != nullptr) && - (m_netif->flags & NETIF_FLAG_UP) && // network interface is up and running - _updateProbeStatus() && // Probing - _checkServiceQueryCache(); // Service query cache check - } - return bResult; -} - -/* - MDNSResponder::_restart -*/ -bool MDNSResponder::_restart(void) -{ - - return ((m_netif != nullptr) && - (m_netif->flags & NETIF_FLAG_UP) && // network interface is up and running - (_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("[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("[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("[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("[MDNSResponder] _parseMessage: Received UNEXPECTED opcode:%u. Ignoring message!\n"), header.m_4bOpcode);); - m_pUDPContext->flush(); - } - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseMessage: FAILED to read header\n"));); - m_pUDPContext->flush(); - } - DEBUG_EX_INFO( - unsigned uFreeHeap = ESP.getFreeHeap(); - DEBUG_OUTPUT.printf_P(PSTR("[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) && (qd < p_MsgHeader.m_u16QDCount)); ++qd) - { - - stcMDNS_RRQuestion questionRR; - if ((bResult = _readRRQuestion(questionRR))) - { - // Define host replies, BUT only answer queries after probing is done - u8HostOrServiceReplies = - sendParameter.m_u8HostReplyMask |= (((m_bPassivModeEnabled) || - (ProbingStatus_Done == m_HostProbeInformation.m_ProbingStatus)) - ? _replyMaskForHost(questionRR.m_Header, 0) - : 0); - DEBUG_EX_INFO(if (u8HostOrServiceReplies) - { - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: Host reply needed 0x%X\n"), u8HostOrServiceReplies); - }); - - // Check tiebreak need for host domain - if (ProbingStatus_InProgress == m_HostProbeInformation.m_ProbingStatus) - { - bool bFullNameMatch = false; - if ((_replyMaskForHost(questionRR.m_Header, &bFullNameMatch)) && - (bFullNameMatch)) - { - // We're in 'probing' state and someone is asking for our host domain: this might be - // a race-condition: Two host with the same domain names try simutanously to probe their domains - // See: RFC 6762, 8.2 (Tiebraking) - // However, we're using a max. reduced approach for tiebreaking here: The higher IP-address wins! - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: Possible race-condition for host domain detected while probing.\n"));); - - m_HostProbeInformation.m_bTiebreakNeeded = true; - } - } - - // Define service replies - for (stcMDNSService* pService = m_pServices; pService; pService = pService->m_pNext) - { - // Define service replies, BUT only answer queries after probing is done - uint8_t u8ReplyMaskForQuestion = (((m_bPassivModeEnabled) || - (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("[MDNSResponder] _parseQuery: Service reply needed for (%s.%s.%s): 0x%X (%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("[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("[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)) && - (ip4_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)) && - (ip4_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("[MDNSResponder] _parseQuery: Legacy query from local host %s, id %u!\n"), IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str(), p_MsgHeader.m_u16ID);); - - 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("[MDNSResponder] _parseQuery: FAILED to add legacy question!\n"));); - } - } - else - { - DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: Legacy query from NON-LOCAL host!\n"));); - bResult = false; - } - } - } - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: FAILED to read question!\n"));); - } - } // for questions - - //DEBUG_EX_INFO(if (u8HostOrServiceReplies) { DEBUG_OUTPUT.printf_P(PSTR("[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("[MDNSResponder] _parseQuery: Known answers(%u):\n"), u32Answers); - }); - - for (uint32_t an = 0; ((bResult) && (an < u32Answers)); ++an) - { - stcMDNS_RRAnswer* pKnownRRAnswer = 0; - if (((bResult = _readRRAnswer(pKnownRRAnswer))) && - (pKnownRRAnswer)) - { - - if ((DNS_RRTYPE_ANY != pKnownRRAnswer->m_Header.m_Attributes.m_u16Type) && // No ANY type answer - (DNS_RRCLASS_ANY != pKnownRRAnswer->m_Header.m_Attributes.m_u16Class)) // 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("[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("[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())) - { - - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[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())) - { - - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[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()); - 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("[MDNSResponder] _parseQuery: Tiebreak (IP4) WON (was an old message)!\n"));); - m_HostProbeInformation.m_bTiebreakNeeded = false; - } - else - { - if ((uint32_t)(((stcMDNS_RRAnswerA*)pKnownRRAnswer)->m_IPAddress) > (uint32_t)localIPAddress) // The OTHER IP is 'higher' -> LOST - { - // LOST tiebreak - DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("[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("[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 (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("[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("[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("[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("[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("[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("[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("[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("[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("[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("[MDNSResponder] _parseQuery: FAILED to read known answer!\n"));); - } - - if (pKnownRRAnswer) - { - delete pKnownRRAnswer; - pKnownRRAnswer = 0; - } - } // 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) - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: Sending answer(0x%X)...\n"), u8ReplyNeeded);); - - sendParameter.m_bResponse = true; - sendParameter.m_bAuthorative = true; - - bResult = _sendMDNSMessage(sendParameter); - } - DEBUG_EX_INFO( - else - { - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseQuery: No reply needed\n")); - } - ); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[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("[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("[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("[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("[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("[MDNSResponder] _parseResponse: Received a response\n")); - //_udpDump(); - ); - - bResult = true; - // - // Ignore questions here - stcMDNS_RRQuestion dummyRRQ; - for (uint16_t qd = 0; ((bResult) && (qd < p_MsgHeader.m_u16QDCount)); ++qd) - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseResponse: Received a response containing a question... ignoring!\n"));); - bResult = _readRRQuestion(dummyRRQ); - } // for queries - - // - // Read and collect answers - stcMDNS_RRAnswer* pCollectedRRAnswers = 0; - uint32_t u32NumberOfAnswerRRs = (p_MsgHeader.m_u16ANCount + p_MsgHeader.m_u16NSCount + p_MsgHeader.m_u16ARCount); - for (uint32_t an = 0; ((bResult) && (an < u32NumberOfAnswerRRs)); ++an) - { - stcMDNS_RRAnswer* pRRAnswer = 0; - if (((bResult = _readRRAnswer(pRRAnswer))) && - (pRRAnswer)) - { - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseResponse: ADDING answer!\n"));); - pRRAnswer->m_pNext = pCollectedRRAnswers; - pCollectedRRAnswers = pRRAnswer; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseResponse: FAILED to read answer!\n"));); - if (pRRAnswer) - { - delete pRRAnswer; - pRRAnswer = 0; - } - bResult = false; - } - } // for answers - - // - // Process answers - if (bResult) - { - bResult = ((!pCollectedRRAnswers) || - (_processAnswers(pCollectedRRAnswers))); - } - else // Some failure while reading answers - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _parseResponse: FAILED to read answers!\n"));); - m_pUDPContext->flush(); - } - - // Delete collected answers - while (pCollectedRRAnswers) - { - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[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("[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("[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("[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("[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("[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) - } while ((bFoundNewKeyAnswer) && - (bResult)); - } // else: No answers provided - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[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("[MDNSResponder] _processPTRAnswer: Processing PTR answers...\n"));); - // eg. _http._tcp.local PTR xxxx xx MyESP._http._tcp.local - // Check pending service queries for eg. '_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); // Update TTL tag - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processPTRAnswer: Updated TTL(%d) for "), (int)p_pPTRAnswer->m_u32TTL); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR("\n")); - ); - } - else // received goodbye-message - { - pSQAnswer->m_TTLServiceDomain.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processPTRAnswer: 'Goodbye' received for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR("\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); - pSQAnswer->releaseServiceDomain(); - - bResult = pServiceQuery->addAnswer(pSQAnswer); - p_rbFoundNewKeyAnswer = true; - if (pServiceQuery->m_fnCallback) - { - MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); - pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_ServiceDomain), true); - } - } - } - pServiceQuery = _findNextServiceQueryByServiceType(p_pPTRAnswer->m_Header.m_Domain, pServiceQuery); - } - } // else: No p_pPTRAnswer - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[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); // Update TTL tag - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processSRVAnswer: Updated TTL(%d) for "), (int)p_pSRVAnswer->m_u32TTL); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" host domain and port\n")); - ); - // Host domain & Port - if ((pSQAnswer->m_HostDomain != p_pSRVAnswer->m_SRVDomain) || - (pSQAnswer->m_u16Port != p_pSRVAnswer->m_u16Port)) - { - - pSQAnswer->m_HostDomain = p_pSRVAnswer->m_SRVDomain; - pSQAnswer->releaseHostDomain(); - pSQAnswer->m_u16Port = p_pSRVAnswer->m_u16Port; - pSQAnswer->m_u32ContentFlags |= ServiceQueryAnswerType_HostDomainAndPort; - - p_rbFoundNewKeyAnswer = true; - if (pServiceQuery->m_fnCallback) - { - MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); - pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_HostDomainAndPort), true); - } - } - } - else // Goodby message - { - pSQAnswer->m_TTLHostDomainAndPort.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processSRVAnswer: 'Goodbye' received for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" 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("[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); // Update TTL tag - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processTXTAnswer: Updated TTL(%d) for "), (int)p_pTXTAnswer->m_u32TTL); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" TXTs\n")); - ); - if (!pSQAnswer->m_Txts.compare(p_pTXTAnswer->m_Txts)) - { - pSQAnswer->m_Txts = p_pTXTAnswer->m_Txts; - pSQAnswer->m_u32ContentFlags |= ServiceQueryAnswerType_Txts; - pSQAnswer->releaseTxts(); - - if (pServiceQuery->m_fnCallback) - { - MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); - pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_Txts), true); - } - } - } - else // Goodby message - { - pSQAnswer->m_TTLTxts.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processTXTAnswer: 'Goodbye' received for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" TXTs\n")); - ); - } - } - pServiceQuery = pServiceQuery->m_pNext; - } // while(service query) - } // else: No p_pTXTAnswer - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[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); - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processAAnswer: Updated TTL(%d) for "), (int)p_pAAnswer->m_u32TTL); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" IP4Address (%s)\n"), pIP4Address->m_IPAddress.toString().c_str()); - ); - } - else // 'Goodbye' message for known IP4 address - { - pIP4Address->m_TTL.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processAAnswer: 'Goodbye' received for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" 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); - if ((pIP4Address) && - (pSQAnswer->addIP4Address(pIP4Address))) - { - - pSQAnswer->m_u32ContentFlags |= ServiceQueryAnswerType_IP4Address; - if (pServiceQuery->m_fnCallback) - { - MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); - pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_IP4Address), true); - } - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[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("[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); - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processAAnswer: Updated TTL(%lu) for "), p_pAAAAAnswer->m_u32TTL); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" IP6 address (%s)\n"), pIP6Address->m_IPAddress.toString().c_str()); - ); - } - else // 'Goodbye' message for known IP6 address - { - pIP6Address->m_TTL.prepareDeletion(); // Prepare answer deletion according to RFC 6762, 10.1 - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _processAAnswer: 'Goodbye' received for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" 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); - 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("[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 - //TODO: Fix the following to allow Ethernet shield or other interfaces - (_getResponseMulticastInterface() != IPAddress())) // Has IP address - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Starting host probing...\n"));); - - // First probe delay SHOULD be random 0-250 ms - m_HostProbeInformation.m_Timeout.reset(rand() % MDNS_PROBE_DELAY); - m_HostProbeInformation.m_ProbingStatus = ProbingStatus_InProgress; - } - else if ((ProbingStatus_InProgress == m_HostProbeInformation.m_ProbingStatus) && // Probing AND - (m_HostProbeInformation.m_Timeout.expired())) // Time for next probe - { - - if (MDNS_PROBE_COUNT > m_HostProbeInformation.m_u8SentCount) // Send next probe - { - if ((bResult = _sendHostProbe())) - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Did sent host probe\n\n"));); - m_HostProbeInformation.m_Timeout.reset(MDNS_PROBE_DELAY); - ++m_HostProbeInformation.m_u8SentCount; - } - } - else // Probing finished - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Done host probing.\n"));); - m_HostProbeInformation.m_ProbingStatus = ProbingStatus_Done; - m_HostProbeInformation.m_Timeout.resetToNeverExpires(); - if (m_HostProbeInformation.m_fnHostProbeResultCallback) - { - m_HostProbeInformation.m_fnHostProbeResultCallback(m_pcHostname, true); - } - - // Prepare to announce host - m_HostProbeInformation.m_u8SentCount = 0; - m_HostProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Prepared host announcing.\n\n"));); - } - } // else: Probing already finished OR waiting for next time slot - else if ((ProbingStatus_Done == m_HostProbeInformation.m_ProbingStatus) && - (m_HostProbeInformation.m_Timeout.expired())) - { - - if ((bResult = _announce(true, false))) // Don't announce services here - { - ++m_HostProbeInformation.m_u8SentCount; - - if (MDNS_ANNOUNCE_COUNT > m_HostProbeInformation.m_u8SentCount) - { - m_HostProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Announcing host (%d).\n\n"), m_HostProbeInformation.m_u8SentCount);); - } - else - { - m_HostProbeInformation.m_Timeout.resetToNeverExpires(); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Done host announcing.\n\n"));); - } - } - } - - // - // 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_Timeout.reset(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_Timeout.expired())) // Time for next probe - { - - if (MDNS_PROBE_COUNT > pService->m_ProbeInformation.m_u8SentCount) // Send next probe - { - if ((bResult = _sendServiceProbe(*pService))) - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Did sent service probe (%u)\n\n"), (pService->m_ProbeInformation.m_u8SentCount + 1));); - pService->m_ProbeInformation.m_Timeout.reset(MDNS_PROBE_DELAY); - ++pService->m_ProbeInformation.m_u8SentCount; - } - } - else // Probing finished - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Done service probing %s.%s.%s\n\n"), (pService->m_pcName ? : m_pcHostname), pService->m_pcService, pService->m_pcProtocol);); - pService->m_ProbeInformation.m_ProbingStatus = ProbingStatus_Done; - pService->m_ProbeInformation.m_Timeout.resetToNeverExpires(); - if (pService->m_ProbeInformation.m_fnServiceProbeResultCallback) - { - pService->m_ProbeInformation.m_fnServiceProbeResultCallback(pService->m_pcName, pService, true); - } - // Prepare to announce service - pService->m_ProbeInformation.m_u8SentCount = 0; - pService->m_ProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Prepared service announcing.\n\n"));); - } - } // else: Probing already finished OR waiting for next time slot - else if ((ProbingStatus_Done == pService->m_ProbeInformation.m_ProbingStatus) && - (pService->m_ProbeInformation.m_Timeout.expired())) - { - - if ((bResult = _announceService(*pService))) // Announce service - { - ++pService->m_ProbeInformation.m_u8SentCount; - - if (MDNS_ANNOUNCE_COUNT > pService->m_ProbeInformation.m_u8SentCount) - { - pService->m_ProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Announcing service %s.%s.%s (%d)\n\n"), (pService->m_pcName ? : m_pcHostname), pService->m_pcService, pService->m_pcProtocol, pService->m_ProbeInformation.m_u8SentCount);); - } - else - { - pService->m_ProbeInformation.m_Timeout.resetToNeverExpires(); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: Done service announcing for %s.%s.%s\n\n"), (pService->m_pcName ? : m_pcHostname), pService->m_pcService, pService->m_pcProtocol);); - } - } - } - } - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _updateProbeStatus: FAILED!\n\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_u8SentCount)); // 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_u8SentCount)); // 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("[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("[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("[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("[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("[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("[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_fnHostProbeResultCallback) - { - m_HostProbeInformation.m_fnHostProbeResultCallback(m_pcHostname, false); - - 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 - if (p_rService.m_ProbeInformation.m_fnServiceProbeResultCallback) - { - p_rService.m_ProbeInformation.m_fnServiceProbeResultCallback(p_rService.m_pcName, &p_rService, false); - 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, - bool p_bIncludeServices) -{ - - 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("[MDNSResponder] _announce: Announcing host %s (content 0x%X)\n"), m_pcHostname, sendParameter.m_u8HostReplyMask);); - - if (p_bIncludeServices) - { - // Announce services (service type, name, SRV (location) and TXTs) - for (stcMDNSService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) - { - 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("[MDNSResponder] _announce: Announcing service %s.%s.%s (content %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("[MDNSResponder] _announce: FAILED!\n")); - }); - return ((bResult) && - (_sendMDNSMessage(sendParameter))); -} - -/* - 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("[MDNSResponder] _announceService: Announcing service %s.%s.%s (content 0x%X)\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("[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; - - DEBUG_EX_INFO( - bool printedInfo = false; - ); - for (stcMDNSServiceQuery* pServiceQuery = m_pServiceQueries; ((bResult) && (pServiceQuery)); pServiceQuery = pServiceQuery->m_pNext) - { - - // - // Resend dynamic service queries, if not already done often enough - if ((!pServiceQuery->m_bLegacyQuery) && - (MDNS_DYNAMIC_QUERY_RESEND_COUNT > pServiceQuery->m_u8SentCount) && - (pServiceQuery->m_ResendTimeout.expired())) - { - - if ((bResult = _sendMDNSServiceQuery(*pServiceQuery))) - { - ++pServiceQuery->m_u8SentCount; - pServiceQuery->m_ResendTimeout.reset((MDNS_DYNAMIC_QUERY_RESEND_COUNT > pServiceQuery->m_u8SentCount) - ? (MDNS_DYNAMIC_QUERY_RESEND_DELAY * (pServiceQuery->m_u8SentCount - 1)) - : esp8266::polledTimeout::oneShotMs::neverExpires); - } - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: %s to resend service query!"), (bResult ? "Succeeded" : "FAILED")); - printedInfo = true; - ); - } - - // - // Schedule updates for cached answers - 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.flagged())) - { - - if (!pSQAnswer->m_TTLServiceDomain.finalTimeoutLevel()) - { - - bResult = ((_sendMDNSServiceQuery(*pServiceQuery)) && - (pSQAnswer->m_TTLServiceDomain.restart())); - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: PTR update scheduled for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" %s\n"), (bResult ? "OK" : "FAILURE")); - printedInfo = true; - ); - } - else - { - // Timed out! -> Delete - if (pServiceQuery->m_fnCallback) - { - MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); - pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_ServiceDomain), false); - } - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: Will remove PTR answer for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR("\n")); - printedInfo = true; - ); - - bResult = pServiceQuery->removeAnswer(pSQAnswer); - pSQAnswer = 0; - continue; // Don't use this answer anymore - } - } // ServiceDomain flagged - - // 2. level answers - // HostDomain & Port (from SRV) - if ((bResult) && - (pSQAnswer->m_TTLHostDomainAndPort.flagged())) - { - - if (!pSQAnswer->m_TTLHostDomainAndPort.finalTimeoutLevel()) - { - - bResult = ((_sendMDNSQuery(pSQAnswer->m_ServiceDomain, DNS_RRTYPE_SRV)) && - (pSQAnswer->m_TTLHostDomainAndPort.restart())); - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: SRV update scheduled for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" host domain and port %s\n"), (bResult ? "OK" : "FAILURE")); - printedInfo = true; - ); - } - else - { - // Timed out! -> Delete - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: Will remove SRV answer for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" host domain and port\n")); - printedInfo = true; - ); - // Delete - pSQAnswer->m_HostDomain.clear(); - pSQAnswer->releaseHostDomain(); - pSQAnswer->m_u16Port = 0; - pSQAnswer->m_TTLHostDomainAndPort.set(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) - { - MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); - pServiceQuery->m_fnCallback(serviceInfo, static_cast(u32ContentFlags), false); - } - } - } // HostDomainAndPort flagged - - // Txts (from TXT) - if ((bResult) && - (pSQAnswer->m_TTLTxts.flagged())) - { - - if (!pSQAnswer->m_TTLTxts.finalTimeoutLevel()) - { - - bResult = ((_sendMDNSQuery(pSQAnswer->m_ServiceDomain, DNS_RRTYPE_TXT)) && - (pSQAnswer->m_TTLTxts.restart())); - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: TXT update scheduled for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" TXTs %s\n"), (bResult ? "OK" : "FAILURE")); - printedInfo = true; - ); - } - else - { - // Timed out! -> Delete - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: Will remove TXT answer for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" TXTs\n")); - printedInfo = true; - ); - // Delete - pSQAnswer->m_Txts.clear(); - pSQAnswer->m_TTLTxts.set(0); - - // Remove content flags for deleted answer parts - pSQAnswer->m_u32ContentFlags &= ~ServiceQueryAnswerType_Txts; - - if (pServiceQuery->m_fnCallback) - { - MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); - pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_Txts), false); - } - } - } // TXTs flagged - - // 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.flagged()) - { - - if (!pIP4Address->m_TTL.finalTimeoutLevel()) // Needs update - { - - if ((bAUpdateQuerySent) || - ((bResult = _sendMDNSQuery(pSQAnswer->m_HostDomain, DNS_RRTYPE_A)))) - { - - pIP4Address->m_TTL.restart(); - bAUpdateQuerySent = true; - - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: IP4 update scheduled for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" IP4 address (%s)\n"), (pIP4Address->m_IPAddress.toString().c_str())); - printedInfo = true; - ); - } - } - else - { - // Timed out! -> Delete - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: Will remove IP4 answer for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" IP4 address\n")); - printedInfo = true; - ); - 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) - { - MDNSServiceInfo serviceInfo(*this, (hMDNSServiceQuery)pServiceQuery, pServiceQuery->indexOfAnswer(pSQAnswer)); - pServiceQuery->m_fnCallback(serviceInfo, static_cast(ServiceQueryAnswerType_IP4Address), false); - } - } - } // IP4 flagged - - 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.flagged()) - { - - if (!pIP6Address->m_TTL.finalTimeoutLevel()) // Needs update - { - - if ((bAAAAUpdateQuerySent) || - ((bResult = _sendMDNSQuery(pSQAnswer->m_HostDomain, DNS_RRTYPE_AAAA)))) - { - - pIP6Address->m_TTL.restart(); - bAAAAUpdateQuerySent = true; - - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: IP6 update scheduled for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" IP6 address (%s)\n"), (pIP6Address->m_IPAddress.toString().c_str())); - printedInfo = true; - ); - } - } - else - { - // Timed out! -> Delete - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _checkServiceQueryCache: Will remove answer for ")); - _printRRDomain(pSQAnswer->m_ServiceDomain); - DEBUG_OUTPUT.printf_P(PSTR(" IP6Address\n")); - printedInfo = true; - ); - 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); - } - } - } // IP6 flagged - - pIP6Address = pNextIP6Address; // Next - } // while -#endif - pSQAnswer = pNextSQAnswer; - } - } - } - DEBUG_EX_INFO( - if (printedInfo) -{ - DEBUG_OUTPUT.printf_P(PSTR("\n")); - } - ); - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[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("[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(), 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("[MDNSResponder] _replyMaskForHost: INVALID RR-class (0x%04X)!\n"), p_RRHeader.m_Attributes.m_u16Class);); - } - DEBUG_EX_INFO(if (u8ReplyMask) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _replyMaskForHost: 0x%X\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("[MDNSResponder] _replyMaskForService: INVALID RR-class (0x%04X)!\n"), p_RRHeader.m_Attributes.m_u16Class);); - } - DEBUG_EX_INFO(if (u8ReplyMask) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _replyMaskForService(%s.%s.%s): 0x%X\n"), p_Service.m_pcName, p_Service.m_pcService, p_Service.m_pcProtocol, u8ReplyMask); - }); - return u8ReplyMask; -} - -} // namespace MDNSImplementation - -} // namespace esp8266 diff --git a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Helpers.cpp b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Helpers.cpp deleted file mode 100644 index d23941ce53..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Helpers.cpp +++ /dev/null @@ -1,850 +0,0 @@ -/* - LEAmDNS_Helpers.cpp - - License (MIT license): - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -*/ - -#include "lwip/igmp.h" - -#include "LEAmDNS_lwIPdefs.h" -#include "LEAmDNS_Priv.h" - - -namespace -{ - -/* - strrstr (static) - - Backwards search for p_pcPattern in p_pcString - Based on: https://stackoverflow.com/a/1634398/2778898 - -*/ -const char* strrstr(const char*__restrict p_pcString, const char*__restrict p_pcPattern) -{ - - const char* pcResult = 0; - - size_t stStringLength = (p_pcString ? strlen(p_pcString) : 0); - size_t stPatternLength = (p_pcPattern ? strlen(p_pcPattern) : 0); - - if ((stStringLength) && - (stPatternLength) && - (stPatternLength <= stStringLength)) - { - // Pattern is shorter or has the same length tham the string - - for (const char* s = (p_pcString + stStringLength - stPatternLength); s >= p_pcString; --s) - { - if (0 == strncmp(s, p_pcPattern, stPatternLength)) - { - pcResult = s; - break; - } - } - } - return pcResult; -} - - -} // anonymous - - - - - -namespace esp8266 -{ - -/* - LEAmDNS -*/ -namespace MDNSImplementation -{ - -/** - HELPERS -*/ - -/* - MDNSResponder::indexDomain (static) - - Updates the given domain 'p_rpcHostname' by appending a delimiter and an index number. - - If the given domain already hasa numeric index (after the given delimiter), this index - incremented. If not, the delimiter and index '2' is added. - - If 'p_rpcHostname' is empty (==0), the given default name 'p_pcDefaultHostname' is used, - if no default is given, 'esp8266' is used. - -*/ -/*static*/ bool MDNSResponder::indexDomain(char*& p_rpcDomain, - const char* p_pcDivider /*= "-"*/, - const char* p_pcDefaultDomain /*= 0*/) -{ - - bool bResult = false; - - // Ensure a divider exists; use '-' as default - const char* pcDivider = (p_pcDivider ? : "-"); - - if (p_rpcDomain) - { - const char* pFoundDivider = strrstr(p_rpcDomain, pcDivider); - if (pFoundDivider) // maybe already extended - { - char* pEnd = 0; - unsigned long ulIndex = strtoul((pFoundDivider + strlen(pcDivider)), &pEnd, 10); - if ((ulIndex) && - ((pEnd - p_rpcDomain) == (ptrdiff_t)strlen(p_rpcDomain)) && - (!*pEnd)) // Valid (old) index found - { - - char acIndexBuffer[16]; - sprintf(acIndexBuffer, "%lu", (++ulIndex)); - size_t stLength = ((pFoundDivider - p_rpcDomain + strlen(pcDivider)) + strlen(acIndexBuffer) + 1); - char* pNewHostname = new char[stLength]; - if (pNewHostname) - { - memcpy(pNewHostname, p_rpcDomain, (pFoundDivider - p_rpcDomain + strlen(pcDivider))); - pNewHostname[pFoundDivider - p_rpcDomain + strlen(pcDivider)] = 0; - strcat(pNewHostname, acIndexBuffer); - - delete[] p_rpcDomain; - p_rpcDomain = pNewHostname; - - bResult = true; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.println(F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!"));); - } - } - else - { - pFoundDivider = 0; // Flag the need to (base) extend the hostname - } - } - - if (!pFoundDivider) // not yet extended (or failed to increment extension) -> start indexing - { - size_t stLength = strlen(p_rpcDomain) + (strlen(pcDivider) + 1 + 1); // Name + Divider + '2' + '\0' - char* pNewHostname = new char[stLength]; - if (pNewHostname) - { - sprintf(pNewHostname, "%s%s2", p_rpcDomain, pcDivider); - - delete[] p_rpcDomain; - p_rpcDomain = pNewHostname; - - bResult = true; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.println(F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!"));); - } - } - } - else - { - // No given host domain, use base or default - const char* cpcDefaultName = (p_pcDefaultDomain ? : "esp8266"); - - size_t stLength = strlen(cpcDefaultName) + 1; // '\0' - p_rpcDomain = new char[stLength]; - if (p_rpcDomain) - { - strncpy(p_rpcDomain, cpcDefaultName, stLength); - bResult = true; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.println(F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!"));); - } - } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] indexDomain: %s\n"), p_rpcDomain);); - return bResult; -} - - -/* - UDP CONTEXT -*/ - -bool MDNSResponder::_callProcess(void) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf("[MDNSResponder] _callProcess (%lu, triggered by: %s)\n", millis(), IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str());); - - return _process(false); -} - -/* - MDNSResponder::_allocUDPContext - - (Re-)Creates the one-and-only UDP context for the MDNS responder. - The context is added to the 'multicast'-group and listens to the MDNS port (5353). - The travel-distance for multicast messages is set to 1 (local, via MDNS_MULTICAST_TTL). - Messages are received via the MDNSResponder '_update' function. CAUTION: This function - is called from the WiFi stack side of the ESP stack system. - -*/ -bool MDNSResponder::_allocUDPContext(void) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.println("[MDNSResponder] _allocUDPContext");); - - bool bResult = false; - - _releaseUDPContext(); - -#ifdef MDNS_IP4_SUPPORT - ip_addr_t multicast_addr = DNS_MQUERY_IPV4_GROUP_INIT; -#endif -#ifdef MDNS_IP6_SUPPORT - //TODO: set multicast address (lwip_joingroup() is IPv4 only at the time of writing) - multicast_addr.addr = DNS_MQUERY_IPV6_GROUP_INIT; -#endif - if (ERR_OK == igmp_joingroup(ip_2_ip4(&m_netif->ip_addr), ip_2_ip4(&multicast_addr))) - { - m_pUDPContext = new UdpContext; - m_pUDPContext->ref(); - - if (m_pUDPContext->listen(IP4_ADDR_ANY, DNS_MQUERY_PORT)) - { - m_pUDPContext->setMulticastTTL(MDNS_MULTICAST_TTL); - m_pUDPContext->onRx(std::bind(&MDNSResponder::_callProcess, this)); - - bResult = m_pUDPContext->connect(&multicast_addr, DNS_MQUERY_PORT); - } - } - return bResult; -} - -/* - MDNSResponder::_releaseUDPContext -*/ -bool MDNSResponder::_releaseUDPContext(void) -{ - - if (m_pUDPContext) - { - m_pUDPContext->unref(); - m_pUDPContext = 0; - } - return true; -} - - -/* - SERVICE QUERY -*/ - -/* - MDNSResponder::_allocServiceQuery -*/ -MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_allocServiceQuery(void) -{ - - stcMDNSServiceQuery* pServiceQuery = new stcMDNSServiceQuery; - if (pServiceQuery) - { - // Link to query list - pServiceQuery->m_pNext = m_pServiceQueries; - m_pServiceQueries = pServiceQuery; - } - return m_pServiceQueries; -} - -/* - MDNSResponder::_removeServiceQuery -*/ -bool MDNSResponder::_removeServiceQuery(MDNSResponder::stcMDNSServiceQuery* p_pServiceQuery) -{ - - bool bResult = false; - - if (p_pServiceQuery) - { - stcMDNSServiceQuery* pPred = m_pServiceQueries; - while ((pPred) && - (pPred->m_pNext != p_pServiceQuery)) - { - pPred = pPred->m_pNext; - } - if (pPred) - { - pPred->m_pNext = p_pServiceQuery->m_pNext; - delete p_pServiceQuery; - bResult = true; - } - else // No predecesor - { - if (m_pServiceQueries == p_pServiceQuery) - { - m_pServiceQueries = p_pServiceQuery->m_pNext; - delete p_pServiceQuery; - bResult = true; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.println("[MDNSResponder] _releaseServiceQuery: INVALID service query!");); - } - } - } - return bResult; -} - -/* - MDNSResponder::_removeLegacyServiceQuery -*/ -bool MDNSResponder::_removeLegacyServiceQuery(void) -{ - - stcMDNSServiceQuery* pLegacyServiceQuery = _findLegacyServiceQuery(); - return (pLegacyServiceQuery ? _removeServiceQuery(pLegacyServiceQuery) : true); -} - -/* - MDNSResponder::_findServiceQuery - - 'Convert' hMDNSServiceQuery to stcMDNSServiceQuery* (ensure existance) - -*/ -MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_findServiceQuery(MDNSResponder::hMDNSServiceQuery p_hServiceQuery) -{ - - stcMDNSServiceQuery* pServiceQuery = m_pServiceQueries; - while (pServiceQuery) - { - if ((hMDNSServiceQuery)pServiceQuery == p_hServiceQuery) - { - break; - } - pServiceQuery = pServiceQuery->m_pNext; - } - return pServiceQuery; -} - -/* - MDNSResponder::_findLegacyServiceQuery -*/ -MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_findLegacyServiceQuery(void) -{ - - stcMDNSServiceQuery* pServiceQuery = m_pServiceQueries; - while (pServiceQuery) - { - if (pServiceQuery->m_bLegacyQuery) - { - break; - } - pServiceQuery = pServiceQuery->m_pNext; - } - return pServiceQuery; -} - -/* - MDNSResponder::_releaseServiceQueries -*/ -bool MDNSResponder::_releaseServiceQueries(void) -{ - while (m_pServiceQueries) - { - stcMDNSServiceQuery* pNext = m_pServiceQueries->m_pNext; - delete m_pServiceQueries; - m_pServiceQueries = pNext; - } - return true; -} - -/* - MDNSResponder::_findNextServiceQueryByServiceType -*/ -MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_findNextServiceQueryByServiceType(const stcMDNS_RRDomain& p_ServiceTypeDomain, - const stcMDNSServiceQuery* p_pPrevServiceQuery) -{ - stcMDNSServiceQuery* pMatchingServiceQuery = 0; - - stcMDNSServiceQuery* pServiceQuery = (p_pPrevServiceQuery ? p_pPrevServiceQuery->m_pNext : m_pServiceQueries); - while (pServiceQuery) - { - if (p_ServiceTypeDomain == pServiceQuery->m_ServiceTypeDomain) - { - pMatchingServiceQuery = pServiceQuery; - break; - } - pServiceQuery = pServiceQuery->m_pNext; - } - return pMatchingServiceQuery; -} - - -/* - HOSTNAME -*/ - -/* - MDNSResponder::_setHostname -*/ -bool MDNSResponder::_setHostname(const char* p_pcHostname) -{ - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _allocHostname (%s)\n"), p_pcHostname);); - - bool bResult = false; - - _releaseHostname(); - - size_t stLength = 0; - if ((p_pcHostname) && - (MDNS_DOMAIN_LABEL_MAXLENGTH >= (stLength = strlen(p_pcHostname)))) // char max size for a single label - { - // Copy in hostname characters as lowercase - if ((bResult = (0 != (m_pcHostname = new char[stLength + 1])))) - { -#ifdef MDNS_FORCE_LOWERCASE_HOSTNAME - size_t i = 0; - for (; i < stLength; ++i) - { - m_pcHostname[i] = (isupper(p_pcHostname[i]) ? tolower(p_pcHostname[i]) : p_pcHostname[i]); - } - m_pcHostname[i] = 0; -#else - strncpy(m_pcHostname, p_pcHostname, (stLength + 1)); -#endif - } - } - return bResult; -} - -/* - MDNSResponder::_releaseHostname -*/ -bool MDNSResponder::_releaseHostname(void) -{ - - if (m_pcHostname) - { - delete[] m_pcHostname; - m_pcHostname = 0; - } - return true; -} - - -/* - SERVICE -*/ - -/* - MDNSResponder::_allocService -*/ -MDNSResponder::stcMDNSService* MDNSResponder::_allocService(const char* p_pcName, - const char* p_pcService, - const char* p_pcProtocol, - uint16_t p_u16Port) -{ - - stcMDNSService* pService = 0; - if (((!p_pcName) || - (MDNS_DOMAIN_LABEL_MAXLENGTH >= strlen(p_pcName))) && - (p_pcService) && - (MDNS_SERVICE_NAME_LENGTH >= strlen(p_pcService)) && - (p_pcProtocol) && - (MDNS_SERVICE_PROTOCOL_LENGTH >= strlen(p_pcProtocol)) && - (p_u16Port) && - (0 != (pService = new stcMDNSService)) && - (pService->setName(p_pcName ? : m_pcHostname)) && - (pService->setService(p_pcService)) && - (pService->setProtocol(p_pcProtocol))) - { - - pService->m_bAutoName = (0 == p_pcName); - pService->m_u16Port = p_u16Port; - - // Add to list (or start list) - pService->m_pNext = m_pServices; - m_pServices = pService; - } - return pService; -} - -/* - MDNSResponder::_releaseService -*/ -bool MDNSResponder::_releaseService(MDNSResponder::stcMDNSService* p_pService) -{ - - bool bResult = false; - - if (p_pService) - { - stcMDNSService* pPred = m_pServices; - while ((pPred) && - (pPred->m_pNext != p_pService)) - { - pPred = pPred->m_pNext; - } - if (pPred) - { - pPred->m_pNext = p_pService->m_pNext; - delete p_pService; - bResult = true; - } - else // No predecesor - { - if (m_pServices == p_pService) - { - m_pServices = p_pService->m_pNext; - delete p_pService; - bResult = true; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.println("[MDNSResponder] _releaseService: INVALID service!");); - } - } - } - return bResult; -} - -/* - MDNSResponder::_releaseServices -*/ -bool MDNSResponder::_releaseServices(void) -{ - - stcMDNSService* pService = m_pServices; - while (pService) - { - _releaseService(pService); - pService = m_pServices; - } - return true; -} - -/* - MDNSResponder::_findService -*/ -MDNSResponder::stcMDNSService* MDNSResponder::_findService(const char* p_pcName, - const char* p_pcService, - const char* p_pcProtocol) -{ - - stcMDNSService* pService = m_pServices; - while (pService) - { - if ((0 == strcmp(pService->m_pcName, p_pcName)) && - (0 == strcmp(pService->m_pcService, p_pcService)) && - (0 == strcmp(pService->m_pcProtocol, p_pcProtocol))) - { - - break; - } - pService = pService->m_pNext; - } - return pService; -} - -/* - MDNSResponder::_findService -*/ -MDNSResponder::stcMDNSService* MDNSResponder::_findService(const MDNSResponder::hMDNSService p_hService) -{ - - stcMDNSService* pService = m_pServices; - while (pService) - { - if (p_hService == (hMDNSService)pService) - { - break; - } - pService = pService->m_pNext; - } - return pService; -} - - -/* - SERVICE TXT -*/ - -/* - MDNSResponder::_allocServiceTxt -*/ -MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_allocServiceTxt(MDNSResponder::stcMDNSService* p_pService, - const char* p_pcKey, - const char* p_pcValue, - bool p_bTemp) -{ - - stcMDNSServiceTxt* pTxt = 0; - - if ((p_pService) && - (p_pcKey) && - (MDNS_SERVICE_TXT_MAXLENGTH > (p_pService->m_Txts.length() + - 1 + // Length byte - (p_pcKey ? strlen(p_pcKey) : 0) + - 1 + // '=' - (p_pcValue ? strlen(p_pcValue) : 0)))) - { - - pTxt = new stcMDNSServiceTxt; - if (pTxt) - { - size_t stLength = (p_pcKey ? strlen(p_pcKey) : 0); - pTxt->m_pcKey = new char[stLength + 1]; - if (pTxt->m_pcKey) - { - strncpy(pTxt->m_pcKey, p_pcKey, stLength); pTxt->m_pcKey[stLength] = 0; - } - - if (p_pcValue) - { - stLength = (p_pcValue ? strlen(p_pcValue) : 0); - pTxt->m_pcValue = new char[stLength + 1]; - if (pTxt->m_pcValue) - { - strncpy(pTxt->m_pcValue, p_pcValue, stLength); pTxt->m_pcValue[stLength] = 0; - } - } - pTxt->m_bTemp = p_bTemp; - - // Add to list (or start list) - p_pService->m_Txts.add(pTxt); - } - } - return pTxt; -} - -/* - MDNSResponder::_releaseServiceTxt -*/ -bool MDNSResponder::_releaseServiceTxt(MDNSResponder::stcMDNSService* p_pService, - MDNSResponder::stcMDNSServiceTxt* p_pTxt) -{ - - return ((p_pService) && - (p_pTxt) && - (p_pService->m_Txts.remove(p_pTxt))); -} - -/* - MDNSResponder::_updateServiceTxt -*/ -MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_updateServiceTxt(MDNSResponder::stcMDNSService* p_pService, - MDNSResponder::stcMDNSServiceTxt* p_pTxt, - const char* p_pcValue, - bool p_bTemp) -{ - - if ((p_pService) && - (p_pTxt) && - (MDNS_SERVICE_TXT_MAXLENGTH > (p_pService->m_Txts.length() - - (p_pTxt->m_pcValue ? strlen(p_pTxt->m_pcValue) : 0) + - (p_pcValue ? strlen(p_pcValue) : 0)))) - { - p_pTxt->update(p_pcValue); - p_pTxt->m_bTemp = p_bTemp; - } - return p_pTxt; -} - -/* - MDNSResponder::_findServiceTxt -*/ -MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_findServiceTxt(MDNSResponder::stcMDNSService* p_pService, - const char* p_pcKey) -{ - - return (p_pService ? p_pService->m_Txts.find(p_pcKey) : 0); -} - -/* - MDNSResponder::_findServiceTxt -*/ -MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_findServiceTxt(MDNSResponder::stcMDNSService* p_pService, - const hMDNSTxt p_hTxt) -{ - - return (((p_pService) && (p_hTxt)) ? p_pService->m_Txts.find((stcMDNSServiceTxt*)p_hTxt) : 0); -} - -/* - MDNSResponder::_addServiceTxt -*/ -MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_addServiceTxt(MDNSResponder::stcMDNSService* p_pService, - const char* p_pcKey, - const char* p_pcValue, - bool p_bTemp) -{ - stcMDNSServiceTxt* pResult = 0; - - if ((p_pService) && - (p_pcKey) && - (strlen(p_pcKey))) - { - - stcMDNSServiceTxt* pTxt = p_pService->m_Txts.find(p_pcKey); - if (pTxt) - { - pResult = _updateServiceTxt(p_pService, pTxt, p_pcValue, p_bTemp); - } - else - { - pResult = _allocServiceTxt(p_pService, p_pcKey, p_pcValue, p_bTemp); - } - } - return pResult; -} - -MDNSResponder::stcMDNSServiceTxt* MDNSResponder::_answerKeyValue(const hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery); - stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0); - // Fill m_pcTxts (if not already done) - return (pSQAnswer) ? pSQAnswer->m_Txts.m_pTxts : 0; -} - -/* - MDNSResponder::_collectServiceTxts -*/ -bool MDNSResponder::_collectServiceTxts(MDNSResponder::stcMDNSService& p_rService) -{ - - // Call Dynamic service callbacks - if (m_fnServiceTxtCallback) - { - m_fnServiceTxtCallback((hMDNSService)&p_rService); - } - if (p_rService.m_fnTxtCallback) - { - p_rService.m_fnTxtCallback((hMDNSService)&p_rService); - } - return true; -} - -/* - MDNSResponder::_releaseTempServiceTxts -*/ -bool MDNSResponder::_releaseTempServiceTxts(MDNSResponder::stcMDNSService& p_rService) -{ - - return (p_rService.m_Txts.removeTempTxts()); -} - - -/* - MISC -*/ - -#ifdef DEBUG_ESP_MDNS_RESPONDER -/* - MDNSResponder::_printRRDomain -*/ -bool MDNSResponder::_printRRDomain(const MDNSResponder::stcMDNS_RRDomain& p_RRDomain) const -{ - - //DEBUG_OUTPUT.printf_P(PSTR("Domain: ")); - - const char* pCursor = p_RRDomain.m_acName; - uint8_t u8Length = *pCursor++; - if (u8Length) - { - while (u8Length) - { - for (uint8_t u = 0; u < u8Length; ++u) - { - DEBUG_OUTPUT.printf_P(PSTR("%c"), *(pCursor++)); - } - u8Length = *pCursor++; - if (u8Length) - { - DEBUG_OUTPUT.printf_P(PSTR(".")); - } - } - } - else // empty domain - { - DEBUG_OUTPUT.printf_P(PSTR("-empty-")); - } - //DEBUG_OUTPUT.printf_P(PSTR("\n")); - - return true; -} - -/* - MDNSResponder::_printRRAnswer -*/ -bool MDNSResponder::_printRRAnswer(const MDNSResponder::stcMDNS_RRAnswer& p_RRAnswer) const -{ - - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] RRAnswer: ")); - _printRRDomain(p_RRAnswer.m_Header.m_Domain); - DEBUG_OUTPUT.printf_P(PSTR(" Type:0x%04X Class:0x%04X TTL:%u, "), p_RRAnswer.m_Header.m_Attributes.m_u16Type, p_RRAnswer.m_Header.m_Attributes.m_u16Class, p_RRAnswer.m_u32TTL); - switch (p_RRAnswer.m_Header.m_Attributes.m_u16Type & (~0x8000)) // Topmost bit might carry 'cache flush' flag - { -#ifdef MDNS_IP4_SUPPORT - case DNS_RRTYPE_A: - DEBUG_OUTPUT.printf_P(PSTR("A IP:%s"), ((const stcMDNS_RRAnswerA*)&p_RRAnswer)->m_IPAddress.toString().c_str()); - break; -#endif - case DNS_RRTYPE_PTR: - DEBUG_OUTPUT.printf_P(PSTR("PTR ")); - _printRRDomain(((const stcMDNS_RRAnswerPTR*)&p_RRAnswer)->m_PTRDomain); - break; - case DNS_RRTYPE_TXT: - { - size_t stTxtLength = ((const stcMDNS_RRAnswerTXT*)&p_RRAnswer)->m_Txts.c_strLength(); - char* pTxts = new char[stTxtLength]; - if (pTxts) - { - ((/*const c_str()!!*/stcMDNS_RRAnswerTXT*)&p_RRAnswer)->m_Txts.c_str(pTxts); - DEBUG_OUTPUT.printf_P(PSTR("TXT(%u) %s"), stTxtLength, pTxts); - delete[] pTxts; - } - break; - } -#ifdef MDNS_IP6_SUPPORT - case DNS_RRTYPE_AAAA: - DEBUG_OUTPUT.printf_P(PSTR("AAAA IP:%s"), ((stcMDNS_RRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); - break; -#endif - case DNS_RRTYPE_SRV: - DEBUG_OUTPUT.printf_P(PSTR("SRV Port:%u "), ((const stcMDNS_RRAnswerSRV*)&p_RRAnswer)->m_u16Port); - _printRRDomain(((const stcMDNS_RRAnswerSRV*)&p_RRAnswer)->m_SRVDomain); - break; - default: - DEBUG_OUTPUT.printf_P(PSTR("generic ")); - break; - } - DEBUG_OUTPUT.printf_P(PSTR("\n")); - - return true; -} -#endif - -} // namespace MDNSImplementation - -} // namespace esp8266 - - - - diff --git a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Priv.h b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Priv.h deleted file mode 100644 index cc56b133a9..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Priv.h +++ /dev/null @@ -1,182 +0,0 @@ -/* - 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 MDNS_PRIV_H -#define MDNS_PRIV_H - -namespace esp8266 -{ - -/* - LEAmDNS -*/ - -namespace MDNSImplementation -{ - -// Enable class debug functions -#define ESP_8266_MDNS_INCLUDE -//#define DEBUG_ESP_MDNS_RESPONDER - -#if !defined(DEBUG_ESP_MDNS_RESPONDER) && defined(DEBUG_ESP_MDNS) -#define DEBUG_ESP_MDNS_RESPONDER -#endif - -#ifndef LWIP_OPEN_SRC -#define LWIP_OPEN_SRC -#endif - -// -// If ENABLE_ESP_MDNS_RESPONDER_PASSIV_MODE is defined, the mDNS responder ignores a successful probing -// This allows to drive the responder in a environment, where 'update()' isn't called in the loop -//#define ENABLE_ESP_MDNS_RESPONDER_PASSIV_MODE - -// 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) do { (void)0; } while (0) -#endif -#ifdef DEBUG_ESP_MDNS_ERR -#define DEBUG_EX_ERR(A) A -#else -#define DEBUG_EX_ERR(A) do { (void)0; } while (0) -#endif -#ifdef DEBUG_ESP_MDNS_TX -#define DEBUG_EX_TX(A) A -#else -#define DEBUG_EX_TX(A) do { (void)0; } while (0) -#endif -#ifdef DEBUG_ESP_MDNS_RX -#define DEBUG_EX_RX(A) A -#else -#define DEBUG_EX_RX(A) do { (void)0; } while (0) -#endif - -#ifdef DEBUG_ESP_PORT -#define DEBUG_OUTPUT DEBUG_ESP_PORT -#else -#define DEBUG_OUTPUT Serial -#endif -#else -#define DEBUG_EX_INFO(A) do { (void)0; } while (0) -#define DEBUG_EX_ERR(A) do { (void)0; } while (0) -#define DEBUG_EX_TX(A) do { (void)0; } while (0) -#define DEBUG_EX_RX(A) do { (void)0; } while (0) -#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) - - However, RFC 3171 seems to force 255 instead -*/ -#define MDNS_MULTICAST_TTL 255/*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 - Delay between and number of announces for host and service domains - Delay between and number of service queries; the delay is multiplied by the resent number in '_checkServiceQueryCache' -*/ -#define MDNS_PROBE_DELAY 250 -#define MDNS_PROBE_COUNT 3 -#define MDNS_ANNOUNCE_DELAY 1000 -#define MDNS_ANNOUNCE_COUNT 8 -#define MDNS_DYNAMIC_QUERY_RESEND_COUNT 5 -#define MDNS_DYNAMIC_QUERY_RESEND_DELAY 5000 - - -/* - 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 -#else -#ifdef F -#undef F -#endif -#define F(A) A -#endif - -} // namespace MDNSImplementation - -} // namespace esp8266 - -// Include the main header, so the submodlues only need to include this header -#include "LEAmDNS.h" - - -#endif // MDNS_PRIV_H diff --git a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Structs.cpp b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Structs.cpp deleted file mode 100644 index ce475de3ba..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Structs.cpp +++ /dev/null @@ -1,2476 +0,0 @@ -/* - 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" -#include "LEAmDNS_lwIPdefs.h" - -namespace esp8266 -{ - -/* - LEAmDNS -*/ -namespace MDNSImplementation -{ - -/** - 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)) - { - 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 ? 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)) - { - 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 ? 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 += strlen(m_pcKey); // Key - stLength += 1; // '=' - stLength += (m_pcValue ? 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 ? strlen(pTxt->m_pcKey) : 0))))) - { - if (pTxt != m_pTxts) - { - *p_pcBuffer++ = ';'; - } - strncpy(p_pcBuffer, pTxt->m_pcKey, stLength); p_pcBuffer[stLength] = 0; - p_pcBuffer += stLength; - *p_pcBuffer++ = '='; - if ((stLength = (pTxt->m_pcValue ? strlen(pTxt->m_pcValue) : 0))) - { - 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 ? strlen(pTxt->m_pcKey) : 0))))) - { - memcpy(p_pcBuffer, pTxt->m_pcKey, stLength); - p_pcBuffer += stLength; - *p_pcBuffer++ = '='; - if ((stLength = (pTxt->m_pcValue ? strlen(pTxt->m_pcValue) : 0))) - { - 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) && - (strlen(pTxt->m_pcValue) == 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) && - (strlen(pOtherTxt->m_pcValue) == 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 - ? (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; - } - 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 == strncasecmp((pT + 1), (pO + 1), *((unsigned char*)pT)))) // Same content - { - if (*((unsigned char*)pT)) // Not 0 - { - pT += (1 + * ((unsigned char*)pT)); // Shift by length byte and 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_u8SentCount(0), - m_Timeout(esp8266::polledTimeout::oneShotMs::neverExpires), - m_bConflict(false), - m_bTiebreakNeeded(false), - m_fnHostProbeResultCallback(0), - m_fnServiceProbeResultCallback(0) -{ -} - -/* - MDNSResponder::stcProbeInformation::clear -*/ -bool MDNSResponder::stcProbeInformation::clear(bool p_bClearUserdata /*= false*/) -{ - - m_ProbingStatus = ProbingStatus_WaitingForData; - m_u8SentCount = 0; - m_Timeout.resetToNeverExpires(); - m_bConflict = false; - m_bTiebreakNeeded = false; - if (p_bClearUserdata) - { - m_fnHostProbeResultCallback = 0; - m_fnServiceProbeResultCallback = 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) -{ - - 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 ? strlen(p_pcName) : 0); - if (stLength) - { - if ((bResult = (0 != (m_pcName = new char[stLength + 1])))) - { - 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 ? strlen(p_pcService) : 0); - if (stLength) - { - if ((bResult = (0 != (m_pcService = new char[stLength + 1])))) - { - 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 ? strlen(p_pcProtocol) : 0); - if (stLength) - { - if ((bResult = (0 != (m_pcProtocol = new char[stLength + 1])))) - { - 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* /) - : m_bUpdateScheduled(false) { - - set(p_u32TTL * 1000); - } - - / * - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::set - / - bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::set(uint32_t p_u32TTL) { - - m_TTLTimeFlag.restart(p_u32TTL * 1000); - m_bUpdateScheduled = false; - - return true; - } - - / * - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::has80Percent - / - bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::has80Percent(void) const { - - return ((m_TTLTimeFlag.getTimeout()) && - (!m_bUpdateScheduled) && - (m_TTLTimeFlag.hypotheticalTimeout((m_TTLTimeFlag.getTimeout() * 800) / 1000))); - } - - / * - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::isOutdated - / - bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::isOutdated(void) const { - - return ((m_TTLTimeFlag.getTimeout()) && - (m_TTLTimeFlag.flagged())); - }*/ - - -/** - 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(void) - : m_u32TTL(0), - m_TTLTimeout(esp8266::polledTimeout::oneShotMs::neverExpires), - m_timeoutLevel(TIMEOUTLEVEL_UNSET) -{ - -} - -/* - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::set -*/ -bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::set(uint32_t p_u32TTL) -{ - - m_u32TTL = p_u32TTL; - if (m_u32TTL) - { - m_timeoutLevel = TIMEOUTLEVEL_BASE; // Set to 80% - m_TTLTimeout.reset(timeout()); - } - else - { - m_timeoutLevel = TIMEOUTLEVEL_UNSET; // undef - m_TTLTimeout.resetToNeverExpires(); - } - return true; -} - -/* - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::flagged -*/ -bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::flagged(void) -{ - - return ((m_u32TTL) && - (TIMEOUTLEVEL_UNSET != m_timeoutLevel) && - (m_TTLTimeout.expired())); -} - -/* - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::restart -*/ -bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::restart(void) -{ - - bool bResult = true; - - if ((TIMEOUTLEVEL_BASE <= m_timeoutLevel) && // >= 80% AND - (TIMEOUTLEVEL_FINAL > m_timeoutLevel)) // < 100% - { - - m_timeoutLevel += TIMEOUTLEVEL_INTERVAL; // increment by 5% - m_TTLTimeout.reset(timeout()); - } - else - { - bResult = false; - m_TTLTimeout.resetToNeverExpires(); - m_timeoutLevel = TIMEOUTLEVEL_UNSET; - } - return bResult; -} - -/* - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::prepareDeletion -*/ -bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::prepareDeletion(void) -{ - - m_timeoutLevel = TIMEOUTLEVEL_FINAL; - m_TTLTimeout.reset(1 * 1000); // See RFC 6762, 10.1 - - return true; -} - -/* - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::finalTimeoutLevel -*/ -bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::finalTimeoutLevel(void) const -{ - - return (TIMEOUTLEVEL_FINAL == m_timeoutLevel); -} - -/* - MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::timeout -*/ -unsigned long MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcTTL::timeout(void) const -{ - - uint32_t u32Timeout = esp8266::polledTimeout::oneShotMs::neverExpires; - - if (TIMEOUTLEVEL_BASE == m_timeoutLevel) // 80% - { - u32Timeout = (m_u32TTL * 800); // to milliseconds - } - else if ((TIMEOUTLEVEL_BASE < m_timeoutLevel) && // >80% AND - (TIMEOUTLEVEL_FINAL >= m_timeoutLevel)) // <= 100% - { - - u32Timeout = (m_u32TTL * 50); - } // else: invalid - return u32Timeout; -} - - -#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*/) - : m_pNext(0), - m_IPAddress(p_IPAddress) -{ - - m_TTL.set(p_u32TTL); -} -#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) && (u32Index < p_u32Index)); pIP4Address = pIP4Address->m_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) && (u32Index < p_u32Index)); pIP6Address = pIP6Address->m_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_bLegacyQuery(false), - m_u8SentCount(0), - m_ResendTimeout(esp8266::polledTimeout::oneShotMs::neverExpires), - 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_bLegacyQuery = false; - m_u8SentCount = 0; - m_ResendTimeout.resetToNeverExpires(); - 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) && (u32Index < p_u32Index)); pAnswer = pAnswer->m_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 MDNSImplementation - -} // namespace esp8266 - - - diff --git a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Transfer.cpp b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Transfer.cpp deleted file mode 100644 index 7400abec42..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_Transfer.cpp +++ /dev/null @@ -1,1779 +0,0 @@ -/* - LEAmDNS_Transfer.cpp - - License (MIT license): - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -*/ - -extern "C" { -#include "user_interface.h" -} - -#include "LEAmDNS_lwIPdefs.h" -#include "LEAmDNS_Priv.h" - - -namespace esp8266 -{ - -/* - LEAmDNS -*/ -namespace MDNSImplementation -{ - -/** - CONST STRINGS -*/ -static const char* scpcLocal = "local"; -static const char* scpcServices = "services"; -static const char* scpcDNSSD = "dns-sd"; -static const char* scpcUDP = "udp"; -//static const char* scpcTCP = "tcp"; - -#ifdef MDNS_IP4_SUPPORT -static const char* scpcReverseIP4Domain = "in-addr"; -#endif -#ifdef MDNS_IP6_SUPPORT -static const char* scpcReverseIP6Domain = "ip6"; -#endif -static const char* scpcReverseTopDomain = "arpa"; - -/** - TRANSFER -*/ - - -/** - SENDING -*/ - -/* - MDNSResponder::_sendMDNSMessage - - Unicast responses are prepared and sent directly to the querier. - Multicast responses or queries are transferred to _sendMDNSMessage_Multicast - - Any reply flags in installed services are removed at the end! - -*/ -bool MDNSResponder::_sendMDNSMessage(MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - bool bResult = true; - - if (p_rSendParameter.m_bResponse && - p_rSendParameter.m_bUnicast) // Unicast response -> Send to querier - { - DEBUG_EX_ERR(if (!m_pUDPContext->getRemoteAddress()) - { - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSMessage: MISSING remote address for response!\n")); - }); - IPAddress ipRemote; - ipRemote = m_pUDPContext->getRemoteAddress(); - bResult = ((_prepareMDNSMessage(p_rSendParameter, _getResponseMulticastInterface())) && - (m_pUDPContext->send(ipRemote, m_pUDPContext->getRemotePort()))); - } - else // Multicast response - { - bResult = _sendMDNSMessage_Multicast(p_rSendParameter); - } - - // Finally clear service reply masks - for (stcMDNSService* pService = m_pServices; pService; pService = pService->m_pNext) - { - pService->m_u8ReplyMask = 0; - } - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSMessage: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_sendMDNSMessage_Multicast - - Fills the UDP output buffer (via _prepareMDNSMessage) and sends the buffer - via the selected WiFi interface (Station or AP) -*/ -bool MDNSResponder::_sendMDNSMessage_Multicast(MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - bool bResult = false; - - IPAddress fromIPAddress; - fromIPAddress = _getResponseMulticastInterface(); - m_pUDPContext->setMulticastInterface(fromIPAddress); - -#ifdef MDNS_IP4_SUPPORT - IPAddress toMulticastAddress(DNS_MQUERY_IPV4_GROUP_INIT); -#endif -#ifdef MDNS_IP6_SUPPORT - //TODO: set multicast address - IPAddress toMulticastAddress(DNS_MQUERY_IPV6_GROUP_INIT); -#endif - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSMessage_Multicast: Will send to '%s'.\n"), toMulticastAddress.toString().c_str());); - bResult = ((_prepareMDNSMessage(p_rSendParameter, fromIPAddress)) && - (m_pUDPContext->send(toMulticastAddress, DNS_MQUERY_PORT))); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSMessage_Multicast: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_prepareMDNSMessage - - The MDNS message is composed in a two-step process. - In the first loop 'only' the header informations (mainly number of answers) are collected, - while in the seconds loop, the header and all queries and answers are written to the UDP - output buffer. - -*/ -bool MDNSResponder::_prepareMDNSMessage(MDNSResponder::stcMDNSSendParameter& p_rSendParameter, - IPAddress p_IPAddress) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage\n"));); - bool bResult = true; - - // Prepare header; count answers - stcMDNS_MsgHeader msgHeader(p_rSendParameter.m_u16ID, p_rSendParameter.m_bResponse, 0, p_rSendParameter.m_bAuthorative); - // If this is a response, the answers are anwers, - // else this is a query or probe and the answers go into auth section - uint16_t& ru16Answers = (p_rSendParameter.m_bResponse - ? msgHeader.m_u16ANCount - : msgHeader.m_u16NSCount); - - /** - enuSequence - */ - enum enuSequence - { - Sequence_Count = 0, - Sequence_Send = 1 - }; - - // Two step sequence: 'Count' and 'Send' - for (uint32_t sequence = Sequence_Count; ((bResult) && (sequence <= Sequence_Send)); ++sequence) - { - DEBUG_EX_INFO( - if (Sequence_Send == sequence) - { - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), - (unsigned)msgHeader.m_u16ID, - (unsigned)msgHeader.m_1bQR, (unsigned)msgHeader.m_4bOpcode, (unsigned)msgHeader.m_1bAA, (unsigned)msgHeader.m_1bTC, (unsigned)msgHeader.m_1bRD, - (unsigned)msgHeader.m_1bRA, (unsigned)msgHeader.m_4bRCode, - (unsigned)msgHeader.m_u16QDCount, - (unsigned)msgHeader.m_u16ANCount, - (unsigned)msgHeader.m_u16NSCount, - (unsigned)msgHeader.m_u16ARCount); - } - ); - // Count/send - // Header - bResult = ((Sequence_Count == sequence) - ? true - : _writeMDNSMsgHeader(msgHeader, p_rSendParameter)); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSMsgHeader FAILED!\n"));); - // Questions - for (stcMDNS_RRQuestion* pQuestion = p_rSendParameter.m_pQuestions; ((bResult) && (pQuestion)); pQuestion = pQuestion->m_pNext) - { - ((Sequence_Count == sequence) - ? ++msgHeader.m_u16QDCount - : (bResult = _writeMDNSQuestion(*pQuestion, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSQuestion FAILED!\n"));); - } - - // Answers and authorative answers -#ifdef MDNS_IP4_SUPPORT - if ((bResult) && - (p_rSendParameter.m_u8HostReplyMask & ContentFlag_A)) - { - ((Sequence_Count == sequence) - ? ++ru16Answers - : (bResult = _writeMDNSAnswer_A(p_IPAddress, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_A(A) FAILED!\n"));); - } - if ((bResult) && - (p_rSendParameter.m_u8HostReplyMask & ContentFlag_PTR_IP4)) - { - ((Sequence_Count == sequence) - ? ++ru16Answers - : (bResult = _writeMDNSAnswer_PTR_IP4(p_IPAddress, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_IP4 FAILED!\n"));); - } -#endif -#ifdef MDNS_IP6_SUPPORT - if ((bResult) && - (p_rSendParameter.m_u8HostReplyMask & ContentFlag_AAAA)) - { - ((Sequence_Count == sequence) - ? ++ru16Answers - : (bResult = _writeMDNSAnswer_AAAA(p_IPAddress, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_AAAA(A) FAILED!\n"));); - } - if ((bResult) && - (p_rSendParameter.m_u8HostReplyMask & ContentFlag_PTR_IP6)) - { - ((Sequence_Count == sequence) - ? ++ru16Answers - : (bResult = _writeMDNSAnswer_PTR_IP6(p_IPAddress, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_IP6 FAILED!\n"));); - } -#endif - - for (stcMDNSService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) - { - if ((bResult) && - (pService->m_u8ReplyMask & ContentFlag_PTR_TYPE)) - { - ((Sequence_Count == sequence) - ? ++ru16Answers - : (bResult = _writeMDNSAnswer_PTR_TYPE(*pService, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_TYPE FAILED!\n"));); - } - if ((bResult) && - (pService->m_u8ReplyMask & ContentFlag_PTR_NAME)) - { - ((Sequence_Count == sequence) - ? ++ru16Answers - : (bResult = _writeMDNSAnswer_PTR_NAME(*pService, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_PTR_NAME FAILED!\n"));); - } - if ((bResult) && - (pService->m_u8ReplyMask & ContentFlag_SRV)) - { - ((Sequence_Count == sequence) - ? ++ru16Answers - : (bResult = _writeMDNSAnswer_SRV(*pService, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_SRV(A) FAILED!\n"));); - } - if ((bResult) && - (pService->m_u8ReplyMask & ContentFlag_TXT)) - { - ((Sequence_Count == sequence) - ? ++ru16Answers - : (bResult = _writeMDNSAnswer_TXT(*pService, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_TXT(A) FAILED!\n"));); - } - } // for services - - // Additional answers -#ifdef MDNS_IP4_SUPPORT - bool bNeedsAdditionalAnswerA = false; -#endif -#ifdef MDNS_IP6_SUPPORT - bool bNeedsAdditionalAnswerAAAA = false; -#endif - for (stcMDNSService* pService = m_pServices; ((bResult) && (pService)); pService = pService->m_pNext) - { - if ((bResult) && - (pService->m_u8ReplyMask & ContentFlag_PTR_NAME) && // If PTR_NAME is requested, AND - (!(pService->m_u8ReplyMask & ContentFlag_SRV))) // NOT SRV -> add SRV as additional answer - { - ((Sequence_Count == sequence) - ? ++msgHeader.m_u16ARCount - : (bResult = _writeMDNSAnswer_SRV(*pService, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_SRV(B) FAILED!\n"));); - } - if ((bResult) && - (pService->m_u8ReplyMask & ContentFlag_PTR_NAME) && // If PTR_NAME is requested, AND - (!(pService->m_u8ReplyMask & ContentFlag_TXT))) // NOT TXT -> add TXT as additional answer - { - ((Sequence_Count == sequence) - ? ++msgHeader.m_u16ARCount - : (bResult = _writeMDNSAnswer_TXT(*pService, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_TXT(B) FAILED!\n"));); - } - if ((pService->m_u8ReplyMask & (ContentFlag_PTR_NAME | ContentFlag_SRV)) || // If service instance name or SRV OR - (p_rSendParameter.m_u8HostReplyMask & (ContentFlag_A | ContentFlag_AAAA))) // any host IP address is requested - { -#ifdef MDNS_IP4_SUPPORT - if ((bResult) && - (!(p_rSendParameter.m_u8HostReplyMask & ContentFlag_A))) // Add IP4 address - { - bNeedsAdditionalAnswerA = true; - } -#endif -#ifdef MDNS_IP6_SUPPORT - if ((bResult) && - (!(p_rSendParameter.m_u8HostReplyMask & ContentFlag_AAAA))) // Add IP6 address - { - bNeedsAdditionalAnswerAAAA = true; - } -#endif - } - } // for services - - // Answer A needed? -#ifdef MDNS_IP4_SUPPORT - if ((bResult) && - (bNeedsAdditionalAnswerA)) - { - ((Sequence_Count == sequence) - ? ++msgHeader.m_u16ARCount - : (bResult = _writeMDNSAnswer_A(p_IPAddress, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_A(B) FAILED!\n"));); - } -#endif -#ifdef MDNS_IP6_SUPPORT - // Answer AAAA needed? - if ((bResult) && - (bNeedsAdditionalAnswerAAAA)) - { - ((Sequence_Count == sequence) - ? ++msgHeader.m_u16ARCount - : (bResult = _writeMDNSAnswer_AAAA(p_IPAddress, p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: _writeMDNSAnswer_AAAA(B) FAILED!\n"));); - } -#endif - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: Loop %i FAILED!\n"), sequence);); - } // for sequence - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _prepareMDNSMessage: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_sendMDNSServiceQuery - - Creates and sends a PTR query for the given service domain. - -*/ -bool MDNSResponder::_sendMDNSServiceQuery(const MDNSResponder::stcMDNSServiceQuery& p_ServiceQuery) -{ - - return _sendMDNSQuery(p_ServiceQuery.m_ServiceTypeDomain, DNS_RRTYPE_PTR); -} - -/* - MDNSResponder::_sendMDNSQuery - - Creates and sends a query for the given domain and query type. - -*/ -bool MDNSResponder::_sendMDNSQuery(const MDNSResponder::stcMDNS_RRDomain& p_QueryDomain, - uint16_t p_u16QueryType, - stcMDNSServiceQuery::stcAnswer* p_pKnownAnswers /*= 0*/) -{ - - 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_Header.m_Attributes.m_u16Type = p_u16QueryType; - // It seems, that some mDNS implementations don't support 'unicast response' questions... - sendParameter.m_pQuestions->m_Header.m_Attributes.m_u16Class = (/*0x8000 |*/ DNS_RRCLASS_IN); // /*Unicast &*/ INternet - - // TODO: Add knwon answer to the query - (void)p_pKnownAnswers; - - bResult = _sendMDNSMessage(sendParameter); - } // else: FAILED to alloc question - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _sendMDNSQuery: FAILED to alloc question!\n"));); - return bResult; -} - -/** - HELPERS -*/ - -/** - RESOURCE RECORDS -*/ - -/* - MDNSResponder::_readRRQuestion - - Reads a question (eg. MyESP._http._tcp.local ANY IN) from the UPD input buffer. - -*/ -bool MDNSResponder::_readRRQuestion(MDNSResponder::stcMDNS_RRQuestion& p_rRRQuestion) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRQuestion\n"));); - - bool bResult = false; - - if ((bResult = _readRRHeader(p_rRRQuestion.m_Header))) - { - // Extract unicast flag from class field - p_rRRQuestion.m_bUnicast = (p_rRRQuestion.m_Header.m_Attributes.m_u16Class & 0x8000); - p_rRRQuestion.m_Header.m_Attributes.m_u16Class &= (~0x8000); - - DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRQuestion ")); - _printRRDomain(p_rRRQuestion.m_Header.m_Domain); - DEBUG_OUTPUT.printf_P(PSTR(" Type:0x%04X Class:0x%04X %s\n"), (unsigned)p_rRRQuestion.m_Header.m_Attributes.m_u16Type, (unsigned)p_rRRQuestion.m_Header.m_Attributes.m_u16Class, (p_rRRQuestion.m_bUnicast ? "Unicast" : "Multicast")); - ); - } - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRQuestion: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_readRRAnswer - - Reads an answer (eg. _http._tcp.local PTR OP TTL MyESP._http._tcp.local) - from the UDP input buffer. - After reading the domain and type info, the further processing of the answer - is transferred the answer specific reading functions. - Unknown answer types are processed by the generic answer reader (to remove them - from the input buffer). - -*/ -bool MDNSResponder::_readRRAnswer(MDNSResponder::stcMDNS_RRAnswer*& p_rpRRAnswer) -{ - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer\n"));); - - bool bResult = false; - - stcMDNS_RRHeader header; - uint32_t u32TTL; - uint16_t u16RDLength; - if ((_readRRHeader(header)) && - (_udpRead32(u32TTL)) && - (_udpRead16(u16RDLength))) - { - - /* DEBUG_EX_INFO( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer: Reading 0x%04X answer (class:0x%04X, TTL:%u, RDLength:%u) for "), header.m_Attributes.m_u16Type, header.m_Attributes.m_u16Class, u32TTL, u16RDLength); - _printRRDomain(header.m_Domain); - DEBUG_OUTPUT.printf_P(PSTR("\n")); - );*/ - - switch (header.m_Attributes.m_u16Type & (~0x8000)) // Topmost bit might carry 'cache flush' flag - { -#ifdef MDNS_IP4_SUPPORT - case DNS_RRTYPE_A: - p_rpRRAnswer = new stcMDNS_RRAnswerA(header, u32TTL); - bResult = _readRRAnswerA(*(stcMDNS_RRAnswerA*&)p_rpRRAnswer, u16RDLength); - break; -#endif - case DNS_RRTYPE_PTR: - p_rpRRAnswer = new stcMDNS_RRAnswerPTR(header, u32TTL); - bResult = _readRRAnswerPTR(*(stcMDNS_RRAnswerPTR*&)p_rpRRAnswer, u16RDLength); - break; - case DNS_RRTYPE_TXT: - p_rpRRAnswer = new stcMDNS_RRAnswerTXT(header, u32TTL); - bResult = _readRRAnswerTXT(*(stcMDNS_RRAnswerTXT*&)p_rpRRAnswer, u16RDLength); - break; -#ifdef MDNS_IP6_SUPPORT - case DNS_RRTYPE_AAAA: - p_rpRRAnswer = new stcMDNS_RRAnswerAAAA(header, u32TTL); - bResult = _readRRAnswerAAAA(*(stcMDNS_RRAnswerAAAA*&)p_rpRRAnswer, u16RDLength); - break; -#endif - case DNS_RRTYPE_SRV: - p_rpRRAnswer = new stcMDNS_RRAnswerSRV(header, u32TTL); - bResult = _readRRAnswerSRV(*(stcMDNS_RRAnswerSRV*&)p_rpRRAnswer, u16RDLength); - break; - default: - p_rpRRAnswer = new stcMDNS_RRAnswerGeneric(header, u32TTL); - bResult = _readRRAnswerGeneric(*(stcMDNS_RRAnswerGeneric*&)p_rpRRAnswer, u16RDLength); - break; - } - DEBUG_EX_INFO( - if ((bResult) && - (p_rpRRAnswer)) - { - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer: ")); - _printRRDomain(p_rpRRAnswer->m_Header.m_Domain); - DEBUG_OUTPUT.printf_P(PSTR(" Type:0x%04X Class:0x%04X TTL:%u, RDLength:%u "), p_rpRRAnswer->m_Header.m_Attributes.m_u16Type, p_rpRRAnswer->m_Header.m_Attributes.m_u16Class, p_rpRRAnswer->m_u32TTL, u16RDLength); - switch (header.m_Attributes.m_u16Type & (~0x8000)) // Topmost bit might carry 'cache flush' flag - { -#ifdef MDNS_IP4_SUPPORT - case DNS_RRTYPE_A: - DEBUG_OUTPUT.printf_P(PSTR("A IP:%s"), ((stcMDNS_RRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); - break; -#endif - case DNS_RRTYPE_PTR: - DEBUG_OUTPUT.printf_P(PSTR("PTR ")); - _printRRDomain(((stcMDNS_RRAnswerPTR*&)p_rpRRAnswer)->m_PTRDomain); - break; - case DNS_RRTYPE_TXT: - { - size_t stTxtLength = ((stcMDNS_RRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_strLength(); - char* pTxts = new char[stTxtLength]; - if (pTxts) - { - ((stcMDNS_RRAnswerTXT*&)p_rpRRAnswer)->m_Txts.c_str(pTxts); - DEBUG_OUTPUT.printf_P(PSTR("TXT(%u) %s"), stTxtLength, pTxts); - delete[] pTxts; - } - break; - } -#ifdef MDNS_IP6_SUPPORT - case DNS_RRTYPE_AAAA: - DEBUG_OUTPUT.printf_P(PSTR("AAAA IP:%s"), ((stcMDNS_RRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str()); - break; -#endif - case DNS_RRTYPE_SRV: - DEBUG_OUTPUT.printf_P(PSTR("SRV Port:%u "), ((stcMDNS_RRAnswerSRV*&)p_rpRRAnswer)->m_u16Port); - _printRRDomain(((stcMDNS_RRAnswerSRV*&)p_rpRRAnswer)->m_SRVDomain); - break; - default: - DEBUG_OUTPUT.printf_P(PSTR("generic ")); - break; - } - DEBUG_OUTPUT.printf_P(PSTR("\n")); - } - else - { - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer: FAILED to read specific answer of type 0x%04X!\n"), p_rpRRAnswer->m_Header.m_Attributes.m_u16Type); - } - ); // DEBUG_EX_INFO - } - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswer: FAILED!\n"));); - return bResult; -} - -#ifdef MDNS_IP4_SUPPORT -/* - MDNSResponder::_readRRAnswerA -*/ -bool MDNSResponder::_readRRAnswerA(MDNSResponder::stcMDNS_RRAnswerA& p_rRRAnswerA, - uint16_t p_u16RDLength) -{ - - uint32_t u32IP4Address; - bool bResult = ((MDNS_IP4_SIZE == p_u16RDLength) && - (_udpReadBuffer((unsigned char*)&u32IP4Address, MDNS_IP4_SIZE)) && - ((p_rRRAnswerA.m_IPAddress = IPAddress(u32IP4Address)))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerA: FAILED!\n"));); - return bResult; -} -#endif - -/* - MDNSResponder::_readRRAnswerPTR -*/ -bool MDNSResponder::_readRRAnswerPTR(MDNSResponder::stcMDNS_RRAnswerPTR& p_rRRAnswerPTR, - uint16_t p_u16RDLength) -{ - - bool bResult = ((p_u16RDLength) && - (_readRRDomain(p_rRRAnswerPTR.m_PTRDomain))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerPTR: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_readRRAnswerTXT - - Read TXT items from a buffer like 4c#=15ff=20 -*/ -bool MDNSResponder::_readRRAnswerTXT(MDNSResponder::stcMDNS_RRAnswerTXT& p_rRRAnswerTXT, - uint16_t p_u16RDLength) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: RDLength:%u\n"), p_u16RDLength);); - bool bResult = true; - - p_rRRAnswerTXT.clear(); - if (p_u16RDLength) - { - bResult = false; - - unsigned char* pucBuffer = new unsigned char[p_u16RDLength]; - if (pucBuffer) - { - if (_udpReadBuffer(pucBuffer, p_u16RDLength)) - { - bResult = true; - - const unsigned char* pucCursor = pucBuffer; - while ((pucCursor < (pucBuffer + p_u16RDLength)) && - (bResult)) - { - bResult = false; - - stcMDNSServiceTxt* pTxt = 0; - unsigned char ucLength = *pucCursor++; // Length of the next txt item - if (ucLength) - { - DEBUG_EX_INFO( - static char sacBuffer[64]; *sacBuffer = 0; - uint8_t u8MaxLength = ((ucLength > (sizeof(sacBuffer) - 1)) ? (sizeof(sacBuffer) - 1) : ucLength); - os_strncpy(sacBuffer, (const char*)pucCursor, u8MaxLength); sacBuffer[u8MaxLength] = 0; - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: Item(%u): %s\n"), ucLength, sacBuffer); - ); - - unsigned char* pucEqualSign = (unsigned char*)os_strchr((const char*)pucCursor, '='); // Position of the '=' sign - unsigned char ucKeyLength; - if ((pucEqualSign) && - ((ucKeyLength = (pucEqualSign - pucCursor)))) - { - unsigned char ucValueLength = (ucLength - (pucEqualSign - pucCursor + 1)); - bResult = (((pTxt = new stcMDNSServiceTxt)) && - (pTxt->setKey((const char*)pucCursor, ucKeyLength)) && - (pTxt->setValue((const char*)(pucEqualSign + 1), ucValueLength))); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: INVALID TXT format (No '=')!\n"));); - } - pucCursor += ucLength; - } - else // no/zero length TXT - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: TXT answer contains no items.\n"));); - bResult = true; - } - - if ((bResult) && - (pTxt)) // Everythings fine so far - { - // Link TXT item to answer TXTs - pTxt->m_pNext = p_rRRAnswerTXT.m_Txts.m_pTxts; - p_rRRAnswerTXT.m_Txts.m_pTxts = pTxt; - } - else // At least no TXT (migth be OK, if length was 0) OR an error - { - if (!bResult) - { - DEBUG_EX_ERR( - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: FAILED to read TXT item!\n")); - DEBUG_OUTPUT.printf_P(PSTR("RData dump:\n")); - _udpDump((m_pUDPContext->tell() - p_u16RDLength), p_u16RDLength); - DEBUG_OUTPUT.printf_P(PSTR("\n")); - ); - } - if (pTxt) - { - delete pTxt; - pTxt = 0; - } - p_rRRAnswerTXT.clear(); - } - } // while - - DEBUG_EX_ERR( - if (!bResult) // Some failure - { - DEBUG_OUTPUT.printf_P(PSTR("RData dump:\n")); - _udpDump((m_pUDPContext->tell() - p_u16RDLength), p_u16RDLength); - DEBUG_OUTPUT.printf_P(PSTR("\n")); - } - ); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: FAILED to read TXT content!\n"));); - } - // Clean up - delete[] pucBuffer; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: FAILED to alloc buffer for TXT content!\n"));); - } - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: WARNING! No content!\n"));); - } - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerTXT: FAILED!\n"));); - return bResult; -} - -#ifdef MDNS_IP6_SUPPORT -bool MDNSResponder::_readRRAnswerAAAA(MDNSResponder::stcMDNS_RRAnswerAAAA& p_rRRAnswerAAAA, - uint16_t p_u16RDLength) -{ - bool bResult = false; - // TODO: Implement - return bResult; -} -#endif - -/* - MDNSResponder::_readRRAnswerSRV -*/ -bool MDNSResponder::_readRRAnswerSRV(MDNSResponder::stcMDNS_RRAnswerSRV& p_rRRAnswerSRV, - uint16_t p_u16RDLength) -{ - - bool bResult = (((3 * sizeof(uint16_t)) < p_u16RDLength) && - (_udpRead16(p_rRRAnswerSRV.m_u16Priority)) && - (_udpRead16(p_rRRAnswerSRV.m_u16Weight)) && - (_udpRead16(p_rRRAnswerSRV.m_u16Port)) && - (_readRRDomain(p_rRRAnswerSRV.m_SRVDomain))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerSRV: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_readRRAnswerGeneric -*/ -bool MDNSResponder::_readRRAnswerGeneric(MDNSResponder::stcMDNS_RRAnswerGeneric& p_rRRAnswerGeneric, - uint16_t p_u16RDLength) -{ - bool bResult = (0 == p_u16RDLength); - - p_rRRAnswerGeneric.clear(); - if (((p_rRRAnswerGeneric.m_u16RDLength = p_u16RDLength)) && - ((p_rRRAnswerGeneric.m_pu8RDData = new unsigned char[p_rRRAnswerGeneric.m_u16RDLength]))) - { - - bResult = _udpReadBuffer(p_rRRAnswerGeneric.m_pu8RDData, p_rRRAnswerGeneric.m_u16RDLength); - } - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAnswerGeneric: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_readRRHeader -*/ -bool MDNSResponder::_readRRHeader(MDNSResponder::stcMDNS_RRHeader& p_rRRHeader) -{ - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRHeader\n"));); - - bool bResult = ((_readRRDomain(p_rRRHeader.m_Domain)) && - (_readRRAttributes(p_rRRHeader.m_Attributes))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRHeader: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_readRRDomain - - Reads a (maybe multilevel compressed) domain from the UDP input buffer. - -*/ -bool MDNSResponder::_readRRDomain(MDNSResponder::stcMDNS_RRDomain& p_rRRDomain) -{ - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain\n"));); - - bool bResult = ((p_rRRDomain.clear()) && - (_readRRDomain_Loop(p_rRRDomain, 0))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_readRRDomain_Loop - - Reads a domain from the UDP input buffer. For every compression level, the functions - calls itself recursively. To avoid endless recursion because of malformed MDNS records, - the maximum recursion depth is set by MDNS_DOMAIN_MAX_REDIRCTION. - -*/ -bool MDNSResponder::_readRRDomain_Loop(MDNSResponder::stcMDNS_RRDomain& p_rRRDomain, - uint8_t p_u8Depth) -{ - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u)\n"), p_u8Depth);); - - bool bResult = false; - - if (MDNS_DOMAIN_MAX_REDIRCTION >= p_u8Depth) - { - bResult = true; - - uint8_t u8Len = 0; - do - { - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): Offset:%u p0:%02x\n"), p_u8Depth, m_pUDPContext->tell(), m_pUDPContext->peek());); - _udpRead8(u8Len); - - if (u8Len & MDNS_DOMAIN_COMPRESS_MARK) - { - // Compressed label(s) - uint16_t u16Offset = ((u8Len & ~MDNS_DOMAIN_COMPRESS_MARK) << 8); // Implicit BE to LE conversion! - _udpRead8(u8Len); - u16Offset |= u8Len; - - if (m_pUDPContext->isValidOffset(u16Offset)) - { - size_t stCurrentPosition = m_pUDPContext->tell(); // Prepare return from recursion - - //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): Redirecting from %u to %u!\n"), p_u8Depth, stCurrentPosition, u16Offset);); - m_pUDPContext->seek(u16Offset); - if (_readRRDomain_Loop(p_rRRDomain, p_u8Depth + 1)) // Do recursion - { - //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): Succeeded to read redirected label! Returning to %u\n"), p_u8Depth, stCurrentPosition);); - m_pUDPContext->seek(stCurrentPosition); // Restore after recursion - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): FAILED to read redirected label!\n"), p_u8Depth);); - bResult = false; - } - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): INVALID offset in redirection!\n"), p_u8Depth);); - bResult = false; - } - break; - } - else - { - // Normal (uncompressed) label (maybe '\0' only) - if (MDNS_DOMAIN_MAXLENGTH > (p_rRRDomain.m_u16NameLength + u8Len)) - { - // Add length byte - p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength] = u8Len; - ++(p_rRRDomain.m_u16NameLength); - if (u8Len) // Add name - { - if ((bResult = _udpReadBuffer((unsigned char*) & (p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength]), u8Len))) - { - /* DEBUG_EX_INFO( - p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength + u8Len] = 0; // Closing '\0' for printing - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): Domain label (%u): %s\n"), p_u8Depth, (unsigned)(p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength - 1]), &(p_rRRDomain.m_acName[p_rRRDomain.m_u16NameLength])); - );*/ - - p_rRRDomain.m_u16NameLength += u8Len; - } - } - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(2) offset:%u p0:%x\n"), m_pUDPContext->tell(), m_pUDPContext->peek());); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): ERROR! Domain name too long (%u + %u)!\n"), p_u8Depth, p_rRRDomain.m_u16NameLength, u8Len);); - bResult = false; - break; - } - } - } while ((bResult) && - (0 != u8Len)); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRDomain_Loop(%u): ERROR! Too many redirections!\n"), p_u8Depth);); - } - return bResult; -} - -/* - MDNSResponder::_readRRAttributes -*/ -bool MDNSResponder::_readRRAttributes(MDNSResponder::stcMDNS_RRAttributes& p_rRRAttributes) -{ - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAttributes\n"));); - - bool bResult = ((_udpRead16(p_rRRAttributes.m_u16Type)) && - (_udpRead16(p_rRRAttributes.m_u16Class))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readRRAttributes: FAILED!\n"));); - return bResult; -} - - -/* - DOMAIN NAMES -*/ - -/* - MDNSResponder::_buildDomainForHost - - Builds a MDNS host domain (eg. esp8266.local) for the given hostname. - -*/ -bool MDNSResponder::_buildDomainForHost(const char* p_pcHostname, - MDNSResponder::stcMDNS_RRDomain& p_rHostDomain) const -{ - - p_rHostDomain.clear(); - bool bResult = ((p_pcHostname) && - (*p_pcHostname) && - (p_rHostDomain.addLabel(p_pcHostname)) && - (p_rHostDomain.addLabel(scpcLocal)) && - (p_rHostDomain.addLabel(0))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForHost: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_buildDomainForDNSSD - - Builds the '_services._dns-sd._udp.local' domain. - Used while detecting generic service enum question (DNS-SD) and answering these questions. - -*/ -bool MDNSResponder::_buildDomainForDNSSD(MDNSResponder::stcMDNS_RRDomain& p_rDNSSDDomain) const -{ - - p_rDNSSDDomain.clear(); - bool bResult = ((p_rDNSSDDomain.addLabel(scpcServices, true)) && - (p_rDNSSDDomain.addLabel(scpcDNSSD, true)) && - (p_rDNSSDDomain.addLabel(scpcUDP, true)) && - (p_rDNSSDDomain.addLabel(scpcLocal)) && - (p_rDNSSDDomain.addLabel(0))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForDNSSD: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_buildDomainForService - - Builds the domain for the given service (eg. _http._tcp.local or - MyESP._http._tcp.local (if p_bIncludeName is set)). - -*/ -bool MDNSResponder::_buildDomainForService(const MDNSResponder::stcMDNSService& p_Service, - bool p_bIncludeName, - MDNSResponder::stcMDNS_RRDomain& p_rServiceDomain) const -{ - - p_rServiceDomain.clear(); - bool bResult = (((!p_bIncludeName) || - (p_rServiceDomain.addLabel(p_Service.m_pcName))) && - (p_rServiceDomain.addLabel(p_Service.m_pcService, true)) && - (p_rServiceDomain.addLabel(p_Service.m_pcProtocol, true)) && - (p_rServiceDomain.addLabel(scpcLocal)) && - (p_rServiceDomain.addLabel(0))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForService: FAILED!\n"));); - return bResult; -} - -/* - MDNSResponder::_buildDomainForService - - Builds the domain for the given service properties (eg. _http._tcp.local). - The usual prepended '_' are added, if missing in the input strings. - -*/ -bool MDNSResponder::_buildDomainForService(const char* p_pcService, - const char* p_pcProtocol, - MDNSResponder::stcMDNS_RRDomain& p_rServiceDomain) const -{ - - p_rServiceDomain.clear(); - bool bResult = ((p_pcService) && - (p_pcProtocol) && - (p_rServiceDomain.addLabel(p_pcService, ('_' != *p_pcService))) && - (p_rServiceDomain.addLabel(p_pcProtocol, ('_' != *p_pcProtocol))) && - (p_rServiceDomain.addLabel(scpcLocal)) && - (p_rServiceDomain.addLabel(0))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForService: FAILED for (%s.%s)!\n"), (p_pcService ? : "-"), (p_pcProtocol ? : "-"));); - return bResult; -} - -#ifdef MDNS_IP4_SUPPORT -/* - MDNSResponder::_buildDomainForReverseIP4 - - The IP4 address is stringized by printing the four address bytes into a char buffer in reverse order - and adding 'in-addr.arpa' (eg. 012.789.456.123.in-addr.arpa). - Used while detecting reverse IP4 questions and answering these -*/ -bool MDNSResponder::_buildDomainForReverseIP4(IPAddress p_IP4Address, - MDNSResponder::stcMDNS_RRDomain& p_rReverseIP4Domain) const -{ - - bool bResult = true; - - p_rReverseIP4Domain.clear(); - - char acBuffer[32]; - for (int i = MDNS_IP4_SIZE; ((bResult) && (i >= 1)); --i) - { - itoa(p_IP4Address[i - 1], acBuffer, 10); - bResult = p_rReverseIP4Domain.addLabel(acBuffer); - } - bResult = ((bResult) && - (p_rReverseIP4Domain.addLabel(scpcReverseIP4Domain)) && - (p_rReverseIP4Domain.addLabel(scpcReverseTopDomain)) && - (p_rReverseIP4Domain.addLabel(0))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _buildDomainForReverseIP4: FAILED!\n"));); - return bResult; -} -#endif - -#ifdef MDNS_IP6_SUPPORT -/* - MDNSResponder::_buildDomainForReverseIP6 - - Used while detecting reverse IP6 questions and answering these -*/ -bool MDNSResponder::_buildDomainForReverseIP6(IPAddress p_IP4Address, - MDNSResponder::stcMDNS_RRDomain& p_rReverseIP6Domain) const -{ - // TODO: Implement - return false; -} -#endif - - -/* - UDP -*/ - -/* - MDNSResponder::_udpReadBuffer -*/ -bool MDNSResponder::_udpReadBuffer(unsigned char* p_pBuffer, - size_t p_stLength) -{ - - bool bResult = ((m_pUDPContext) && - (true/*m_pUDPContext->getSize() > p_stLength*/) && - (p_pBuffer) && - (p_stLength) && - ((p_stLength == m_pUDPContext->read((char*)p_pBuffer, p_stLength)))); - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _udpReadBuffer: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_udpRead8 -*/ -bool MDNSResponder::_udpRead8(uint8_t& p_ru8Value) -{ - - return _udpReadBuffer((unsigned char*)&p_ru8Value, sizeof(p_ru8Value)); -} - -/* - MDNSResponder::_udpRead16 -*/ -bool MDNSResponder::_udpRead16(uint16_t& p_ru16Value) -{ - - bool bResult = false; - - if (_udpReadBuffer((unsigned char*)&p_ru16Value, sizeof(p_ru16Value))) - { - p_ru16Value = lwip_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 = lwip_ntohl(p_ru32Value); - bResult = true; - } - return bResult; -} - -/* - MDNSResponder::_udpAppendBuffer -*/ -bool MDNSResponder::_udpAppendBuffer(const unsigned char* p_pcBuffer, - size_t p_stLength) -{ - - bool bResult = ((m_pUDPContext) && - (p_pcBuffer) && - (p_stLength) && - (p_stLength == m_pUDPContext->append((const char*)p_pcBuffer, p_stLength))); - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _udpAppendBuffer: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_udpAppend8 -*/ -bool MDNSResponder::_udpAppend8(uint8_t p_u8Value) -{ - - return (_udpAppendBuffer((unsigned char*)&p_u8Value, sizeof(p_u8Value))); -} - -/* - MDNSResponder::_udpAppend16 -*/ -bool MDNSResponder::_udpAppend16(uint16_t p_u16Value) -{ - - p_u16Value = lwip_htons(p_u16Value); - return (_udpAppendBuffer((unsigned char*)&p_u16Value, sizeof(p_u16Value))); -} - -/* - MDNSResponder::_udpAppend32 -*/ -bool MDNSResponder::_udpAppend32(uint32_t p_u32Value) -{ - - p_u32Value = lwip_htonl(p_u32Value); - return (_udpAppendBuffer((unsigned char*)&p_u32Value, sizeof(p_u32Value))); -} - -#ifdef DEBUG_ESP_MDNS_RESPONDER -/* - MDNSResponder::_udpDump -*/ -bool MDNSResponder::_udpDump(bool p_bMovePointer /*= false*/) -{ - - const uint8_t cu8BytesPerLine = 16; - - uint32_t u32StartPosition = m_pUDPContext->tell(); - DEBUG_OUTPUT.println("UDP Context Dump:"); - uint32_t u32Counter = 0; - uint8_t u8Byte = 0; - - while (_udpRead8(u8Byte)) - { - DEBUG_OUTPUT.printf_P(PSTR("%02x %s"), u8Byte, ((++u32Counter % cu8BytesPerLine) ? "" : "\n")); - } - DEBUG_OUTPUT.printf_P(PSTR("%sDone: %u bytes\n"), (((u32Counter) && (u32Counter % cu8BytesPerLine)) ? "\n" : ""), u32Counter); - - if (!p_bMovePointer) // Restore - { - m_pUDPContext->seek(u32StartPosition); - } - return true; -} - -/* - MDNSResponder::_udpDump -*/ -bool MDNSResponder::_udpDump(unsigned p_uOffset, - unsigned p_uLength) -{ - - if ((m_pUDPContext) && - (m_pUDPContext->isValidOffset(p_uOffset))) - { - unsigned uCurrentPosition = m_pUDPContext->tell(); // Remember start position - - m_pUDPContext->seek(p_uOffset); - uint8_t u8Byte; - for (unsigned u = 0; ((u < p_uLength) && (_udpRead8(u8Byte))); ++u) - { - DEBUG_OUTPUT.printf_P(PSTR("%02x "), u8Byte); - } - // Return to start position - m_pUDPContext->seek(uCurrentPosition); - } - return true; -} -#endif - - -/** - READ/WRITE MDNS STRUCTS -*/ - -/* - MDNSResponder::_readMDNSMsgHeader - - Read a MDNS header from the UDP input buffer. - | 8 | 8 | 8 | 8 | - 00| Identifier | Flags & Codes | - 01| Question count | Answer count | - 02| NS answer count | Ad answer count | - - All 16-bit and 32-bit elements need to be translated form network coding to host coding (done in _udpRead16 and _udpRead32) - In addition, bitfield memory order is undefined in C standard (GCC doesn't order them in the coded direction...), so they - need some mapping here -*/ -bool MDNSResponder::_readMDNSMsgHeader(MDNSResponder::stcMDNS_MsgHeader& p_rMsgHeader) -{ - - bool bResult = false; - - uint8_t u8B1; - uint8_t u8B2; - if ((_udpRead16(p_rMsgHeader.m_u16ID)) && - (_udpRead8(u8B1)) && - (_udpRead8(u8B2)) && - (_udpRead16(p_rMsgHeader.m_u16QDCount)) && - (_udpRead16(p_rMsgHeader.m_u16ANCount)) && - (_udpRead16(p_rMsgHeader.m_u16NSCount)) && - (_udpRead16(p_rMsgHeader.m_u16ARCount))) - { - - p_rMsgHeader.m_1bQR = (u8B1 & 0x80); // Query/Responde flag - p_rMsgHeader.m_4bOpcode = (u8B1 & 0x78); // Operation code (0: Standard query, others ignored) - p_rMsgHeader.m_1bAA = (u8B1 & 0x04); // Authorative answer - p_rMsgHeader.m_1bTC = (u8B1 & 0x02); // Truncation flag - p_rMsgHeader.m_1bRD = (u8B1 & 0x01); // Recursion desired - - p_rMsgHeader.m_1bRA = (u8B2 & 0x80); // Recursion available - p_rMsgHeader.m_3bZ = (u8B2 & 0x70); // Zero - p_rMsgHeader.m_4bRCode = (u8B2 & 0x0F); // Response code - - /* DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readMDNSMsgHeader: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), - (unsigned)p_rMsgHeader.m_u16ID, - (unsigned)p_rMsgHeader.m_1bQR, (unsigned)p_rMsgHeader.m_4bOpcode, (unsigned)p_rMsgHeader.m_1bAA, (unsigned)p_rMsgHeader.m_1bTC, (unsigned)p_rMsgHeader.m_1bRD, - (unsigned)p_rMsgHeader.m_1bRA, (unsigned)p_rMsgHeader.m_4bRCode, - (unsigned)p_rMsgHeader.m_u16QDCount, - (unsigned)p_rMsgHeader.m_u16ANCount, - (unsigned)p_rMsgHeader.m_u16NSCount, - (unsigned)p_rMsgHeader.m_u16ARCount););*/ - bResult = true; - } - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _readMDNSMsgHeader: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_write8 -*/ -bool MDNSResponder::_write8(uint8_t p_u8Value, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - return ((_udpAppend8(p_u8Value)) && - (p_rSendParameter.shiftOffset(sizeof(p_u8Value)))); -} - -/* - MDNSResponder::_write16 -*/ -bool MDNSResponder::_write16(uint16_t p_u16Value, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - return ((_udpAppend16(p_u16Value)) && - (p_rSendParameter.shiftOffset(sizeof(p_u16Value)))); -} - -/* - MDNSResponder::_write32 -*/ -bool MDNSResponder::_write32(uint32_t p_u32Value, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - return ((_udpAppend32(p_u32Value)) && - (p_rSendParameter.shiftOffset(sizeof(p_u32Value)))); -} - -/* - MDNSResponder::_writeMDNSMsgHeader - - Write MDNS header to the UDP output buffer. - - All 16-bit and 32-bit elements need to be translated form host coding to network coding (done in _udpAppend16 and _udpAppend32) - In addition, bitfield memory order is undefined in C standard (GCC doesn't order them in the coded direction...), so they - need some mapping here -*/ -bool MDNSResponder::_writeMDNSMsgHeader(const MDNSResponder::stcMDNS_MsgHeader& p_MsgHeader, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - /* DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSMsgHeader: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), - (unsigned)p_MsgHeader.m_u16ID, - (unsigned)p_MsgHeader.m_1bQR, (unsigned)p_MsgHeader.m_4bOpcode, (unsigned)p_MsgHeader.m_1bAA, (unsigned)p_MsgHeader.m_1bTC, (unsigned)p_MsgHeader.m_1bRD, - (unsigned)p_MsgHeader.m_1bRA, (unsigned)p_MsgHeader.m_4bRCode, - (unsigned)p_MsgHeader.m_u16QDCount, - (unsigned)p_MsgHeader.m_u16ANCount, - (unsigned)p_MsgHeader.m_u16NSCount, - (unsigned)p_MsgHeader.m_u16ARCount););*/ - - uint8_t u8B1((p_MsgHeader.m_1bQR << 7) | (p_MsgHeader.m_4bOpcode << 3) | (p_MsgHeader.m_1bAA << 2) | (p_MsgHeader.m_1bTC << 1) | (p_MsgHeader.m_1bRD)); - uint8_t u8B2((p_MsgHeader.m_1bRA << 7) | (p_MsgHeader.m_3bZ << 4) | (p_MsgHeader.m_4bRCode)); - bool bResult = ((_write16(p_MsgHeader.m_u16ID, p_rSendParameter)) && - (_write8(u8B1, p_rSendParameter)) && - (_write8(u8B2, p_rSendParameter)) && - (_write16(p_MsgHeader.m_u16QDCount, p_rSendParameter)) && - (_write16(p_MsgHeader.m_u16ANCount, p_rSendParameter)) && - (_write16(p_MsgHeader.m_u16NSCount, p_rSendParameter)) && - (_write16(p_MsgHeader.m_u16ARCount, p_rSendParameter))); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSMsgHeader: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_writeRRAttributes -*/ -bool MDNSResponder::_writeMDNSRRAttributes(const MDNSResponder::stcMDNS_RRAttributes& p_Attributes, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - bool bResult = ((_write16(p_Attributes.m_u16Type, p_rSendParameter)) && - (_write16(p_Attributes.m_u16Class, p_rSendParameter))); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSRRAttributes: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_writeMDNSRRDomain -*/ -bool MDNSResponder::_writeMDNSRRDomain(const MDNSResponder::stcMDNS_RRDomain& p_Domain, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - bool bResult = ((_udpAppendBuffer((const unsigned char*)p_Domain.m_acName, p_Domain.m_u16NameLength)) && - (p_rSendParameter.shiftOffset(p_Domain.m_u16NameLength))); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSRRDomain: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_writeMDNSHostDomain - - Write a host domain to the UDP output buffer. - If the domain record is part of the answer, the records length is - prepended (p_bPrependRDLength is set). - - A very simple form of name compression is applied here: - If the domain is written to the UDP output buffer, the write offset is stored - together with a domain id (the pointer) in a p_rSendParameter substructure (cache). - If the same domain (pointer) should be written to the UDP output later again, - the old offset is retrieved from the cache, marked as a compressed domain offset - and written to the output buffer. - -*/ -bool MDNSResponder::_writeMDNSHostDomain(const char* p_pcHostname, - bool p_bPrependRDLength, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV' - uint16_t u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)p_pcHostname, false); - - stcMDNS_RRDomain hostDomain; - bool bResult = (u16CachedDomainOffset - // Found cached domain -> mark as compressed domain - ? ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset - ((!p_bPrependRDLength) || - (_write16(2, p_rSendParameter))) && // Length of 'Cxxx' - (_write8(((u16CachedDomainOffset >> 8) | MDNS_DOMAIN_COMPRESS_MARK), p_rSendParameter)) && // Compression mark (and offset) - (_write8((uint8_t)(u16CachedDomainOffset & 0xFF), p_rSendParameter))) - // No cached domain -> add this domain to cache and write full domain name - : ((_buildDomainForHost(p_pcHostname, hostDomain)) && // eg. esp8266.local - ((!p_bPrependRDLength) || - (_write16(hostDomain.m_u16NameLength, p_rSendParameter))) && // RDLength (if needed) - (p_rSendParameter.addDomainCacheItem((const void*)p_pcHostname, false, p_rSendParameter.m_u16Offset)) && - (_writeMDNSRRDomain(hostDomain, p_rSendParameter)))); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSHostDomain: FAILED!\n")); - }); - return bResult; - -} - -/* - MDNSResponder::_writeMDNSServiceDomain - - Write a service domain to the UDP output buffer. - If the domain record is part of the answer, the records length is - prepended (p_bPrependRDLength is set). - - A very simple form of name compression is applied here: see '_writeMDNSHostDomain' - The cache differentiates of course between service domains which includes - the instance name (p_bIncludeName is set) and thoose who don't. - -*/ -bool MDNSResponder::_writeMDNSServiceDomain(const MDNSResponder::stcMDNSService& p_Service, - bool p_bIncludeName, - bool p_bPrependRDLength, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - - // The 'skip-compression' version is handled in '_writeMDNSAnswer_SRV' - uint16_t u16CachedDomainOffset = p_rSendParameter.findCachedDomainOffset((const void*)&p_Service, p_bIncludeName); - - stcMDNS_RRDomain serviceDomain; - bool bResult = (u16CachedDomainOffset - // Found cached domain -> mark as compressed domain - ? ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset - ((!p_bPrependRDLength) || - (_write16(2, p_rSendParameter))) && // Lenght of 'Cxxx' - (_write8(((u16CachedDomainOffset >> 8) | MDNS_DOMAIN_COMPRESS_MARK), p_rSendParameter)) && // Compression mark (and offset) - (_write8((uint8_t)(u16CachedDomainOffset & 0xFF), p_rSendParameter))) - // No cached domain -> add this domain to cache and write full domain name - : ((_buildDomainForService(p_Service, p_bIncludeName, serviceDomain)) && // eg. MyESP._http._tcp.local - ((!p_bPrependRDLength) || - (_write16(serviceDomain.m_u16NameLength, p_rSendParameter))) && // RDLength (if needed) - (p_rSendParameter.addDomainCacheItem((const void*)&p_Service, p_bIncludeName, p_rSendParameter.m_u16Offset)) && - (_writeMDNSRRDomain(serviceDomain, p_rSendParameter)))); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSServiceDomain: FAILED!\n")); - }); - return bResult; - -} - -/* - MDNSResponder::_writeMDNSQuestion - - Write a MDNS question to the UDP output buffer - - QNAME (host/service domain, eg. esp8266.local) - QTYPE (16bit, eg. ANY) - QCLASS (16bit, eg. IN) - -*/ -bool MDNSResponder::_writeMDNSQuestion(MDNSResponder::stcMDNS_RRQuestion& p_Question, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSQuestion\n"));); - - bool bResult = ((_writeMDNSRRDomain(p_Question.m_Header.m_Domain, p_rSendParameter)) && - (_writeMDNSRRAttributes(p_Question.m_Header.m_Attributes, p_rSendParameter))); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSQuestion: FAILED!\n")); - }); - return bResult; - -} - - -#ifdef MDNS_IP4_SUPPORT -/* - MDNSResponder::_writeMDNSAnswer_A - - Write a MDNS A answer to the UDP output buffer. - - NAME (var, host/service domain, eg. esp8266.local - TYPE (16bit, eg. A) - CLASS (16bit, eg. IN) - TTL (32bit, eg. 120) - RDLENGTH (16bit, eg 4) - RDATA (var, eg. 123.456.789.012) - - eg. esp8266.local A 0x8001 120 4 123.456.789.012 - Ref: http://www.zytrax.com/books/dns/ch8/a.html -*/ -bool MDNSResponder::_writeMDNSAnswer_A(IPAddress p_IPAddress, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_A (%s)\n"), p_IPAddress.toString().c_str());); - - stcMDNS_RRAttributes attributes(DNS_RRTYPE_A, - ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - const unsigned char aucIPAddress[MDNS_IP4_SIZE] = { p_IPAddress[0], p_IPAddress[1], p_IPAddress[2], p_IPAddress[3] }; - bool bResult = ((_writeMDNSHostDomain(m_pcHostname, false, p_rSendParameter)) && - (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS - (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) && // TTL - (_write16(MDNS_IP4_SIZE, p_rSendParameter)) && // RDLength - (_udpAppendBuffer(aucIPAddress, MDNS_IP4_SIZE)) && // RData - (p_rSendParameter.shiftOffset(MDNS_IP4_SIZE))); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_A: FAILED!\n")); - }); - return bResult; - -} - -/* - MDNSResponder::_writeMDNSAnswer_PTR_IP4 - - Write a MDNS reverse IP4 PTR answer to the UDP output buffer. - See: '_writeMDNSAnswer_A' - - eg. 012.789.456.123.in-addr.arpa PTR 0x8001 120 15 esp8266.local - Used while answering reverse IP4 questions -*/ -bool MDNSResponder::_writeMDNSAnswer_PTR_IP4(IPAddress p_IPAddress, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_IP4 (%s)\n"), p_IPAddress.toString().c_str());); - - stcMDNS_RRDomain reverseIP4Domain; - stcMDNS_RRAttributes attributes(DNS_RRTYPE_PTR, - ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - stcMDNS_RRDomain hostDomain; - bool bResult = ((_buildDomainForReverseIP4(p_IPAddress, reverseIP4Domain)) && // 012.789.456.123.in-addr.arpa - (_writeMDNSRRDomain(reverseIP4Domain, p_rSendParameter)) && - (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS - (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) && // TTL - (_writeMDNSHostDomain(m_pcHostname, true, p_rSendParameter))); // RDLength & RData (host domain, eg. esp8266.local) - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_IP4: FAILED!\n")); - }); - return bResult; -} -#endif - -/* - MDNSResponder::_writeMDNSAnswer_PTR_TYPE - - Write a MDNS PTR answer to the UDP output buffer. - See: '_writeMDNSAnswer_A' - - PTR all-services -> service type - eg. _services._dns-sd._udp.local PTR 0x8001 5400 xx _http._tcp.local - http://www.zytrax.com/books/dns/ch8/ptr.html -*/ -bool MDNSResponder::_writeMDNSAnswer_PTR_TYPE(MDNSResponder::stcMDNSService& p_rService, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_TYPE\n"));); - - stcMDNS_RRDomain dnssdDomain; - stcMDNS_RRDomain serviceDomain; - stcMDNS_RRAttributes attributes(DNS_RRTYPE_PTR, DNS_RRCLASS_IN); // No cache flush! only INternet - bool bResult = ((_buildDomainForDNSSD(dnssdDomain)) && // _services._dns-sd._udp.local - (_writeMDNSRRDomain(dnssdDomain, p_rSendParameter)) && - (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS - (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL - (_writeMDNSServiceDomain(p_rService, false, true, p_rSendParameter))); // RDLength & RData (service domain, eg. _http._tcp.local) - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_TYPE: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_writeMDNSAnswer_PTR_NAME - - Write a MDNS PTR answer to the UDP output buffer. - See: '_writeMDNSAnswer_A' - - PTR service type -> service name - eg. _http.tcp.local PTR 0x8001 120 xx myESP._http._tcp.local - http://www.zytrax.com/books/dns/ch8/ptr.html -*/ -bool MDNSResponder::_writeMDNSAnswer_PTR_NAME(MDNSResponder::stcMDNSService& p_rService, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_NAME\n"));); - - stcMDNS_RRAttributes attributes(DNS_RRTYPE_PTR, DNS_RRCLASS_IN); // No cache flush! only INternet - bool bResult = ((_writeMDNSServiceDomain(p_rService, false, false, p_rSendParameter)) && // _http._tcp.local - (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS - (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL - (_writeMDNSServiceDomain(p_rService, true, true, p_rSendParameter))); // RDLength & RData (service domain, eg. MyESP._http._tcp.local) - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_NAME: FAILED!\n")); - }); - return bResult; -} - - -/* - MDNSResponder::_writeMDNSAnswer_TXT - - Write a MDNS TXT answer to the UDP output buffer. - See: '_writeMDNSAnswer_A' - - The TXT items in the RDATA block are 'length byte encoded': [len]vardata - - eg. myESP._http._tcp.local TXT 0x8001 120 4 c#=1 - http://www.zytrax.com/books/dns/ch8/txt.html -*/ -bool MDNSResponder::_writeMDNSAnswer_TXT(MDNSResponder::stcMDNSService& p_rService, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_TXT\n"));); - - bool bResult = false; - - stcMDNS_RRAttributes attributes(DNS_RRTYPE_TXT, - ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - - if ((_collectServiceTxts(p_rService)) && - (_writeMDNSServiceDomain(p_rService, true, false, p_rSendParameter)) && // MyESP._http._tcp.local - (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS - (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL - (_write16(p_rService.m_Txts.length(), p_rSendParameter))) // RDLength - { - - bResult = true; - // RData Txts - for (stcMDNSServiceTxt* pTxt = p_rService.m_Txts.m_pTxts; ((bResult) && (pTxt)); pTxt = pTxt->m_pNext) - { - unsigned char ucLengthByte = pTxt->length(); - bResult = ((_udpAppendBuffer((unsigned char*)&ucLengthByte, sizeof(ucLengthByte))) && // Length - (p_rSendParameter.shiftOffset(sizeof(ucLengthByte))) && - ((size_t)os_strlen(pTxt->m_pcKey) == m_pUDPContext->append(pTxt->m_pcKey, os_strlen(pTxt->m_pcKey))) && // Key - (p_rSendParameter.shiftOffset((size_t)os_strlen(pTxt->m_pcKey))) && - (1 == m_pUDPContext->append("=", 1)) && // = - (p_rSendParameter.shiftOffset(1)) && - ((!pTxt->m_pcValue) || - (((size_t)os_strlen(pTxt->m_pcValue) == m_pUDPContext->append(pTxt->m_pcValue, os_strlen(pTxt->m_pcValue))) && // Value - (p_rSendParameter.shiftOffset((size_t)os_strlen(pTxt->m_pcValue)))))); - - DEBUG_EX_ERR(if (!bResult) - { - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_TXT: FAILED to write %sTxt %s=%s!\n"), (pTxt->m_bTemp ? "temp. " : ""), (pTxt->m_pcKey ? : "?"), (pTxt->m_pcValue ? : "?")); - }); - } - } - _releaseTempServiceTxts(p_rService); - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_TXT: FAILED!\n")); - }); - return bResult; -} - -#ifdef MDNS_IP6_SUPPORT -/* - MDNSResponder::_writeMDNSAnswer_AAAA - - Write a MDNS AAAA answer to the UDP output buffer. - See: '_writeMDNSAnswer_A' - - eg. esp8266.local AAAA 0x8001 120 16 xxxx::xx - http://www.zytrax.com/books/dns/ch8/aaaa.html -*/ -bool MDNSResponder::_writeMDNSAnswer_AAAA(IPAddress p_IPAddress, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_AAAA\n"));); - - stcMDNS_RRAttributes attributes(DNS_RRTYPE_AAAA, - ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - bool bResult = ((_writeMDNSHostDomain(m_pcHostname, false, p_rSendParameter)) && // esp8266.local - (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS - (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) && // TTL - (_write16(MDNS_IP6_SIZE, p_rSendParameter)) && // RDLength - (false /*TODO: IP6 version of: _udpAppendBuffer((uint32_t)p_IPAddress, MDNS_IP4_SIZE)*/)); // RData - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_AAAA: FAILED!\n")); - }); - return bResult; -} - -/* - MDNSResponder::_writeMDNSAnswer_PTR_IP6 - - Write a MDNS reverse IP6 PTR answer to the UDP output buffer. - See: '_writeMDNSAnswer_A' - - eg. xxxx::xx.in6.arpa PTR 0x8001 120 15 esp8266.local - Used while answering reverse IP6 questions -*/ -bool MDNSResponder::_writeMDNSAnswer_PTR_IP6(IPAddress p_IPAddress, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_IP6\n"));); - - stcMDNS_RRDomain reverseIP6Domain; - stcMDNS_RRAttributes attributes(DNS_RRTYPE_PTR, - ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - bool bResult = ((_buildDomainForReverseIP6(p_IPAddress, reverseIP6Domain)) && // xxxx::xx.ip6.arpa - (_writeMDNSRRDomain(reverseIP6Domain, p_rSendParameter)) && - (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS - (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_HOST_TTL), p_rSendParameter)) && // TTL - (_writeMDNSHostDomain(m_pcHostname, true, p_rSendParameter))); // RDLength & RData (host domain, eg. esp8266.local) - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_PTR_IP6: FAILED!\n")); - }); - return bResult; -} -#endif - -/* - MDNSResponder::_writeMDNSAnswer_SRV - - eg. MyESP._http.tcp.local SRV 0x8001 120 0 0 60068 esp8266.local - http://www.zytrax.com/books/dns/ch8/srv.html ???? Include instance name ???? -*/ -bool MDNSResponder::_writeMDNSAnswer_SRV(MDNSResponder::stcMDNSService& p_rService, - MDNSResponder::stcMDNSSendParameter& p_rSendParameter) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_SRV\n"));); - - uint16_t u16CachedDomainOffset = (p_rSendParameter.m_bLegacyQuery - ? 0 - : p_rSendParameter.findCachedDomainOffset((const void*)m_pcHostname, false)); - - stcMDNS_RRAttributes attributes(DNS_RRTYPE_SRV, - ((p_rSendParameter.m_bCacheFlush ? 0x8000 : 0) | DNS_RRCLASS_IN)); // Cache flush? & INternet - stcMDNS_RRDomain hostDomain; - bool bResult = ((_writeMDNSServiceDomain(p_rService, true, false, p_rSendParameter)) && // MyESP._http._tcp.local - (_writeMDNSRRAttributes(attributes, p_rSendParameter)) && // TYPE & CLASS - (_write32((p_rSendParameter.m_bUnannounce ? 0 : MDNS_SERVICE_TTL), p_rSendParameter)) && // TTL - (!u16CachedDomainOffset - // No cache for domain name (or no compression allowed) - ? ((_buildDomainForHost(m_pcHostname, hostDomain)) && - (_write16((sizeof(uint16_t /*Prio*/) + // RDLength - sizeof(uint16_t /*Weight*/) + - sizeof(uint16_t /*Port*/) + - hostDomain.m_u16NameLength), p_rSendParameter)) && // Domain length - (_write16(MDNS_SRV_PRIORITY, p_rSendParameter)) && // Priority - (_write16(MDNS_SRV_WEIGHT, p_rSendParameter)) && // Weight - (_write16(p_rService.m_u16Port, p_rSendParameter)) && // Port - (p_rSendParameter.addDomainCacheItem((const void*)m_pcHostname, false, p_rSendParameter.m_u16Offset)) && - (_writeMDNSRRDomain(hostDomain, p_rSendParameter))) // Host, eg. esp8266.local - // Cache available for domain - : ((MDNS_DOMAIN_COMPRESS_MARK > ((u16CachedDomainOffset >> 8) & ~MDNS_DOMAIN_COMPRESS_MARK)) && // Valid offset - (_write16((sizeof(uint16_t /*Prio*/) + // RDLength - sizeof(uint16_t /*Weight*/) + - sizeof(uint16_t /*Port*/) + - 2), p_rSendParameter)) && // Length of 'C0xx' - (_write16(MDNS_SRV_PRIORITY, p_rSendParameter)) && // Priority - (_write16(MDNS_SRV_WEIGHT, p_rSendParameter)) && // Weight - (_write16(p_rService.m_u16Port, p_rSendParameter)) && // Port - (_write8(((u16CachedDomainOffset >> 8) | MDNS_DOMAIN_COMPRESS_MARK), p_rSendParameter)) && // Compression mark (and offset) - (_write8((uint8_t)u16CachedDomainOffset, p_rSendParameter))))); // Offset - - DEBUG_EX_ERR(if (!bResult) -{ - DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _writeMDNSAnswer_SRV: FAILED!\n")); - }); - return bResult; -} - -} // namespace MDNSImplementation - -} // namespace esp8266 - - - - - - diff --git a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_lwIPdefs.h b/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_lwIPdefs.h deleted file mode 100644 index a3bcc4b370..0000000000 --- a/libraries/ESP8266mDNS/OLDmDNS/LEAmDNS_lwIPdefs.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - 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 MDNS_LWIPDEFS_H -#define MDNS_LWIPDEFS_H - -#include -#if LWIP_VERSION_MAJOR == 1 - -#include // DNS_RRTYPE_xxx - -// cherry pick from lwip1 dns.c/mdns.c source files: -#define DNS_MQUERY_PORT 5353 -#define DNS_MQUERY_IPV4_GROUP_INIT IPAddress(224,0,0,251) /* resolver1.opendns.com */ -#define DNS_RRCLASS_ANY 255 /* any class */ - -#else // lwIP > 1 - -#include // DNS_RRTYPE_xxx, DNS_MQUERY_PORT - -#endif - -#endif // MDNS_LWIPDEFS_H From c6db2e52e8592ce5d9fc666ab9033848a04f11f9 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sat, 23 May 2020 13:24:27 +0200 Subject: [PATCH 011/152] wip --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 4 +-- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 17 +++------- .../ESP8266mDNS/src/LEAmDNS2_Backbone.cpp | 33 ++----------------- 3 files changed, 8 insertions(+), 46 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index c8c05082c5..ce6d2cef85 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -246,15 +246,13 @@ clsLEAMDNSHost::~clsLEAMDNSHost(void) */ bool clsLEAMDNSHost::begin(const char* p_pcHostName, - netif* p_pNetIf, clsLEAMDNSHost::fnProbeResultCallback p_fnCallback /*= 0*/) { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s, netif: %u)\n"), _DH(), (p_pcHostName ? : "_"), (p_pNetIf ? netif_get_index(p_pNetIf) : 0));); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s)\n"), _DH(), (p_pcHostName ? : "_"));); bool bResult = false; if (!((bResult = ((setHostName(p_pcHostName)) && - ((m_pNetIf = p_pNetIf)) && (_joinMulticastGroups()) && (p_fnCallback ? setProbeResultCallback(p_fnCallback) : true) && ((m_pUDPContext = _allocBackbone())) && diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index bdcc05e21e..c643c9296c 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -267,7 +267,7 @@ class clsLEAMDNSHost /** list */ - using list = std::list; + //using list = std::list; // File: ..._Backbone /** @@ -291,15 +291,16 @@ class clsLEAMDNSHost UdpContext* m_pUDPContext; bool m_bDelayUDPProcessing; uint32_t m_u32DelayedDatagrams; - list m_HostList; + //list m_HostList; + clsLEAMDNSHost m_uniqueHost; bool _allocUDPContext(void); bool _releaseUDPContext(void); bool _processUDPInput(void); - const clsLEAMDNSHost* _findHost(netif* p_pNetIf) const; - clsLEAMDNSHost* _findHost(netif* p_pNetIf); + const clsLEAMDNSHost* _findHost() const { return &m_uniqueHost; } + clsLEAMDNSHost* _findHost() { return &m_uniqueHost; } const char* _DH(void) const; }; @@ -1256,12 +1257,6 @@ class clsLEAMDNSHost // Later call 'update()' in every 'loop' to run the process loop // (probing, announcing, responding, ...) // If no callback is given, the (maybe) already installed callback stays set - bool begin(const char* p_pcHostName, - netif* p_pNetIf, - fnProbeResultCallback p_fnCallback = 0); - bool begin(const char* p_pcHostName, - WiFiMode_t p_WiFiMode, - fnProbeResultCallback p_fnCallback = 0); bool begin(const char* p_pcHostName, fnProbeResultCallback p_fnCallback = 0); @@ -1640,8 +1635,6 @@ class clsLEAMDNSHost protected: - netif* m_pNetIf; - typeNetIfState m_NetIfState; UdpContext* m_pUDPContext; char* m_pcHostName; diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp index d3d4849a54..5bdf85d1d3 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp @@ -218,10 +218,9 @@ bool clsLEAMDNSHost::clsBackbone::_processUDPInput(void) while ((m_pUDPContext) && (m_pUDPContext->next())) { - netif* pNetIf = m_pUDPContext->getInputNetif();//ip_current_input_netif(); // Probably changed inbetween!!!! + netif* pNetIf = m_pUDPContext->getInputNetif();//ip_current_input_netif(); // Probably changed in between!!!! clsLEAMDNSHost* pHost = 0; - if ((pNetIf) && - ((pHost = _findHost(pNetIf)))) + if ((pHost = _findHost(pNetIf))) { DEBUG_EX_INFO( if (u32LoopCounter++) @@ -255,34 +254,6 @@ bool clsLEAMDNSHost::clsBackbone::_processUDPInput(void) return bResult; } -/* - clsLEAmDNS2_Host::clsBackbone::_findHost -*/ -const clsLEAMDNSHost* clsLEAMDNSHost::clsBackbone::_findHost(netif* p_pNetIf) const -{ - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _findHost\n"), _DH());); - const clsLEAMDNSHost* pResult = 0; - for (const clsLEAMDNSHost* pHost : m_HostList) - { - if ((p_pNetIf) && - (pHost->m_pNetIf == p_pNetIf)) - { - pResult = pHost; - break; - } - } - return pResult; -} - -/* - MDNSResponder::_findHost -*/ -clsLEAMDNSHost* clsLEAMDNSHost::clsBackbone::_findHost(netif* p_pNetIf) -{ - return (clsLEAMDNSHost*)(((const clsLEAMDNSHost::clsBackbone*)this)->_findHost(p_pNetIf)); -} - - /* MISC */ From b5fefcfc2815e29c5526fe9df0d1b307dd47fb81 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sat, 23 May 2020 18:26:52 +0200 Subject: [PATCH 012/152] fix for lwIP-v1 --- cores/esp8266/IPAddress.h | 1 + libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 2 +- libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 2 +- libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h | 2 ++ 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/IPAddress.h b/cores/esp8266/IPAddress.h index cdd4b2499f..6fcbee95fe 100644 --- a/cores/esp8266/IPAddress.h +++ b/cores/esp8266/IPAddress.h @@ -42,6 +42,7 @@ #define CONST /* nothing: lwIP-v1 does not use const */ #define ip4_addr_netcmp ip_addr_netcmp #define netif_dhcp_data(netif) ((netif)->dhcp) +#define netif_get_index(netif) ((u8_t)((netif)->num + 1)) #else // lwIP-v2+ #define CONST const #if !LWIP_IPV6 diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index c8c05082c5..1553c3df4c 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -941,7 +941,7 @@ bool clsLEAMDNSHost::_leaveMulticastGroups(void) #ifdef MDNS_IPV4_SUPPORT ip_addr_t multicast_addr_V4 = DNS_MQUERY_IPV4_GROUP_INIT; #if LWIP_VERSION_MAJOR == 1 - if (ERR_OK != igmp_leavegroup(ip_2_ip4(&m_rNetIf.ip_addr), ip_2_ip4(&multicast_addr_V4))) + if (ERR_OK != igmp_leavegroup(ip_2_ip4(&m_pNetIf->ip_addr), ip_2_ip4(&multicast_addr_V4))) #else if (ERR_OK != igmp_leavegroup_netif(m_pNetIf, ip_2_ip4(&multicast_addr_V4))) #endif diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 36b5ece554..0165d2962d 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -624,7 +624,7 @@ IPAddress clsLEAMDNSHost::_getResponderIPAddress(enuIPProtocolType p_IPProtocolT if (enuIPProtocolType::V4 == p_IPProtocolType) { #if LWIP_VERSION_MAJOR == 1 - ipResponder = ip_2_ip4(m_rNetIf.ip_addr); + ipResponder = ip_2_ip4(m_pNetIf->ip_addr); #else ipResponder = netif_ip_addr4(m_pNetIf); #endif diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h b/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h index b28ea2a26e..54d7f36f48 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h @@ -35,6 +35,8 @@ #define DNS_MQUERY_IPV4_GROUP_INIT IPAddress(224,0,0,251) /* resolver1.opendns.com */ #define DNS_RRCLASS_ANY 255 /* any class */ +struct netif* netif_get_by_index(u8_t idx); + #else // lwIP > 1 #include // DNS_RRTYPE_xxx, DNS_MQUERY_PORT From 1272926c867415e787ad1438ea87ca7cd533712e Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sat, 23 May 2020 18:37:04 +0200 Subject: [PATCH 013/152] wip --- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index 151555d15c..ee2f4f63b0 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -50,20 +50,6 @@ clsLEAMDNSHost_Legacy::~clsLEAMDNSHost_Legacy(void) */ -/* - clsLEAMDNSHost_Legacy::begin - -*/ -bool clsLEAMDNSHost_Legacy::begin(const char* p_pcHostname) -{ - bool bResult = (((!(WIFI_STA & (WiFiMode_t)wifi_get_opmode())) - || (addHostForNetIf(p_pcHostname, netif_get_by_index(WIFI_STA)))) - && ((!(WIFI_AP & (WiFiMode_t)wifi_get_opmode())) - || (addHostForNetIf(p_pcHostname, netif_get_by_index(WIFI_AP))))); - return ((bResult) - && (0 != m_HostInformations.size())); -} - /* clsLEAMDNSHost_Legacy::begin (String) @@ -130,19 +116,18 @@ bool clsLEAMDNSHost_Legacy::end(void) NEW! */ -bool clsLEAMDNSHost_Legacy::addHostForNetIf(const char* p_pcHostname, - netif* p_pNetIf) +bool clsLEAMDNSHost_Legacy::addHostForNetIf(const char* p_pcHostname) { - clsLEAMDNSHost* pHost = 0; + bool bResult = true; - if (((pHost = new esp8266::experimental::clsLEAMDNSHost)) - && (!((pHost->begin(p_pcHostname, p_pNetIf /*, default callback*/)) + clsLEAMDNSHost* pHost = &esp8266::experimental::clsLEAMDNSHost::clsBackbone::sm_pBackbone.m_uniqueHost; + + if ((!((pHost->begin(p_pcHostname /*, default callback*/)) && (m_HostInformations.push_back(stcHostInformation(pHost)), true)))) { - delete pHost; - pHost = 0; + bResult = false; } - return (0 != pHost); + return bResult; } /* From 5ffbe293bb64b72dc2e9c1132b9a74e1a30077c6 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 24 May 2020 11:39:38 +0200 Subject: [PATCH 014/152] wip --- cores/esp8266/IPAddress.h | 4 + libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 221 +++--------------- .../ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 19 +- .../ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp | 17 +- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h | 3 +- 5 files changed, 46 insertions(+), 218 deletions(-) diff --git a/cores/esp8266/IPAddress.h b/cores/esp8266/IPAddress.h index 6fcbee95fe..1260db39ee 100644 --- a/cores/esp8266/IPAddress.h +++ b/cores/esp8266/IPAddress.h @@ -50,6 +50,10 @@ struct ip_addr: ipv4_addr { }; #endif // !LWIP_IPV6 #endif // lwIP-v2+ +// to display a netif id with printf: +#define NETIFID_STR "%c%c%d" +#define NETIFID_VAL(netif) (netif)->name[0], (netif)->name[1], netif_get_index(netif) + // A class to make it easier to handle and pass around IP addresses // IPv6 update: // IPAddress is now a decorator class for lwIP's ip_addr_t diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 81901dfed5..fb8aaf0c56 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -171,15 +171,14 @@ const char* clsLEAMDNSHost::indexDomainName(const char* p_pcDomainName, */ // static -bool clsLEAMDNSHost::setNetIfHostName(netif* p_pNetIf, - const char* p_pcHostName) +bool clsLEAMDNSHost::setNetIfHostName(const char* p_pcHostName) { - if ((p_pNetIf) && - (p_pcHostName)) - { - netif_set_hostname(p_pNetIf, (char*)p_pcHostName); // LWIP 1.x forces 'char*' instead of 'const char*' - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[mDNS] setNetIfHostName host name: %s!\n"), p_pcHostName);); - } + if (p_pcHostName) + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf)) + { + netif_set_hostname(pNetIf, p_pcHostName); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[mDNS] setNetIfHostName host name: %s on " NETIFID_STR "!\n"), p_pcHostName, NETIFID_VAL(pNetIf));); + } return true; } @@ -214,9 +213,7 @@ const char* clsLEAMDNSHost::clsConsts::pcReverseTopDomain = "arpa"; */ clsLEAMDNSHost::clsLEAMDNSHost(void) - : m_pNetIf(0), - m_NetIfState(static_cast(enuNetIfState::None)), - m_pUDPContext(0), + : m_pUDPContext(0), m_pcHostName(0), m_pcDefaultInstanceName(0), m_ProbeInformation() @@ -239,9 +236,9 @@ clsLEAMDNSHost::~clsLEAMDNSHost(void) */ /* - clsLEAmDNS2_Host::begin (hostname, netif, probe_callback) + clsLEAmDNS2_Host::begin (hostname, probe_callback) - Creates a new mDNS host (adding the netif to the multicast groups), + setup global mDNS (adding all netif to the multicast groups), sets up the instance data (hostname, ...) and starts the probing process */ @@ -264,46 +261,6 @@ bool clsLEAMDNSHost::begin(const char* p_pcHostName, return bResult; } -/* - clsLEAmDNS2_Host::begin (hostname, probe_callback) - -*/ -bool clsLEAMDNSHost::begin(const char* p_pcHostName, - clsLEAMDNSHost::fnProbeResultCallback p_fnCallback /*= 0*/) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s)\n"), _DH(), (p_pcHostName ? : "_"));); - - return begin(p_pcHostName, (WiFiMode_t)wifi_get_opmode(), p_fnCallback); -} - -/* - clsLEAmDNS2_Host::begin (hostname, WiFiMode, probe_callback) - -*/ -bool clsLEAMDNSHost::begin(const char* p_pcHostName, - WiFiMode_t p_WiFiMode, - clsLEAMDNSHost::fnProbeResultCallback p_fnCallback /*= 0*/) -{ - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin(%s, opmode: %u)\n"), _DH(), (p_pcHostName ? : "_"), (uint32_t)p_WiFiMode);); - - bool bResult = false; - - if (p_WiFiMode == WIFI_STA) - { - bResult = begin(p_pcHostName, netif_get_by_index(WIFI_STA), p_fnCallback); - } - else if (p_WiFiMode == WIFI_AP) - { - bResult = begin(p_pcHostName, netif_get_by_index(WIFI_AP), p_fnCallback); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s begin: FAILED for WiFi mode '%u'! Only 'WIFI_STA' or 'WIFI_AP' is allowed (HostName: %s)!\n"), _DH(), (bResult ? "Succeeded" : "FAILED"), p_WiFiMode, (p_pcHostName ? : "-"));); - } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin: %s for WiFi mode '%u' (HostName: %s)!\n"), _DH(), (bResult ? "Succeeded" : "FAILED"), p_WiFiMode, (p_pcHostName ? : "-"));); - return bResult; -} - /* clsLEAmDNS2_Host::close @@ -747,8 +704,7 @@ bool clsLEAMDNSHost::update(void) //if (clsBackbone::sm_pBackbone->setDelayUDPProcessing(true)) //{ - bResult = ((_checkNetIfState()) && // Any changes in the netif state? - (_updateProbeStatus()) && // Probing and announcing + bResult = ((_updateProbeStatus()) && // Probing and announcing (_checkQueryCache())); // clsBackbone::sm_pBackbone->setDelayUDPProcessing(false); @@ -883,10 +839,8 @@ bool clsLEAMDNSHost::_joinMulticastGroups(void) bool bResult = false; // Join multicast group(s) - if (m_pNetIf) + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf)) { - bResult = true; - #ifdef MDNS_IPV4_SUPPORT ip_addr_t multicast_addr_V4 = DNS_MQUERY_IPV4_GROUP_INIT; if (!(m_pNetIf->flags & NETIF_FLAG_IGMP)) @@ -900,20 +854,29 @@ bool clsLEAMDNSHost::_joinMulticastGroups(void) } } - bResult = ((bResult) && + if ( #if LWIP_VERSION_MAJOR == 1 - (ERR_OK == igmp_joingroup(ip_2_ip4(&m_pNetIf->ip_addr), ip_2_ip4(&multicast_addr_V4)))); + (ERR_OK == igmp_joingroup(&pNetIf->ip_addr, &multicast_addr_V4)) #else - (ERR_OK == igmp_joingroup_netif(m_pNetIf, ip_2_ip4(&multicast_addr_V4)))); + (ERR_OK == igmp_joingroup_netif(pNetIf, ip_2_ip4(&multicast_addr_V4))) #endif - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: igmp_joingroup_netif(%s) FAILED!\n"), _DH(), IPAddress(multicast_addr_V4).toString().c_str());); + ) + { + bResult = true; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: igmp_joingroup_netif(" NETIFID_STR ": %s) FAILED!\n"), + _DH(), NETIFID_VAL(pNetIf), IPAddress(multicast_addr_V4).toString().c_str());); + } #endif #ifdef MDNS_IPV6_SUPPORT ip_addr_t multicast_addr_V6 = DNS_MQUERY_IPV6_GROUP_INIT; bResult = ((bResult) && - (ERR_OK == mld6_joingroup_netif(m_pNetIf, ip_2_ip6(&multicast_addr_V6)))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: mld6_joingroup_netif FAILED!\n"), _DH());); + (ERR_OK == mld6_joingroup_netif(pNetIf, ip_2_ip6(&multicast_addr_V6)))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: mld6_joingroup_netif (" NETIFID_STR ") FAILED!\n"), + _DH(), NETIFID_VAL(pNetIf));); #endif } return bResult; @@ -922,15 +885,14 @@ bool clsLEAMDNSHost::_joinMulticastGroups(void) /* clsLEAmDNS2_Host::_leaveMulticastGroups */ -bool clsLEAMDNSHost::_leaveMulticastGroups(void) +bool clsLEAMDNSHost::_leaveMulticastGroups() { bool bResult = false; - if (m_pNetIf) + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf)) { bResult = true; /* _resetProbeStatus(false); // Stop probing - _releaseQueries(); _releaseServices(); _releaseHostName();*/ @@ -939,9 +901,9 @@ bool clsLEAMDNSHost::_leaveMulticastGroups(void) #ifdef MDNS_IPV4_SUPPORT ip_addr_t multicast_addr_V4 = DNS_MQUERY_IPV4_GROUP_INIT; #if LWIP_VERSION_MAJOR == 1 - if (ERR_OK != igmp_leavegroup(ip_2_ip4(&m_pNetIf->ip_addr), ip_2_ip4(&multicast_addr_V4))) + if (ERR_OK != igmp_leavegroup(ip_2_ip4(pNetIf->ip_addr), ip_2_ip4(&multicast_addr_V4))) #else - if (ERR_OK != igmp_leavegroup_netif(m_pNetIf, ip_2_ip4(&multicast_addr_V4))) + if (ERR_OK != igmp_leavegroup_netif(pNetIf, ip_2_ip4(&multicast_addr_V4))) #endif { DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("\n"));); @@ -949,7 +911,7 @@ bool clsLEAMDNSHost::_leaveMulticastGroups(void) #endif #ifdef MDNS_IPV6_SUPPORT ip_addr_t multicast_addr_V6 = DNS_MQUERY_IPV6_GROUP_INIT; - if (ERR_OK != mld6_leavegroup_netif(m_pNetIf, ip_2_ip6(&multicast_addr_V6)/*&(multicast_addr_V6.u_addr.ip6)*/)) + if (ERR_OK != mld6_leavegroup_netif(pNetIf, ip_2_ip6(&multicast_addr_V6)/*&(multicast_addr_V6.u_addr.ip6)*/)) { DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("\n"));); } @@ -959,123 +921,6 @@ bool clsLEAMDNSHost::_leaveMulticastGroups(void) } -/* - NETIF -*/ - -/* - clsLEAmDNS2_Host::_getNetIfState - - Returns the current netif state. - -*/ -clsLEAMDNSHost::typeNetIfState clsLEAMDNSHost::_getNetIfState(void) const -{ - typeNetIfState curNetIfState = static_cast(enuNetIfState::None); - - if ((m_pNetIf) && - (netif_is_up(m_pNetIf))) - { - curNetIfState |= static_cast(enuNetIfState::IsUp); - - // Check if netif link is up - if ((netif_is_link_up(m_pNetIf)) && - ((m_pNetIf != netif_get_by_index(WIFI_STA)) || - (STATION_GOT_IP == wifi_station_get_connect_status()))) - { - curNetIfState |= static_cast(enuNetIfState::LinkIsUp); - } - -#ifdef MDNS_IPV4_SUPPORT - // Check for IPv4 address - if (_getResponderIPAddress(enuIPProtocolType::V4).isSet()) - { - curNetIfState |= static_cast(enuNetIfState::IPv4); - } -#endif -#ifdef MDNS_IPV6_SUPPORT - // Check for IPv6 address - if (_getResponderIPAddress(enuIPProtocolType::V6).isSet()) - { - curNetIfState |= static_cast(enuNetIfState::IPv6); - } -#endif - } - return curNetIfState; -} - -/* - clsLEAmDNS2_Host::_checkNetIfState - - Checks the netif state. - If eg. a new address appears, the announcing is restarted. - -*/ -bool clsLEAMDNSHost::_checkNetIfState(void) -{ - typeNetIfState curNetIfState; - if (m_NetIfState != ((curNetIfState = _getNetIfState()))) - { - // Some state change happened - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: DID CHANGE NETIF STATE\n\n"), _DH());); - DEBUG_EX_INFO( - if ((curNetIfState & static_cast(enuNetIfState::IsUp)) != (m_NetIfState & static_cast(enuNetIfState::IsUp))) DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Netif is up: %s\n"), _DH(), ((curNetIfState & static_cast(enuNetIfState::IsUp)) ? "YES" : "NO")); - if ((curNetIfState & static_cast(enuNetIfState::LinkIsUp)) != (m_NetIfState & static_cast(enuNetIfState::LinkIsUp))) DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Netif link is up: %s\n"), _DH(), ((curNetIfState & static_cast(enuNetIfState::LinkIsUp)) ? "YES" : "NO")); -#ifdef MDNS_IPV4_SUPPORT - if ((curNetIfState & static_cast(enuNetIfState::IPv4)) != (m_NetIfState & static_cast(enuNetIfState::IPv4))) - { - DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IPv4 address is set: %s\n"), _DH(), ((curNetIfState & static_cast(enuNetIfState::IPv4)) ? "YES" : "NO")); - if (curNetIfState & static_cast(enuNetIfState::IPv4)) - { - DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IPv4 address: %s\n"), _DH(), _getResponderIPAddress(enuIPProtocolType::V4).toString().c_str()); - } - } -#endif -#ifdef MDNS_IPV6_SUPPORT - if ((curNetIfState & static_cast(enuNetIfState::IPv6)) != (m_NetIfState & static_cast(enuNetIfState::IPv6))) - { - DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IPv6 address is set: %s\n"), _DH(), ((curNetIfState & static_cast(enuNetIfState::IPv6)) ? "YES" : "NO")); - if (curNetIfState & static_cast(enuNetIfState::IPv6)) - { - DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IPv6 address: %s\n"), _DH(), _getResponderIPAddress(enuIPProtocolType::V6).toString().c_str()); - } - } -#endif - ); - if ((curNetIfState & static_cast(enuNetIfState::LinkMask)) != (m_NetIfState & static_cast(enuNetIfState::LinkMask))) - { - // Link came up or down - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Link state changed -> restarting\n"), _DH());); - restart(); - } - else if (curNetIfState & static_cast(enuNetIfState::LinkIsUp)) - { - // Link is up (unchanged) - if ((curNetIfState & static_cast(enuNetIfState::IPMask)) != (m_NetIfState & static_cast(enuNetIfState::IPMask))) - { - // IP state changed - // TODO: If just a new IP address was added, a simple re-announcement should be enough - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: IP state changed -> restarting\n"), _DH());); - restart(); - } - } - /* if (enuProbingStatus::Done == m_HostProbeInformation.m_ProbingStatus) { - // Probing is done, prepare to (re)announce host - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Preparing to (re)announce host.\n"));); - //m_HostProbeInformation.m_ProbingStatus = enuProbingStatus::Done; - m_HostProbeInformation.m_u8SentCount = 0; - m_HostProbeInformation.m_Timeout.reset(MDNS_ANNOUNCE_DELAY); - }*/ - m_NetIfState = curNetIfState; - } - - bool bResult = ((curNetIfState & static_cast(enuNetIfState::LinkMask)) && // Continue if Link is UP - (curNetIfState & static_cast(enuNetIfState::IPMask))); // AND has any IP - DEBUG_EX_INFO(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _checkNetIfState: Link is DOWN(%s) or NO IP address(%s)!\n"), _DH(), (curNetIfState & static_cast(enuNetIfState::LinkMask) ? "NO" : "YES"), (curNetIfState & static_cast(enuNetIfState::IPMask) ? "NO" : "YES"));); - return bResult; -} - - /* PROCESSING */ @@ -1089,8 +934,6 @@ bool clsLEAMDNSHost::_processUDPInput(void) bool bResult = _parseMessage(); - /* bResult = ((_checkNetIfState()) && // Any changes in the netif state? - (_parseMessage()));*/ DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s processUDPInput: FAILED!\n"), _DH());); return bResult; diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index a061d77da8..bab5eba3cf 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -206,15 +206,12 @@ bool clsLEAMDNSHost::_parseQuery(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader // Local host check // We're a match for this legacy query, BUT // make sure, that the query comes from a local host -#ifdef MDNS_IPV4_SUPPORT - ip_info IPInfo_Local; -#endif - if ((m_pNetIf) && - (m_pUDPContext) && + if ((m_pUDPContext) && #ifdef MDNS_IPV4_SUPPORT (m_pUDPContext->getRemoteAddress().isV4()) && - ((wifi_get_ip_info(netif_get_index(m_pNetIf), &IPInfo_Local))) && - (ip4_addr_netcmp(ip_2_ip4((const ip_addr_t*)m_pUDPContext->getRemoteAddress()), &IPInfo_Local.ip, &IPInfo_Local.netmask)) + (ip4_addr_netcmp(ip_2_ip4(m_pUDPContext->getRemoteAddress()), + ip_2_ip4(m_pUDPContext->getInputNetif()->ip_addr), + ip_2_ip4(m_pUDPContext->getInputNetif()->netmask)) #else (true) #endif @@ -227,14 +224,6 @@ bool clsLEAMDNSHost::_parseQuery(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader #endif ) { - /* ip_info IPInfo_Local; - ip_info IPInfo_Remote; - if (((IPInfo_Remote.ip.addr = m_pUDPContext->getRemoteAddress())) && - (((wifi_get_ip_info(SOFTAP_IF, &IPInfo_Local)) && - (ip4_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)) && - (ip4_addr_netcmp(&IPInfo_Remote.ip, &IPInfo_Local.ip, &IPInfo_Local.netmask))))) // Remote IP in STATION's subnet - {*/ Serial.println("\n\n\nUNICAST QUERY\n\n"); DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Legacy DNS query from local host %s!\n"), _DH(), m_pUDPContext->getRemoteAddress().toString().c_str());); diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp index bc7316be88..94855d2ab5 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp @@ -40,23 +40,16 @@ namespace experimental clsLEAmDNS2_Host::_DH */ -const char* clsLEAMDNSHost::_DH(const clsLEAMDNSHost::clsService* p_pService /*= 0*/) const +xxxxx const char* clsLEAMDNSHost::_DH(const clsLEAMDNSHost::clsService* p_pService /*= 0*/) const { static char acBuffer[16 + 64]; *acBuffer = 0; - if (m_pNetIf) - { - sprintf_P(acBuffer, PSTR("[mDNS %c%c%u]"), m_pNetIf->name[0], m_pNetIf->name[1], m_pNetIf->num); - if (p_pService) - { - strcat_P(acBuffer, PSTR(">")); - strcat(acBuffer, _service2String(p_pService)); - } - } - else + sprintf_P(acBuffer, PSTR("[mDNS]"); + if (p_pService) { - strcpy_P(acBuffer, PSTR("[mDNS]")); + strcat_P(acBuffer, PSTR(">")); + strcat(acBuffer, _service2String(p_pService)); } return acBuffer; } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h index 9ab1f14d0c..c574cfee08 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h @@ -154,8 +154,7 @@ class clsLEAMDNSHost_Legacy // NEW! The ESP-default network interfaces are 'auto-added' via 'begin' when active // Additional netifs may be added, but if done so after calling 'update' for the // first time, 'notifyAPChange' should be called to restart the probing/announcing process - bool addHostForNetIf(const char* p_pcHostname, - netif* p_pNetIf); + bool addHostForNetIf(const char* p_pcHostname); // Change hostname (probing is restarted) // Caution! The final hostname (after probing) may be different for every host From ad6b4260013639a2cabdf27bda75590464e3c91c Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 24 May 2020 11:50:48 +0200 Subject: [PATCH 015/152] fix for lwIP-1.4 --- libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp | 1 + libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h | 3 +++ 2 files changed, 4 insertions(+) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp index d3d4849a54..4eb45918f9 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp @@ -311,6 +311,7 @@ const char* clsLEAMDNSHost::clsBackbone::_DH(void) const Extracted (and slightly changed) from: https://github.com/yarrick/lwip/blob/master/src/core/netif.c */ +extern "C" struct netif* netif_get_by_index(u8_t idx) { struct netif *netif; diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h b/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h index 54d7f36f48..0aa1712a81 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h @@ -35,6 +35,9 @@ #define DNS_MQUERY_IPV4_GROUP_INIT IPAddress(224,0,0,251) /* resolver1.opendns.com */ #define DNS_RRCLASS_ANY 255 /* any class */ +#ifdef __cplusplus +extern "C" +#endif struct netif* netif_get_by_index(u8_t idx); #else // lwIP > 1 From bae5f6655769529c38e381d4f0b298112050139c Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 24 May 2020 15:05:40 +0200 Subject: [PATCH 016/152] wip --- cores/esp8266/IPAddress.h | 2 +- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 6 ++++ .../ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 11 +++--- .../ESP8266mDNS/src/LEAmDNS2_Backbone.cpp | 34 ++++++++++--------- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 18 +++++++--- 5 files changed, 44 insertions(+), 27 deletions(-) diff --git a/cores/esp8266/IPAddress.h b/cores/esp8266/IPAddress.h index 1260db39ee..327c61f997 100644 --- a/cores/esp8266/IPAddress.h +++ b/cores/esp8266/IPAddress.h @@ -52,7 +52,7 @@ struct ip_addr: ipv4_addr { }; // to display a netif id with printf: #define NETIFID_STR "%c%c%d" -#define NETIFID_VAL(netif) (netif)->name[0], (netif)->name[1], netif_get_index(netif) +#define NETIFID_VAL(netif) (netif)->name[0], (netif)->name[1], netif_get_index((netif)) // A class to make it easier to handle and pass around IP addresses // IPv6 update: diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index c643c9296c..d724a19ba7 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -140,18 +140,24 @@ #ifdef DEBUG_ESP_MDNS_RESPONDER #ifdef DEBUG_ESP_MDNS_INFO #define DEBUG_EX_INFO(A) A +#define DEBUG_EX_INFO_IF(C,A...) do if (C) { A; } while (0) #else #define DEBUG_EX_INFO(A) +#define DEBUG_EX_INFO_IF(C,A...) #endif #ifdef DEBUG_ESP_MDNS_INFO2 #define DEBUG_EX_INFO2(A) A +#define DEBUG_EX_INFO2_IF(C,A...) do if (C) { A; } while (0) #else #define DEBUG_EX_INFO2(A) +#define DEBUG_EX_INFO2_IF(C,A...) #endif #ifdef DEBUG_ESP_MDNS_ERR #define DEBUG_EX_ERR(A) A +#define DEBUG_EX_ERR_IF(C,A...) do if (C) { A; } while (0) #else #define DEBUG_EX_ERR(A) +#define DEBUG_EX_ERR_IF(C,A...) #endif #ifdef DEBUG_ESP_MDNS_TX #define DEBUG_EX_TX(A) A diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 0165d2962d..6ad703d4cc 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -636,15 +636,16 @@ IPAddress clsLEAMDNSHost::_getResponderIPAddress(enuIPProtocolType p_IPProtocolT bool bCheckLinkLocal = true; for (int i = 0; ((!ipResponder.isSet()) && (i < 2)); ++i) // Two loops: First with link-local check, second without { + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf)) for (int idx = 0; idx < LWIP_IPV6_NUM_ADDRESSES; ++idx) { - //DEBUG_EX_INFO(if ip6_addr_isvalid(netif_ip6_addr_state(&m_rNetIf, idx)) DEBUG_OUTPUT.printf_P(PSTR("%s _getResponderIPAddress: Checking IPv6 address %s (LL: %s)\n"), _DH(), IPAddress(netif_ip_addr6(m_pNetIf, idx)).toString().c_str(), (bCheckLinkLocal ? "YES" : "NO"));); - if ((ip6_addr_isvalid(netif_ip6_addr_state(m_pNetIf, idx))) && + //DEBUG_EX_INFO(if ip6_addr_isvalid(netif_ip6_addr_state(&pNetIf, idx)) DEBUG_OUTPUT.printf_P(PSTR("%s _getResponderIPAddress: Checking IPv6 address %s (LL: %s)\n"), _DH(), IPAddress(netif_ip_addr6(pNetIf, idx)).toString().c_str(), (bCheckLinkLocal ? "YES" : "NO"));); + if ((ip6_addr_isvalid(netif_ip6_addr_state(pNetIf, idx))) && (((!bCheckLinkLocal) || - (ip6_addr_islinklocal(netif_ip6_addr(m_pNetIf, idx)))))) + (ip6_addr_islinklocal(netif_ip6_addr(pNetIf, idx)))))) { - //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _getResponderIPAddress: Selected IPv6 address %s (LL: %s)\n"), _DH(), IPAddress(netif_ip_addr6(m_pNetIf, idx)).toString().c_str(), (bCheckLinkLocal ? "YES" : "NO"));); - ipResponder = netif_ip_addr6(m_pNetIf, idx); + //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _getResponderIPAddress: Selected IPv6 address %s (LL: %s)\n"), _DH(), IPAddress(netif_ip_addr6(pNetIf, idx)).toString().c_str(), (bCheckLinkLocal ? "YES" : "NO"));); + ipResponder = netif_ip_addr6(pNetIf, idx); break; } } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp index af3cb5b97c..c5c465e8ad 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp @@ -218,30 +218,32 @@ bool clsLEAMDNSHost::clsBackbone::_processUDPInput(void) while ((m_pUDPContext) && (m_pUDPContext->next())) { - netif* pNetIf = m_pUDPContext->getInputNetif();//ip_current_input_netif(); // Probably changed in between!!!! + netif* pNetIf = m_pUDPContext->getInputNetif(); clsLEAMDNSHost* pHost = 0; if ((pHost = _findHost(pNetIf))) { - DEBUG_EX_INFO( - if (u32LoopCounter++) - { - DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Multi-Loop (%u)!\n"), _DH(), u32LoopCounter); - if ((remoteIPAddr.isSet()) && - (remoteIPAddr != m_pUDPContext->getRemoteAddress())) - { - DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Changed IP address %s->%s!\n"), _DH(), remoteIPAddr.toString().c_str(), m_pUDPContext->getRemoteAddress().toString().c_str()); - } - } - remoteIPAddr = m_pUDPContext->getRemoteAddress(); - ); + DEBUG_EX_INFO_IF(u32LoopCounter++, + DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Multi-Loop (%u)!\n"), _DH(), u32LoopCounter); + DEBUG_EX_INFO_IF((remoteIPAddr.isSet()) && (remoteIPAddr != m_pUDPContext->getRemoteAddress()), + DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Changed IP address %s->%s!\n"), + _DH(), + remoteIPAddr.toString().c_str(), + m_pUDPContext->getRemoteAddress().toString().c_str()))); + DEBUG_EX_INFO(remoteIPAddr = m_pUDPContext->getRemoteAddress()); + bResult = pHost->_processUDPInput(); - DEBUG_EX_INFO2(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: FAILED to process UDP input!\n"), _DH());); - DEBUG_EX_ERR(if ((-1) != m_pUDPContext->peek()) DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: !!!! CONTENT LEFT IN UDP BUFFER !!!!\n"), _DH());); + DEBUG_EX_INFO2_IF(!bResult, + DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: FAILED to process UDP input!\n"), _DH())); + DEBUG_EX_ERR_IF((-1) != m_pUDPContext->peek(), + DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: !!!! CONTENT LEFT IN UDP BUFFER !!!!\n"), + _DH())); } else { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Received UDP datagramm for unused netif at index: %u\n"), _DH(), (pNetIf ? netif_get_index(pNetIf) : (-1)));); + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Received UDP datagramm for unused netif at index: %u\n"), + _DH(), + (pNetIf ? netif_get_index(pNetIf) : (-1)))); } m_pUDPContext->flush(); } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index ee2f4f63b0..551a754cdf 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -119,14 +119,22 @@ bool clsLEAMDNSHost_Legacy::end(void) bool clsLEAMDNSHost_Legacy::addHostForNetIf(const char* p_pcHostname) { bool bResult = true; - - clsLEAMDNSHost* pHost = &esp8266::experimental::clsLEAMDNSHost::clsBackbone::sm_pBackbone.m_uniqueHost; - - if ((!((pHost->begin(p_pcHostname /*, default callback*/)) - && (m_HostInformations.push_back(stcHostInformation(pHost)), true)))) + + if (m_HostInformations.length() > 0) { + //XXXFIXME only one pHost instance, many things can be simplified bResult = false; } + else + { + clsLEAMDNSHost* pHost = &esp8266::experimental::clsLEAMDNSHost::clsBackbone::sm_pBackbone.m_uniqueHost; + + if ((!((pHost->begin(p_pcHostname /*, default callback*/)) + && (m_HostInformations.push_back(stcHostInformation(pHost)), true)))) + { + bResult = false; + } + } return bResult; } From 52787b2a44387c89177465471b41456a255ad407 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 24 May 2020 20:33:20 +0200 Subject: [PATCH 017/152] wip --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 355 ++++++++++-------- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 56 +-- .../ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 37 +- .../ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp | 4 +- .../ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 170 ++++----- .../ESP8266mDNS/src/LEAmDNS2_Backbone.cpp | 15 +- 6 files changed, 353 insertions(+), 284 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index fb8aaf0c56..58d0a6f07b 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -175,10 +175,10 @@ bool clsLEAMDNSHost::setNetIfHostName(const char* p_pcHostName) { if (p_pcHostName) for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf)) - { - netif_set_hostname(pNetIf, p_pcHostName); - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[mDNS] setNetIfHostName host name: %s on " NETIFID_STR "!\n"), p_pcHostName, NETIFID_VAL(pNetIf));); - } + { + netif_set_hostname(pNetIf, p_pcHostName); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[mDNS] setNetIfHostName host name: %s on " NETIFID_STR "!\n"), p_pcHostName, NETIFID_VAL(pNetIf));); + } return true; } @@ -506,37 +506,55 @@ clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryService { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryService '_%s._%s.local'\n"), _DH(), p_pcService, p_pcProtocol);); - clsQuery* pQuery = 0; - if ((p_pcService) && (*p_pcService) && + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector ret; + + if (_removeLegacyQuery() && + (p_pcService) && (*p_pcService) && (p_pcProtocol) && (*p_pcProtocol) && - (p_u16Timeout) && - ((pQuery = _allocQuery(clsQuery::enuQueryType::Service))) && - (_buildDomainForService(p_pcService, p_pcProtocol, pQuery->m_Domain))) + (p_u16Timeout)) { - if ((_removeLegacyQuery()) && - ((pQuery->m_bStaticQuery = true)) && - (_sendQuery(*pQuery))) + std::list queries; + + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + if (netif_is_up(pNetIf)) + { + clsQuery* pQuery = 0; + if (((pQuery = _allocQuery(clsQuery::enuQueryType::Service))) && + (_buildDomainForService(p_pcService, p_pcProtocol, pQuery->m_Domain))) + { + if (((pQuery->m_bStaticQuery = true)) && (_sendQuery(pNetIf, *pQuery))) + { + queries.push_back(pQuery); + } + else + { + // FAILED to send query + _removeQuery(pQuery); + } + } + } + + if (queries.size()) { // Wait for answers to arrive DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryService: Waiting %u ms for answers...\n"), _DH(), p_u16Timeout);); + //XXXFIXME could this delay be ASYNC? delay(p_u16Timeout); // All answers should have arrived by now -> stop adding new answers - pQuery->m_bAwaitingAnswers = false; + for (auto& q : queries) + { + q->m_bAwaitingAnswers = false; + ret.insert(ret.end(), std::make_move_iterator(q->answerAccessors().begin()), std::make_move_iterator(q->answerAccessors().end())); + } } else { - // FAILED to send query - _removeQuery(pQuery); + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s queryService: INVALID input data!\n"), _DH());); } + } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s queryService: INVALID input data!\n"), _DH());); - } - return ((pQuery) - ? pQuery->answerAccessors() - : clsQuery::clsAnswerAccessor::vector()); + return ret; } /* @@ -546,40 +564,58 @@ clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryService clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryHost(const char* p_pcHostName, const uint16_t p_u16Timeout) { + clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector ret; + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost '%s.local'\n"), _DH(), p_pcHostName);); - clsQuery* pQuery = 0; if ((p_pcHostName) && (*p_pcHostName) && (p_u16Timeout) && - ((pQuery = _allocQuery(clsQuery::enuQueryType::Host))) && - (_buildDomainForHost(p_pcHostName, pQuery->m_Domain))) + (_removeLegacyQuery())) { - if ((_removeLegacyQuery()) && - ((pQuery->m_bStaticQuery = true)) && - (_sendQuery(*pQuery))) + std::list queries; + + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + if (netif_is_up(pNetIf)) + { + clsQuery* pQuery = 0; + if (((pQuery = _allocQuery(clsQuery::enuQueryType::Host))) && + (_buildDomainForHost(p_pcHostName, pQuery->m_Domain))) + { + if (((pQuery->m_bStaticQuery = true)) && (_sendQuery(pNetIf, *pQuery))) + { + queries.push_back(pQuery); + } + else + { + // FAILED to send query + _removeQuery(pQuery); + } + } + } + + if (queries.size()) { // Wait for answers to arrive DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost: Waiting %u ms for answers...\n"), _DH(), p_u16Timeout);); + //XXXFIXME could this delay be ASYNC? delay(p_u16Timeout); // All answers should have arrived by now -> stop adding new answers - pQuery->m_bAwaitingAnswers = false; + + for (auto& q : queries) + { + q->m_bAwaitingAnswers = false; + ret.insert(ret.end(), std::make_move_iterator(q->answerAccessors().begin()), std::make_move_iterator(q->answerAccessors().end())); + } } else { - // FAILED to send query - _removeQuery(pQuery); + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost: INVALID input data!\n"), _DH());); } } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s queryHost: INVALID input data!\n"), _DH());); - } - return ((pQuery) - ? pQuery->answerAccessors() - : clsQuery::clsAnswerAccessor::vector()); -} + return ret; +} /* clsLEAmDNS2_Host::removeQuery @@ -611,84 +647,100 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::getQuery(void) clsLEAmDNS2_Host::installServiceQuery (answer) */ -clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcService, +/*clsLEAMDNSHost::clsQuery* */ bool clsLEAMDNSHost::installServiceQuery(const char* p_pcService, const char* p_pcProtocol, clsLEAMDNSHost::clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer) { + bool bResult = false; clsQuery* pQuery = 0; - if ((pQuery = _installServiceQuery(p_pcService, p_pcProtocol))) - { - pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; - } - return pQuery; + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + if (netif_is_up(pNetIf) && (pQuery = _installServiceQuery(pNetIf, p_pcService, p_pcProtocol))) + { + pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; + bResult = true; + } + return bResult; } /* clsLEAmDNS2_Host::installServiceQuery (accessor) */ -clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcService, +/*clsLEAMDNSHost::clsQuery* */ bool clsLEAMDNSHost::installServiceQuery(const char* p_pcService, const char* p_pcProtocol, clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor) { + bool bResult = false; clsQuery* pQuery = 0; - if ((pQuery = _installServiceQuery(p_pcService, p_pcProtocol))) - { - pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; - } - return pQuery; + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + if (netif_is_up(pNetIf) && (pQuery = _installServiceQuery(pNetIf, p_pcService, p_pcProtocol))) + { + pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; + bResult = true; + } + return bResult; } /* clsLEAmDNS2_Host::installHostQuery (answer) */ -clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installHostQuery(const char* p_pcHostName, +/*clsLEAMDNSHost::clsQuery* */ bool clsLEAMDNSHost::installHostQuery(const char* p_pcHostName, clsLEAMDNSHost::clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer) { + bool bResult = false; clsQuery* pQuery = 0; if ((p_pcHostName) && (*p_pcHostName)) - { - clsRRDomain domain; - if ((pQuery = ((_buildDomainForHost(p_pcHostName, domain)) - ? _installDomainQuery(domain, clsQuery::enuQueryType::Host) - : 0))) - { - pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; - } - } - return pQuery; + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + if (netif_is_up(pNetIf)) + { + clsRRDomain domain; + if ((pQuery = ((_buildDomainForHost(p_pcHostName, domain)) + ? _installDomainQuery(pNetIf, domain, clsQuery::enuQueryType::Host) + : 0))) + { + pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; + bResult = true; + } + } + return bResult; } /* clsLEAmDNS2_Host::installHostQuery (accessor) */ -clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installHostQuery(const char* p_pcHostName, +/*clsLEAMDNSHost::clsQuery* */ bool clsLEAMDNSHost::installHostQuery(const char* p_pcHostName, clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor) { + bool bResult = true; clsQuery* pQuery = 0; if ((p_pcHostName) && (*p_pcHostName)) - { - clsRRDomain domain; - if ((pQuery = ((_buildDomainForHost(p_pcHostName, domain)) - ? _installDomainQuery(domain, clsQuery::enuQueryType::Host) - : 0))) - { - pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; - } - } - return pQuery; + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + if (netif_is_up(pNetIf)) + { + clsRRDomain domain; + if ((pQuery = ((_buildDomainForHost(p_pcHostName, domain)) + ? _installDomainQuery(pNetIf, domain, clsQuery::enuQueryType::Host) + : 0))) + { + pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; + bResult = true; + } + } + return bResult; } /* clsLEAmDNS2_Host::removeQuery */ -bool clsLEAMDNSHost::removeQuery(clsLEAMDNSHost::clsQuery* p_pMDNSQuery) -{ +/* + bool clsLEAMDNSHost::removeQuery(clsLEAMDNSHost::clsQuery * p_pMDNSQuery) + { bool bResult = ((p_pMDNSQuery) && (_removeQuery(p_pMDNSQuery))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeQuery: FAILED!\n"), _DH());); return bResult; -} + } +*/ /* @@ -702,15 +754,21 @@ bool clsLEAMDNSHost::update(void) { bool bResult = false; - //if (clsBackbone::sm_pBackbone->setDelayUDPProcessing(true)) - //{ - bResult = ((_updateProbeStatus()) && // Probing and announcing - (_checkQueryCache())); + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + if (netif_is_up(pNetIf)) + { + //if (clsBackbone::sm_pBackbone->setDelayUDPProcessing(true)) + //{ + if ((_updateProbeStatus(pNetIf)) && // Probing and announcing + (_checkQueryCache())) + { + bResult = true; + } - // clsBackbone::sm_pBackbone->setDelayUDPProcessing(false); - //} + // clsBackbone::sm_pBackbone->setDelayUDPProcessing(false); + //} + } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s update: FAILED (Not connected?)!\n"), _DH());); - return bResult; } @@ -726,7 +784,7 @@ bool clsLEAMDNSHost::announce(bool p_bAnnounce /*= true*/, /* clsLEAmDNS2_Host::announceService */ -bool clsLEAMDNSHost::announceService(clsService* p_pService, +bool clsLEAMDNSHost::announceService(clsService * p_pService, bool p_bAnnounce /*= true*/) { return _announceService(*p_pService, p_bAnnounce); @@ -840,45 +898,45 @@ bool clsLEAMDNSHost::_joinMulticastGroups(void) // Join multicast group(s) for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf)) - { -#ifdef MDNS_IPV4_SUPPORT - ip_addr_t multicast_addr_V4 = DNS_MQUERY_IPV4_GROUP_INIT; - if (!(m_pNetIf->flags & NETIF_FLAG_IGMP)) { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: Setting flag: flags & NETIF_FLAG_IGMP\n"), _DH());); - m_pNetIf->flags |= NETIF_FLAG_IGMP; - - if (ERR_OK != igmp_start(m_pNetIf)) +#ifdef MDNS_IPV4_SUPPORT + ip_addr_t multicast_addr_V4 = DNS_MQUERY_IPV4_GROUP_INIT; + if (!(pNetIf->flags & NETIF_FLAG_IGMP)) { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: igmp_start FAILED!\n"), _DH());); + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: Setting flag: flags & NETIF_FLAG_IGMP\n"), _DH());); + pNetIf->flags |= NETIF_FLAG_IGMP; + + if (ERR_OK != igmp_start(pNetIf)) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: igmp_start FAILED!\n"), _DH());); + } } - } - if ( + if ( #if LWIP_VERSION_MAJOR == 1 - (ERR_OK == igmp_joingroup(&pNetIf->ip_addr, &multicast_addr_V4)) + (ERR_OK == igmp_joingroup(&pNetIf->ip_addr, &multicast_addr_V4)) #else - (ERR_OK == igmp_joingroup_netif(pNetIf, ip_2_ip4(&multicast_addr_V4))) + (ERR_OK == igmp_joingroup_netif(pNetIf, ip_2_ip4(&multicast_addr_V4))) #endif - ) - { - bResult = true; - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: igmp_joingroup_netif(" NETIFID_STR ": %s) FAILED!\n"), - _DH(), NETIFID_VAL(pNetIf), IPAddress(multicast_addr_V4).toString().c_str());); - } + ) + { + bResult = true; + } + else + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: igmp_joingroup_netif(" NETIFID_STR ": %s) FAILED!\n"), + _DH(), NETIFID_VAL(pNetIf), IPAddress(multicast_addr_V4).toString().c_str());); + } #endif #ifdef MDNS_IPV6_SUPPORT - ip_addr_t multicast_addr_V6 = DNS_MQUERY_IPV6_GROUP_INIT; - bResult = ((bResult) && - (ERR_OK == mld6_joingroup_netif(pNetIf, ip_2_ip6(&multicast_addr_V6)))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: mld6_joingroup_netif (" NETIFID_STR ") FAILED!\n"), - _DH(), NETIFID_VAL(pNetIf));); + ip_addr_t multicast_addr_V6 = DNS_MQUERY_IPV6_GROUP_INIT; + bResult = ((bResult) && + (ERR_OK == mld6_joingroup_netif(pNetIf, ip_2_ip6(&multicast_addr_V6)))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: mld6_joingroup_netif (" NETIFID_STR ") FAILED!\n"), + _DH(), NETIFID_VAL(pNetIf));); #endif - } + } return bResult; } @@ -890,33 +948,33 @@ bool clsLEAMDNSHost::_leaveMulticastGroups() bool bResult = false; for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf)) - { - bResult = true; - /* _resetProbeStatus(false); // Stop probing - _releaseQueries(); - _releaseServices(); - _releaseHostName();*/ + { + bResult = true; + /* _resetProbeStatus(false); // Stop probing + _releaseQueries(); + _releaseServices(); + _releaseHostName();*/ - // Leave multicast group(s) + // Leave multicast group(s) #ifdef MDNS_IPV4_SUPPORT - ip_addr_t multicast_addr_V4 = DNS_MQUERY_IPV4_GROUP_INIT; + ip_addr_t multicast_addr_V4 = DNS_MQUERY_IPV4_GROUP_INIT; #if LWIP_VERSION_MAJOR == 1 - if (ERR_OK != igmp_leavegroup(ip_2_ip4(pNetIf->ip_addr), ip_2_ip4(&multicast_addr_V4))) + if (ERR_OK != igmp_leavegroup(ip_2_ip4(pNetIf->ip_addr), ip_2_ip4(&multicast_addr_V4))) #else - if (ERR_OK != igmp_leavegroup_netif(pNetIf, ip_2_ip4(&multicast_addr_V4))) + if (ERR_OK != igmp_leavegroup_netif(pNetIf, ip_2_ip4(&multicast_addr_V4))) #endif - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("\n"));); - } + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("\n"));); + } #endif #ifdef MDNS_IPV6_SUPPORT - ip_addr_t multicast_addr_V6 = DNS_MQUERY_IPV6_GROUP_INIT; - if (ERR_OK != mld6_leavegroup_netif(pNetIf, ip_2_ip6(&multicast_addr_V6)/*&(multicast_addr_V6.u_addr.ip6)*/)) - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("\n"));); - } + ip_addr_t multicast_addr_V6 = DNS_MQUERY_IPV6_GROUP_INIT; + if (ERR_OK != mld6_leavegroup_netif(pNetIf, ip_2_ip6(&multicast_addr_V6)/*&(multicast_addr_V6.u_addr.ip6)*/)) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("\n"));); + } #endif - } + } return bResult; } @@ -1041,7 +1099,7 @@ const char* clsLEAMDNSHost::_instanceName(const char* p_pcInstanceName, /* clsLEAmDNS2_Host::_collectServiceTxts */ -bool clsLEAMDNSHost::_collectServiceTxts(clsLEAMDNSHost::clsService& p_rService) +bool clsLEAMDNSHost::_collectServiceTxts(clsLEAMDNSHost::clsService & p_rService) { if (p_rService.m_fnTxtCallback) { @@ -1053,7 +1111,7 @@ bool clsLEAMDNSHost::_collectServiceTxts(clsLEAMDNSHost::clsService& p_rService) /* clsLEAmDNS2_Host::_releaseTempServiceTxts */ -bool clsLEAMDNSHost::_releaseTempServiceTxts(clsLEAMDNSHost::clsService& p_rService) +bool clsLEAMDNSHost::_releaseTempServiceTxts(clsLEAMDNSHost::clsService & p_rService) { return (p_rService.m_Txts.removeTempTxts()); } @@ -1084,7 +1142,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_allocQuery(clsLEAMDNSHost::clsQuery:: MDNSResponder:clsHost:::_removeQuery */ -bool clsLEAMDNSHost::_removeQuery(clsLEAMDNSHost::clsQuery* p_pQuery) +bool clsLEAMDNSHost::_removeQuery(clsLEAMDNSHost::clsQuery * p_pQuery) { bool bResult = false; @@ -1154,9 +1212,9 @@ bool clsLEAMDNSHost::_releaseQueries(void) clsLEAmDNS2_Host::_findNextQueryByDomain */ -clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_findNextQueryByDomain(const clsLEAMDNSHost::clsRRDomain& p_Domain, +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_findNextQueryByDomain(const clsLEAMDNSHost::clsRRDomain & p_Domain, const clsLEAMDNSHost::clsQuery::enuQueryType p_QueryType, - const clsQuery* p_pPrevQuery) + const clsQuery * p_pPrevQuery) { clsQuery* pMatchingQuery = 0; @@ -1191,7 +1249,8 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_findNextQueryByDomain(const clsLEAMDN clsLEAmDNS2_Host::_installServiceQuery */ -clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installServiceQuery(const char* p_pcService, +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installServiceQuery(netif* pNetIf, + const char* p_pcService, const char* p_pcProtocol) { clsQuery* pMDNSQuery = 0; @@ -1203,7 +1262,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installServiceQuery(const char* p_pcS { pMDNSQuery->m_bStaticQuery = false; - if (_sendQuery(*pMDNSQuery)) + if (_sendQuery(pNetIf, *pMDNSQuery)) { pMDNSQuery->m_u8SentCount = 1; pMDNSQuery->m_ResendTimeout.reset(clsConsts::u32DynamicQueryResendDelay); @@ -1221,7 +1280,8 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installServiceQuery(const char* p_pcS /* clsLEAmDNS2_Host::_installDomainQuery */ -clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installDomainQuery(clsLEAMDNSHost::clsRRDomain& p_Domain, +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installDomainQuery(netif* pNetIf, + clsLEAMDNSHost::clsRRDomain & p_Domain, clsLEAMDNSHost::clsQuery::enuQueryType p_QueryType) { clsQuery* pQuery = 0; @@ -1231,7 +1291,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installDomainQuery(clsLEAMDNSHost::cl pQuery->m_Domain = p_Domain; pQuery->m_bStaticQuery = false; - if (_sendQuery(*pQuery)) + if (_sendQuery(pNetIf, *pQuery)) { pQuery->m_u8SentCount = 1; pQuery->m_ResendTimeout.reset(clsConsts::u32DynamicQueryResendDelay); @@ -1246,13 +1306,10 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installDomainQuery(clsLEAMDNSHost::cl _printRRDomain(p_Domain); DEBUG_OUTPUT.println(); ); - DEBUG_EX_ERR(if (!pQuery) -{ - DEBUG_OUTPUT.printf_P(PSTR("%s _installDomainQuery: FAILED for "), _DH()); - _printRRDomain(p_Domain); - DEBUG_OUTPUT.println(); - } - ); + DEBUG_EX_ERR_IF(!pQuery, + DEBUG_OUTPUT.printf_P(PSTR("%s _installDomainQuery: FAILED for "), _DH()); + _printRRDomain(p_Domain); + DEBUG_OUTPUT.println()); return pQuery; } @@ -1277,8 +1334,8 @@ bool clsLEAMDNSHost::_hasQueriesWaitingForAnswers(void) const /* clsLEAmDNS2_Host::_executeQueryCallback */ -bool clsLEAMDNSHost::_executeQueryCallback(const clsQuery& p_Query, - const clsQuery::clsAnswer& p_Answer, +bool clsLEAMDNSHost::_executeQueryCallback(const clsQuery & p_Query, + const clsQuery::clsAnswer & p_Answer, clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, bool p_bSetContent) { diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index d724a19ba7..d5bdffcb09 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -298,15 +298,15 @@ class clsLEAMDNSHost bool m_bDelayUDPProcessing; uint32_t m_u32DelayedDatagrams; //list m_HostList; - clsLEAMDNSHost m_uniqueHost; + clsLEAMDNSHost* m_uniqueHost; bool _allocUDPContext(void); bool _releaseUDPContext(void); bool _processUDPInput(void); - const clsLEAMDNSHost* _findHost() const { return &m_uniqueHost; } - clsLEAMDNSHost* _findHost() { return &m_uniqueHost; } + const clsLEAMDNSHost* _findHost() const { return m_uniqueHost; } + clsLEAMDNSHost* _findHost() { return m_uniqueHost; } const char* _DH(void) const; }; @@ -1252,8 +1252,7 @@ class clsLEAMDNSHost static const char* indexDomainName(const char* p_pcDomainName, const char* p_pcDivider = "-", const char* p_pcDefaultDomainName = 0); - static bool setNetIfHostName(netif* p_pNetIf, - const char* p_pcHostName); + static bool setNetIfHostName(const char* p_pcHostName); clsLEAMDNSHost(void); ~clsLEAMDNSHost(void); @@ -1328,18 +1327,23 @@ class clsLEAMDNSHost // - hasAnswerIPv6Address/answerIPv6Address service/host // - hasAnswerPort/answerPort service // - hasAnswerTxts/answerTxts service - clsQuery* installServiceQuery(const char* p_pcServiceType, + + /* + install*Query() creates several queries on the interfaces. + it no more returns a single query but a boolean until the API is adapted + */ + /*clsQuery**/bool installServiceQuery(const char* p_pcServiceType, const char* p_pcProtocol, clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer); - clsQuery* installServiceQuery(const char* p_pcServiceType, + /*clsQuery**/bool installServiceQuery(const char* p_pcServiceType, const char* p_pcProtocol, clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor); - clsQuery* installHostQuery(const char* p_pcHostName, + /*clsQuery**/bool installHostQuery(const char* p_pcHostName, clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer); - clsQuery* installHostQuery(const char* p_pcHostName, + /*clsQuery**/bool installHostQuery(const char* p_pcHostName, clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor); // Remove a dynamic service query - bool removeQuery(clsQuery* p_pQuery); + /*bool removeQuery(clsQuery* p_pQuery);*/ // PROCESSING bool update(void); @@ -1419,9 +1423,11 @@ class clsLEAMDNSHost clsQuery* _findNextQueryByDomain(const clsRRDomain& p_Domain, const clsQuery::enuQueryType p_QueryType, const clsQuery* p_pPrevQuery); - clsQuery* _installServiceQuery(const char* p_pcService, + clsQuery* _installServiceQuery(netif* pNetIf, + const char* p_pcService, const char* p_pcProtocol); - clsQuery* _installDomainQuery(clsRRDomain& p_Domain, + clsQuery* _installDomainQuery(netif *pNetIf, + clsRRDomain& p_Domain, clsQuery::enuQueryType p_QueryType); bool _hasQueriesWaitingForAnswers(void) const; bool _executeQueryCallback(const clsQuery& p_Query, @@ -1433,7 +1439,8 @@ class clsLEAMDNSHost // File: ..._Host_Control // RECEIVING bool _parseMessage(void); - bool _parseQuery(const clsMsgHeader& p_Header); + bool _parseQuery(netif* pNetIf, + const clsMsgHeader& p_Header); bool _parseResponse(const clsMsgHeader& p_Header); bool _processAnswers(const clsRRAnswer* p_pPTRAnswers); @@ -1450,11 +1457,11 @@ class clsLEAMDNSHost #endif // PROBING - bool _updateProbeStatus(void); + bool _updateProbeStatus(netif* pNetIf); bool _resetProbeStatus(bool p_bRestart = true); bool _hasProbesWaitingForAnswers(void) const; - bool _sendHostProbe(void); - bool _sendServiceProbe(clsService& p_rService); + bool _sendHostProbe(netif* pNetIf); + bool _sendServiceProbe(netif* pNetIf, clsService& p_rService); bool _cancelProbingForHost(void); bool _cancelProbingForService(clsService& p_rService); bool _callHostProbeResultCallback(bool p_bResult); @@ -1462,9 +1469,11 @@ class clsLEAMDNSHost bool p_bResult); // ANNOUNCE - bool _announce(bool p_bAnnounce, + bool _announce(netif* pNetIf, + bool p_bAnnounce, bool p_bIncludeServices); - bool _announceService(clsService& p_pService, + bool _announceService(netif* pNetIf, + clsService& p_pService, bool p_bAnnounce = true); // QUERY CACHE @@ -1479,16 +1488,19 @@ class clsLEAMDNSHost // File: ..._Host_Transfer // SENDING - bool _sendMessage(clsSendParameter& p_SendParameter); - bool _sendMessage_Multicast(clsSendParameter& p_rSendParameter, + bool _sendMessage(netif* pNetIf, clsSendParameter& p_SendParameter); + bool _sendMessage_Multicast(netif* pNetIf, + clsSendParameter& p_rSendParameter, uint8_t p_IPProtocolTypes); bool _prepareMessage(clsSendParameter& p_SendParameter); bool _addQueryRecord(clsSendParameter& p_rSendParameter, const clsRRDomain& p_QueryDomain, uint16_t p_u16QueryType); - bool _sendQuery(const clsQuery& p_Query, + bool _sendQuery(netif* netif, + const clsQuery& p_Query, clsQuery::clsAnswer::list* p_pKnownAnswers = 0); - bool _sendQuery(const clsRRDomain& p_QueryDomain, + bool _sendQuery(netif* netif, + const clsRRDomain& p_QueryDomain, uint16_t p_u16RecordType, clsQuery::clsAnswer::list* p_pKnownAnswers = 0); diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index bab5eba3cf..48eba00b0d 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -82,7 +82,7 @@ bool clsLEAMDNSHost::_parseMessage(void) { // Received a query (Questions) //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: Reading query: ID:%u, Q:%u, A:%u, NS:%u, AR:%u\n"), _DH(), header.m_u16ID, header.m_u16QDCount, header.m_u16ANCount, header.m_u16NSCount, header.m_u16ARCount);); - bResult = _parseQuery(header); + bResult = _parseQuery(m_pUDPContext->getInputNetif(), header); } } else @@ -119,7 +119,8 @@ bool clsLEAMDNSHost::_parseMessage(void) Legacy queries have got only one (unicast) question and are directed to the local DNS port (not the multicast port). */ -bool clsLEAMDNSHost::_parseQuery(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader) +bool clsLEAMDNSHost::_parseQuery(netif* pNetIf, + const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader) { bool bResult = true; @@ -209,9 +210,9 @@ bool clsLEAMDNSHost::_parseQuery(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader if ((m_pUDPContext) && #ifdef MDNS_IPV4_SUPPORT (m_pUDPContext->getRemoteAddress().isV4()) && - (ip4_addr_netcmp(ip_2_ip4(m_pUDPContext->getRemoteAddress()), - ip_2_ip4(m_pUDPContext->getInputNetif()->ip_addr), - ip_2_ip4(m_pUDPContext->getInputNetif()->netmask)) + (ip4_addr_netcmp(ip_2_ip4((const ip_addr_t*)m_pUDPContext->getRemoteAddress()), + ip_2_ip4(&m_pUDPContext->getInputNetif()->ip_addr), + ip_2_ip4(&m_pUDPContext->getInputNetif()->netmask))) #else (true) #endif @@ -548,7 +549,7 @@ bool clsLEAMDNSHost::_parseQuery(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader sendParameter.m_Response = clsSendParameter::enuResponseType::Response; sendParameter.m_bAuthorative = true; - bResult = _sendMessage(sendParameter); + bResult = _sendMessage(pNetIf, sendParameter); } DEBUG_EX_INFO(else { @@ -1281,7 +1282,7 @@ bool clsLEAMDNSHost::_processAAAAAnswer(const clsLEAMDNSHost::clsRRAnswerAAAA* p Conflict management is handled in '_parseResponse ff.' Tiebraking is handled in 'parseQuery ff.' */ -bool clsLEAMDNSHost::_updateProbeStatus(void) +bool clsLEAMDNSHost::_updateProbeStatus(netif* pNetIf) { bool bResult = true; @@ -1314,7 +1315,7 @@ bool clsLEAMDNSHost::_updateProbeStatus(void) if (clsConsts::u32ProbeCount > m_ProbeInformation.m_u8SentCount) { // Send next probe - if ((bResult = _sendHostProbe())) + if ((bResult = _sendHostProbe(pNetIf))) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Did sent host probe for '%s.local'\n\n"), _DH(), (m_pcHostName ? : ""));); m_ProbeInformation.m_Timeout.reset(clsConsts::u32ProbeDelay); @@ -1378,7 +1379,7 @@ bool clsLEAMDNSHost::_updateProbeStatus(void) if (clsConsts::u32ProbeCount > pService->m_ProbeInformation.m_u8SentCount) { // Send next probe - if ((bResult = _sendServiceProbe(*pService))) + if ((bResult = _sendServiceProbe(pNetIf, *pService))) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Did sent service probe for '%s' (%u)\n\n"), _DH(), _service2String(pService), (pService->m_ProbeInformation.m_u8SentCount + 1));); pService->m_ProbeInformation.m_Timeout.reset(clsConsts::u32ProbeDelay); @@ -1482,7 +1483,7 @@ bool clsLEAMDNSHost::_hasProbesWaitingForAnswers(void) const - A/AAAA (eg. esp8266.esp -> 192.168.2.120) */ -bool clsLEAMDNSHost::_sendHostProbe(void) +bool clsLEAMDNSHost::_sendHostProbe(netif* pNetIf) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendHostProbe (%s.local, %lu)\n"), _DH(), m_pcHostName, millis());); @@ -1521,7 +1522,7 @@ bool clsLEAMDNSHost::_sendHostProbe(void) } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendHostProbe: FAILED!\n"), _DH());); return ((bResult) && - (_sendMessage(sendParameter))); + (_sendMessage(pNetIf, sendParameter))); } /* @@ -1537,7 +1538,7 @@ bool clsLEAMDNSHost::_sendHostProbe(void) - PTR NAME (eg. _http._tcp.local -> MyESP._http._tcp.local) (TODO: Check if needed, maybe TXT is better) */ -bool clsLEAMDNSHost::_sendServiceProbe(clsService& p_rService) +bool clsLEAMDNSHost::_sendServiceProbe(netif* pNetIf, clsService& p_rService) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendServiceProbe (%s, %lu)\n"), _DH(), _service2String(&p_rService), millis());); @@ -1571,7 +1572,7 @@ bool clsLEAMDNSHost::_sendServiceProbe(clsService& p_rService) } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendServiceProbe: FAILED!\n"), _DH());); return ((bResult) && - (_sendMessage(sendParameter))); + (_sendMessage(pNetIf, sendParameter))); } /* @@ -1670,7 +1671,8 @@ bool clsLEAMDNSHost::_callServiceProbeResultCallback(clsLEAMDNSHost::clsService& inside the '_writeXXXAnswer' procs via 'sendParameter.m_bUnannounce = true' */ -bool clsLEAMDNSHost::_announce(bool p_bAnnounce, +bool clsLEAMDNSHost::_announce(netif* pNetIf, + bool p_bAnnounce, bool p_bIncludeServices) { bool bResult = false; @@ -1716,14 +1718,15 @@ bool clsLEAMDNSHost::_announce(bool p_bAnnounce, } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _announce: FAILED!\n"), _DH());); return ((bResult) && - (_sendMessage(sendParameter))); + (_sendMessage(pNetIf, sendParameter))); } /* clsLEAmDNS2_Host::_announceService */ -bool clsLEAMDNSHost::_announceService(clsLEAMDNSHost::clsService& p_rService, +bool clsLEAMDNSHost::_announceService(netif* pNetIf, + clsLEAMDNSHost::clsService& p_rService, bool p_bAnnounce /*= true*/) { bool bResult = false; @@ -1749,7 +1752,7 @@ bool clsLEAMDNSHost::_announceService(clsLEAMDNSHost::clsService& p_rService, } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _announceService: FAILED!\n"), _DH());); return ((bResult) && - (_sendMessage(sendParameter))); + (_sendMessage(pNetIf, sendParameter))); } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp index 94855d2ab5..f0edcd4232 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp @@ -40,12 +40,12 @@ namespace experimental clsLEAmDNS2_Host::_DH */ -xxxxx const char* clsLEAMDNSHost::_DH(const clsLEAMDNSHost::clsService* p_pService /*= 0*/) const +const char* clsLEAMDNSHost::_DH(const clsLEAMDNSHost::clsService* p_pService /*= 0*/) const { static char acBuffer[16 + 64]; *acBuffer = 0; - sprintf_P(acBuffer, PSTR("[mDNS]"); + sprintf_P(acBuffer, PSTR("[mDNS]")); if (p_pService) { strcat_P(acBuffer, PSTR(">")); diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 6ad703d4cc..1d1a4df1b1 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -50,14 +50,14 @@ namespace experimental Any reply flags in installed services are removed at the end! */ -bool clsLEAMDNSHost::_sendMessage(clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_sendMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { bool bResult = false; uint8_t u8AvailableProtocols = 0; #ifdef MDNS_IPV4_SUPPORT // Only send out IPv4 messages, if we've got an IPv4 address - if (_getResponderIPAddress(enuIPProtocolType::V4).isSet()) + if (_getResponderIPAddress(pNetIf, enuIPProtocolType::V4).isSet()) { u8AvailableProtocols |= static_cast(enuIPProtocolType::V4); } @@ -68,7 +68,7 @@ bool clsLEAMDNSHost::_sendMessage(clsLEAMDNSHost::clsSendParameter& p_rSendParam #endif #ifdef MDNS_IPV6_SUPPORT // Only send out IPv6 messages, if we've got an IPv6 address - if (_getResponderIPAddress(enuIPProtocolType::V6).isSet()) + if (_getResponderIPAddress(pNetIf, enuIPProtocolType::V6).isSet()) { u8AvailableProtocols |= static_cast(enuIPProtocolType::V6); } @@ -113,7 +113,7 @@ bool clsLEAMDNSHost::_sendMessage(clsLEAMDNSHost::clsSendParameter& p_rSendParam (ipRemote.isV4())) && // OR IPv4 (u8AvailableProtocols & static_cast(enuIPProtocolType::V4))) // AND IPv4 protocol available { - bResult = _sendMessage_Multicast(p_rSendParameter, static_cast(enuIPProtocolType::V4)); + bResult = _sendMessage_Multicast(pNetIf, p_rSendParameter, static_cast(enuIPProtocolType::V4)); } #endif #ifdef MDNS_IPV6_SUPPORT @@ -121,7 +121,7 @@ bool clsLEAMDNSHost::_sendMessage(clsLEAMDNSHost::clsSendParameter& p_rSendParam (ipRemote.isV6())) && // OR IPv6 (u8AvailableProtocols & static_cast(enuIPProtocolType::V6))) // AND IPv6 protocol available { - bResult = _sendMessage_Multicast(p_rSendParameter, static_cast(enuIPProtocolType::V6)); + bResult = _sendMessage_Multicast(pNetIf, p_rSendParameter, static_cast(enuIPProtocolType::V6)); } #endif } @@ -130,7 +130,7 @@ bool clsLEAMDNSHost::_sendMessage(clsLEAMDNSHost::clsSendParameter& p_rSendParam { // Multicast query -> Send by all available protocols bResult = ((u8AvailableProtocols) && - (_sendMessage_Multicast(p_rSendParameter, u8AvailableProtocols))); + (_sendMessage_Multicast(pNetIf, p_rSendParameter, u8AvailableProtocols))); } // Finally clear service reply masks @@ -152,7 +152,7 @@ bool clsLEAMDNSHost::_sendMessage(clsLEAMDNSHost::clsSendParameter& p_rSendParam Fills the UDP output buffer (via _prepareMessage) and sends the buffer via the selected WiFi protocols */ -bool clsLEAMDNSHost::_sendMessage_Multicast(clsLEAMDNSHost::clsSendParameter& p_rSendParameter, +bool clsLEAMDNSHost::_sendMessage_Multicast(netif* pNetIf, clsLEAMDNSHost::clsSendParameter& p_rSendParameter, uint8_t p_IPProtocolTypes) { bool bIPv4Result = true; @@ -164,9 +164,9 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(clsLEAMDNSHost::clsSendParameter& p_ IPAddress ip4MulticastAddress(DNS_MQUERY_IPV4_GROUP_INIT); DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast IPv4: Will send to '%s'.\n"), _DH(), ip4MulticastAddress.toString().c_str());); - DEBUG_EX_INFO(if (!_getResponderIPAddress(enuIPProtocolType::V4)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast IPv4: NO IPv4 address!.\n"), _DH());); + DEBUG_EX_INFO(if (!_getResponderIPAddress(pNetIf, enuIPProtocolType::V4)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast IPv4: NO IPv4 address!.\n"), _DH());); bIPv4Result = ((_prepareMessage(p_rSendParameter)) && - (m_pUDPContext->setMulticastInterface(m_pNetIf), true) && + (m_pUDPContext->setMulticastInterface(pNetIf), true) && (m_pUDPContext->send(ip4MulticastAddress, DNS_MQUERY_PORT)) && (m_pUDPContext->setMulticastInterface(0), true) /*&& (Serial.println("Did send MC V4"), true)*/); @@ -186,17 +186,17 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(clsLEAMDNSHost::clsSendParameter& p_ IPAddress ip6MulticastAddress(DNS_MQUERY_IPV6_GROUP_INIT); DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast IPv6: Will send to '%s'.\n"), _DH(), ip6MulticastAddress.toString().c_str());); - DEBUG_EX_INFO(if (!_getResponderIPAddress(enuIPProtocolType::V6)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast IPv6: NO IPv6 address!.\n"), _DH());); + DEBUG_EX_INFO(if (!_getResponderIPAddress(pNetIf, enuIPProtocolType::V6)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast IPv6: NO IPv6 address!.\n"), _DH());); DEBUG_EX_ERR( bool bPrepareMessage = false; bool bUDPContextSend = false; ); bIPv6Result = ((DEBUG_EX_ERR(bPrepareMessage =)_prepareMessage(p_rSendParameter)) && - (m_pUDPContext->setMulticastInterface(m_pNetIf), true) && + (m_pUDPContext->setMulticastInterface(pNetIf), true) && (DEBUG_EX_ERR(bUDPContextSend =)m_pUDPContext->send(ip6MulticastAddress, DNS_MQUERY_PORT)) && (m_pUDPContext->setMulticastInterface(0), true) /*&& (Serial.println("Did send MC V6"), true)*/); - DEBUG_EX_ERR(if (!bIPv6Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast (IPv6): FAILED! (%s, %s, %s)\n"), _DH(), (_getResponderIPAddress(enuIPProtocolType::V6).isSet() ? "1" : "0"), (bPrepareMessage ? "1" : "0"), (bUDPContextSend ? "1" : "0"));); + DEBUG_EX_ERR(if (!bIPv6Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast (IPv6): FAILED! (%s, %s, %s)\n"), _DH(), (_getResponderIPAddress(pNetIf, enuIPProtocolType::V6).isSet() ? "1" : "0"), (bPrepareMessage ? "1" : "0"), (bUDPContextSend ? "1" : "0"));); if ((clsConsts::u32SendCooldown) && (can_yield())) @@ -288,25 +288,25 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa // A if ((bResult) && (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::A)) && - (_getResponderIPAddress(enuIPProtocolType::V4).isSet())) + (_getResponderIPAddress(pNetIf, enuIPProtocolType::V4).isSet())) { u32NSECContent |= static_cast(enuContentFlag::A); ((static_cast(enuSequence::Count) == sequence) ? ++ru16Answers - : (bResult = _writeMDNSAnswer_A(_getResponderIPAddress(enuIPProtocolType::V4), p_rSendParameter))); + : (bResult = _writeMDNSAnswer_A(_getResponderIPAddress(pNetIf, enuIPProtocolType::V4), p_rSendParameter))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_A(A) FAILED!\n"), _DH());); } // PTR_IPv4 if ((bResult) && (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::PTR_IPv4)) && - (_getResponderIPAddress(enuIPProtocolType::V4).isSet())) + (_getResponderIPAddress(pNetIf, enuIPProtocolType::V4).isSet())) { u32NSECContent |= static_cast(enuContentFlag::PTR_IPv4); ((static_cast(enuSequence::Count) == sequence) ? ++ru16Answers - : (bResult = _writeMDNSAnswer_PTR_IPv4(_getResponderIPAddress(enuIPProtocolType::V4), p_rSendParameter))); + : (bResult = _writeMDNSAnswer_PTR_IPv4(_getResponderIPAddress(pNetIf, enuIPProtocolType::V4), p_rSendParameter))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_PTR_IPv4 FAILED!\n"), _DH());); } #endif @@ -314,35 +314,35 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa // AAAA if ((bResult) && (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::AAAA)) && - (_getResponderIPAddress(enuIPProtocolType::V6).isSet())) - { + (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6).isSet())) + { - u32NSECContent |= static_cast(enuContentFlag::AAAA); + u32NSECContent |= static_cast(enuContentFlag::AAAA); ((static_cast(enuSequence::Count) == sequence) ? ++ru16Answers - : (bResult = _writeMDNSAnswer_AAAA(_getResponderIPAddress(enuIPProtocolType::V6), p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_AAAA(A) FAILED!\n"), _DH());); + : (bResult = _writeMDNSAnswer_AAAA(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6), p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_AAAA(A) FAILED!\n"), _DH());); } - // PTR_IPv6 - if ((bResult) && + // PTR_IPv6 + if ((bResult) && (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::PTR_IPv6)) && - (_getResponderIPAddress(enuIPProtocolType::V6).isSet())) - { + (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6).isSet())) + { - u32NSECContent |= static_cast(enuContentFlag::PTR_IPv6); + u32NSECContent |= static_cast(enuContentFlag::PTR_IPv6); ((static_cast(enuSequence::Count) == sequence) ? ++ru16Answers - : (bResult = _writeMDNSAnswer_PTR_IPv6(_getResponderIPAddress(enuIPProtocolType::V6), p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_PTR_IPv6 FAILED!\n"), _DH());); + : (bResult = _writeMDNSAnswer_PTR_IPv6(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6), p_rSendParameter))); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_PTR_IPv6 FAILED!\n"), _DH());); } #endif - for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) - { - clsService* pService = *it; + for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) + { + clsService* pService = *it; - // PTR_TYPE - if ((bResult) && + // PTR_TYPE + if ((bResult) && (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_TYPE))) { ((static_cast(enuSequence::Count) == sequence) @@ -383,16 +383,16 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa uint16_t& ru16AdditionalAnswers = msgHeader.m_u16ARCount; #ifdef MDNS_IPV4_SUPPORT - bool bNeedsAdditionalAnswerA = false; + bool bNeedsAdditionalAnswerA = false; #endif #ifdef MDNS_IPV6_SUPPORT - bool bNeedsAdditionalAnswerAAAA = false; + bool bNeedsAdditionalAnswerAAAA = false; #endif - for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) - { - clsService* pService = *it; + for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) + { + clsService* pService = *it; - if ((bResult) && + if ((bResult) && (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_NAME)) && // If PTR_NAME is requested, AND (!(pService->m_u32ReplyMask & static_cast(enuContentFlag::SRV)))) // NOT SRV -> add SRV as additional answer { @@ -446,41 +446,41 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa // Answer A needed? if ((bResult) && (bNeedsAdditionalAnswerA) && - (_getResponderIPAddress(enuIPProtocolType::V4).isSet())) - { - // Additional A - u32NSECContent |= static_cast(enuContentFlag::A); + (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4).isSet())) + { + // Additional A + u32NSECContent |= static_cast(enuContentFlag::A); ((static_cast(enuSequence::Count) == sequence) ? ++ru16AdditionalAnswers - : (bResult = _writeMDNSAnswer_A(_getResponderIPAddress(enuIPProtocolType::V4), p_rSendParameter))); + : (bResult = _writeMDNSAnswer_A(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4), p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_A(B) FAILED!\n"), _DH());); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_A(B) FAILED!\n"), _DH());); } #endif #ifdef MDNS_IPV6_SUPPORT - // Answer AAAA needed? - if ((bResult) && + // Answer AAAA needed? + if ((bResult) && (bNeedsAdditionalAnswerAAAA) && - (_getResponderIPAddress(enuIPProtocolType::V6).isSet())) - { - // Additional AAAA - u32NSECContent |= static_cast(enuContentFlag::AAAA); + (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6).isSet())) + { + // Additional AAAA + u32NSECContent |= static_cast(enuContentFlag::AAAA); ((static_cast(enuSequence::Count) == sequence) ? ++ru16AdditionalAnswers - : (bResult = _writeMDNSAnswer_AAAA(_getResponderIPAddress(enuIPProtocolType::V6), p_rSendParameter))); + : (bResult = _writeMDNSAnswer_AAAA(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6), p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_AAAA(B) FAILED!\n"), _DH());); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_AAAA(B) FAILED!\n"), _DH());); } #endif - // NSEC host (part 2) - if ((bResult) && + // NSEC host (part 2) + if ((bResult) && ((clsSendParameter::enuResponseType::None != p_rSendParameter.m_Response)) && (u32NSECContent)) - { - // NSEC PTR IPv4/IPv6 are separate answers; make sure, that this is counted for + { + // NSEC PTR IPv4/IPv6 are separate answers; make sure, that this is counted for #ifdef MDNS_IPV4_SUPPORT - uint32_t u32NSECContent_PTR_IPv4 = (u32NSECContent & static_cast(enuContentFlag::PTR_IPv4)); + uint32_t u32NSECContent_PTR_IPv4 = (u32NSECContent & static_cast(enuContentFlag::PTR_IPv4)); u32NSECContent &= ~static_cast(enuContentFlag::PTR_IPv4); #endif #ifdef MDNS_IPV6_SUPPORT @@ -503,35 +503,35 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa #ifdef MDNS_IPV4_SUPPORT // Write separate answer for host PTR IPv4 && ((!u32NSECContent_PTR_IPv4) || - ((!_getResponderIPAddress(enuIPProtocolType::V4).isSet()) || - (_writeMDNSAnswer_NSEC_PTR_IPv4(_getResponderIPAddress(enuIPProtocolType::V4), p_rSendParameter)))) + ((!_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4).isSet()) || + (_writeMDNSAnswer_NSEC_PTR_IPv4(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4), p_rSendParameter)))) #endif #ifdef MDNS_IPV6_SUPPORT - // Write separate answer for host PTR IPv6 - && ((!u32NSECContent_PTR_IPv6) || - ((!_getResponderIPAddress(enuIPProtocolType::V6).isSet()) || - (_writeMDNSAnswer_NSEC_PTR_IPv6(_getResponderIPAddress(enuIPProtocolType::V6), p_rSendParameter)))) + // Write separate answer for host PTR IPv6 + && ((!u32NSECContent_PTR_IPv6) || + ((!_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6).isSet()) || + (_writeMDNSAnswer_NSEC_PTR_IPv6(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6), p_rSendParameter)))) #endif - ))); + ))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_NSEC(Host) FAILED!\n"), _DH());); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_NSEC(Host) FAILED!\n"), _DH());); } - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: Loop %i FAILED!\n"), _DH(), sequence);); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: Loop %i FAILED!\n"), _DH(), sequence);); } // for sequence - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: FAILED!\n"), _DH());); - return bResult; + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: FAILED!\n"), _DH());); + return bResult; } -/* - MDNSResponder::_addQueryRecord + /* + MDNSResponder::_addQueryRecord - Adds a query for the given domain and query type. + Adds a query for the given domain and query type. -*/ -bool clsLEAMDNSHost::_addQueryRecord(clsLEAMDNSHost::clsSendParameter& p_rSendParameter, - const clsLEAMDNSHost::clsRRDomain& p_QueryDomain, - uint16_t p_u16RecordType) + */ + bool clsLEAMDNSHost::_addQueryRecord(clsLEAMDNSHost::clsSendParameter& p_rSendParameter, + const clsLEAMDNSHost::clsRRDomain& p_QueryDomain, + uint16_t p_u16RecordType) { bool bResult = false; @@ -556,7 +556,8 @@ bool clsLEAMDNSHost::_addQueryRecord(clsLEAMDNSHost::clsSendParameter& p_rSendPa Creates and sends a query for the given domain and query type. */ -bool clsLEAMDNSHost::_sendQuery(const clsLEAMDNSHost::clsQuery& p_Query, +bool clsLEAMDNSHost::_sendQuery(netif* pNetIf, + const clsLEAMDNSHost::clsQuery& p_Query, clsLEAMDNSHost::clsQuery::clsAnswer::list* p_pKnownAnswers /*= 0*/) { bool bResult = false; @@ -584,9 +585,8 @@ bool clsLEAMDNSHost::_sendQuery(const clsLEAMDNSHost::clsQuery& p_Query, // TODO: Add known answers to query (void)p_pKnownAnswers; - bResult = ((bResult) && - (_sendMessage(sendParameter))); + (_sendMessage(pNetIf, sendParameter))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendQuery: FAILED!\n"), _DH());); return bResult; } @@ -597,7 +597,8 @@ bool clsLEAMDNSHost::_sendQuery(const clsLEAMDNSHost::clsQuery& p_Query, Creates and sends a query for the given domain and record type. */ -bool clsLEAMDNSHost::_sendQuery(const clsLEAMDNSHost::clsRRDomain& p_QueryDomain, +bool clsLEAMDNSHost::_sendQuery(netif* pNetIf, + const clsLEAMDNSHost::clsRRDomain& p_QueryDomain, uint16_t p_u16RecordType, clsLEAMDNSHost::clsQuery::clsAnswer::list* p_pKnownAnswers /*= 0*/) { @@ -605,7 +606,7 @@ bool clsLEAMDNSHost::_sendQuery(const clsLEAMDNSHost::clsRRDomain& p_QueryDomain clsSendParameter sendParameter; bResult = ((_addQueryRecord(sendParameter, p_QueryDomain, p_u16RecordType)) && - (_sendMessage(sendParameter))); + (_sendMessage(pNetIf, sendParameter))); // TODO: Add known answer records (void) p_pKnownAnswers; @@ -617,16 +618,16 @@ bool clsLEAMDNSHost::_sendQuery(const clsLEAMDNSHost::clsRRDomain& p_QueryDomain /* MDNSResponder::_getResponderIPAddress */ -IPAddress clsLEAMDNSHost::_getResponderIPAddress(enuIPProtocolType p_IPProtocolType) const +IPAddress clsLEAMDNSHost::_getResponderIPAddress(netif* pNetIf, enuIPProtocolType p_IPProtocolType) const { IPAddress ipResponder; #ifdef MDNS_IPV4_SUPPORT if (enuIPProtocolType::V4 == p_IPProtocolType) { #if LWIP_VERSION_MAJOR == 1 - ipResponder = ip_2_ip4(m_pNetIf->ip_addr); + ipResponder = ip_2_ip4(pNetIf->ip_addr); #else - ipResponder = netif_ip_addr4(m_pNetIf); + ipResponder = netif_ip_addr4(pNetIf); #endif } #endif @@ -636,7 +637,6 @@ IPAddress clsLEAMDNSHost::_getResponderIPAddress(enuIPProtocolType p_IPProtocolT bool bCheckLinkLocal = true; for (int i = 0; ((!ipResponder.isSet()) && (i < 2)); ++i) // Two loops: First with link-local check, second without { - for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf)) for (int idx = 0; idx < LWIP_IPV6_NUM_ADDRESSES; ++idx) { //DEBUG_EX_INFO(if ip6_addr_isvalid(netif_ip6_addr_state(&pNetIf, idx)) DEBUG_OUTPUT.printf_P(PSTR("%s _getResponderIPAddress: Checking IPv6 address %s (LL: %s)\n"), _DH(), IPAddress(netif_ip_addr6(pNetIf, idx)).toString().c_str(), (bCheckLinkLocal ? "YES" : "NO"));); diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp index c5c465e8ad..2898bec218 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp @@ -71,10 +71,9 @@ UdpContext* clsLEAMDNSHost::clsBackbone::addHost(clsLEAMDNSHost* p_pHost) { UdpContext* pUDPContext = 0; - if ((m_pUDPContext) && - (p_pHost)) + if ((m_pUDPContext) && (p_pHost) && (m_uniqueHost == nullptr)) { - m_HostList.push_back(p_pHost); + m_uniqueHost = p_pHost; pUDPContext = m_pUDPContext; } DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s addHost: %s to add host!\n"), _DH(), (pUDPContext ? "Succeeded" : "FAILED"));); @@ -89,11 +88,9 @@ bool clsLEAMDNSHost::clsBackbone::removeHost(clsLEAMDNSHost* p_pHost) { bool bResult = false; - if ((p_pHost) && - (m_HostList.end() != std::find(m_HostList.begin(), m_HostList.end(), p_pHost))) + if ((p_pHost) && (m_uniqueHost == p_pHost)) { - // Remove host object - m_HostList.remove(p_pHost); + m_uniqueHost = nullptr; bResult = true; } DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s removeHost: %s to remove host!\n"), _DH(), (bResult ? "Succeeded" : "FAILED"));); @@ -107,7 +104,7 @@ bool clsLEAMDNSHost::clsBackbone::removeHost(clsLEAMDNSHost* p_pHost) */ size_t clsLEAMDNSHost::clsBackbone::hostCount(void) const { - return m_HostList.size(); + return m_uniqueHost == nullptr? 0: 1; } /* @@ -220,7 +217,7 @@ bool clsLEAMDNSHost::clsBackbone::_processUDPInput(void) { netif* pNetIf = m_pUDPContext->getInputNetif(); clsLEAMDNSHost* pHost = 0; - if ((pHost = _findHost(pNetIf))) + if ((pHost = _findHost())) { DEBUG_EX_INFO_IF(u32LoopCounter++, DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Multi-Loop (%u)!\n"), _DH(), u32LoopCounter); From b465c16eeedc0a94bf2ddf6b9f4f6ae14bad6e4f Mon Sep 17 00:00:00 2001 From: david gauchard Date: Mon, 25 May 2020 00:29:07 +0200 Subject: [PATCH 018/152] wip --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 29 +++++++-- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 19 +++--- .../ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 62 ++++++++++--------- .../ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 38 ++++++------ libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 10 +-- 5 files changed, 91 insertions(+), 67 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 58d0a6f07b..7f53746038 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -431,9 +431,18 @@ bool clsLEAMDNSHost::removeService(clsLEAMDNSHost::clsService* p_pService) { bool bResult = false; - if ((bResult = ((p_pService) && - (m_Services.end() != std::find(m_Services.begin(), m_Services.end(), p_pService)) && - (_announceService(*p_pService, false))))) + if (p_pService && + (m_Services.end() != std::find(m_Services.begin(), m_Services.end(), p_pService))) + { + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + if (netif_is_up(pNetIf) && + (_announceService(pNetIf, *p_pService, false))) + { + bResult = true; + } + } + + if (bResult) { m_Services.remove(p_pService); delete p_pService; @@ -760,7 +769,7 @@ bool clsLEAMDNSHost::update(void) //if (clsBackbone::sm_pBackbone->setDelayUDPProcessing(true)) //{ if ((_updateProbeStatus(pNetIf)) && // Probing and announcing - (_checkQueryCache())) + (_checkQueryCache(pNetIf))) { bResult = true; } @@ -778,7 +787,11 @@ bool clsLEAMDNSHost::update(void) bool clsLEAMDNSHost::announce(bool p_bAnnounce /*= true*/, bool p_bIncludeServices /*= true*/) { - return _announce(p_bAnnounce, p_bIncludeServices); + bool bResult = false; + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + if (netif_is_up(pNetIf) && _announce(pNetIf, p_bAnnounce, p_bIncludeServices)) + bResult = true; + return bResult; } /* @@ -787,7 +800,11 @@ bool clsLEAMDNSHost::announce(bool p_bAnnounce /*= true*/, bool clsLEAMDNSHost::announceService(clsService * p_pService, bool p_bAnnounce /*= true*/) { - return _announceService(*p_pService, p_bAnnounce); + bool bResult = false; + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + if (netif_is_up(pNetIf) && _announceService(pNetIf, *p_pService, p_bAnnounce)) + bResult = true; + return bResult; } /* diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index d5bdffcb09..6ef6aa69da 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -278,6 +278,7 @@ class clsLEAMDNSHost // File: ..._Backbone /** clsBackbone + XXXX should be merged with holder clsLEAMDNSHost because there is no list anymore in it */ class clsBackbone { @@ -292,6 +293,8 @@ class clsLEAMDNSHost bool removeHost(clsLEAMDNSHost* p_pHost); size_t hostCount(void) const; bool setDelayUDPProcessing(bool p_bDelayProcessing); + + clsLEAMDNSHost* getUniqueHost() { return m_uniqueHost; } protected: UdpContext* m_pUDPContext; @@ -1438,12 +1441,12 @@ class clsLEAMDNSHost // File: ..._Host_Control // RECEIVING - bool _parseMessage(void); + bool _parseMessage(); bool _parseQuery(netif* pNetIf, const clsMsgHeader& p_Header); - bool _parseResponse(const clsMsgHeader& p_Header); - bool _processAnswers(const clsRRAnswer* p_pPTRAnswers); + bool _parseResponse(netif* pNetIf, const clsMsgHeader& p_Header); + bool _processAnswers(netif* pNetIf, const clsRRAnswer* p_pPTRAnswers); bool _processPTRAnswer(const clsRRAnswerPTR* p_pPTRAnswer, bool& p_rbFoundNewKeyAnswer); bool _processSRVAnswer(const clsRRAnswerSRV* p_pSRVAnswer, @@ -1477,9 +1480,10 @@ class clsLEAMDNSHost bool p_bAnnounce = true); // QUERY CACHE - bool _checkQueryCache(void); + bool _checkQueryCache(netif* pNetIf); - uint32_t _replyMaskForHost(const clsRRHeader& p_RRHeader, + uint32_t _replyMaskForHost(netif* pNetIf, + const clsRRHeader& p_RRHeader, bool* p_pbFullNameMatch = 0) const; uint32_t _replyMaskForService(const clsRRHeader& p_RRHeader, clsService& p_rService, @@ -1492,7 +1496,7 @@ class clsLEAMDNSHost bool _sendMessage_Multicast(netif* pNetIf, clsSendParameter& p_rSendParameter, uint8_t p_IPProtocolTypes); - bool _prepareMessage(clsSendParameter& p_SendParameter); + bool _prepareMessage(netif* pNetIf, clsSendParameter& p_SendParameter); bool _addQueryRecord(clsSendParameter& p_rSendParameter, const clsRRDomain& p_QueryDomain, uint16_t p_u16QueryType); @@ -1504,7 +1508,8 @@ class clsLEAMDNSHost uint16_t p_u16RecordType, clsQuery::clsAnswer::list* p_pKnownAnswers = 0); - IPAddress _getResponderIPAddress(enuIPProtocolType p_IPProtocolType) const; + IPAddress _getResponderIPAddress(netif* pNetIf, + enuIPProtocolType p_IPProtocolType) const; // RESOURCE RECORD bool _readRRQuestion(clsRRQuestion& p_rQuestion); diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index 48eba00b0d..d4e592843c 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -44,7 +44,7 @@ namespace experimental clsLEAmDNS2_Host::_parseMessage */ -bool clsLEAMDNSHost::_parseMessage(void) +bool clsLEAMDNSHost::_parseMessage() { DEBUG_EX_INFO( unsigned long ulStartTime = millis(); @@ -54,6 +54,7 @@ bool clsLEAMDNSHost::_parseMessage(void) m_pUDPContext->getDestAddress().toString().c_str()); ); //DEBUG_EX_INFO(_udpDump();); + netif* pNetIf = m_pUDPContext->getInputNetif(); bool bResult = false; @@ -76,7 +77,7 @@ bool clsLEAMDNSHost::_parseMessage(void) { // Received a response -> answers to a query //DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseMessage: Reading answers: ID:%u, Q:%u, A:%u, NS:%u, AR:%u\n"), _DH(), header.m_u16ID, header.m_u16QDCount, header.m_u16ANCount, header.m_u16NSCount, header.m_u16ARCount);); - bResult = _parseResponse(header); + bResult = _parseResponse(pNetIf, header); } else { @@ -135,7 +136,7 @@ bool clsLEAMDNSHost::_parseQuery(netif* pNetIf, // Define host replies, BUT only answer queries after probing is done u32HostOrServiceReplies = sendParameter.m_u32HostReplyMask |= ((probeStatus()) - ? _replyMaskForHost(questionRR.m_Header, 0) + ? _replyMaskForHost(pNetIf, questionRR.m_Header, 0) : 0); DEBUG_EX_INFO(if (u32HostOrServiceReplies) DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Host reply needed %s\n"), _DH(), _replyFlags2String(u32HostOrServiceReplies));); @@ -143,7 +144,7 @@ bool clsLEAMDNSHost::_parseQuery(netif* pNetIf, if (clsProbeInformation_Base::clsProbeInformation_Base::enuProbingStatus::InProgress == m_ProbeInformation.m_ProbingStatus) { bool bFullNameMatch = false; - if ((_replyMaskForHost(questionRR.m_Header, &bFullNameMatch)) && + if ((_replyMaskForHost(pNetIf, questionRR.m_Header, &bFullNameMatch)) && (bFullNameMatch)) { // We're in 'probing' state and someone is asking for our host domain: this might be @@ -282,7 +283,7 @@ bool clsLEAMDNSHost::_parseQuery(netif* pNetIf, { /* - RFC6762 7.1 Suppression only for 'Shared Records' - // Find match between planned answer (sendParameter.m_u8HostReplyMask) and this 'known answer' - uint32_t u32HostMatchMask = (sendParameter.m_u32HostReplyMask & _replyMaskForHost(pKnownRRAnswer->m_Header)); + uint32_t u32HostMatchMask = (sendParameter.m_u32HostReplyMask & _replyMaskForHost(pNetIf, pKnownRRAnswer->m_Header)); if ((u32HostMatchMask) && // The RR in the known answer matches an RR we are planning to send, AND ((Consts::u32HostTTL / 2) <= pKnownRRAnswer->m_u32TTL)) // The TTL of the known answer is longer than half of the new host TTL (120s) { @@ -318,7 +319,7 @@ bool clsLEAMDNSHost::_parseQuery(netif* pNetIf, // IPv4 address was asked for #ifdef MDNS_IPV4_SUPPORT if ((enuAnswerType::A == pKnownRRAnswer->answerType()) && - (((stcRRAnswerA*)pKnownRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V4))) + (((stcRRAnswerA*)pKnownRRAnswer)->m_IPAddress == _getResponderIPAddress(pNetIf, enuIPProtocolType::V4))) { DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: IPv4 address already known (TTL:%u)... skipping!\n"), _DH(), pKnownRRAnswer->m_u32TTL);); @@ -331,7 +332,7 @@ bool clsLEAMDNSHost::_parseQuery(netif* pNetIf, // IPv6 address was asked for #ifdef MDNS_IPV6_SUPPORT if ((enuAnswerType::AAAA == pKnownRRAnswer->answerType()) && - (((stcRRAnswerAAAA*)pKnownRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V6))) + (((stcRRAnswerAAAA*)pKnownRRAnswer)->m_IPAddress == _getResponderIPAddress(pNetIf, enuIPProtocolType::V6))) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: IPv6 address already known... skipping!\n"), _DH());); @@ -355,7 +356,7 @@ bool clsLEAMDNSHost::_parseQuery(netif* pNetIf, if (enuAnswerType::A == pKnownRRAnswer->answerType()) { // CHECK - IPAddress localIPAddress(_getResponderIPAddress(enuIPProtocolType::V4)); + IPAddress localIPAddress(_getResponderIPAddress(pNetIf, enuIPProtocolType::V4)); if (((clsRRAnswerA*)pKnownRRAnswer)->m_IPAddress == localIPAddress) { // SAME IP address -> We've received an old message from ourselfs (same IP) @@ -383,7 +384,7 @@ bool clsLEAMDNSHost::_parseQuery(netif* pNetIf, #ifdef MDNS_IPV6_SUPPORT if (enuAnswerType::AAAA == pKnownRRAnswer->answerType()) { - IPAddress localIPAddress(_getResponderIPAddress(enuIPProtocolType::V6)); + IPAddress localIPAddress(_getResponderIPAddress(pNetIf, enuIPProtocolType::V6)); if (((clsRRAnswerAAAA*)pKnownRRAnswer)->m_IPAddress == localIPAddress) { // SAME IP address -> We've received an old message from ourselfs (same IP) @@ -608,7 +609,7 @@ bool clsLEAMDNSHost::_parseQuery(netif* pNetIf, TXT - links the instance name to services TXTs Level 3: A/AAAA - links the host domain to an IP address */ -bool clsLEAMDNSHost::_parseResponse(const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader) +bool clsLEAMDNSHost::_parseResponse(netif* pNetIf, const clsLEAMDNSHost::clsMsgHeader& p_MsgHeader) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _parseResponse\n"));); //DEBUG_EX_INFO(_udpDump();); @@ -665,7 +666,7 @@ bool clsLEAMDNSHost::_parseResponse(const clsLEAMDNSHost::clsMsgHeader& p_MsgHea if (bResult) { bResult = ((!pCollectedRRAnswers) || - (_processAnswers(pCollectedRRAnswers))); + (_processAnswers(pNetIf, pCollectedRRAnswers))); } else // Some failure while reading answers { @@ -726,7 +727,7 @@ bool clsLEAMDNSHost::_parseResponse(const clsLEAMDNSHost::clsMsgHeader& p_MsgHea TXT (0x10): eg. MyESP._http._tcp.local TXT OP TTL c#=1 */ -bool clsLEAMDNSHost::_processAnswers(const clsLEAMDNSHost::clsRRAnswer* p_pAnswers) +bool clsLEAMDNSHost::_processAnswers(netif* pNetIf, const clsLEAMDNSHost::clsRRAnswer* p_pAnswers) { bool bResult = false; @@ -796,14 +797,14 @@ bool clsLEAMDNSHost::_processAnswers(const clsLEAMDNSHost::clsRRAnswer* p_pAnswe bool bPossibleEcho = false; #ifdef MDNS_IPV4_SUPPORT if ((enuAnswerType::A == pRRAnswer->answerType()) && - (((clsRRAnswerA*)pRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V4))) + (((clsRRAnswerA*)pRRAnswer)->m_IPAddress == _getResponderIPAddress(pNetIf, enuIPProtocolType::V4))) { bPossibleEcho = true; } #endif #ifdef MDNS_IPV6_SUPPORT if ((enuAnswerType::AAAA == pRRAnswer->answerType()) && - (((clsRRAnswerAAAA*)pRRAnswer)->m_IPAddress == _getResponderIPAddress(enuIPProtocolType::V6))) + (((clsRRAnswerAAAA*)pRRAnswer)->m_IPAddress == _getResponderIPAddress(pNetIf, enuIPProtocolType::V6))) { bPossibleEcho = true; } @@ -1291,13 +1292,13 @@ bool clsLEAMDNSHost::_updateProbeStatus(netif* pNetIf) if ((clsProbeInformation_Base::enuProbingStatus::ReadyToStart == m_ProbeInformation.m_ProbingStatus) && // Ready to get started AND (( #ifdef MDNS_IPV4_SUPPORT - _getResponderIPAddress(enuIPProtocolType::V4).isSet() // AND has IPv4 address + _getResponderIPAddress(pNetIf, enuIPProtocolType::V4).isSet() // AND has IPv4 address #else true #endif ) || ( #ifdef MDNS_IPV6_SUPPORT - _getResponderIPAddress(enuIPProtocolType::V6).isSet() // OR has IPv6 address + _getResponderIPAddress(pNetIf, enuIPProtocolType::V6).isSet() // OR has IPv6 address #else true #endif @@ -1340,7 +1341,7 @@ bool clsLEAMDNSHost::_updateProbeStatus(netif* pNetIf) else if ((clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == m_ProbeInformation.m_ProbingStatus) && (m_ProbeInformation.m_Timeout.expired())) { - if ((bResult = _announce(true, false))) + if ((bResult = _announce(pNetIf, true, false))) { // Don't announce services here ++m_ProbeInformation.m_u8SentCount; // 1.. @@ -1405,7 +1406,7 @@ bool clsLEAMDNSHost::_updateProbeStatus(netif* pNetIf) (pService->m_ProbeInformation.m_Timeout.expired())) { // Probing already finished OR waiting for next time slot - if ((bResult = _announceService(*pService))) + if ((bResult = _announceService(pNetIf, *pService))) { // Announce service ++pService->m_ProbeInformation.m_u8SentCount; // 1.. @@ -1771,7 +1772,7 @@ bool clsLEAMDNSHost::_announceService(netif* pNetIf, When no update arrived (in time), the component is removed from the answer (cache). */ -bool clsLEAMDNSHost::_checkQueryCache(void) +bool clsLEAMDNSHost::_checkQueryCache(netif* pNetIf) { bool bResult = true; @@ -1786,7 +1787,7 @@ bool clsLEAMDNSHost::_checkQueryCache(void) if ((!pQuery->m_bStaticQuery) && (pQuery->m_ResendTimeout.expired())) { - if ((bResult = _sendQuery(*pQuery))) + if ((bResult = _sendQuery(pNetIf, *pQuery))) { // The re-query rate is increased to more than one hour (RFC 6762 5.2) ++pQuery->m_u8SentCount; @@ -1819,7 +1820,7 @@ bool clsLEAMDNSHost::_checkQueryCache(void) { if (!pQAnswer->m_TTLServiceDomain.finalTimeoutLevel()) { - bResult = ((_sendQuery(*pQuery)) && + bResult = ((_sendQuery(pNetIf, *pQuery)) && (pQAnswer->m_TTLServiceDomain.restart())); DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: PTR update scheduled for "), _DH()); @@ -1851,7 +1852,7 @@ bool clsLEAMDNSHost::_checkQueryCache(void) { if (!pQAnswer->m_TTLHostDomainAndPort.finalTimeoutLevel()) { - bResult = ((_sendQuery(pQAnswer->m_ServiceDomain, DNS_RRTYPE_SRV)) && + bResult = ((_sendQuery(pNetIf, pQAnswer->m_ServiceDomain, DNS_RRTYPE_SRV)) && (pQAnswer->m_TTLHostDomainAndPort.restart())); DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: SRV update scheduled for "), _DH()); @@ -1897,7 +1898,7 @@ bool clsLEAMDNSHost::_checkQueryCache(void) { if (!pQAnswer->m_TTLTxts.finalTimeoutLevel()) { - bResult = ((_sendQuery(pQAnswer->m_ServiceDomain, DNS_RRTYPE_TXT)) && + bResult = ((_sendQuery(pNetIf, pQAnswer->m_ServiceDomain, DNS_RRTYPE_TXT)) && (pQAnswer->m_TTLTxts.restart())); DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: TXT update scheduled for "), _DH()); @@ -1940,7 +1941,7 @@ bool clsLEAMDNSHost::_checkQueryCache(void) { // Needs update if ((bAUpdateQuerySent) || - ((bResult = _sendQuery(pQAnswer->m_HostDomain, DNS_RRTYPE_A)))) + ((bResult = _sendQuery(pNetIf, pQAnswer->m_HostDomain, DNS_RRTYPE_A)))) { pIPv4Address->m_TTL.restart(); bAUpdateQuerySent = true; @@ -1993,7 +1994,7 @@ bool clsLEAMDNSHost::_checkQueryCache(void) { // Needs update if ((bAAAAUpdateQuerySent) || - ((bResult = _sendQuery(pQAnswer->m_HostDomain, DNS_RRTYPE_AAAA)))) + ((bResult = _sendQuery(pNetIf, pQAnswer->m_HostDomain, DNS_RRTYPE_AAAA)))) { pIPv6Address->m_TTL.restart(); bAAAAUpdateQuerySent = true; @@ -2056,7 +2057,8 @@ bool clsLEAMDNSHost::_checkQueryCache(void) In addition, a full name match (question domain == host domain) is marked. */ -uint32_t clsLEAMDNSHost::_replyMaskForHost(const clsLEAMDNSHost::clsRRHeader& p_RRHeader, +uint32_t clsLEAMDNSHost::_replyMaskForHost(netif* pNetIf, + const clsLEAMDNSHost::clsRRHeader& p_RRHeader, bool* p_pbFullNameMatch /*= 0*/) const { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _replyMaskForHost\n"));); @@ -2074,8 +2076,8 @@ uint32_t clsLEAMDNSHost::_replyMaskForHost(const clsLEAMDNSHost::clsRRHeader& p_ // PTR request #ifdef MDNS_IPV4_SUPPORT clsRRDomain reverseIPv4Domain; - if ((_getResponderIPAddress(enuIPProtocolType::V4).isSet()) && - (_buildDomainForReverseIPv4(_getResponderIPAddress(enuIPProtocolType::V4), reverseIPv4Domain)) && + if ((_getResponderIPAddress(pNetIf, enuIPProtocolType::V4).isSet()) && + (_buildDomainForReverseIPv4(_getResponderIPAddress(pNetIf, enuIPProtocolType::V4), reverseIPv4Domain)) && (p_RRHeader.m_Domain == reverseIPv4Domain)) { // Reverse domain match @@ -2084,8 +2086,8 @@ uint32_t clsLEAMDNSHost::_replyMaskForHost(const clsLEAMDNSHost::clsRRHeader& p_ #endif #ifdef MDNS_IPV6_SUPPORT clsRRDomain reverseIPv6Domain; - if ((_getResponderIPAddress(enuIPProtocolType::V6).isSet()) && - (_buildDomainForReverseIPv6(_getResponderIPAddress(enuIPProtocolType::V6), reverseIPv6Domain)) && + if ((_getResponderIPAddress(pNetIf, enuIPProtocolType::V6).isSet()) && + (_buildDomainForReverseIPv6(_getResponderIPAddress(pNetIf, enuIPProtocolType::V6), reverseIPv6Domain)) && (p_RRHeader.m_Domain == reverseIPv6Domain)) { // Reverse domain match diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 1d1a4df1b1..df34f81787 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -94,7 +94,7 @@ bool clsLEAMDNSHost::_sendMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParamete DEBUG_EX_ERR(if (!ipRemote.isSet()) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage: MISSING remote address for unicast response!\n"), _DH());); bResult = ((ipRemote.isSet()) && - (_prepareMessage(p_rSendParameter)) && + (_prepareMessage(pNetIf, p_rSendParameter)) && (m_pUDPContext->send(ipRemote, m_pUDPContext->getRemotePort())) /*&& (Serial.println("Did send UC"), true)*/); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage (V4): FAILED!\n"), _DH());); @@ -165,7 +165,7 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(netif* pNetIf, clsLEAMDNSHost::clsSe DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast IPv4: Will send to '%s'.\n"), _DH(), ip4MulticastAddress.toString().c_str());); DEBUG_EX_INFO(if (!_getResponderIPAddress(pNetIf, enuIPProtocolType::V4)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast IPv4: NO IPv4 address!.\n"), _DH());); - bIPv4Result = ((_prepareMessage(p_rSendParameter)) && + bIPv4Result = ((_prepareMessage(pNetIf, p_rSendParameter)) && (m_pUDPContext->setMulticastInterface(pNetIf), true) && (m_pUDPContext->send(ip4MulticastAddress, DNS_MQUERY_PORT)) && (m_pUDPContext->setMulticastInterface(0), true) /*&& @@ -191,7 +191,7 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(netif* pNetIf, clsLEAMDNSHost::clsSe bool bPrepareMessage = false; bool bUDPContextSend = false; ); - bIPv6Result = ((DEBUG_EX_ERR(bPrepareMessage =)_prepareMessage(p_rSendParameter)) && + bIPv6Result = ((DEBUG_EX_ERR(bPrepareMessage =)_prepareMessage(pNetIf, p_rSendParameter)) && (m_pUDPContext->setMulticastInterface(pNetIf), true) && (DEBUG_EX_ERR(bUDPContextSend =)m_pUDPContext->send(ip6MulticastAddress, DNS_MQUERY_PORT)) && (m_pUDPContext->setMulticastInterface(0), true) /*&& @@ -220,7 +220,7 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(netif* pNetIf, clsLEAMDNSHost::clsSe output buffer. */ -bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +bool clsLEAMDNSHost::_prepareMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage\n"));); bool bResult = true; @@ -314,25 +314,25 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa // AAAA if ((bResult) && (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::AAAA)) && - (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6).isSet())) + (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)).isSet())) { u32NSECContent |= static_cast(enuContentFlag::AAAA); ((static_cast(enuSequence::Count) == sequence) ? ++ru16Answers - : (bResult = _writeMDNSAnswer_AAAA(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6), p_rSendParameter))); + : (bResult = _writeMDNSAnswer_AAAA(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)), p_rSendParameter))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_AAAA(A) FAILED!\n"), _DH());); } // PTR_IPv6 if ((bResult) && (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::PTR_IPv6)) && - (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6).isSet())) + (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)).isSet())) { u32NSECContent |= static_cast(enuContentFlag::PTR_IPv6); ((static_cast(enuSequence::Count) == sequence) ? ++ru16Answers - : (bResult = _writeMDNSAnswer_PTR_IPv6(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6), p_rSendParameter))); + : (bResult = _writeMDNSAnswer_PTR_IPv6(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)), p_rSendParameter))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_PTR_IPv6 FAILED!\n"), _DH());); } #endif @@ -446,13 +446,13 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa // Answer A needed? if ((bResult) && (bNeedsAdditionalAnswerA) && - (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4).isSet())) + (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4)).isSet())) { // Additional A u32NSECContent |= static_cast(enuContentFlag::A); ((static_cast(enuSequence::Count) == sequence) ? ++ru16AdditionalAnswers - : (bResult = _writeMDNSAnswer_A(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4), p_rSendParameter))); + : (bResult = _writeMDNSAnswer_A(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4)), p_rSendParameter))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_A(B) FAILED!\n"), _DH());); } @@ -461,13 +461,13 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa // Answer AAAA needed? if ((bResult) && (bNeedsAdditionalAnswerAAAA) && - (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6).isSet())) + (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)).isSet())) { // Additional AAAA u32NSECContent |= static_cast(enuContentFlag::AAAA); ((static_cast(enuSequence::Count) == sequence) ? ++ru16AdditionalAnswers - : (bResult = _writeMDNSAnswer_AAAA(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6), p_rSendParameter))); + : (bResult = _writeMDNSAnswer_AAAA(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)), p_rSendParameter))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_AAAA(B) FAILED!\n"), _DH());); } @@ -503,24 +503,24 @@ bool clsLEAMDNSHost::_prepareMessage(clsLEAMDNSHost::clsSendParameter& p_rSendPa #ifdef MDNS_IPV4_SUPPORT // Write separate answer for host PTR IPv4 && ((!u32NSECContent_PTR_IPv4) || - ((!_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4).isSet()) || - (_writeMDNSAnswer_NSEC_PTR_IPv4(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4), p_rSendParameter)))) + ((!_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4)).isSet()) || + (_writeMDNSAnswer_NSEC_PTR_IPv4(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4)), p_rSendParameter)))) #endif #ifdef MDNS_IPV6_SUPPORT // Write separate answer for host PTR IPv6 && ((!u32NSECContent_PTR_IPv6) || - ((!_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6).isSet()) || - (_writeMDNSAnswer_NSEC_PTR_IPv6(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6), p_rSendParameter)))) + ((!_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)).isSet()) || + (_writeMDNSAnswer_NSEC_PTR_IPv6(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)), p_rSendParameter)))) #endif ))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_NSEC(Host) FAILED!\n"), _DH());); } - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: Loop %i FAILED!\n"), _DH(), sequence);); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: Loop %i FAILED!\n"), _DH(), sequence);); } // for sequence - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: FAILED!\n"), _DH());); - return bResult; + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: FAILED!\n"), _DH());); + return bResult; } /* diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index 551a754cdf..0c0be01906 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -120,17 +120,17 @@ bool clsLEAMDNSHost_Legacy::addHostForNetIf(const char* p_pcHostname) { bool bResult = true; - if (m_HostInformations.length() > 0) + if (m_HostInformations.size() > 0) { //XXXFIXME only one pHost instance, many things can be simplified bResult = false; } else { - clsLEAMDNSHost* pHost = &esp8266::experimental::clsLEAMDNSHost::clsBackbone::sm_pBackbone.m_uniqueHost; - - if ((!((pHost->begin(p_pcHostname /*, default callback*/)) - && (m_HostInformations.push_back(stcHostInformation(pHost)), true)))) + clsLEAMDNSHost* pHost = new esp8266::experimental::clsLEAMDNSHost; + if (pHost + && (!((pHost->begin(p_pcHostname /*, default callback*/)) + && (m_HostInformations.push_back(stcHostInformation(pHost)), true)))) { bResult = false; } From 6646acba392d7f7e930f829004428bba29e7352d Mon Sep 17 00:00:00 2001 From: david gauchard Date: Mon, 25 May 2020 12:10:20 +0200 Subject: [PATCH 019/152] wip - compilation OK - not tested --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 24 +++-- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 46 +++++---- .../ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 96 +++++++++---------- .../ESP8266mDNS/src/LEAmDNS2_Backbone.cpp | 46 ++++----- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 39 +++++--- 5 files changed, 137 insertions(+), 114 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 7f53746038..706cd94006 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -432,11 +432,11 @@ bool clsLEAMDNSHost::removeService(clsLEAMDNSHost::clsService* p_pService) bool bResult = false; if (p_pService && - (m_Services.end() != std::find(m_Services.begin(), m_Services.end(), p_pService))) + (m_Services.end() != std::find(m_Services.begin(), m_Services.end(), p_pService))) { for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf) && - (_announceService(pNetIf, *p_pService, false))) + (_announceService(pNetIf, *p_pService, false))) { bResult = true; } @@ -677,7 +677,8 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::getQuery(void) */ /*clsLEAMDNSHost::clsQuery* */ bool clsLEAMDNSHost::installServiceQuery(const char* p_pcService, const char* p_pcProtocol, - clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor) + clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor, + std::list* ret) { bool bResult = false; clsQuery* pQuery = 0; @@ -686,6 +687,10 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::getQuery(void) { pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; bResult = true; + if (ret) + { + ret->push_back(pQuery); + } } return bResult; } @@ -741,16 +746,13 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::getQuery(void) /* clsLEAmDNS2_Host::removeQuery */ -/* - bool clsLEAMDNSHost::removeQuery(clsLEAMDNSHost::clsQuery * p_pMDNSQuery) - { +bool clsLEAMDNSHost::removeQuery(clsLEAMDNSHost::clsQuery * p_pMDNSQuery) +{ bool bResult = ((p_pMDNSQuery) && (_removeQuery(p_pMDNSQuery))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s removeQuery: FAILED!\n"), _DH());); return bResult; - } -*/ - +} /* PROCESSING @@ -790,7 +792,9 @@ bool clsLEAMDNSHost::announce(bool p_bAnnounce /*= true*/, bool bResult = false; for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf) && _announce(pNetIf, p_bAnnounce, p_bIncludeServices)) + { bResult = true; + } return bResult; } @@ -803,7 +807,9 @@ bool clsLEAMDNSHost::announceService(clsService * p_pService, bool bResult = false; for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf) && _announceService(pNetIf, *p_pService, p_bAnnounce)) + { bResult = true; + } return bResult; } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index 6ef6aa69da..ab5dd4003c 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -293,8 +293,11 @@ class clsLEAMDNSHost bool removeHost(clsLEAMDNSHost* p_pHost); size_t hostCount(void) const; bool setDelayUDPProcessing(bool p_bDelayProcessing); - - clsLEAMDNSHost* getUniqueHost() { return m_uniqueHost; } + + clsLEAMDNSHost* getUniqueHost() + { + return m_uniqueHost; + } protected: UdpContext* m_pUDPContext; @@ -308,8 +311,14 @@ class clsLEAMDNSHost bool _processUDPInput(void); - const clsLEAMDNSHost* _findHost() const { return m_uniqueHost; } - clsLEAMDNSHost* _findHost() { return m_uniqueHost; } + const clsLEAMDNSHost* _findHost() const + { + return m_uniqueHost; + } + clsLEAMDNSHost* _findHost() + { + return m_uniqueHost; + } const char* _DH(void) const; }; @@ -1330,23 +1339,24 @@ class clsLEAMDNSHost // - hasAnswerIPv6Address/answerIPv6Address service/host // - hasAnswerPort/answerPort service // - hasAnswerTxts/answerTxts service - + /* - install*Query() creates several queries on the interfaces. - it no more returns a single query but a boolean until the API is adapted + install*Query() creates several queries on the interfaces. + it no more returns a single query but a boolean until the API is adapted */ - /*clsQuery**/bool installServiceQuery(const char* p_pcServiceType, - const char* p_pcProtocol, - clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer); - /*clsQuery**/bool installServiceQuery(const char* p_pcServiceType, - const char* p_pcProtocol, - clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor); - /*clsQuery**/bool installHostQuery(const char* p_pcHostName, - clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer); - /*clsQuery**/bool installHostQuery(const char* p_pcHostName, - clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor); + /*clsQuery* */bool installServiceQuery(const char* p_pcServiceType, + const char* p_pcProtocol, + clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer); + /*clsQuery* */bool installServiceQuery(const char* p_pcServiceType, + const char* p_pcProtocol, + clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor, + std::list* ret = nullptr); + /*clsQuery* */bool installHostQuery(const char* p_pcHostName, + clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer); + /*clsQuery* */bool installHostQuery(const char* p_pcHostName, + clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor); // Remove a dynamic service query - /*bool removeQuery(clsQuery* p_pQuery);*/ + bool removeQuery(clsQuery* p_pQuery); // PROCESSING bool update(void); diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index df34f81787..72fe253730 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -315,34 +315,34 @@ bool clsLEAMDNSHost::_prepareMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParam if ((bResult) && (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::AAAA)) && (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)).isSet())) - { + { - u32NSECContent |= static_cast(enuContentFlag::AAAA); + u32NSECContent |= static_cast(enuContentFlag::AAAA); ((static_cast(enuSequence::Count) == sequence) ? ++ru16Answers : (bResult = _writeMDNSAnswer_AAAA(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)), p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_AAAA(A) FAILED!\n"), _DH());); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_AAAA(A) FAILED!\n"), _DH());); } - // PTR_IPv6 - if ((bResult) && + // PTR_IPv6 + if ((bResult) && (p_rSendParameter.m_u32HostReplyMask & static_cast(enuContentFlag::PTR_IPv6)) && (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)).isSet())) - { + { - u32NSECContent |= static_cast(enuContentFlag::PTR_IPv6); + u32NSECContent |= static_cast(enuContentFlag::PTR_IPv6); ((static_cast(enuSequence::Count) == sequence) ? ++ru16Answers : (bResult = _writeMDNSAnswer_PTR_IPv6(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)), p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_PTR_IPv6 FAILED!\n"), _DH());); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_PTR_IPv6 FAILED!\n"), _DH());); } #endif - for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) - { - clsService* pService = *it; + for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) + { + clsService* pService = *it; - // PTR_TYPE - if ((bResult) && + // PTR_TYPE + if ((bResult) && (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_TYPE))) { ((static_cast(enuSequence::Count) == sequence) @@ -383,16 +383,16 @@ bool clsLEAMDNSHost::_prepareMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParam uint16_t& ru16AdditionalAnswers = msgHeader.m_u16ARCount; #ifdef MDNS_IPV4_SUPPORT - bool bNeedsAdditionalAnswerA = false; + bool bNeedsAdditionalAnswerA = false; #endif #ifdef MDNS_IPV6_SUPPORT - bool bNeedsAdditionalAnswerAAAA = false; + bool bNeedsAdditionalAnswerAAAA = false; #endif - for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) - { - clsService* pService = *it; + for (clsService::list::iterator it = m_Services.begin(); ((bResult) && (it != m_Services.end())); it++) + { + clsService* pService = *it; - if ((bResult) && + if ((bResult) && (pService->m_u32ReplyMask & static_cast(enuContentFlag::PTR_NAME)) && // If PTR_NAME is requested, AND (!(pService->m_u32ReplyMask & static_cast(enuContentFlag::SRV)))) // NOT SRV -> add SRV as additional answer { @@ -447,40 +447,40 @@ bool clsLEAMDNSHost::_prepareMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParam if ((bResult) && (bNeedsAdditionalAnswerA) && (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4)).isSet())) - { - // Additional A - u32NSECContent |= static_cast(enuContentFlag::A); + { + // Additional A + u32NSECContent |= static_cast(enuContentFlag::A); ((static_cast(enuSequence::Count) == sequence) ? ++ru16AdditionalAnswers : (bResult = _writeMDNSAnswer_A(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4)), p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_A(B) FAILED!\n"), _DH());); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_A(B) FAILED!\n"), _DH());); } #endif #ifdef MDNS_IPV6_SUPPORT - // Answer AAAA needed? - if ((bResult) && + // Answer AAAA needed? + if ((bResult) && (bNeedsAdditionalAnswerAAAA) && (_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)).isSet())) - { - // Additional AAAA - u32NSECContent |= static_cast(enuContentFlag::AAAA); + { + // Additional AAAA + u32NSECContent |= static_cast(enuContentFlag::AAAA); ((static_cast(enuSequence::Count) == sequence) ? ++ru16AdditionalAnswers : (bResult = _writeMDNSAnswer_AAAA(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)), p_rSendParameter))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_AAAA(B) FAILED!\n"), _DH());); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_AAAA(B) FAILED!\n"), _DH());); } #endif - // NSEC host (part 2) - if ((bResult) && + // NSEC host (part 2) + if ((bResult) && ((clsSendParameter::enuResponseType::None != p_rSendParameter.m_Response)) && (u32NSECContent)) - { - // NSEC PTR IPv4/IPv6 are separate answers; make sure, that this is counted for + { + // NSEC PTR IPv4/IPv6 are separate answers; make sure, that this is counted for #ifdef MDNS_IPV4_SUPPORT - uint32_t u32NSECContent_PTR_IPv4 = (u32NSECContent & static_cast(enuContentFlag::PTR_IPv4)); + uint32_t u32NSECContent_PTR_IPv4 = (u32NSECContent & static_cast(enuContentFlag::PTR_IPv4)); u32NSECContent &= ~static_cast(enuContentFlag::PTR_IPv4); #endif #ifdef MDNS_IPV6_SUPPORT @@ -504,17 +504,17 @@ bool clsLEAMDNSHost::_prepareMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParam // Write separate answer for host PTR IPv4 && ((!u32NSECContent_PTR_IPv4) || ((!_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4)).isSet()) || - (_writeMDNSAnswer_NSEC_PTR_IPv4(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4)), p_rSendParameter)))) + (_writeMDNSAnswer_NSEC_PTR_IPv4(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V4)), p_rSendParameter)))) #endif #ifdef MDNS_IPV6_SUPPORT - // Write separate answer for host PTR IPv6 - && ((!u32NSECContent_PTR_IPv6) || - ((!_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)).isSet()) || - (_writeMDNSAnswer_NSEC_PTR_IPv6(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)), p_rSendParameter)))) + // Write separate answer for host PTR IPv6 + && ((!u32NSECContent_PTR_IPv6) || + ((!_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)).isSet()) || + (_writeMDNSAnswer_NSEC_PTR_IPv6(_getResponderIPAddress(pNetIf, (enuIPProtocolType::V6)), p_rSendParameter)))) #endif - ))); + ))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_NSEC(Host) FAILED!\n"), _DH());); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: _writeMDNSAnswer_NSEC(Host) FAILED!\n"), _DH());); } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: Loop %i FAILED!\n"), _DH(), sequence);); @@ -523,15 +523,15 @@ bool clsLEAMDNSHost::_prepareMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParam return bResult; } - /* - MDNSResponder::_addQueryRecord +/* + MDNSResponder::_addQueryRecord - Adds a query for the given domain and query type. + Adds a query for the given domain and query type. - */ - bool clsLEAMDNSHost::_addQueryRecord(clsLEAMDNSHost::clsSendParameter& p_rSendParameter, - const clsLEAMDNSHost::clsRRDomain& p_QueryDomain, - uint16_t p_u16RecordType) +*/ +bool clsLEAMDNSHost::_addQueryRecord(clsLEAMDNSHost::clsSendParameter& p_rSendParameter, + const clsLEAMDNSHost::clsRRDomain& p_QueryDomain, + uint16_t p_u16RecordType) { bool bResult = false; diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp index 2898bec218..a382f071a1 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp @@ -104,7 +104,7 @@ bool clsLEAMDNSHost::clsBackbone::removeHost(clsLEAMDNSHost* p_pHost) */ size_t clsLEAMDNSHost::clsBackbone::hostCount(void) const { - return m_uniqueHost == nullptr? 0: 1; + return m_uniqueHost == nullptr ? 0 : 1; } /* @@ -215,33 +215,23 @@ bool clsLEAMDNSHost::clsBackbone::_processUDPInput(void) while ((m_pUDPContext) && (m_pUDPContext->next())) { - netif* pNetIf = m_pUDPContext->getInputNetif(); - clsLEAMDNSHost* pHost = 0; - if ((pHost = _findHost())) - { - DEBUG_EX_INFO_IF(u32LoopCounter++, - DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Multi-Loop (%u)!\n"), _DH(), u32LoopCounter); - DEBUG_EX_INFO_IF((remoteIPAddr.isSet()) && (remoteIPAddr != m_pUDPContext->getRemoteAddress()), - DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Changed IP address %s->%s!\n"), - _DH(), - remoteIPAddr.toString().c_str(), - m_pUDPContext->getRemoteAddress().toString().c_str()))); - DEBUG_EX_INFO(remoteIPAddr = m_pUDPContext->getRemoteAddress()); - - bResult = pHost->_processUDPInput(); - - DEBUG_EX_INFO2_IF(!bResult, - DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: FAILED to process UDP input!\n"), _DH())); - DEBUG_EX_ERR_IF((-1) != m_pUDPContext->peek(), - DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: !!!! CONTENT LEFT IN UDP BUFFER !!!!\n"), - _DH())); - } - else - { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Received UDP datagramm for unused netif at index: %u\n"), - _DH(), - (pNetIf ? netif_get_index(pNetIf) : (-1)))); - } + clsLEAMDNSHost* pHost = _findHost(); + DEBUG_EX_INFO_IF(u32LoopCounter++, + DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Multi-Loop (%u)!\n"), _DH(), u32LoopCounter); + DEBUG_EX_INFO_IF((remoteIPAddr.isSet()) && (remoteIPAddr != m_pUDPContext->getRemoteAddress()), + DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Changed IP address %s->%s!\n"), + _DH(), + remoteIPAddr.toString().c_str(), + m_pUDPContext->getRemoteAddress().toString().c_str()))); + DEBUG_EX_INFO(remoteIPAddr = m_pUDPContext->getRemoteAddress()); + + bResult = pHost->_processUDPInput(); + + DEBUG_EX_INFO2_IF(!bResult, + DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: FAILED to process UDP input!\n"), _DH())); + DEBUG_EX_ERR_IF((-1) != m_pUDPContext->peek(), + DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: !!!! CONTENT LEFT IN UDP BUFFER !!!!\n"), + _DH())); m_pUDPContext->flush(); } } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index 0c0be01906..ca15775eba 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -119,7 +119,7 @@ bool clsLEAMDNSHost_Legacy::end(void) bool clsLEAMDNSHost_Legacy::addHostForNetIf(const char* p_pcHostname) { bool bResult = true; - + if (m_HostInformations.size() > 0) { //XXXFIXME only one pHost instance, many things can be simplified @@ -129,8 +129,8 @@ bool clsLEAMDNSHost_Legacy::addHostForNetIf(const char* p_pcHostname) { clsLEAMDNSHost* pHost = new esp8266::experimental::clsLEAMDNSHost; if (pHost - && (!((pHost->begin(p_pcHostname /*, default callback*/)) - && (m_HostInformations.push_back(stcHostInformation(pHost)), true)))) + && (!((pHost->begin(p_pcHostname /*, default callback*/)) + && (m_HostInformations.push_back(stcHostInformation(pHost)), true)))) { bResult = false; } @@ -788,24 +788,41 @@ clsLEAMDNSHost_Legacy::hMDNSServiceQuery clsLEAMDNSHost_Legacy::installServiceQu for (stcHostInformation& hostInformation : m_HostInformations) { - clsLEAMDNSHost::clsQuery* pQuery = hostInformation.m_pHost->installServiceQuery(p_pcService, p_pcProtocol, [this, p_fnCallback](const clsLEAMDNSHost::clsQuery& /*p_Query*/, - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor & p_AnswerAccessor, - clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, // flags for the updated answer item - bool p_bSetContent)->void + std::list queries; + + /*clsLEAMDNSHost::clsQuery* pQuery =*/ + hostInformation.m_pHost->installServiceQuery(p_pcService, p_pcProtocol, [this, p_fnCallback](const clsLEAMDNSHost::clsQuery& /*p_Query*/, + const clsLEAMDNSHost::clsQuery::clsAnswerAccessor & p_AnswerAccessor, + clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, // flags for the updated answer item + bool p_bSetContent)->void { if (p_fnCallback) // void(const stcMDNSServiceInfo& p_MDNSServiceInfo, MDNSResponder::AnswerType p_AnswerType, bool p_bSetContent) { p_fnCallback(stcMDNSServiceInfo(p_AnswerAccessor), _answerFlagsToAnswerType(p_QueryAnswerTypeFlags), p_bSetContent); } - }); - if (pQuery) + }, &queries); + + if (queries.size()) { if (!hResult) { + // - hMDNSServiceQuery handle is 'const void*' + // used to retrieve pQuery when updating or removing. + + // - unexplained - before multi interface change: + // there is a loop, only the first is returned, why ? + // Store first query as result and key - hResult = (hMDNSServiceQuery)pQuery; + //hResult = (hMDNSServiceQuery)pQuery; <- before netif, only the first query is returned + + // - netif transformation: even more returned values (a list per loop), + // still, only the first handle is returned. + hResult = (hMDNSServiceQuery) * queries.begin(); // take the first } - hostInformation.m_HandleToPtr[hResult] = pQuery; + // this was overwritten ? + //hostInformation.m_HandleToPtr[hResult] = pQuery; + // ... overwritten with only the first query + hostInformation.m_HandleToPtr[hResult] = *queries.begin(); } } return hResult; From eae8b4d1c109d8382374ecfaeac6ac49a3386748 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Mon, 25 May 2020 15:32:14 +0200 Subject: [PATCH 020/152] wip --- cores/esp8266/IPAddress.h | 7 +++++-- libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 6 +++++- libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp | 3 ++- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 11 +++++++++++ 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/IPAddress.h b/cores/esp8266/IPAddress.h index 327c61f997..abced22db4 100644 --- a/cores/esp8266/IPAddress.h +++ b/cores/esp8266/IPAddress.h @@ -51,8 +51,11 @@ struct ip_addr: ipv4_addr { }; #endif // lwIP-v2+ // to display a netif id with printf: -#define NETIFID_STR "%c%c%d" -#define NETIFID_VAL(netif) (netif)->name[0], (netif)->name[1], netif_get_index((netif)) +#define NETIFID_STR "%c%c%u" +#define NETIFID_VAL(netif) \ + ((netif)? (netif)->name[0]: '-'), \ + ((netif)? (netif)->name[1]: '-'), \ + ((netif)? netif_get_index(netif): 42) // A class to make it easier to handle and pass around IP addresses // IPv6 update: diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 72fe253730..0a3c23e2f5 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -53,8 +53,10 @@ namespace experimental bool clsLEAMDNSHost::_sendMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { bool bResult = false; - uint8_t u8AvailableProtocols = 0; + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage: if=" NETIFID_STR "\n"), _DH(), NETIFID_VAL(pNetIf))); + #ifdef MDNS_IPV4_SUPPORT // Only send out IPv4 messages, if we've got an IPv4 address if (_getResponderIPAddress(pNetIf, enuIPProtocolType::V4).isSet()) @@ -157,6 +159,8 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(netif* pNetIf, clsLEAMDNSHost::clsSe { bool bIPv4Result = true; bool bIPv6Result = true; + + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast: if=" NETIFID_STR "\n"), _DH(), NETIFID_VAL(pNetIf))); #ifdef MDNS_IPV4_SUPPORT if (p_IPProtocolTypes & static_cast(enuIPProtocolType::V4)) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp index a382f071a1..d4ab972a30 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp @@ -41,7 +41,8 @@ namespace experimental clsLEAMDNSHost::clsBackbone::clsBackbone(void) : m_pUDPContext(0), m_bDelayUDPProcessing(false), - m_u32DelayedDatagrams(0) + m_u32DelayedDatagrams(0), + m_uniqueHost(0) { } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index ca15775eba..87ee6dc074 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -50,6 +50,17 @@ clsLEAMDNSHost_Legacy::~clsLEAMDNSHost_Legacy(void) */ +/* + clsLEAMDNSHost_Legacy::begin + +*/ +bool clsLEAMDNSHost_Legacy::begin(const char* p_pcHostname) +{ + return addHostForNetIf(p_pcHostname) + && (0 != m_HostInformations.size()); +} + + /* clsLEAMDNSHost_Legacy::begin (String) From 46f6d2b3a35acf16174d863f93bb3a5bfb45ba1f Mon Sep 17 00:00:00 2001 From: david gauchard Date: Mon, 25 May 2020 17:42:43 +0200 Subject: [PATCH 021/152] clockv2 example: ap + sta --- .../examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino | 12 +++++++++--- libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino index 5c429226f4..76750ea47e 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino @@ -69,6 +69,11 @@ #define STAPSK "your-password" #endif +#ifndef APSSID +#define APSSID "ap4mdnsClock" +#define APPSK "mdnsClock" +#endif + const char* ssid = STASSID; const char* password = STAPSK; @@ -181,7 +186,8 @@ void setup(void) { Serial.begin(115200); // Connect to WiFi network - WiFi.mode(WIFI_STA); + WiFi.mode(WIFI_AP_STA); + WiFi.softAP(APSSID, APPSK); WiFi.begin(ssid, password); Serial.println(""); @@ -201,12 +207,12 @@ void setup(void) { // Setup MDNS responder // Init the (currently empty) host domain string with 'esp8266' - if (responder.begin("ESP8266", WIFI_STA, [](clsMDNSHost & p_rMDNSHost, + if (responder.begin("leamdnsv2", [](clsMDNSHost & p_rMDNSHost, const char* p_pcDomainName, bool p_bProbeResult)->void { Serial.printf("mDNSHost_AP::ProbeResultCallback: '%s' is %s\n", p_pcDomainName, (p_bProbeResult ? "FREE" : "USED!")); // Unattended added service - p_rMDNSHost.addService(0, "http", "tcp", 80); + p_rMDNSHost.addService(0, "espclk", "tcp", 80); })) { Serial.println("mDNS-AP started"); } else { diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index d4e592843c..c8cf993ecb 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -226,7 +226,7 @@ bool clsLEAMDNSHost::_parseQuery(netif* pNetIf, #endif ) { - Serial.println("\n\n\nUNICAST QUERY\n\n"); + DEBUG_EX_RX(DEBUG_OUTPUT.println("\n\n\nUNICAST QUERY\n\n")); DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Legacy DNS query from local host %s!\n"), _DH(), m_pUDPContext->getRemoteAddress().toString().c_str());); sendParameter.m_u16ID = p_MsgHeader.m_u16ID; @@ -248,7 +248,7 @@ bool clsLEAMDNSHost::_parseQuery(netif* pNetIf, } else { - Serial.printf("\n\n\nINVALID UNICAST QUERY from %s\n\n\n", m_pUDPContext->getRemoteAddress().toString().c_str()); + DEBUG_EX_RX(DEBUG_OUTPUT.printf("\n\n\nINVALID UNICAST QUERY from %s\n\n\n", m_pUDPContext->getRemoteAddress().toString().c_str())); DEBUG_EX_RX(DEBUG_OUTPUT.printf_P(PSTR("%s _parseQuery: Legacy DNS query from NON-LOCAL host at %s!\n"), _DH(), m_pUDPContext->getRemoteAddress().toString().c_str());); bResult = false; } From 0a8187142caaeee33545c80ae385425ef49257ba Mon Sep 17 00:00:00 2001 From: david gauchard Date: Mon, 25 May 2020 18:16:05 +0200 Subject: [PATCH 022/152] style --- .../examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino | 2 +- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 4 ++-- libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 2 +- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino index 76750ea47e..0a6ad00cfe 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino @@ -208,7 +208,7 @@ void setup(void) { // Setup MDNS responder // Init the (currently empty) host domain string with 'esp8266' if (responder.begin("leamdnsv2", [](clsMDNSHost & p_rMDNSHost, - const char* p_pcDomainName, + const char* p_pcDomainName, bool p_bProbeResult)->void { Serial.printf("mDNSHost_AP::ProbeResultCallback: '%s' is %s\n", p_pcDomainName, (p_bProbeResult ? "FREE" : "USED!")); // Unattended added service diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 706cd94006..f93315d8df 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -956,8 +956,8 @@ bool clsLEAMDNSHost::_joinMulticastGroups(void) ip_addr_t multicast_addr_V6 = DNS_MQUERY_IPV6_GROUP_INIT; bResult = ((bResult) && (ERR_OK == mld6_joingroup_netif(pNetIf, ip_2_ip6(&multicast_addr_V6)))); - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: mld6_joingroup_netif (" NETIFID_STR ") FAILED!\n"), - _DH(), NETIFID_VAL(pNetIf));); + DEBUG_EX_ERR_IF(!bResult, DEBUG_OUTPUT.printf_P(PSTR("%s _createHost: mld6_joingroup_netif (" NETIFID_STR ") FAILED!\n"), + _DH(), NETIFID_VAL(pNetIf))); #endif } return bResult; diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 0a3c23e2f5..6e6f74d992 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -159,7 +159,7 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(netif* pNetIf, clsLEAMDNSHost::clsSe { bool bIPv4Result = true; bool bIPv6Result = true; - + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast: if=" NETIFID_STR "\n"), _DH(), NETIFID_VAL(pNetIf))); #ifdef MDNS_IPV4_SUPPORT diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index 87ee6dc074..910a8d5943 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -57,7 +57,7 @@ clsLEAMDNSHost_Legacy::~clsLEAMDNSHost_Legacy(void) bool clsLEAMDNSHost_Legacy::begin(const char* p_pcHostname) { return addHostForNetIf(p_pcHostname) - && (0 != m_HostInformations.size()); + && (0 != m_HostInformations.size()); } From b543f0312979b28c2324b4752e9ea39e28ca1176 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Mon, 25 May 2020 18:27:24 +0200 Subject: [PATCH 023/152] style --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index f93315d8df..801630e042 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -174,7 +174,8 @@ const char* clsLEAMDNSHost::indexDomainName(const char* p_pcDomainName, bool clsLEAMDNSHost::setNetIfHostName(const char* p_pcHostName) { if (p_pcHostName) - for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf)) + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + if (netif_is_up(pNetIf)) { netif_set_hostname(pNetIf, p_pcHostName); DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[mDNS] setNetIfHostName host name: %s on " NETIFID_STR "!\n"), p_pcHostName, NETIFID_VAL(pNetIf));); @@ -920,7 +921,8 @@ bool clsLEAMDNSHost::_joinMulticastGroups(void) bool bResult = false; // Join multicast group(s) - for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf)) + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + if (netif_is_up(pNetIf)) { #ifdef MDNS_IPV4_SUPPORT ip_addr_t multicast_addr_V4 = DNS_MQUERY_IPV4_GROUP_INIT; @@ -970,7 +972,8 @@ bool clsLEAMDNSHost::_leaveMulticastGroups() { bool bResult = false; - for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf)) + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + if (netif_is_up(pNetIf)) { bResult = true; /* _resetProbeStatus(false); // Stop probing From 41bd315e69efeeb07841def52be1ff613ab4aca8 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Thu, 28 May 2020 19:43:27 +0200 Subject: [PATCH 024/152] using experimental namespace for not-default v2+Legacy API --- libraries/ESP8266mDNS/src/ESP8266mDNS.h | 2 +- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS.h b/libraries/ESP8266mDNS/src/ESP8266mDNS.h index 7b921817e7..54eeb42797 100644 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS.h +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS.h @@ -56,7 +56,7 @@ using clsMDNSHost = esp8266::experimental::clsLEAMDNSHost; // Maps the implementation to use to the global namespace type //using MDNSResponder = Legacy_MDNSResponder::MDNSResponder; // Legacy using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; // LEA -//using MDNSResponder = esp8266::MDNSImplementation::clsLEAMDNSHost_Legacy; // LEAv2Compat +//using MDNSResponder = experimental::MDNSImplementation::clsLEAMDNSHost_Legacy; // LEAv2Compat //using MDNSResponder = clsMDNSHost; // LEAv2 extern MDNSResponder MDNS; diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h index 9ab1f14d0c..baab2b4a5d 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h @@ -105,7 +105,7 @@ #include "LEAmDNS2Host.h" -namespace esp8266 +namespace experimental { namespace MDNSImplementation @@ -689,7 +689,7 @@ class clsLEAMDNSHost_Legacy } // namespace MDNSImplementation -} // namespace esp8266 +} // namespace experimental #endif // __LEAMDNS2HOST_LEGACY_H__ From 92cd216d13699d397fb741b15d00b0affef204d7 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Thu, 28 May 2020 20:00:55 +0200 Subject: [PATCH 025/152] ditto --- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index 151555d15c..0e3f5e6ae7 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -8,7 +8,7 @@ #include "LEAmDNS2_Legacy.h" -namespace esp8266 +namespace experimental // esp8266 { /** @@ -1487,7 +1487,7 @@ clsLEAMDNSHost_Legacy::clsLEAMDNSHost::clsQuery::clsAnswerAccessor clsLEAMDNSHos } // namespace MDNSImplementation -} // namespace esp8266 +} // namespace experimental // esp8266 From cc2af846b605ecb1563e6232c45f47aa3e12515d Mon Sep 17 00:00:00 2001 From: hreintke Date: Sat, 30 May 2020 14:11:59 +0200 Subject: [PATCH 026/152] Fix HostProbeResult callback --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 3 + libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 4 +- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 66 +++++++++++-------- 3 files changed, 44 insertions(+), 29 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 1553c3df4c..ec14d08e7a 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -108,6 +108,9 @@ namespace experimental */ //static + +clsLEAMDNSHost::fnProbeResultCallback clsLEAMDNSHost::stProbeResultCallback = nullptr; + const char* clsLEAMDNSHost::indexDomainName(const char* p_pcDomainName, const char* p_pcDivider /*= "-"*/, const char* p_pcDefaultDomainName /*= 0*/) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index bdcc05e21e..502361bb80 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -471,8 +471,10 @@ class clsLEAMDNSHost const char* p_pcDomainName, bool p_bProbeResult)>; + static fnProbeResultCallback stProbeResultCallback; + protected: - /** + /** clsProbeInformation */ class clsProbeInformation : public clsProbeInformation_Base diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index 0e3f5e6ae7..f92e564456 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -136,7 +136,7 @@ bool clsLEAMDNSHost_Legacy::addHostForNetIf(const char* p_pcHostname, clsLEAMDNSHost* pHost = 0; if (((pHost = new esp8266::experimental::clsLEAMDNSHost)) - && (!((pHost->begin(p_pcHostname, p_pNetIf /*, default callback*/)) + && (!((pHost->begin(p_pcHostname, p_pNetIf, clsLEAMDNSHost::stProbeResultCallback)) && (m_HostInformations.push_back(stcHostInformation(pHost)), true)))) { delete pHost; @@ -1062,17 +1062,21 @@ bool clsLEAMDNSHost_Legacy::setHostProbeResultCallback(clsLEAMDNSHost_Legacy::MD { bool bResult = true; + clsLEAMDNSHost::stProbeResultCallback = + [p_fnCallback](clsLEAMDNSHost& /*p_rHost*/, + const char* p_pcDomainName, + bool p_bProbeResult)->void + { + if (p_fnCallback) // void(const char* p_pcDomainName, bool p_bProbeResult) + { + p_fnCallback(p_pcDomainName, p_bProbeResult); + } + }; + + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { - bResult = (*it).m_pHost->setProbeResultCallback([p_fnCallback](clsLEAMDNSHost& /*p_rHost*/, - const char* p_pcDomainName, - bool p_bProbeResult)->void - { - if (p_fnCallback) // void(const char* p_pcDomainName, bool p_bProbeResult) - { - p_fnCallback(p_pcDomainName, p_bProbeResult); - } - }); + bResult = (*it).m_pHost->setProbeResultCallback(clsLEAMDNSHost::stProbeResultCallback); } return bResult; } @@ -1085,17 +1089,20 @@ bool clsLEAMDNSHost_Legacy::setHostProbeResultCallback(clsLEAMDNSHost_Legacy::MD { bool bResult = true; + clsLEAMDNSHost::stProbeResultCallback = + [this, p_fnCallback](clsLEAMDNSHost& /*p_rHost*/, + const char* p_pcDomainName, + bool p_bProbeResult)->void + { + if (p_fnCallback) // void(clsLEAMDNSHost_Legacy* p_pMDNSResponder, const char* p_pcDomainName, bool p_bProbeResult) + { + p_fnCallback(this, p_pcDomainName, p_bProbeResult); + } + }; + for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { - bResult = (*it).m_pHost->setProbeResultCallback([this, p_fnCallback](clsLEAMDNSHost& /*p_rHost*/, - const char* p_pcDomainName, - bool p_bProbeResult)->void - { - if (p_fnCallback) // void(clsLEAMDNSHost_Legacy* p_pMDNSResponder, const char* p_pcDomainName, bool p_bProbeResult) - { - p_fnCallback(this, p_pcDomainName, p_bProbeResult); - } - }); + bResult = (*it).m_pHost->setProbeResultCallback(clsLEAMDNSHost::stProbeResultCallback); } return bResult; } @@ -1113,15 +1120,18 @@ bool clsLEAMDNSHost_Legacy::setServiceProbeResultCallback(const clsLEAMDNSHost_L { clsLEAMDNSHost::clsService* pService = 0; bResult = (((pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService])) - && (pService->setProbeResultCallback([this, p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/, - const char* p_pcInstanceName, - bool p_bProbeResult)->void - { - if (p_fnCallback) // void(const char* p_pcServiceName, const hMDNSService p_hMDNSService, bool p_bProbeResult) - { - p_fnCallback(p_pcInstanceName, p_hService, p_bProbeResult); - } - }))); + && (pService->setProbeResultCallback( + + [this, p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/, + const char* p_pcInstanceName, + bool p_bProbeResult)->void + { + if (p_fnCallback) // void(const char* p_pcServiceName, const hMDNSService p_hMDNSService, bool p_bProbeResult) + { + p_fnCallback(p_pcInstanceName, p_hService, p_bProbeResult); + } + } + ))); } return bResult; } From ff3e86989e4d836da42a8b8b3954670ca280c70c Mon Sep 17 00:00:00 2001 From: hreintke Date: Sun, 31 May 2020 13:38:10 +0200 Subject: [PATCH 027/152] Style --- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 2 +- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 56 +++++++++---------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index 502361bb80..793fc76c3b 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -474,7 +474,7 @@ class clsLEAMDNSHost static fnProbeResultCallback stProbeResultCallback; protected: - /** + /** clsProbeInformation */ class clsProbeInformation : public clsProbeInformation_Base diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index f92e564456..3758d89d4f 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -1063,15 +1063,15 @@ bool clsLEAMDNSHost_Legacy::setHostProbeResultCallback(clsLEAMDNSHost_Legacy::MD bool bResult = true; clsLEAMDNSHost::stProbeResultCallback = - [p_fnCallback](clsLEAMDNSHost& /*p_rHost*/, - const char* p_pcDomainName, - bool p_bProbeResult)->void - { - if (p_fnCallback) // void(const char* p_pcDomainName, bool p_bProbeResult) - { - p_fnCallback(p_pcDomainName, p_bProbeResult); - } - }; + [p_fnCallback](clsLEAMDNSHost& /*p_rHost*/, + const char* p_pcDomainName, + bool p_bProbeResult)->void + { + if (p_fnCallback) // void(const char* p_pcDomainName, bool p_bProbeResult) + { + p_fnCallback(p_pcDomainName, p_bProbeResult); + } + }; for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) @@ -1090,15 +1090,15 @@ bool clsLEAMDNSHost_Legacy::setHostProbeResultCallback(clsLEAMDNSHost_Legacy::MD bool bResult = true; clsLEAMDNSHost::stProbeResultCallback = - [this, p_fnCallback](clsLEAMDNSHost& /*p_rHost*/, - const char* p_pcDomainName, - bool p_bProbeResult)->void - { - if (p_fnCallback) // void(clsLEAMDNSHost_Legacy* p_pMDNSResponder, const char* p_pcDomainName, bool p_bProbeResult) - { - p_fnCallback(this, p_pcDomainName, p_bProbeResult); - } - }; + [this, p_fnCallback](clsLEAMDNSHost& /*p_rHost*/, + const char* p_pcDomainName, + bool p_bProbeResult)->void + { + if (p_fnCallback) // void(clsLEAMDNSHost_Legacy* p_pMDNSResponder, const char* p_pcDomainName, bool p_bProbeResult) + { + p_fnCallback(this, p_pcDomainName, p_bProbeResult); + } + }; for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { @@ -1122,16 +1122,16 @@ bool clsLEAMDNSHost_Legacy::setServiceProbeResultCallback(const clsLEAMDNSHost_L bResult = (((pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService])) && (pService->setProbeResultCallback( - [this, p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/, - const char* p_pcInstanceName, - bool p_bProbeResult)->void - { - if (p_fnCallback) // void(const char* p_pcServiceName, const hMDNSService p_hMDNSService, bool p_bProbeResult) - { - p_fnCallback(p_pcInstanceName, p_hService, p_bProbeResult); - } - } - ))); + [this, p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/, + const char* p_pcInstanceName, + bool p_bProbeResult)->void + { + if (p_fnCallback) // void(const char* p_pcServiceName, const hMDNSService p_hMDNSService, bool p_bProbeResult) + { + p_fnCallback(p_pcInstanceName, p_hService, p_bProbeResult); + } + } + ))); } return bResult; } From 40262ce4aa263f629e77a7e59ae6d7c5822fdab6 Mon Sep 17 00:00:00 2001 From: hreintke Date: Sun, 31 May 2020 13:39:21 +0200 Subject: [PATCH 028/152] fix copy-paste error --- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h index baab2b4a5d..4d44ac82df 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h @@ -397,7 +397,7 @@ class clsLEAMDNSHost_Legacy */ bool hostDomainAvailable(void) const { - return m_rAnswerAccessor.serviceDomainAvailable(); + return m_rAnswerAccessor.hostDomainAvailable(); } /* hostDomain From 6e186376fc928d2c5e1318ef7fefa6bf15c590cb Mon Sep 17 00:00:00 2001 From: David Gauchard Date: Wed, 3 Jun 2020 10:56:23 +0200 Subject: [PATCH 029/152] wip --- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index 1541d98321..274d84993f 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -129,7 +129,7 @@ bool clsLEAMDNSHost_Legacy::end(void) */ bool clsLEAMDNSHost_Legacy::addHostForNetIf(const char* p_pcHostname) { - bool bResult = true; + bool bResult = true; if (m_HostInformations.size() > 0) { From e82d252d33b9fa8837bba044389b9850bc6f4993 Mon Sep 17 00:00:00 2001 From: David Gauchard Date: Wed, 3 Jun 2020 11:00:01 +0200 Subject: [PATCH 030/152] wip --- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index 274d84993f..1541d98321 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -129,7 +129,7 @@ bool clsLEAMDNSHost_Legacy::end(void) */ bool clsLEAMDNSHost_Legacy::addHostForNetIf(const char* p_pcHostname) { - bool bResult = true; + bool bResult = true; if (m_HostInformations.size() > 0) { From 9a3a696b2f935868a753bfe5f85906c965dda724 Mon Sep 17 00:00:00 2001 From: hreintke Date: Sat, 6 Jun 2020 16:58:02 +0200 Subject: [PATCH 031/152] wip. probe and service --- libraries/ESP8266mDNS/src/ESP8266mDNS.h | 4 +- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 23 +++++++---- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 19 +++++----- .../ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 38 +++++++++---------- .../ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 22 ++++++++--- 5 files changed, 62 insertions(+), 44 deletions(-) diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS.h b/libraries/ESP8266mDNS/src/ESP8266mDNS.h index 54eeb42797..d5f73d75ab 100644 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS.h +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS.h @@ -55,9 +55,9 @@ using clsMDNSHost = esp8266::experimental::clsLEAMDNSHost; #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) // Maps the implementation to use to the global namespace type //using MDNSResponder = Legacy_MDNSResponder::MDNSResponder; // Legacy -using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; // LEA +//using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; // LEA //using MDNSResponder = experimental::MDNSImplementation::clsLEAMDNSHost_Legacy; // LEAv2Compat -//using MDNSResponder = clsMDNSHost; // LEAv2 +using MDNSResponder = clsMDNSHost; // LEAv2 extern MDNSResponder MDNS; #endif diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index cb008ae975..e17104a899 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -438,12 +438,15 @@ bool clsLEAMDNSHost::removeService(clsLEAMDNSHost::clsService* p_pService) if (p_pService && (m_Services.end() != std::find(m_Services.begin(), m_Services.end(), p_pService))) { + bResult = _announceService(*p_pService, false); + /* for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf) && (_announceService(pNetIf, *p_pService, false))) { bResult = true; } +*/ } if (bResult) @@ -769,13 +772,14 @@ bool clsLEAMDNSHost::update(void) { bool bResult = false; + bResult = _updateProbeStatus(); + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf)) { //if (clsBackbone::sm_pBackbone->setDelayUDPProcessing(true)) //{ - if ((_updateProbeStatus(pNetIf)) && // Probing and announcing - (_checkQueryCache(pNetIf))) + if (_checkQueryCache(pNetIf)) { bResult = true; } @@ -790,8 +794,10 @@ bool clsLEAMDNSHost::update(void) /* clsLEAmDNS2_Host::announce */ -bool clsLEAMDNSHost::announce(bool p_bAnnounce /*= true*/, - bool p_bIncludeServices /*= true*/) + +//bool clsLEAMDNSHost::announce(bool p_bAnnounce /*= true*/, +// bool p_bIncludeServices /*= true*/) +/* { bool bResult = false; for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) @@ -801,12 +807,14 @@ bool clsLEAMDNSHost::announce(bool p_bAnnounce /*= true*/, } return bResult; } - +*/ /* clsLEAmDNS2_Host::announceService */ -bool clsLEAMDNSHost::announceService(clsService * p_pService, - bool p_bAnnounce /*= true*/) + +//bool clsLEAMDNSHost::announceService(clsService * p_pService, +// bool p_bAnnounce /*= true*/) +/* { bool bResult = false; for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) @@ -816,6 +824,7 @@ bool clsLEAMDNSHost::announceService(clsService * p_pService, } return bResult; } +*/ /* clsLEAmDNS2_Host::restart diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index 4eed6900b1..0616d3eb8f 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -132,9 +132,9 @@ #if defined(DEBUG_ESP_PORT) && defined(DEBUG_ESP_MDNS_RESPONDER) #define DEBUG_ESP_MDNS_INFO #define DEBUG_ESP_MDNS_INFO2 -#define DEBUG_ESP_MDNS_ERR -#define DEBUG_ESP_MDNS_TX -#define DEBUG_ESP_MDNS_RX +//#define DEBUG_ESP_MDNS_ERR +//#define DEBUG_ESP_MDNS_TX +//#define DEBUG_ESP_MDNS_RX #endif #ifdef DEBUG_ESP_MDNS_RESPONDER @@ -1472,11 +1472,11 @@ class clsLEAMDNSHost #endif // PROBING - bool _updateProbeStatus(netif* pNetIf); + bool _updateProbeStatus(); bool _resetProbeStatus(bool p_bRestart = true); bool _hasProbesWaitingForAnswers(void) const; - bool _sendHostProbe(netif* pNetIf); - bool _sendServiceProbe(netif* pNetIf, clsService& p_rService); + bool _sendHostProbe(); + bool _sendServiceProbe(clsService& p_rService); bool _cancelProbingForHost(void); bool _cancelProbingForService(clsService& p_rService); bool _callHostProbeResultCallback(bool p_bResult); @@ -1484,11 +1484,9 @@ class clsLEAMDNSHost bool p_bResult); // ANNOUNCE - bool _announce(netif* pNetIf, - bool p_bAnnounce, + bool _announce(bool p_bAnnounce, bool p_bIncludeServices); - bool _announceService(netif* pNetIf, - clsService& p_pService, + bool _announceService(clsService& p_pService, bool p_bAnnounce = true); // QUERY CACHE @@ -1505,6 +1503,7 @@ class clsLEAMDNSHost // File: ..._Host_Transfer // SENDING bool _sendMessage(netif* pNetIf, clsSendParameter& p_SendParameter); + bool _sendMessage(clsSendParameter& p_SendParameter); bool _sendMessage_Multicast(netif* pNetIf, clsSendParameter& p_rSendParameter, uint8_t p_IPProtocolTypes); diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index c8cf993ecb..2517d16f61 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -1283,14 +1283,15 @@ bool clsLEAMDNSHost::_processAAAAAnswer(const clsLEAMDNSHost::clsRRAnswerAAAA* p Conflict management is handled in '_parseResponse ff.' Tiebraking is handled in 'parseQuery ff.' */ -bool clsLEAMDNSHost::_updateProbeStatus(netif* pNetIf) +bool clsLEAMDNSHost::_updateProbeStatus() { bool bResult = true; // // Probe host domain - if ((clsProbeInformation_Base::enuProbingStatus::ReadyToStart == m_ProbeInformation.m_ProbingStatus) && // Ready to get started AND - (( + if ((clsProbeInformation_Base::enuProbingStatus::ReadyToStart == m_ProbeInformation.m_ProbingStatus))// && // Ready to get started AND +/* + (( #ifdef MDNS_IPV4_SUPPORT _getResponderIPAddress(pNetIf, enuIPProtocolType::V4).isSet() // AND has IPv4 address #else @@ -1303,6 +1304,7 @@ bool clsLEAMDNSHost::_updateProbeStatus(netif* pNetIf) true #endif ))) // Has IP address +*/ { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Starting host probing...\n"), _DH());); @@ -1316,7 +1318,7 @@ bool clsLEAMDNSHost::_updateProbeStatus(netif* pNetIf) if (clsConsts::u32ProbeCount > m_ProbeInformation.m_u8SentCount) { // Send next probe - if ((bResult = _sendHostProbe(pNetIf))) + if ((bResult = _sendHostProbe())) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Did sent host probe for '%s.local'\n\n"), _DH(), (m_pcHostName ? : ""));); m_ProbeInformation.m_Timeout.reset(clsConsts::u32ProbeDelay); @@ -1341,7 +1343,7 @@ bool clsLEAMDNSHost::_updateProbeStatus(netif* pNetIf) else if ((clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == m_ProbeInformation.m_ProbingStatus) && (m_ProbeInformation.m_Timeout.expired())) { - if ((bResult = _announce(pNetIf, true, false))) + if ((bResult = _announce(true, false))) { // Don't announce services here ++m_ProbeInformation.m_u8SentCount; // 1.. @@ -1380,9 +1382,9 @@ bool clsLEAMDNSHost::_updateProbeStatus(netif* pNetIf) if (clsConsts::u32ProbeCount > pService->m_ProbeInformation.m_u8SentCount) { // Send next probe - if ((bResult = _sendServiceProbe(pNetIf, *pService))) + if ((bResult = _sendServiceProbe(*pService))) { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Did sent service probe for '%s' (%u)\n\n"), _DH(), _service2String(pService), (pService->m_ProbeInformation.m_u8SentCount + 1));); + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Did sent service probe for '%s' (%u)\n\n"), _DH(), _service2String(pService), (pService->m_ProbeInformation.m_u8SentCount + 1));); pService->m_ProbeInformation.m_Timeout.reset(clsConsts::u32ProbeDelay); ++pService->m_ProbeInformation.m_u8SentCount; } @@ -1390,7 +1392,7 @@ bool clsLEAMDNSHost::_updateProbeStatus(netif* pNetIf) else { // Probing finished - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("\n%s _updateProbeStatus: Done service probing '%s'\n\n\n"), _DH(), _service2String(pService));); + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("\n%s _updateProbeStatus: Done service probing '%s'\n\n\n"), _DH(), _service2String(pService));); pService->m_ProbeInformation.m_ProbingStatus = clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce; pService->m_ProbeInformation.m_Timeout.reset(esp8266::polledTimeout::oneShot::neverExpires); @@ -1406,7 +1408,7 @@ bool clsLEAMDNSHost::_updateProbeStatus(netif* pNetIf) (pService->m_ProbeInformation.m_Timeout.expired())) { // Probing already finished OR waiting for next time slot - if ((bResult = _announceService(pNetIf, *pService))) + if ((bResult = _announceService(*pService))) { // Announce service ++pService->m_ProbeInformation.m_u8SentCount; // 1.. @@ -1484,7 +1486,7 @@ bool clsLEAMDNSHost::_hasProbesWaitingForAnswers(void) const - A/AAAA (eg. esp8266.esp -> 192.168.2.120) */ -bool clsLEAMDNSHost::_sendHostProbe(netif* pNetIf) +bool clsLEAMDNSHost::_sendHostProbe() { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendHostProbe (%s.local, %lu)\n"), _DH(), m_pcHostName, millis());); @@ -1523,7 +1525,7 @@ bool clsLEAMDNSHost::_sendHostProbe(netif* pNetIf) } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendHostProbe: FAILED!\n"), _DH());); return ((bResult) && - (_sendMessage(pNetIf, sendParameter))); + (_sendMessage(sendParameter))); } /* @@ -1539,7 +1541,7 @@ bool clsLEAMDNSHost::_sendHostProbe(netif* pNetIf) - PTR NAME (eg. _http._tcp.local -> MyESP._http._tcp.local) (TODO: Check if needed, maybe TXT is better) */ -bool clsLEAMDNSHost::_sendServiceProbe(netif* pNetIf, clsService& p_rService) +bool clsLEAMDNSHost::_sendServiceProbe(clsService& p_rService) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _sendServiceProbe (%s, %lu)\n"), _DH(), _service2String(&p_rService), millis());); @@ -1573,7 +1575,7 @@ bool clsLEAMDNSHost::_sendServiceProbe(netif* pNetIf, clsService& p_rService) } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendServiceProbe: FAILED!\n"), _DH());); return ((bResult) && - (_sendMessage(pNetIf, sendParameter))); + (_sendMessage(sendParameter))); } /* @@ -1672,8 +1674,7 @@ bool clsLEAMDNSHost::_callServiceProbeResultCallback(clsLEAMDNSHost::clsService& inside the '_writeXXXAnswer' procs via 'sendParameter.m_bUnannounce = true' */ -bool clsLEAMDNSHost::_announce(netif* pNetIf, - bool p_bAnnounce, +bool clsLEAMDNSHost::_announce(bool p_bAnnounce, bool p_bIncludeServices) { bool bResult = false; @@ -1719,15 +1720,14 @@ bool clsLEAMDNSHost::_announce(netif* pNetIf, } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _announce: FAILED!\n"), _DH());); return ((bResult) && - (_sendMessage(pNetIf, sendParameter))); + (_sendMessage(sendParameter))); } /* clsLEAmDNS2_Host::_announceService */ -bool clsLEAMDNSHost::_announceService(netif* pNetIf, - clsLEAMDNSHost::clsService& p_rService, +bool clsLEAMDNSHost::_announceService(clsLEAMDNSHost::clsService& p_rService, bool p_bAnnounce /*= true*/) { bool bResult = false; @@ -1753,7 +1753,7 @@ bool clsLEAMDNSHost::_announceService(netif* pNetIf, } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _announceService: FAILED!\n"), _DH());); return ((bResult) && - (_sendMessage(pNetIf, sendParameter))); + (_sendMessage(sendParameter))); } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 6e6f74d992..01fffe189c 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -50,6 +50,20 @@ namespace experimental Any reply flags in installed services are removed at the end! */ +bool clsLEAMDNSHost::_sendMessage(clsLEAMDNSHost::clsSendParameter& p_rSendParameter) +{ + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + if (netif_is_up(pNetIf)) + { + _sendMessage(pNetIf, p_rSendParameter); + } + + // Finally clear service reply masks + for (clsService* pService : m_Services) + { + pService->m_u32ReplyMask = 0; + } +} bool clsLEAMDNSHost::_sendMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { bool bResult = false; @@ -135,12 +149,6 @@ bool clsLEAMDNSHost::_sendMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParamete (_sendMessage_Multicast(pNetIf, p_rSendParameter, u8AvailableProtocols))); } - // Finally clear service reply masks - for (clsService* pService : m_Services) - { - pService->m_u32ReplyMask = 0; - } - clsBackbone::sm_pBackbone->setDelayUDPProcessing(false); } @@ -256,6 +264,7 @@ bool clsLEAMDNSHost::_prepareMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParam // Two step sequence: 'Count' and 'Send' for (typeSequence sequence = static_cast(enuSequence::Count); ((bResult) && (sequence <= static_cast(enuSequence::Send))); ++sequence) { + /* DEBUG_EX_INFO( if (static_cast(enuSequence::Send) == sequence) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), @@ -268,6 +277,7 @@ bool clsLEAMDNSHost::_prepareMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParam (unsigned)msgHeader.m_u16NSCount, (unsigned)msgHeader.m_u16ARCount); ); + */ // Count/send // Header bResult = ((static_cast(enuSequence::Count) == sequence) From 930339ecaf2cfd986fa06a1d96eeab22b5106adb Mon Sep 17 00:00:00 2001 From: hreintke Date: Sun, 7 Jun 2020 13:39:03 +0200 Subject: [PATCH 032/152] wip query to use sendmessage() instead of sendmessage(netif) --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 36 +++++++------------ libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 11 +++--- .../ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 12 +++---- .../ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 10 +++--- 4 files changed, 27 insertions(+), 42 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index e17104a899..867e8d41e7 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -531,14 +531,11 @@ clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryService { std::list queries; - for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) - if (netif_is_up(pNetIf)) - { clsQuery* pQuery = 0; if (((pQuery = _allocQuery(clsQuery::enuQueryType::Service))) && (_buildDomainForService(p_pcService, p_pcProtocol, pQuery->m_Domain))) { - if (((pQuery->m_bStaticQuery = true)) && (_sendQuery(pNetIf, *pQuery))) + if (((pQuery->m_bStaticQuery = true)) && (_sendQuery(*pQuery))) { queries.push_back(pQuery); } @@ -548,7 +545,6 @@ clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryService _removeQuery(pQuery); } } - } if (queries.size()) { @@ -590,14 +586,11 @@ clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryHost(co { std::list queries; - for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) - if (netif_is_up(pNetIf)) - { clsQuery* pQuery = 0; if (((pQuery = _allocQuery(clsQuery::enuQueryType::Host))) && (_buildDomainForHost(p_pcHostName, pQuery->m_Domain))) { - if (((pQuery->m_bStaticQuery = true)) && (_sendQuery(pNetIf, *pQuery))) + if (((pQuery->m_bStaticQuery = true)) && (_sendQuery(*pQuery))) { queries.push_back(pQuery); } @@ -607,7 +600,7 @@ clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryHost(co _removeQuery(pQuery); } } - } + if (queries.size()) { @@ -711,18 +704,16 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::getQuery(void) bool bResult = false; clsQuery* pQuery = 0; if ((p_pcHostName) && (*p_pcHostName)) - for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) - if (netif_is_up(pNetIf)) - { + { clsRRDomain domain; if ((pQuery = ((_buildDomainForHost(p_pcHostName, domain)) - ? _installDomainQuery(pNetIf, domain, clsQuery::enuQueryType::Host) + ? _installDomainQuery(domain, clsQuery::enuQueryType::Host) : 0))) { pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; bResult = true; } - } + } return bResult; } @@ -735,12 +726,10 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::getQuery(void) bool bResult = true; clsQuery* pQuery = 0; if ((p_pcHostName) && (*p_pcHostName)) - for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) - if (netif_is_up(pNetIf)) { clsRRDomain domain; if ((pQuery = ((_buildDomainForHost(p_pcHostName, domain)) - ? _installDomainQuery(pNetIf, domain, clsQuery::enuQueryType::Host) + ? _installDomainQuery(domain, clsQuery::enuQueryType::Host) : 0))) { pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; @@ -772,8 +761,8 @@ bool clsLEAMDNSHost::update(void) { bool bResult = false; - bResult = _updateProbeStatus(); - + bResult = (_updateProbeStatus() && _checkQueryCache()) +/* for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf)) { @@ -787,6 +776,7 @@ bool clsLEAMDNSHost::update(void) // clsBackbone::sm_pBackbone->setDelayUDPProcessing(false); //} } +*/ DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s update: FAILED (Not connected?)!\n"), _DH());); return bResult; } @@ -1300,7 +1290,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installServiceQuery(netif* pNetIf, { pMDNSQuery->m_bStaticQuery = false; - if (_sendQuery(pNetIf, *pMDNSQuery)) + if (_sendQuery(*pMDNSQuery)) { pMDNSQuery->m_u8SentCount = 1; pMDNSQuery->m_ResendTimeout.reset(clsConsts::u32DynamicQueryResendDelay); @@ -1318,7 +1308,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installServiceQuery(netif* pNetIf, /* clsLEAmDNS2_Host::_installDomainQuery */ -clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installDomainQuery(netif* pNetIf, +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installDomainQuery( clsLEAMDNSHost::clsRRDomain & p_Domain, clsLEAMDNSHost::clsQuery::enuQueryType p_QueryType) { @@ -1329,7 +1319,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installDomainQuery(netif* pNetIf, pQuery->m_Domain = p_Domain; pQuery->m_bStaticQuery = false; - if (_sendQuery(pNetIf, *pQuery)) + if (_sendQuery(*pQuery)) { pQuery->m_u8SentCount = 1; pQuery->m_ResendTimeout.reset(clsConsts::u32DynamicQueryResendDelay); diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index 0616d3eb8f..c179ad454f 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -1441,8 +1441,7 @@ class clsLEAMDNSHost clsQuery* _installServiceQuery(netif* pNetIf, const char* p_pcService, const char* p_pcProtocol); - clsQuery* _installDomainQuery(netif *pNetIf, - clsRRDomain& p_Domain, + clsQuery* _installDomainQuery(clsRRDomain& p_Domain, clsQuery::enuQueryType p_QueryType); bool _hasQueriesWaitingForAnswers(void) const; bool _executeQueryCallback(const clsQuery& p_Query, @@ -1490,7 +1489,7 @@ class clsLEAMDNSHost bool p_bAnnounce = true); // QUERY CACHE - bool _checkQueryCache(netif* pNetIf); + bool _checkQueryCache(); uint32_t _replyMaskForHost(netif* pNetIf, const clsRRHeader& p_RRHeader, @@ -1511,11 +1510,9 @@ class clsLEAMDNSHost bool _addQueryRecord(clsSendParameter& p_rSendParameter, const clsRRDomain& p_QueryDomain, uint16_t p_u16QueryType); - bool _sendQuery(netif* netif, - const clsQuery& p_Query, + bool _sendQuery(const clsQuery& p_Query, clsQuery::clsAnswer::list* p_pKnownAnswers = 0); - bool _sendQuery(netif* netif, - const clsRRDomain& p_QueryDomain, + bool _sendQuery(const clsRRDomain& p_QueryDomain, uint16_t p_u16RecordType, clsQuery::clsAnswer::list* p_pKnownAnswers = 0); diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index 2517d16f61..ebeaaa2e65 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -1772,7 +1772,7 @@ bool clsLEAMDNSHost::_announceService(clsLEAMDNSHost::clsService& p_rService, When no update arrived (in time), the component is removed from the answer (cache). */ -bool clsLEAMDNSHost::_checkQueryCache(netif* pNetIf) +bool clsLEAMDNSHost::_checkQueryCache() { bool bResult = true; @@ -1787,7 +1787,7 @@ bool clsLEAMDNSHost::_checkQueryCache(netif* pNetIf) if ((!pQuery->m_bStaticQuery) && (pQuery->m_ResendTimeout.expired())) { - if ((bResult = _sendQuery(pNetIf, *pQuery))) + if ((bResult = _sendQuery(*pQuery))) { // The re-query rate is increased to more than one hour (RFC 6762 5.2) ++pQuery->m_u8SentCount; @@ -1820,7 +1820,7 @@ bool clsLEAMDNSHost::_checkQueryCache(netif* pNetIf) { if (!pQAnswer->m_TTLServiceDomain.finalTimeoutLevel()) { - bResult = ((_sendQuery(pNetIf, *pQuery)) && + bResult = ((_sendQuery(*pQuery)) && (pQAnswer->m_TTLServiceDomain.restart())); DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: PTR update scheduled for "), _DH()); @@ -1852,7 +1852,7 @@ bool clsLEAMDNSHost::_checkQueryCache(netif* pNetIf) { if (!pQAnswer->m_TTLHostDomainAndPort.finalTimeoutLevel()) { - bResult = ((_sendQuery(pNetIf, pQAnswer->m_ServiceDomain, DNS_RRTYPE_SRV)) && + bResult = ((_sendQuery(pQAnswer->m_ServiceDomain, DNS_RRTYPE_SRV)) && (pQAnswer->m_TTLHostDomainAndPort.restart())); DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: SRV update scheduled for "), _DH()); @@ -1898,7 +1898,7 @@ bool clsLEAMDNSHost::_checkQueryCache(netif* pNetIf) { if (!pQAnswer->m_TTLTxts.finalTimeoutLevel()) { - bResult = ((_sendQuery(pNetIf, pQAnswer->m_ServiceDomain, DNS_RRTYPE_TXT)) && + bResult = ((_sendQuery(pQAnswer->m_ServiceDomain, DNS_RRTYPE_TXT)) && (pQAnswer->m_TTLTxts.restart())); DEBUG_EX_INFO( DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: TXT update scheduled for "), _DH()); @@ -1941,7 +1941,7 @@ bool clsLEAMDNSHost::_checkQueryCache(netif* pNetIf) { // Needs update if ((bAUpdateQuerySent) || - ((bResult = _sendQuery(pNetIf, pQAnswer->m_HostDomain, DNS_RRTYPE_A)))) + ((bResult = _sendQuery(pQAnswer->m_HostDomain, DNS_RRTYPE_A)))) { pIPv4Address->m_TTL.restart(); bAUpdateQuerySent = true; diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 01fffe189c..0493afd70b 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -570,8 +570,7 @@ bool clsLEAMDNSHost::_addQueryRecord(clsLEAMDNSHost::clsSendParameter& p_rSendPa Creates and sends a query for the given domain and query type. */ -bool clsLEAMDNSHost::_sendQuery(netif* pNetIf, - const clsLEAMDNSHost::clsQuery& p_Query, +bool clsLEAMDNSHost::_sendQuery(const clsLEAMDNSHost::clsQuery& p_Query, clsLEAMDNSHost::clsQuery::clsAnswer::list* p_pKnownAnswers /*= 0*/) { bool bResult = false; @@ -600,7 +599,7 @@ bool clsLEAMDNSHost::_sendQuery(netif* pNetIf, // TODO: Add known answers to query (void)p_pKnownAnswers; bResult = ((bResult) && - (_sendMessage(pNetIf, sendParameter))); + (_sendMessage(sendParameter))); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendQuery: FAILED!\n"), _DH());); return bResult; } @@ -611,8 +610,7 @@ bool clsLEAMDNSHost::_sendQuery(netif* pNetIf, Creates and sends a query for the given domain and record type. */ -bool clsLEAMDNSHost::_sendQuery(netif* pNetIf, - const clsLEAMDNSHost::clsRRDomain& p_QueryDomain, +bool clsLEAMDNSHost::_sendQuery(const clsLEAMDNSHost::clsRRDomain& p_QueryDomain, uint16_t p_u16RecordType, clsLEAMDNSHost::clsQuery::clsAnswer::list* p_pKnownAnswers /*= 0*/) { @@ -620,7 +618,7 @@ bool clsLEAMDNSHost::_sendQuery(netif* pNetIf, clsSendParameter sendParameter; bResult = ((_addQueryRecord(sendParameter, p_QueryDomain, p_u16RecordType)) && - (_sendMessage(pNetIf, sendParameter))); + (_sendMessage(sendParameter))); // TODO: Add known answer records (void) p_pKnownAnswers; From bd3baa9bfa7e5af26afd433df9e5ba85093fb802 Mon Sep 17 00:00:00 2001 From: hreintke Date: Sun, 7 Jun 2020 16:58:13 +0200 Subject: [PATCH 033/152] wip, installservicequery re-add announce() fix announce to use correct probing status --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 58 ++++++------------- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 12 ++-- .../ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 3 +- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 20 +++---- 4 files changed, 34 insertions(+), 59 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 867e8d41e7..f5e90953b5 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -656,43 +656,32 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::getQuery(void) clsLEAmDNS2_Host::installServiceQuery (answer) */ -/*clsLEAMDNSHost::clsQuery* */ bool clsLEAMDNSHost::installServiceQuery(const char* p_pcService, +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcService, const char* p_pcProtocol, clsLEAMDNSHost::clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer) { - bool bResult = false; clsQuery* pQuery = 0; - for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) - if (netif_is_up(pNetIf) && (pQuery = _installServiceQuery(pNetIf, p_pcService, p_pcProtocol))) + if (pQuery = _installServiceQuery(p_pcService, p_pcProtocol)) { pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; - bResult = true; } - return bResult; + return pQuery; } /* clsLEAmDNS2_Host::installServiceQuery (accessor) */ -/*clsLEAMDNSHost::clsQuery* */ bool clsLEAMDNSHost::installServiceQuery(const char* p_pcService, +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcService, const char* p_pcProtocol, - clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor, - std::list* ret) + clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor) { - bool bResult = false; clsQuery* pQuery = 0; - for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) - if (netif_is_up(pNetIf) && (pQuery = _installServiceQuery(pNetIf, p_pcService, p_pcProtocol))) + if (pQuery = _installServiceQuery(p_pcService, p_pcProtocol)) { pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; - bResult = true; - if (ret) - { - ret->push_back(pQuery); - } } - return bResult; + return pQuery; } /* @@ -785,36 +774,23 @@ bool clsLEAMDNSHost::update(void) clsLEAmDNS2_Host::announce */ -//bool clsLEAMDNSHost::announce(bool p_bAnnounce /*= true*/, -// bool p_bIncludeServices /*= true*/) -/* +bool clsLEAMDNSHost::announce(bool p_bAnnounce /*= true*/, + bool p_bIncludeServices /*= true*/) { - bool bResult = false; - for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) - if (netif_is_up(pNetIf) && _announce(pNetIf, p_bAnnounce, p_bIncludeServices)) - { - bResult = true; - } - return bResult; + return _announce(p_bAnnounce, p_bIncludeServices); } -*/ + /* clsLEAmDNS2_Host::announceService */ -//bool clsLEAMDNSHost::announceService(clsService * p_pService, -// bool p_bAnnounce /*= true*/) -/* +bool clsLEAMDNSHost::announceService(clsService * p_pService, + bool p_bAnnounce /*= true*/) { - bool bResult = false; - for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) - if (netif_is_up(pNetIf) && _announceService(pNetIf, *p_pService, p_bAnnounce)) - { - bResult = true; - } - return bResult; + + return _announceService(*p_pService, p_bAnnounce); } -*/ + /* clsLEAmDNS2_Host::restart @@ -1277,7 +1253,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_findNextQueryByDomain(const clsLEAMDN clsLEAmDNS2_Host::_installServiceQuery */ -clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installServiceQuery(netif* pNetIf, +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installServiceQuery( const char* p_pcService, const char* p_pcProtocol) { diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index c179ad454f..be72504277 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -130,7 +130,7 @@ Enable/disable debug trace macros */ #if defined(DEBUG_ESP_PORT) && defined(DEBUG_ESP_MDNS_RESPONDER) -#define DEBUG_ESP_MDNS_INFO +//#define DEBUG_ESP_MDNS_INFO #define DEBUG_ESP_MDNS_INFO2 //#define DEBUG_ESP_MDNS_ERR //#define DEBUG_ESP_MDNS_TX @@ -1346,13 +1346,12 @@ class clsLEAMDNSHost install*Query() creates several queries on the interfaces. it no more returns a single query but a boolean until the API is adapted */ - /*clsQuery* */bool installServiceQuery(const char* p_pcServiceType, + clsQuery* installServiceQuery(const char* p_pcServiceType, const char* p_pcProtocol, clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer); - /*clsQuery* */bool installServiceQuery(const char* p_pcServiceType, + clsQuery* installServiceQuery(const char* p_pcServiceType, const char* p_pcProtocol, - clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor, - std::list* ret = nullptr); + clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor); /*clsQuery* */bool installHostQuery(const char* p_pcHostName, clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer); /*clsQuery* */bool installHostQuery(const char* p_pcHostName, @@ -1438,8 +1437,7 @@ class clsLEAMDNSHost clsQuery* _findNextQueryByDomain(const clsRRDomain& p_Domain, const clsQuery::enuQueryType p_QueryType, const clsQuery* p_pPrevQuery); - clsQuery* _installServiceQuery(netif* pNetIf, - const char* p_pcService, + clsQuery* _installServiceQuery(const char* p_pcService, const char* p_pcProtocol); clsQuery* _installDomainQuery(clsRRDomain& p_Domain, clsQuery::enuQueryType p_QueryType); diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index ebeaaa2e65..8f1d7460ce 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -1680,7 +1680,8 @@ bool clsLEAMDNSHost::_announce(bool p_bAnnounce, bool bResult = false; clsSendParameter sendParameter; - if (clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == m_ProbeInformation.m_ProbingStatus) + if ((clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == m_ProbeInformation.m_ProbingStatus) || + (clsProbeInformation_Base::enuProbingStatus::DoneFinally == m_ProbeInformation.m_ProbingStatus)) { bResult = true; diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index 1541d98321..c910260c17 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -802,16 +802,16 @@ clsLEAMDNSHost_Legacy::hMDNSServiceQuery clsLEAMDNSHost_Legacy::installServiceQu std::list queries; /*clsLEAMDNSHost::clsQuery* pQuery =*/ - hostInformation.m_pHost->installServiceQuery(p_pcService, p_pcProtocol, [this, p_fnCallback](const clsLEAMDNSHost::clsQuery& /*p_Query*/, - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor & p_AnswerAccessor, - clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, // flags for the updated answer item - bool p_bSetContent)->void - { - if (p_fnCallback) // void(const stcMDNSServiceInfo& p_MDNSServiceInfo, MDNSResponder::AnswerType p_AnswerType, bool p_bSetContent) - { - p_fnCallback(stcMDNSServiceInfo(p_AnswerAccessor), _answerFlagsToAnswerType(p_QueryAnswerTypeFlags), p_bSetContent); - } - }, &queries); +// hostInformation.m_pHost->installServiceQuery(p_pcService, p_pcProtocol, [this, p_fnCallback](const clsLEAMDNSHost::clsQuery& /*p_Query*/, +// const clsLEAMDNSHost::clsQuery::clsAnswerAccessor & p_AnswerAccessor, +// clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, // flags for the updated answer item +// bool p_bSetContent)->void +// { +// if (p_fnCallback) // void(const stcMDNSServiceInfo& p_MDNSServiceInfo, MDNSResponder::AnswerType p_AnswerType, bool p_bSetContent) +// { +// p_fnCallback(stcMDNSServiceInfo(p_AnswerAccessor), _answerFlagsToAnswerType(p_QueryAnswerTypeFlags), p_bSetContent); +// } +// }, &queries); if (queries.size()) { From 27f275ea0ce68057fbc48bb81a43ed1323e50455 Mon Sep 17 00:00:00 2001 From: hreintke Date: Sun, 7 Jun 2020 17:27:38 +0200 Subject: [PATCH 034/152] fix no retrun statement in sendmessage --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 0493afd70b..fed239e54a 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -52,10 +52,11 @@ namespace experimental */ bool clsLEAMDNSHost::_sendMessage(clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { + bool bResult = true; for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf)) { - _sendMessage(pNetIf, p_rSendParameter); + bResult = bResult && _sendMessage(pNetIf, p_rSendParameter); } // Finally clear service reply masks @@ -63,6 +64,7 @@ bool clsLEAMDNSHost::_sendMessage(clsLEAMDNSHost::clsSendParameter& p_rSendParam { pService->m_u32ReplyMask = 0; } + return bResult; } bool clsLEAMDNSHost::_sendMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { From 5e58c73c3aafd902ac17fd449f81ecb7a7d63cd5 Mon Sep 17 00:00:00 2001 From: hreintke Date: Mon, 8 Jun 2020 13:43:39 +0200 Subject: [PATCH 035/152] wip installHostQuery fix service announce --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 14 +++++--------- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 6 +++--- libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 3 ++- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index f5e90953b5..d49b2f4c2b 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -687,10 +687,9 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcSe /* clsLEAmDNS2_Host::installHostQuery (answer) */ -/*clsLEAMDNSHost::clsQuery* */ bool clsLEAMDNSHost::installHostQuery(const char* p_pcHostName, +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installHostQuery(const char* p_pcHostName, clsLEAMDNSHost::clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer) { - bool bResult = false; clsQuery* pQuery = 0; if ((p_pcHostName) && (*p_pcHostName)) { @@ -700,19 +699,17 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcSe : 0))) { pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; - bResult = true; } } - return bResult; + return pQuery; } - /* clsLEAmDNS2_Host::installHostQuery (accessor) */ -/*clsLEAMDNSHost::clsQuery* */ bool clsLEAMDNSHost::installHostQuery(const char* p_pcHostName, + +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installHostQuery(const char* p_pcHostName, clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor) { - bool bResult = true; clsQuery* pQuery = 0; if ((p_pcHostName) && (*p_pcHostName)) { @@ -722,10 +719,9 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcSe : 0))) { pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; - bResult = true; } } - return bResult; + return pQuery; } /* diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index be72504277..ef815c7571 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -130,7 +130,7 @@ Enable/disable debug trace macros */ #if defined(DEBUG_ESP_PORT) && defined(DEBUG_ESP_MDNS_RESPONDER) -//#define DEBUG_ESP_MDNS_INFO +#define DEBUG_ESP_MDNS_INFO #define DEBUG_ESP_MDNS_INFO2 //#define DEBUG_ESP_MDNS_ERR //#define DEBUG_ESP_MDNS_TX @@ -1352,9 +1352,9 @@ class clsLEAMDNSHost clsQuery* installServiceQuery(const char* p_pcServiceType, const char* p_pcProtocol, clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor); - /*clsQuery* */bool installHostQuery(const char* p_pcHostName, + clsQuery* installHostQuery(const char* p_pcHostName, clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer); - /*clsQuery* */bool installHostQuery(const char* p_pcHostName, + clsQuery* installHostQuery(const char* p_pcHostName, clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor); // Remove a dynamic service query bool removeQuery(clsQuery* p_pQuery); diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index 8f1d7460ce..e6e44b72ff 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -1708,7 +1708,8 @@ bool clsLEAMDNSHost::_announce(bool p_bAnnounce, // Announce services (service type, name, SRV (location) and TXTs) for (clsService* pService : m_Services) { - if (clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == pService->m_ProbeInformation.m_ProbingStatus) + if ((clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == pService->m_ProbeInformation.m_ProbingStatus) || + (clsProbeInformation_Base::enuProbingStatus::DoneFinally == pService->m_ProbeInformation.m_ProbingStatus)) { pService->m_u32ReplyMask = (static_cast(enuContentFlag::PTR_TYPE) | static_cast(enuContentFlag::PTR_NAME) | From 352a39afce6034983d06d9d7e0c1cb00159f9af0 Mon Sep 17 00:00:00 2001 From: hreintke Date: Mon, 8 Jun 2020 13:46:47 +0200 Subject: [PATCH 036/152] Fix MDNS Clockv2 example --- .../examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino index 0a6ad00cfe..18fa15c074 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino @@ -212,7 +212,9 @@ void setup(void) { bool p_bProbeResult)->void { Serial.printf("mDNSHost_AP::ProbeResultCallback: '%s' is %s\n", p_pcDomainName, (p_bProbeResult ? "FREE" : "USED!")); // Unattended added service - p_rMDNSHost.addService(0, "espclk", "tcp", 80); + hMDNSService = p_rMDNSHost.addService(0, "espclk", "tcp", 80); + hMDNSService->addDynamicServiceTxt("curtime", getTimeString()); + hMDNSService->setDynamicServiceTxtCallback(MDNSDynamicServiceTxtCallback); })) { Serial.println("mDNS-AP started"); } else { From c786920b7a77b2964eca49e8159c0f0385d33a63 Mon Sep 17 00:00:00 2001 From: hreintke Date: Mon, 8 Jun 2020 16:57:03 +0200 Subject: [PATCH 037/152] fix ipv6 --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index e6e44b72ff..f3f1c220be 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -1996,7 +1996,7 @@ bool clsLEAMDNSHost::_checkQueryCache() { // Needs update if ((bAAAAUpdateQuerySent) || - ((bResult = _sendQuery(pNetIf, pQAnswer->m_HostDomain, DNS_RRTYPE_AAAA)))) + ((bResult = _sendQuery(pQAnswer->m_HostDomain, DNS_RRTYPE_AAAA)))) { pIPv6Address->m_TTL.restart(); bAAAAUpdateQuerySent = true; From 258afd412fa88d3ab945727accff3b92a78dca42 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 9 Jun 2020 11:20:35 +0200 Subject: [PATCH 038/152] fix emulation on host for LEAmDNS2x --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 2 ++ .../ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 2 ++ tests/host/Makefile | 6 ++++++ tests/host/common/ArduinoMainUdp.cpp | 5 ----- tests/host/common/MocklwIP.cpp | 3 ++- tests/host/common/MocklwIP.h | 15 +++++++++++++++ tests/host/common/include/UdpContext.h | 7 +++++-- tests/host/common/user_interface.cpp | 4 +--- 8 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 tests/host/common/MocklwIP.h diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index d49b2f4c2b..86f9b5fc02 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -22,6 +22,8 @@ */ +#include + #include "ESP8266mDNS.h" #include "LEAmDNS2Host.h" diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp index e027f494ac..1b5acaa076 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -22,6 +22,8 @@ */ +#include + #include "ESP8266mDNS.h" #include "LEAmDNS2Host.h" diff --git a/tests/host/Makefile b/tests/host/Makefile index f6361f36aa..6a1e987d8a 100644 --- a/tests/host/Makefile +++ b/tests/host/Makefile @@ -275,6 +275,12 @@ OPT_ARDUINO_LIBS ?= $(addprefix ../../libraries/,\ LEAmDNS_Structs.cpp \ LEAmDNS_Transfer.cpp \ ESP8266mDNS.cpp \ + LEAmDNS2Host.cpp \ + LEAmDNS2Host_Control.cpp \ + LEAmDNS2Host_Debug.cpp \ + LEAmDNS2Host_Structs.cpp \ + LEAmDNS2Host_Transfer.cpp \ + LEAmDNS2_Backbone.cpp \ ) \ ArduinoOTA/ArduinoOTA.cpp \ DNSServer/src/DNSServer.cpp \ diff --git a/tests/host/common/ArduinoMainUdp.cpp b/tests/host/common/ArduinoMainUdp.cpp index 2962f4a483..6c4a034d2a 100644 --- a/tests/host/common/ArduinoMainUdp.cpp +++ b/tests/host/common/ArduinoMainUdp.cpp @@ -29,11 +29,6 @@ DEALINGS WITH THE SOFTWARE. */ -#include "lwip/opt.h" -#include "lwip/udp.h" -#include "lwip/inet.h" -#include "lwip/igmp.h" -#include "lwip/mem.h" #include #include #include diff --git a/tests/host/common/MocklwIP.cpp b/tests/host/common/MocklwIP.cpp index 8628cc47bc..1e19f20fc7 100644 --- a/tests/host/common/MocklwIP.cpp +++ b/tests/host/common/MocklwIP.cpp @@ -1,6 +1,7 @@ #include -#include + +#include "MocklwIP.h" esp8266::AddressListImplementation::AddressList addrList; diff --git a/tests/host/common/MocklwIP.h b/tests/host/common/MocklwIP.h new file mode 100644 index 0000000000..e7fead4cb7 --- /dev/null +++ b/tests/host/common/MocklwIP.h @@ -0,0 +1,15 @@ + +#ifndef __MOCKLWIP_H +#define __MOCKLWIP_H + +extern "C" +{ + +#include +#include + +extern netif netif0; + +} // extern "C" + +#endif // __MOCKLWIP_H diff --git a/tests/host/common/include/UdpContext.h b/tests/host/common/include/UdpContext.h index c4a87c19b7..12c41b433e 100644 --- a/tests/host/common/include/UdpContext.h +++ b/tests/host/common/include/UdpContext.h @@ -23,6 +23,9 @@ #include +#include +#include + class UdpContext; #define GET_IP_HDR(pb) reinterpret_cast(((uint8_t*)((pb)->payload)) - UDP_HLEN - IP_HLEN); @@ -136,7 +139,7 @@ class UdpContext return pos <= _inbufsize; } - uint32_t getRemoteAddress() + IPAddress getRemoteAddress() { return _dst.addr; } @@ -146,7 +149,7 @@ class UdpContext return _dstport; } - uint32_t getDestAddress() + IPAddress getDestAddress() { mockverbose("TODO: implement UDP getDestAddress\n"); return 0; //ip_hdr* iphdr = GET_IP_HDR(_rx_buf); diff --git a/tests/host/common/user_interface.cpp b/tests/host/common/user_interface.cpp index e7b109401a..00482a356e 100644 --- a/tests/host/common/user_interface.cpp +++ b/tests/host/common/user_interface.cpp @@ -39,13 +39,11 @@ #include #include +#include "MocklwIP.h" extern "C" { -#include -#include - uint8 wifi_get_opmode(void) { return STATION_MODE; From 45c83eff72a87e214d19a520b6010545a7c2cb03 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 9 Jun 2020 11:21:04 +0200 Subject: [PATCH 039/152] fix warning --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 86f9b5fc02..b2d3078246 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -663,7 +663,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcSe clsLEAMDNSHost::clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer) { clsQuery* pQuery = 0; - if (pQuery = _installServiceQuery(p_pcService, p_pcProtocol)) + if ((pQuery = _installServiceQuery(p_pcService, p_pcProtocol))) { pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; } @@ -679,7 +679,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcSe clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor) { clsQuery* pQuery = 0; - if (pQuery = _installServiceQuery(p_pcService, p_pcProtocol)) + if ((pQuery = _installServiceQuery(p_pcService, p_pcProtocol))) { pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; } From 49979f2f5fefadee3f3371d7d4a03b1771fbbab7 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 9 Jun 2020 22:09:24 +0200 Subject: [PATCH 040/152] comment out 3 delay(clsConsts::u32SendCooldown) - separate defines in a new _Priv.h file --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 5 +- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 87 ------------- .../ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 2 +- .../ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp | 2 +- .../ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 2 +- .../ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 8 +- .../ESP8266mDNS/src/LEAmDNS2_Backbone.cpp | 2 +- libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h | 117 ++++++++++++++++++ 8 files changed, 131 insertions(+), 94 deletions(-) create mode 100644 libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index b2d3078246..ba2eae1800 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -26,6 +26,7 @@ #include "ESP8266mDNS.h" #include "LEAmDNS2Host.h" +#include "LEAmDNS2_Priv.h" #ifdef MDNS_IPV4_SUPPORT #include @@ -748,7 +749,7 @@ bool clsLEAMDNSHost::update(void) { bool bResult = false; - bResult = (_updateProbeStatus() && _checkQueryCache()) + bResult = (_updateProbeStatus() && _checkQueryCache()); /* for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf)) @@ -764,7 +765,7 @@ bool clsLEAMDNSHost::update(void) //} } */ - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s update: FAILED (Not connected?)!\n"), _DH());); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s update: FAILED (Not connected?)!\n"), _DH())); return bResult; } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index ef815c7571..7d48a1afe3 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -113,93 +113,6 @@ #define MDNS_IPV6_SUPPORT // If we've got IPv6 support, then we need IPv6 support :-) #endif -/* - LWIP_OPEN_SRC -*/ -#ifndef LWIP_OPEN_SRC -#define LWIP_OPEN_SRC -#endif - -/* - Enable class debug functions -*/ -#define ESP_8266_MDNS_INCLUDE -#define DEBUG_ESP_MDNS_RESPONDER - -/* - Enable/disable debug trace macros -*/ -#if defined(DEBUG_ESP_PORT) && defined(DEBUG_ESP_MDNS_RESPONDER) -#define DEBUG_ESP_MDNS_INFO -#define DEBUG_ESP_MDNS_INFO2 -//#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 -#define DEBUG_EX_INFO_IF(C,A...) do if (C) { A; } while (0) -#else -#define DEBUG_EX_INFO(A) -#define DEBUG_EX_INFO_IF(C,A...) -#endif -#ifdef DEBUG_ESP_MDNS_INFO2 -#define DEBUG_EX_INFO2(A) A -#define DEBUG_EX_INFO2_IF(C,A...) do if (C) { A; } while (0) -#else -#define DEBUG_EX_INFO2(A) -#define DEBUG_EX_INFO2_IF(C,A...) -#endif -#ifdef DEBUG_ESP_MDNS_ERR -#define DEBUG_EX_ERR(A) A -#define DEBUG_EX_ERR_IF(C,A...) do if (C) { A; } while (0) -#else -#define DEBUG_EX_ERR(A) -#define DEBUG_EX_ERR_IF(C,A...) -#endif -#ifdef DEBUG_ESP_MDNS_TX -#define DEBUG_EX_TX(A) 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_INFO2(A) -#define DEBUG_EX_ERR(A) -#define DEBUG_EX_TX(A) -#define DEBUG_EX_RX(A) -#endif - -/* - Enable/disable the usage of the F() macro in debug trace printf calls. - There needs to be an PGM comptible printf function to use this. - - USE_PGM_PRINTF and F -*/ -#define USE_PGM_PRINTF - -#ifdef USE_PGM_PRINTF -#else -#ifdef F -#undef F -#endif -#define F(A) A -#endif - - namespace esp8266 { diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index f3f1c220be..f471999fa3 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -24,7 +24,7 @@ #include "ESP8266mDNS.h" #include "LEAmDNS2Host.h" - +#include "LEAmDNS2_Priv.h" namespace esp8266 { diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp index f0edcd4232..50a7d07594 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp @@ -24,7 +24,7 @@ #include "ESP8266mDNS.h" #include "LEAmDNS2Host.h" - +#include "LEAmDNS2_Priv.h" namespace esp8266 { diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp index 1b5acaa076..3cae0ca3bc 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -26,7 +26,7 @@ #include "ESP8266mDNS.h" #include "LEAmDNS2Host.h" - +#include "LEAmDNS2_Priv.h" namespace esp8266 { diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index fed239e54a..b7a800d393 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -25,7 +25,7 @@ #include // for can_yield() #include "ESP8266mDNS.h" #include "LEAmDNS2Host.h" - +#include "LEAmDNS2_Priv.h" namespace esp8266 { @@ -117,11 +117,13 @@ bool clsLEAMDNSHost::_sendMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParamete (Serial.println("Did send UC"), true)*/); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage (V4): FAILED!\n"), _DH());); +#if 0 if ((clsConsts::u32SendCooldown) && (can_yield())) { delay(clsConsts::u32SendCooldown); } +#endif } else { @@ -186,11 +188,13 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(netif* pNetIf, clsLEAMDNSHost::clsSe (Serial.println("Did send MC V4"), true)*/); DEBUG_EX_ERR(if (!bIPv4Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast (V4): FAILED!\n"), _DH());); +#if 0 if ((clsConsts::u32SendCooldown) && (can_yield())) { delay(clsConsts::u32SendCooldown); } +#endif } #endif @@ -212,11 +216,13 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(netif* pNetIf, clsLEAMDNSHost::clsSe (Serial.println("Did send MC V6"), true)*/); DEBUG_EX_ERR(if (!bIPv6Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast (IPv6): FAILED! (%s, %s, %s)\n"), _DH(), (_getResponderIPAddress(pNetIf, enuIPProtocolType::V6).isSet() ? "1" : "0"), (bPrepareMessage ? "1" : "0"), (bUDPContextSend ? "1" : "0"));); +#if 0 if ((clsConsts::u32SendCooldown) && (can_yield())) { delay(clsConsts::u32SendCooldown); } +#endif } #endif diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp index d4ab972a30..870b00e24e 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp @@ -24,7 +24,7 @@ #include "ESP8266mDNS.h" #include "LEAmDNS2Host.h" - +#include "LEAmDNS2_Priv.h" namespace esp8266 { diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h new file mode 100644 index 0000000000..b0235ec123 --- /dev/null +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h @@ -0,0 +1,117 @@ +/* + LEAmDNS_Priv.h + + License (MIT license): + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#ifndef MDNS2_PRIV_H +#define MDNS2_PRIV_H + +/* + LWIP_OPEN_SRC +*/ +#ifndef LWIP_OPEN_SRC +#define LWIP_OPEN_SRC +#endif + +/* + Enable class debug functions +*/ +#define ESP_8266_MDNS_INCLUDE +#define DEBUG_ESP_MDNS_RESPONDER + +/* + Enable/disable debug trace macros +*/ +#if defined(DEBUG_ESP_PORT) +#define DEBUG_ESP_MDNS_ERR +#endif +#if defined(DEBUG_ESP_PORT) && defined(DEBUG_ESP_MDNS_RESPONDER) +#define DEBUG_ESP_MDNS_INFO +#define DEBUG_ESP_MDNS_INFO2 +//#define DEBUG_ESP_MDNS_TX +//#define DEBUG_ESP_MDNS_RX +#endif + +#ifdef DEBUG_ESP_MDNS_RESPONDER +#ifdef DEBUG_ESP_MDNS_INFO +#define DEBUG_EX_INFO(A) do { A; } while (0) +#define DEBUG_EX_INFO_IF(C,A...) do if (C) { A; } while (0) +#else +#define DEBUG_EX_INFO(A) +#define DEBUG_EX_INFO_IF(C,A...) +#endif +#ifdef DEBUG_ESP_MDNS_INFO2 +#define DEBUG_EX_INFO2(A) do { A; } while (0) +#define DEBUG_EX_INFO2_IF(C,A...) do if (C) { A; } while (0) +#else +#define DEBUG_EX_INFO2(A) +#define DEBUG_EX_INFO2_IF(C,A...) +#endif +#ifdef DEBUG_ESP_MDNS_ERR +#define DEBUG_EX_ERR(A) do { A; } while (0) +#define DEBUG_EX_ERR_IF(C,A...) do if (C) { A; } while (0) +#else +#define DEBUG_EX_ERR(A) +#define DEBUG_EX_ERR_IF(C,A...) +#endif +#ifdef DEBUG_ESP_MDNS_TX +#define DEBUG_EX_TX(A) do { A; } while (0) +#else +#define DEBUG_EX_TX(A) +#endif +#ifdef DEBUG_ESP_MDNS_RX +#define DEBUG_EX_RX(A) do { A; } while (0) +#else +#define DEBUG_EX_RX(A) +#endif + +#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_INFO2(A) +#define DEBUG_EX_ERR(A) +#define DEBUG_EX_TX(A) +#define DEBUG_EX_RX(A) +#endif + +/* + Enable/disable the usage of the F() macro in debug trace printf calls. + There needs to be an PGM comptible printf function to use this. + + USE_PGM_PRINTF and F +*/ +#define USE_PGM_PRINTF + +#ifdef USE_PGM_PRINTF +#else +#ifdef F +#undef F +#endif +#define F(A) A +#endif + + +#endif // MDNS2_PRIV_H From 310526d9c96e2e987798f43f21e2fcbd58694b7b Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 9 Jun 2020 22:21:49 +0200 Subject: [PATCH 041/152] fix debug macros --- libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h index b0235ec123..de0021d000 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h @@ -53,21 +53,21 @@ #ifdef DEBUG_ESP_MDNS_RESPONDER #ifdef DEBUG_ESP_MDNS_INFO -#define DEBUG_EX_INFO(A) do { A; } while (0) +#define DEBUG_EX_INFO(A) A #define DEBUG_EX_INFO_IF(C,A...) do if (C) { A; } while (0) #else #define DEBUG_EX_INFO(A) #define DEBUG_EX_INFO_IF(C,A...) #endif #ifdef DEBUG_ESP_MDNS_INFO2 -#define DEBUG_EX_INFO2(A) do { A; } while (0) +#define DEBUG_EX_INFO2(A) A #define DEBUG_EX_INFO2_IF(C,A...) do if (C) { A; } while (0) #else #define DEBUG_EX_INFO2(A) #define DEBUG_EX_INFO2_IF(C,A...) #endif #ifdef DEBUG_ESP_MDNS_ERR -#define DEBUG_EX_ERR(A) do { A; } while (0) +#define DEBUG_EX_ERR(A) A #define DEBUG_EX_ERR_IF(C,A...) do if (C) { A; } while (0) #else #define DEBUG_EX_ERR(A) From 2204902b8917bf4056d3a8c2e2d27847b9a2fa2d Mon Sep 17 00:00:00 2001 From: hreintke Date: Thu, 11 Jun 2020 10:58:07 +0200 Subject: [PATCH 042/152] Add serviceMonitor_v2 --- .../mDNS_ServiceMonitor_v2/MSDN_V2.cpp | 275 ++++++++++++++++++ .../LEAmDNS/mDNS_ServiceMonitor_v2/MSDN_V2.h | 22 ++ 2 files changed, 297 insertions(+) create mode 100644 libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/MSDN_V2.cpp create mode 100644 libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/MSDN_V2.h diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/MSDN_V2.cpp b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/MSDN_V2.cpp new file mode 100644 index 0000000000..8c77734767 --- /dev/null +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/MSDN_V2.cpp @@ -0,0 +1,275 @@ +/* + 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 + +/* + 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 "LocalDefines.h" +#include "Netdump.h" +using namespace NetCapture; +/* + Global defines and vars +*/ + +#define SERVICE_PORT 80 // HTTP port + +char* pcHostDomain = 0; // Negociated host domain +bool bHostDomainConfirmed = false; // Flags the confirmation of the host domain +MDNSResponder::clsService* hMDNSService = 0; // The handle of the http service in the MDNS responder +MDNSResponder::clsQuery* hMDNSServiceQuery = 0; // The handle of the 'http.tcp' service query in the MDNS responder + +const String cstrNoHTTPServices = "Currently no 'http.tcp' services in the local network!
"; +String strHTTPServices = cstrNoHTTPServices; + +// HTTP server at port 'SERVICE_PORT' will respond to HTTP requests +ESP8266WebServer server(SERVICE_PORT); + + +/* + setStationHostname +*/ +bool setStationHostname(const char* p_pcHostname) { + + if (p_pcHostname) { + WiFi.hostname(p_pcHostname); + Serial.printf("setStationHostname: Station hostname is set to '%s'\n", p_pcHostname); + return true; + } + return false; +} + + +void MDNSServiceQueryCallback(const MDNSResponder::clsQuery& p_Query, + const MDNSResponder::clsQuery::clsAnswer& p_Answer, + MDNSResponder::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, + bool p_bSetContent) +{ + Serial.printf("----------------Query callback cnt = %d tp = %2x set = %s\r\n",p_Query.answerCount(),p_QueryAnswerTypeFlags,p_bSetContent?"true":"false"); + + for (int i=0;isetHostname(). + +*/ + +void hostProbeResult(clsMDNSHost & p_rMDNSHost, String p_pcDomainName, bool p_bProbeResult) { + + Serial.printf("MDNSHostProbeResultCallback: Host domain '%s.local' is %s\n", p_pcDomainName.c_str(), (p_bProbeResult ? "free" : "already USED!")); + + if (true == p_bProbeResult) { + // Set station hostname + setStationHostname(pcHostDomain); + + if (!bHostDomainConfirmed) { + // Hostname free -> setup clock service + bHostDomainConfirmed = true; + + if (!hMDNSService) { + // Add a 'http.tcp' service to port 'SERVICE_PORT', using the host domain as instance domain + hMDNSService = MDNS.addService(0, "http", "tcp", SERVICE_PORT, serviceProbeResult); + + if (hMDNSService) { + hMDNSService->setProbeResultCallback(serviceProbeResult); + // MDNS.setServiceProbeResultCallback(hMDNSService, serviceProbeResult); + + // Add some '_http._tcp' protocol specific MDNS service TXT items + // See: http://www.dns-sd.org/txtrecords.html#http + hMDNSService->addServiceTxt("user", ""); + hMDNSService->addServiceTxt("password", ""); + hMDNSService->addServiceTxt("path", "/"); + } + + // Install dynamic 'http.tcp' service query + if (!hMDNSServiceQuery) { + hMDNSServiceQuery = MDNS.installServiceQuery("http", "tcp", MDNSServiceQueryCallback); + if (hMDNSServiceQuery) { + Serial.printf("MDNSProbeResultCallback: Service query for 'http.tcp' services installed.\n"); + } else { + Serial.printf("MDNSProbeResultCallback: FAILED to install service query for 'http.tcp' services!\n"); + } + } + } + } + } + else { + // Change hostname, use '-' as divider between base name and index + if (MDNSResponder::indexDomainName(pcHostDomain, "-", 0)) { + MDNS.setHostName(pcHostDomain); + } else { + Serial.println("MDNSProbeResultCallback: FAILED to update hostname!"); + } + } +} + +/* + HTTP request function (not found is handled by server) +*/ +void handleHTTPRequest() { + Serial.println(""); + Serial.println("HTTP Request"); + + IPAddress ip = server.client().localIP(); + String ipStr = ip.toString(); + String s = "\r\n

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

"; + s += "

Local HTTP services are :

"; + s += "
    "; + /* + for (auto info : MDNS.answerInfo(hMDNSServiceQuery)) { + s += "
  1. "; + s += info.serviceDomain(); + if (info.hostDomainAvailable()) { + s += "
    Hostname: "; + s += String(info.hostDomain()); + s += (info.hostPortAvailable()) ? (":" + String(info.hostPort())) : ""; + } + if (info.IP4AddressAvailable()) { + s += "
    IP4:"; + for (auto ip : info.IP4Adresses()) { + s += " " + ip.toString(); + } + } + if (info.txtAvailable()) { + s += "
    TXT:
    "; + for (auto kv : info.keyValues()) { + s += "\t" + String(kv.first) + " : " + String(kv.second) + "
    "; + } + } + s += "
  2. "; + + } + */ + s += "

"; + + Serial.println("Sending 200"); + server.send(200, "text/html", s); + Serial.println("Done with request"); +} + +//Netdump nd(Netdump::interface::LWIP); + + +/* + setup +*/ +void setup(void) { + Serial.begin(115200); +// nd.printDump(Serial, Packet::PacketDetail::NONE, [](const Packet& p){return(p.getInOut() && p.isMDNS());}); + Serial.setDebugOutput(false); + + // Connect to WiFi network + WiFi.mode(WIFI_AP_STA); + WiFi.softAP("Soft1"); + 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 HTTP server + server.on("/", handleHTTPRequest); + + // Setup MDNS responders + MDNS.setProbeResultCallback(hostProbeResult); + + // Init the (currently empty) host domain string with 'esp8266' + MDNS.begin("esp8266_v2"); +/* + if ((!MDNSResponder::indexDomain(pcHostDomain, 0, "esp8266")) || + (!MDNS.begin(pcHostDomain))) { + Serial.println(" Error setting up MDNS responder!"); + while (1) { // STOP + delay(1000); + } + } +*/ + Serial.println("MDNS responder started"); + + // Start HTTP server + server.begin(); + Serial.println("HTTP server started"); +} + +void loop(void) { + // Check if a request has come in + server.handleClient(); + // Allow MDNS processing + MDNS.update(); +} + + + diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/MSDN_V2.h b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/MSDN_V2.h new file mode 100644 index 0000000000..62ca01f6ab --- /dev/null +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/MSDN_V2.h @@ -0,0 +1,22 @@ +// Only modify this file to include +// - function definitions (prototypes) +// - include files +// - extern variable definitions +// In the appropriate section + +#ifndef _MSDN_V2_H_ +#define _MSDN_V2_H_ +#include "Arduino.h" +//add your includes for the project MSDN_V2 here + + +//end of add your includes here + + +//add your function definitions for the project MSDN_V2 here + + + + +//Do not add code below this line +#endif /* _MSDN_V2_H_ */ From 46ab0a00a37f6bfad0074afbfa31d0db1fa8d900 Mon Sep 17 00:00:00 2001 From: hreintke Date: Thu, 11 Jun 2020 16:35:06 +0200 Subject: [PATCH 043/152] Improve MDNSServiceQueryCallback in servicemonior v1 --- .../mDNS_ServiceMonitor_v2/MSDN_V2.cpp | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/MSDN_V2.cpp b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/MSDN_V2.cpp index 8c77734767..dd87b7d14e 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/MSDN_V2.cpp +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/MSDN_V2.cpp @@ -87,13 +87,32 @@ void MDNSServiceQueryCallback(const MDNSResponder::clsQuery& p_Query, MDNSResponder::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, bool p_bSetContent) { - Serial.printf("----------------Query callback cnt = %d tp = %2x set = %s\r\n",p_Query.answerCount(),p_QueryAnswerTypeFlags,p_bSetContent?"true":"false"); - - for (int i=0;i(MDNSResponder::clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain): + answerInfo = "ServiceDomain " + String(p_Answer.m_ServiceDomain.c_str()); + break; + + case static_cast(MDNSResponder::clsQuery::clsAnswer::enuQueryAnswerType::HostDomainPort): + answerInfo = "HostDomainAndPort " + String(p_Answer.m_HostDomain.c_str()) + ":" + String(p_Answer.m_u16Port); + break; + case static_cast(MDNSResponder::clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address): + answerInfo = "IP4Address "; + for (auto ip : p_Answer.m_IPv4Addresses) { + answerInfo += "- " + ip->m_IPAddress.toString(); + }; + break; + case static_cast(MDNSResponder::clsQuery::clsAnswer::enuQueryAnswerType::Txts): + answerInfo = "TXT "; + for (auto kv : p_Answer.m_Txts.m_Txts) { + answerInfo += "\nkv : " + String(kv->m_pcKey) + " : " + String(kv->m_pcValue); + } + break; + default : + answerInfo = "Unknown Answertype " + String(p_QueryAnswerTypeFlags); + + } + Serial.printf("Answer %s %s\n", answerInfo.c_str(), p_bSetContent ? "Modified" : "Deleted"); } /* From 225a46e6e6585b45a2aadf411e511d07880d3c18 Mon Sep 17 00:00:00 2001 From: hreintke Date: Thu, 11 Jun 2020 16:36:31 +0200 Subject: [PATCH 044/152] Fix HostDomainPort --- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index 7d48a1afe3..e5e21d4c08 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -945,6 +945,7 @@ class clsLEAMDNSHost ServiceDomain = 0x01, // Service domain HostDomain = 0x02, // Host domain Port = 0x04, // Port + HostDomainPort = 0x06, Txts = 0x08, // TXT items #ifdef MDNS_IPV4_SUPPORT IPv4Address = 0x10, // IPv4 address From 39dafbca9e29d2f44cdac29a48af988df61bf035 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 12 Jun 2020 00:16:49 +0200 Subject: [PATCH 045/152] temporary test mDNS_ServiceMonitor_v2_test.ino --- .../mDNS_ServiceMonitor_v2_test.ino | 393 ++++++++++++++++++ 1 file changed, 393 insertions(+) create mode 100644 libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino new file mode 100644 index 0000000000..1386b072b0 --- /dev/null +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino @@ -0,0 +1,393 @@ +/* + 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. + +*/ + + +#ifndef STASSID +#define STASSID "ssid" +#define STAPSK "psk" +#endif + + +#include +#include +#include + +/* + Include the clsMDNSHost (the library needs to be included also) + As LEA clsMDNSHost is experimantal in the ESP8266 environment currently, the + legacy clsMDNSHost is defaulted in th include file. + There are two ways to access LEA clsMDNSHost: + 1. Prepend every declaration and call to global declarations or functions with the namespace, like: + 'LEAmDNS:clsMDNSHost::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 "LocalDefines.h" +//#include "Netdump.h" +//using namespace NetCapture; +/* + Global defines and vars +*/ + +clsMDNSHost MDNSv2; + +#define SERVICE_PORT 80 // HTTP port + +char* pcHostDomain = 0; // Negociated host domain +bool bHostDomainConfirmed = false; // Flags the confirmation of the host domain +clsMDNSHost::clsService* hMDNSService = 0; // The handle of the http service in the MDNS responder +clsMDNSHost::clsQuery* hMDNSServiceQuery = 0; // The handle of the 'http.tcp' service query in the MDNS responder + +const String cstrNoHTTPServices = "Currently no 'http.tcp' services in the local network!
"; +String strHTTPServices = cstrNoHTTPServices; + +// HTTP server at port 'SERVICE_PORT' will respond to HTTP requests +ESP8266WebServer server(SERVICE_PORT); + + +/* + setStationHostname +*/ +bool setStationHostname(const char* p_pcHostname) +{ + + if (p_pcHostname) + { + WiFi.hostname(p_pcHostname); + Serial.printf("setStationHostname: Station hostname is set to '%s'\n", p_pcHostname); + return true; + } + return false; +} + +void MDNSServiceQueryCallback(const clsMDNSHost::clsQuery& p_Query, + const clsMDNSHost::clsQuery::clsAnswer& p_Answer, + clsMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, + bool p_bSetContent) +{ + Serial.printf("CB MDNSServiceQueryCallback\n"); + + String answerInfo; + clsMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType mask = 0; + if (p_QueryAnswerTypeFlags & (uint8_t)clsMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain) + { + answerInfo += "(ServiceDomain "; + answerInfo += p_Answer.m_ServiceDomain.c_str(); + answerInfo += ")"; + mask |= (uint8_t)clsMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain; + } + if (p_QueryAnswerTypeFlags & (uint8_t)clsMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::HostDomainPort) + { + answerInfo += "(HostDomainAndPort "; + answerInfo += p_Answer.m_HostDomain.c_str(); + answerInfo += ")"; + mask |= (uint8_t)clsMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::HostDomainPort; + } + if (p_QueryAnswerTypeFlags & (uint8_t)clsMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address) + { + answerInfo += "(IP4Address "; + for (auto ip : p_Answer.m_IPv4Addresses) + { + answerInfo += "- "; + answerInfo += ip->m_IPAddress.toString(); + } + answerInfo += ")"; + mask |= (uint8_t)clsMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address; + } +#ifdef MDNS_IPV6_SUPPORT + if (p_QueryAnswerTypeFlags & (uint8_t)clsMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address) + { + answerInfo += "(IP6Address "; + for (auto ip : p_Answer.m_IPv6Addresses) + { + answerInfo += "- "; + answerInfo += ip->m_IPAddress.toString(); + } + answerInfo += ")"; + mask |= (uint8_t)clsMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address; + } +#endif // MDNS_IPV6_SUPPORT + if (p_QueryAnswerTypeFlags & (uint8_t)clsMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Txts) + { + answerInfo += "(TXT "; + for (auto kv : p_Answer.m_Txts.m_Txts) + { + answerInfo += "kv: "; + answerInfo += kv->m_pcKey; + answerInfo += ": "; + answerInfo += kv->m_pcValue; + } + answerInfo += ")"; + mask |= (uint8_t)clsMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Txts; + } + if (p_QueryAnswerTypeFlags & ~mask) + { + answerInfo += "(other flags are set)"; + } + +#if 0 + switch (p_QueryAnswerTypeFlags) + { + case static_cast(clsMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain): + answerInfo = "ServiceDomain " + String(p_Answer.m_ServiceDomain.c_str()); + break; + + case static_cast(clsMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::HostDomainPort): + answerInfo = "HostDomainAndPort " + String(p_Answer.m_HostDomain.c_str()) + ":" + String(p_Answer.m_u16Port); + break; + case static_cast(clsMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address): + answerInfo = "IP4Address "; + for (auto ip : p_Answer.m_IPv4Addresses) + { + answerInfo += "- " + ip->m_IPAddress.toString(); + }; + break; + case static_cast(clsMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Txts): + answerInfo = "TXT "; + for (auto kv : p_Answer.m_Txts.m_Txts) + { + answerInfo += "\nkv : " + String(kv->m_pcKey) + " : " + String(kv->m_pcValue); + } + break; + default : + answerInfo = "Unknown Answertype " + String(p_QueryAnswerTypeFlags); + + } +#endif + + Serial.printf("Answer %s %s\n", answerInfo.c_str(), p_bSetContent ? "Modified" : "Deleted"); +} + +/* + MDNSServiceProbeResultCallback + Probe result callback for Services +*/ + +void serviceProbeResult(clsMDNSHost::clsService& p_rMDNSService, + const char* p_pcInstanceName, + bool p_bProbeResult) +{ + Serial.printf("MDNSServiceProbeResultCallback: Service %s probe %s\n", p_pcInstanceName, (p_bProbeResult ? "succeeded." : "failed!")); +} + +/* + MDNSHostProbeResultCallback + + Probe result callback for the host domain. + If the domain is free, the host domain is set and the http service is + added. + If the domain is already used, a new name is created and the probing is + restarted via p_pclsMDNSHost->setHostname(). + +*/ + +void hostProbeResult(clsMDNSHost & p_rMDNSHost, String p_pcDomainName, bool p_bProbeResult) +{ + + Serial.printf("MDNSHostProbeResultCallback: Host domain '%s.local' is %s\n", p_pcDomainName.c_str(), (p_bProbeResult ? "free" : "already USED!")); + + if (true == p_bProbeResult) + { + // Set station hostname + setStationHostname(pcHostDomain); + Serial.printf("settng hostname = '%s'\n", pcHostDomain); + + if (!bHostDomainConfirmed) + { + // Hostname free -> setup clock service + bHostDomainConfirmed = true; + + if (!hMDNSService) + { + Serial.printf("adding service tcp.http port %d\n", SERVICE_PORT); + // Add a 'http.tcp' service to port 'SERVICE_PORT', using the host domain as instance domain + hMDNSService = MDNSv2.addService(0, "http", "tcp", SERVICE_PORT, serviceProbeResult); + + if (hMDNSService) + { + Serial.printf("hMDNSService\n"); + hMDNSService->setProbeResultCallback(serviceProbeResult); + // MDNSv2.setServiceProbeResultCallback(hMDNSService, serviceProbeResult); + + // Add some '_http._tcp' protocol specific MDNS service TXT items + // See: http://www.dns-sd.org/txtrecords.html#http + hMDNSService->addServiceTxt("user", "x"); + hMDNSService->addServiceTxt("password", "y"); + hMDNSService->addServiceTxt("path", "/"); + } + else + Serial.printf("hMDNSService=0 ??\n"); + + // Install dynamic 'http.tcp' service query + if (!hMDNSServiceQuery) + { + Serial.printf("installing hMDNSServiceQuery\n"); + hMDNSServiceQuery = MDNSv2.installServiceQuery("http", "tcp", MDNSServiceQueryCallback); + if (hMDNSServiceQuery) + { + Serial.printf("MDNSProbeResultCallback: Service query for 'http.tcp' services installed.\n"); + Serial.printf("hMDNSServiceQuery:%p/%p\n", hMDNSServiceQuery->m_fnCallbackAnswer, MDNSServiceQueryCallback); + } + 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 (clsMDNSHost::indexDomainName(pcHostDomain, "-", 0)) + { + MDNSv2.setHostName(pcHostDomain); + } + else + { + Serial.println("MDNSProbeResultCallback: FAILED to update hostname!"); + } + } +} + +/* + HTTP request function (not found is handled by server) +*/ +void handleHTTPRequest() +{ + Serial.println(""); + Serial.println("HTTP Request"); + + IPAddress ip = server.client().localIP(); + String ipStr = ip.toString(); + String s = "\r\n

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

"; + s += "

Local HTTP services are :

"; + s += "
    "; + /* + for (auto info : MDNSv2.answerInfo(hMDNSServiceQuery)) { + s += "
  1. "; + s += info.serviceDomain(); + if (info.hostDomainAvailable()) { + s += "
    Hostname: "; + s += String(info.hostDomain()); + s += (info.hostPortAvailable()) ? (":" + String(info.hostPort())) : ""; + } + if (info.IP4AddressAvailable()) { + s += "
    IP4:"; + for (auto ip : info.IP4Adresses()) { + s += " " + ip.toString(); + } + } + if (info.txtAvailable()) { + s += "
    TXT:
    "; + for (auto kv : info.keyValues()) { + s += "\t" + String(kv.first) + " : " + String(kv.second) + "
    "; + } + } + s += "
  2. "; + + } + */ + s += "

"; + + Serial.println("Sending 200"); + server.send(200, "text/html", s); + Serial.println("Done with request"); +} + +//Netdump nd(Netdump::interface::LWIP); + + +/* + setup +*/ +void setup(void) +{ + Serial.begin(115200); + // nd.printDump(Serial, Packet::PacketDetail::NONE, [](const Packet& p){return(p.getInOut() && p.isMDNS());}); + Serial.setDebugOutput(false); + + // Connect to WiFi network + WiFi.mode(WIFI_AP_STA); + WiFi.softAP("Soft1"); + WiFi.begin(STASSID, STAPSK); + Serial.println(""); + + // Wait for connection + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.print("Connected to "); + Serial.println(STASSID); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + // Setup HTTP server + server.on("/", handleHTTPRequest); + + // Setup MDNS responders + MDNSv2.setProbeResultCallback(hostProbeResult); + + // Init the (currently empty) host domain string with 'esp8266' + MDNSv2.begin("esp8266_v2"); + /* + if ((!clsMDNSHost::indexDomain(pcHostDomain, 0, "esp8266")) || + (!MDNSv2.begin(pcHostDomain))) { + Serial.println(" Error setting up MDNS responder!"); + while (1) { // STOP + delay(1000); + } + } + */ + Serial.println("MDNS responder started"); + + // Start HTTP server + server.begin(); + Serial.println("HTTP server started"); +} + +void loop(void) +{ + // Check if a request has come in + server.handleClient(); + // Allow MDNS processing + MDNSv2.update(); +} + + + From 59a6ae446f5f1cfda4a569f3cd8f0da529944a15 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 12 Jun 2020 00:17:32 +0200 Subject: [PATCH 046/152] remove useless debug messages --- libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp index 870b00e24e..1d15a04137 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp @@ -212,19 +212,10 @@ bool clsLEAMDNSHost::clsBackbone::_processUDPInput(void) if (!m_bDelayUDPProcessing) { - DEBUG_EX_INFO(uint32_t u32LoopCounter = 0; IPAddress remoteIPAddr;); while ((m_pUDPContext) && (m_pUDPContext->next())) { clsLEAMDNSHost* pHost = _findHost(); - DEBUG_EX_INFO_IF(u32LoopCounter++, - DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Multi-Loop (%u)!\n"), _DH(), u32LoopCounter); - DEBUG_EX_INFO_IF((remoteIPAddr.isSet()) && (remoteIPAddr != m_pUDPContext->getRemoteAddress()), - DEBUG_OUTPUT.printf_P(PSTR("%s _processUDPInput: Changed IP address %s->%s!\n"), - _DH(), - remoteIPAddr.toString().c_str(), - m_pUDPContext->getRemoteAddress().toString().c_str()))); - DEBUG_EX_INFO(remoteIPAddr = m_pUDPContext->getRemoteAddress()); bResult = pHost->_processUDPInput(); From accbd9ce921bfc131a92f6e00322e74742adc1b9 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 12 Jun 2020 10:39:02 +0200 Subject: [PATCH 047/152] fix debug macros --- .../mDNS_ServiceMonitor_v2_test.ino | 1 - libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h | 29 ++++++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino index 1386b072b0..2bae65501d 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino @@ -257,7 +257,6 @@ void hostProbeResult(clsMDNSHost & p_rMDNSHost, String p_pcDomainName, bool p_bP if (hMDNSServiceQuery) { Serial.printf("MDNSProbeResultCallback: Service query for 'http.tcp' services installed.\n"); - Serial.printf("hMDNSServiceQuery:%p/%p\n", hMDNSServiceQuery->m_fnCallbackAnswer, MDNSServiceQueryCallback); } else { diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h index de0021d000..e67e6c03d0 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h @@ -36,14 +36,16 @@ Enable class debug functions */ #define ESP_8266_MDNS_INCLUDE -#define DEBUG_ESP_MDNS_RESPONDER +//#define DEBUG_ESP_MDNS_RESPONDER /* Enable/disable debug trace macros */ + #if defined(DEBUG_ESP_PORT) #define DEBUG_ESP_MDNS_ERR #endif + #if defined(DEBUG_ESP_PORT) && defined(DEBUG_ESP_MDNS_RESPONDER) #define DEBUG_ESP_MDNS_INFO #define DEBUG_ESP_MDNS_INFO2 @@ -51,7 +53,14 @@ //#define DEBUG_ESP_MDNS_RX #endif +#ifdef DEBUG_ESP_PORT +#define DEBUG_OUTPUT DEBUG_ESP_PORT +#else +#define DEBUG_OUTPUT Serial +#endif + #ifdef DEBUG_ESP_MDNS_RESPONDER + #ifdef DEBUG_ESP_MDNS_INFO #define DEBUG_EX_INFO(A) A #define DEBUG_EX_INFO_IF(C,A...) do if (C) { A; } while (0) @@ -59,6 +68,7 @@ #define DEBUG_EX_INFO(A) #define DEBUG_EX_INFO_IF(C,A...) #endif + #ifdef DEBUG_ESP_MDNS_INFO2 #define DEBUG_EX_INFO2(A) A #define DEBUG_EX_INFO2_IF(C,A...) do if (C) { A; } while (0) @@ -66,6 +76,7 @@ #define DEBUG_EX_INFO2(A) #define DEBUG_EX_INFO2_IF(C,A...) #endif + #ifdef DEBUG_ESP_MDNS_ERR #define DEBUG_EX_ERR(A) A #define DEBUG_EX_ERR_IF(C,A...) do if (C) { A; } while (0) @@ -73,29 +84,31 @@ #define DEBUG_EX_ERR(A) #define DEBUG_EX_ERR_IF(C,A...) #endif + #ifdef DEBUG_ESP_MDNS_TX #define DEBUG_EX_TX(A) do { A; } while (0) #else #define DEBUG_EX_TX(A) #endif + #ifdef DEBUG_ESP_MDNS_RX #define DEBUG_EX_RX(A) do { A; } while (0) #else #define DEBUG_EX_RX(A) #endif -#ifdef DEBUG_ESP_PORT -#define DEBUG_OUTPUT DEBUG_ESP_PORT -#else -#define DEBUG_OUTPUT Serial -#endif -#else +#else // !defined(DEBUG_ESP_MDNS_RESPONDER) + #define DEBUG_EX_INFO(A) +#define DEBUG_EX_INFO_IF(C,A...) #define DEBUG_EX_INFO2(A) +#define DEBUG_EX_INFO2_IF(C,A...) #define DEBUG_EX_ERR(A) +#define DEBUG_EX_ERR_IF(C,A...) #define DEBUG_EX_TX(A) #define DEBUG_EX_RX(A) -#endif + +#endif // !defined(DEBUG_ESP_MDNS_RESPONDER) /* Enable/disable the usage of the F() macro in debug trace printf calls. From 1638402a1a4282b5a12ba683426cdc1059909f54 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 12 Jun 2020 12:27:13 +0200 Subject: [PATCH 048/152] always enable mdns error message when debug on serial port is enabled --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 29 +++++++------------ .../ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp | 4 +-- .../ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 4 +-- .../ESP8266mDNS/src/LEAmDNS2_Backbone.cpp | 2 +- libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h | 17 +---------- 5 files changed, 17 insertions(+), 39 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index ba2eae1800..af004c3e19 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -747,26 +747,19 @@ bool clsLEAMDNSHost::removeQuery(clsLEAMDNSHost::clsQuery * p_pMDNSQuery) */ bool clsLEAMDNSHost::update(void) { - bool bResult = false; + if (!_updateProbeStatus()) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: FAILED\n"), _DH())); + return false; + } - bResult = (_updateProbeStatus() && _checkQueryCache()); -/* - for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) - if (netif_is_up(pNetIf)) - { - //if (clsBackbone::sm_pBackbone->setDelayUDPProcessing(true)) - //{ - if (_checkQueryCache(pNetIf)) - { - bResult = true; - } + if (!_checkQueryCache()) + { + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: FAILED\n"), _DH())); + return false; + } - // clsBackbone::sm_pBackbone->setDelayUDPProcessing(false); - //} - } -*/ - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s update: FAILED (Not connected?)!\n"), _DH())); - return bResult; + return true; } /* diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp index 50a7d07594..d638bf209a 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp @@ -34,7 +34,7 @@ namespace experimental { -#ifdef DEBUG_ESP_MDNS_RESPONDER +#ifdef DEBUG_ESP_PORT /* clsLEAmDNS2_Host::_DH @@ -315,7 +315,7 @@ const char* clsLEAMDNSHost::_NSECBitmap2String(const clsNSECBitmap* p_pNSECBitma return acFlagsString; // 31 } -#endif // DEBUG_ESP_MDNS_RESPONDER +#endif // DEBUG_ESP_PORT } // namespace MDNSImplementation diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index b7a800d393..0be8d6bf17 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -1451,7 +1451,7 @@ bool clsLEAMDNSHost::_udpAppend32(uint32_t p_u32Value) return (_udpAppendBuffer((unsigned char*)&p_u32Value, sizeof(p_u32Value))); } -#ifdef DEBUG_ESP_MDNS_RESPONDER +#ifdef DEBUG_ESP_PORT /* MDNSResponder::_udpDump @@ -1500,7 +1500,7 @@ bool clsLEAMDNSHost::_udpDump(unsigned p_uOffset, } return true; } -#endif +#endif // DEBUG_ESP_PORT /** diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp index 1d15a04137..d3ae03ad1f 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp @@ -239,7 +239,7 @@ bool clsLEAMDNSHost::clsBackbone::_processUDPInput(void) MISC */ -#if not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_MDNS_RESPONDER +#if not defined ESP_8266_MDNS_INCLUDE || defined DEBUG_ESP_PORT /* clsLEAmDNS2_Host::clsBackbone::_DH diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h index e67e6c03d0..30f82b03b3 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h @@ -56,11 +56,9 @@ #ifdef DEBUG_ESP_PORT #define DEBUG_OUTPUT DEBUG_ESP_PORT #else -#define DEBUG_OUTPUT Serial +#define DEBUG_OUTPUT Serialx #endif -#ifdef DEBUG_ESP_MDNS_RESPONDER - #ifdef DEBUG_ESP_MDNS_INFO #define DEBUG_EX_INFO(A) A #define DEBUG_EX_INFO_IF(C,A...) do if (C) { A; } while (0) @@ -97,19 +95,6 @@ #define DEBUG_EX_RX(A) #endif -#else // !defined(DEBUG_ESP_MDNS_RESPONDER) - -#define DEBUG_EX_INFO(A) -#define DEBUG_EX_INFO_IF(C,A...) -#define DEBUG_EX_INFO2(A) -#define DEBUG_EX_INFO2_IF(C,A...) -#define DEBUG_EX_ERR(A) -#define DEBUG_EX_ERR_IF(C,A...) -#define DEBUG_EX_TX(A) -#define DEBUG_EX_RX(A) - -#endif // !defined(DEBUG_ESP_MDNS_RESPONDER) - /* 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. From 79efe995d183e16d033685772095e4372eda26f1 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 12 Jun 2020 12:29:01 +0200 Subject: [PATCH 049/152] fix crash in mDNS_ServiceMonitor_v2_test.ino --- .../mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino index 2bae65501d..9b14e22a85 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino @@ -221,7 +221,7 @@ void hostProbeResult(clsMDNSHost & p_rMDNSHost, String p_pcDomainName, bool p_bP { // Set station hostname setStationHostname(pcHostDomain); - Serial.printf("settng hostname = '%s'\n", pcHostDomain); + Serial.printf("setting hostname = '%s'\n", pcHostDomain?: "nullptr"); if (!bHostDomainConfirmed) { From bb08c8cb9cd10678ac3b868077ae8a4876e6e1fe Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 12 Jun 2020 14:30:08 +0200 Subject: [PATCH 050/152] temporarily disable ipv6 --- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index e5e21d4c08..2a9fa30295 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -110,7 +110,8 @@ #define MDNS_IPV4_SUPPORT #if LWIP_IPV6 -#define MDNS_IPV6_SUPPORT // If we've got IPv6 support, then we need IPv6 support :-) +#pragma message "IPV6 for MDNS temporarily disabled" +//#define MDNS_IPV6_SUPPORT // If we've got IPv6 support, then we need IPv6 support :-) #endif namespace esp8266 From 5505446b05cf8619a5bf55a33fb89f4603cf45da Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 12 Jun 2020 18:52:35 +0200 Subject: [PATCH 051/152] reintroduce `delay()` without `can_yield()` calls --- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 3 +-- libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 12 ++++++------ libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index 2a9fa30295..e5e21d4c08 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -110,8 +110,7 @@ #define MDNS_IPV4_SUPPORT #if LWIP_IPV6 -#pragma message "IPV6 for MDNS temporarily disabled" -//#define MDNS_IPV6_SUPPORT // If we've got IPv6 support, then we need IPv6 support :-) +#define MDNS_IPV6_SUPPORT // If we've got IPv6 support, then we need IPv6 support :-) #endif namespace esp8266 diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 0be8d6bf17..a0cb00a729 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -117,9 +117,9 @@ bool clsLEAMDNSHost::_sendMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParamete (Serial.println("Did send UC"), true)*/); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage (V4): FAILED!\n"), _DH());); -#if 0 +#if 1 if ((clsConsts::u32SendCooldown) && - (can_yield())) + 1)//(can_yield())) { delay(clsConsts::u32SendCooldown); } @@ -188,9 +188,9 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(netif* pNetIf, clsLEAMDNSHost::clsSe (Serial.println("Did send MC V4"), true)*/); DEBUG_EX_ERR(if (!bIPv4Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast (V4): FAILED!\n"), _DH());); -#if 0 +#if 1 if ((clsConsts::u32SendCooldown) && - (can_yield())) + 1)//(can_yield())) { delay(clsConsts::u32SendCooldown); } @@ -216,9 +216,9 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(netif* pNetIf, clsLEAMDNSHost::clsSe (Serial.println("Did send MC V6"), true)*/); DEBUG_EX_ERR(if (!bIPv6Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast (IPv6): FAILED! (%s, %s, %s)\n"), _DH(), (_getResponderIPAddress(pNetIf, enuIPProtocolType::V6).isSet() ? "1" : "0"), (bPrepareMessage ? "1" : "0"), (bUDPContextSend ? "1" : "0"));); -#if 0 +#if 1 if ((clsConsts::u32SendCooldown) && - (can_yield())) + 1)//(can_yield())) { delay(clsConsts::u32SendCooldown); } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h index 30f82b03b3..312a03d045 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Priv.h @@ -36,7 +36,7 @@ Enable class debug functions */ #define ESP_8266_MDNS_INCLUDE -//#define DEBUG_ESP_MDNS_RESPONDER +//#define DEBUG_ESP_MDNS_RESPONDER // force debug, arduino IDE uses DEBUG_ESP_MDNS /* Enable/disable debug trace macros @@ -46,7 +46,7 @@ #define DEBUG_ESP_MDNS_ERR #endif -#if defined(DEBUG_ESP_PORT) && defined(DEBUG_ESP_MDNS_RESPONDER) +#if defined(DEBUG_ESP_PORT) && (defined(DEBUG_ESP_MDNS) || defined(DEBUG_ESP_MDNS_RESPONDER)) #define DEBUG_ESP_MDNS_INFO #define DEBUG_ESP_MDNS_INFO2 //#define DEBUG_ESP_MDNS_TX From 4563fde8aa4d083724237bc6f2bb6565dc1a0935 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 12 Jun 2020 20:02:21 +0200 Subject: [PATCH 052/152] netdump as an option --- .../mDNS_ServiceMonitor_v2_test.ino | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino index 9b14e22a85..7e9926c50c 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino @@ -30,6 +30,8 @@ */ +#define END 0 // enable netdump + #ifndef STASSID #define STASSID "ssid" #define STAPSK "psk" @@ -40,6 +42,14 @@ #include #include +#if END +#include +using namespace NetCapture; +Netdump nd; +#endif + + + /* Include the clsMDNSHost (the library needs to be included also) As LEA clsMDNSHost is experimantal in the ESP8266 environment currently, the @@ -326,7 +336,6 @@ void handleHTTPRequest() Serial.println("Done with request"); } -//Netdump nd(Netdump::interface::LWIP); /* @@ -378,6 +387,11 @@ void setup(void) // Start HTTP server server.begin(); Serial.println("HTTP server started"); + +#if END + nd.printDump(Serial, Packet::PacketDetail::FULL); +#endif + } void loop(void) From 6a83937eb20c14ffde8380e0504c3e9a5bcec7ab Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sat, 13 Jun 2020 12:42:31 +0200 Subject: [PATCH 053/152] change mdns service names in test example --- .../mDNS_ServiceMonitor_v2_test.ino | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino index 7e9926c50c..a74e14ce86 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino @@ -13,7 +13,7 @@ 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 service itself is is announced as 'host domain'._sapuducul._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. @@ -76,8 +76,8 @@ clsMDNSHost MDNSv2; char* pcHostDomain = 0; // Negociated host domain bool bHostDomainConfirmed = false; // Flags the confirmation of the host domain -clsMDNSHost::clsService* hMDNSService = 0; // The handle of the http service in the MDNS responder -clsMDNSHost::clsQuery* hMDNSServiceQuery = 0; // The handle of the 'http.tcp' service query in the MDNS responder +clsMDNSHost::clsService* hMDNSService = 0; // The handle of the sapuducu service in the MDNS responder +clsMDNSHost::clsQuery* hMDNSServiceQuery = 0; // The handle of the 'espclk.tcp' service query in the MDNS responder const String cstrNoHTTPServices = "Currently no 'http.tcp' services in the local network!
"; String strHTTPServices = cstrNoHTTPServices; @@ -240,9 +240,9 @@ void hostProbeResult(clsMDNSHost & p_rMDNSHost, String p_pcDomainName, bool p_bP if (!hMDNSService) { - Serial.printf("adding service tcp.http port %d\n", SERVICE_PORT); - // Add a 'http.tcp' service to port 'SERVICE_PORT', using the host domain as instance domain - hMDNSService = MDNSv2.addService(0, "http", "tcp", SERVICE_PORT, serviceProbeResult); + Serial.printf("adding service tcp.sapuducu port %d\n", SERVICE_PORT); + // Add a 'sapuducu.tcp' service to port 'SERVICE_PORT', using the host domain as instance domain + hMDNSService = MDNSv2.addService(0, "sapuducu", "tcp", SERVICE_PORT, serviceProbeResult); if (hMDNSService) { @@ -250,7 +250,7 @@ void hostProbeResult(clsMDNSHost & p_rMDNSHost, String p_pcDomainName, bool p_bP hMDNSService->setProbeResultCallback(serviceProbeResult); // MDNSv2.setServiceProbeResultCallback(hMDNSService, serviceProbeResult); - // Add some '_http._tcp' protocol specific MDNS service TXT items + // Add some '_sapuducu._tcp' protocol specific MDNS service TXT items // See: http://www.dns-sd.org/txtrecords.html#http hMDNSService->addServiceTxt("user", "x"); hMDNSService->addServiceTxt("password", "y"); @@ -259,18 +259,18 @@ void hostProbeResult(clsMDNSHost & p_rMDNSHost, String p_pcDomainName, bool p_bP else Serial.printf("hMDNSService=0 ??\n"); - // Install dynamic 'http.tcp' service query + // Install dynamic 'espclk.tcp' service query if (!hMDNSServiceQuery) { Serial.printf("installing hMDNSServiceQuery\n"); - hMDNSServiceQuery = MDNSv2.installServiceQuery("http", "tcp", MDNSServiceQueryCallback); + hMDNSServiceQuery = MDNSv2.installServiceQuery("espclk", "tcp", MDNSServiceQueryCallback); if (hMDNSServiceQuery) { - Serial.printf("MDNSProbeResultCallback: Service query for 'http.tcp' services installed.\n"); + Serial.printf("MDNSProbeResultCallback: Service query for 'espclk.tcp' services installed.\n"); } else { - Serial.printf("MDNSProbeResultCallback: FAILED to install service query for 'http.tcp' services!\n"); + Serial.printf("MDNSProbeResultCallback: FAILED to install service query for 'espclk.tcp' services!\n"); } } } @@ -394,12 +394,20 @@ void setup(void) } + + void loop(void) { // Check if a request has come in server.handleClient(); // Allow MDNS processing MDNSv2.update(); + + static esp8266::polledTimeout::periodicMs timeout(10000); + if (timeout.expired()) + { + Serial.printf("up=%lumn heap=%u\n", millis() / 1000 / 60, ESP.getFreeHeap()); + } } From cf74cc1f84ca76b8fc9827d0d35b6d789b9cb8e3 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 14 Jun 2020 14:58:08 +0200 Subject: [PATCH 054/152] brings parts of #7375, + sendTimeout() --- .../ESP8266WiFi/src/include/UdpContext.h | 41 +++++++++-- tests/host/common/include/UdpContext.h | 73 +++++++++++-------- 2 files changed, 77 insertions(+), 37 deletions(-) diff --git a/libraries/ESP8266WiFi/src/include/UdpContext.h b/libraries/ESP8266WiFi/src/include/UdpContext.h index a81384b2a7..c3a63380c4 100644 --- a/libraries/ESP8266WiFi/src/include/UdpContext.h +++ b/libraries/ESP8266WiFi/src/include/UdpContext.h @@ -31,6 +31,7 @@ void esp_schedule(); } #include +#include #define PBUF_ALIGNER_ADJUST 4 #define PBUF_ALIGNER(x) ((void*)((((intptr_t)(x))+3)&~3)) @@ -419,7 +420,16 @@ class UdpContext return size; } - bool send(CONST ip_addr_t* addr = 0, uint16_t port = 0) + void cancelBuffer () + { + if (_tx_buf_head) + pbuf_free(_tx_buf_head); + _tx_buf_head = 0; + _tx_buf_cur = 0; + _tx_buf_offset = 0; + } + + err_t trySend(CONST ip_addr_t* addr = 0, uint16_t port = 0, bool keepBuffer = true) { size_t data_size = _tx_buf_offset; pbuf* tx_copy = pbuf_alloc(PBUF_TRANSPORT, data_size, PBUF_RAM); @@ -435,16 +445,12 @@ class UdpContext data_size -= will_copy; } } - if (_tx_buf_head) - pbuf_free(_tx_buf_head); - _tx_buf_head = 0; - _tx_buf_cur = 0; - _tx_buf_offset = 0; + if (!keepBuffer) + cancelBuffer(); if(!tx_copy){ - return false; + return ERR_MEM; } - if (!addr) { addr = &_pcb->remote_ip; port = _pcb->remote_port; @@ -463,6 +469,25 @@ class UdpContext _pcb->ttl = old_ttl; #endif pbuf_free(tx_copy); + if (err == ERR_OK) + cancelBuffer(); + return err; + } + + bool send(CONST ip_addr_t* addr = 0, uint16_t port = 0) + { + return trySend(addr, port, /* don't keep buffer */false) == ERR_OK; + } + + bool sendTimeout(CONST ip_addr_t* addr, uint16_t port, + esp8266::polledTimeout::oneShotFastUs::timeType timeoutMs) + { + err_t err; + esp8266::polledTimeout::oneShotFastMs timeout(timeoutMs); + while (((err = trySend(addr, port)) != ERR_OK) && !timeout) + delay(0); + if (err != ERR_OK) + cancelBuffer(); return err == ERR_OK; } diff --git a/tests/host/common/include/UdpContext.h b/tests/host/common/include/UdpContext.h index 12c41b433e..775dd5852e 100644 --- a/tests/host/common/include/UdpContext.h +++ b/tests/host/common/include/UdpContext.h @@ -1,22 +1,22 @@ /* - UdpContext.h - emulation of UDP connection handling on top of lwIP + UdpContext.h - emulation of UDP connection handling on top of lwIP - Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. + Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef UDPCONTEXT_H #define UDPCONTEXT_H @@ -55,12 +55,13 @@ class UdpContext void unref() { - if(--_refcnt == 0) { + if (--_refcnt == 0) + { delete this; } } - bool connect (const ip_addr_t* addr, uint16_t port) + bool connect(const ip_addr_t* addr, uint16_t port) { _dst = *addr; _dstport = port; @@ -111,7 +112,8 @@ class UdpContext // warning: handler is called from tcp stack context // esp_yield and non-reentrant functions which depend on it will fail - void onRx(rxhandler_t handler) { + void onRx(rxhandler_t handler) + { _on_rx = handler; } @@ -135,7 +137,8 @@ class UdpContext mockUDPSwallow(pos, _inbuf, _inbufsize); } - bool isValidOffset(const size_t pos) const { + bool isValidOffset(const size_t pos) const + { return pos <= _inbufsize; } @@ -176,7 +179,7 @@ class UdpContext int read() { char c; - return read(&c, 1)? c: -1; + return read(&c, 1) ? c : -1; } size_t read(char* dst, size_t size) @@ -187,7 +190,7 @@ class UdpContext int peek() { char c; - return mockUDPPeekBytes(_sock, &c, 1, _timeout_ms, _inbuf, _inbufsize)?: -1; + return mockUDPPeekBytes(_sock, &c, 1, _timeout_ms, _inbuf, _inbufsize) ? : -1; } void flush() @@ -198,7 +201,7 @@ class UdpContext _inbufsize = 0; } - size_t append (const char* data, size_t size) + size_t append(const char* data, size_t size) { if (size + _outbufsize > sizeof _outbuf) { @@ -211,16 +214,28 @@ class UdpContext return size; } - bool send (ip_addr_t* addr = 0, uint16_t port = 0) + err_t trySend(ip_addr_t* addr = 0, uint16_t port = 0, bool keepBuffer = true) + { + uint32_t dst = addr ? addr->addr : _dst.addr; + uint16_t dstport = port ? : _dstport; + size_t wrt = mockUDPWrite(_sock, (const uint8_t*)_outbuf, _outbufsize, _timeout_ms, dst, dstport); + err_t ret = _outbufsize ? ERR_OK : ERR_ABRT; + if (!keepBuffer || wrt == _outbufsize) + cancelBuffer(); + return ret; + } + + void cancelBuffer() { - uint32_t dst = addr? addr->addr: _dst.addr; - uint16_t dstport = port?: _dstport; - size_t ret = mockUDPWrite(_sock, (const uint8_t*)_outbuf, _outbufsize, _timeout_ms, dst, dstport); _outbufsize = 0; - return ret > 0; } - void mock_cb (void) + bool send(ip_addr_t* addr = 0, uint16_t port = 0) + { + return trySend(addr, port, false) == ERR_OK; + } + + void mock_cb(void) { if (_on_rx) _on_rx(); } @@ -231,7 +246,7 @@ class UdpContext private: - void translate_addr () + void translate_addr() { if (addrsize == 4) { @@ -263,7 +278,7 @@ class UdpContext uint8_t addr[16]; }; -extern "C" inline err_t igmp_joingroup (const ip4_addr_t *ifaddr, const ip4_addr_t *groupaddr) +extern "C" inline err_t igmp_joingroup(const ip4_addr_t *ifaddr, const ip4_addr_t *groupaddr) { (void)ifaddr; UdpContext::staticMCastAddr = groupaddr->addr; From f8533fc63aa474c106eb049a77ffab35a057661f Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 14 Jun 2020 14:58:54 +0200 Subject: [PATCH 055/152] use udp::sendTimeout --- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 3 ++- .../ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 15 ++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index e5e21d4c08..6e89556bad 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -179,7 +179,8 @@ class clsLEAMDNSHost #else static const uint8_t u8DNS_RRTYPE_NSEC = 0x2F; #endif - static const uint32_t u32SendCooldown = 50; // Delay (ms) between to 'UDPContext->send()' calls + //static const uint32_t u32SendCooldown = 50; // Delay (ms) between to 'UDPContext->send()' calls + static constexpr uint32_t u32SendTimeoutMs = 5; // timeout (ms) for a call to `UDPContext->send()` (1.5ms=1460@1Mbits/s) }; diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index a0cb00a729..7642c19221 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -113,11 +113,10 @@ bool clsLEAMDNSHost::_sendMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParamete bResult = ((ipRemote.isSet()) && (_prepareMessage(pNetIf, p_rSendParameter)) && - (m_pUDPContext->send(ipRemote, m_pUDPContext->getRemotePort())) /*&& + (m_pUDPContext->sendTimeout(ipRemote, m_pUDPContext->getRemotePort(), clsConsts::u32SendTimeoutMs)) /*&& (Serial.println("Did send UC"), true)*/); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage (V4): FAILED!\n"), _DH());); - -#if 1 +#if 0 if ((clsConsts::u32SendCooldown) && 1)//(can_yield())) { @@ -183,12 +182,11 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(netif* pNetIf, clsLEAMDNSHost::clsSe DEBUG_EX_INFO(if (!_getResponderIPAddress(pNetIf, enuIPProtocolType::V4)) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast IPv4: NO IPv4 address!.\n"), _DH());); bIPv4Result = ((_prepareMessage(pNetIf, p_rSendParameter)) && (m_pUDPContext->setMulticastInterface(pNetIf), true) && - (m_pUDPContext->send(ip4MulticastAddress, DNS_MQUERY_PORT)) && + (m_pUDPContext->sendTimeout(ip4MulticastAddress, DNS_MQUERY_PORT, clsConsts::u32SendTimeoutMs)) && (m_pUDPContext->setMulticastInterface(0), true) /*&& (Serial.println("Did send MC V4"), true)*/); DEBUG_EX_ERR(if (!bIPv4Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast (V4): FAILED!\n"), _DH());); - -#if 1 +#if 0 if ((clsConsts::u32SendCooldown) && 1)//(can_yield())) { @@ -211,12 +209,11 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(netif* pNetIf, clsLEAMDNSHost::clsSe ); bIPv6Result = ((DEBUG_EX_ERR(bPrepareMessage =)_prepareMessage(pNetIf, p_rSendParameter)) && (m_pUDPContext->setMulticastInterface(pNetIf), true) && - (DEBUG_EX_ERR(bUDPContextSend =)m_pUDPContext->send(ip6MulticastAddress, DNS_MQUERY_PORT)) && + (DEBUG_EX_ERR(bUDPContextSend =)m_pUDPContext->sendTimeout(ip6MulticastAddress, DNS_MQUERY_PORT, clsConsts::u32SendTimeoutMs)) && (m_pUDPContext->setMulticastInterface(0), true) /*&& (Serial.println("Did send MC V6"), true)*/); DEBUG_EX_ERR(if (!bIPv6Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast (IPv6): FAILED! (%s, %s, %s)\n"), _DH(), (_getResponderIPAddress(pNetIf, enuIPProtocolType::V6).isSet() ? "1" : "0"), (bPrepareMessage ? "1" : "0"), (bUDPContextSend ? "1" : "0"));); - -#if 1 +#if 0 if ((clsConsts::u32SendCooldown) && 1)//(can_yield())) { From 5c3d2b682c4d26435f2868eb8ad52299574c7034 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 14 Jun 2020 15:02:45 +0200 Subject: [PATCH 056/152] fix constant --- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index 6e89556bad..8770d084c3 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -180,7 +180,7 @@ class clsLEAMDNSHost static const uint8_t u8DNS_RRTYPE_NSEC = 0x2F; #endif //static const uint32_t u32SendCooldown = 50; // Delay (ms) between to 'UDPContext->send()' calls - static constexpr uint32_t u32SendTimeoutMs = 5; // timeout (ms) for a call to `UDPContext->send()` (1.5ms=1460@1Mbits/s) + static constexpr uint32_t u32SendTimeoutMs = 25; // timeout (ms) for a call to `UDPContext->send()` (12ms=1460B@1Mb/s) }; From 54066fb8fe0492beeb07d59ca01b7197ce437d10 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 14 Jun 2020 15:09:26 +0200 Subject: [PATCH 057/152] +mock udp::sendTimeout() --- tests/host/common/include/UdpContext.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/host/common/include/UdpContext.h b/tests/host/common/include/UdpContext.h index 775dd5852e..cd3e453982 100644 --- a/tests/host/common/include/UdpContext.h +++ b/tests/host/common/include/UdpContext.h @@ -25,6 +25,7 @@ #include #include +#include class UdpContext; @@ -235,6 +236,18 @@ class UdpContext return trySend(addr, port, false) == ERR_OK; } + bool sendTimeout(ip_addr_t* addr, uint16_t port, + esp8266::polledTimeout::oneShotFastUs::timeType timeoutMs) + { + err_t err; + esp8266::polledTimeout::oneShotFastMs timeout(timeoutMs); + while (((err = trySend(addr, port)) != ERR_OK) && !timeout) + delay(0); + if (err != ERR_OK) + cancelBuffer(); + return err == ERR_OK; + } + void mock_cb(void) { if (_on_rx) _on_rx(); From 295f397baf48f8cf6cb5161c03243ac2a079fbfa Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 14 Jun 2020 15:16:54 +0200 Subject: [PATCH 058/152] fix typo --- libraries/ESP8266WiFi/src/include/UdpContext.h | 2 +- tests/host/common/include/UdpContext.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266WiFi/src/include/UdpContext.h b/libraries/ESP8266WiFi/src/include/UdpContext.h index c3a63380c4..9a429d9d85 100644 --- a/libraries/ESP8266WiFi/src/include/UdpContext.h +++ b/libraries/ESP8266WiFi/src/include/UdpContext.h @@ -480,7 +480,7 @@ class UdpContext } bool sendTimeout(CONST ip_addr_t* addr, uint16_t port, - esp8266::polledTimeout::oneShotFastUs::timeType timeoutMs) + esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs) { err_t err; esp8266::polledTimeout::oneShotFastMs timeout(timeoutMs); diff --git a/tests/host/common/include/UdpContext.h b/tests/host/common/include/UdpContext.h index cd3e453982..bf104bc40f 100644 --- a/tests/host/common/include/UdpContext.h +++ b/tests/host/common/include/UdpContext.h @@ -237,7 +237,7 @@ class UdpContext } bool sendTimeout(ip_addr_t* addr, uint16_t port, - esp8266::polledTimeout::oneShotFastUs::timeType timeoutMs) + esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs) { err_t err; esp8266::polledTimeout::oneShotFastMs timeout(timeoutMs); From bc10fbf55fd55a112fdf14df3e72f7d3d6c63dc1 Mon Sep 17 00:00:00 2001 From: hreintke Date: Sun, 14 Jun 2020 15:22:25 +0200 Subject: [PATCH 059/152] Fix HTTP Request in servicemonitor example --- .../mDNS_ServiceMonitor_v2_test.ino | 85 ++++++++++--------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino index a74e14ce86..22407a4f6b 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino @@ -295,45 +295,54 @@ void hostProbeResult(clsMDNSHost & p_rMDNSHost, String p_pcDomainName, bool p_bP */ void handleHTTPRequest() { - Serial.println(""); - Serial.println("HTTP Request"); - - IPAddress ip = server.client().localIP(); - String ipStr = ip.toString(); - String s = "\r\n

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

"; - s += "

Local HTTP services are :

"; - s += "
    "; - /* - for (auto info : MDNSv2.answerInfo(hMDNSServiceQuery)) { - s += "
  1. "; - s += info.serviceDomain(); - if (info.hostDomainAvailable()) { - s += "
    Hostname: "; - s += String(info.hostDomain()); - s += (info.hostPortAvailable()) ? (":" + String(info.hostPort())) : ""; - } - if (info.IP4AddressAvailable()) { - s += "
    IP4:"; - for (auto ip : info.IP4Adresses()) { - s += " " + ip.toString(); - } - } - if (info.txtAvailable()) { - s += "
    TXT:
    "; - for (auto kv : info.keyValues()) { - s += "\t" + String(kv.first) + " : " + String(kv.second) + "
    "; - } - } - s += "
  2. "; - - } - */ - s += "

"; + Serial.println(""); + Serial.println("HTTP Request"); + + IPAddress ip = server.client().localIP(); + String ipStr = ip.toString(); + String s = "\r\n

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

"; + s += "

Local HTTP services are :

"; + s += "
    "; + // hMDNSServiceQuery-> + + + for (auto info : hMDNSServiceQuery->answerAccessors()) { + s += "
  1. "; + s += info.serviceDomain(); + + if (info.hostDomainAvailable()) { + s += "
    Hostname: "; + s += String(info.hostDomain()); + s += (info.hostPortAvailable()) ? (":" + String(info.hostPort())) : ""; + } + if (info.IPv4AddressAvailable()) { + s += "
    IPv4:"; + for (auto ip : info.IPv4Addresses()) { + s += " " + ip.toString(); + } + } +#ifdef MDNS_IPV6_SUPPORT + if (info.IPv6AddressAvailable()) { + s += "
    IPv6:"; + for (auto ip : info.IPv6Addresses()) { + s += " " + ip.toString(); + } + } +#endif + if (info.txtsAvailable()) { + s += "
    TXT:
    "; + for (auto kv : info.txtKeyValues()) { + s += "\t" + String(kv.first) + " : " + String(kv.second) + "
    "; + } + } + s += "
  2. "; + } + s += "

"; - Serial.println("Sending 200"); - server.send(200, "text/html", s); - Serial.println("Done with request"); + Serial.println("Sending 200"); + server.send(200, "text/html", s); + Serial.println("Done with request"); } From 68d440975f857b7a9e387fb19749671b67488e5b Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 16 Jun 2020 16:01:02 +0200 Subject: [PATCH 060/152] emulation on host: adding key/value cmdline parameters usable in sketches --- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 2 +- .../ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 5 +---- tests/host/common/Arduino.h | 7 +++++++ tests/host/common/ArduinoMain.cpp | 15 ++++++++++++++- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index 8770d084c3..88d403baa8 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -180,7 +180,7 @@ class clsLEAMDNSHost static const uint8_t u8DNS_RRTYPE_NSEC = 0x2F; #endif //static const uint32_t u32SendCooldown = 50; // Delay (ms) between to 'UDPContext->send()' calls - static constexpr uint32_t u32SendTimeoutMs = 25; // timeout (ms) for a call to `UDPContext->send()` (12ms=1460B@1Mb/s) + static constexpr uint32_t u32SendTimeoutMs = 50; // timeout (ms) for a call to `UDPContext->send()` (12ms=1460B@1Mb/s) }; diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 7642c19221..f54e6fa475 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -2029,10 +2029,7 @@ bool clsLEAMDNSHost::_writeMDNSAnswer_TXT(clsLEAMDNSHost::clsService& p_rService attributes.m_u16Class, (p_rSendParameter.m_bUnannounce ? 0 - : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL)), - p_rService.m_pcInstanceName, - p_rService.m_pcType, - p_rService.m_pcProtocol); + : (p_rSendParameter.m_bLegacyDNSQuery ? clsConsts::u32LegacyTTL : clsConsts::u32ServiceTTL))); ); _releaseTempServiceTxts(p_rService); diff --git a/tests/host/common/Arduino.h b/tests/host/common/Arduino.h index 4c0a8c6765..8735ecba6c 100644 --- a/tests/host/common/Arduino.h +++ b/tests/host/common/Arduino.h @@ -270,3 +270,10 @@ extern "C" void configTime(long timezone, int daylightOffset_sec, #include "pins_arduino.h" #endif /* Arduino_h */ + +#if __cplusplus +#include +#include +#define MOCKARGS 1 +extern std::map mockArgs; +#endif diff --git a/tests/host/common/ArduinoMain.cpp b/tests/host/common/ArduinoMain.cpp index 7bfcd97032..f507c081a1 100644 --- a/tests/host/common/ArduinoMain.cpp +++ b/tests/host/common/ArduinoMain.cpp @@ -54,6 +54,8 @@ int mock_port_shifter = MOCK_PORT_SHIFTER; static struct termios initial_settings; +std::map mockArgs; + int mockverbose (const char* fmt, ...) { va_list ap; @@ -132,6 +134,8 @@ void help (const char* argv0, int exitcode) "\t (spiffs, littlefs: negative value will force mismatched size)\n" " -T - show timestamp on output\n" " -v - verbose\n" + " -K - key\n" + " -V - value\n" , argv0, MOCK_PORT_SHIFTER, spiffs_kb, littlefs_kb); exit(exitcode); } @@ -149,6 +153,8 @@ static struct option options[] = { "spiffskb", required_argument, NULL, 'S' }, { "littlefskb", required_argument, NULL, 'L' }, { "portshifter", required_argument, NULL, 's' }, + { "key", required_argument, NULL, 'K' }, + { "value", required_argument, NULL, 'V' }, }; void cleanup () @@ -181,10 +187,11 @@ int main (int argc, char* const argv []) mock_port_shifter = 0; else mock_port_shifter = MOCK_PORT_SHIFTER; + String key; for (;;) { - int n = getopt_long(argc, argv, "hlcfbvTi:S:s:L:", options, NULL); + int n = getopt_long(argc, argv, "hlcfbvTi:S:s:L:K:V:", options, NULL); if (n < 0) break; switch (n) @@ -222,6 +229,12 @@ int main (int argc, char* const argv []) case 'T': serial_timestamp = true; break; + case 'K': + key = optarg; + break; + case 'V': + mockArgs[key] = optarg; + break; default: help(argv[0], EXIT_FAILURE); } From 9cd34f35c3641fc1e7357a131c5fde53d0b8c926 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 16 Jun 2020 23:14:22 +0200 Subject: [PATCH 061/152] fix services enumeration --- .../mDNS_ServiceMonitor_v2_test.ino | 50 ++++++++++--------- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 23 +++++---- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino index 22407a4f6b..9ca8b10f60 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino @@ -307,36 +307,38 @@ void handleHTTPRequest() // hMDNSServiceQuery-> - for (auto info : hMDNSServiceQuery->answerAccessors()) { - s += "
  • "; - s += info.serviceDomain(); - - if (info.hostDomainAvailable()) { - s += "
    Hostname: "; - s += String(info.hostDomain()); - s += (info.hostPortAvailable()) ? (":" + String(info.hostPort())) : ""; - } - if (info.IPv4AddressAvailable()) { - s += "
    IPv4:"; - for (auto ip : info.IPv4Addresses()) { - s += " " + ip.toString(); + if (hMDNSServiceQuery) { + for (auto info : hMDNSServiceQuery->answerAccessors()) { + s += "
  • "; + s += info.serviceDomain(); + + if (info.hostDomainAvailable()) { + s += "
    Hostname: "; + s += String(info.hostDomain()); + s += (info.hostPortAvailable()) ? (":" + String(info.hostPort())) : ""; + } + if (info.IPv4AddressAvailable()) { + s += "
    IPv4:"; + for (auto ip : info.IPv4Addresses()) { + s += " " + ip.toString(); + } } - } #ifdef MDNS_IPV6_SUPPORT - if (info.IPv6AddressAvailable()) { - s += "
    IPv6:"; - for (auto ip : info.IPv6Addresses()) { - s += " " + ip.toString(); + if (info.IPv6AddressAvailable()) { + s += "
    IPv6:"; + for (auto ip : info.IPv6Addresses()) { + s += " " + ip.toString(); + } } - } #endif - if (info.txtsAvailable()) { - s += "
    TXT:
    "; - for (auto kv : info.txtKeyValues()) { - s += "\t" + String(kv.first) + " : " + String(kv.second) + "
    "; + if (info.txtsAvailable()) { + s += "
    TXT:
    "; + for (auto kv : info.txtKeyValues()) { + s += "\t" + String(kv.first) + " : " + String(kv.second) + "
    "; + } } + s += "
  • "; } - s += ""; } s += "
    "; diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index c910260c17..ba5c6bd9cd 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -889,20 +889,23 @@ std::vector clsLEAMDNSHost_Legacy::an { std::vector serviceInfos; - for (stcHostInformation& hostInformation : m_HostInformations) + if (p_hServiceQuery) { - clsLEAMDNSHost::clsQuery* pQuery = (clsLEAMDNSHost::clsQuery*)hostInformation.m_HandleToPtr[p_hServiceQuery]; - if (pQuery) + for (stcHostInformation& hostInformation : m_HostInformations) { - for (clsLEAMDNSHost::clsQuery::clsAnswerAccessor& answerAccessor : pQuery->answerAccessors()) + clsLEAMDNSHost::clsQuery* pQuery = (clsLEAMDNSHost::clsQuery*)hostInformation.m_HandleToPtr[p_hServiceQuery]; + if (pQuery) { - serviceInfos.push_back(stcMDNSServiceInfo(answerAccessor)); + for (clsLEAMDNSHost::clsQuery::clsAnswerAccessor& answerAccessor : pQuery->answerAccessors()) + { + serviceInfos.push_back(stcMDNSServiceInfo(answerAccessor)); + } + } + else + { + serviceInfos.clear(); + break; } - } - else - { - serviceInfos.clear(); - break; } } return serviceInfos; From 7d99299f48b3b5d0bc637dd021e6fcc00cb47c6d Mon Sep 17 00:00:00 2001 From: david gauchard Date: Thu, 18 Jun 2020 13:38:59 +0200 Subject: [PATCH 062/152] add LwipIntf::stateUpCB() This new class will be extended in the ethernet PR to come. This static method will be used in the MDNSv2 PR to come. --- cores/esp8266/LwipIntf.h | 25 ++++++++++++++++++ cores/esp8266/LwipIntfCB.cpp | 41 +++++++++++++++++++++++++++++ tools/sdk/lib/liblwip2-1460-feat.a | Bin 1595772 -> 1592566 bytes tools/sdk/lib/liblwip2-1460.a | Bin 1421598 -> 1418632 bytes tools/sdk/lib/liblwip2-536-feat.a | Bin 1595696 -> 1592490 bytes tools/sdk/lib/liblwip2-536.a | Bin 1421522 -> 1418556 bytes tools/sdk/lib/liblwip6-1460-feat.a | Bin 1924160 -> 1920874 bytes tools/sdk/lib/liblwip6-536-feat.a | Bin 1924084 -> 1920798 bytes tools/sdk/lwip2/include/lwipopts.h | 3 +++ 9 files changed, 69 insertions(+) create mode 100644 cores/esp8266/LwipIntf.h create mode 100644 cores/esp8266/LwipIntfCB.cpp diff --git a/cores/esp8266/LwipIntf.h b/cores/esp8266/LwipIntf.h new file mode 100644 index 0000000000..cbf2927315 --- /dev/null +++ b/cores/esp8266/LwipIntf.h @@ -0,0 +1,25 @@ + +#ifndef _LWIPINTF_H +#define _LWIPINTF_H + +#include + +#include + +class LwipIntf +{ +private: + + LwipIntf () { } + +protected: + + static bool stateChangeSysCB (std::function&& cb); + +public: + + static bool stateUpCB (std::function&& cb); + +}; + +#endif // _LWIPINTF_H diff --git a/cores/esp8266/LwipIntfCB.cpp b/cores/esp8266/LwipIntfCB.cpp new file mode 100644 index 0000000000..4eb81cb6b8 --- /dev/null +++ b/cores/esp8266/LwipIntfCB.cpp @@ -0,0 +1,41 @@ + +#include +#include + +#define NETIF_STATUS_CB_SIZE 3 + +static int netifStatusChangeListLength = 0; +std::function netifStatusChangeList [NETIF_STATUS_CB_SIZE]; + +extern "C" void netif_status_changed (struct netif* netif) +{ + // override the default empty weak function + for (int i = 0; i < netifStatusChangeListLength; i++) + netifStatusChangeList[i](netif); +} + +bool LwipIntf::stateChangeSysCB (std::function&& cb) +{ + if (netifStatusChangeListLength >= NETIF_STATUS_CB_SIZE) + { +#if defined(DEBUG_ESP_CORE) + DEBUGV("NETIF_STATUS_CB_SIZE is too low\n"); +#endif + return false; + } + + netifStatusChangeList[netifStatusChangeListLength++] = cb; + return true; +} + +bool LwipIntf::stateUpCB (std::function&& cb) +{ + return stateChangeSysCB([cb](netif* nif) + { + if (netif_is_up(nif)) + schedule_function([cb, nif]() + { + cb(nif); + }); + }); +} diff --git a/tools/sdk/lib/liblwip2-1460-feat.a b/tools/sdk/lib/liblwip2-1460-feat.a index 6c231417b2b65ae9588bd5fb6039dfd80d378def..4840eb4875a9739c2d2071c12e9b15465b5176d1 100644 GIT binary patch delta 234037 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°lItYIvN{mc)k73drl=3blhto^oC%)2el;q? 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 MTT|`7XSv(|09bEh`Tzg` delta 237130 zcmdqK33wDmyZ7DIGf9R_W-^n7gndW?31LZq0AUaNPLNIZO?CmmFeobEu7IGZAOhkFxT5epqN2S2f6YDE5AQkWIp=uJ^%hG?9>p-}z8`i+W=d_bV*W!Ql2>*ux ze)6qlogQLYKP~!SIn=82VGBQh>9BYAwVa2maJ2QGKf?bN<^KiU@V}E<_w5U;zjDNR zM_B)vBm5sJ#jhW2{d-43%k$R%b<8Zb68^0tGSf=&aE|7G{^zf9qu2`G2Yvi>ti;-Gm};^?IR@bIQSV(|!j^G{g+tz&&<%j-Cj z&Ud!{8%OxRocFTV_@b4bUX{W5@sx$xVNBDA22a2ua z|K<_23ax)S{?^?J{);30?cV#QXx8BOgD6}#{+pUcJ_Etv2H?548-K~s{2ds=NwotWyPzNv z%*&00@)9D&d4-y@bLN8AmcRtF=`Hq7$;kziRa zk9%3))%0?WmU<;N==QlXgisHpKLper>u6(>;li0VeM{F>U88( z`+W6WB(=j_wJLIFhZgFd$es=}RsSs|9p6k&c(0sg+17n}O~drGcH<_^8dG=7jHy<^ zq-(AkIjLaQj4{(^6pR}=>#Ff1r;jecF>AtrR?8VX9vHn5U%a zf8TqsoTu4e@c#5hck3-(uDl_+*_eSFK#aglD}^)^P9ah5SV z$*2G{N69EcnqSCCQEu!8Cr7ytIS}QE3C6OYfRp#Kg$%q6QvWEO@H$6!2`}nTX$-Cl;2(jlwsAmkKWi-zj`E z_%7jF!1oH@23{k42lz4J<=_p%tH93)-wWPu7_qK~%j+vp9Fs> zyb*jnFdLM14>&0N4md;jU2qj)wvOz0B%Rmh zM_6i!!{5OL!asu>3I76aF8mv~jc~lOtPa9y-~r@{(K<9t7MBxk;-Bht4 z9oB4NHkEn8yrJDF90D&D&IaEpTmyWUFe~Q0!ujAe!mM#;MXnUB;rkqXf{awM=6@y3n*XhE8u*NG2<%FLKJH%BFDH!tCT#h{!gd@YBfC_p zDa=6Y2s6+^;SjhTxte8B-bJ`NxK!9QIpM~T4;JQ4Zlqz9KXW-w97@5Hg|7t95FQG? zPIx#tEX+IJV&T!?+l0q~R|ro8YvIY@`-SI#AC3jI2$sjiVF`GX@KW$L;akBwgqMSN z3$FyfA$&J@pRfiW6uuYyk?{TCPlUs3Vfj)lPk>Jgv&mf`qdZx>Hu_UCs)FSaX2tXi zhrsE=tc;a}>w>Ebv!lxuZUnCDVgHXno4`^e4o$($gxi8kgxiBV3U>r|7iQ1cTbP$} zfN)RnFySk}qlMXHP81%@{(q`iM#5pXFnhzf!t4$2AZJB~Wf5W2JL_-4aIzj0W~F;n zxF+~X;acFQg=>S$$YJprYGGF1`-RaLSr3;(V`jv> zmXC`A>Z7$un5DE$n6-L`a6Wjqa2@a)!n}O@gjr$V-J*AmOI43VE*>mZZ$?VrpQFBw zyzzdiozypy`N3?vuy17f2eZ}cNWzDu>Tu-B4@>Q|ev$Pb_Oyrei=6+kr&&NnuJC6tKwk44@$7|xr{Eg$e>g&i4_?;BV_#_vS z)}Q3YFGm-e=F+>|8Ci&A*(bIBoO;;xNv3L~-~Fw8EwxoIYvQRE$vTl%evv}wqCELL z*>U%nQ^K@J_Y-v+x1`0LuqJvM&Xe*K;}qw<5rrE+3qQ8oy_^mncSqxH4~%R&k*#{@ z;l-YskzYYEkwb%?1TY0x!rLSrp&Zl zD*kE;m8a0S#m(W#gHaVv3AsGEfw6Tq;jYm}S@vP;CpnwNgj$mgeo$L-BSH{ahGK^NNSyw63Px`b#J`{&jso`3S0V;+IOp? zzKQW|b=2t?@2R6slItd^ujKqC&()7!%(LHEnNZiKKBL`J|IO-$gDJZMKK+MEOw!9Y zBql}X{=AKsbDkZkd%nh&E6!hM`*>rbL4Rj0j{N*-G4^$lu!ehhPNYvg3B)WM`C%Jo8&ox$n`#qvBZQB0v0=-s0XwcuVJf*Zvj7>#jbA zSd-Gaym7kR?rpGON^v>-*)5?H=em+QnTe5-3*%~Ph&`!n)NU+qpzHK_ny|(t^7+nF z+zP5z;z-e`OiW=)Bg;jnc@E$DVgs793f2ePmKKWd)=%lkmvZv;1?Nf zj_cr?`YJs5+`bi@u<@sQB0c`-Vh0i1C*NXSY zA?^f!dxYfiuR|Wn`LBUI(ccHbdadLxNR&HyS^!%9EJo;m4-U$IOQiH-)pB#;sw}%_ zLS**E(RO7|i!&HMg45qZXdJ&ZDei{#(F*bI6iV zD6u5Bg^Vf6W!ihdv6jP;+4zlR+Mpm&FNi|~qx8BsRb3s?Z^fxvOvJf3)!4&wuo_lK z&<*0%5H&+Di&xEY+7qw(H(G}<^Hd*{52IYs3q{ff+J_*{T?;r4h)XgFQuVFE$07P5 zsQV|V8p_i15>%=3=)DQ5NBR-uMD0`TfznZAxitWJx}HaM3v*Uc9age z3DpI>2}f=hD6(ibBRW%z7(-_bgRl>x%$*1r4LEFE`>G;letuNZZRO4u=b!%#XOES8 zqd1pG!t&HV`%bcQZ-w(h1fFl4O{L3S$uWEc!d8C@3m<|auL{#QAKb7IbzaR?e3IFQ zL#>{Z9LxG#lXcDlRhclAO01MMk?Cqn&70nF0_PeW^sT3$FWNW+ z4ElZQPo6o8_E+II_{F%zh zr?;1(I#MgIsy?8e)`z59FYalPW zq+rw%tfs43;3XX(kA{^{Rlkk2=RStomAplry84K2Mp7~ua_?B)7SJLmB&qChD(a^=evGS>(uuX3 zl{UBz(8g+!#bkV>*d0WZ(Sna}$$J$0fT*P8ZTL)`^c;d6bxGK2K4MS3X2vB!>z@!{ z^26q$vaK7x+UrxH@I&zL>BX3L)4}}g1)*Huz$u|UE>XPuJ>mpl%QXl(ZCI<@nAJ`{ z&O_FPstqiI_Trdy% z&2T8IH%}WaefyNSYVFn=(NLZ?;&5AMRi6g|#x6Sg| z>Of^H4mAC`F}4P98bq>d5X&sbzm;tEb}sO7v!db3?v5j#cx@WjkuC zDlWYnYv*OP3e*^-cO}@KvU>_uz^#XEbS0?*J*|5nBfTpUm4)LW9B2lHH|jQ>1G&0q zBh@+5Yl1scI3c8WrPu)-e!*Q!@BPlxKqcwxulH3e~X5ODQ zrxOkvcBgS+=7Eiz?i_*GRy7^`F1{d`WAzX{b{NWPuiK<~sux`OPr`O$i=JFozBN)L zOp~%Y=%>ed3-pPvGDhgBWpP>h&Jq5qkpt5Ldh^MQ&^<4@3L}Nn3nNpf2O`UHY@Qz4 zQaED-is?mHAaY=OmhRb1RaG_hea0*$1Nqx%C>sJ&Sp>%M~aoBe{ZHr(S+AS z?18Ad5$Efn%~ds3N6&ArTB~~cMcAqrS{+dnsFZ7z8dxJ?F9hFh>_z(b=4!ZVtcSLM z-(o$Vr)K(oo?7S^Tc`@S=SNGO_h{BMw%rtzJxFKf_B zeQ)c9SNqcR@HXl$^P-17+eYQ4n&MHcnlKDQb{H5)r6gL%E;ax`h~VCTlLf@+Nz8SJt2Z=hl5(Zab_&4R0rCrjFzQf z7-mTIGHB{9RVicKUXdGbk_TRH)u)1_wKGI%XmRvL_IA?;5QECrr=ZoZFGMg6 zaip;sz>vKn=lm$;^l5U(X9}3MI4Gx#>=n65K!3fwy~-HbA95IzzCKar-GkmJBYQjyPU>W7PX`oej;_PgAl<8j%IGo(E@EO=h%=IDY^^XR4@06{ z4=2hgBYQ<|@-S3yMsQ)?<6ul4q(J$ILvNIkW0k_Bdl&>Trs6<{GO}0Xto4c+hv|-k z;ZR2QiX3>Ua_Np8Rj|tlilTi$8gA=MFZ%=U2>ghN6<8wdX_-+W`JW1`yI#{#6`%$l z=!lY%8fcP)_!mLjWC8V^hS_5YGHIqn0L5Taf~Ze1qq^(DPO8u3(NTe^Fi0KKK=z8f zGFXj?>aZ} zcSa#kiQ22t8Mh@C9@#5$4>(pIYKRVH-8j^>_<>;% z)TfN>75Q+HPr}(;8tHN7irjMrfRSZa+RyQcUR3R;?>~+(OunDCyG|V$^)WP-SIFB(Rx^UK$O`d zD%uV!4~RO8=uKF8Ky+;n)k!zG&Y!6d^-!4%0%Iw2!0|$lXxt%)ctBM7_l$~qSWi`# zs+}R@0r{$)QPuIV@_^_BMEaV!2)bdZDm1Bs!}_S>)Y7Q_URZfRf4hjjg_VcOy{A_+ zqiHsZ3kQ_?_KNx|f|UnEt0B@&=J_*=P`7A?K%NTD19Pku}V)3>y61@HsHEVgyR4Zxep5N^ephoS<^c5?M7zd#*fU2lPZlCLuy zRK2Kc54~=Izn}~ImYC=U2QPN;?GBDOm`x_;_hDUap}(NPF^A|22cLHEIR{^~^n!){ z^5Jw|4oO*c2iJ3OQwMi&@D&ao>fnhEp6%cp9lR{cXznogIUJsGu&F+hI8G_Y68edQ zzjW|74*t=>KRft$2fJ*<9}Bb`ub@Ph?BH|~XN)!Rfkmk-tFSy2zj8fx+gLgal4F~UYFdxQZaeWl!Xg&YL;qaw{zjg3=2lJ^d7LXga zvKS{iIOyOi4$gLPzPsR0&%GQDLmg~heU|z^N0%-3cT(@_vy1)N;kdXzeWy6MnuF^& zxTAx)z7tFK2nX|xz?eMZ;5Qx2cj%zQF)kL2;{Wt;m4o@(SuCLW4!+H>J}^LK*T^>^ zST8%A_dED#R5f#;%C0X3pb`+OcuOSXpz)LIM7g5T(US&Zz@M*I!#7U5qNgtgV(p}! z4h_PcT7rBns@`jm%BUeB95}^_S0LBZTM=|vf^J5Uq^>o&YSi16Mm5UA$kn1e z3LG18O?K#9OO|lvJLF3p@;e;z@G6JH{bY&YX$L>=;Ex=9-oY9EKmFEraBBzmBG;3Q zkwfx~GZ>cGh;Xb!X&PB_v%tZN9XiV$@&_IKh(l+SL%!7^-|5HAAQr)Xhtm5FrB594 zvkrL>3s%U1F%2Eu!oi~)d=t4|RR2zLbue#6D`AO^QSWysttU(FH#+1WJNP7del*aZ z9CAM%dSa1PBuh1HfrSbe$&B6{^YRr z$DbB6Sr$umJ|@m$+`_><9ekC8XF7PjgAY6S69=C$%<_lf-lAA!9S-#!+{nQ#99-gHF0dmYWBNOIsDnp3c!Gl`$02?dGJY*C&Bu75gO@s( zJ0oH`cR83lBVzLP4&LD4Ee+5&d#V>+aco4g_vq5GA33}x~ry)AhQpH;Z6iqU5|g;->PiX6t!8^ z%Y$??4i&-=%vKz!q3A=unJ5vTFHl}xPg;WIeE-AAg=LRTRh?boM>yO@K4{FCAHy)3 z_N-YLpU~V!mSNfjGHypSsmwn*Y8q34&@a+-q8zQB#*{xAA`Lg1#D+&9Gh zHUee=Sq3uqlVzmxIvLZeG%Yz+lxfsKWEo1VCCk14L$ci5&yuBW^96DhLLe{&?@VWe>$CO!Lo5x! z7la#uZ4@;FDh7Lmn}PkpEx>8Qt-)N*qJA52NVpxirf^4a9bxt*4ZvY~=m|@)IP?Oy z5bg&qC8Hj2blXpuB|li0IUXro2Ru%=0eG@7GcZHA1bm$^5)?Mu*cbs*#Fv)Hy}|d8 zQRx`*YGFqFkT9>B*?0z>T<}vO=Y4#ua9uF_V1`ow-X+`w{JLS3KZ|s)IIu|H6K(}Q zEX-m&F5DUXg)mFb>{LSpEV(lxX9ms*UkOe?y-Go=fynYYxN4XdX+l{JX1|+)=0p#C z3z5v$*jktsp8YPj;IV`T2(yHS3G>p77G`N(Bb*JMEgWXeoF^7uzZ->{gW2ygf)enZ z!puGUUCKv+?-d>cUL!mm{D|;OF#B5W4`gOu6lP|2$Km?Zfw^H{n}V#Md0Iz=+0UF1 zW`FXPa4Ps);dJm%!XfZKgtNeWeonz!7`u~XVU|$3a8>p{mBbQ)Lv`WW;9Oz0*Sf+* z;D*AiD@}#lgIf!;BDWJ}J?KwHhrx^uaquW|7!J(YcyZvhoFWV-Yo;(enQMhZU_R-` zb}z0FPK5k!VRlyQ$>=>9@l(Pq(XGPN2|q8E5G>rMO%HkCcgcvg5PV3Ob>f&XJDJag znfuei9l<{c4*>sNco_IM;n84DE+G7{S$|E4TC7QMFq_H{F)LB3$nOAG5MBkB^)ZX~=N%vX_PyW)B#;QG_TqU$XVES523)JoRatA$Zdt!cu%G;@S` zY32*F(%mGS1HMJLCU}`JOZRT!e8g}4O)Tv5H;~bAlfch7c)Ktoeo2@SzbeeO{--p1n$f0O2dA%@)6r3YU35BDb&&}e1!ia7dg9mn#X2Tem#b`E+f!QCJ z4P)TOV6$NiTnsiF#=y6cMjNW7=wH+_&braw9X1I0{4Ao%_y%9fH51)$a>ig>U|$B5)B)&3YAR_tJ-c=E%NGX z1-=kOFhfvNRY=csr8ZZgNQ>7CJUK>gy{|X@>MkgIV3BHT>phEAxkxi_s&0BSln*aa z)$}uaeJN#E->j~*^+UJdUGk(`@v_d#x2VcxZEjWdRix$qTKbRY)AIC7w*~XczP?SZ zxAm}9@p<-IJF+*%b}{~Y|*UeN_; zsc#p#syDn$z4(e~>{Mrg$JDRZ-#}eGqElDE*Mpb&Y7bvI(55e*W4>O{S5dv} zGWGFLzaZ-D*kbA*>Mc;ufA=!=(Y^l+JjxO8Ob-)vA7(-s(Ql7iyS0w%u5A+v~F{vEKUjN)@PZ z7&rIXU9KWiy~v@yPJa${{d@!| z*p9(=ECuXU5QM2mZ|a73p-^ABEXbo!XF?eyJD!;OH*US?E>yd(F4IryZ&@QmpPf@o z->(lMc>Vmba)JCw(8&><77m?y7*Qehe8M$msr#PYY2_o&eiR5VhnJa>vsQ471>5g3 zFL-76((AVjgL*W*ASaj50^`Ol;V{n3RtfDm*qWwY79O9l;TG9;Fc`c;+|baB@k9zf z&ImqunY03>&G}y~^#b(JLvq8IG410cn-7)`^Xmvs@9p6|ASyi3 zjfNjZh%xPbp|>j*C+x9kK7$^)1P9GgWIqSG%s!g{-Arc^4G%XfF??a~L`ux|XJqFt z3y}AZqZnT@z^*uA0rJZW&IF!A*L5tGS?I;wGXF^rwfw~M_fG+0szCqnu`eT>eVO+QxQ|svfid0aFfVy;ahdnzTnqwYX@Fh~n_L!N z=6u|5kUxfp-s@6$rP1*IEZA}Aq1vHvV+ROXfW2IWL3z zHg2kn<{NeEQZo7NiI^V-GZKyW1U-t0x!n>Cul%JNdI|wjkp($O|L`|e$=ALeL`d^c zh`Q+T?e0qPIo&u}V0IG6y}2CseLehMRl~V4vwU2WM#Y82dRcx(UfKS8)w&dKDr&kK zjj|sYrx$Kil|9!Za%_IBtsmW}YF6~2wC#LcLs$7GhzVO>ccQXm%j-w_%Z;jaRwcAo z*~8cy1uk0^S0RkJIC`+dy5mzS%g0NOJ&mk!_8~p%DOI~7x6~vQ9f1;L@w7Cpg%U{W zmY|=1N>#V>2;;If;}eB~DS_K{NHDuD?mu3%>v-cj8QyhN>HE z#zG7}!=@X221RCD_AC^Wci5MZr@4~`cBa-jcsXJPA3&_HTfv7>1`UFnAC z`yBivy+E|Qr{OVa=j}kZH#b42ZdT1O%IUdTg~Df$LKQ^qvh3hdcyR^!l?Zq6VaVfx z8rTa$ zsrs}UP-z@BI;hDAH7E5HqVRW7tD#+(T2-%jT9s6K9ijUNE8Z(g%1@a!+42uluR@`1 z>SOxbr&Wby?iuypY-F7+d#g@+M&;Nk3A))cs-Zfjr_h>OPT%#63aguR+7={WkRG)K z>!Q!;2ex2I`gi0adGLOOndW^1sZJijI+W>cf|!%9VqBHIzmUg}t9Y}hKZR4(A@3*p zk1g0Wc)QNosF3I@HLjH|+O7(#^@K;& zqAtpR7{aT|{lBVZLs%oUV(h$K^-VEKqp4KPYgn%!q3rZ_HAtz~%i2G$1}N1}-@iju zR~z)s9jaPz5Ys%^#ZVqa`DFd5f7qdx)XqWT)GQYpTn<9nfJ3c5pNuip_0WV1`89fY zU9WjTwNk(7PhL>B*3LlLsSU;->aV%6s?}VEi~8GO{Ov-V&3@B&y{NL(bY1qMs)wyF zU%iN{UgFV7FJZsx2HoZ*^^NWF>Uuj>joRF&tSY&g8ivY;ceT0^?#V^>ut+MotHNbD z5TV|%6QQ2bYjPW6QPv25%v)mf=cWt(=ZMYh_ji(XX&ak}$W=&sXGzlyE9 zd-W;WR%-8Ss$G!Xl#1B*BZmhtk5hD54}VRytiGMmJz%rueeXwfyoIO-?b|S}>5T$u zwzurX*OV(R=SL>EGM6!;+xlw#08~}jkVKH^)nB}=>KCzVQ(4>pbTHR1hu2Ru`%PG$I9_;rRt&i=&E|e?v1)MTmxD07Iui+6p^LC^)vj%>w zZi{vOy{aPKy6L`G%~bd3-FsDgyJ4bE*r)OvHb7R=ijHE;k+}(}&wMo%Ub|7%`(~?jY1upb)W}K+ zdoVb%t&`?clmEZp{?%F!TI_03w()Ba*QfiSo5%R4$q=WYiCux5*&@%owy(RXxh*kx0dU1m+?# z#-*P^Fe@~fV+OBqGjPy_`03CBmJD$y0ap?30L~Wf3a%ww3N8@FQ=8RDcrduR@HlWA z;hEr0!VADXgzsSf`-p{Ad7$v);NimC!DEE?f_WEaqS-r46Mh#wNBAK4df{VW^SMSg zH83})rhr+H{9I$0mMmD-hyzRSG2xov4Z?X~^Swss=YzSCi+<~XUlQhH^Q*!vQS-S* z=(Gl#&ozSEfX(L`!R;RLIasAbJRG>LPeyNSUZ039 zV!lc_x>lOEg%O{1K$v&%4~5ggM};{h&*>2QtpPqIoD2Rb%3-S(Ea%0cE|@P7&;vUY z8>u2U2B(vg0WfBHPuMK)fjI;)%X?tv(k$D6lFAr>%_rL{Uv%Cjp2If*9+z7ltxFvXraBIYG z-7c0k;FZE1!F z9A=>G#oiN##o)ujH-nE0v*-Fkm_66m!uNp9hb0ltD)2u<&Q9!tFmG&jLNo&pfIY%% z*#GlQWG3PXIB)|knLQWZN+v%A4hcUC&JkwE)r5@N*B8w9N5})g?SzMby9kc}mkM76 z?q?X~KLHl=B}#Z^pFdKRSd5c|=Yh?~C!uo#m``YoYysGOGZK6Yc(KTD1DlUeLVhRM ze0&njQq-cuQoKLr`ERg1A`UFBCxssZKP~(y*sK{MpmkugW(Z~pnKeW3Rf11}RU1m7*( z9DJW}8}M3T4sh0y!|>b-mW|@Tk<1q1kzmdnFoMzGox)?muL(~C?-8B?eph%p_>k~S z@G;@J;LnAZfWxQ7!dm)+@N)3qg;#>j!Xgr}3T)OD!EE2<(4R7K9Hdkxqr=Yu^J^|- zmPDR#LvTIerr@F|ht1HNU+ka<7Ewpx_TcWqJ;1$%`+x@s4+9Sq9uMYBBg0|tCknIa zP8DY7#`pTE!zMb9?~2jF+}|h;Y@K!ujAL;X2@E!c0Voa075h;l|+Z z!Y$bU_ZCZAI1CW(2_7cgA3R!k0C=MCAn;V-A>i41 z7Rytx91&)PIwAZF_zPiHsGrG|O|NVHE*ym1<&DbAgMGrhl&QiYFy~n4w-&gXu-QUU zLoChUz?~vgY5{IRMkPoBw-rtUcNS(P?J3N<(pNYH9wf|4IzqS}c&u=J@FXwC|M1C5 zF;yIj!8egH#$v>`2&aIT2{UJR3!}8H`-DT_wZhCLKhVc;7}=A;?1;=9QZd~34TYIpBz3ctii{HxgYxr;fKIq3qJxrV;JTC7%cw~hYjEh!kfUhKN|5? zut#_o*f0DFI8FF9a7E!az#-wcz%_;Ufa?hF2R8_dUnt7k3?%+=Bp!u`Ns3112RR(OPs>wiWpqu}65MnE~y*R#qA-;HLOEUdxl!t6{c z39kcJ7k(U^EBrLLuJBecx4JR>GH^?B7?$T?=_C#>fv*r|H#}JQ4e(fDb|#aB_km{$ z9|T`7dmV9XlegFrWQ`!kxew!d<{s zgxM)&3zvdx3G+FyAb{)7>v0V%+}%fB3~nyG3fx9`Ex3~~JCGj2FM#_9?*tDN-U}Ws z{4RKm@cZCN!Y9J8%n-{dFn0zr5x;^L2)j`+mk9IY4z~+?!4Y8}_+H@vc&#u$`mjzo z6TDHF&2yk?H@OJHt=r2cE* zLg7Q;#)fhIPr$-Gl2rN(+*bHBxU+CM7bcH{gW!I`6~RMzkA9=guxqy`Pqmq!jFQV z7q0RIEW5w&Rn7kx8vM-iC%!STCewS zxAA`oy4rKl8n9GPeGbxjdMB*v3!VHtPHXgVob0~K^ycT`xJqB7HLybW+5zht{ooE* zb5`mzu-YwF>XH{AeqS$p!JebelqI}qC)jpijSlRDbMd2k#7=v*-T7sGnD(Tvblxtg zzp3Z!vTLjT`r%!6HllcAmz^8eYXtf)eHIcsX)<5O()D)R0lVsCU9#J*Y4^u@Q~C1x z1_;$sy==Fgt3K3aIN6;O^a-e_?KVR5`5ox;TsXT!CeX*B#*6 zZ(QJUaC~4EI3dsunw|i6Uz7`Q0U$B(Hhg(8pN4qc6{fv{@B$v@B=8D6sKB!5ktQ{kB5eYfq?YeMp7c08rzLga3bC&bB1r1S#9O{j z}eh#|~#nvdHSeZv+) zo@ph0isX3w9k|=ucNijnM|!Y)+?wR?L{`3?hEew!$Lbp zQX6n+cq*jPI6li`obGseh0xgKhISa8R*XG^6eU!I~2FjaXpfx$kH-cP34*04v zDQ&{Er1|)YP@pY2(`QoDj$9cv>3yb#A0`R~IuOlsi&g5v$16|0T zXgpoXNj~nL2y`O{e0-NP(4Cy-OC$G4qb1YFQGKANA6(f=o26Inx9f%3{?$#a=o!~Q zS1slORt5`LEnxv?EP`F#%%!Z%4F2DoTE(TT48D?}{>GK8jQgl@??8CT*i3~77+Zx$ zA+5L#Nmq}21>_nCLfC_d)MNKS4JO{*Pj@(AH}Y|ZpBk$8=3e=0^vwtCs=kX1X+#aG zRhzA!Ibdg3F)1l!N@%jO@ly`R&G41IP5*SjuB-Nxh2FLM*>)pa&pBvksYUwUgLb8W z$=N>Uj88=k-qEiev`?zf^~U$@>;e<)X$HF%j@5a!)z`e*{Nhx$N$@vJuxrJdaCXSn z7vHxlsLi^<2X-a3O*i_${-gGICN>#gQ^x5X%uX~hVJ0gES{!1Q(-eDEtLZ*H>_fXk z&_q&0F_sVtviTyt^h5jas-nK(kX>P-acyZ_Z-Hw|zTJv{pJ0{6N9mh3X3=*tu^~wF zI=xyw0uNBljK@yKBZm|%Su3GwK7`Q6%Kn_`GtDOYa#{9ntq$8AoANCT&ossEti)`l zGoOa%Gn|*tT5YVhpCcn_IebX-E`-EegAte9s}~%$)6}E-?!$K9Om6K<%u;N*Q}M{) z?S$a0b}4aMePjx(5>9ohAz8kulaP>Csl$e36uqp0R(t+mO2$iyeP_FG^@xw`Y6Xkw zW1~XAWegYpW9UQ-~&muWS3pGju*6cumjJ*6Y&8;aHiU zVenneG~5^qH5z@#?g?e7NARD@p1;GtjX#zBf8*=rt;^a! zm^()ADoVUZ-?_JZ<+5sD+nwUe)}FEdNWizL*POR=0!7x?Nh4>BA3kH|^qivNCWZR_ z^LBH)xVS9%i@h-`oQvr!&U|?9mRnY*WHD)!M%kG!2^VwfrkmV26NcYKs$pu zj^N5F*}U!r?g?Hf@?PMZg?oeVFfY47sV^)MaTo#qoA6cO2Zbkq`JBKAt_D9TJPrJ` zFo%I35vdKi}dVtE(*fiTC)9}AxXe=2+i%po)bx&Z!8m~Yse z6|M{ZMYtjOqVN!K94;&Ua?a5UHZQyJqmCT9(19c94B@HZD#A0s*}}X_)e>F;E)eEW zxREgDADatvp0SPa6W|WQm|6^5186}-%Rz29fF&1zM+c~G)`|nK>pI~n;Elq(#vH*gaWlcs3C{-a6rKlu zO_-N;kMNCP^Y_2-yA*s##z*3tQwC`dtyJK=Ya1-GU;FiLj z!R>^*g89TleLmdq0f}4+?kC(2JXn~QW27)I!8r3_1bE;j;HCz8X7SDtX31$@ zyc)b%cpdmQVV2Me;a9<0_yCyilQaDHzz++*kNRivL4Xb)!eNszhrru}kArszp8)R` z{tWzvFiVjeKN!wQ@Im1p!5<0#0{%pp(}`b_!|)IX%V}{)0$(7bzbt|W^ST$fDVRf1 z%DI~67j6ko6K)NzD9p{w0dt2K z^%sJB2`>Sg&p$wZCwPd+BVhCS2grHJ`1nV^Yr*FAuvC=)!?4U02hJ1E72XP7ApAUd zi7;!pc|8mP?FF0H!@x(t=Jhb}DX@7x4E!DVA@TbIcpEtl0J9?=)i7q+Ee2a#4tVW*eE!$2j)@SlvJ+TF*b!}Iv z-K)7?UK`f!`V&|&TT@U6r(HVC(_X#5j_U`^A?>US+c}+F&o##`?5&s9b78`1fIf^f zlyVDPwbd`WPk}32&D3)WT)A;K*R-rc{V*hUSv@nsbG*P6P;coU;A-PPMHc*m`JH-& zkR8yi3n8mjpvM=wYT8L7^>UmMY#GHZ8tW6ZZqNz!U5@|BoSIiYuDEGoqsGw*o~dQm z*LNkPcsr(G3^3h`k)KE3(au%bQ;>*(x}EThe!88jX7U?;*gSDxdU#5>T7T8f)ghac zpt6ar2ef6XC7058R#v--U-xY9s_r|7rwdH2jKRsiL(gmPs-~Li)$Ls^EBp%4rAd=+ zv^4FHX5@cR|J2@Y?R)ilb_#{vT?@K_kg5~h*3Z^0-?%+1P zyMwEGI3595!R81u9NY%JAy@^G%%@jtUa*x5H?iPq{LT&Tfw${|^YMFaa2o2?oM2B3 zpk@b~A<$VtzGyNtcr(gnMv$MKn;v8s(}LW^In}aHAkZW;8!Ku5p;5+ zols0oE=7sClZ*MUZb2?#SV6w-tAbOJa65P@>X1sPwnwR?$JyL_C|6& zLB4TYF8CdMC0h0l{ctB&C%dv=f7{8`%zqLZ!GCZfCCR6fbx~(mJ+(|<)!CJU%D%L- zYf$p@sDQyNm4>7irC;IIKX!JFu9Sp?1dGh`(v|6aS1s7cJopSq->0W`aaG9s9v2|k z!pKIXKSqVt#xgqn@5q0!#8}3s|E`~gc6bR>(N4XKY|KpOXHJ9djb(QFEV^_wmTS|0 z!j%YiRxFpf>0EIQb~l!J>HMNau+*sCkp3YqNwBx7g%mGH{|Hwg*vD8FrI*m9pRp`W z|B|5%P`L8Lt=rQVQE#x~3xCVgPcXO<>M7KvyV94Sh=ZdP=ayEbpJRGPE4+AUtxo5w zF2S+tFkIH87cgJr6?gnSlKvt?n`Sb(K7F8G-xb}$X1%|wD_0%VKX-M_S7mxuH&-Z! z>$<@ujamLJQKp{YgG_<%Q(V5_+7FOX%XdUS*UgpBEB0l#*cJPnTIPz??T#z9N)PGo zYMt~g5*P2zVWq06*L8Q*P*3W8-CYHRW0BR=3N;W~5)>ZC*^Z~EVn6CWiIPmM_!JP6 z8WgP1b$YmJx0-{rxf2`njjtfrG^`-k;{(Bi;E?G$k&|g z;GO#J9;igi^z%Jj*)=L7sKg!*!zIc05Bw(f+=@^GzMBwWV(CtB8uDrPbme8vhb6H$ zLrpq_7y^lXe*^oL>9##x*%eGc157}c{XKmTe2J=y^$k5;88wz69f?C*Au(>BQ5{BA zk7a*?R+KnA9s$~~>gRj9YS;;0{b^6v0MBz&2&M&en^IRscoJf_f(iIV>x3WMO5$$T zw8T66A!y$_DE7o%19J*!)-Q#AkK@ zG1nNp0>yg)&WUAigwe~_6csvgJNXLVtGdS(u8a!z(s{>1w&J`P+vLFRjh{VJp)}8T9|}9 zZZjbhk+<53h)q3dvn(Hh-KtfHYM^#vwksM|9TVQ6^2`Ii-tDTQ?$@mbx$;p06JTSx zEgj^l1?w||5X(&c4NhH6EQKz{!q=x-?q&81U2N=_{i|iZ9No^vIB3v(k!E*hteh|i zVx@w( z*=xJeM8nI`(?oLvd?70@42fR!V?$h-wL7wg?7O5m$tZd%#?rITq^B#C^2g${+ZaP_ z(xxe2SO7KRqWxO*30bX9sD?|^m^sTeF30HM3C5ad z>~Xm3TUPyC{PfMjPpwaMc$BMFkS}?uK5?AIZOCf)tA1{j>vqi8cD%|p8J}?4dX=lb zdQE@D(?y**+Evw87omFhus(EX>DTQ>W32zFzGk$mR$&D3XI{xnHm?pH-U_1=!R{_?S?q$y&H*$3^-PP8I{b}XGs=IE*`#6kHkZna zGRHcqR+Qf&=SO)WyYkvm_M+5Oohb7UY*pPTmyrvid>$NDg;5J<~5L-dyBWz_AV1=%g-fux0_oHdr&$vHjKTHDdd#k+<-r4c5%W7zvbVifyoN z0G=lDB5-Vjbqg?Gv|*yVf^QNY1m>z4<)gsMgeQXU7M=yZ&oIjWdRW$q!+h{MVP2Yz z!Z(Ar2;U8UPWVyqPT_Un*MuJj?-70y%%?IYXcPF5@HX(Vuvp4q`CRxpu$gg(haKQw zDVJu+F-nXRg>k|hK~#2o$FU+PN6lUu+li&!4t(21tDcpKe50)HJY7EX7<|9g>a652g z;TyqigcpH33*QXxDZCs!Q1~tI2;sfpvBK|y!;{2v5SBT@oQu9e_+zly!;FL;2QL%( z7htoW8S<0h)gu2Iyhiw2@D?(9O6LAK;YQ${Q4X8WLGar$RB8r?J;H6l?+SMS9}?~a zJ|^4~{JHRzU^AzUaE5??5cw$Z--XA3e-oYzR%YulJj{kA0ZojFSOE43F9!1qY?R*y zt{{9DxSH_az%_&)0@oIP8C+kO#m-NsF&vg|OW`BzWZH@4D{vR#(_ns3iynRgn?29q zbKt=u{~0_|_*d{aVVA<%hHyFX3}MdnUnd*@^Cg8aBS?qEeD@g4f%ghg;`Y$h!gat8 z3-cN6ap4BwO~Ng~X0jXMlz^WTIo}rEOHPUo>faMa^A1P%Il}=rxaedznC<(tD6um! z6Wox8z-DhVxCYpK>ln=Yh1t&xZU{EpnZayLW;-*uHP~!t2Db-?&=I6?>q&Q5%zkD# zl!DEEW+aV?F#DOo)xc&yGq^g~>}Liu5oSL#m^Iw&X9lxLo9)ct`rxsQ3|s`BEX)MW zFk6q|z}jy1GlQ9+n4AeR`swwisf4Hi^9Asz9M`B_$}e3;QhkvWIhmH4gOg8L9qEYG9q3JJ}L5t!QTl# z20ojB>(63&9F||iVJrBe@blm}PqgS>1bc;F0SATO1ZN1dN2?;t4k}ytTW~GmbKttd zKePWYp~WBV+s(I;!5r%fqRP%`*!nfWXM@3%(s!jSzxnm8Jq{^e<3i;+Tf|e zb-{CmSq~PFx%H$eEK9_p1$c#UXYeZFZr}%mdx0MjX0P{z@Nn>E;gR5Hg|7m?AUp>A zvhYN3_)W1~2g^ZWj)IN|qkyav!i&IP3Eu(!R`?$98R1o67w($;Km$aU*`5sM9Wq(u z>EQGzhfPse5(l>R>cSy#u5dQEu5e8-KmEoCSfnk5S)^Q=pu7o~pVlI`0dq!{%yvIm zxC?l=*?J6z!LUrHLjV9{_9qM9LU}MM=R!@4&30oB8ekrwT)2$P?85AD@H^zRsQkSc zvv|Lxg=GSB+QApd<)d;eJ6vL}_VBy+H2efxjPuEvQMuU}EZl~2)Mz)H%&uQyE)s<4 zfS%SGC`#zMt>MBffHA^M_|?MjV$Bi`fv*?lWm+iA%f!z*GW`F;-FwDIQT<`xGn1L! z$!>PD8$v=SBq0ek2}vNKhF+ye5ftgY_YMo96ancTkkAAX6$JzgAPOQXcI;hI6bq&?BDqMp#3d!rnQ?(T&EMLxQ_7tGk!yL-Wm zfJbN%m=5dRz2ItKy}K98D)|M`!z%ez;Vkgm!YqpK3)ctpT*8FPh<`@QPg)RV4wZBJ zFg?UCS~5y0&IgP#J*0Q^LLP2Mcl3f;2k0HWV0xmq=x6A8o)+z?3vMKw4Q>(ZhMD$9$&7P6dgre28Ih-@JQ0j~n4iuGqfs?p7e+ygZrrU7z=PR8 z)c+#TJ9ojfNblSQGn9JgE|{T=fo;?e`-~)V3II>pW?gcs$kU!IVS1>(FkVIjVLkrP zmICG=Je{xM9>TQf24PyHH|Rn?q>RBL&yY@E6EWUU~XI| z3eCZrgj<8R33mYR7VZV+Lmcn=$?S+{f*9kN8_7z3}F$M~!fQJVnQHxk! zCy7E1c&2b3n3oXJv4&vIwUJwZmkYN6uNLkCUN77Yyoa0~_2@ofmaK<`%fWr)s0i2u zJTAIx6MEAu3NC%EH_d_(QQS0ZB!HRG^qyHTJ$*vt*+%F+vyf+_rT5H&nS6TB zESO35y54#WK_&!x(=0d_tT)Yq^T8Lz(ju_lGz)op`g@V5r+*fvr}ds$SjgDYduGAR zE9K$a1TZTly=N92VbtkOvk2Z~?fjFr&A# za4WFhOAGz&!8eFJ^V7|&e`!%42!@IRYqZhAH-je%4+Kva9s<^zY+-gdSZ}fgj{z?g zJ(Iwzgr|eo3(o~_7G4JT4ZZmmhF3x19#Pl;en5C9_@MAEaH;TK@MFS9z)uPv1wSi% z9Q>j%E41@ulusLcksN^_4uZ>~fMhhj7Y>1W@-7XqZ>cxpf>|PLC{mso$|uZp4GGr* zCkfXDR}p3@NEK#&ie!nP7=rr3t-<-iUBQiodxDFF`-9sE4*>JV%>=m0klifIfDIL9 zz(xz>WlW4R(jS89qQH>N6=uj52{U9|h(re%vQ@$incjR0Nrq;#$TtCR7iNokk8m;g z0pYgbgL>;R40nK_R1}yq9}{M#dqS8^>g!|_2ZoY&fRY)q^TK!;7lj$H%fgA^?}gcO z*ZXZ@mi& zutkhjB4B#27p@N8EX)dOyD%y~;~rsTe&Ye*eDFbG#>xrdmf$Ca87rLZrsukVU)GHH zXAXZu6#9YR6CMfvSa>v;%e-iK9QbQt#?TMKjGyGlpW28EAF|IEjojjRRMS zhyZ$xRADwGS;CCE`oa~#`NE8?#=>k!iiJ^+8C(=e2MWMlgqwk{7cK_(6^?X(puY$@ zfhUsDhB7Bl7uLm6nCZ7jm>G7da1HP(VR~Y{a1MC0Fx!jm!VSUqB(VO6V=S)^hys1O zU$_PMG&wt330VOVX{DOcLRkKLdemgKIUW{-8vaq9PZb~p9ad^=9{ znQymIjTeCSfUsiV(tNuct`lmq0I%283clV~@8M;2*`ex2?5@flSe+Wg0Z{{&R@6OUokMCMxhpfxs?~)Rp6)jN> z_@bDtUWb)TgB5lr{E@a2ua&AZUtg%HczG(^iSDkWFWK;{X=EB7#i`5pdMYIhnLeWa z#O#!m+=9Zq1_ilAg@x<6EIF@1euEs9?clzRMyu=;76=U zPJ#5l+sltZ(z=-{2{%A1Hi=Kx+8ix%osTK$dS9ZX?X=P_??d20;7mW0_R5+(x z`N(0&+vU5^h;6^8P1;ML#X3O)Sy2b9p^%Ba6*h%DNq)S12$11PCK~?z`0-RA+kT#h z5oOnZ2Ml@sS@yUKx-44ZsZ@o(_Dhh6Gxw->H`+DLHFw@=cZlafHK(B15lW%(NOi-V zb`SGKb>E%#mz9d3W>yMA73m_2dnU!8>3d4;Q}!Y2B|{C~WY?%lJGk#Vx~?UIMU%~J zWk_X?RNFV%XUyYj*k-$z`Jp+dYugb zbK+1)X5K-SNmMZh)~!TOO{07Yyi${H*YK9PU3~)9uu)@2sMTGss%}AOMyiQh?9#^C z!i6+gcQUP7M19rZ$GV$o09j;=J)Er zR=Z(GZFDn@enF#oG`fXua5GIb3?PrM&2@&5Ze(xMUR|`!ZVERZ z-)4_Cx2UY`cEkGGjYHJyM~zlj$Hox~IY(7@G;KOgH#qB5XMfV9O?o`M?RoXHA=Q?Y_*sg8$K4ko!N1MgOB% z^goLEIeFJOlAm+!cF+F#`8h?`+LWJLbgkwFIsc>Bpx}Rmc?H+%&1?8Siut*vgP*hq z*NSvv_Cyxvvx3Zq7|#W=FUr4?*(l?QM0~z6cn*-+2gCCjSz4PQ zieTBIqmJZ6elg-1L;<@PcxIB>QNvRn9xGd2be3E-D$nUvvqqGA!*g5=Lbq=crd5N5 z`GGJ}n0>zS!tC=+)S2D4Cqc_bX1Ich3Sb0 zh3Sby!gTAHa3;flQUv%e&`0{vf%;%>mM0g2UlC^Cjz|4az73cs6_C4tza~qPeRmDV zAPG1z&BuEUbFo^4v&e~1s7?iNWpHg_#&*6ieXUn1K~FkZuTlaRfZK~6b~g1YCCIl0 z_YnDx;Gty6E2IDAh%sIS@UXFvESYG@HM~+7S;JT-j6fTkgc-PP!fe@g3p3yC6=onG zB_q9P&l8#v|1|uJDA4eW!Zh%jF!SR(!tDNjAk4wUD`dDtOMez-D(E}XAutD@XQ|VRh`?!J?y#W#n&4f+{9?IJxE6T7a2@aw zVSJ|;kLndmFq{X$6QaNj#fzzEX(RB9!Y#nB3AX}&DBKJDnJ_;%FA0wae%1a3bDdl!2#jz;1n`yk92T`Fhf&Mn4!rN&IT6=GXTZHtV`MnGXP!4 z5m?$4g6l=0JNRbd-r!-vH-g6qvveNzUeUvt)0Zef zz5!S-A_6xCpAbFGz)ynpVW}-4cuo`;b?1Z`b?*tY_4`ElI`9|5{lMP{GZudoo(}#^ zcsAHTFOvb73-kyh@HZH7B3J~4B;i}ZRfLy=Gldz|^@W*X^M&_;xy_Oe90WJ_mM;6r zzT3qa8ZG~_%f;}M5>VdE6>&~JA<-(*5{XtpZfVPUZmd~)_usY~gD>a@ET(pjn!e>>1x$&tZc{GW_53@b~{d2+$Q#e16_Zp)*ifu zm9FN|GPowH>@fxS#e+YiSj2II z)ZthsqxO3l$Othai5KPr4WXw=M&2y`D;^~i9l#BN8siEsO) z1T`bWsad%wgqD*xegAVz9G9)&JHACgJ(A(HsrPsokyDS^&T57hRrVpj30q_zausNh zeaQ1vW~P%G=u9VE`$1S_eXsgtI@QfiYDT8h(EM6GnCWnn>8qJe^V}ZL=jLBx{1_G& zt+++cBGTeHUmY7XKLLB}w)(dcetoy8mRU}die2Fi-{kVp6bhbzDc=;TCI*kFjag2O zQ7Q4TE<76wkuWq2hZn(S>?S^}s?~CmDg_X~aVKtME(vl0uw14YY#67y)^ZwK z;efimmQy`405+RpHaAu{7iMDgwn{tv3WDK;uRz8V=18@3O`9R?yNF|Je9y@;W$6&pc#|T#o(*7IZH!C~=%&o;-lI`UVU~4W90S^|u0p}CA zea8+KwnGjLa~8k|4}=yo+z46E3V)}1)OK=E0B@=7w70s)tLJMwjROp2=w}`)5*Hk& z66!eF=2+FLj+1U>`qZd8PQTy#Nb)4aqKZot1eDgWDaYG`np$HY2 zOzpmjuc>Zzor+l{i26`dEjK9f8ER;u1;Y~0(P*(2j7of4t%2^yPJ}BfKO(Zb(3n%jU+0aeqXn1u^BJZdO4KlgTa6=*&ZiI%I zcR{czk%OtBVP+Aul_Y-5K#nl6bkNw5IGYZQGI^cJ?!-zIOxA(in>a=-t>y zg&tN-vYi&e>hw(bPX;s?D5sWXJFOzC5%|P}Lax{H^W43JB4!OEsOM4}lM#!E;e;kk zFFSY(Jdn_o>;|7BH=~|7|I7))jD+UoVDLC%AfW|06nqFdIiZ-GfPA!=bJ z+>Kvf*TFCr3hsf0zHZ~diNR%RYOa%2ftwk9*V9&?g00w~ycY(7W7Q#8R7qRZS6gIQ zoZ0sE8%z5t_|=tMC#mLG1XXq@2esY-)QcU;`%xHu0}nx9omX`lI5n}^utx)@z`RK< zZQ!IO4y3j)*EWT)pB;fQ5*Dk+8#u`kF8Hv*-y#deggIVo8$MpBfj!GXs05Sf-__ic zX9S<7>ubnXkfp=7mTU*NQO`QEC)k)CS ztE6i?-{!xeVg&D?3wM$2;5%wUo|9~$0$iHs6bE(CqnnozboEA_Qy(MmcD|DxXwBH% zN4wL5hg5OClUA4O<9r9k!82`wyoknkkPDo+cX>3Nusl?Lcp6%T`1&CC`T35ogim_~`%?d-+p_6aUL=?arangN>aNwz*18XyGIU%29t4R?0=5fLhxE&1|kZ z*utrd{gE%WaCV{gm|W~6Hob^EWGWU$9t_eX7a=q^)7TOSn~ugwh>WpUsHUylLNzY& zY?2>VPZT@N%!vBC*r{M8lv5R3I<=ZINzKQZZ@x!*G#P^UGf(~sVHhqIGk=?>n5t&4 zCOhG(`2;PT1T~}aeQH5VXIb$tv?11H(5n88`pNX^ZPZo2WRU!3OW0VI_ZXOQnyXdS zaT9McZt(H_p=#I4$%%BO!!54W(Ms#6)@stwAqMWCrMuOt>HzoDf~sxlSg$B^mf7sB zo2P13(;3+VwE~?mTJNOlCUX#&K{HGCS4St=L-U)=havN>2O&x;h%f!UmGiBID&P1bORlECbRYFeJ7*4N0B&yYEHIx_@3(hqLFDP+oHa{I zU+#eZqxnSX`A&}C#BlZH&Q6|X#i<%yoY9#6S<%J$2vcBVuXAc(T5HvHPDhB|f~a#p z`rWme3ERLyrlwhvSPKs7Xmf2w+f|H@rR#X{*^HMoj6S4sZJg=1+3OlgajnwUU70g) zD;?7fnLN~)Hh=9@!KXVmAE$kw9_{WtW;OGxIX#@o8Lbfiv6J<2#a%Zbzl7WCwyY~p zatZg)aoN?Epu*RqL-C4gbvK=MK|QIcb4m9eaX1#hsbF4J z_w_`X=%b$M=?u3L<5b;VPF0lVPQ6fI9#&&|IkoFIK}HPsm{`Be9~|MwA|Qk>eB4|@ z=__^0TfBl`#3pfM`+k6$a`MRYKZ+F(GpbBeu%4ra4aLrJ9 zLBrfawPuHts7g*baaKWL>HVjil^Y^?s3ptB)%AEda}S<+$Vz2n?5D}d<$O5JR5q6W zAo}}d!{NNHuWSgM#~GLHuYH=#Aw)cS(=JB_@Z_>^b6kZ_b7a=C4YvEqs3!OWp)sD*l6^8pJawVUstkY3k z_*7#Rg)GabA2~b9JIFav-hv@YGdIeu%7Yt3`D1c^lzHwF$L(46c(yzlVKR7V5;>mX z?=AwkZ}3oZDj>%j{e@Af{B!yw$4y6xJR@m>a2GHSqo>*4;5ouKf%RxLqt_IHn>)~oJ$42yUHTX8L96uN>Z2zLkTjkM5nJy>s~1#_Hrq39U|=1KMR1Y=^Q z@HFr`;RWDL!pp(igd+-q-6GfuzF&AB_<-;hx)??t{c3?dO4rXlWA#iXfa6B?1 z?O_aYE{@z6Tv>P!xP~xeC{uVk>mNNf4g-s!pvT6+j3PZY4&DTAAqMUSw-(+H?n6fQ zrepnuZw3z$9ta*KJQ6%Xcr8?t$)tzF!7qRhh}oCGj|jg3j_9#*XgUwUX;JtD%xO$I{2BNq;V-~?%pCGR zgWnbTU%}^vx%Kp-Ft?sw7LEshFU*x7zeG7=l!M?eQAh&YXjJHM1+Y(;!#N>gZcj}T zP61aDP6MY3*8*n=a|>&IVfOCwg}H^bu^v~4V@)9_7KPT}cEY^bri*Y7@b$u6!qZoH z6nKE}bnr0Yh2Sy5w}U4MuLI8z-pEBa^F*)(e2efN@G{{Cz;_7q=%|ho}J;o011=eHi;67jjH7eb`iS@r8WQSld!vF*UJt@L~os zOGh=~Szx_^7xIh1wMBjvI7gU8x1sPZa8uz2!L2l-{2zg!gD4yYcNON;Mla!K!8Z!O z0v;s%0eG13m*AP?iX211XsK{5@M2+hHI@t41FzOQeqkUR0*)}#Z~>U-Jd!yVvQxMi ze6Mg@@Popv0S^f?dXEXSwLB?20Q{6NWBPgF$Rr3}5dow3Enzko9|+$Hz975={H5?R z@VCN@_Me0|f`1p@3O3znVD15Xh3^Lk^d-ayC6l0>C>#e@6lTp;UHCn)-q{QNjCOq? zF**t%u)dHO45jFW#Neu6eIYTpI+*AFaW^58LtjV?E@1tCqbL+YVUTcR@Cf0i;Bmq& zz*B@~Y~`V0|HR<>+LvzK|HqrdeM|3@!pkIOfbfimf2f7ZQUR0(~Jdn7-B* z5`%k#e;57CMEpLW*%9D4;nCo7!t7U85}p7~5oQ1~q8!opyVezjIZ$XIJP%wb%#bw~ zUI=a@d>gov@CI;q;VobuCP>e11NRr+4IUzVFL;#R2@Fm5K`=oS9so}hei%GQ_&9i> z@Y7(9{Lq2t!7GJd1g{f*AG}HUD=p|BjR_bG$df`1l0 z3aqy#L%sy;BAVzhb9hjACsf$I}U4wZv-ph z{@|^`gTcFm8NFqf4KqrQQ@(oiYxk7!NXS1eJR1Ch@Hp20uZn=R(%Ztb!54&~#rR72 z7Vs5eCezQtE5JMfln&nkwoul{YzbZAE#P=zCKD%L6C%*WbgC>0Os5*c2f>-bOfwFP z)6!CKo-mWFNcd533*nPsy~P>&UjXYZ&fu58dW$po95|x)I79Fz1bUA%_-*h|vFIY0 z=b6#rZ@?3Ue*{k#z6zczY@6tg3A3NSOqkvFJA~OK-x%eH!G8D_QQ&a+PT^wky~11w z|DZ4j#SaPd2Ha!9oxmrBIm`2u@F4K>!b8BEEM{Oh{reVAf}vm{1Rsb3uPwVEJO})x z@I3Ih!i&H^3G)Vu--S1Vd7BCCybJ6VE&&IG9|D&XKFt1qMG>$MUtRcVa82Q};5x$G z8IUV{4%|rieQ-13Pr-FFgv3@uwo(jnh@(W~~4J6hSK}=u3!U zDKn85y$w3h1zd@YZc+%$wJ~IRE<-pO%oQe-r{@|7qfs>qg;T)Ig<-AHh8%%`x)AWF zTpG>>bMlYe6x>I+Ik>-YF?fhDo6%9ioxu}?`+)W3#IT3CeU8Wv0xuLE4vyR^0_Jc< zMjSG2whA*j_6jq^`-K_eBf<>vqrwdF6T+xkjAw+A75>>1%{=j1R34WQL-3AhY6JdA zxIOqY;p@O$HbT#J2me8aEwqR;yyRT4E1U-o3KxJAg;{@a!3gy;MpN`oUX7TDn?+=fLDtAv*2~YFM&&hUkC32M;I07Avhok-+&(#{uz8) z_*d{*;XlAH30o$5P{Kj5zIeD+G->q3!(b+jzIYhSq`6A{U?fe%_)`Q-8y@sd1*T1` zFq0-;m`PJ!m`S7eL_Yd02gUS{4ar^g(xU+Tj6`borMpA zdk7x^-yqB`*UiF@frkp81dkSe3OrGmRqt%!bKu1h5wPmjd!iAVufUr`{(G?A2Mzh3 z!1sv!FJQe78uEXE_lvv_NpPB6o2yw+7V@n_?_2=Fp43MYbjD*`QI zS^r)b?ioJ|v*LO``n}pdv7%a@1bfj=w@L1t4tp68KfS|C}yH%Jq+U>$<7!9syp&okT zPT>r2i7*P0aknrc%-AbT&pjlJ+-@8eW~FzW9D$`xAUGuo%=u3XcLcv6+ztGy@b%!g zg?ocP6z&WDRQM+FSHgqAdb=~+8Vdedu%236BGFQwQZIf|G=& zfoqZLN7FuAm}y@i%(QPJ%zi^lVU&2IeUu}5-tRh5V6^uTX0#6@XGa|vBTP#t3DeRU z!pvOrgb^m=7UA;XWy0*3+#y^Yyn!e2&;hnin?!+W`2ZQYsSWs`Fq5NHxC{6(;jZ8( zg}Z~F6=rh0EIbJOhA@-hJ>lJO-}qPr`@o+I?+1S^%!=g);b*}-Y@VKb9n6J4UxEuIQ;Typ3 z3-S(x000A($x&jOzKqt-yN5Od3=v2WO-ha;_+PwnJfqyasCERBu-4 zO?y3cTtBZ;GkIp}09&m+;HjO?Rq&SOZSO}gVogWJBzE#b?Mp9>s(Q+7aeqCYNH-T1I4 zrQCf`G2*$*A`;IDmj&^g)T)O)$r0{Sn;p+p0kh({pMPfjNH5yecy61X9={0k)8ZXi zJ~jRley7AgjNi%ezrvzP2otpW2XM+EXvg)t%YqY}8hF=;e;fv%HsX1CLWB5c;9z<@ zcf24>JYzR*(q!m`nYA9?Kpp?0s&&XyIcF~X61Vpr$oRJ5Clq(QC4Axk0S4nv5OH<^ z9F99V6=?e}sA-2hO^~q<9P(7F5r$uk_^YtpG_YV3y2E$kr%S*#Z-t?RAJn&pJQW#l zzQdlrl{wHKXlr(aKhqPq&mhpz`~^1TBs5m@4||HO8nNo+VNZqHyiGGO+>AjWTPJjc zj=(gtI^^0U1Zl-mtiP)0{DZpv5opC$g-1Nec|5>3IFt@l@wY@&1&5QX`aeW;1Si}Iw^ICE2pgQlaHku- zR<8Qx5l{W(l+5+gF^Y1OvWQ>S?%Y{Y7Ro{1fQHwZAskQ%7MDqTY=E0?u0T2h~HR2*ob-TB#>3gY%2N>yAQKoWB>$`MN%f z-;lo&<-5HOPV{GbRF$Ki%nHdc@4KGX`glw?mivdG%fCzAbkvhpQQOp4+hoKnqw#*P zL08NGrH*=Pn#0v&M?L)_+-2#;bK^iLelmO>HhjGxg%$cdR@na*lGV4GAIOHE)7!o^ zWXu05c`e!Yb9JL{9oggm5x>6mPvF<}PeJH?8_02f9u@4{NX82N_S%6>!ys2l=QiKw z#U_;eJTcpM7uojTp!Og0B%|y-bIj8+u!}0Y*aNWrkE`;>J=wT-tSw#vuK4loqs{65 zOKQ<^PgwW13O<;pz0z6!kI;bSk9nr8{6pcmD{= zA0_wjbM2My*h292{vufCJI>tE%YTUp^C-EmpC!|Gf>TTV{eD+9ebm!9z@-ninURQ+ z8UM3d{HUkc{8OEI)YCf5YR0T>a%s}7FkWq+s&K-S9ex}Jur!$8_Z+TIIjH)c@EpJe zS+S3K8X`v*KjsE58@%AZC z?T|LLoTlhcm`r#AO-u4e^v@ui-GFaM{F^2Gh^w}xbo*(P&APWCj?BBv2cUvjG*Yw< zcKfT9XJro+@RLZFYWcD1?Gr2R9|yc#gkc%){U13#B^>KEE*dv+=-_eL)29xfGBtbT;ORq04W2SA8~@WsPncNt z9yomJq{0Rb8)nP9-nf~gCn=nNlKWp)K2NUWYpeFncABVFx5WfXtv5ZJegDP2McbW< z>ZKh{h0;UkJ(ZkN_o8P{^T-#>z=%OUClS{s(wWa-CM42<&ml76nh$q8a(b3e4>CJ{ zcvx$2PKb}rlffwe0QRuQ3E#(&(MlqC2ns2}r@ef_zqHzME?%pZjhgMSk)1sllq zOuv(0kMJ3AobU@^mJI4S2d*Ie8n~J;cH~4a1A?Bnz_<(uU&au;0|8qO@<-r?!ko)( zD*O$&mGF;Xc4(>p4{$eO6O~GDVGp>UunQhbMumn3!k8?q??{>{0yNsjeBm&7v2YT2 zxiD)neM=B5ssUy*MoVjgw+Pn*?-b4f-z&@t>H*<4V17C1JCa!Z&QRcs7Qg3&S^Ulk z4*j7|t7miYJ00LgV zMLq+rAj~3HP55PSx-g4eZDCe2Il`$(j)uZ@!7YVx)}YZrxHY(Elq1Gy2=u~YI6N6V zN;J&|PY}KpJWY5hc#iNo@Iv7Y;3dL4!7GJ#gVzZ&=j&VT8DQ`>y)qhtqY&&CO{c;4 z3qJ=wAp9!$5n*OheRv1VehNM<@|VG9g|C2L68-_q&j!ZsPvCcjuSOs^FM?me7lr=@ zUl#Tw_kJ%N1pgwO2>wf0Z`DU>paU30h~5|kP6t<}Jeq9u&-CGcqv5H0|>6)pyM6z%}-PmY7b;32{+1*3%Xz!QXPMW0?rA{5$+6L zD104wiSUi!mBMU-*9o(9Y!V&=-X_c@Slp-)+>x!7IIf7|iZ{j@Fw28O2<+y586K^WdU2WpcbULF;byk%&@upej ziaENJP#ujpeEH8)6Vl%$sY*?~A?xPiJF{bw66!(=Ls=}EikGNfO}!ZfTOlzEYGn`d zu6~OCU;1|}iRxp1sIR`=Y*w}^v{5g>3Qu3;oj*-|e2G=zc2%XBH_MfiNQ=~|KjV_^ zyut>BDt;ADG%a1-44xXxt}2?4|9Px75L&O(g&5n*F&CU*!rt7!r;{Fn_i(=G?FdPX z)b<0*Lzi)bH1d=_I?O+`bg4GF`AK7HNua$n3yhfVHL_+8k?$WpTFcyh~8=@pI75XLkoCSd-aBuLAuq(Y2u77aL<*9LkBaJq7Fx z1bBL$5s2^>w@6?;eisB@g8cjdcV5g341?aefvNbN6Zi}=vjZogIxFx!jLi%@0ht+r zxlo-R;NJad0ruyo26zG8lmO31njGM!DV$XDHoV{&a14&cHpEZJdz_V+uM2*>C$1#m zh28Yf>ODCFXycfb4@;Ah1IuBn_Z$b}<9zR^n_7AsM82oqKo=;OfqJmj3QPyb1ZqRx z4jczNfkHU#3GmE3Z=fc$y8%`tu>m$QzQ9v3Z{&8Sii+G3}f?AEpk=TXB_t)YWq^;Si#z3J9xwi?*ln`eCF&Ml%g=&W1MlExVJI*RPA>@seun6DD8P>7k%d-ZGs3s<7Ax=qZ2w@D6)@q}g*8@y z?e?d5^E>7{BFFt+2jSh_dxwcytygm0^UJbe|2rH6&M8r3-7j!*d4IZ z<8Rs$3c3vVn=$kV3wL)0{avO|p}YE^4T2NzQ88`3={n)e9!#pb&{?^yYSh+SHE;w5 z%s#sy6RcWC4Q}h*Rpkv>g^?SUmJsAQh-Q|sIZ6FJMy0g#HqG4$OU#f-Q(Sj; zy|+?j4g&?-(;vC0=M!0e5_MLq{iAxKy|;Yw{m_7Vn2CLJnGPdshWfg_H{0x5TD60> zm2G)r)Pa3=#o`IWr;Q$AWdDC3guo!eoc}n6Q99u|?~|6g_VV7A7_pctNO(Rx;lxDj^5FtaeonBFEGL`>W`U=C2DrL#eQ-@-R$Fz1 z(Y-LZh?Dx;fEx+712>CuMDO5gEed_1fL2x;xCvZ#nCT$!O(M_Aoz*cdWiBf_%ya>G zyvVbwF;#dYc((8saM@X=_kwRLJMi=%1gxlN>0$6%;p1RtD9S$#-YR?!yi1th!TOL> z=y@N^x|JPy7K2m5?1DTk%ou$^I0M}ugGZjwKo_HjRPcDE}N^w+Z#MM=|PWEOvf8cX&gxWtD z_2Q6=~#1Amtk<~Mnn4K%nT6TTaWeNtM0aI9v{)aKD>04Gb9s%1T<`sS z*$z7z=(#+Bm+rYROT+ZX5PNI|f%kU&SU*E-*JF1YhpKnr%e9wg(X46JhJzNmhlbOj zCVt%(_%V0j$AAKA)!47qpy}S4W>>Xjx;F!_2d8_7;)s&)3~!=&hsv0N)8Ji8b(rDJ zg5u;E-T`KgItRrBj-YvNoB(T*7ty}T7fXMh;XPt`IZ0+2=hfg*Nfny>m+Efk9=G^E zRCoUeMtn=(o#Sm_CX63Ge&E#6bA}I`W)MeD7_COm^EUd=wdS^Y-o5?^PiiZxHUA=` zc=0KZcq|*hZB9l5!-thu*BBoSL{As%;Y0snw7@tK!#q>zLD3lQk&l#pTtYH7LH^ zHrPn5fRy>2+P?+`JI1d+Y`$58Dxit~&b8hQD?LeN@!iv}y7S#3NzLNBZk*bIw=)i( zYi**XJ0)3NS$j?CUiW1MWbfR)o?7OH(xL0T5j!#v0Ww4Q;lol|_E@X}|M@t%5eb3P zz80^4OM40>O_v+sP})a8KZ^LX`h{!-k?p)ik?oy{moDNg+c3g$4v)Q(pVS=Hl;ZAM z4m=_sE%k}fj}{*>`q8qM_QlT9w%YtGL0#|zEQ>vcpR$jZuOR==N6T+ih<>!3hrIL8 zM+*lNVvfWVgt_M9LZK7L_(&ED$fV0D78 zuhjdSy|?26l96|n>7A?f@{noh-F}zWd-5)1;nOO9i?>oQZQ*Khmws5Sdzy)9@T0j# zbGgAohmx5DZL7DU z*-ee!>TPOvRC~61+hR)f@>XwQg}Y(ITW0_aMZ3u!nSI1*rf9XT_dsF{&FI1g-$a{b3ll#4;>5&{poK%c8m@HBmUL+$heBzP7WtHmR zWYk-H_zhS#Lv}M6Lu`DSz|OMDwxuwP8SRBxKVKK+2)=evNs2-exSucwjRp!=1`iib z0gn|<12enOF}B+Cg|on%H=ukS@N(h$;MKyp;Eg8g3~FKrdW$GD2JaMZ2IlZ7^%R47 zL<6}E_>gdW@MFRq!A}Zz1wSj?1N^dZKQQM6XwS{8v)&WIAn<3xL&2N}pr%pa?}W#K zuL@59|0z5T9D^jI{@GyN#(@`s<3)Zsn6(4-pvKf+75a=ImIU3%A?@S9x{U)Tf(vN? z%%oxCN=8S};C?VNhg3TWr-Hi+r-S#HZi2o%J+%F2tzz2jkfFBXw20kIY z2Ygz1FZitR{ot2`9|pfJd>s6)@CopT!cTy|(;r4CW+*+zRVsk-XZ$IQ(N?r$QF%_) zc!kS@E0Nu3lBEbU$ufkQWNekFhoz~3FiTURF!!h$`Xno8N(HwOg-ozM$qMouh0q6C zfmv+yK~~@*us+BN+yp#C%(evUgRCIWB%3Jm9l*S!gC6Pxo*ToNFbKLpz|kx+i^@{r z{@_)@Q^D(nXMj0$L;bVB+lA+Y?-5=Ien6N>d{B5bSkDK;>^8V>JSGZE*C&Pdf}a&W z41QVoaqt_$PlMkRJ`4U>_&MuhkayyU=_Fym}9T7KcX|#HZhP7h26r9 z!1oKYO4gS)z%Z-W6C&Rkd|H@wz**t$;FpA%hh7&R0Df0^5cos8bliL1+FmZE{RD0O zEcH2FR$`_qynw!Sp;~$YvX|9ccv+z=mGvoP^VQ5xA-haH!`JI7`7`v&o2u2HL9|bO z#1~fXUPPz-g1YG&xo8PaganeexP9D&=#0SPoO| zKlf&0Tx}v>`;_AAs5*(4Q&Y_qlT+<;}#b0Oza;T58+Z-XH(-_v^ZEyl;CVoB%HCYIOy( z9cHxj63zhg^N8}Sat8@F0FMx6D>+V>t>hHpHee3F(rhR20%4YfTcTY0N;IH{|5XUbXT!Lv2TC(ma`%+q9!%}gk7xZ zu6?XGA^PXjlRX6FeNJusoA>)s(sWyCHRlED=eN~A15Nx;MKRdUm+d^FU)~>~O@D=U zh08G!(`9e@+L5M)kdKZuSqp$pboxvY^_M7$$_j01ni*@%tZ3Io1acEfNa{xJwVg=~ea zub{`P$F_WbLDBM!yB;$B+0;JsVVX7K*5k+aPo4>dSQIGyZpN<}JDdh$n?X9p(6ekN zu9IL@65Ik$Mj)af9%DWRco7VSM#A1Of6US5YfucoNSnr(B~VNVAH%OXMGF{lFF}L1 z2tOXX1i$5`Q+dD1PH!SzF!$4bqx@pp_tPREB=vSvvt*#v60Mik^^?{G743SmcKtVw zOVof|1?_>~>Au;sBAX^yrU$z#9NXB!Ei_-*ILoa*GL_zJq{Bcr*hZQq??W<0JJCpo zlqeS3TMy2pETyeIOr|aeio+u}{^e#Qw_tGA zbIwWc4L3`kr`|29iRms)9!t}^wPL!qi1{I9sd~qB_u#uuLaWYWlzezk#)c(-YCq#bi1qLt5fvQ**x;wk&Se}b&5lIWaA+4p?_QD63b-;2ziB%U77 z&qfDHZE$$2jBgf3kIfhw

    9P8X&{wT0x=i*X?Tcud3$ZZEh-kKG*eHR+Vb%^*o5LsI+`{CR*|>`7pFl zUBb)CudSLExa?<)EO65>yS1Xg&2%#1i5;pGGS)MRx}U*j@!EdIJCNq`oQ7`546Dem zNy%!@>BNv#rJm~5(9Q4^;q&7cQ(f8XOAYj`M~)6GG>oph$xl`(0@svvH{G*U zl}7G06IZ7`s$e!!YuhE)!Yw1K8@bi}Ef5DL>-@^5TI3{G9yt%z{@aD%h!VOG7oh>& zXAT|x+kJkSlD6}!et93Dn(a-c$aJH> zI`2_vv5be+DNl0w|JuE6;Y}`H-}+L5N@(nU{$J}Lbviw{@Xn3NH>$t?PC8WDys2Aa zS?SLD;?HN8byXxez7D#A`!5F4oE9i8hu8bRNHc#}pZ!Iq<=9Bg46E2)zwnC;E99t2 zdC3{*bZ#G;TuI$l?8g5yo2VBE!L9!1eN2IwTfHr;$q8h;ZG>Yw3a5 zbZgnstC_vDTF)X{Yk<~O-1Y??Mf{<&xE!Hz3%ORw zdPY?n;3h?Q$deJM0vR*lg)>%QEj$tv7)CD#F2EPC8o!>vaMa4)z(cela2z^g1Jhy3 z2VeRMI2{()$(k+jJt8_1cmQrM2=L&>`E&qlxj1_^H^3@=PT(&Ddv@Rj{6=O4@?h^w zeG$rxKux5>^uS6eObdkI($oOY`k4}V9KVwT9>`-c*DtWuH-KX;u}$HRkpK8Iu+{fB z^!QH@4d1u0$A5AW(DogHcK_L9U^kvKGXCeuKHn|W6FK(~1aZE#u*m-!IS|h&8UMTF zppR!A`ad9ZO6Epb9;}5>+i{b>2M1c}*n3td%sB+y3h@4q7$aB_0z0mRllg%Q@PHBE z(QaI~#riJ9MZ#U;Y%@Cr?uO~}IIl^~MwpA=GR4fsD-^Cz zL5A52uf*^Xcq6Wkxg3HD;VmjO$gNnw1DoR7nq2Xo9_|AV#dS2-!tR`KZ>n@L83FkT zxXKHi8MpjBFhKU3SGJ5-U@ zE!A>Zu~j}+ogIX#euug|$jvursv3jcbPS@j8|*GLzf{i*cC#~f!2Y9w?-wH_eJdF` zKQf?(?>+c3%C@gtjH)uktsi8x1b*gH_c&i5p!yDRQ~S+>`M}Tge(y4p;K@J7{SsGA$frO{mN zP@nK7WRgI>7TgeSLzM!pyKi_4Lr`cgfrtBtd(+!RS}-8Yqf-M-wYEXwE;Q3Z3xWt+jR2!lxJuZM0xU_z|kK(}G#y2kC_lIw*6( zvzQ9e>@hEVhOTzg+7{@0;sV#3jPiwH-VPAxsRg%&`=Z_n^wxr9;T^QCuNh$}Tp3=E z{1dpzydGg$6W&Sd2AL1TCmX^%Cp9p{WH#6ou0R`xnW>N~36G-62=jfY>f|{PnMP~~krxX7$%OI!tzLiyX)MD*pAMNXTtGqC{0!=G z@F9)HHt?(PNH?j|BM4u7V_IwZ`Xa*Po9KY}{D|rJrexPQh1`sK;^KMLS$uPH(DxVo z5#NFw@>xMOd8Avd>vKqlU>BX<72CqT;B_WTuTiNV6wGF($skD|AiOR2tEVGwu zw*5z?m#fWGc|(0L(rsZ?u~oHEZY%RHjQpWeQR>c7Zev`y`N}BwYx7lga3}j zAcig2LlCEohl05w#)S@Ga>fN!(F=tfXIJXUk z+-8k)8=4c;{&DV9^KX?l-o4Js@~J7~-7G81Q6=NuPF6TheKFq6HD6YhCb(%K76S9U zmxaLWX=E%`*Fjd76Z1ze%L!!zv1-u-H`lt+QHLkEskMh9BbqhbY{aYClZU1wG(8L>-;z7A9sO3z(m1Z7)L_U+Xv6%AVvFrj}48$EF3d;guYgWHZ-hN#>#C zxrp-tlkjmmRIQriR;kPi-|TLu(-~+n8ggobC(aL5>JDzngHg>qoiPH)XSvMlW`V^F&NFgzncqdcfnruWW+RJ!e2e1S)Ul~3!*n|6Zx8eGY+&x6j2gpBH`yN4 zXbBj?@E3hUYmHcjfJ46sK}Ce%b^6hWWnA%S%$$BP$~Oa1^tWm-!_BFol~c4b&yLKg zMCYR7nar#c)Z!T^EiT=A;51+9ur!Y7EPA6JUP!G*^lPd9qe)9vfEmT`;AAF!of8F{X_2Fzct0@nz zF#GGeY%;#g8|;P?$S}rM7|7+`QFENGN!IbRy@7*5%kuiDRxNEc2UUgTIBLOM7oYuJ z)pwrTGTezVTVDHiDf8YAb^ko~mV~`1N9L;zN8?eDwl9wobRSL;0ciCew)639bU-&l^#D}vuU4~4Dlm0eo!w%x}V|} zLDh7DTP<@E44B95hHKztRw@*JV~l*LHNJ`bk++wAS*jK-a65-XtViPI6gm3(*3sGfFQui-( znKrB!CnmiXEdb4-?b$|80`J?KN7tfdP`R? zcF(1HnSm^0^ZyS9S{IbQcdt9S(tjP~DD86Ez0P0y$&2pO8CHH#>HR;ti5nt&5T9iO zsy~pCu6(Mn?m@=q)0T{!&1V7`iN}YNL1lxeE)v2->ht-9jQWBP?>I6!GKZ%b64XqH z@(6M{E?&UH9as7!3~(ow8&p7iwvt)l;dzyenuU*_g^i61p88~@H6PxMTXwVC5;E6` z;MqmyNGu+HD&S@}{e0l&K)_*HJRVlw)uYTij?I)PcOj=nc~`7j^I`e_hqgP9v$=ZU z$A8~WY>l~8CC)d;11LmMr2 zJ`$Bsq(b?oJL7+ zq8lguJ>4W}_7dzgP5Mf@S<=gBR(`=BTL*DkB<=Bjq-E06X&$2ZyQ~bhn$tEJcm?|m z%d@#P3gs#=U+Xcx6a1ds9sWe_2ixjJ$TJAuqxd24A$d4_R2~WQwI|1U6Zaav0c7PF z{1*seN2IZ)0>`$-Ac z!l&e+@LBm5_=0=~%=OKgmH^+0GdkY#YpHDL4`M5`i;H_y#^F?}u6cnFIU+^UGuUH#jPv zfwSbl;4<<*a77u$2R32iOuD z?Zel~*dLRvM+Fa?&%rQzi=&y1_( z%J9o_HTX4|JL4vqJL5ZY1NbAEXUXj{cg!7df)nPI@mnRdhJT{5C|t@@GO`8E%AC0i za#=WtRm1!_FdLwwIip!}HMp$IQ+SSynN9@Qt|15D+|`kJc55KFf!Qt_^K^n+$-Uur za)0KOz5gA~! zjqE3n`$TJ*OTf}Y#$yEpy2{)qddpmM*T_|1esIkEdGOGpZs{5$Fo_8q5`WV!F^iNK z&(rflnzN5TbBUjjna6hC#%+f`+j*P3fq7u&*-G;!gUfGwZ?gfM#gxEI$7pT~{GGnU z=Va#L>oMlRS`HM$?GihA;a{msTwdlHtt{i<2~?+9vs|6|ABOW>Y<~s}0{KdK3T`Gp1GkZ%gFDG<;BNAZa36Us+)rKy z50W>)!{klyP4ZiqeqfveZ^M)2cj4*s2k@QpxA0tfFZ_VKAAVRq2tO_#hM$&?!_UfR z;1}Qom)^k@uTTlREWRbj;P+)#Tl-Wl2Y)SBg7?Z*;Qz>c_j*ij0H2Uq-IyhAoNfV| zB~QVrjDa>th{&DcEScXXm6cgZGDjW=SC=Qjm&vo>hBC|an#%Lx)-vmn@;ydoWvu_j z2-wcpFcXi$1DSx`o~PUEFY%2sWG z4|7`I%L$$YY^Q5T;APQvx(0J5Y^Q55Cu}=igL7eC1URBPU>1$3a#$AZ(L6&}lzC#U zD(AqpWzJ}QAKRY;)JLF+5_m0WDYt;z%WdH+;pJ<3b4)f(Q^PGTfpJ*^&WqhT0euQEBK|_27yif5Z;Zt<&9~mog8ZkM?*vt+KJd*lSS<^2k9TP~8h#E;2b zjZev3`jv7O_<0%UvB2wcBlu0Z3H+Yi4E{vsseO9}&;MA0b_g6~LPOkE!9U1X!pCKv z0e_RZCjOLfg#VF8!J&9^JUkJ(^38Bu9tW3{Z{_(vTY;%as3h|yvxa;JoG0G}=gSYk z&EyB+Hu564ll&OmO*YV;H)P&FZjt%D&IdB@8Mn$j z1AZy<`?c?qP6R3FQsvOhOqgT!wCj> zAz7^iUPx>oXNc#8WWD0?6ZnwENt*+2lR4~HGKc+6=CFHZ`~*(YI2rJ`|6Rrn2-kly z;R+GUdH4-h!rSmD z`8{}|`~iH2{4so&ycNDz=3%)&{tkXbJ_tV{AAy(2r{EWO|IMX5i@@vhKk#OmHy5^> zGUQ<)<#xr#;hi!c!FJ2l;e&E*_y;*3J}x(df0GmZj_fZ5I>5Ze@?3Z*jRT!) zVuZ}4A1mj;6O&E^xV>*vLT%V~)P;d@e;Amf_y+Ji8T)2nq1+NSGS|vd`3iWs+!J0c z_k&-Shr+KF=lKT{8;iguC2;BAk-5Yl$+O|@@;&el`CfRJJP$q~FM^NAOW>38D)=vX z4W=KssK5&_d!pbBy$qM6v4~+fTjr^>l8m1~4VkMtPv)x5mn*`yM=^|tM^hWc*M&R5 z2~MCs0^O8=(|e$gjQI%klX;6aNaiXXCJ%&flDSI9$v482cQMSw1F-E~tQFS(LIiC0 zVn|pD@6iBH!H49P@Xs>0-|zA(@Ok-Fm{mNulpEo6c{3c9--5H`_u(=L1wKWfqPzpH zD({19%ZK6m@-euH{43m2J_EOx|A4QM&%r%q7V%vrCyF63K!F^1sLb-a5pr{QtlS2k zDtCj67T{`lj^eL}=gA}Bh4N(B$h^T>T9W5~tS;WYELQ^WUS5)U$Ff1@N8elImGB4h zDtN2>68xq7HvFyp9=uQf5I!t_!X}4)RA3ttPRd`wzsq04=jGjS5LX<|^gcKu{{Uym z$KVq3FK{{eG@L76glo_Vj3^Yu^STlua5LG1+sZNca+!^;cb7}UedTg+e>n#pEVE_2 z;c`oOwA>C(j8}jk{Y;U&!FR~4rFNIx2fk0{2Q~|3HYa4{LGV&}D7;)A1+SLJ!0VGn zZPMn-&@^v+dyVGCx_JBJ*qaJKzMDvK<0%($a_z9U6hnC6x;pb%56kPEg34$Eud4EZ&k%lYgspI{{3Tpf{su0f+a_;B zTgkX_4s?(){XiF)m)ahM8Lvcs&SeF~3tvU{M}o!QFnjk#dY}oL+WR6sgEcmq>H8u* zg55TnZTLlFGv)S2*b(Gt{=98A;U}2?rtuFTQ=2!VU0)GiDEe#+cgw!Ov84JAY=It^9e=lsk-{ zC1x~#UNxKe^S1H6$ImG<06)Q;cg+jmV{!!_m<#;v^?~Vj1QS_no;ZT!(1+$YgJ(?h zA21L;NybmG%BN=U56Im4Q&Z59%mDlZOMhWr_|Xn}p0Tf*u0L_moo2~T7!)6#9OrL~Z%m7y@mu^`GatX0t35wQ zJ_=^-f(i-ah~Ld)+HBWBB5BFBSpc>vlp8d{NEP7JQ`pEOO!_U0$>C zk?u!Gvmfa`k5Q(DuzZ1Z3^^=~p(hav^Ij{+vw2F&H6*IY;tgQ3Jy635qzz&;TL(!= zeyRI4!YM^x>hjx3e5v~#CKox41a}tx!MJ%h4%#B z1xD9#4r6Fz9piC=?4>9;WHaK@27S()9wO4)@vhMug@MB9oknwP6T^tU+>Xs>J0|I! zc}Elq1T(NM(+6-)yx;IoJpH;?n4jfa>4QjoES7FQTI*I%T+Q({dI?b(O*lW{f?`<6 zI9q5B#Muym6TgkK6Xz{xN}S)uh2l>lH8sxuMAPEOFjRWHByxu1XAl#K^L*|Gyha>D zWaA8s&Q3Y9nGE=>a~Rn&3k>;F&DdAmQqfjOhmp@<1b*Y(&7u@^mrBI49AzjkvQuyx$*mv z6W=nk8irdi8wnYIVvV6u8-7_3--%@`6^LJk`E=r?5eUW$G0BubFqCFmt#kA0|BV4R z_`mU^H2(s!`DeIl@ue%`lMeZ;p6j3C0)+j2X6ZV&vQyr?v(7Db&Hc#hpM4kq7WZ%9 z&}W&ev_Hhgb5oS{t8z?#(b@iR`aEmcRLIKZSW7w_ASdep2S^5Tvnrdq>)kSmHq2bk zeq~xCi=VFg*$(HvP8NHJ!iHcVFRL?CD%foGvVO->qsPG$m_mLQ@7Db4_H)h-#rc`4 zU(^#XYuyZspE02MuHZUlw1V_-qW*d^lM%w(=HZUxUH;sN98yJ~2(sX?l z%gpa9{q}ayNm=)E$vW7;Gh{*=A%- z;NjF zdN^wx^Ih+7UwJf(4d(g79ClN*B!tnRiVt1afrA8!|ORn}LC z@+aEB3t4S30e_MWtj*%>g+JA)yByhH&Egls{xrLuU&~s+fu`HQ>sghVlCS}MsCwEg ze9g@ZUQ99Xyyn&jW~P~+UvoP+bxpn3-IBd_a6Xp3k1=vJ_=$5fvcf_NA+uggoX;1Z#1)SfFfKN6!VQ5~Q%)G4sv|zuj1Ky^)&*kC?W*wEcT=nd z9rjZ(L$Q|3b)=vP9^&#NlipMOZb&r zrFvK+h-GFzZ&w;&&TVij27d~fa&Ne~&QjCz4Yy%%SBjbNhTA20AzY;9R5g!oa<2})88D|dxy2KEkmB6B-!DoJUxA1^T;q|8tTJ#JH)G!GSk`E7 zhtCXoKjNS0mDY9n+~+Rh9+MM=co7KHtBHT(Z`p;d{}Kmm)qq7c?ju(e_vnl1Wdh!4O;pA0RgaT~_J4`{49{$+CYXC&R7jwfxuoGg|!8{%;IT!6nDu_Ewk zko|j^E}ryjm@Yp5NZ6viP00YRzjzjrIj)anUVyjD<=`E1F1$;w4D%|*Z%V4cM`S*S z{4Cdjf0gs#KZ@F0u7|+iN?=KA3YM57Du5$$8#qgD2bYyQ!@O29e=oSYd=-3|%sag% za(|d*5X?Ui58r{d3Jim>~KyxKZU~t;(kGO_BWIb+hocu{uA%jEMfeSw+k1=5)&7CZHN>ZR&O|+! zPxCZ)>Tl&l3j}Pd!AR%~pI`#!fUE9|%n6*6>%e@JW1hMopBL!*aHh<0mX_PW732VFt zeIEAM{%^$ye(IT9_%qdP!A~$J-^5*yg)oC%#11nr;|I;vF7^<4%JlRQ*}**Jc_*Fu zW`U2;adQAa!BBHkFX~MW20EKnQRF>n{)i%Vuem(t)yC)h6JuUw6mFQ9S3NZ!bCzq~ zLrn0h>bAF^e_~!7m->thuX^yQ>Sk?TN=efr1998TV8o#}gnRhY!Mx0$N#~rU|MPD2%WtN|d6#H+H%eWe)Liq(v8zh^4&R_ZZ`N$qObpq}q-!%tQ-KOFE zG_1B@>RgkX=GJ$%o4#qdWqi`yn&wt=-ZGD-xmBD+=FK#>W`dtwps?asyB&hnFkB41 zF)qbw_jyeS_{T8TgwIa}XZXC)yThM=L8klsoa%O;Pkqz;{VJ&W*cAT| z@=W%-AoW&%ImR=|XZt}D{m1a{1fL%skN4XlwGj<~L;UM>BKC{eufJhE8+$o4T%B z+F4^dqmi*{PhnyYI(uhhkuoMt8iUp@UPZ2eTNX3q?B`5`-1LY!<+`^!E6f-V4-d!9 zV$ZFObIZG)`=`_1Z1UZvC~5oMcSkuv7Q<#gRM@5=cfkq|3!y`E$g^m%q(o?Zuy^2JYRhhu2MLux%*qU#7|gnMb8`c zvFeJNCAb?K&*5befBXu#=(&Q8_!K=$RKi*;dY0%-aIeMd z2>$(!>Eez%`MhyH8L+jx9Lxl7#bd~nF7t{Ul{v1W+y3%!8O8GstfE{Gt|~Wx`M}6= z7QiiN%t$x5oo(|Kf!+voR>HM#5BYl7J^~=mFxWl<3AJW{?7zFB?SvZ<#w|N+>zux8R8U37jEsgGnGH2is>1cp_|{F<>5k_89}71KVc| z_#Su&#{=IBv*3fC2ak~#@_B=WzD#%)3HEsdE`(<*{%!a^nM?Mdyc1q5AApz0$KYpV z-rBB`Ps1#6;k3foKWv93{Mep%t+ovU*oV;ufz4DJ{z$G0+a?H*hcEPYD83fFOXlHs zK&}TLkqh9TWgK#WU*+y_;tvIQ2>mUyYOrma028|lwrvx@c%PSSngHezQ39t_F3tTg zo1&o?z_~Kt)>N05!E7Rqc~-#rNhfTVSuDk80t?LB$Q$5J@>g&-c@Nx2{vPfp{{jz^ zPr<|F)9_95S$Mqs7d$l;+n*D@fWQnT1aa2l4Hx5G_&&Kf%)Yi6Uj;5|zn}(O)P6xe zyh3>z!$r*(bbyPRFX#fZH8_r^FW+>sQ*H(Z!SBjL;g4lrP(G7yf%%e(`FU~KBi{}m zl6i4CD&GzNBJ;X&T7C}x3r=uEyl`Ap0?UzHT;mzf3r8`T7miZ$yKs5=8@RH31g<6j z0@st-B6?$)H*hWFzxe`%_w}6EMI_iZ4q#p^Y#Rq~2)1n;z`R!2HV)t{c&LUg1CNk% z;jwZ}Epl5(OY0=6LpjEJRbmoWkN(P6mZC2lISl&rPP+r5r53q;saJ%{B245X2P z+$S69_CrfKQ`LSO;gxyu6l`c^{W6e*;&Szk@H6_rne4gRpHM z0pmOj+x8LQBd~2B0p^u9VH-$5;3NXJfdu$h*fx*=^QvVVNPy46H)sMEVLq61rqgit zyhY~y;H`2s_;#6@s z2KqMK@TBGUmb3+h_D)0haTz&~IEx!uart#>6D|Dc~jQuLm zM8;2`rOZRCtz673_&tj|N~Sa_Q2FIdbFz$AF~RBw4&Uz={RO{(oSilBFG?j_ zBL&rzp5z>aQb*l92~S389wU=IKA}wQL*@*pvNA5}CY|9=D3y<(7r;$ zl7)KlLFR>QuS^Y=Q>I_#aKA5zS7e539In|`ajyOJ{x;A${+#&*xoa-tEDX1C?c*3K zkUq)z5cxX9c{`GRk4-@n{CAqV<-IB>puDoYmmkaun)}M*{lOXYQhBeObGP}lyw^Xm z2IC5a8#Tp`-Lb-rwPW$D8E%<__;5A+Q#^ddC(LsKa~1B&c?(4Dzy=O?r=1AT=;59Z z!$EKGs9}L{Z#oqD0sn^k(rJ;GnWsPVha-3K-vRgHbd4^|p2FgXuVW_HW37$wV9t*p znZ@`abQ~?SV%bVZMpwW@BYb{{+-x^uWGpg8#?hh3P>yaQvxFnNkv}qxBlRP1GyimM z&3NPiF2)SHcp%N>R`hCB;R)HT%{Ed~F%V`oR1+@6n@6t=7aP%Ld_lOm8P_>I%e2{3 zthPB;(JM^vhXI3uJ}gfx{=XFBp*Zn2b5*YQ!2dFmUo*Yxg@<#!u|e}By2mgzJ`87? z!xyq{EG$*kI}k}Lj=QX#A#<=*xRR+**XxA}skhYiY6k~8X6+BrN@=|9crta7f2>k) zYjjaP^w6>|YQE@Ls(L7fA|+F?4SI9<(+sYMw?=2O@LHmzd9t3D`G1^nQyVs@r_TC< z-%VItC9A@s3a8TwbLxBRQ`7j?`}s7ptFf1z;NgQa?YH>(@0oTd)+SDye2l@Fb|CJ6 zai;x{k>PY+>2apb!8mZH-N0=U0v<|7*a;Wn$My;zu&o`~I;QFkp7$}T{kef7w*9mu zf{$_rl0Cz-396LvNNjhUba``t>Yu#yhFO4!+Mc|ViVVf{L;1X$!xrz3OrfT{ZF5-| zfEOstgOe_w#ys@u$L{<5<`_KSe}sP>|2<4D=%3`=qskZVQbRspgQxnxbIyG}j->mw zkU#7<#H1tsA;i1>LW~`i8~IR}RuIKVy-<3i?>O?kcH~=;E8+9@Y=+O$q&xgZ@O0l` zfZP55;NNNfS;XAt^I|g9pO5KG@z?M!X|i7fEBIFbag1n^e=kNf(VvBXC-`+Rz<56$ zsdziH4s(z`=uM2<3x)gp$n0k#YB#D9!XF0w`!SIQekx|Vihl!!$8t_bhVZzIo{ zSL6!kZp77#uQmgl;i|xbi^x?DTbL|}^XeD5#`fIOD$d*4NPin>9e>>{ZRSJaDqkjOnY117K6IiF_RnR6~GSgIs&{F%G{wa|I$>G1jC*kvlOa-!5}F@}%kB(yN@{?&J65i$p*2 z6X&}(7cm~;X7c;c#UfR)di<;D%t$S)2EQMj6`6$D_6JtMu*D;}j330kqjVs4Cw@ZF zhPjv*e>46G_$@Iy#~+DI!9b8renty8r=@~#rJ7$`dS#*|F)pVg#~Ubnk15s4>k}-I zZbrBAvYnDy2(7#8oN`9`KYy5xdIo!>EUn*oW6Cq2ncZ*oGSy>pEcn& zUXx0tIOi+uD0tgglLw!((jIR-a%%2OHACBYb%KY3g$vqvw>nN)liCi$*-2k-hvQM1 zXNMbRhg;4th8xt*>y0kc*0%Hd2R8?e*WRlVEDW0Z?Y#=l4%4f>SH`Y`_Fgw!TW4XA8Xn+-%!APAT_xx>)wX@WygL$x{ zSG*1%ZPOlMk6Br~9A$Bn1R7Pvh|(6(K5hqWN}%Z-ga%mdp>)+!v`-)oZkKi||mGsrvBF!2zUqUg=$eOP3Nn+JdI&|Lla;~TA_ zH=bP7qK4S*Qv;u)@i(6FEPcY?B<_fKNx?skyQoq2S~OM=|CZ8hMwWjU7$|0Gn|v*L zb;{S(d?Oi0(sCIESb^0tUmEfLmwC#;ouSFDS(TrXEuY2s%N%tUM6r zZ5an123M1Bf$PZJ-x|o%-~xF*+)C!1I*Zjgp2ct%`Ej@xoL~YEqN|ngBs@@F0o!-B z$nz{bM)7>~weM^Z&*PMp2{;}W#m$g6!FS5m<{xA<~Dv!MsZHE zswM_52XAA%cFeCXv3(tjctizwAICh*#@j=_Y~v*h^Esxw76IEI!Wikd@iLc!w<++;fLi~@Z)j=_-Q#GepXI2L13){E#cQ??y;NY zKJdFTcg&CF5%6d7?eI={Cd>y#&fI+Xpu7p42L}gmz4Ryz-uMr|Ag(^QaH`QW>P%g9rBKnc`|tXcTu1!66`BdBvgm( zD^j>7JXCq=z`Rx9z${|5uSj9OueUA2zrxexKtgYi81@Uyv0HjY;l z0b3;#t^?aDnJ`Z{t2F>mGWNA7;@iXawJ6*hwr@q@A@DoOKN_}gMG-#^{z~x^;C(XB zABn>X%tqix`EK~6{3!gpycFgQF_+*e*vGETX66xPkAE5mNPt!CXzt4_HlXoeppsl1 z=A8#BJ11+lG*1TX@!D4Az?aKhrQPLBxUb9+v4DZKrcr*Je47dL&}Bgx_H6%$ayqE(vdx%ffHVoQV(R zD)2TrkLUld6yU_ZlN-bPWfTerzLz-@ycy<9w1Q8`ZQwIIq+Z&%O$>{ z@Z{s(M9`za*MY#uRiPb2-az7LeHU6Zh`6hocEN|%bb1=b#ptL_N(wS$t>c} zqvkMvg4u0N{%T~MYwlW&*cZ%>)!vj~>2{{u8pKXDPpt9SL2}mfNNi(n;LmKc{&}x< z)sdWG{oz2heu+yuKIDy`8HtT{YTA!Awr-zxcRgEio9!9R{4!`uaZo?%k_rrdl!>*u8UKQ+Zq zn$NRQy3bZZ!#=CSM0~y+a|2P7?tH^>wd5(q=Nr_7{~vgU|AE>5Ic9$}$+Dbq0}LAvZ|s42 zi=5+pY$gMd`Pl!8%3s*)X!v6;XE?^La>HAf@H*{A_;GPjiOc|uD7<4K;-fLX9SHBH zW076v{x7_;2|h2O^atDB@f(0aza(~q6u&BBLjFsbLaM(HF=_s3tiE*rLF5VhY-u>+ zv&x(6v)N=+=-rQmkURcU#Q6j5n!eSJv=0_7=r4y;0v_MWhukHHndwH1JJ8r?3&||= z`4Mp$ELrU4hMaURJr~{t$-2 z8!=vLOUJvXnx)8|&BkC+b9NgdtHifqhTNj!oPJUBC34i@$qJw0?M04)_)3nWs62;j z(Jd;^S#OGcg{SXZOl|yRROD#?Ey=mZ^!*ACxTPcJwy(T~&JX5A2D3f0<0~&w?MEzM z$ZM2?p9ptouc#=8<$+$ybo?9c7&TLOc+I*jV3dkwx?=L4ie*@|>J8vVj6^zOhP`B| zjQyI@8_ZGrkq+D@Lui!BtT7jMc$Hh)@|uaM+%+P6bMC9)#g=cV;H3`se!r;TCFCJ* zdTop(_^lbZ)2mzUCQKUL0T08Li;O|m=$UhvjXhKx%N@3Cgxz3A|Dv-aT)WZpY#qEpd<+gR^Z?A|%Zal(fdaYlv8Kn@ zSnb2i$gjO>?Mq?4(D&~;FQ?NfemAF6#}3mu zetD|-@@ubNVh?5!o&R!zSI4(v&qe3I3|tdu^$>La%SrT$U(We0a9GAWFkX@~+0+IG z#TRpWEo`W=f50gSo~Fvt&I(gjBoyLU`+fQ&Ph9_p!47OX0EIDc65{CJvh1! zHZVEhRm+l=^6%+|;0iwra4%{bM*HZVJW zISyoW{>zlPaW-Ag&VPAcnIGT4(G74KBJ#mFtKy^c-$mHY565fZUv&PT@GtMybIGq=f7N(2)opYF5{#Fk@8%sp#5HUxvf|X=gSa}-b(!Rv^vs9;eInW+c%Hw_dW^MN;N|cpijti=FS7& zgU(Ttbz1$*pC+JBYH|qvrR6SmdLo+9CUM%^|P5 z^QBpN$lKhTA3Hjg)7jEo0hTOx8^+~SN#`hgFwA~3oa*U2krJa8C;|>*ZjX4a2G+uH z<4k25yT@o=8mqv$jl1rj3?AcNa640nVL6)PMP0y|&eTsB@=g3d-*26w`J4@Pr$=$LG zkIhu96x8wIQR{T&0jh`<=85B8g9PhjJNDIz;(3@ly|~5{F%iT39}#Sn!0F8$QaOfj zVft{pD}v_>r!N;r5qwkSTy;srV_0(M>K;MU?H8}c@JEn8Ub|VvqWozg7j7TIl>M1& zu_*0WL(A#M)t4M&hXt6;*yZ+Y++i6e6zgnHPaVEN##k?B8wTyrn0b2JQ$~kwQM2n8 zum0u5F^brm&X*W}7Wb$?CsvscmvW9VhF26@njYwMBjzx+(YeTI`;Xn~X?EjZKGgI+ z;dSc#KF%U4RUeU4Gzd0Ib9L!T z|IRFH`9|MyYW*A2@dK{UzMP-d|3-HD9jh<)hSU4sn2t}HrKh|qty%9i)}OiO{hQXA zpJwT0!!pL&70{^zVxwO>>;KKywO{1*U%iSY(jUTM7YywGpTDXq#M^`OrT^!*1w$+P zw}nDC;E|&nS%I)En$l#1GXTvs4%?_G8Gl37r;5n z(;Q~KbdIz=+=RydkPY)9Lsy2|%iO20kZZ!MoWwkh;H&J1Q3!CP1C-DK9x8W(N65Wl z)~eyaH^LL;`7mFuGyVbiE_pG0zsy27BR>Hzl|M@$uv~$iuq{l-h<3xaFdg0t+ro7C z0K7^0kHCD(!wLTcvrYwl65cBR4zof4OYmyId=o@p2LA*n7^sK9DJ5`KpOsObk^Hy` zd0N9kY&|cjhv6G&?6BO?Y(YHC;>BAOUl!&SH!~CKpF0j;w{n1r zFpF8}T=*Wj3e38RjIRZ=))mc?y~kwkH}<0^#P@)0p*)V1(y%R*hq*J_LV1`w?R-b^9k=LcR!>lY=;hZK*u+xNvpFXThx0$zjXE4dn_jtEVwO7w#al zP@WIfjA!LPoZIpG0D;R8xK0U;;TvSufE*?Bqqtk-q42HpF!*+vB~G*Co8Wuou`tUr zIIVH;BKcOnK6p%lnMim_o(Hd#7sAiWtQz@>`~+Mm^HZNU_~ElPMEwtcN4{v~*;;$Me(Z^||C2FxN?dNaIF-U1(%d1_|S z6Z7!Yd{XB7&)?yg^ZM|mH7Qs(LSclj`U zUgoKpl`^<6Kf>wq2{>wdAw|Nk2xKYYG+ajJIlH2K92!SUZn<}*=qISp}#<7&Ry1BBv(b~~@_q1X`C<5Fnft>=c@_M=yp|1N zeXhV}Bz!Hu3-6J)!H4AU;G^) z$dILkFkD7<;fit|%$IW-;&GUaM0-PWRNQY<2 z5ttKUd>xn*py<-z8PLCkAoAhD=-~_&GIbxJ^3#96L}8& zxqJ`&waorn_sH|%L-GUgQF$T!i~KNrI%)JmiNIe&bkhS4@5j zzoq1l@mpSIVN?})H-2l&f8e)){1<);d~APC;2#89DItX7&knK+cafuTFPX2Oua>LB z17-Hz%5raxlf|ebxsWljJU}K6bkTJz%bSX1WIEDyFZ4ACiZ`TuY2+->oa; zaq#ofdD+ zrk|JjB?7Aub37wq+a)eM87`std-=&vIR#kmTTN!Q;mc%xN8C_e2{)D5q;hL{JIqFL zIkAIqPx%;pmCPEx17sFT4z`(TMn{yjmmWqq6N17n$0?wo6>N9c;VAh51$M zJIdb!wq4>P{z`be;s?SzEufe(?F$t=5TD36Al$~VKU zW!78oC{KdB%9G*VGM_E4k*C4e$8u)oSfu(0b;cH5G7v3cEL+N*9mH~bwv%1xGnGc;i9KNpC>ycov;lYbx}fZB=nL;z_!m_%n(1F8>o2J(!E}u3y+i^gl%8E$o~X9P4RpH zoh3gD&y`oh3o>~A#{e%NuviH%!As?}@N#(_{G$8@TqwT<+dg+Oo{wPL=PtY*wteox z2jHE`&)3^~3%Wu!<7O!nNdVxSm`UZY#LzcaUFz zZKuANp_kxZif57A)$+&iK$+iUUoZazk4!od_zi)ZmGC<}N&W+#CZB_6%0WE4-z~Gq zZNALXlZRw}I`pVq7JgE$2(OSU!E5;bkE^r>0&A6!58Dob5zpc`+aWOA5q?*BSd{Xy z+#UW*?giVruZC^s!0=%B2j#z>CYf1?Xv5M_9 z7z@CXvObDuNm)OcE%^+RSqe5xZV|!pf0F_%1sf-~g(u6bS3X^4iP)WTPk64}2Yx`l z3Vv8-DcIw30)ASa4?k;-?a!hV+siPPU@5##nO4EJmtn-e2-{wU;n(2zmFI2P_A-q4 zeX#9i7(N2qUWQ>7QtefKmNWfFW=YDigaYLdI3ZVr&&YM)b8-)ut+R87y&6uFuY-Mg z8qB7~m}e$jTAmA6kXiOqMNTY1pq2tF;Ck{_xUu{h+(KqqPCNNqxU>8{Ye0Uja00*{qh*f3G%w;#93+u(%! zIXoxnM1W;A^OUd?UMPPH8+jMJRNezGmswD;T4q7T%Q6cpUX$7R&L;U3{Eo~oD?Tcr z^Z#E6Y*zwHFm}i+!Pq6UTua*{G zjQ5u*$&bB;m<50PrOdjc(V}*Ll3#qKG-=diQL*1@I(rK9*p4StU2^8dSENLGQs$6j0b6HFb4>r0SdwY6y@-Fq!Ht+| z=(6E(uncB5HQ8O{9L!Q`^6N2PdQy|UMb^QjQ_~+{-ZS_oH9UjlYlGAXW6^R@7cNUU z#Al93;swk~D6L^7{J2*mCM|zAa$yj*9g)^Ff_Sec<{E8`F%Oy!I)P>M(GnLM35f2& zy+$C)%AJYmAWV8j^m`1)Jr=hJQP#D&J<5Kfr$t#A;5O96$K@j$#NtkgvYK~da&!R# zw?_FkbyD;_%;&@?uOAbldH8pHG#&ByBCIF!h6f$NSp86>KN|sw?nK_*fha4#e~8a7 zaEfRU?Si-}(FGV1ORxs>78$n@c`?kwOw4xldZf%ujj}F~6U{_Q;vIC;hP93orQmLi zDYA+!xJ5=F3f)NFhhbb^!6KVU^nQQ^jcl0@hTL4tQsm?J;pA6mk*&1rZo|kUU%UbP zE-Ru%cF@U>&LX?%m^+8V?t>GyO}g?Jd2kc*x(%vwxR966nj}$vr5=cGLuMzM4+f)r zACMB|4SFa#8ULn6Z^eSAMHgYg)1xbqCmg*J+aVGSa^9nVVI81dCe}gp3Z6hz#%IVA zijL35sH0!4u{pB4;GZ>S%5tI2lo~POQrBb>(aQZ<(ic0+ zc`fVi!tBKUqO^vL!i>R9!J-~ z1}0}#vzx^Rre^ZPR8)cIvP{d|%0M?8n4ZbnRd6Rmp))asEf{O57MQBD%g%zs z>I|jv0nILlJ4G4RYJ;l=9vL3XxSS_YXOG?C%JPKk>~(m?EqfT_boSZ!K)E|H24}tV zEq<~wqV*h4d7FDZGndaze`HgvVwB76tmk3}Dztv&XslY)=II|{=+#Y)B5M7VR1*Uo+zzWI%&WO}TY^Sf!A9;?`p7xFy5es+#?L~I!@ zZyML}FI*k%{~zMsJ4}iqZ1?V-olP^-(=*GmyX4L65|_LnVaZXlBuSEyoRKsrD2ONs zNGW1KRK$#d6-5Da79$t|L4v3lP*Fh7eb+p#^`7(nbv)Pg^#wb>r|PNDRn^ti6+z9| zl&QCsLH|s%$!dLaIKP5|Rq-6iNNKCqesqK@^=5LodhG{cQB!}76ZnZfnLy(o@W0Jn zlsJFI4yXz#;ilu*sgBdA={LW(^KYYF1$!-`a}DH%EHrF`py_Mb+4gLFX~8 zxWso^qRI<~bF!a@CDUr1*5zPievpZv zRhop6PTZ;t@;c;4KgjtfnVc^r6d-tx*5!}l10R>4ha69vxEw&)sy`NrpH{=4l3UgH z!Ek=88ccJU&vt@;HOvP>&e18FpdZ(pY0pWZuxARt0-5QM=w}@Mgp|*!+fLH9P6RR= zMOaNw?e>&8JLa2Sqp@x41kNGLy8ij@tu}+M^zFoN%x7S6SM}!>bd7Spe*rI^7A^ zvtL$sIY@_Me6PgQ;bH$agmo3`+n5VlJPyH_)48`*AH7 zQ`YyrL(458H!3sko1YaWSMgR4`uQcG+J(cFn~jHBJQ4?WB-k4mkHkS82^P=z4aeof zklVW{YJE7|AlF9W)3->|nnQ7m^sd=Rgj6dADJNYW4~I+Yv}JZ2(*Cu9ll+${ANEhj zs*XvNtMN9z*7n;~I~NU^Be->B9_!nyUnr>-t^QxaK>!uZr~ ze>n@`G%fpX9!dYFC$bFIS6@fM-EuC4PTbAqT4yTcf|+CS_zg?!3P0AUOQYeknveXW zwoI$NOZh%N$jcJ>;GKbtpwv^*aNV3LSYo4-lv+AT>Acx|FG(*^zO-h^Ar*IrQVISS zT!GT3<9HX9Mp^b_T6#jglNQd4vKofU^DRU~L$hSjgWM0+2ehex9GZxv`w9M`8n%xR-@;N^s#eP$pwovmbggfR8 zf@SVQ^y*OJX6P)3oS(xSJd(&q`33c5h45upaDYEryD=(*L|qFf`TgL|OHhdH8QuRe z?y?I`x^g(u?PK%frCT+4?fzv?g-VnLR(DS02(D)pwv2}S0JM5cQ|r^im)KL(d+Fgy zzM%p2ZF;zh?~*`ya>ekuP~Z>fBv?xp-}OiNg`Dszy8;bDI1DWHLghsX_>&8pWqIL| z6_US2ZR)dbQFk}0n01L&fAX}^S4^%yXV%!6v+9q(V$SFZSIit!AOGh}oI1_>9Wi#+ z^wNe+o7R`#x|8QkoUXRqpHlW8HeQUTrnLCKcV2#^S$LQGpI2UP>Kxvd;-6DGVz&DG zos``2$9sow2&;N0QmU5^9T9HWGxjL!5Y)81o-mkgYR~+6gE`%paQCx&<4 zSS1Ow9GT_ zWAI3W#~3`x;AsZWHF$x+968hek3nu->j_|vqIm(|Z1CL%bIPu#!!I(nM0u4P%!$pO z`HO1Lli|$RJBATI+&t^U2A?$eJA*ma<>~)!Fb^O1%sIT}F~9h1v?_UVNDCY)&fw_=&o+3W!7+nx=JZw1qgxD%%?96X@HT@VGWZFDcN)y@3(tQ}W%ZaHZ<=}k zoY(4Ea9*p&hYaSlR?qyj!R!(7%>Oc&JtLlZ)Zj`6R|Bh2yTa*}iVV~G1~*b`cA<=P zHjLP%;(5$zs2-0pc$~pg44z>yXQ6s-7a7b>8M}%uQhMeoI9^7SVR5^`_Zht1VC>WO zk4Kz&>Uqju9*^HN_NdC*|jV9qM_bSfLn z?j~){`!DeXa5IB1F}R((|fSI`_Ue^#bp!R|X7-~GJm8r;}mP5||E+8Es4;7bkeWAGq@hsF$Hw87&IX8)lV z*kXg18@$qBPW1HjHyV70!CQp&{j)>Tb5L&Z3kGv?r>FC_!TSw9VDRS#vy0Pn`;)<# z!x{U>;(}qpK2XnrYcMBndghf4W*@0%Ue{pum3ro!pXqUze;D`A37VcoKZ6Gv%&%-N52HWYBpHh8|rynpuL+t~nIHyM13!M7QFx54)s{HVcC7`)5i z=MCmAm@zw>!NzAc+Sw#bXfl+(GMEDjc1~R9Cxg!!e8FH2IC%PL23Pu*V|oUor^K0x z9yc<$g~6>2=4ge@Q2<dW27hTV2UffQelhq@ga0x(5#uDDeyYJ7lcBl3{~Crxp~1Bb zzQo`*26NQL&X3=5UxNo5Jj~!R22bE;y62d~JsvMH_*#RnH+ZeV8w}<+k>}5Y2JbNV z>3hs`#O<{KO{c$vYg4PI+72iv^3Y&G}^gLfMI zQk>(D=WB+=dj@}KFh}JucSm2}8T^aEzZ?9Q!9I-1d2TtH=W!Od0r7+yhDDyiwG6Ij zaH+v94Q_96XM;Ix=!G@RW8VK5PXJFac$&ep3|?UH5`(Wb_-QaA4^9^ocaBG7*8r;=j z4z${Z(j*%i7j#h?ZSZ)5ry4xdV2;qjBYmwjc#Xm94Zg$REtNa}>-Yh~;$eedF!)7- z-!S-{^3iXGW6805H~@^{US7YDF-L(HSFYO_JL2^Q8KWV*T5?z#9r(P)lF=p1>n1XK zDS7=tMkg9CuJ^UkJHu-r8C?Xtz9w@GC$BaL)Ehzgij0;vug)AxMEj1{YBCxQytp#W z>j&n7GOug43J1IJ(DKs5cql!*xI2#59t5Zn z_lR3**x7OB!V<3`lFj~EX=HGt2=8^u9WlHrtpU3g3*v59ky!+I|BoQqxxShg&o1B(f& z`?G@4NfO_m6yOMV>r6)M@nc;oj1;!|2_wO*!NR;ry;UoKsXJZo z;fge;d~mjK9dIKus#yBNtrN)fr;RZE=_pKp zx(m~vST7Oi5vMe!p?;=E6NKpz*Amd2g^^n-keQ4NgjpXf6=r?FT@R_l)LJcE0$wM~ zr{>l;$5;vAjP$qz7Os1RnRE{dw*qrNe0tguyi>Rfc(-tWuy@KDpL_0#Kz%;zp9qfz zeX|Ri@-tQ+F-rKD$KiqGsT<{@(xxT z1lC)uLT4ygZ?Ou#0_87$;u?f$;AY}@4!D)@_2BlxH-WnfZvyucei(e2@S|WJ_0EXy z0FM&h3Fh)Jn(qQn1IH-rhA>wwUH~r=-UsHiZR)%XUMc)4_$J}k!Q6I%I&Xrx?E?87 z@ZG}u!CX~E^AEv#i&gL^tpB;c0xdp;#V+B)V7=ce%#VY)+az^Pf!`AT5v;dfh529L zFT|V+QS^4JFi!^S?N-6ueEN*&a6#2?nvwn$AzTm(&f4ZeAqG|iP7&sg)7+C_&;>#_GP-&B`1KaXqihWjhLy!# zE2+cUZIm!y%@c(AC{7h-;pK`8>KA|)2($1m71s6tbt2S*#aiLUU~Y3s$4$Xogj<3+ zg`DOcz}&xq+!_3oFiZQh!b8A&g@=J(6&?x3dNJ(b1jnNwd>|GRz@G?D2A?9M=3%Ow z5oR-x`#sQ{6_MUn6`T#$+p2;K!QA74I=o}<@j&L|s<%}I*Jb@*Ni6EaqK0roaDi|s zxVCU>a1-IK;7f$NgWCxA2KObiQo%LI;NikPn2#aHpyY?Z?K0^AKh|{N0C<6L2)tC7 zWrEviQXh$DasLMH<<9F4gSQAXu&2l@G`L5{bHKfYS@H)67lB6#mw?A`{vbVV0)g}UxHTRlu~?Xaae5!k znL^hKv(T*(<|o4@;Tqt(h3kU12{#3E9|-zW3Vu?!1;V#>iNLoU*NjoAC-@cNv0yGA zq4{|5`@&a(4+!%~;U*H)nF>BCJO_MAcpmtS@KxYn!FmrrM&i6!EC=&jj~-nI=Dwcf zbzrvn$Q!_1yGK@F&J!fx4X!18AGm?=HgGfH$HA?HpJM&rUW8|1;caia2YjiR?*oq{ zBh8sL~swVHA3%^_?Qi>_eqYQeyUgE`@l zJQB=}L&>bH9u*z~eoB~G!?}mlnF!u1%&d7;cp8{<5~(v2e1OXZD9ncNmGE3Jx5S`1 zv*V;Nv*SD&x68+jpW$StM6xh)!E%K86jTt-2Xjd_^=pD_2*;Qd1tK&8*A`|?*Fd-v zn0s!}@nzsHWK%u0#trK!(+y+ybo_;%qk@D||?;D>}egLeosi_3+3u>OBugg&r%Nq7MGHQ_2LX6Me^>`pBy)hXJ%k!!!K5x0?hkGx zJOtcacqq7y@Ca~6;mP2x!mI;^l551vir!ch%nD6!EDDZ*V|r&%2abj_ScN`WA+_c4g zIjB?&&Jt!`)+Qqv7+3>g2G&fNCBKz01Ln3I)Mv@pn~Z`Pu-;@8oPqGIexih;5Z_D` zC5Qn`r8zhiJV%(GE)wSJb(t`edX+E(;6z5cW%h9*BbiUoJ;Hn|-Vct^q7H;zv_PgZ zfIY&@r&omO(Obe$v)&hG3LOw;UGk+cAIlTMEbV$fP&jM><`O)HRmS@Nq6pn#p*IAD z#V9bRHd5(Ia3$gC;2OdU!3Dxgz{SE#!7a(SMb?t-gjvqI2s0&mYDW4a8?63fQ4>5w zxGs34Fz;-fa8vMPVLq<&gu8*42=@YCBRmj%3%M}<$mu;j!HmQfe*e>gdH=9*Hn?1v zk<U})Ht-yL8PjDNs-p3Q%0enjQFW-40+_JKL zSp8zV*!kod)xqcX^i97;ZNM)!L^T+g2$)lvT3sv4!hZP^WKc?xxJP#@vfKB@9jVY^n%;?FVl5`KKWZ&c|49p2~w zOn0j%`EyPs1o88*>W3d+{u*^}5IRq(Uue2lwGX-du+xhQxp|3Y&!E~+dw@8E{AkFn z=_@&^LXKN#k5r8uh%Jw+p%4?ZkiS2u6^@(dyZ2l5FpapW$#QjoMysym@!)BnC&k~2 zNNs&>hVf4sszun1*dM9xVYg<&BFk2D!frv6rzYXaArjc?r6iGdiR{zC$>F+UA&Po; zm=6)%B>%OD931jAha~?x2+1_@HD@3RJ&3=d&Xn`SxgBpTA(=PiTcACk0L11mi}CPc z^B3*`nbHd3;8^i*VUs!sO8&-=0O8^&Jn}bx9~eksB*Hdzu=fl5%7Xr+Ci9>j;(=yX z=uLPS3tfPVB_Wb1Ucv0xMA4#_`#NAF8gqA_+s?dk9o*x>8sLcy?g2~)aC)myj z@tmO9A@2JyE3_7V&J6LZbw;Q)Y_azX4@XHFjzVOm@Og_QKYIoqrE(YQ-V4yec*Tn*Wt-&$@|%!4yDdhb2~T=1kZ zd?9Z3oM44dK-ZtTc{!DMC>1>E19!IN%T*;Eor0n!aML5zZ|lRxcvq>(_Px-y(yoV1 zYRLW->`!}KE$--K4dq5Tso7dNlGYJ^rxx1Z<0oBj$B|mw&V{3_w2u*m)TTE3k*lU< z!MD`*Hq$3Nja>z)9qk)olb^;NvQxY2Jqk+FW~-k%I+coem{sa!_8m|tO=GlE2ii=( z)@k#Y1cSBEChcm~u#;0*tsT@-XWOIk(;p><7xFvCueC-KhwbVwT8zwSN>-)8DPLW&w3nx!0b-X;{GdO>Y(5=EizMR-7D^v zU9(wD)V|J6#)NmlFxRS;KcKs53HAuB5Er!P6LEjA;4N8IxXp&$BEi1uAFUP%I`OM= zq{JSW#G=lN!P}c(fLgUCs!bPcXER-0-o;r^nLXq73%1=G8I#4g7zB2HXWgvMba4u! z`o7-LIm#fbO;?3oog({Y)vK#>e;WH@?Hw?;a8C%Jb|;nG%^6bjUM9~u_PXddn!TBw zE7i1L=d@qkTM2%x?B+DJ*Qq_-oQgHHZDXB+cHe3`4IAq;JPY0Wm#AO5InA4lM>Opj zKHd%+kM`yt;BnmP3~}0E{5+~K^_L=6_1{fYS9Etuo6TTcH~LtiPS#HOwHlApM(s3& zB&)`L7HH9RDdor2{_ak7S7+mvc;3**i`|{d*ug8Shx2NVj(EDhtp#uv_aeil7jII2 zT>ag{sh-1PY-j0WJdyeE0_-5Oh;F5Pg=&4NQ#IO}8Mi|Fb`i!6*Q-gFI@9e=>WfRA zta>`EGtA=FNQtJ}&og{7Mo`pVo}rK29jECPDz~RIGV>0mS?>e{r@J)l&D=x~#o)G{ zI2L_}datKb(Qcx?>FJbYMp>S|@v#+hgHEFnXox|zPA}&HY$oz?FQ-X2?t_ax5MZsx z0`1M~pcBu6eY)`3eQWUfg$|!zyPs7rpWYr$VA8YwVQ>B|?rs0x&ZUKGm@Y|-oo*4^ zo2#H168A63&pWmIT8+O^ul06{)Aa4HOQLC*g{$#gYlZRO;pQ1 zIKO>~TGYp>6t~@;v>LYT7K82meVmfo7kMKSlUS@7IzQL!&GXQJuBN@4s2v2*(1ruM zuakq8MP6U0lXE8m!zO4P7HY9c&4o?fXAq&_u#0dTVce`%965FS@#)Kqh+%{U!fO4i zU%lMdsqOpRRzLN1(yKH@+Cb2c^kMi4(LsO)}DCA*Sp(GRa<9%m4|oO;|JIYit#eZLiJ)?GDsCqUvrJBkf?YRH7xwlL2FuNU(|@NL2+y#KpI;H!yk9(u%r$&J=H z@Qy;Q4@@NUMqdJ)$v3)ci62ghi^k1FnFK&}J+ zS-1pzPMF`doH#+9Qm~#k0d5D@^CrMOzvQ$3oa+*)r7BW!EGFkkLR z^*jjpNXMsWA$&%dj(-)VV?Fl)I&>U@BaZaaoK0{t&G}hE=3Q17&I0EPvrD6vaEv8Z z&vAeQ7M!MHkpt$OCwi0*W-F4+6zU>e1nw!!GQ=Ivs8a`gxo~~(6~c|cLW{X7!FxwdPs1tapa2K$i2Lbaz;MHP22E0~yB6uqq&m?bbyKpi1F=0mNY2i}V zKhKH4PPKi)ZNRSyw*|i=+#dX)FeCS=a98jl;qKsXgnNTe3-<%-c@T))0PycLk3kp= z;i4!F1t*{q;vI|t^IQt@OfbLH$g{x}h3A2*2ww%x5ncqYDZB(+SNLkMo;Lx1mV;w@ z;sk^h5cI?e@bzFlaRPh;SWlb)-w5s_9^C=vyf#K?J9wDz!{E`vkAc|^q|TGzX~Mg} zbK)G+gYXN*VjnE77Jdz^CsDxhe(-uR{}_Cm@aN#WgpYvl6Fv@pNcbdphwy1|x$yVk z=e>ClKSFp(EY5>@{sd#qL3KTG0?!q4$U01Oa4Pt?Fzbfzgz;orKMO<6Iw#CW@q#c5 zrkezR@WSIKxK+Uuz=dSI{TLXx_an1Lx{>QQBTM`2v{_L#jCWyJD#zATetxHfiT}%2ZdST91^Yv{zjNLa9X$} z_-ElxV0O*W|K4C8QIVF3^dA7hpB%Rs4h{&90Y`)9eaPd4j+J(mGO9xU355>thfa2Z$;nCDfHYlC@41-UMGurLeV2;oxjXkotP=92L^ z;76YbO6F5@4b5W^P|sLAqk^>kYz;++D)O;QNK^gC7=Vz}yOn{VKfXG>RLVhEE>!~$ z!t|ut#-Fq5cmB**oj=9TTBZ1NP@TY!uTEc8`Walju2y`87@Sjoe+J9fRj<#TdA{^3 z)$5F|Yrn zQ`7!M4dl=3D#o8>Y8!vKuwW`ZkPSL+T%X<%H+0E4Z=QE1o zd`+{I8o@zGIGY8vnys!*%&angiH#)pCr+F?8=bRBH$y@P@kuD4XL&276ucDplgtc5 z&oW;==vjV*cK&4ME*$)ZpG5yP_?t9G+xvOgA3BNodP1jfrgC#L^X#A+QJS(Y`5CDA ztS8joq|A!{;iD5@uU%D}oW;eQ$adzbGQ z*!;7VKO5N?mAMr^KJI@1zi;K=6@>#?F<+}CR;2XLWAr?;!Jo)_7thbPkn{k}b&!*~ z``6OgPWcnYN&Y(^B-13x{{a4`_%_pRVl%Lh0}u(x%YYb>8PA^-|6u;uzRO^hnxTf; zPN6+YEyGVS-vsu=Rrrev)EZ4`^ONu_SPWfzqJlv?{13KQAK2(v?yb(-PMhMUv|QR1 z7Q9;GzrDE$%!2p8DJn=9MCi#jHQMLYuYEbyS2Di3BY`gjw#l54_+zi8`3`8{O8r|s z?{l&u+MBKP<}3QTO?~5YS`XLWJVf;v-US>5*KgmrH;>Vr!wR@6)L?*|iJ<2?*_*lL ze()p4?KR#i`+b97Ymslu-za{ru1;`PV;mvucQP_}!qT5Z%O~J8gE7IR0-w6X?^LtH z<%9iBX-e{Ku<}_0)fE#m{@bpHsIPkcm!|^z7G6>wbDWqjJbBvG@!9$hD{l3d>(o%= zBF>Wk;{8u)!J2B+5$9&LDC!JIoHcR5*b(JlMV&~c*cc|Feh=U?jEt5kFOEfd-Ep;< z87y76){@y|!gZd^7dftJ(Dyp9I9llOLSdvd)-=b>nXT){NoWGm-&^B?WUN4(1=V!^xQ z00+%W!7GJ%$G4Lu@3$DdO&CA%byd(|q@NUXMq00{f;l6-N6ZoFnDvSXj5WuM831Gb zzA$5bK$x-SnYYxz_nUQ8xCDGkn2|ms%zEt?;nv_JltRv@WC7%8R9XP(k8ckvO)SuO zwX%e>!PSL}!1=HN@4D1qcJ7Gqmi*Q?TPj4wwZwURxqAyr4 zvV!Bwz+=RG3|KF+g87wTy~qkY8LSssfoFghiT-TxHNsbcR|+oy-y|Hp8o~waJ!m zy;YsTkFRkZRaWZMvhPrnO3@zfuhx{JJ=_D$`zh){-rR}!5-O^a z=6JPDPy?I8s!Bz*l;Uc&4I)h6!%yNlWY}`u07EOiMhhoX^>y9<*#Ju@ZJZr{`|ppp zjl1K{>>uzaN7}~M;6eVkZF~zPyqZ6U0vcr7DE;$N=L3P4`rrB|c?a$M$+?v5lvDWm zpAE7t+O0oDd+4hYZ;-kF?;B)n8?SGFdY8H}KeK}RF3oN9pWhGvqmf)bD&4);=PQY~ zk4yiL_VKg2ef)=R9|z;@;|{ug>`PBn)3e+{Ux#?(_|YsgZ>ASz`kFLWH}A+O_GRB+ z9;)OvOZ8PtRvmNQ(o}vXBKcoRQrG3W^+q?OXs7UNHp$QTck(Ly@$qxpUv~(R2X!aL zC%4o@jgS~`KGnd>BQdET6@Ev-PShQHX2pkRc4-BEygxTQuFU% z6-dOJ5N8=B#|}-T>C{eKi?hDmFp0aLNCDUPVrxwcvWhE)RRX)0%DxX3GVN+&t$o+@mwAKAfkdKCfC7xg&f@0d;$k+qN>d<}GsjOht54 zmqNkq$9u6-gQ@DbBDWJ_)T-FctW*de+{>SUO?v8j@W$Q3w7MOcCGSSb=@&GwAR$aZ8lPS>S6ou7ge;r zyV|}+-C5tg%^s*qOWfx0c4mp&&AvfBU*e9no0R7@aMSFx&WNL(l*mP1VZPByYpKo+ z5&BSde?v^48?N4I=ypYiMrI?oz0V1#p^eSjXq!UVo+rGs0@s7ulTNmWIiwzAG$6c3W-URm~c^wS4a+m5*-hMxx1`pyac* z{P&UT|9^K7pV7(9bJdrZxqtoF3bp+60q*%rV~-+{y#_YhFYQN zgrx;($*VuhB3nlN`ZKL~zU%s&G^ zA^as9*w2V?3>JK6Ft8uNuM1xQe;~|0y@SF@;ID+)o;fDWb1}XZP6z)eoCW?(n5Vg2 z(2Vr21|b=xodM^9!@`B&bm3yK9xjFs*Lv#VVsK-y9xeuR9j6{G26qMP(PHo*upTW2 z&jjnyV(@Hm7v0eS;VKCIB(T+BPQ}!D4<0GZ*_Gpjp8#WsSnKQt>k(r3^E!B;m>&dt z{(l8tM{_(fSx8Y%HzCLP{duPd{QkUGI2-(+a31(^;X?3E;bQP^VYWl{s4zU`)AX*G zHwS+t%oIOPXb<7ISab$|FU-%5UxWvMxrqp4JsAAA@K7+@MKm7+4hl~M z^O>hPKQ$@|^YbEGcsc7IzWAubN3M?W&ETfOe6-4hH-S0rm->91dJEqT9wK}{c#QCa z;46h60Z$ix608SyWAJDvgloj&8SqNs=fF1!KM&p@ycfJln2+M!!Y_mG7k&-=i0~WW zr-a`HKO?*!9D9obs@Xaa-WO&*9T4sU=HZIe;b+QG;hx}A!mRbq2=@p7Dm)N;UYOa$ z&Kvr3B{(_GF`YM#SS)~r9`S|JQg9_PXV$RYOvkr_3xw|k*B0IiZXo;=Sg)FgK2uk( zng_Fj(yQjdN5DNfWsshpf}n?j!Dqmii$@p1R|xYoQO-Z5eg=4oFe{c>!Z~0)LJXan zU_C+%t_#*9#NaZp9w7#|XP=WEB8JcrtcQrfT>E{Gc-ja2uFjIiTF!Qc>B4+VqydetMr;0Rcc1cUjePDjN< zf6~F#g;~ku3$tFSC7cf~5iUmf7AHW`aXnbH6fOa`6J`aXM}MKy8r(z7%fKVZXn-&< zJ^BkS08bM0`e4rLrcOifJmDtb#hQ`-r4W{h1p~NVm?eLWFe{&1gvWw!7rqjFpYUuj zTjUJvD)7_7i@?tbUk%zq zGy5(G!&}RT`h_|UI7OJ*$LX^)k7Yw(ZAKv%tXH^G8<$?;F3fW5K4 zc$IKd@LFM(n~lP(&^8OV1>YmwmG%DvBJ_sElfwPL&kD1TYp?Ji@T12(%gPtR%7j31(TOeZEvnyvM{pLa)g=3dZ|3zBG;@;F{f9x z$jo3|JQc^|ro!~Ug)qxa>{1G7dLw+RpD?qWC*jbXo{kWPXpI%F1m;ON)X4_xmGWRd z-+HAym|3|(bZUcd7On?YagOPB_#I-=2o_s}nP*RtQNu8D&kD0t?-gc5UlnFW^tLby z-3P)|!Tj{5KZW40gc+f)^;&;eFh*SZ&iPAB7QONvj5^2qTg;iNtf=(dBycr@^Mz5P z##g(eDx`jim@~DS3TK1aAwqxh6CrdAl$Sl>ZgO$b+vofA0<_vM;^%R-kUt~UTl_hx zs=S1sy-MQ!>eezZKxj`ko=FY=7{leEEol|e|XR@mDI>wy4sm`xs`C5c>YjGV}y|rfQie?NyE5 zaBDU>jz_UEvsNx!e%bkJs`uU$*XsoP4tjx`uWC2-@EdMn(xP7Y4vVjFL(-go!!7=2 zoOS*5qKw35&6+i7s_G|Z7L`wX(`}O((_coYWy z#(RM9rwfJVM}fip$Pz4kqYj2;T|~$CnR?-CHzW2q0<)Yq@yB*}l&;U&3f%;U9~6G4 zB1{sUZ2V1fy5o+Lo#lvgio>tEREOjE0el}NH{lV2&LBkAa*7aY%((zw;!K2Bi}_Gu zJu;_oEOcJL-vv$q%&&6(pyqt15&WI!@Ra7c&MxpAXBRAHJ3K0BmU9GuXF413cZO2} zb1b0?LN$3fqa60949-NRI6Mj($EUNi0>`Jng#2viyn{Q-cMie^cg1xZDbslzI^5jc ziEj46X6d9vhl>|&=TB(i_;h}IV-Ui}mPE<}xzJ1HXK^Iu*%!cpQ*e{An`i~Lz+Fmw znO=bJ@RZlL!#Ks|ah)k|kplsK8>H;N6C4UWs#+a$GwU=%>{Gs^k&80Xhaqr3jFgmb zI3yGg;e>>smL7A{V=p36Fm?@hw!?;p&tWy6;H-g(->HGWi4Ir3CpqbeUa~{KQ=B8X z<5Y+5=YTT?j6T-Muud&yFrn>;m_M+2IPT41TfuVJpK2pU9KMMp!MaDlZ(oC;t44X( zfn=M?R`ezO4TS7uB&9z(Pu2g%%_`!ja3EW2N22U%2o&1eV3r>J0BV8S_7@PcqSMs0 zZ`?{v=hM56c4vf2WvT*Ctinf834PZ>qu*ZvBF%5W+LTpFcse2A)7t1<&$Ut-urn7H5B8ky-%NFw-UY z+rtPx6zCgJwB?8eqhO=SL!^t4Djm!(fJvRd)1t6lxK2 zqHB;&!AxzFo5u6ogIPA8=KSa;)$pWS*p409!Rp$qBzgiN2XpO7@U(IC4&-96ru`JO zJEZaa_F%D2yH3&F47!eX( zOuhEnyV=q2nF<}WFgLoIHl4IEKl(1C&_&sa_=TEOw`F}2`ZcMzt!eE6-KZ_ifezIC%N zXCo_k-Ho`x)ISk;O7Je;N?;X(+oInBfdt;wRx(d-=R=;#j?MhwA(q8>2|r?#aLp{? zvrc;@{3t!uCH(QzZmy$C_|fg~5hYw5I_>5cu~#YhE{!slLU$p2&YT7b$G7t!w@BzB z^CfUf)&9=S&E=yI3g{aPL`P7F+B^%7$G6jI>K|&_cW&lTHZz?T^wk$wf!lB{(YFz} zmF6wUDFL?poL1Bcx$~xuv7FZAaDcO4oi^l1ARm4^Wn>=T{uV;WdQN@)om({iC#0Nx zgk{106iQV%_1HebH%gT(cwir)qbi(!FZc>gzou^0>7#Mx>(@NXuF4pAX@AW+sfyk? z_9(F$4to8DURQro&G_D}YJaY7`rfTq%A-~7-pN$S;mzB9lX>$wU*it#e#zZXk>{*t zi2aipI%3Q_t&9y2zIPkc)@S3c^3%2bGE^(+Xsq%x9@#q>ySQaeVuI@ZgPZ9~O;!_s za2rLo^Cs`4yG!xEaD{sK2aE^pP@n$bu8AFBJSrzMwrs$hqVJpk!pBrSrZsFB1sjz> zJsm`49W(ZO*1MW17MCxjT#r$xqV=azhG^MUwLaS^^?EU+xXf8G^;j0{YE^Y|d;x>{ z%tTSR0J&sDg7W|9-r*aTsJ8y-7Uk*)w9+14h3Lj35SKSmu9u>||IzIp*2%fu&*aQb zOepX46P_AhQlgstvwNcto}T~N#Z)oB8u^Pmv%1c}>*CKHvZ2*D#%iU_^sq{9P(S|S z)`)InCSR|sk&kfGCBLZbvoP+@thzy0Pw(JnOLnNZgXx{hC!pLJVAHy|YYOKr~Tv*bqQ`_-+P!w1=HTkPu;R9k;@TOwBv|K`?c$9)QL#5(hQI`XoFYa#4*TIN}5C0EhD zeFA-dfin00vpZ<{eaP()xwgN%n|&bh7Nd#7{m)%Kn0^>c1h4D$Vb z&TZ{$6i{vcaG%Hc>BT?X#r8$D@K3j(51(3lw=NA6@%ub@WxikS=ac8*wjQF) zcdW<Zd<-sjYtAz13GWRXuYa1Eo9E-{;+`K9u-s7u?tEBkG$A zZV~g^z3Apv)dk_nL>7eHJiZt0&k}jd=v~^VF1_duuFkg?4*q5tuZd|{d1GhJM17!- z#m<2*FJE-0RM9bwCNid*pkuXp0Ds=Xw`57IyvJYe!#1kqKmK;>`zj^gQY&(!z2lZ= zB1^DWKr=hip2cmE9jTXd4{x`73Nxe_&sm8MxJL>D?hLur4)vBD`Nn=r?eIl1D)&Kl zggV)5UUcb)gba1oFOaUS6Vwr3q(Uaoc1+3Dg?4-n+FH7_c3Y)_36aXaP@<}t5NXll zRk%)x=#u;ul~}i>^Y+u(2DZ8lgU5k7J~r*n(w=d%x^nNUNLw1F=ECrO6ho`)Y4u1# zq{!Z*K1ql)v`?v2f25cFo*L|r9p568Ez&wAj$PgLCTQsBn& z5&4l-(f>TWd8$byAvJzV@?V217M71_6WOyg#%X#s(umic%pN22&19{>Qibaj zIU~-s5U96|_Z>1TXI$n%gxQFIw~V(dnFBbu^bvjR>%g@aH_4%oZ1AhXHNkJ&2%i?k z5ZF>D*9LQ>h+G2ZND-O+YR802!JIEl^A_MAh0DNt{vpiUfmwr7rxVzR)FpQT>$!;F z?!5o7SX>TH7oGsFEIbLEEj$HWD9i>%9pU-lhQil@ONDO&w-&wy+(B4@yMbej5xNts z-eR#C>@8@%3(Rd#sIvt;O85!z1mRb~Q-wbT&ldh1yg>LEc&YFSFdKIC^INuqSBvl$ zEY=Bg_;RB#x1{B}n}dJM{>Oy#!A}b_FP{^x1KuZG5B!=iv+^BbW*_@9^w#DrA$%$p zt-*(cJA%IvW}clE?hWRoAV%mi@bAJL+_@-xB{%_33UwBOQ-xQ7+4(^8)!>SHJ|Tp4 z5UPm9c5sgHlVH698ag|{b;W!axRLN~us1aRF4!9y{{+k}Wf<5&aCc#54OiyVJoXiY zfg&)Uh6y8YtSf|n1hZR<&o3VN_{wN7+{af&gIOh9CpvueR|_-C)(KYz-zr=HzB2*& zPfu$?xJN9QPfwHayhp(Ncq3NS%-K2cvG7>%r^0-iz9S=+=wys< z5D(5_{qu)d6vN_g;f7#t!9fQs9}!_b#~H$1!BvHOfpdlXfQy7@gX;;;1veJH3fzLs zLw1)!C=-ioz@3Dz1@{o~9tz$dd~!-A3@oqI5g>bK(2IM}!{$pA_cPaYlG2_*da)z~_aZ1@i^R6xs()7JeD*=q0_d zcoRapSnyG)EPNE4E&L6*rtk@{UL_5;XTgod{C98*;UpW$EnFSUt}sTT0Ng{k7KbML ziNG;YJ!TH267Usb-Vi)NxCvOVjE2r7V7)RL+#0OM&%qojy;}79gI5R-0P|Cskr)Q% zBIy`~Nf7iZX)q^Y=vC5S&cV>Dq`{mht5->b*^{Yvmj`pr&GX_Z+Y)+VG|c&N^M;tS z&+|Rum%$$kzilJ`KNn#?ERG1D0)Hp`2lyA^^WZ;({{sIloP=sOF)4m$sokU8`lf%T9%n9FqZ zkU5yIAn*^umxIp=4+HM?dO8!~!~9n88x53z&aWBsqk*dcrb3q8gTJ^|nNPdP-n?TXK>gBXI;g zMEDqZr0^;5IN=|_lZClG{!C#$>Uu?X3?8+EpqFHW`Ml~S+2Da-y(Akv7`#>-4+U=& z9uD3td<9sq$cFxM@B?Cg8~9P-+rdu>-wBTCE$HFkAqacLg3EYb73KtbJq{0@onU_Y z^X_+pKNH>uJ}mqO_?Ylp;NQt;5WtCbQJAmtgcNNav$7$i#sxjz?g|&cyrOVTaJFzQ zupWnpr***%#Jnk(LkkRy1(x$H$?d@Hg$IKkH}4%XxF;Hu#LbPKKyW*;s&2du~5!A#w+VIHFelk}u0 zwFmzoJPLePcr5r&;YnZ%%{IE71xym259T-%&94DhBV*8*kMQ;~Ij#yiSD}mXSNw;~d{~r;d zF)W@GW)kfZX7hQEFrNVT~T;bW^rsT|cSgnLvo?;zD$b-;b zn2%o{;Ue%LVMao4QxA{WZWtrxCEzQCn}GH9_0VYpo+swrz)OUCfUk{n%wiJVAQo&C z+$=mEtb~_>Ia?*-QrW+hW9%sXo>{4}_OFz=E*zYOd}aBpEgO#{F&TD$?_3bD8d z)_dQ>oYNch*7slsthc@gyI{TbJva@#RNPhs>#gr$&PkxF#XJYRPMC)Q-71WaoS1c| z2#sLzfN)dr6T+q7UBYZB?G^3}eoL4mFCPo{1s@U~0zM@?8LYRvN91OJ^>+7JNdE;8 zE{YOA#}ZI6GgbIygZTrRp9QoAdjh zf&B@gjqpWqN8!K0-G$kK*he@KJW!aOiNl2X5i(ks{ecsO`4KWr_!6+*Dj(s-+Co?; z7VI3nTDTKVn>jlw;_>xDUY<2K=e;Jbu}fw^x!Z;XqU9~Qm|{Dd&qr9b1a{)ZC3 zNL~<&E#Q}h?+3pr{1AA*@J{e2!odWzYJ|h!uZ1Jvlfv9v@CV^a;IqP=5|ICYiqI7n zoYl(+v8yaexFJJgd>%$r;O#N}fY=%!3X23Irv%&L)OIiOf5upWmxiA|S ztAzW5*9s2;Zx9{=-bO}9+2BWn`TBlRn6K|$!hB8d5pD>61stPezO&yFi`L*zggbzb z2=mo^T6iq@S7E-U{}#RyoQR~MpL}Trh3^JOh3^4#3t;Npi}0;#B76ahJmKHK#llrl z1J)O=0d6A9m&zr=HNkC#`I+5WnD3EGg*$-z3A2Z6ux5Ob^5rqC{LE*O+bd#r$qheZ zP;bBboIg*g+CSmvm|Do6+3GF+tW;Hg#?Lcq5`Vr^&++H13jTth&FXUgl&XjD0G_%)uwZih;JSEr;MnR7*`Pu>rM6Hxkyc%263K0qg37>_}Qzv z-ElT!C0zv1Kq{3Q8TBbLd00OQNmQ+#t6NT-=q*T|z6{E6bo1wQR0&o~6CL(e+s+kmikYKr)pJSF>Xo_I zcJl6iFvBEB*vmltH7zwFNjINc^Pgozmt8&DW!wqoy(3C9Om#D*lEBU zaq8*Fz61lGvl4$3ESG~H{?vAxf1%d(2+eXnggedx{R2i3E}i394&13c${WL34%D@x zePM&M99U8Lqu=<{BbQ}l4y%oDaF_$NBhd|vUZKr}j_FY@`odujGazI|xzi;MbJz%> zYLo-M-eC^W1uRM(^<>ffD2IP>n8S4lq9nR6LFEj{$SU5;NL*%r2e+ls$Eh*UW?Ht6 zUPQ!kngfM4(RUNn3n|fpY8ec7w#@;V4pAR9R%i{(1uak9m>SKqFIV}g(Z;^xel>*R z47DURTE(udHl;=fPZug_@Ya7psAEH!xK z3AHEJK$)osEw?cpJDv4>y^csG%UJ~PF%gi>wiJ65d)@~ONj&~txy zEN6d>B_~-~Cv3GP7|pb=Q@euE?!MP-l@y8=*0_%P_cFSJpqIzViT3?;vYg@#s(UC} zrP}xK+TPB^NOcf=!3J2h#Q*n@5_vbPWufR3#dm}4qkQ2*OMe~WAOc|SG)DL+k)uJm z3)OHZS|w)F$CC{95KQxQEVRgxn7jnWLW@Pvi%04d{q9Tgkp$Rkk%Jg{+mNXCx3uMW zMG*^CHADtU$mA|ALCE$}MyeJ5+nWc%?|gO6i8@G%q;RyWqhpBaHqfbjgBlu+W{uIu z+7Ghnsa+S1!AcXSV;0jOn-Q)&34g#$@5-%^4`3?R)u}YZW-`^?%sUX~?zqBrW?1&+ z+GX8hbub*wtD*xRt^=>j4nli`_ROmHb)w2}qmAups=FJl5!Iew(4N;vm<@{6LKtJh z*zNc!)-M0nE*t15S^5MNHg6lAWX?S1YkkE=xy+i`oAzCGk8;jUK>DaOxu|ayW>RBOL&%V5T zafN8auCRm7W-@M^>WPosKYZ%$^k~OWEBGH=%9^5OH}z9`v?3Zf$rYp7nJu7~oU3c5 z0ch2v>*u0%Q`Nd+v})#j=sSI{_sr9$!egsVJvF0ZG(ENvUe1O59IXlI%-8g3_+z!1 zj)=M=8U6jVCRlBF+4~fOCga6bp58>T zZE(xUJ;)gA5n+CtagRfq<7?a6C5#NP_QV+-0T5mh3wDWe^oS1lef7R@b?^biq(7YxXnV`8Cm?T^q9Mbzh3mz%V3VodLM(||e zr@%9XUjVcJg?_#SzDD?MFk3)0-w$TLjNVx8LkJuxrp1@wO~Qx4cME?FX1@$|z5zch zd;-h{3C+I;v!TUaYKYbw!sscFZ#@EYe7;*Bi#fgpF%dw78n8GboC7`~oCju=N>7=u zzX&s5*;=DH^VN?+N@kg0=McFUI4ZmxoGHwd<_TeG>$z4iN7BXTT7mPxdaf0?E_k!(^ZQ;;uY!3qFng}(Ckyf}G9KgD7zler zm<*)!zk}HYMrKbp8))QgP6X{O z!XQ)_gM^2GIn9+yqrqc@XMwL2j)D2Ap$@kfm@B*%yhvDqxt5GN9FN!2uD~3RUr%$M zad`&>Zm3G72f_Mu3NYu@=+h~{9Cv?AblwC%E&Mk4IpO!f`-C~G^>tzPR_kp@;17GN z^)@76w&!Ac9})JO)NOCCQq1Orp3ao!1aaOfSU++1YaWD8QfO5E4Z_85AdbJ zy})|f6(Yo5?7?~>7z9?kBSeW^+GB-BgC_~I+MOZHYL|23d1tf1i-qTcmkG1Ud%ZAw zDb@(z2);%5o*0DNMYtEdMR+%u&1DAgKKK#gFThUg=ao}WO)_9Ka z6tLdf1p3qX{8thS_QL4BO<*w}Tp;E*folst4%T~{K!^S9dT$f(J7B%H3HW=k-rEFx z7R=oN8M!~ey@gTnSOYZU`R7NiKJo%etZYV!k_%>QmX4!fHbBYgV4l25&H(Ep5HC>R^5fQimUaHwv?&S}$A-zD-XAgHmk>cZmgOj_OHUFlXofLt@U_Y=>}9u-^X! zI_%1MUd%58za-4c>!jFQZ!t9U86y6EO#~<2%aPSO-JhAv++`V~p6h+(j-#wWm zGn4LQIx|BO5|R)?67~QA!oG!lmwgiuP*65m1Z5de1jH2(OEjX0B5o+621R6d0|mqd z5EVoL6%_;#6!iUmtFLhLJikBh_nh~f-yBGPs_Lq>tE;=JuD*IJxR&r9aD8E}eKZ#S z4ct=rFK}mJ4nll=glY1jw!d&Zc&M-+JW|-<_0N~J86gb;6NPiYeBqJ)Tzr@*%mIn7 ze$k)Pv_-;=!4C-YgQwa?3S>HhwVoEZCz$V;Gwww2b7YSH)8NqhSV`{F%T@+}i*TpX z`dDDzzxRlLV=$L8DboRbP`DHL8{w{Ct&atn?%a#X7|7fZlO1n`A; zMpzA|okQ{(aA#pYT=W!v3EWTk74Q(@_23(XH-N_ozX_foybC;4D}o_|&)~R20=@*# z6+Q`GAp9fvKH;t!Ji3pi~O)~fACS^Vc-+Oqrsna!T)KnHo^j43)V(hz?diEBZ_4Y-sL>! zhvCph_u~!mBDIi*aq3GRidFstI6R}K^KeSN|A6zPC!?<#^Ptn+6Yj4zKIq(rJ()TW zA#|gP^3X@^eaNX{wp4#VgoVu#m9@mlk9`i;(jL`i34ErgiSY5<-9p>vShoaA7n{`< zcwyh?BoDu+q@_4qR*iT#uZH0eH>v|Vh^apz%?bUhOP&9GZHXqgn^CV{oXuJ z_S~}EWzGZX32U)5U>>mWpcSJwuful6DYOl?GhPj%S+9QZqZP(1>`icN#C8T>aq=ue z`^Qu)M8Cq$#?y58;+G&D>}=>2Y;SxX!t~AtwN(*&2!@brt?#yB7OwT(&w#G=-KZym z8M&C>jYURcbN4ZvlB+g%=d+AHZw3y&i+FhO#?}7|nfX`3Bd%yO5Cx=_p18)xfPv+h zxxf@JWw5)!vC_jkx36AR_#}A!MO;g?UV%Rh@>0>mv^b1-tWjv480$Uwc&&=~8*5#M zrGPk#yM*!9pOEoci%>|vwGVgefb~0OE(u66pp9-=2jMiVpHYyg#f|DY)?QR@HWI?q zw`C&AOlu_m-eE0+|Ls;fmCj~ZccN_5EvmrWX8jGGW*tMtrdnN)>lCX$$~4)chUKl+ zIV6p(6BD9=>pwuM3El*4bi<@%qZ|I-C{4na zc}U&sU!W?jcT)0RKvD_&o`aX==dM!10kUmR9(|*caG0Fv?`Ww(>zzu`DJY&{QGLa< zIQDp~K45&;dN3SbtJH&*vJP+(YGtD_xf9}pSJD*Z(be*r)HevSJXX9(S-d5Q94$+m zVD<*7z-igqp=}adu5D&3$mc36+l<%ogSV+G>z%X)tLWL#d=_bD1aDyXXlt@&*}?N@ zS*xA-GeUBMccX+>Cvyox3WKXvm)D%Mg8NY(tFOtIJc@&ys#^U`*0e>CEnp4Qj+VhW z>OM$Un}&9^Mr*&e!TAugrkb1}l?3l#MhkTa_C%gmpS z2GwnYGq)<0y}fpWmN+x|DzF>MRY3iAyE!+ze5LC54NlX}C+O4rHRKfMe+T~dKo-vD z=lcQnV6xx;50bNoQYK(?r^6n`lr8@+sIEPn95j4?s_CygRq`G{yu>NF`0Ml222|o5 zjA!{5pyr9QV$pO#{~)j0`nprW6K|=5uRBFmKSv5MubhW${3}q|q~4S>{C^{Tm%aY+ zs_q+3oe?~tN&SZ*dAA28>EZ_coC~<7my`W(p#PVfVdff7((q$QE!F=mlN-q@W%z$V zmq{8;uH+w&PLwo(FHmIrr>SjkIN|)I2!^$2{4ot*7xcuUU|Zht?HSk;3bxm+>F?@Q zm)~%5@FI2An@&o!4X&!-b-I=O%aK)3x2olLP~>1A`X~AuAl+bJa?sxtr#je=ob3Mq zMF{pMhy7j9YQX{I3fhWva3EVF)&B*Cw%{OECBx5W>fm5b}8YBep6n*Q4;-<_}4kMrM$ zIy*gBjMIL@~>n-*ur9&0N+VypzD;kBBe^~$gj9Ui2sEtUI)cImtnZLcz<&h1YgE&G-;Fg48MwK)9&W7n-B=uLA;-J$@;r4TlE`@jO=|ug%f;>V2iOwlVyV}eRgY}NWx-<5 z^KUyjJ#J??E5)&P>1`P&Gfu}jPXDYpHfP0j-2^k^*gey$Ar*X=+zoh|6ELn>FFVmG7~e6bC#C3%`h!(F98tjc}I$eJti(#NY}?JzdA|w6teQtNJ%Xj;rRmXi~REKgTR6`}|#}oeAT5 z>06zN9X#wx6}-8Ss=rE}& zztve}x^4fvj`tKA6msg8?M^190*kjhm1^nYUi7lKw;)BgxR<=F9E*Dsj-TWIo7kVo zUsCqjcIO+@ZLJCVA!jPe>SjI3WY&A=)~$7`$=0G)xZ7ZpO{VFC3>$2Uey(7jcN=V~ zHbXHSZnp%}v@}aF3$Zv&ygn7Ox(k%m-R0!P45T3^Gh&)wpyQ_L)`{pM;c7rYiY|^( z`8A%ouBPd$ST5C7OS#uiS5vMZ2;pOj$ll1gS-tv!(>BV7dGlyoI@z+^ z@-Mjab&lV~@qDCX1a%wXcpTHHO*i*D&F|xw{!Mhx;5GDv9uW9s#YQ-xU9T`yU(F}A z^kKML8Kzw}?LcRSUizaV|{6RywcmmI3aUM*&BPDWV3*qz~=B66v-H&6Myh(;=^^~4$c*O;BOy09lEsQ zd77_nsjxWNY_QTLmCK7WU-#LbZVUu1(0zLr-MWJ<)G;5Vd!w57iBlsopZR1(Dryb&cgUR*&8&*Xez_T=Dyz zMkc;&*YZ;*%{|2@^+~$|G3I^qr_Q=!-P4+Df46V9^yr?NT7mU>M>m6c8N-Ep6}+R{ zjrW9}2b>B~Jz8zm7jy>t+%_+(pRSu~yW3RyWbV)s71Q+p-9hQ@gZgS)h~ zRg;5w(tc45J?Jz|PGTCXj$M`N_O5#2pp#|I!OgNlLMc-1@qqgLpi?)^14$U^yRABs z<@pYA&@*~wC#bALPR-&!{0Pp`W8d55B1TWDMMrwECVT10A;sv<9jK(~oOtwX=2Lea za+=}xzV(n(#~h)~9Ky|huW}AMVRO04JM0vMxWJio`^FlG&p5_)?R;v)VW(Q^lNf=6 zb1yM~XFfEIt<^0pzU=A4PK#>s`>}iKF}CwtC;yH5#sAYUohW?iWL#N~*yJmz|Fy

    @)dHf%UgM!Rdt#A=w_bF)pa9UQOuV{Ef%shKY{I-)m&_9sYKN zkiW^H8oyB}y~&3|{C>v8XV!LkCHj+D$K_4rLYEJtwWyB5-_dLMgfRQ;X<<~>I3tX^ zE) zSNICJn4515z#Q9XDeM8a7v{P}S79IcI^h6#ps)oVE}R6uQJ8P*jT7cAUaKu2uT1b= z^hZx*{>DAxV29-H9s_V6Fu1!%#?@v#F3b+KN*K>!#&g1WXff6c^AaytPhdyeCI0Ly z+7t}ZYz)TS96N#Ny6i09Q3%WcZSn=&6#T3Bw*{XUW{2VqAmuxNjaZkv0^@|cgQ>Gj znLc1%s^n2%Zu*gL=Jl5)j;RRXN;U&#g1Jsho(--i%+AGKKlH6Sl$8x5UBDvQ@Yu_&wn)@E+k@Fx3Z`Xie~U!VSQug}Z_;2oC}q z7zQan1nd>&_LR2HLX&XWkkRH@!f1Z5w$1|Ppj3?!z#Le#brx_#a0Bsg4Q?vT{=wyG z%C`r15bgx-Cd@I0#sw)e1UyiG0R@gRaPT360n@;vg?a9&PE3CeCKH9{gSn}|R|9jw zi-dXm;PNf~dCSn2TEO+d+ENR+2>g`DG-8KZBaUX^mxNif*M$3kwFwtQ9t74FT)?cs zPLUY_))rjgehq(Hc;Kgv9l7t=tbBUWZSPs@ET);1Z zVZufGzXCQeSWxB-V4U#VV7==P|E=Iu@&6ETqFR1vhF&n{fElW?LW>0Z$g8bnpye z_WU`*eCKSwFnhk%20+{ru>LYiMfdrA6=lF2J@u|XI0AlI{1L_2pud2EfNVJ4l7JlW z7U6vGF5%kX-NFsP`-K~UsUyzJIC_39+y%_VIr?)n{84xi_!r^mP&oKh!B_N(fm|RY zHwSb5mfRA|T>x@xaH=pbxlG}9;9TJj;OfH6xKNlKxuGzJVW@~>{70fU!J*BzfNuqB zvn}8m;2sj;4zS+!hyN_F-t`Bw^XgrHFo)i;BEJ+oQTQ?NG~rV4OyQ?_P0SO=vk2fG zEDN+2OoQ`e_V!1FUjvs4v*)iC-UxnP_!F?+=||iz!EcNI2`~-iGEH{gedH({=i$)1 z_+a+fqaqZ84yAYT;U5RqyZGQB_;-=veO+(j!#@SAH}S!E`3`%AZe~Sby@?P1R$#q} z4{igFRuG{sa8wrV39czT7_4{h5#eTVEAgKJ?ks!|&;R}4(56}tFaWHLq<{y5@0Q3ng0-a<_)iCGOD*8r!L-@T%w~bP!bM&H zen$9y@C(B1AADhfGE2Z4^%qd!cpQ!`60j1yOL!G{x9~IIPlcZY9~5T)_(pg=_)Aya>P1^(<@sp5YMtgX4g|2MF< z<^nzo=KcxOIR~yMd`-Hbsg5#5xHVW? zZ9$ssPugk=nEgpxZ2|WNKPd7;X%b?&I7TAiap4=m+Jp-tj00;EF5rb=ZNUZn5LjDq z0j~hRD{=Ww=!e35J>h`xyI_86gcYXFER9nqXQKT-gX0GYI0U9mS_*OD=Q24PBO#iL zi`e8OuunJ{oG6?M<|ipAQw3a6xGFeXxDcExTo2qRN(Tmf253_)V2`wAB{y>tJoQ1W3V>c z0zLwMUkdjv2KtkGVi!i^LL;o)Dr`ig+7dTtEH#kq216oaC4ruj+M}V~j7sMS2))ri%aEykd zlL+xnsLi&(|7I|CR+!l&@DO26PHqsM4;~}D1UyA}6_{!wh5IzT9E&K=gdEtxTSB3uqzbR~>72gr&V-~+o!}O^m_o1*Kyw~NZkqC!2 z=K>+#*}f8?5crrd73oe2r-8Xez(hGfofGB&bxAlM{I4)iYpm_c*9IpD^U=+*asHW5 zQ#ev2pe?wPa3^pTVcMFhCj30Oj_`VL1L2L}rotR>S_^LlcM#qM?k2oD3P*2odr@95X0Q|Uc zUGOU5#^C3KTY+B|?g-u>+ztGea9{8iVZPGzp70Rx$HJpMxc(1_<0b?g5*`btstF53 zMf)FwXM=whz6*R-coq0h;a9*{gx>_mB)aKvDb6qa8#qZbuYWiq5^x!uE*y!$6OnL5 zaK3O3xR!7OaD8Fw&^H!t32rGo5Zq397`Th@C~z-o0<)GA;OH+*J@}!*bHO8pssA%p z_#Ut}1%tR;W}7Dd)SI6v{4jW)FqhvJ3aH9E2?}@6Sv#6&w(zlD;+(1Ai*uXNrFfFV25eaZu^LhHyi0 zUE$W?V&R_Pw!(wK+*M>|d~2weFqhx@3y%d46}|;LQg}RgEScl~L^vi&z^&kE!c)OB zg{iPVPk0V^q3~Sr1Hw;%sVTq$tpz_W{5p7*Fm>*=85ziMi7vWM0;nPPy6}78jlz4u zyUBQr0UG;-aWK9Vj)1=wP5~bmt_1#3I1BuXaAh#`-HhZsoPQ=7=Q^OAgGVi4T*7+c z2txXiD^>jY$dxI~N3I;<46rsAlaJdkxGDX?O~I{&IjPm=V&LBj+%3rQ9}eEvwY?ZH z@88;940r^1m_+7N(2c^=z~h9GgfUr|6OS3fi@;poWBQB13xppA-!H5fd6_skOI;z% zN2@1=p9DWE%t`8t!Y_i?3$Fv03G*>)n=qfO-WT2n-YxtE_#1FExxjt8{hk3}2LXIn znj8Y35$1WlAe;uiEX-#2AQI&Z!9L*z;6!0Idsw(NxT0_e-87~1P;3>kL!M6(!2G13~ z9=t$!4ER3biQuKew}Mv)PXRwAJRAI+@LcfAQE}V_#|GiM!EXyM0&f+50Q|nN0`C`I z13n`B68NMr`@vb^cfo%O?*LyBj&jl&gKLUSx)1CZ{tTQX{3SRd{53dT_&m6>@W0@E zVG|D{wS>7*L1k&C&&RvA!dzYJmdx=V0bEk+F9F5i5yDNtqlKG;XOOESC;aZZh8GLt z?rkg=jsrg_jN5~;Mwp%V1>p?jZ)~QchPy7iLzo@pLt*xhy~1t5p9{AGeitv5l z>cS6#3x!vJ8wx)Tjy4m=6L7Q0ldLnC;T#afbeVJA;PbNCz5Nqx6x_W@JwM|Df5JR6)Y5HXJ0Ce&K@;Zh=W7y zGr}XlFACoXUN1ZqTqZmVyj_^1dD@r>{x z@CD)H;LE~4fjtqo_NT#q;a|Z)Va|?Ig#QLt66OL$6=BYlt4GDbwcbKu_V7l++?H!D zoC7WqE&z8CE(G@y=7`x}xFvX~FxPrV3UiBYtZ=j^923RCwYh1+1Hm(e$AjkyPXjL$ zo&&yLcs}@H;gw*1H;nz`74RzIP2iV=cYB;1>mqS?>7~Naj_WL!iC^G z;dl$ zxnY9vL*S{x%fNRC^NN@&%%Oe6z2bNYj-|ruz>f&82R~1)<39htA{>DK2H_z1Ju;fa z3*IA4nSEt{UrF8`GV7PU>x53n;se_+ri7+@ysxX=)KGU%#_MV-j`+^^j?@rsDu&W< zn59PY@Q~WT!x0rzG1SA;Y?JC!5mAaZt7mY;7sAicJGfPqq{BHzElY>9=Wg{qol{l) z48(m#-Gzgv*~jX$48-+)qH-%C^eHu^5<>T>9Xx!dLYX*>QzLmmt~huy4k~XJqO>}w z`ecQE#Ye{5W+U{BT9%FS=UU3DjD*gs!Ij}VNxfJ(RKxsSeOWnF#nY;Tx?DMw7rS90 z8d&A$z$^Ti{$4m=I*CWCl>hSJjqimo&ItuQ*FUC8b3%E@Xh%*czd;4up7&Vgf?7dD zJ9Z-pHgYQont=s!%CWcHq`wai8Vz_e&=3e-Vx;z$hq?!z zWu1`Uf8J+&16L>BNW{C{5R2stTX>0)%kOxJ@n1;dCB`CLkTB0k1p@sN<4GUfrqA>% zr!W+W9-(kdf5>`X^8@jDaNswnZA={RfSx+YJ0|`vVoja!6d=A1J_Owi?=JEkTK^Q} z0n3bh01a@_&|9<fNqQTf0w%oA0wkK0!MlrmGo6+hqYuDsbDhVq6CoV6`8v%U zE)ZfdZ83P3y&Z*_Y5$DBci0J#yxl&ANHc7{-#^{v{?2W7BGQ{?e}r;OwKt$hQ?$j# z$@V;i-fGimAYLz|J%+gJ_abRpX57x4JAI75FwID#7ckAp{r-A3^_6n%Z;%YCz*o!S zC#`@OEi-cQ&*pyU+*o@ugiV_Ue_)xhn;Ks?l-Znnb@A_TAhOo9#_y58uXrL7O5$c3EHzRW&K}Kr+0^AQ?ICCw zk2YutON}(#;q^yLsTgO^L~bxhj+4yy6Zz&t&_EPgYNXK@hZTjTM#{p05LjxoSVAWT zUVd9Ue91RW$LgPE%bA7TVY-pggH8ti!gM1QtHKVyq7KuI3*ks@=zNPp!FJa6~SZLOJky4;}E%^KUqDHXa_=T21K>}TvvJtq< z)76b^2G-&P270hpl-qA4djr3s7J=U6xIik(7#K)-A5sAiUajgh2*sxThMkV~ z9a$bLFi&-B5K4{iW`_-Gi+&4&_BIG_584mm@Wxz^{R$GAd#A_FK$2hI@3G^M$?=EK z>k$4ujvG*qGdObUxO9ofu7~3@IDQZLJvh=@%Eve!g!?#-2T<8RaqJO;bf5MZ{;pNw z6K6HMou%jSU^V9nLc18Ko7Zl-lmRo9zhNjNIuFf|VfRZ%l)zFnkKLcOf{990!5%>N z1h~g+>t^r^m^iK{{qyI4a zry5DMk*L>>j^(R#HXqMSdlI}*{!vKEm)M^6EduQJi5-YEW0}~IS319W%~(kfpp4$6 zqDf3)IV4OYiF^BAvj-Ja?4e-8=E^VL$$*E)-h-x%v46u~ui@X%a;PeeLe9_#QZ;Wl z1#xeHSDiVM*O@P{5(PF#vG7*lWyCT^v$DwnUL@v?Y=p3pI)_5bOdEwsgI&dCENn%X zXV;%H_KW^cM1NOJ#{!&va8Z`58Mn-#Q&=7 z4rVVe2bbD#t9u%UlFUccipHVsDUZU#tm8pN^g@(b+6es)wVm7qj{-SrMH3XencCDO zlvY<4yMsp;8{M|5Zn6#@X48o7OKgpfp3O*LB;5;DY|~ITGgA*Q%s#a*Db+NT-Eaxd@EVhMLh})X6l$q8dTcG!@z>}RRH#q?8gmD7D(t6z zX&S0&u2B`6g{qi_YSAo|mBo&3-t9SsT6va~)Mx*`>TH#_vuswg(CucUzu0b1cy)ej z6U*~^LZ28nzbAAf7M@3SAjlJB)SRPgwg{y;x@_yT>_&8~su9(%MQAd5#)mC*%bjTv zDoQ`c8lCnq2hIVwYG#%`j^|ahaLbmVN}k>E>iU+Ut!7V^)v7$vXJgpJz0fI;Xs1@8 zN_D?r%RU#w`evgHd5-|hwJ}VVujA&?u#5S8OeZ9NnC|zLY85I%S@yIFWtfx7er$#F zkj{_Wm?=6|E+o0ZX_kJ3RRI*HSDR2JM>law3{U7OG=JVa6>SsB#7lH5+l19#-GZ{ON{?_TQ==xj4|r9T4uQHsarzd^^DRyrHQuVuNkjqG%$a4 zQFA_;@aIE?X*YtuPGsBVb!7Bz{&>6Li`e}2Vc+D`3BS9@Sd!(>gWhZ=yG$!Zc&maB zp;)hHFT~V}zb{ZAYBV!HJ~@*a|2yFn@G0SR@UOzz;Pb*c;J<`(!Mq7FodRH-aCNY2 zFB1VZ;n4On!L`ABX^jyYf-{9%fpdjRz}j9WWO!{9ihoZqX9JAOMNS%VqkbVP!#!j& z8>zRj!}`9NZmeLN|iv2#*2J7oGrKEX?M8Nce8BehCF}x!7GQ{!fCR7G4EjE4%@` zPB{7|9NZCS#&3b&7Un|YR^fNS+y$r14)7k~_rQFsn*JYx4+`%A>laeszaRX)_;ZJa z6DrF81U{qfWx{b0jtdfiXCvdXa4Zd2GSyv8%%a5r!)dJ;3Hj#z?lAFw0b53JvKfy@A~ ze&Gdt16aTC0-g%4F7jMir`Zvvxqw=-4aKnt0nLQ(2e%RC9J-_MGvKbmFM;)oEkXD8 zs$XmY^AgoBwt(^2XWUA8a2}YiZ<5(zX{jceS`xM4&~LUNCWZm`%@#1P2>oUYI1Bs~ zBY=5DtP!pX)-SfepXt0Q{*A#~g`0uj6K)CKBisi3X;d8T;5aDU6U@~?X4V_bMJDoK z@K3^{z-NRffiDQp0_&GqAb%&=#$y)0W10#M30DU5%O~`YR)Zr;9JRn)NMJxAm}`1u z4p4Q4OTdkUyMdbv4+WP9j|6uS9u3weERhZ`QO=N==3MYlZ7&m!yWtoi0Z)Oal5z5i zz;_7qiDj-Z2a^TDY|{ILTZ5MhbCi5kn60}~xC?l#aCeUXuZe@r%lQ^FW({Z`h&&Fw zQ+OiyBjMTLeZq^tUkKj|J|g@C_&edJ!KZ{d-uz0AB7-$>oR8?b(X1$-VH)o-xCaT$+Q&~ ztF2K7K;4p92D!$^|@6qiC-I4ZH;BFv5JTV2M7bK#gS0bKW* zE!+&eP`Cy7e&JT&Wy0;iD}+0MxoX19IB-2H%z=xuM*2f7-&ik9P5d%#FB1Wi;CNR8 zxDU8XcrKVLdyITH_)}pHN}M{=|7q}#WIT!Fq7{D=t^xi-m{%5!3{s{p*z~(xAN|jW z7Y83KZDBqKgoN3X(}a70vxNJAs|xeMvWD;wa9!c+!L(+_j7Nc+3*Q883yv~?x71D& zFdp1Pm;*~+;mP3P!kjxP$JY?vr$XO^rehE8 z-fs}-Tdt0L1KA=~hKa9^cGwL;0~yT43U)p8FPCawj|KExC~~=v$~< z`R@|Eu_YHciY>XiozYtNYi)MV0k$_Wu%Jw3c!z z`(+r)*rPTfYFx8^I7g1-Es!&|=^keRuG$(6(9`t&;9;0HZH2u#PWG`km&k)Rw!1#W zA4V#AZ;p#Mao)R`sOj6m2}qoGDO~aN^7M^`e@s{6a;EKW)%8N0nD`m={TY9*emRXx z4A`IJ?8C>tNZ(kfgE#6u@ zhG#Q;jcl5{w{A?DgGBb%dlR@yAHwG}lbZz+X?&6HL?yevAOGue2(KsA+FR zlQIy(9j_X2IxUc5SD)AHgh)Bm`gI3oqV=-_(It6@+E4vk~tyv zW$Wm{WJRewa{~Or$-gmv4RbXdsmXQeXlQ;AW7IG*l9wY*Ut5z+n4LU=LG4VgXXhrL zprey{A3_R~o2u@ou;X1q$K45PJQ z+hjiN`lgzk0GA|lzV2J7L$KpDPVGO19k0jLg;Ut^s_IcSf5a;D2dd|fp>%Ve8uw%9 z`ra{!ZZ=_F{58OT7=1(fsqA596Ss%K$woArFogGwv==GZj9re;G-zo~HRIXbDLYi9 zpF*iwdKGUd zVN}asgHMa@I&Yje3@+HlO4VUqTZMht}-fuQiXRnJ?T=r2; zN?DJ%@GU7?N2z@EbuCXenvOrJ0hQaUc@&j4@$A6gHZtx*{Baw#e1&!p;&RQAzv0&~ zA9CmqQT@6hnORJB{iE0)#Kc#GIQe0Rr4VNI1{I&k4tTLJZl3!6MEYlf9})jtaH((| z@KeHd!5hfPm34SanCWj}zhM9#qTEj=fm6U-lcqnHbM_18f%zH{{dr5!%faCKU~bLQ zzaf}cJ()8wDprwkXVg{-$*sT~v1p^lV!zePzHl%zz3dCl0PAI6a1K~6`+`|Zz3dCl z1M6j9Fdy}3i;{`f0&^jq%tEvg=KO`P6w#jr;*BXvM{79xNI(ZL-zlPhC-89LUf>&r z`Pes3xF2}3@F4IE;Su0D!Z(BU5-`%727XBVaX*XdHDG487y)_-82l*sB@tQ)rddA9 zJOkb+{5<$w;a9*rh1Y|rB0_oI#6B11z5J-~cJNP{(f%L6L7fRk_y|m;3^E_-E(`wx z_Mqp}{|wkC{5v>N_z!Scm``-H@MrV)Uie3Q!J(IW;ph#%TZH<8 zxgpCMa3w`A^@2x%c|+rrkJt3`!U%HB3c;V3^c&)z0DeoDm$6>Cg#a%_tlUCJQ5<^d z792o;Ub+RRgZ0uaxDr?|-GZ~gSi045D}(jQE&Qv1^~x={D%gV(GW~oo-(MnE2Uid- zME*v$IO-ulFWo|@Be+QXJA-)#U}Qd*brkLare+KMuLBPe9uDSH3jN1`Zx+4_JV|&l z_%<@%$mgw|`;v_C2zY_;3h;fx&x4l=zXayiHs#sdR|>xlen$9B@C(Ac3SJd{7yPF1 z4)BO~#PIN*weZUx zXa{bp5$1;n5^CZwLk;F(k$RDbm(<@p3|C!hh2N*XVr{rusgbqA)6GKl6WE1}e~4i=u^I=@8z|n@FBfpF788rgJa}V$4D!YgN9py%LhJLHzB6&I z(jPVIIVxPT$`9w%)`OY*HrPr0u$kZV*H6K z+Ns;g!*8?1H9iGLLQfO`A1t5@CKbIRKc5JcSp&l+s7btn>`+()9g`5XR6I_>`c+xag*)Y2))(jhCP)2 zxKrX2r9ZzY{WXCAl>TU^1WJE2wOG&2gCp0z56PfuDYp#(%*7~%#F1_Mu=%Q zL7q_htEdh&3umT{M#=o5^vA2rFG_zaRIqtCCGiA={rlD-T`LglQw^Gj>qdFgHtgH* z*R=0Jl|1%VaE#5xA+J3Wfw4ALAfe>gTcBGJPY7W2QGjl>Dg8WY{YqdsWGgGlZ@QqU6VlC6p`q zarY*nT*;4HxCz%N`6ZMq`Hezi3E86L*8!O&lq>lSMM)Dx$uH_q7cQY($?p>LDOd7q zNx63B@5nCK`IjN(N`7Ck1YOOyQ0SuM@l&T62|cuN`kFkG#SXWl6Z&iQq!vyr zQyZuqEuHx+&tNk-Rvm8<&aCt>(<@i`8-UhJSf~Xt0XpYZnJvRrVqa!4)#3V~!q~Bt zR!K#nY|rLc^+e0??a|v18nimk!Xd!6vbqvs@*IDx?x*nA8{kdH>dCY33uGV<>pHSO zkbr_&eOQ-3;| z2h@bt;rxP+nAjx`#$tm{L}uw(B$%bMy`=T4;Avzb>&>mhHO*D(r`F+2&vCB`viGB!15-Pp~H=0*f zjgoMF22YiFn73@RcLSaL5hib5XEbc7CO{%fOPr^~mu`H7UZ6}2l7vF|>m}i0%x8Zo z38y+buer>tB?FOHg?8bVp8clk-!5FQo^Il{S~n~f=~Z6ECTeH0Rk^ubS?AwgpFB>Y zE5GAao7#ob&E4wDcHw!=a#*lax&v^M!^yT;T7ya~?(_jL@XIXauFI`|a?Bbzrn>TJH~7;e>@PtE2^ zkJ&A;ymznEDZ71lrS7{%G1cYG`5snaw64Gh5U;G8et{OJDYKRc(YcT96z-l(wI1`9 zQ}A*Z0~#c$4?Bh1S9}B+#@}kPVcVQS9TOhaZP@lRkE+%=+|-<{Mt2TprB_9e)0vm# zD@m9blmfzZ86}D8$ zc8QEL!{0MFm*pyAN$l<%Q1Iivrx|j29&YxQAFL;{;A-G~`t!;M!iQ@FXzr zBaA!)oGW|>xVrExaG@~MX(&7o+)Q{rxQ*}%a7W>%!F()?GJ|K~(6e9gb6`FD1wRiS zE)iY=b2XUquYvVU82lkv&xFByzz;M*w7ezZk64ZKU3H>2IcSzx{n#B_4N`n$m3s^G7+KiaK19LGhd zCHP0-HsIfcOTg!Zd5+`I+c}+Mf%HsRI4J(?q*cfk9~;owggMRQ%R%(#Mbv=nt#t6p zX)4TleQRM}6ntu>3@?Ok!mMR)Vb)R`*o91G@G$YO0v;_~6FgD4A()29nP#*Yj@!k- z6)inOM$zEq&XB==u%01I*bVxWC{H-us=m+5%;Gc!rT4#mXT7L>R0ACSiOT=LOXU43U{`9Vc_U{Zw zMFyZ~%s5+^y*E!d4_s52nbi~K=_nRvv$qg#0xl8e@Z4FrAGoJ5N87>Ls4g;|1jh{$ zumG$-s|)|fz!Sv(NwEH`F8rSX-y!~Mz;lIP1}_%g2!2TTZSW()+rXv5pQHa7++Jbr z4}#YU9|o@zJ_&wZn8WAW!WY3?h5rF-og}1#iK(lT1m=d*ry>&v)+$Nx=V~bp>_(X} zH^zUX5XMw?3eIxK>0o|?ketnQ<6SNQb812Vx?ru71a1u0I!WM`V6Bq`?gG{-N#O2a zZATZ}o8!OMLxN)f0<<0ycqq7qWN;%`>mk8^D!8-w-wEz1JRhv}kRY=V%vAsu?tbt{ z;fKKbOS<6#wWt9!Ji42fDa4v3i?5~E0{}TEND;g z@4_4>E((tT$6&CaOms9H0dd>}4hl~I>(A#xh*w8G{V}En!L@{WL#i*#OSiFbE|@D4 zl&=MDC(QAni!jH7UfQTGggU^{UjkU0>xEf^QNn$}HwzB}Ykeccy#cJ%jliscRyP8V z1K%a`Gr;!<&jxFIxsX}F@qf7lJdA+Hg;#)A39kfe-6KSJ3cQj26>#4NYZW6f`@~N1 zXP@{;n0-PU&xH*8gjO*EvrinMOcaiWaC|2b*eOm4vrqgg%s#=FrkHUnFz?Ca_CUV< zOYQ}>gn0#}2(yJUg-3yNh1o*U0&(zsbFQ9@lggU4zJ@yp<6dQS6XyBuEu0A+C|nsl zOjtjF2R0Q{sd`-e89 zi_F*(>&1T=JGF6hxVy({uYB)@yJNAu|GQWh|6V#7R2A;lrx)C?Xox~mVi zg;zoY=%(!mF7B;1ZV%t)DH^E?cObacL^T&jWW09=R^s1QfA7G$d)LV-dncC2Pt}3F zXVqmV)=Va;i95sjo;!=(uLJK42O+$LUWHM03c{Y8#wxfgoCo!tM!T@`j_(8G5chCf ztPP76PpNukSNK0#JnHDKa9T|L!Xo{7U~^GfmG{DJGZL!d&S3sx@=;&i`dv6<$QHCB z*4XRg4*s7tc7DGCYwX#&yPbu>OC3oT#It_8nb`%A<`fob#7Kj|6BBF>WQ zE;AC1aA9@5CWdvq^$s7bu5)ARPJFR<8&dMGJqS<#F?a?2Z%;*A1WqA}{~e+c*pGPS ztM1bhl+!$1Axi7@<;(7Tw)G!a2T?nbpX~J?CMO0eqo)3^$w`<8(WZ&z4HWIh(M~`V z!@eB?racSju>+_|jJ*mzUi(+nJJ$XHK5_QVs9d~#2r@pKmJI#&P;{hdz^1v%1S2UE zB`;r>e;VP2{k=|gGMdn1S4V4LUH%YC>rHUqz2~c>hW!#^UtN~B5PWr6o;AO^EYG(A zuq>a#Oq?W~EiB8^5!El>V_lvO%i)gE)pdF9;9XspFGgX@*X5lYob;>9@~Mbt_|znIq(wNj8o#@jFj}v;w{`A8 zm9MVF|9}?#&szL!boKzCVEh<%rEi_=>8^$?)jB%gD}axo_d~ z>aZHh)iE0Zmircd1xKwr8E#UQ%J^3Aw~!&;s7Jx9f$RXj0Oui=etp~@xLMh!!YN4w zI0x47_6P_X{(fFn|5P|9>PLKgN)7zQ8`;RqzJqC50UAlLXR*11fkWgu|6;wOkY#Mu0$iiuXhByb6oKT_>HunY+@u{hh&4Yi%}_7jrcKKDLNj) zUZ;K=#I*I&nax#H)25C96bF}~N=|VVIQ%}gKyrh{U~Q@(xgo1(1lZKcjoez(LPm12 zTWz*Ra$|Cw;kg1wsM~VHjq~!I`pDb(6P{?kwB{RxCSkBWf9%G!HVlhKI(t!wr~d(p@N!H(cC_!>1jGZf@9| zv6%KQl-FbDKr+VOqtDx8@QJmb#osuav(tE+Uvc-@!@+)g1Y!jY&xfkf&*3^%2f%M* zL~YhQav_%22+Tvg|8uy?AXYkZf#s{HD;>E+&d?1L`J1MN zGo5Y7FH%8Ytl7>O#1=cC?~G-fnmSHLCnHY1bUIuZ<<3T=uFk5T zW25yVMcUEdi7?d$T6}=>E}9`yY_cB=aXPc$jkV)?hp&xCnrX4&P9?@^sU4%73pkOH zR@yPf`R6~5an5d*qzyx&#w6!{HXpVs5qGK+Vsa(gaho%aA?>wehVvzh&{5ar4rdSQ z8gU26S_sJYJV%(b_6%y+8b))IkUCqfoEA2GE7W|TL(`)>{RV|)#18H{m2a6+^;+95H)g#cD&*2#@UO^(vCMB z>eWP|+EIq_D^cC|Yq&q2E5H9WoNIoo{J({>2lqgwA`4ESdbH9+ybt=L_VQcg+mbi3<;;)0T;%^i#V6T3Rl?rfm5p4nQ%@OjuI6Dx?%z6Fv?QF zF_$3RLirfgUn4=yI}@(Zi33?m6DI4?V|GeYU5&uQ^lwJ?1%4$rr;L?2eZnXsr3E=r z5B(`E$-%&Kb@WU)E4meZzCu@hw$nHZs?g2k#cL$da)8KXy!d98~!*D)r+L`E9n_}#JI2Ze4?B`L@ zuM%y(5%Oa-yEnfvyI~jVw+X4IrnUMP`2}dOGp#l2;tt|zZOC3dh^MtB`vNq~oK~Xi z7??tC=hiVVUhO}N=d-~mSX#$*kTuQtP{rYNc5fNm+;aD2xdVX}_?zZ#(gzaIKhpX$L)*}ww9htwJ;YSs#)&Y0 zBjWy!+{`oIg1rGQrkiKE0^$q2h78Q#)epailZV7m<~%b92Waov{DWK};2;B256P*4 z+tBdlpY*R7Sj2QLGUE*NJD&SEFV9HE1t$8X?w+QN$d&SvRPS@)yb?Y+nuD~#4kP0r zv->25G^PN z4~~1zhZlP?{A$1-;R2K#GVklL)9jsxPtAU4vcLug^6ID56MtYJnXR_}5w6CVzx)xd z<0-II=7n%U61y2TeA!fH?`lU>-wWYF^PpOAAzZDVu3x~z`f(~%pmPamSt_6w@U)w* z`vtlS64kK_;S8vw{c|C#YI5b<{K0MCQCK~3OZkH?W#*)jHyGwglaCWdQNviZ?ay$5 z2Pfd?Kf^DY7nHgfZd{p{gV|2!_Zk`j>1#L3!^iQ0DeCOSaCKIx(xvckp3dWBD9BbF!A7im#hC*LBDJ!lTr*$UP& zdW*Puh)(;F1v}JiUnI+%te)^iQk@_>ZI3u+UIXh))t9OFe32dICAHY^rn$o(X;D|F znX1#A!LFy%Ox0<=LN`A~Vy5X-_QGBLF;y>s=vhH^OCZvaMOz(+wD4H*>UaRpGEb?J z1UL2B39k3%gvg^5>ugU-bAigTBNaMiqVb@M!CkP9 zFGEOlF*?4CQ<+ezyJV*gkg?}d6|GYxv_ohLui%coAyJ{HsnD7o)~i`Io>nKSC+$cs zOakr1q1J5l+syuKvgc}G;I0bLe~fM%wlfFu0Wg&wa@bx6dc;vRKh zA|5k8Rv#xuGNU8Vyh***)Py+4;4r6!M$ZxGS^gAr^Hp!w(df$0fK=96BVRJ46n?JO zNY^@OiQx5|wVh=M&uy3^hSBwNWRTsFX~)%qC&B2t6!mqQ;u=#oCq-&Txd0hj-K#uY zM>o1nwD_oT)Bp1AfT&45Xa6tX$!M_Put)!guUmi~mu=J~Lk|xdu7>YKDU2R8*5HS| zpGYtgHXkUf9*j(^;=36`tH)>^qgK_AWHuf*apZ_`b)X$Lc}ks|Mob$yX2j%Cb?|@M z*a;KMkHbb!nN(b_VZ%CdtTk@>*h%XD`DdhutG#hnrLwdJk>r&B_B9&wV;$q8w9~f!2HcFj`a&?!l$mjt4jYGqhzlXDfjPA-`22NhNy2~^&ItPDuqSuwH zxKNF@T&;yWQYOv^e|&?wTziFI)-%(%4uRi(I=H!pAMNFpe`vZB`h;1@^?!(*>GFPZ zmdj&MFf-fbbo4i~vdfQ=b6n;(ip(l5^MT&Xb-6K3gql@d#|`LZW}eHd$@wl9g}?YiQPVxjGr$l)r&!(PXp|@-&8B!=r^Gtp6?Iz^G>2D$D{-7iKy5 zS}GG_-ggNzk9&mcgO>?62Cooq27Xew9r#({F5nlzTHU2P9P1^Z7np|em>E~4`L-sR zI|w_3hl4*99s}MhJPE8n*$nyHz$fUB^UvC!zJ`An#+iy57sbIE|0B#Ad(r6_nKcdw z7lO4$3H%#^D~dmBnJvs(Ru^Vv3WZsLhQcGk%`~I^Sp!aenArrde*Xe|E4aJ(PY3rA z=6=~A;k&`Zh3^B85?%_PPDW=&-!^7n!*>beMrqum)m`ER*4icDdf+hn0OPVi)J3N13z9HuUBgf)!TUJ~z(c+JX=ZR5 zxI~25DqVzg!Tp3;STxH1ol^;=d4FU3e+D+&Imn;Am3`D20QIa7<_= zSbugI{5qJ{?&x0z9wfXQ%-J^m4}fnL{v13>_#pT;;bUN}AOiX0;QQ%Mbr)V9%Up-n zpIRXtg#VMmVeqrU>>sZRXMxLvdDUzdW?$JMTo?SIa54B};U?gtTHPh&sx+No0GQ|g zv@p;8@4`Iy7lk{6{}JZs>BV@#xP8C@;d{VA;m5$VMoXC&z&XM%bNsIrKKnz~hB$fv13@j8F#-t>gh_?X?aD zxE1(riO>;zuW%Re65-xpCEOqUgzymXYT;4f=Y`p+UlE>)tc*9r!QQ)7cn)~CFnjXn z!W?415`F-DOn3?Sr0@#xFT#(5&j~*Pz9jq<_+QOv|Ciw43m$9<_IAEgL4Fg=SCzb0cN4KG6%RZSSxdYS?D@`H{*6-{UK!dcLZx)4sbV) z|J>SQA$lM{e+e0U9e9BF4+LK?JQO@ics!VU7>s)xSlflkaBnd4=nrPav|Si5E2izj zfNO%6QHI};=B<-UDU47A)=C>--oKv}e~w&SY@ke2uvXvzcLkS;KP#|V_y+I};ZfiZ zg~x&S3ghxKqMwW7ZaBUYUI;!WtS>F$74X-#W03JPU~M}Fyas$pWY&WJ6@C${KZ*>Q zbzuEbWbk^h6VR23>Ps%&b?6&RRpECaq`!oW2z$UZ+|1hV1ve5t1a2<;4Y;-NQE(q} zCAVV^x`sy!TaXzS!1Nmu@}6G{a&K~TZziNM6CjX4ARt6Shfos;MT#IDDNzv- zMF_nuTo4xvDqz%OK}B(~p#maTnu4yvE{X_ZS+TI76j?h+Q~uxYncpRVtM2>4?*DoB zdGGTi^Eq{9PCsYP`Tb&HAoFb!g!90TaD6a8w9yX>DL*oj^TD-*3&C}TyMh}DvxjUh z+yh)7jJ^ZgUB%D`4n2hXgZl~(1P>DCu;~in5#Vv;EH5RrB4lB$2pNo*^%Nn4(UE$J zkiiaED?-i+d-<(3A;SSL4$H8r|{Ka?opsU z-&aC7j*!Yokr zg?odG$S9$FxX!|SxE{jE;J(7l;>(43-|!7I0aBuJ@!h^tjg?Xa$pzuU6x3Dpunc$OJhcpaxVTeog94OW)QFswJO?U}7 zQ+O4)rtms&UE#lj8w-C9ZXvu6Tqt}9+)?;Dp19}9LO#(6a4%ug!s3xIr3@4c^UOHE zme7BD@b$u7z+;4agC_}7Y}Rz)Y2ewyv%t54!;EMF4D-a{N$@?wpMkYbVfgtR{HWOf z2!2ACl1iQxt_*%b_+qfuDU7fbDx!4?gZqPD7e5mypJR&{X2IbdVXjiWFT4!=u`q>u z>=FJO_<-==!Cwo12|g^$b)yr)DPS&E@`ZVRFHSS+KaV2W;=o0rRAEYxsvvwLI8&Gk zW@-rE59V$?M*0-Ef$%!8);kP)9(HXl_J0Q#34adm%=te9^Pp!B;jh4bg?|7K5;pwU zq$?Z)zE(I9JW4njJYF~z9F3pLwsXY326%yR7;7+w5(5wQJS5CRJ&T2Vfu9mC2J^rv zANpGGi^A7~HwaGwZxWsceoJ^3c!w}$2!C9T?LQ*o^2$DOcmb@H4a1%XcD@(;KZB16 z^T5t2VJ@%u(Tg#jUEq-Lci?2MIU6Mh=pOZY|b0O76RV&M-t{~soXgW&6hkATMr2hkr-66U(@bm7Y2 z*}|Nx-YU$=>O5giR=MwzFF6+cpzw|0N5Q)N=d4s~HijooKA#m&oN#Kz#;|`5yjtv6 zf?pBl%jTK zoR+l_eh=JUcn`RXFsEXd2_FP&1<8o>5O}cIbBZ=JEQW7j7$N*Uc(m|g@C4zX!PA6; z82ir>P6FR7%qiL(!p*>?!bRZwg*$?^c4a(J_%ay&Bo3T%JtNF%)^g#i!4csxV6Al- zfhT}BiTz~oJHp(b@xCxs%6%++7kH2GLhu2tLmCk+f#GX$cn*A6cm?=`@JcX0Dl)%U zf#ZZ%gKgop;51?Gf3GC`I=HIvPH-*Zy&>d(t{8rXLmP4}Z;sbqxGhGmU4%K@zD&3u zxSud5En1^8!d?Z|8kNB#!CIp-csy8ZR0huhYmLfWf4c()tx_5MFnE?k`X{i~s0{lE zSZh=UuLEn1%HS*dD# zz^@5^1OAKfQSjTsC%`*}e+GXfOesyfg>CRY;Uw@sgj2xZdmJ{X5$>2c@Q}kP;p$+2 znwK59;E*u&03-`H1-rtOI#*ekbBAocOB@D%bIDpfxIk<6!?a z1AsY`JuJ*A^f6&Zlz@pLbx~uisu!xs44W?Y3|mE*VXFxvY}jZ_!^Ph8xP>r=c1EEv zntP+8FyFG9FeBaL;)3PO6v^A zMQUhgXA*DI89~w;tK2TI-KC~=aq2)|?Gs%bE#j_=Q`7ICjb=gp2penHJzB&azf>_V zqrb0UO%Zopout}1_j-cHqi!gt`j>|QEDHsm(I^_M?(XX3Scyul!_{6i#55KnkH^Bj z#8Z2_RE4@ZHDlj=9=$@07Oicy9Hj^b~ErFY;KE~W>b2`R67=LHN|cV*U2_T zL`|~!opYj{0oMukd$1pGSG*q)jk6cv|5!T@&SUI+IO9NFHT7C=r<#2=A`Ez9w1Xqm zcfFC%-zle$Q@eNpd_$FM4q~>uA-rKze2Hmy=7ZZWB6^%A`Lp*Rvidp=6Zz>C`d06QU7O?y>dL-OPG!nvfr?db z6)8&o2c8`&R_Ouq*7n4x$NS(poknJT^;2Jm@~5lybEcYw>Y;v4_3+aO7i!6^ z7FY&ejDL>+-O&K-*ynNkj8I>m=L#0!-l2Zygf9rii~?FP#X9aJ?O;MB8+F|L`0d4gilNrS(t9jZKh^=5v)ig|1Dq#na_gnpO}`VnJ9FVog!SI3 zn|`Oe5p%`a>fy_s7RmZS25akgJ?n#)J5?L-54tA=Tl_*2XJf-UoZ-}o`c%FeFCZ8%9;-n+^twWrQN-O!y z@fOP%e;i?$3zvKTHVo0X`D}>Op~6(e=O1JChxex3Ef#;Dexqi$sW!z2wY1o&kjX7CbY4tnLfQiUW7RcU z_+uk`i=ByqxYqDx8FA+;F19J5KI(4{^qeNa=eJ27N8jP*w1`L zHu6`4Wfp16-yX8nKLZNWo@yMspv_XFP`JQzGv3oOGM^NHB~a~EGZ@a@7F{TX)&^TziH z^P9!P!W=$6CR`OvnYfgY#nx!8a2xPO;X?2x;dYoo7*qkl0A0a5ga?6H-f2Gs{HgGD zU@oT9{su5l+>*zF+2fMOgSp5~W=a1^cnUZMSxNgpfVoLq>$jW%15XaqVHVgCo(;|r zo&&BXd=Hp|9{Ojct}DC*+(`H-Fohq|&r)!KFl%iG;pH5`cNN1*IP?^bfVtGqz-z!m zgg1gYETR1-u-^L$eiN+szJlKc>%FhwUEt~Be>Zrx@E72lH6#7^!|(tNDB&yzi-cJb zmI$-(>piXj7Sfl*o`v*fVHVO?h1opsB%`LVQgClHnPqFYFsCN_!eYR*)%b@n3*Gm^ zoctUU&IbP^%t@Et%ZjJr%^W;AH%yW_$|JKot0r6v%#jl9YlG_whjU?QB!)b2bK&~n z0%4Zq4#Mowy9yV8dkS{|v*G3qx`KxYvmonzt+2lee7)FT3m&TlmcfCgT1!m>vsCMS zt>6;yA0+@w_*`L@@VkUr!ucVEH@*qHNccAJ65+eSONAc+uMmC=<ar#$4y%M;P+=V19%l9|7|t44La3$A!7h!4EPi{DMzGQn56E z*>UkJ44HB^DhP9gTv?c>wd;{lveFPw6JhqNErpr)ZG~BGI83Min&95TEC=Ds#lQk} zg>WnIHNtE@M+)}@j}`6@o-8~D%yABHz^u_)Ai7m-ey^NVjIA(ISh~i)*ED{H_N8Ia9v@B%@;;i8$3@;KWXaiZ=Hsbt&cfZ1kCv= z?-|UqKTu&_PO7(gS*kLZ;<8_j=B1N*g_n0#xo0`)ZlbPu7PI)4P1G`6;d%w&p>h2?dolK!{Wq`yj-g?m*cWbb%d6x z)6;9U;im-GYL;J`{M|)XMp0zla;J{Zy2!5%yy#?CG0|$3&8%-n`NcH(9wZQE)^GV# z?Nv_2WHzaK7R@hdaIUEOta7T>xrZiCBK+$}T~RZ|x$|i?Q*Ud7FH=3b3e##HC6Q_M zlZ^88w3@TF1Yd09$+*4UnK@-&1ixYXLHwVgr>)a%el`1pJse)A+5A*H)&2}NQ*18N zO}1Af)Fdqk9kOZQIzgXX9&b}ZAGSZh=h(sa5M0LCi{Xs9^a?ye?2y&C8K&_Amh#r@ z@F1Lq>va%L!`WN|o8scC+Zz!L#-Hq^;z~IDN0H5Silk3c}f{s194>7ER$q_1OCx0f(3Pf^#r`NK2-^A?Sws28Q(HRQ1A3PD5*prS`w%G$_aI&`|1hA7XEl`~k!Y zuXbu=9EVRRa-vk_qGZY%fg&gRz`XiWb=_*Gy7j(K&0mdZHpZyeSEC^A^QnWYo!jlj z@EqTfe$C(sHG7Sd-Gu`jU$7|;3fK)1v0<~4n>Ho&SvJ=#VrL7?@v6RH zM>dc)2O5UWJsg;l4^-c-ankFh!7kK_&hZo{n-;p{4RDZB07G4PYa>W0CZTTLW7Jdm zYn|-YhZ#!G)dNB5xC#xVbA0e)^5De`rNxJYirE4u1Y6_fLsyU?sb!~n0C7}kf@l-Q zRl@&3@Co{w%5Vw6s_H#B<51u6wNAB`OarsGPOwU244h@{gqOr-xhr-Es=Q)AboiTEN$y}I716aOS*%rI*=QAgH0ADP)|`^(Pj zmKCS&f5jOD2?XM*rW1m(Ye#4#yq;3SjfVaitZ`x zbEJ>a_)h#U*O_m1F)i7Ir_p3Ien6QU9c;K!oi{oa%}h0Hqf^6tM$O%bWtEAMWgDHB z{c&+fGs~EM0hCfriPYWfeCUt#*y_wa|9R4mZBDz4@NFzbNFM&$;kBcaqjh-a=;Vm0 z5S<*|1OJ?hq{IJ%!qvc!3Red+C+Q~#{H$;t@C(9q!K*zE>$5Pgh(ilFye`}hthGQP zqR!xV#J(%|ec?;N9}8aw-Xq)_d_cG_SZjnrJpI9k#h#&Bnmc zLmarI*;jZ0_)1}}(p@WjFL;#jBJgTy|^sd5s`bxU#zl6j?YMerta0R0q_no|DAXk@)3k6?}@DYA?9N#KuTuvMQ1 z2L^V;(fR2SVFo@a%)l06rXL25^+h&+>ooCWJbCH}MumG-_aAZDtRCUzkop=ID=AkM z9K~?5x0-hp#)8bwL3KZGDEDN4RuAc zz=8{%BLzA$?x^lIKHtnx$c((m=K=Ti7%SJykw(ABk+nKUj_Vvr@N%R`=ZHTIrMJ9R z?FGGKUuU`Ls#oH_lp{>P9LbTJk#c1@!Yv17Il}qLFLLAr)7Tg1quc2mSq0PCIl>Lj zs3P38fp3^77OWy^|CTCpIz!4;8$EVd-iVTkP4Y%SUQm;|LyeK6M-9&!F>1UqZp74K zp;sl*gFR8-QW_{yuV9*eBZ z`@fgH@oh?|rh2uy+w9(Z+{+_&hI`q0Hi2dlD_q%n2RkXKt9|DLG<9!2qwRYNDR;hY zAKTBen)ct)_C=5#=!j~0&0f7ojxMg0-*QLn}9LDABHcsbE zz!yVrrsa5TT=2rTagXG>UDfu)K#D4O$lZ0mtyiR4U3c&Kq`cLV{lBB0Yv`V#NAj07 z;CE7v5=-Q31aReRdFU!8AVINd3zQ>UC4N*~^v z9I4sXooD^}0C}Cxo)bEI>~jXlDysw9CEYcotKtslv`aC~{@Li5{dYCVo7v#9ywT&~ z)1zZOrTLZq`}ao2k9m3XA08d+{=0DK4J90hU5L(m)je*|j9{F#==_IR=sJGC_ht_K zE4{auzR^D9?6H*|L!TZe|JU^1V|)E(@BN~#B|qp|66@8H0&miibb8Vf?M8wF&S}KX z>A>|Qk_lHustLC_msJyPk1Lz9Jg$rSIa$Hh3T@YObUU3DtPN*3VcwE}&#xo2(!C}u z;osAQMdy%EuQ=!eOe8tBMfJ!uBXx$jbFE(=Rz-Bi9MKsQI5T6cB!B6mZ)H?f(H(Kk zl_ewUj?T;CuYTiZ4I4DBpPygfTa5WHagJf2SuNk)GGM>waUF|B}v^A9cRO zpV{kUxEoz}F$>fq*Z+32rH5SX?q#q$d^!qqSzX~U7B#oUzd0RQrEA7vT{Gg&X|cQk zS4(g4t9Zsa1Fo}EK9wJ|%u4uI8R^8IRZDGb8b}>J1Lm?i!mkLZBm3b2)5Ej#qmY(9 zzbt`CesG%<0&wm4?EXaZL$8#Xmh1H=p?^z%lCQQcbbacxnQl_FXF31X$>@TK!h*?i zJvN>;`#0;!?`BoB%U^Nxxm|t}H6|%^5q7O`dB1GkxT19V)6UAGJH0HDb>p9z{^yR2 zIaxo67$@|J)2YYcVTz0FqPJohnFB{;Z3< zy7N!RVoYb$owLVcZE5NApFI|eX+eK6Tw%k(kHby92SS@CkNB zoO)=fn-T69$2t)=8CGR|%{s3k%hGQCiMUw&T7Fu+o1U=*;=1Wp%wNS~4T@739q65L zN9olJ*zx?pt5B2TZo@z85qu;THE%l*&{Ojkwz}d2vFLfr!b6C1H;xZaD|d@SOc+26 z#tj085LNsv{6pNk@juuBcZasO5CVj_XD+tUdb%1`79x(#{|=atkC48&CXL~2Q}~!+ za|dm#UEhbj19go=%I|1Sg5lQbcz!zr zAu8B@xI-&R0z5z~$tKuAE6F|Z2dyNx;AYTD@+mIRO2WfG&`PoePX?_d+{y{9B;3Uh ztt55vD9}oh0yk(S;a+4aB`N0%HNKg*`Xk=Ju=^qMu-%iQ;bz$1<3Xp}Kfvk__7cQ3 z&F1l?sdhWudW!u4TqoN+6*S4dA5S>ZZj8H6ueDMh5_3PsTsF{-4pk#ZVyIW2vmabcF99< zN1z3V8HlnSxV;dbq52LsP<@w&%Z*TdHwt0wC*Of7*dCZU$38752?RmGswfTq?kBWb$-3Q5F+4bQm z#?D0?K6@_MZ$Evoj*xemBU%^S#munGIq>JMD^$yzJc=$9quyu9O+O8{mZ z=8a}4c$<5Pkl?_D^|1B5{${)%Z0XnRY1HG^cXQY2pq|GDuG+fVtzDBlwalYB2JVA2 z7cM~zb*NPxwrrytBJx_@RpuIZVnr>;ziJF?F1Pz;=feGB{vB1%uW|Fty6V$4Zn`;H z9bMzLu6c^DSro&R8iGG_Aq8F4ndjN^;pQNYI!~*?YZ2zCnz&ZSuw<=!xw%uB>)aa2 zI$tKnFjdW7MuX2x)pVVEQ3{VvnV&HRKXc4z__!Lo&aI!_mZ|aG8TME6UPsQbC{}N* zb352ncmP!&+y>obq^htU?ad9U$$B^2odzqrnl3r}@!;{g$;`i8m8^Fwny;$4>)m!% zOh|28@76Va>ePC-c3K2kpLWb%hzD7jj4hBxv($iU^|D*t{G+-G7l)!&lP0q04f*;?U6YSEP?CFAnOjT&EaK1=xjySguSH%+|se5PB;@qzl0LtEU=0d?n_jvKl6 zUH8)1^NEnFf|0#{bDOje*F$lLZYh3>%s~l$90o+sNm3S^$wH3bCu9@?{yt9 z@d|R5$2-YYJ#K-Q;jUoT;6ma0;Euu#!QEoe;?on`xnAPX930hjQ2;I$`%d6%gu8%8 z3U>o@D8zU;6`CT<7D4YAg?$P5k77R=%&`Rh&%*tU1!9;D*1J35a2NO?v1iqJRQPGI z-o=S*$^x&UJ-7yVgK#bI8^V11H-%e*w+pk~`v4qf;6fNa5r=kQ_N27$3O*=&8CdW7 zgncit-t`H-2CR2|g0BPXU7z5Q-~h@BHUm$Eyx&#f`aHu1k1Ln~U z+Sdj*6=qA^N|>u&?SwfHqku#D&j)uGz68vH9POD+1BH8nuhfk6?+*hH*wE8(@D0Lz zgK@%q1AQP6erAI8fk5zVus#q7z7xzrIOCZQULafwR>Jp!9}?Et8ja{-IIN@tsu^qA z8e!I`4Z<$?4dDvlH-+nhw+lA|bI`zen8ydmsIGj;uZ0JK4-1D`N~v~;o~{P7Eg&;r zIn*Ox3$}%s$Em_Mfc1eu_+biViv2Y3MZz<{lpnx&m|Crb{|JU43P?&Du$$=EHFnD?7@4f;S3x1#co#V@Y=y z-V%p?;CF-vgZGlr#MZ+@9290_^^Gv|`-m_bF3N+Xf7W#?)?=0f9%7??AvjT(jaWEM z441;d`4&B~70VL73Y;T+J(z=I`k4uCAk2KF&==Y>%UTMvgm)*S6lZ|@2v-GD)g|o1 zm|h?+#UTegTA1~IiZJW+Y~l7`t!@L6_5hcPJxjO}9t3`xj7|t;+taWi%tKbRPlEj> ztuX`jik`O6L737^Qqnzpj-{h~n2fqd|0m919>k$NE=HU%#zclKjE2uh7Dlv2buv0U zE=w4-qXuw2;SjiqFxnG?n%fxw#lf;43G6N!i;B$a3*-Ea5Zp5xH@>fa1QQo{8O3_1Ag+sS{Vko6}Xqy zm;pl@7^qN;5w!&u3$wISY$xqIg0B~5Z#G7_H+YgTOa3(BV(^`0`}vtWL41|Jfp|L+1g zOhQi#a8w+a>?b1+)=qcJLEzX6`RU!Q9$l5MdHSVRNjKFSS976S+#J@q-PI4Y9@JA6 zwSe`0RoWt*2aosg@_}mD5|?k(Y+S5BAN6ia_|5I3s<*;%PpyQ}YS}O1Ym?sEw65r@*@vJb!Zp{!Zb4 z**)-|q%i7jpAmI@@_%|n-#WqgN5}W`jp$#F+DA;>SR-@5P z9N1^X{{0d7CrbaK;~jIG;l1|M|IiWmuLTr0Kh21{dHR3*h;KZ~u#Z3p{tIVc-wuXv z*oOblhyR-zM(7BH;L3Bt2E+K<XaRjau7)ePxjijo+%g2a9BWe6? zM$)v?M$!#Ujij4a8%ei+Yb4!WY9u|d(n#8T)<}A?gOT*i8YAf?@SEUc;7`HdwK0-@ zUST9%I&CCJmHe9{``-z#kgX}+u_fY|sas~Nl;laHr;i&meA@I8(`QT@e%06!ldc{; z%FrJ#P>K4Q<|-=qKy->WTd3?E=c3zrgIlY_2CWL$1skQkvSV(AC>1 zH&uK@(RoKs)dmrkQBV*Zk)0pBIlEpYdIa_A6*Vl;^MCNCdK#28C<%U)ovUu#5s}j) zT74fJQ22zJAB--_jNBYy7*?cbRNndqc@S2i)&9@?gk&VXjG_6sd z8WCLEXpZ@GM6hDxT!h)Gab0!Yjv0;f6;A7$bWv(T@cX70p}phVW)CVgAh@E%MEbqd zVg^q$OBry;-Tdf=yfHsWHK;Tg_2zgP*i&s^$f&wVtk;?^xOT<@m^3q71_{ zdYi$Fj}r5*o-ku{?a|XF8wF#pojAJS>Jc+g&QnJfj2b<&0OFFm4H|^bx@e;(O>f&9 zS0m%^b^ZI|8vg?|1leN8XMN_y>$iT3N(#O*YE$OlCnVVN>k2!jjShAWl$q$Lg&DS) zMaD&C+>$_{TMS8@0?a#e@VlB+qq37JwE4mXYjXF9w&67j3*j^%ke zWI6l|Imh87l!B_^@HHrj1Szd+UA&NtNRW18sW1!gHesgqelmpAc}SS?8=J&JrF~=+ zH4Ec;VHUv4!p!LF!s+0n!py@l;cW0J;au?N!Uf=Og^R#Hf&+}K7?xkfp&9s3;Zm>% z*PA*Wz|q1(z&_zJaEkCqaAo09;7s8$;2Oe{z;%SDBYvY;EG&t}!VAHr!i&M}gqMK3 z2;U6uC44J*fbcT#FyR&8QNpXi zd|dcD@F&8*fO5MBUYBD@m3On5CgD7+4Qx5EMB zL0Ik=hljuq2|o(nEW8E$gz!#qx$s`_)4~VA2ZUb*9}<2Y{D$zG;CF<1cYGLzr1RSR z0L#bX@Dun8;a|Yt3I7H@Cwu|?n{c=?j6Z~vz`k&VS<$IODZ;4ZMrC0>05gSCS#&kT zf^-;lgn6qJ3-f{2SU4SADqJ1hPB!xy`FH5sX7&0j0bn!iCf3A|A_9lV!}rx*2~6GnRzFkTc3@8kE$$S##m z3Nz5t!VL6F;dJl?ay7%CoE;Z(1~@|4zU71)K%OYfhg=1lQU1(jHF4+;&KB+u&KDjE zE)pINZYazrUNhlQ;MT%dgF6b32X_~q2<|I93p_X=mK$LiA$$|~YT>2eiNd#lrwOkB zUnhJgI26t*@M4i`@U6o4fbS5#53Gd)>tVT1ERTZM3-gxSNk)0Hc=rgSDj55PSuqa^ zr-NS;W@UU^xHk9$VK#Ih3D*aI7Qyx(5j2G5TXARv{!zFU_*daJ;6H`ifjwy8>6a~I zv@kEFPq-U6MYt!pvM^iBOyR+7|7(b41RUxJvo)+M%+|0iIn(KubrwclGkOZc$>=Z4 zN;gzE2Yi)qP4GD3eDJm80DSUZoFNXy;Om8(fENfe2TO$ef|m)81P6s#(e4($7JR?( z0`P;vi@;Bj*?7PN_P8vsk&%3yjJI96Zp7n`_$DW~5^ zVfu}AIAG9oHCk#op0iz?FHFxx!g1h+!t8W36K1p8S~wNlQJAf2cVSlEzQSmWjKNX3 znHe#!pWv3G-Bfz; z0%W`T1-rb{O&P&^-|4Q>g5Th`X|Tz=-BnreR{Y)={1CtMgZb}uSEGX0zc&l=bKo_> z*6(*W-ygJN<@;llsujHOL3i`$h+vmvGu4XVfn(kALY(+vck{?i!BHRP<2lL~y#9Eux+VA^eq)2L;@1~EcRV+|5KVHDr6;danMic&6E!anIF#0p zOH+lqWuxet>PFq7a#FS6yC;%TCLjf-icH|i44Y@4A_m@hvUY=PvL|Axb863%icH5T z%(D>537>%<)8m;+i(22 z!N)%O{qObhVqRZQ9_FjCqt3_YE%UTTmo7`1X?`1~3mZk|skQp{WpUNRGEB7Q`q@vD zs+-#_eKsN{$9%!kM;b-um=`TQe|b`ldDx>1vvK}4Os|NHNeRC4dBVSxT&h{Op3Zn6 zsU~Vt?ibCwjYDoNGolnHk0)&0r0IsG!h2JwBGYWQu;DyKqNl@CL@rOBuzU(V5i^|= zZ$fy;3GVo!jw%j*^2MG1jp8cQfuX(%y6l>yM}oh8<@@`>+tKptD3977?EL*KbAw%C zIboBKFe$ZIlv=dB#J;7-5v{7;(0*$1~UTp4I*ik%hsN=e>a+FXh!Le12V2QSI0@UDer3`|)6j zZ4#T1UOhdtOOu0qK~UdP+RPiW(!S%n@kGu_y z0j~Sn;K|=onq)=8Q!<~$=I@BWlQEjO4b6+EHXo*z$I}8T7z*U$)@%lyFsnax9talv zJ~pR5q>+gwh}2_5&4ahd$cD>di+%HhL2&@p@vKv_Kefe7qQO zpCDC%I1bv(i~ACl^@cb;`{u@-f&BV7FFeeNdpbPW;zDKbay(b2$FUHm#q|!3y-?I| zqwQ@lBsav(1ka6|2M^cBJ!yNp4ynE_t`%Ho$K@a`v*Lb(&dfM=LubUDgM4~i4@5RC z?rWrEY8;;&Q{v8r2hUunl@SYvtT+q5$S7asypvym53k322ggj}o(ML**h$?JTyn9x zY82djvB&TXcn^#F8IIv`ZQv^+ZUPb<8TT8)h>GLeU9=JZHk3T^Q#?pc9Q$QP9LJ$l z+*=6SG~zEo?ulPhh0bvh8F3zDLK)_J;lV+Fj8c1o&;C&%E(YPo=c}ye zMiRRHIR?*>C;$8^emo*n&D0@W1N5W%2OqoCpwbtVbyezlO(dk#!9tw*$O<2-lrRiG-iaSK#j;d)b!tw&NTI89R=J?0ozE zI>A)M6+JAZ&8$k$wBH%j8a~8pP1j|n%1K?yQhNM$UICOpJg4I)*RSt0)x^5Z@H+Pq zoIY&Rv#emO^$dQj*IokI+pmRYC36{VbKMX6ktsGB{h`W2u@ZQuLyw8bq+D zjCv1f)1!u}CA!R`n&7m?qx#j~i?ESX_&qy*h9Om#CWC+M|ds!Q@HBvHMlG*pstwQ{onP4%TP)i$sVC86F|`Ch|l z38zA)ryp7Ygle5rxM1O)dr$)!Y(;@6j~Qjx2HvBvt{D$s)fN&xCI`euLfp`<79Nw; zLiIN4&1ad%vl80vj#_g&JUL47W;2fMAeMTosC7cIRxD$9Yu2F0SFJK#PbglnMZjuH zm=DLk0rnC*s#A6iJOaC2>by)}Aqu%nnIqx(YGf#P5@0sKkC0KfR51%TaD$`hF>>dK zd!gH1oFk3go5Z>Mzj2N=a+kw-6+*wtcDCz%?p=^Q0@*B=>|w}w(QkER0rLhF`c?>8 zmXcJk&Jt<%DBl?dIJhAVXbx(FOg%PNaw3(g{-UT zypn}CM`c5uBPP}Q;F_r1If_A_q5j0_GpO(a6e{8Vc1m}iUAj&5PZ6qq*luJdpbH{Z zl{mYWGlTC!wp9;`R2fxyWl^ZAD3p(I&npN#VmH*1zt?X?s;sLoLEzk6;hs0Zjaez< z*VwV7w8lluK;fjWfKVBYIx&eY{E#?aQr1D<;V%VSEy8Ft0(XHb9r%}8L|vaA8l}>6 z3z6mc9g1y_-)^Rw&4jAIVKWY{`WrOgh+>Wzy;pCDQd#OleJDy*N@YXhXJs}@2je$p z44*Q%T`xqb>LYSdxx{fa{3f(#)ntwHKLTiJG~JKeDgFb+cEO3UX){V*RU`hAl=gwo z#0k3*?99J}%_kx2iPtXCfoPSi_Ucv9sz#s|6eD{uig&1J|C|Ehp8OD}h&H$x!#$r9 z*^z6;FD*&Kn%~OIwYP9x{Pb%yPeT&?^^z#~l7dz#D23!F%oYRT;Polywkch|V^+^W z!_S9Pz$kqS3Ms9aRJI9r&Yf&E$CLmg^?Ke|ezqM(^E;WV$@UFq7j^5yD12UA-W7g= zof`iNkkK>_;m03SygQ_vI`CQ@R;*%Tl;+F@N7f z{$Kh4S@aJ+Icj{*EGlnZQN@M_AMYPlc%w&I5xA@>3iU@RBMemw&#l`%Dhgx^m~rcb zr7VM&zhAl!mf+N$qQ*!sGwL58UMoYpN*gA0&@oMDaB|wfuov zYLwD}KYWoYN&63b8|rJfdt%Fb)lrA7;HfcLdU8x;mQFuodUUl2e`M`CNF9Xs3(52+t_!|1ZL}VslQGk#E;`+BklrjP|{3Uf)n%n~`Z`qV1w#?lX~> z9D~u1f!lY z0(Rb7t89CHUDQYoR}J+Yjo`PDeuAea`c0mi=^q-Y3ha*{5iRWRen9&oXBwr(eQ2;k zhL_?A*jZDT;YdKs|JoJqpI%}A`U?A>SJ)E~F)mRj`+1*Wv^HuoC1Mz1;T3j%UZdeD z10QO8i=DcDjE@e}2O6unX8pqQmQ9pp*%L#O{@uh7edf)?WIdp{TFE&fxd|*j#hL=c zFjZ&Ww1sNYSL#bUh?3A-W1S_(gz6DhyWt0>E)Kqn(bz$BcQ^+p$|)nqbk~PlsOqYR zKHEa2R_NhqwHB>jI5QU2Q>T}z)Mh;$XJ(KcQyM0d0a8Ye5xMP+0vJ0#o%Qrm)w_bE zwFAUScm~oNIi{;Vg&0(p{uNq%`#}U#4@VO34j6Kb$oY(*oIdT`$xYKr&%Iu0$Mk)N^P;5tGy5L|qp~fmk;*y2A*n-lImeAIY~lYo(-#=1=M#MJIj!_0ox@4 zE(Y5ri24*Vs+%rtqk2yq;|S~uQ_)E*IY#7c$Q3Uk49htQI+T%PM4k=~=_~dBCQeC6czwr;E2a0tx#MKVUX{)mcxNe*V?=J3;2=kbS)dMO4 zRmOBn;^l@J&Ch7GOoL?|j*tU;)nvxu!J+2j2c`oK$|)nqhC=RgTa-fk|!$}a`$h>?!RE^+@1=YQf zu*`TsvVTEE{bL8!h~gd)@lZAN{MRE=bhnNwoASw!@u0k;YERK>Sb0FSVxF0zzwW5K zly8KL2jrek%8qOotUMqp7SYSF@_^|0%btpQUME$XzRyC&1M*#>iY^)Kw&=@FstL8y zVdVj>!p=@|I>5>UqNZ0TSJZ1es|>1+g^UN}hdVpIm%_>eqNFa4Xd|pVAQ~c~!?5yz zXoHBpg_Q?H-*$1LO)^okI3OzR>V#DcD-Vd4il`5)JRmyKRaHb4!z3Ztd2L4F%tL`` z#*)(kFcYYgZlf`o44vsX^9bqBCTBQ0bEyNa1?vJAa~>b%1(4riqKebPr~hg?AkZ~9 z^9TjHj-2TPx&fRBW(DH>z7TH{#)HZz7p?$)MmQb(k}x9<$D#7@!&}U*?v%o?H==}- zUCh8#ZT!G6gQ|{=b|)*O&rF24v5Pynn0H=CXAn6ck&SRUjB)W47teF?Vi(`$;-HJy zxR?*7P{ey&9C*=XIpX3IEFKe+f87ysd63zaSuzWo9& zmz(3Yww(D-4q3pJT%6(JTo>1NaXl9|a&f7Pd%Jj`i-&^)q126bIdDF1NIu2I_Dp8U z!3{3?A{XE6;@e%k%Ek9y=76#Ovc-7B#gDmohl}^P_%#iPb1r_##e9Vhh5xp&o&OJ94j;LgQ(HqGesuA#F6Mi7NGA+Ej}ZG^oa*ALE^fn_ zsv!?OT@J%se2t55bTJ2GLw?t~c(aRlxtNnsL;7$1#mN6he_1g5(8Zi#8uGwdr6Ept zabp*^cX3}A&vEhNEe=~dH()looS$;> z0Y`NkRJ{@#XZxvqeX6fY^~zI?6a66a^uxZOsSX{?{pS3BbgW{bWS9L+8U^Z z3X5~aLe2wRJl4e%$&!$Hwp?HAui7J|HUm%)5;*5eK{Oqr3X-z|ip06O&3YSsi1Pr7 zvh}F}D7tJXg{z!EJOeKWvdb07OHLrA1MS$CLaRu^YK-^3P*nVOkChv?=}*S1FU@dr zfj$mz+2U=!%iCgdnxm>6)vQ66Rd=~mpC+>-1M`xjIsvMod`II7h6=1QIbUxagzMf- zpB{v7P6fJTFsep@9y=JdAXGl+JB4awN3zJfx#R;~@?m7Yc){Fc>*%8huCHWlvrBcG z?d+)Tb;#j{;}n~U!wONnlXYY$7Nle3L*2zA(Z zxRjnDOU_<%@mnsP6Z+YHQK^9+T%wCERW6+h#TiSM;3~T~!=+QpB`!S0bdt1u-Fh+{HUwY_HOjFgg1wq|Z4PAr2gMSw3{J-CYsSpS$E=yZ8qe z|K#Eb_A;dUafU;vu2*oe-Mz__7qcvvL%xd(T+F!OMX|3YspGVf5La_?u8Z?s+{DE#T+HXWp zri4F)a;JVoID;fL_2j6yy5-M|SKE{_^tBUIY7y^1dkjr#;Xc?ykF5RRP{=P(j*yee z@1LN8)+By+47v4jaT~ruAp10&62h)1&1o_^Pc-?g#W4p2) zG+%i1k=DMH02f)=(|jPaXEUYP&c-*COLr@sxt2auGqQA&#*iyI`X|UTTK_v)dND28 zmzD0wOtSPVg5+v)7d=W#x)ab|vJ_l%_DiMcyCbuqHL$&1JcbNEXs3+{!c6y6VWw}k zFw-$lI30YWg<4F{Ik4O!4t2mQgn23N66WyL8ex8DctE%WyivF@m=o0)W>fG^;TGUM z!Y#r3ggqdT7%T68k zm?DJhg1z7X9hd`t{w0@!D+n_|)r6U#>cYLi4alf(jJT;VBW@+kE7(Ce7u-!a58Ow% zHis7niG@$DGU0~cF~Tg;3BoMWslv^{vxQrO=LvTJ^EH@>W69kj%#vFn%nYow8Rg#} zmPcs89fOm>_u~-r;lq9(&GRm{7h~d1qCTHJT)K+FvtO0Tto-(3OgzL{LcdZ6%o6%j zn3u*Az->kc7FWD*b#RI>D`sV3UcXG?Cg2*vrQkZk%zd%&NHE{O8UAQ+sqj>=y+{-C zz;synz~LHL=4Plcb2CDixtUHzR&Yled|@ZEiJ32qu#B686T!C$bCCE>;dJm_!n~_D zh2i=qpuu6=!iNl*Mf9XF+lsxy>EP#tS&8_(p*~CHHQ~D8w}n}6_`IPG>&HjJtjM1V z_hS3=Gc9N_n6ry6Hp3k`8&m--Vujg zU=F>6R(^gj%%b~In57bi)-SXQG)5Tp*YFFo{ZAD}AsJPKS?Q_^XMt-*;QBM599Rm( zp$53VFe_RkVYc~Q$hdK1!M$8OK$vx6m@prbql9?}jTh#noFtsb_J08_kn;9hBFr4y zizp#yj@MFN4FL0ii+7OIZSh}gB6&v~rW}n5`7Pmk;P-_afKP%0R4Rexv^bQ4zZPx< zJ}b;h^s{g~@I~P+U=uZ%;S2#s3bXNv6P^T45}pIDD0~y!|8%h|heM9=UEo^6Yr%De z9|kuN-U{wW#zkcl+g+H&-dC8#K3JHSc!V(Dey_F}<F19HJ&EShtPGxY)r5T znyu3eyjZvte5)`UnLC7gfVJ>o@O{EV!J##gW5Jt3flhco z!7mA;7!7+3B{HxSY_FjN-vYMRP=Z;y_8LksZ_&@hZzhYH+m0~z>%eD(cYtYBgf2Ym zN!5FMcw+VabJRvJE;J28QJK1IQAB~>TQROo=Z=dlEKi)PCRh{Gsens}8m-a+Fr2Qd z(g83S);2<2ydEwd;^MI`p6=rLF22oXz3xr_@X$~rAG9hbc;v;r@?rB;D^s6ds8TCW z7&m?N)DaWv{M*@Q3%D!M>kC!5KDz)DJ-I)T-nUStmtR<@#+iD{VpX}q^wG1Y|9jun z7OP~vcJaUXzGbn>EPr{iDpGn_L3EAs%q8kq)0}JS$psNP<~?$LR?cU_^pTq&KWXZ1 zg%LUGw2uGG-$Z|%7nh{|)G@cJ+*NbJd=+o(j(!jdZx{~NYh=`JhEgp(_XP3w&iko4&GLzK-bwRDb6R^~q4bDC)T`_47J$naZug4llD( zii2iCugjs=)ZKTQmGsnQDy=3vx^BG{(CYwwrh>OW-C}WM>m3NTX8INC??XLQdbM5Z zZM3x<#W82O@>ST-+v(&p$O4znNZlJcHHTgis4#u{cGNfzI!g?q6bDUGKlI>nrat>3q2O!jdMi|JFMr$aqU)Y+zn z)Z0T3d9q)xaWa;A^#0=Ll!U9Ukl*If^Or=V1QuN(XD5F*J&IwtH1n>Ae>LJ?Err(- z?xFY}y&^mg(>!m7;QWNbcuzy1-{0WL^&JEl$w&@UVmt=swSwb#7jmiSMh>(cTgG{I+{A<;u6;#-Lxw{S zMZo~O;0OiS+zBw`-KdO)@kA)MkcPtR`7e5i|F>$+)*~UGAw}+G;?7BrDTa}bz8E7$ zd-_+zBmN~oEV{};$%F#KITRkpi`?N&z8iH>%2!H4r}rY*aZFxvJ32L%p?9*eqkQ`vjyIi`{4GDw|Y`>ilLb z%b87t6y~g2Zi_I&!sx;LEm8+IVGbQ%U$MUV2u@~C{n#dz-;8gu5yi)#1sMl#BO292 z8N~hyd1Rbw1&)3ng%=xDycF!klD??=)G%V+3)9Jusz#MAaV)_941Sf7!0jqxqbI5F zppv*nk9$)|JMV{b^JV^L!G2`Gbb|2A*`HC5O78lg;#2gLRoj0q-YMj1lvq}#vM_QDh&l}T!8@yP4eyR8P zhaw4K{*_2VxF0vE5#jHToJRWlA;2jAvq(*}k-UK+#!VTHX#FSbQnBYQ_)Fky8p&0V zX;0i5jyC$e2-Wacu?sESV-GqmMyjIQ#Cg>1fW&dSV2i3?qL4dnQ3EQaA}w)kRUZVA zl{gaN#dTC`5JauS*Y(p|RH-V}e?q@2U$}to5M{vekfnOe1*JM0{@Ub+Un;7{Dp1^OC z$5-?@&VEkhGW1#0zCnV;pZK@w9az3G|sI>B7PpBTsd^JjM+pco3 ze)+xa>U#70Xx(RrDyYU)*{Ug*TMR>ZwYjEWHLD0~kk+>PfgP$(f~_=)N+q~_MxEo~ z<&itpAf-l?U-hIKpp>ciJ*6_#Kz;HlRm~rPl2L;#hQfY8=2{(Bu5QYI9;sF{EZ!Vh z2xSWnwdSLEH2V3_gbVpadKjgjEmzIe4E<-hx+VV(){`x^Kh#6JqpH@_#WUIQx5f6i zA8|IGp?B<3nOMj4?k-hFCFro-xa21zbpCEtjKr1gR^O-{y2~Dwlh3UOR3#4+!$8?V zRBI~Zaa-(Tbd@|+;j#jVK=0dwK-cO0d$3-7hCa4OJ*w80FW##xv+(V#wykM@oT}0l$rQmJZ5~v0i`fXN z%w3n|_SMVc)f2_n8B{hGi;Eytk3EQ0{<-?`gQ}LQtB)VVk}JQycn}N0B6OvfRMoT_ z5NcA^ma3?&w;`!%+4wO!Uy9T{UqY!Y(AT`ArmJ@P)Jv+3`B;?Bd0FMv`vdUMuLta+ZD@{U@6XNlVGCHP1 zn|FK~n^B{C{qpZ#RwF7!F7o3Z!!Dlx|1Y;}p$E^mnwBShu0|$5a_E8z`-kfrpFCvV zk*w;L4^A;39~any+=V`7HNmZ>P;4|Dkq&*d;^R-DrqDc3MlGR9X2p@7DmQ0SENz%9 zUhOo850NiH|Z-_%F_#NT4U{21YPA4!YXOg>v zIXRQeZrOLj99%jld^Pwt;pyN@!t=qr3+Q(l^B*G?);7QJCUC0oE-)u>(!*hJbz$DX zHHF^<7YM%%t|xo~+>VUPz$?;4m<8Dj9H2ucECa-Wr8i7C2h7=@)Tsd;FPsOqH%^7T z7TDf56qOoa{IGBj@D^c= z2pHRic?ot24*)+S%**kDFgud32ww%Z_f6%_yJKLnH%>*w1hyvxL`y^MoG+ z7YaWNw!i-&9M&8A`yY4<*#7ALOj0_V+*VTVVV9ANU9Gxl25s z1g{bP6#RhjXJAfNq5ikvt-{}fcMAUi=42J>oCoh0{snwc_z&=5VT*H^-VzHR2kd&& zb1e9za6I_5a02*iVXhQ7D_jx$vv4)=Md2*4iQAT8vKP;Nv&dXc5J%>Ka}g{_;?MwG zQMd^>UAPo%?*a>bz9iHVIaeRl7485o5$+6bD%=g+jf_43(q#;EaiB~r;cyrwj276K zNk(%@GuOong^><}lUS(3W`DVGGI*shoBex)v%%|xbHNah? zh@~Z%vs>uU4%|byAGn|JP%uX^s524FIUnR}!4rh1fH~iqaz1!w3(o>`BP+@mfNul` zXjufyE#j~kyh3;h_%30#Tx*2cay=k?7np+y3}-cXt1ug}ox*&uabSTuYr*@4*RlOS zD3(Xza9Efv*IU9{!S4${1wJm!hU*L&wXYBOCt(bBIXl8aJ_O9!In*fw+dIO7uLARp zFd-4;KMs~eQDU24L72suB|H~w?*a?Y^T75lu;BS%dly*n&0u>MSn#c2zCbbJxrXW)VYZSl2y@p! zPNt{dO5h{HeA#)|-uV>{>98CZhw9)@gmb`O3bSSWUbq?fyf9n9--X!%a)_0YaV=K3 z@DOmU@HjA=VCu|d`_J8QXqgWOdwW>$t>6O6)13R8o6&{1iEuvRYAIX?++Mg4+*P;< zxVLaiFgMs?IPBmICkJ5Z0n2D{U{8kcT~rzYo+3O7JWF^C_y*ze;6=idz&8s|1>Y__ z9lT0-4)|W-o4|qfVqqu|vCdjBF0ETTV!+kic2uo-AqaI|o5FkhG{9|lel9t*B4%-m-R^Va3SHuc%K z)e+_`S{#MzPY33{u{iJ^EfwZH+D@1`?jrmkxR>xYFjrYH9NvDzgm;0t(HiB?fyW9T z0AEk8h)T)!e}P!|=;YWV9a6!|gsXsq!s+0h|+^JG+lSn%({Nnj(!k+YJ93$w1o3a5iP-;aJ-Nz;TGS5@I6a8?Za|4?G3s38ul zrA^4_V=>~E!U^E^!pvD$VHB9rTR0s&P?)(KF3iZT5@tihUEer;hwmT8oWCr%<;dxH zAVDc8oDaTRnEku^g;}c~5@!Eyvv3pe6T;2F<-#4oPYd?~^D9I`Al>P`91;h%^GAhQ z0Y4PJ9{jQJ0`M2Yw}HPCW~DnPybk=E@CNWDVb-T;6e}~i9~|(D<#||=gEK&VxS@<_qX|!f{|O5u=U|{JSu_ zR-B>0;dV{1+*&Io^ZylRhBq32j>Zwf(wOPgBu9912+|B zm#dX9UphMo_Xc+p9tQ3sJO(^S7|Y@VMwwWq!eNZ?jo=BwH-o1Nvs*S-csckc;T2#m zP-8AN_%30tqhBNZ0Qdpn4d9J7qx>I+Wve)B0q+!k9K1((CwRZ`elS4BR+PZEa~U~I>1%Ugoy2)72$7j6r_Nw__j>-iW?NAR7({35wp zxG#9EaDVUy;WCc@a-APNkA%Zsa+Wjo>N(+6xXWG=)?lvrqYfLBw}l@8e;~XG{E_ez z;Ln72g1-?i2meG4AfVl_ToQ+8z)|r|1Z;-6!kCf01gmDj zjfBsFTMPdP?kfB%IM81#mtYwojFmIS1Yy48O&5*;&k^S6*L>kB;G2Zg!OMhmz$=9d z!S@K)2d{THV3feJQ5-np#pR034I84TgxL^1EzE}KfH2?v4heH3e{LX4za7Ev2(wZ8 zP`EqzV`07rei4uBPY>6^@|`#=2A>mN4gO7dJ(#=IQlAY-n9t#T;27a&!Q7sfI)}lj z!f%4B2pA-!c*0tAh(P z$!Xxx!n|F%STjI}>aa`^hkD>y!i~W<2=i84B+OguX5k^=+lBc(_by@X&vBpdeDH(9 z+)rnd@G|bFvrQ~_!l7JPgP#$80Q`b5_uF_y_(|{)VSej*SD2gAa>Xb!_6+zF;TOSQ z3cm;DO45WZT>n$BoEL{rz`qNB1?IpY1B$XZ1;Jr{qVWn>1at2_>STc{3Fm?{gbTsk zCzv|*!F7ZigX;&x(gKzy!fn7Uh5LZp3v;bmSK*Q1-on$t1BGt@4;K!AM+@`w%QeCe zfu{;rc@&n}V&UhMdBS_Z3(L>kYaU2ZmGzG8W_PthTRU(Xt9$dbT0e-B`RihReuvo& z-~Lh47%{+XIK8>>~*7cr*`0O%$nS*w>1j{Vd4+y~ z*6BL296{fvui`07?gEey>2LR%Ibmmpqao9&`yd)T!I}8f9z#X=`^!w6U{8F~bM~3OyoV;BQTxjY zV~4_h;KKth75mOGy>p+LgMAe~+-K$@E!NXGMe2NGOmr#r^3RL z^{S`M_{(#X@NY{}JtajY_WwyWFODuh`?UFbc-$?BL5)D!cVZ&-k-HKr1kR%(Vs_G6 zNH70Wj!8%x{?|fm7~R5{I+MFlSrrT64^x&Z(GnTKej)xp$3ylhf^%TQJorZ)br4p# zX9ZPNB$s}Md)Ob2pd{SG-fg7$D#V`JgE&5FvP}%Lxg|@dsE8AuwfC4 zi0n;Tj8hIk> zb;HTaPa_e<@4yxA95jz;#D_;*R|JdcQq-}O$(>J3{=3nrVW*%N?};9`4lcg;k;`L- zZ!7qq;oA*`x<2llljY+F2l(M?y6DNX;0`}`utVlM%5}!`!+iYgq)O6j== z&6*wgAuIaX1@P+e-Up}Xm!Abk`X`K@g!u&IXfMA#MjzqBKGtg?bJ1^;z5c1A#~9Ja z$Z_5p`ul@s#lU_9Vfgs+t9;gcN%g0eNEU^J}(Utg#ImO1X0D z+Sq2#!y&dhJSW9=yap2QYX~~FGZSohpCflAEAJ`&%u8l!r&&lqY;Wc{+{>?+u>E&+Yv4hD`UVa#k9cq`0f7;ZmjM(9fKGw_cb+Kh+pJ8s$)m}D>i}=j0?CsnUPF{Wi z^LAyj4exJ=+F_5kl-nHhn@h{;#K0+b@X{8Z~}z)zg9 z-mBr+*YIgdyaTCI!qg-gvAf`q?rU;03t}|9`I-_9@1Nvm_dwO-okMQ!1j=pjd@aba zUiJlirDUIXHG^&$pe4!M3qkl=k<+|(idvH^qnf)$lb zH+h5Q+RXL=WqVq?T2l!xF^-RBwvU_PqZz-^L6h|z|Nf)Vr=~vgmYG&$2i)FPV;7{E zy^mvi#ajVt)%ixR>|4y4Fjjq%uJE?muG^i^jhv#`td&L~4bFdDcpVYP@ZGGX(P}8- zOUgP;$pT1X>?>HBgm4qfSf75Q3&ChrO+WUw*{ADA@DZJcH-`)%L9-(A(kFCb+@Bt6)!ugsm~}ju&u}F*+WM6-4C2l!c0!G|X~<97K-S$>oKzb>u@>DJMhsgeN2~PbF#YjSGqLh6MB$x2 zgenX#hE1BNY}USa%nIhg@bdh3%#}Jx{e;ED$$eguGP%S z1I$+l>huD)wRcj0r4KBf#i0z`Q}`-ye_?d+jG@BUfUgps0v;#K?%cJ)OTjaQmw~Sr z-U?nId@umZ60y7qUM74392EW%e7Epf@cqIU!F)+!ZaAA}vv6%NJ1dme1D6XA0Y5Fw z@x=pRdtw^~7>z^XFcJKQ@MJI-x-p<>;17j4?)b6rO<;BqsB<&;J7Km==Y%hj2JL{s9YR+%|dQcgbToxh1u`R6z12K8p3VB^@Q7l8w<0a-BOsN@9l*Lf!QBs zT*JY=ZASTzg=L^P@VX8co&+8(%xlaY3+Z_}c#7~$Fnf5E&jsHg%*(n+cp>;^;iX{C zqo+Pc;#b)d+hDmLmV3ovJ$SwF2CzMy3p$&@_H-^V2f*ynj4bONVg`IHv4kR@O)LLj#WpAl{g=6*wzcK}}y?gVDrK{?-2B80nx zy~2ILiNd@b6@+;Ss@Xd!z%wsFwm7hOxhD|=WziK0uLL&~UIT6>{0O+UFiWVT@C)GX z!biY;h2H{me%Aw!W=rhj~qZiVX&+hhgk4VGTO^xc(A8-fg6GMi=1E1xH^wI&A^9+TY%pZ z=H=tcJnD1?pA;SjJ}q1Z4ty<^k+7TTZ79P*2FQx|!P=>wP$@G#!&` zb;sz?V4l+TW}a&43%S-AjNN=w1GYZ8Nlj}O{t@1eny{5=D-Wk6-8;|9S5N3bo>kr4 zH$ZR5vvTt4oiccEx;D>+B2TMh5BBKje9LF%)p7pi>{(F~9b;r2-D6Br ziXH?dr1P453;%gm2YKqF_u}NORfNxu$ZcJCsU5s@yi)j@`iKnwxd#7-HvIfZ6rIJf zoeGI{t}RHnO;PjH=o4l#1>YY%<;UPoP}omUym{X!@>Bla8`Zu z7JXd{D>H5m!;1893jy<#S8r-zRaM*d%Pp*C73Ra~ud$Rjp%BrCLkw}*x@xJFsbX}? zQmcx2E?$?FT8-;Jj0mHr@c+*FyovA`J(XVqlDyBOosFKh08ysl^=U6;K2JWr3FYTup+`Z~Xb-`W0wsDiWn zFUH`@uAiHO%Y|ODjFV&)~|>hnVT-u78%lBKmJ0e~$zdp8r!6VYvS}xJLLtM_MEO zClFGUpPwb8jYRJIw zzJ#Mo{sH{*V)*S>L;nDz)%0_^h2>`t%H!t~C(O?QtZ+X!k%{oXiF`)-w?h);{|*61 z8)mUS(8_9$e~B5>+G^r0LmvD;bDSjB+ei0qZ53d*gGH^aEY(kMY;6rP-;36sHrA-X zaK!8{w%=gdCubws{`&SCNr&V%NWH&Ay@(`qN#?f!{5TT z^hxf(&`NE~faD&REklyIoY&vVb}35^W9nKnw16=x`2$==e;YgOG07Zc_qVexW0S`* z2^|#6YkYD?6t=&MZJChFO+NhHZM8|s9dVKTz0^~XO-|-_N`G(LGCg?+0~nxq-DfAi z#{dQ^3u<$d$1s30)q!&#=O=SgihraU2+NJh6PdbE>Q`8nCUY*0e~jX6#@mt?GVHPH zJy=#G`)HYB=V)c}eR^wKD@Wz(ciN(9$kBhawQf++`ucWOy1GuUX=l~Im3gsU=*oP` zMDWUZ+gkixkdn>>BE6A`Xy1{KM-{(-ShW{&Q`utrGPr8xV3?X=; zTX2z$pYvD@KUX^{e<8AL`n|ea2UM4EJ*|UPJ^NbL^saP?_0Gj_bhno9=JRrnLUeb| zcuT^KwYP&+Bh7*(x)(!@9g0wW(S7(IQoZ5&0s`u52Qe45Qb^QUd7}`FaHG2-a&APzC8>63~ximy@G6d zSrw!2AbY&WsdFbe+}n-G2sQ>sdKaRMqkFETCDxmT>Jhz)>@#9lAT>$Rs~Mp80umE_ z_mgliya^2T9UML)sSj2%!znjucJKApFB0LC7P(XYZs(KNV6 zmwOOO4==x!NADu{^zz>nNALay$?fHh#ElfaX92j6H(gilVx^id#p;G#tj5(iF+r7@ z^rOB7SK)p;*v*9kqw09QqKj2Ab{v(?RYmCO+4`w2R*v~`oIcsb8m&@vo32(NR{YNB zYIX42dK0J@PVX7F>en6DOK^o!hi+EGxK4~<-^Wl=y>rLuW!8-%fKnmc@t0)uxG)(ANE?E z$~W||sw;+esHas&b<)4`v_$9jvIbXPhC)new2&wwG1aZ-@7_ z9UlKLbgfSMwO-b^c)Nh&?E>-`HT&tdy{$HCvA(6ZRm1!-PVek()l?tolfA79kp9}+ z>V;G1K2}N7&v5b7CX<6exM7W4JH|>Wc8sa1QmWmbh2+XW2H)qE~m|Iyb<^V@2zZMA4z{bp5kPCsN2q4enIgm!a3t9{1P zEX6t2<}`d;g<4)Kv{A6L_B2K6k%%AqS>y1Pad3Yp{WJSpRV&%`WShnOCE=nLHANm* zWH6(5>&Ky(V8{NN9eXDzCZRfd2KCaAF15m-%>n#JU3XZGK z!yLZtxV}5U8YvmK#idAP-cDRoRp^esL!$DS6pIuGIc$h}>j7+c9utET@aUb^0499pf%N;qFn9ctZ%t=2CMwI-^a zdg3svNVU=T^R!sMIn1gW%SjX-H}E7SeozE>tmEC+h{naWy;Yb;B+GU&sB!t@?P-{!HO=om^(sQ!RAIGApkN zX9K2I{5S(%c_oZCkE51V=(&iYbk-}%aIY;c-&1C#R*gK3hS)S-_dB#fiaqd7%J<3^Lg$3E*0443vP z+`=>*l2IsjnliSLY(il8uBcF+H1?8S6eZ1f2rsk**!~yc@f=}*sm3+|RhXs$8Mg^d zS29YQ#{PHXsM|E0`5qcty_1YeN3)5HDn_%Poa*pT@KsrSM2_d*SimuEI0Gy=_MMUk?jE zo6_?Q;Nil&G^2%=fcb5NI;+4_gdYOW5`F}HgYYKsBH_(oe#D~R$HBJ?KM7vN={0mH zhvi=3-C%nZ8}hwidx5gtmi7W=;peFj#$9N}yCl-O>B78#?m8ofl$+}MN9REDt!pM~wgXD~Bh4?cq#zCHL1W(MrRXRw#| zpFR2v3nQ>cpTW#ckdcAeZahqmcJ7WXF5WK8c4?O|?}%rFGr@<1dH31_%Ls>e>ARG3 z=|&w`j*C(QFh996GWO-a6mAXvUU(t+H{lz>2I>j*mjF2kKwbg%3m*cf2_FVm6@C*O z$P&xjuoMV$e7Aw{hhTdRFakXZ=1y*m;567?4-EbS+(+bJgE>M$oo~U{kkKMC_fv%H zgJ(G$FdD*ggE%w>bL%&HXa(jMV{%(CKV6gCgI5W61K%s$A8d~kLuUy15s{AsKPEgH zyhC^*c(=WD7anH9@~k+_2Xi|YMz$EtDNN*B!EXw$1ivSIH<+`ZsPh2$Q{m^qUkS6= z&j_=0xr-b1kFk-tAePU;eEibkD=_z?A^!;W3ZDlj3jYGGApAR+d&^PZQutmV90j&F zE{2?==S3p-ft!K@^pFfoYjNO9181X9UKQL&xE6S@FkfZ3TN-uhf;j<&%oUaP05Z4~ z%&{oSIop=&g!wkhX24!1494vpaMlUKf(M*4fDGpSy= zw$}rLnFxD5Fqk#mUJnfBEp0Cc1{Z;=qSc|_VsN%F6O?Z+-Gu{dyS*M5%mmr%fx%3W zy&f3c6Kt;s2KNTr>w&?2!S;G!@Gx*+iC_%aUJeZTB=AU)PX~_`4zT9iI~PM~Hh6|O z@Tqvc@I3GW;icdu!fa%g39kWj|9|H4e(>GG>%sR6KL~zE_+jv7d+9DbY=VVz_~>~j znDeg4d%#Z%KMg)0d;ok%_+>CBl2D&58voH2nGMv3!ry{F7CsN=@IQ5aVf)W`&J=k*xQ1|Ta9v^618y*u5P*k9 zuy6>5fi?x(J0OEQfH}BAd1r7x;U3^2!ff?M3J(X56&?YeBzzTky6|Z59O3ccz z2g|L(>;>H^i~=%N3-e*KR(KhBgYaG8jl!$J_U6X8pCNLV_k#I^v^O^fCxh+HjoJTa zQQNy4!-4m@y}L0u9sCgk1y=`uCY%GdH#CNvMf#J-S)><)`AiANqnmzPg1y4L@A(~q z@{ZtCd+9DLgJH>|11kZHy}nnN0~XwFpXUEC_a4wyRPEdM%;d~Dlhble2)!kwkpu_{ z2_)3edy^u)_Zm6~dO)d)(%A?|5qv~Iiikl3K}EraJdfB>5W84FvA6fS_PHZG-}kQd z`d{n)*7{~8XMXqYefI3xrS(m$m+P|AfNKENg(s64U0l;{;>F~IsQTTd%;bH50@DQ7 z_M7-|a=EBF&sHntm&xU$>aX3zm&p~P>Uw3Z@Gn$HGP|IqSIr7@NHszYG;Br~0vXM< zQC^q{P*s=#=WcK`gFmCTa3Z*#Fte#pnAy}sI0M{`Tqzm|y*M_?k?87J2&j3{)v;iv zwq6|zW&-Hdv0!?vSI2^@f%WQGa1wZ-c)%w4ZsD5XmBOrwYlZ88A0}5u`ZHM`)k2gH z2{S-X3Zn)Z9D3%;S_Vk3hy}y%=!#e{+W@^H7R*3g7W0ff_oJfSTHtSmv%%k&F5S(A zpjW}N3gFVKV1@Yz<#JsM>UtF{n1R--V8JN#Mh!8;?8_&!rNUKo6YGVqP^UY3;VU>o z3q8aTTEyteS7nD@nGra3OX@>O`AsMFCqg;}1K3ge+_EEh&ai!OYv4#0(l zuUNVzf?oLwrbBw=E10p=D__Bkwm_IQqNy+oCcmG>M__@a zv4dzd1$Pr}3GOZ24$O~L^q?nrm~cPv7~!$tNy6j7(}gF3Zxfyjo-aHH9JyPB1rSyW zF9fd_z8lQ%l=OH7_(9>#;9bI7!25)^fgcxs5PVelQLtXr3U>~J^`cguxOD`AUeyYX zr@;JTM9+?cF9<&ez9jrS_VmU{>w)uy>w_B!Hv{wI6g_AKZY$gg+)=nIcn~=y8qg8KtXboPxpjqV-t%tq!;QD+R63Zo(z_X#7xjJ3inTpNWMnQg)a;C~4hf%k&-(yf*d9up15 z;D~T{@YBL9bf<*-g8A)`G3yULFFXYNuJCZMUa$%?EST4*j$~$*{UnTRiWq;0z+{O* zJ4-`+dd3PfRpNx1DrJS){&21^%_A3#B;nd%y%rVfjD0;(XP=-@xG}hyW~6^B2(3k< zJ-EFvQ$jB~Ma5;P^`cWS!plXcMm(4WO|LlxGthd?DVWa)z2+3mhn8M*3TF1{HK$-^ z*>d*(=ukR@)xtSoz33F`dEhOgUI^BUPNB{~?-q3idY>=@{R|lnGPU%YQ!vYlUULd& zqomiIf+I{iz33DIQY^aY6pX*#sDvJhNv?Sqy;B?`^;M&5&!1aVjg7qR*xHAsiOw^0Pt%YZR zI||PQcNbm?{|&u(6_)RX#z4_n3mz`K9XwWe2Y9mZF7OQD!{E8XN5Bh&kAfEovq9TH zM*Vbww~!+c{1A4E22#-2EgS+L5N6*}FJy&z)`(-G&I0wUF!S|{a0d9Sa4qn8Vb+3o zg;|~=mqaLn@TqW1@K?fJz~2e?0RJpJ0Q|e~ATU2A#lu&|tgJ92R#})4OA^M-NRKk| zA3|->V9e?XGiHUtj9D{b#;mn4W2P6cLXolQF6xcIw+i!#I#9R>JY2Xncq~sBWvJUh zm@FDBnKOi0=;jFXk-D6W>cCj87G}&g2;*jK5oW}83MYVf3$y30*Q>%E_CJj$M1w8b zG2uk;v%-w!8DX{!{MN`oFvjl+v&vl(X4~+oa2xPf!fejJ6J`zi862TQBOv@K8e_mN zw8d1P2lfgt0EdL{0+$nB3a%u454bitD_W@Q39~gU6lSB;Oc)Qgh|yXE=66To>fr9e zY@luxM#EED-JlUZNT4&k}yGX!HfI5grY`UwABd ztMCNyL&8j(tH!JSGWiq5{|TkkRU<_a5@>!P!tEFwy>_2!py%y zVHVhC!mOgLg&ByB!gayjh55YTDYkUKK6qd}+kXhGufs)yAssE;4160oJ6h%D3;UqH zSU3P)E{yibSS`#zZxCjnw+J)PJB1V3{_~t%dXNH*r^pDh3;fJYd|KEI^;d;s!LJK5 zK<@}MfK=3l{=-nZg?C<6(UkzVlPfv1&I}e5m)NfR6r8?e@+ZeTpx9RE%Z}U~7J8)aB?%{2NdJi}2$U~}D1Qv&= zxe=(=QZM6Xoq1SQnGe-IYW#etZdOn6mZ*FSpt*Cm8n6JGBh(&<2*Gc>C8&;f;ffESqN}ohUE~T`7iKB{Vpp1VBw6L*p1*l^ zw4Ez8ZwmYyjxhI&M1C8yeAphYfxJo|Az^J>ruM=K_c`ychCKfnMBcQJ_iWkVgmDu8rI$4nWSwWN!3t}x zufwEX2c~Z<+TIRDt2K4vZ>2HQ>8p*|T5{|vlpN0!yt(X5ee;ZgNxf+MR=5?@+H|;E zirb~2?7161Zr6kOb*yjk>nhs`zgTx&PP^oq45QXhP_k=AJ+wMPC3b>(dWEZelP{s+ zuHb`Fmv1zFqwM%*(u2RJB<_GIYXY?^F??>alHi(IDGXiYj~*2_Hy6iX4CGPu@haCLE7nx^uXZI_znJRS zYF8z*t9om->y-JR+Pucq(|kc)TjMIg-UFFyA&pm&wJ2A$)oW{A^$NH}h&eG8u5yWd z)@uNB@^C1o&!kQ~xW<}qsv-B6`f-S6eQ3{W z>1ZCN*nnbpx^$F&^nw^okEgVkQ((kAMuTIaLq9Iwul>l`=&A=lMs9RXtnde&&AN^T zkzfCr)2F~W3)GhzUD@%Ypyu>zggne%K;4`rDr=Lgn)QvTI&N}RHS3j(-Q>F6XZ_?> zwc2_H#HTW;hyxfvsj=P3tM0D9lvEs!Yl>VOoL-t1150d#9#C4j?dWws4JA_vSi(hZInsUpB_eUwY-XPTx|mjcBNZ^$SP2IupW zk*Y>FVMeaEFkjgQ2(#P`5oRQ(k&$0?XO3p1KP~I2I?$lyMPi5+?h$5rTqVrD-a26p z-|Zs9Cpx-Mn7Qz{Fyp}GAvD7PasLl8Q~KZ9W0V?Nev2Al28e4+$PCa&!VJ(C!VJ*2 z!VJ(4!VHjJa0%7Ev9{yw{6K!K`(9 z+737w+)K=)g82xfdA=hI70v*U7R~}s6vmr@F-^}Af`xhz=7)E^|1QyBlI|B~k{%XjlInBC;b;!{8Bu5Q zo)%`y_^NPA@aw{@!FmlS>~;X_ZU4bt!B_MgAqXQOToVh$;Gcx=1pgtt7_7Jbhxw&o zz3o4kNgam=5@XDy*3)*ttH65N4tO0{Pul^roat#h;7urh(Rn)%c7fSVqeD-F8wsBT zcOj$h@u{V!?SNSo_4-Z#lXr-iVakmX&IRjfJ22A_JYCeAfVqc+-rlo0ggZrpNw-wE zHF%9MpMD#KJA=0g_XYn;cqDkQ@C@){!gIjfuZ9tr3w~M{k-ybACBgz|oE2UOJ}Uz+r-N2|bb@GUz(}&i%(Pzs;zv=(2%AVY} zKfB7-%KzVKqR;lfv68P2j`Dn{24;IRN*ey^a$@ji-SQ8-e$-TJ{%~!zjufc8KV38N z4z%Y_s4i5$;b!eBRGt390KhASYV%*NE!Ne-b&c&Vrfc0jHr}(>y@y{{NtS7kHt|Zn z#Dp zvQ<7a`u5C5_vW|fjLhiU^SdrPWDU(+f5}r$t^Xn%!tr2bY&$*wU?wU!?37-nz!IoY zxk%*eW1{9*uiEJsXbcJ50~g#q(TwNgz+|k0wRTQa7i_!cjn`%@d@XO+&Cjh@f6b-T zH0%1(l5TGMzDhohxR`e!pihR>7m0QiC)JB8XL;7CK$4wSc{#cdmU5s}x`){z3V0p% zt1d})t2#}>2wrWLLhBgNj=7aM0FKCVSPlcra#*avIGJQ81^$hG1lGgW!_8`~zDcsH zo6o6W4ZA*;?zOIAr{LXZTn)Qv4yTzrc~^AAtvCvWFCtmuKJuX@2%49{ZWrI&-FS0E zd+t*2)UX>>d==sFPI(Z9LctcW<(*31grK8pC)-J7rUl_(_%B2w5*~@@-4X7uh9=wP zd;Nq!&k3)`@9Z!eky+stelC9f1w12M4eHawZQ%B_@GKy@}GXp0s_KQJRVzGJ^PFu%(>eFPb=UA$eQtZU0Z@{n_=Cj5M?}jTe;XnY%7#@xu zgdOIROm{d2VfKXimBk6)4X0wm{I-RKr-v0LnU-tEHL^Hi5z#Qi&%ibpzH-Y957(z! zd|e3UzX2oh+^NzPEMO-i%#Q&^n7eYC;Q)e(#kcDH6uU0=JPM}TZOt93cdFee$g#lC zk0W5iA3PDKwx-&3tjm7&S}MvnPIbGUYWFp#s4i*t$jS}jVkpmi3GTK{=!(#U@=fMg zyMzwvY?@uMCTCJ$^E9e;OSqK=nrWeDLNcuuX`xR-mWoTaD@GFOb4&9QA~GUj0+Kw` zN(-YBI0_qTqlGaEa}eQBJG~ZkT*7dqOQ@3;CM0kTb*QU0HYtJMEkn1MJdL0@fuDpz zJ+v@AVHh3gV=|9sCp4x`f3uo{X@K(*_R)nQ<_^?}`3d_N`eEjD2n!RoF>oWyGBB_- zVKj9{nOy3)JmD3(Fvk22!pek+6sG8Stxeb$Qop9#sb(dWSkq27%d2)Z?b~q{;0rbF zMC;L@`kbmeR4Ah~**`)sqRHMj!>*Ic4NXIzG4rYz79R|uv&>a5hhjtFKNvL>vb`C0 zOY3q#UCXe`4bUG-8`6{&)RUwdG4Wl&H;@|fjmb{1D#94wgl7D{S(8Q>@lDCWU=hkq zd^2(=s6Ravk>gRec#MO$h8mt}XV-lgrfw{ceH*b9t%P7uZOF1~R^$;9-dpIbm&ej! zeJm&R1mV&4PZ~rMY%PkGP0~m1H14kFg2DRNmw2K9@ z_oAM82N%J-RZn%Rg*Tfk^cm}8bMECOYey!UN_PVhmL5bp}E8#97U7~z#e$(V7wP$4x||ZsRU_-z$yx0Xd+&h>2uZ8p zXommb+ej&{kNuI6<>F)SW2W|99fF05w?f%lQVDK!3o@O&N66iS71XzN?DCa9fNk$n z%aH_qg4~PEd#o729T5C7pc1p~5rH@qDm&Z?`tI-|wJIAe*T3V`6WMk*9LwpcYxg!| z)S$X{bF;hJR@bhS$S;)Ua~7v!o2+wH4#5K`RDjn;(kY*Mx2~OsW-=}Zx9+M{j(yS` zTw>?iX{PmAY)S2Ub`#UgRO9pP_DLMyHV4|H5G=Ob$-Ur)Imo6{O;DsWx0Jk*XCHSZ z`)RJw{2K3l1;xXM8D=9kxrL(`lqlY=4j0;qRS(eMOltms7N+r7!<@y$NI*I^>Zrae zw0EQyAikzTn(6Ir%+1~KR@ziAgjKA;Bayz14ydCI?aCqT{wBJ{g{_U2s81T&O)J^R zAoD0y;i_>6|K{e`=*T^)Nh3S6F>~8|nkC>nm~3-3)PNB_R=CQCpTkFCPUS!c+#0(y@L($A}ERcA3{ z25Sv^VYEzSP&SxDz>J!?xp8&8T1RMqgZUU#-n9{<*=5h7uf}7Th*b+ z{+GF4T`RK7TCe(*)7+k8PE&U_x9`AWNwbBW0V%76ea8AWSn@*)JI*rOme{RrpJ{#V zQ}J!=Tyzaux3R}rSHkLG8~X#Zi(1*%PBPQfV{PsBkbbAMS5<3gSHvf(f_8TG@KC0e z&&4j$a1^2BT_t1Mp*W%^yP`b`c8C)q%&+Z7%A$U)8j@UBUFu*Tx1RH=tsU(tsq2uA zu~STT8M;(LIY6)A3ZmWR8DxLByH1lX%T#_RI|mhJbSE@t^VPkb>{)|eLTZE_GCSA8 z)E%aEfRDhyOR#Hn{~Zp6wwojV*5gyu=?=4j}y1y4x z!#dlIa%MxvyGa+1OVrgxpxaw;rtGcDVf9xkrULYj(PJjot7CWF3$f}-XM2RXQFZHL zS3@^)au@V13)G4(c4qneP(;Jsf3FQoI@lcRH@YuY=epSC%suL}E_N++Y7)BIt;{=B z->&v4I8&{gUETU9sG4=NhnhV~ws*6COma>$L5&ao|FB+_r_I(=k5aVirJO6O`s>1Ts3MFUnx0fVhO>pm{c5dkA;qd(Wb#v5)qv)&T<&+%7eQo3l zs$=OlS3T;?yu|ehN>b@a_H8H^r6blc=x3IWSdT;prgYGm$GDXaG=D@!W5eqX6u{DT zqk1ta-@kEvfwG0cXuiGUZ4(8gbm1s_VxgPL6Yd1ACEN>~E8HJkAUp)D$AsbWC@{x_BMilO2zpQ$TnyHO!r&QTJtz#G z4c3FgV2(fx5l0t+^`J2FoUzn{!eGWy56yy+6VWkYFe9nQgu#epBswSzfw5F{6wFxi zvj>^6+#x&w{D|;K@P6TO;KRaxBT5!gaap&1SiIl|4rLE+|L?vqM0t-+P_I4y*B5E4bB3ph=< z8(1$8g_&ExdVwgoAGkow3;{P4W=gaYo(}FHdJXQELupV6YV>lJ82baOD6x>dgUoPr_m2f^-4>Uua1CJPJ#tat-Oh!G@3}({l zk!ElaSdTP=+ko{zGnlER2b#ehz^BCnrVzh)(ak>K*M*0G-w|dCT@;?d_D7FE!@^zA z&?C@bCXpV225$iWBo-b7|0cX297Jv4HyL`yZ_wm{VD7F>9t=(t9t}8#+c#POR3!WtW1~{Te zpke3|gxf^pGI+l5C*Z}xpM&+tG|Y2sWVNXO0^T6ZC6!x*xukNZa2$BIFefS;h;qaz z1K|nL;FOAE!WF>J3Rec75$3YXv%-ns^TNsCcZD;+mxQ@A^HX8I4}2xerJ3KA9!}dB z!q1}768xty$5vc;$}umygT2B7!6D%>;Bvw5pp9or909t8`2oQ%{34hSBbxa)nEObR-v?I_{sPRs(D_j*5u7ia0d6GBu10g= z+TgZ&g)1y%L+C6T`QRSH4Z+-3jgA(92Mf0bj}&GLI9`~^J4KjJ%UQyMz_$xCrSB4s z6hl}h0+V-zFdrD}gcpK02`>h37hVc}SeVKFsPH=QA>jwWPYOQ-J|Vmt{JdV_ikKaS z@Umze1^-)^Ef=?oW9;7p>lLeDCc8eU79E8USRYgihF0{TT5we`dn8;|R~@Jiss%GU z^g*@Ye767Gdx3@uprH?{1vdn9dnD*g|hSGYoW1VWl<>;cyjehi!|d=y+D`~tYC@XO#S8oz=ki8?oNn=Z_mgtrL?!1IOqwA7=+u!}jFhF(Go z<^)H*g0>1uJ>1+u^I#Msy&q77LMnv)qLBf9Qn(iQgfJf%&kHvO>t(jE%W}gnKy-)C z0)FKovrxY;JOuoq@JR4=GU_LCF=G6BL%{Pjs^Nnnw{QUL7sdlk&(ERdcyI+#F9+rq z9jdbp$Ph+2jJm=Z;QGRBdYfxT`m?#^cqT130(TK^1MVx#S};VIu^c7b9Xv(2FPI}8 zbZ0R54&ia&Wx{N2R|-!Buh%PFVPQ4|Zm~qm%fNd5EqE1ppQtOaUUCcd&ES)w&JzBD z@OH31gcfEV0lzKk`@tUxKMwvdQ)(YFw05;lgg9u2-jcr19K@C3I1cZZvvkdW;VS_j=(bW>2=XyKD{G+5PVUXdG?8L3HXXIv+SDiQ{bP3Pk{B( zUAX@WSTEfLp9SlsyWn%+h+ex3;Y|p7?JoFjaAkiqW}kwSguel&3ttD<7XArbPuOMR z!6M9lzWxLSH`#4(BkJsucaCyIZ;{eNG&mmJSGWi~Shxjvq%cRN#|!gh)G5Lpz_WyV zfNvKb0=`Rl7m! zeo}ZN_=NCg@bkhvE9+(9$JzhqqGf;d9_`g)e}A5dIka zn=lVNbHtFWdpl zJ%>5zB!u$ORk$L!mvAE6|NbJ>gvLHyB2{LiF$kR zJ;Lm;tP<`6R>A|p4`_}=+v**nF$@}y2#*4Dn?rgw2F!0HH6N42?OWQ3kBn{X)Ggc#){{ zfpm{BIyS~CVLp(QFds+{2{tp@5 zB$nj2g&DXHgv*0J7G~hS6vk87xGJ0o{!tiVGJYpV;Akxf7P>a{ARFumHwFiV`55IM ztu#{vt|ZLIXrgdOaGG#$a4lh$_FUm1-~!>1;0R|+&{3A~u4JSk+75$XImyh9A;OF? zcZQ}qW6Y@$WX5=!Fk?JN7)=WvFi|te{=e7yvP|3)Rmb795LStyR$wLE7W{y4XYh96 zZs5nsaD@&X70v;l6s`w;K{y}G2^}=g_TxXoOwqUX;k3}8#~+AB2k^(j?2LRV+!K6N zxG(re;j!S-wZ7BA777VHyB+KZF98RI?*k_YuSEGXDv7|xAyJt1K24ZiG@esMM~{MY zg-?R@0k%+o7OW4j1)l=95;HG?I|!cz_Y^)49t4ii(Mu4C z@M2-hL=Q?h2-XMEW<;}Q2i3vM8hs!wm|3%5)R8q2HM*1&?@RMjL@E^htgJaM#F$M?0vBHPJal-6! zl@&e?t}J{4oFx1_SRYOcci8mii26BjBfY{E8f<#?T3_&&;BI2*JFs5g3-uqsdVMeW zXRu!13;q*4TFiTq1-FqiInM-DPB<34To_$MW3@1=r?Ei<+>9;43E-W=tn0gl;h(Wj zm<`u6WIV6w*=b>V#%X(0r)RGV)30}g2J-Z?r^z52&BKRla6!0Iy3`7iE zpxxTwSmC1CkP`E4jZsA+ND}{T3*9-Rn zZx-$keo%NQSTEy+U&F!sL|wQ4kBcxG8b^i4f=>xg0CSE2Q)n{ytnhU3hvYiZy#Gv? zdH=OA^Zt8b_8Wc?Mg=qeigHB1Xxezn&><#!tT2*EEz%zuqfaeN#11}I} zb}SJd0=`$6S+GWUC;T_=7hx}WtMGpCL&9uW_6WZSJ}7)1Tq1l4d|dc4_&MRvz%L53 zU3pFTEASiO2xIvzg!e?_8u&xu@4=r5v+?;__$To9!he9dsXE=^v*0gbz6aW<*Hrg_ zeZu@c6c)xdo7mx4gm`FF73TMzWMRHIW(X&NvxWJMCto-Z+(?+)Y&I9>yJB16PT-Eh zUBQFMIY@s55VKP$M8|^03A2tC3$rnqDa^)Xo-oVDox&^=ONG0F?-RZiyjFMsc%$$* z@V1BuvmpFScn)~4@Ivro!pp!%gzp7EExZbRN_ZXkCE*R=bHbazZwhY*Ul5M4&|Mbc zG4SWY$H3nRp8|g;d>U+{PQ~YS1$cq*TJREK1-@7K0q`o}ZQz|`#L5reeG?xLj)(da$q8Gj;oaQ?wQyPVAFbt zZ$GQPS`Wk9PN~KU_mj`7<+#K2dy3j;RILpVJE^%F;KmU3GH%v|bE?YyP_3oL-w)Ld z>M7pxly4($hU$--wd=gvyO9pRseZ>DhB|J7@>g%G&6^-DRaYRIDXP(C+}f#oc$=W! z!_7K!LDhNyX6CE84?w(rQN8j2LflTZ+yX;A)tW8tS=QAu%D)xvUuv!f@V>K^+Pl@A zX}w%ly}i|410nuxt2^D!yAv-NDt((96WG^97iheK2ag8{R(hRS7hRxn_cnJZ^QUzv z*rhtSqx$QMXD7KpMN!c>FkYS7=1$AMxE_PlVNt}b)I9f%yxF{eqN&YIftktC?GMiH zQjNE})9mw)p)FLC*O~Y(t?t?G&bYC7L+^P|kW-g?9$1%_Kk=YD)A6yRWX^E#WRF$( zyWN>7KZ7mHlj((*E_>{x>4xR<o^o+UU$?pyWI)a zHLrSWx4UC`ZZhePX@Ky>41_Va-I{o>D%|5v3T8mX$*ZVi=YK$r+2gKZz2Z{K_qeN= zFRMrPxEoa(3j5wEc?e6$cOD*ir*4A-3BD&C_2(XUVj1qbYQ$}UP9%+m}zjys7LBNlEn zEQ9zRBk1z?-Dts!A7S=gBaRzpzhK01oHjR(A4O8)I3$iZ{Q~Fx#atE)JFDC;($WF- z>Rxx{3jFf!-^Inf-YxjSYL4azh40PvkGjia+k*;^x*PQm(E9r^@G=fFch1uo=is;k zn}@(`9KTW~kk$SA}Ey+s7) zC)l6wWeL7>2wQLnr$AKj4TN98;dH%Vf@J=1$zZz);+? z;6x+zFpOepKvQb#IvZDr1T*8-!CNbCkZOGp(dwqg9dswB@r*uiXKaOxl?~ke+uP+B zenY;~Q1Ny>4NmZ#x7DG8?(_<$SUzu|v);orx%);qUB!tTih>o2W33JwUe1ohTz!6L^evgMm1N7u z&-mU|WS4Ir&8#N7eb@2pUGofn9bYfR-n*9U_t7!$Ix;5fUxx)8Z*?owa(!;vzW**0 zTE5|QbtBp3b1}r5=&BQlPTwbceJj#D-z?&PdnsC3{e+->FKye{v-ad5pIGISuq5c4IK9^;otZ*?^a7CL;wlrPx^ zH@f-CQ2hwGyN?q?y-&^u-{LETbKavYE#k;(xHJ8e;j#HW>t%+wWM7GUx@jh< z)*`eh9*W3?yOJ04~oooLC+iRMCS+V4=i|#c|Biqpa`glHL^JmWQCK8tynD%A_YrAN!~wc}xxZd>p#rMqu*fS~t~fCf9qeB#mrc8-0*J;2;=gz9_`DK9(99y|;#JzM4oxb$q9GvFR#<|Qz@12q2zc(Cw0 z;E}=?!Q+KL22U3L3VbIx!dY2t_Lfou+!K7CaBuKh;bGv7!ehV>36BHs6`lyz$H~B* zRbYLb40sz@?>PZ}7<@*|Kf>|L|A?>;8gC0f3;sa(W$?$suY$i6J`dJYis9&+U_GT6 z{5e=pDF%ND)>DeXSHXHpG59-h8MGPk9;82CmMduiksZD>SMrzgvWtL==sYq#ILF2L}NC%SePG5XA1K{HcxmF_)g*b zz)OW!g6|Vv4_+(01-w!CVemHL$RiNgYh>PYqNF~52K+c!pFaaG0Ur@FC%{h&p8~Vf zK)bJiUlKkCJ}3M-Sf51$GjD+PQ8eJUz?b#>WeD#;_*^Vp1b-vUFRpyL(}QoozY1Rm zv(cjZ?_iIxiF(e3yi|9C!@}I+jJtKU2dU{=37g<1WU3J(I`Cp-eI zrwhY88y`Jg7(4;2rwfBugY|S_@PlAIT^Rfb_^7zE2OK#i!T|^`37-O=6K0irQ}{LT z1z}dX%ff7AJ{PWm?D$5w7Wfz824Dlv4o0#in45!vBgR+=dipOkrhpU0&>V1@@Ir7c z;U(Z)VNL}u5MB$`=aj>p?ci3Tz7wp^DTg{szCNcMd;r|rjrz|J9f2@FEIbPyCj1h3 zjPQTJ?4{E@3+Z&>kHNPIe+8Z|d=uQdX_KDuswK?>gesz?k-{4-6u@Dj|El6Zm$Y* zJY8_W=#m^yS8H1r^)|)EDm@qC+qbCMxe%AD(-bfCR2Aw$G}P#N9*!23@U~QW@;tNf z-WSP(nKQSkb9pecR#ngUWLd{Os#CtF28M|y=X=uaACU`t)TVq-vbDV#$A8kSL(QV& zJ%8qVLe`aLDpVheCl{##+;N!Z;QF3)jF~OLjhlfSfU5iBwkQVCljbpy^;dn*O=C3J zV534z{k*zr#iE3oW_HQ+2A(*}eGrA_CqpeO^i=5Y&= z0(*fr&@cig5Ee5q3SJgmYU?#3uNE4GJF3?jd(z_SGuYG3EZm9=YrJwb@uZl)sf;F`JPRAN z3~quN=(E+rCZ6e$XJOyv&5J|e0({iZ4+XedWpOC52hy{lz*$I#=Uaheh{XImt-w53 zc>i82kOJ2~T4@F5L;qvk?}GkS+`A)mt5;cpVNl&))V ztXeQw`9An*_UH{C`cNE(AG7BU{N9QGD(vT*y(tE(c2TvOd3Kmr)VXG!idHq3y3)+k zIOj_^WQI%{;iSeC?OxcVgX~YIcu{4|c&$GPa_W1kPm$+%%0N2YY&10a28aH94OlRX zZYb3J$ySBUJynNscA=-b&aFRTJb|?+A(eJ&UV`7jwyf1$&Jfknr7)9E_cXJA5v9i7r40n|%_zo$9T?9^p1%PMw5$M6Z1+BN{9| z)r9+lQ-z0svxM22=LmDqv4QX%VD3ar&-nOnDZCEcUU(C@tMJ3%UZq0<2O;zqjmN=5 zg^z+q3%>xKD0~h)O_=YdvxF~z*-peG6+_^a!t55T7iNlX7EVK##9#|Y3pK&JgxQPQ zC(O>$JsWoHR_o${m z9^=`9&rWxZMK7b1dVMT<83R`671YC0LcIHg8T_6#rg7%ZC~GeOH}Qzsz% zXQ~e-dlIdy=K5k!O_+4asOdhav*qFS3|Z_xe-pn=hC9p+ zW;B`a4t%Es_tW*^OVMCymCdG2lQmd>7>41I^Sd zX>gZExlk)!TY_pmUsYd<`n)r!ikEu2SZA;`9q!neD{dKTyI(C?#@b#+y}b;oV^sRx zo-FKe-s5gm?Mn%2_T8Q|o2%%ms~uEY<<+%aeQ-D4C3bjO#nY_!ysDeWS5DQr2a2c4 zs-k8!=-uS$c6+@Qq5_0wz#tIk0e}(mGT$o$p}~m6q9sH;ys-=BY$I zm(7ke-Z;wPrfVu|D6A~U&Fxx_;qE!BJi*Ao35eq5MLrqxisbDrxi&lHK)x647b?Gja zxg1=km?FJM*a=%;T24@^^BPZ5kj;YWG5KIJ%6C=s)_BspYh(4bvBR)dQQ^m|uU&i- zSYg`K;fC43T!`>m@PIGJr80Zvm4*(7exb?tFfNU#yi8qR0#~qT6mLcc;nf5TXUPxV{pNzx6RxkksN94FJ5 zYqcvz`6_DNI!}6%_VYC47RdQ{Er8b5fLm%<%{-=#?iEp>3BqdUT(UOKS7WrD*sGcPw#JqkYqebEM8FII@Q4Jx? zrJtf&lQHbXD;aK@|TZFfOcMAUtyjyq|_<%5vW?z%$@?_)1sW=eQ`$TK9=ChBb{V!hC*yD_j<=_g!+LS*EvL0yE3>wo71U zncj8@%-U25b&y>V)}};ZF4>Iky9A9IV7>1WI329_T>^6ufunY`%xa_eT>=+^TZwuj zu-dnC%v!Z!s*{#Cuz#J5ydI#|E800^NP7ub52CItxFbeen;2EMm4LnzPCU}AH zZ156c_POsBo)2Cl%q+fNcm;T?@D}(V{dfw`n6G<8V;A_K@Z;bT;itjJgci8YWytBa`2}xyN@nt(o8&9e>??O z09U5EH)=OYn0C{Jp=Hz-X7#Qo%(4J3!JQ7M5;gwCA`*_g4bLzv%W#VEje5 zR|2pvJVwm3U~(D`?b4m;!d1W=ccpq3cz%>4x97v@;;YGF3Xdaor|ZV%om z>K(zmh1mui5bg%%uo*pL89F9B2>h(@5bz5wq!SGdSJl6cZ&vc_8=jBd<`H%A0=nVt zRQCJqXs4<9@1vvbsMjEx$5izXpn54?6@LI#mpTCv$701@#BHY6e(YJ&H3Gqx)fB(cPLu5l zf&&l0qSY=Cr2Tt?{h{RAVg~pOwI2$w^XhIA;6u^VFTj^^XKbK8Zn2|P_nC2x)wcRU zzpD3%=V1@K1Qy05@KRj(KN|J>qX!jTI5ied1M{_r>TGhE*yLPr7hygndkXU@*-y9?c!+QZFf)U8Sra(7 z0bDZls;9k$3DSGML#dpve#6Z=?^T_?_sle3QO|u3@v2`XT=!(AG8s#+Qal0$)E%Qp zG#oYjx+gtzE7bVHPj%1ETK~e$ak=xVLN(TeIh??Y3^8LltJjMCgF%X&hhJB0GChyw z$f-N_Ww0m0_8i;Hw1m;vSdK4Y3wO?2au@KqWw8_N(SupuHPE!Y6FNb~R~;t2vmT>e z!_Ttm@=amQirtDIBeph-nz0vv7AO5ez{v&2a1Sims?7Z+d%FqRnf-LpD4U@=|LUpGR4e_Um7p4;Yvr|* zzxDtoL9K#z^*3g=*`p#KOo)XZcdpP!?fBJGU>2(jzj~@$!5H-ioHU!Oa=&?!YQ-?j z-OU9sjACXaApv;;OjqC?XF8$&8(Bg0jrw$48<>yIZ_Eq&PRhVHw&ruzY zxX_!AenFuCz91TO=&~?9`&^hkif@EN;OoMCxcn+y70kwh9wY-@!hAyOS*OS*`c)O` zg`D%km?hH?m>%e9r(k-Zr=5bC9reWwv!h|jtQt;R4@PrOWH?)`ot4#)Os9)=zOp)i zJ7%K%mFcjT*)o}Qa zGgT(rNye~LgKQ_=<^}_8)NrU+7%jwPlX7ZHwiCjNf&I`nJE*Rs63VM@pk!UhT3^>m zGpDNby3nbhy7D$e&El<_+RB?tov!QLG|>bj!WA%){Mwj=3`}-uRwbdjZz*DDvgcRB zP&v(ll}C?26}j;k z=OTx1(5>vqn`sSF+4Y0T<}E6&Y68YjVZ<^vu1UOD_J7*%>+p}eeAaw#yt=oZ^O^e3 z>VyK-%`V$dZM~RqsH9STXR~Epcd4R#6J}b!+p2CM@@1!5aW7Uov4U<^Cq7C@ww{Yo zS3SNo>x~$7t`I37Qjz--(7AW1#D)+Z_1C)zmCDzdoSpdhm6KnXn^&)1PC;EIr|9I= zFHo172Gh+*$+3pcHx)-rM;SDE#KbK0jjkdgN-wJ>=$Ed{UyJ#dF}(9p@bO9gv54;8JG2-DAaG2Ao09>*#43Db^Fht_3-#Vpp*U_aIAkIYi-6wO!40ebxdAr52M~eufuKU5CRhN9c6aMX2YcK7}1F3^i1E01wdDc+UiJ|`>J^ZcB^Uz5NjYbgt?afv2 zu2!gVKux$SRAC@z*!p|vFSL1~Z4CCUCL`G}#K`;mYN1hxOZfc#&FfHW9OiO2{~)c? zG{lWG{Uf!|EX0>$|8$c>7Da|NO6~9Cq*&J+^+q43o^{4i-o9w5?^kvDI`hqnYJXoR zJFPs-?hJg_9?rzZAe#c$nFdDeR|svCU9m4%%GJ-Q6JUx3ek^2=W8+k_eok_;?`h>n z7SBqt4SnA)J_mBNlWjs9Q4RumT4)=3h&uV& zc)QTKSXJELsSx4jo`J?%v0La-nrNnlo*`~_7AVp}pU^!wgaIM$!yRa$b%unvlT@Ik z$*+kcLSeerN(-YxpHiod7RH1wGX>h2OoDMCZetkeL?L2K2$f~LyJ~ZjLU&T@7V`>> z7l$nR+CvM|LvPT9J|?Tj?9e*oIu<#85C7(czGJY5n6n|w53zrTsb0*rg`sxLmJ#OX z5SE6x&1hhh`5D&|@qqU)9)S9M+4WZS@(!g{rYz`e{#AoZ| z+!i`Xk8ab#_Rv#^S74qN9t^#MAO<2@*kM?+)E5JsJ~-p4#Xu(otC&X*bP^lzq=LX= zZtv}O{emDk+<-qZaFN9#TG&3ksjz9*h3)BqrG@Py#+`-jmw`@ljxKB;MGG5uxC?w1 z2e)xJRFL66OAV(8i4h9^!K97NR-FeqHEOUX1-&|?-Vm3s1jFVa1kI0NZHGChrCK!z zP0wkl#WkcWR_r6luDC{Lz^+*C<`~zQ?8Hu2ufpgME&xDMCMsh*Pv6L zpJH6Wj^^EPu|gIyF4#%uVukf^HrUx@6*4LoBPeE1$J_~1mGlWNJsqu6IZd@1>@+ji zsD*=_mev(Vy*SwUmx=H7L!5@zM_#peh*OZg1;H}cS@f8rH&t;oj?)7dPe299uY&DLt= z2q!tr0%~6Juz;F9jI8q>b!voDLlx5rkxp_P%evXrVOckO<(n%RtM+4^D%JIi z-~?SDSqzQZ?9p(H{Rddg^p`9g>ohbmD12g^^Nu-G-9O%G5zpPN&9ZLB`hJwM%r|1y z*W;Z9@zt4j|FLtSUS}nKGPBkE2~K-6S-mvDNw@ZU)b|sdn(^GV(Tve0vnMhs&NTzdfE zLHzshwv}0tIn`b}K9`2sl$afKV5#pXI+c;Qag#7B%cI_zVpmXXTrfhsh4Hdn?u zOMNjJuQ%B$Tph>^I@MX&crdf> zIp;Ar&MOW7m{EprcNv`jDmqP2E;2YA3z@y9Zj?`Z)!=DPgM^w){ma^gODsP|&9gCT z_cW)VhHj_oy6E=Zc(cynD)o{CNgFF6-8ta*2dXdE)w<2c2Ho8uA7^f=Fa+2~sVr|^RjEXwfp&66) z(GkcKQLq`R12>tdtBPkiiI|_WYL-(r(h(-(eH>owTp8&UJZrM+UNaf5`a1hNw}uaa z54392zg4^FQI2&zsla!WGE7sWD?f(?e=)o9v!=6#YEw#07@e;nAif&!HV6TdQD|A)}Q}7)frtH5}yZ(k$2wpI6 znTsgV@1E2mlwrGB<~IBoou?xd{#q7`**NMmCyZ`!C_3ThKXay6gsNO9VDvcQD@mN= z)UDzCfMR1A-7S@LYp~+~;6?oZgR#FfHE2y-N=f+#uw{t)dR+{TjNKXMEqV3{=V9+Z zed8|~`;1dJ)yyr~_^C5}d1MgMqIB$!Z;PeleO@Gc=@?%FD_LrU9dfw zjD+O12zgq3 zwT8?#3)cZMN274@;Iz_Huf8O6(g3b9tQQ8V$_tmieNhPY|4 znH=R`+2=`(vThsGqCAf+PI{Dc*ml>9@~7mCD90fc%&aKSC)bMdNTi5a8_f9MN1;ws zqlBCt<(fz;vu>0}lXIi|0XZ+qn~-W|ew4WZm03T^Uz7PU8kZNTX%5>Ck&OS=|Ha#The=U|mSJ~+CFhL9CFdZ5AXx=PKm`RQXAv0?R8$Oz z+6E*GiW$QiFkn^;sF*MS0_x8MDkA3h^Q}3H+|T>Z@44UWdgp@ePn|klU0q#WU0r>u zjs?aOvEKLs*cqj8tyI@EST7?5`aBt<6j)W{Rg;t7kW9a%Qv=r53~>W^mvJL_kFg5% zOUIOdI|AQXgi7WoSotZsT6?1IDToj~e%fpE4c>Z!;bV zZ+Dh79PAtd&fo+d3p;}o_>nzD)O@GejX`DG@a-;%c~o@ojKZ<27)x@tv@S5tQgXa5v)z;aQ~0#go|ASFO(dv4Lf@iSZ$88H-S~FoxKUHX5$vqSCfEmFSl=J z*x8$)<1UTKRjJd?$b^U?1e}oxd@B5^DUF8TGS+3%sFN~u9=ywVBD}|VD*UCfF2{Gq z^WmS2^;kZd;dJN<0vhX5qSwIc&x)7BdS?^g3|BN>1y?t|6Lu5v(b)`ZR-W{A3ECJx z19vih8SY`c9q#L#PqD);0s}2#4}7Ze7x0wd>Ec#d<4G8_$aKm zOl9tO_Al>E^mRMCDlt_S~R+yu_b$sAY}EoQ7|QW;}ax^l*y;L65bVfla8 zvDB)z6!Q?{Bw2jg0&3gcJ z8}fW9@Edy^Utmlttp$Iuq(q527FwhTJ;?d#i%BWFIrGTF$X7|OBk|jC9J@L5$ffg? zqb}wr?EMf@}9~fT_e`>rG)`uu%Vi~NreDN)?*3J{(1|K%Q1OD514eXp! zp>sFvoKnH}!iCQH6oD-SoL4IN5x54|M2S8IJEv6eR@gbEf}e+5nvNbM&M6i0_hILh z3jPFkPO0EMuyaZU{|9zXso?L_{yVQ!1P;Q^D;4|`e2yI;fd8c+dixqWPHdIC!h^1@QC6)8SW*H9uKP zhbqy<@J{1v;a$dh@7rU%7XH#$^Ah+PNc+#un+W`55qHB{&QEd;Nc?RqlbQ%mCCML! z3lBRpQaYrLqxH$Ol>+Ce-C#v*6?^gW4$L1 zF+KthH~tGAWvmGfV~rE=1;(1}Fjbr;7v%}ewg?$5%s1A{d;HAbj;cJa$Z+WY+ zUNYAh>m_reaWQxD%Xy#sYl^ylC7He%*Kgyu)}f{DJXM_*3K4;4h5F!QUBQ z03R`)2W;h*MNFZqum&1j|SHqQz^&(oscqLrVcr)C{_(8a{xR!fbhx8p^ ztlm*yWA%=P8SCjf$~m7xNsrgD7Qq8BG})LaOcgQ-)ra+ASBbWRofj*(8+@I~`@qhL z74m+tb7BP#fY+MNX|Qu*g?yCSf9J)Dz!>;pQyL4G7@rTnF0Rd+47|hmMEDcqzVH{u z{o(J8^_V_vd?Eb1@k}_xyJuR7>a{CDKztKi&RCDp%ElXD=X?sCyW!?0zaQ>k{2biN z_yu@?@k{V<<5%HP#@ji5XsiX^BH{w$k6>A=C_{VT*~SOp`NoIfD~yl8&dC(|f5Xnn z6s*rqYfUE(-({@t!_Kc%ZOXqU0q0nX9qPf(u@o$msLcQD7#>&K<#=0%wVAoMEGOllhps;ZGn@CIAA;!c21+n+;I30lb;29v?w~8 z@o=v3BsgiTo~}NTNoNjR(RdzQ-FP8fSDZ%aasmx4VkzwWL2)5f5q9>oyMEtUB>sqrT(KH zfPa*nN}!tmt8qU3m$6DWE5-Gfl5Uk;?kRC|*!hE^Jfm<8$zeS_oIfa771sHKf|Zc- z2L;P@x(~Q;5k2LN9pJO}_c7CGRc_cj9 z_t2n1o$OpmBX%wsjJD*YT zXxRCTg3p1s*}i&!y<&VJ?0iO{GX;LnlZ?NI zhZz3?k1+lfc0QxXs2)yJCFlN(z_Xq6DFV3!=37JzzSKAYUvA7MbgQ^&X6Mz$+V@Ul z?R$^0_I<#ZP3Uv{2=v6||ijCB_r zHP&75w{b1l`H3P;HA<;`_5TT|=9lM)V%5wl#vS1r#@*po;^vvNZ*QDL-qpAae4;U% zP(Nd3?o{JC@R`Q-)c%jQKtm#&L#URSB69wqV4D7nKPZ?J$@qhURVvOO6kHy5{-9u$ zit`5r>%h(*6s%Hl{-9u$it`7RCQy@ra|i`1cg`OatPDMy&l$t5;g^lohRc*g@(%F3 z#+~2~jMZlRM_kO~9X@D04F1LVG&uc-1x69zd#u|*4M?u>1X#94N@yZnU_2SFXgn3J zZafRFYdjZjY^!jjxCM8n1u{8s7?^YP=pk(|99%w(B&XBfW-&vVYFIJ371EVPJs;LD9agqIobg>Nw458q<^6|9BlRYL!PHyD2l zZ#Guj@SyP__%Y+3sDGiY7B~t&Z|r&8qsAKZdCRx}-f3J8-ep_`c0QxXM0MEtjDqXJ z&Sw;?=?czg6xRig7yZoJJv^1)pN_Iq*o-? zdW6J+(0+(3CweVu&>)|VnHJWvu@y+lGV~uP%pHbYO z5iXze83n6Jc0Qxv80>sT!D^DXYF}7w$=lBP6wRM@eqRx=cK*azJMT5t&IgRO^HDL4 zfjTLFAIITx8Mzvza$+^9{3;*EHI4O6I8@&P+3*R*O0=DE9PVjMa}gR~te(%Q#?@fw z>xqNafhS5%>r@O+H|`G4HSPm1aL%Vt>PKLyMGSy%Fdho8G(HvHXgnOg*Z2&0i}7gq zabq=B&ls!mdDU2r&s)Ze)c)_Zz-2`2GF}SrF}@o9()dRBJL3TU$#^w<)Oa2IxA7)8 z!gWwdsIwY3z7I}`(?mQ-pu9zFfvXs+<*H?@maBoWIxJ0%pM#5ypNBgbzXW$PRwLHi z_*1yQ@#k=Qhz0f#7;d}|9%cL;Jl6Oye1WkVs9DBQc)oESe5r9Eywtcdyu!E^d|QUo zp*jTCSVTj3qp@13dyO^kyu`R2yvcg*w&Y8dNz-`Kb&+|sx<+|IZ@#}9R} zfNqt(#@*pT#y#QFj8B3`8V`ogG1eV1$#^6@(^!x7xyEP1SBvQ=sJ&SZr%MAjS%mKO zyTn}pe)k{8j~IuMKWVIlo-@vYUp7`cZy2jv_O3C78T!bW_M>g+a|=`}PET1wCJ2 zvn*J_Cj8(-!TuLw1H3vxs~3?r4;HftfA>hR^F^eop!Q2h`v)`Fgg<>Wc=08qoq~dw zk**BRWfT7BvEY%HV*|qb9}6O{us!rxFihJ&9}70HWy;&nub^ER^n8^~lVF85?Sg%5 z)C|wdNnxw20HD6o^K*u7_87H73|AwTD?W6I9ROB@ZdddmIk%oX2-|h z3}(K~L3Zp2UexxHcY?wlY~Oe%7`r3(GTCqZPV68S{*~WFd(XSU#CM5(@bh5nyRq3= zQU=BE5q|uHV2QSWwg`5<7i(1aH!f1?kK4oj5>yG*mTs9K6FF<|jMZ=Sz+66^s)pE4 z>iSpG||HBb(&h zRSb#v8C#u2EB^Vka`s1?F{W@1;&P_a%@`gJ{%jYoW6LivInhlFwbea0WG05{0pO|0 zDxDarZ)E>YJyhp|$%GmrWa^=7F^(-bq6ZHXLr+#*Bvqs!BN4#FRC?~i+%2JN2dc^bpEpWF0OmW*_@G{m@y`|)j9`R4( zIrZre9nKCuJ0;#O>RgDUWs5@1QvS-_?Hkcc75=W!SaA?~C9Pzdn7Q`MxY|vj_LmSl%a^s{OkeB%&+rHEce-DV*lE5N)eg=c7%$)YVWhi4 zeiam74*9afY02a}I!JysV)%ZULX z+1vYE*^0!s2D^vE=X)c9kwfDRy|uyRL*rT!bj#5A(eTk|aPP2qJFjZVfno6pEX5HF zJT2bL>s^vQEuQqkM{|Q~PmgzI644u{$EOk7Wq5pwx2@#<;qi%{cV0>5GvbrN-Xp=4 zBjXo(qhAeq#8a+~Cf$;*DA7KX~`7c)OZTC6ObQk12;L z9kq*}Gg~JMR(YeO@u>Jh57&P8j*ho4>(r096!p2YO2^>H==k-l#B{}&cu!iN?PKCY z3*I3iueqlQ`x&GBjuV0wXUALAY)M3JBTrTGDc;1&&PAhaz@f^jKto-w2o|3mf1hX1 zh;!oYnWnYkocMqmcd}F7m3lU}r;Ay3nbh5%y4t5>Zi#npyjm((YZ8P*_x)e5SK-!W z{O^|vKWk?E&*Xn=cpjuy#486gFO6>q#?|qv1`iI%%?>_qUJxtUusFV@e_9{+mk#N|YM_SS zOB@%=VR}X50>zF@P>q ze>mT*LP0>Cx+04>1FmL#4qV50EZoR=8rjLSqfFTyDGzUS_N}k^oNYz;BSlvz=w?WQBNQJbznWC|1>@gcE;hz$H8ueL-+z%AN{3233e+S!W#8(D;&xa zNkbj_pf4qT9c*fRG2G7hD%h=Lh|UdgFO%ynx{zHKkBwi0cN@O} zA25C&K4|5d@y~GD zSnuwaiPPjLPT*>b@L{==QBI3s&0H7Ph25%#a0A#`nZu3XyG=*8-vh?Y;YW?z!A}`; z%Z0WX>wP_~wS;v5-9m2~YmiTqXC+?@e{8%I-fgTqLUa73a}E5h@pbTz#w+1pjn~3| z8E=4{!Fh`EzmI^X+9{zeusUbr$KmqE@4{7#KY?o*?}z2VnFTZ{>;~hrVdp9irHQa}6$fiF&<4|yQJ6f;C?QP-ddOI9zt&fgTy6i; z#;f2LjAi?!6*SUP+DzbWi;%(BF5}1Gy~bPNuZ*9Ezc>B>{@M5d{JXL2w?gy}l>RSp zjFc{_uImvb&mOd=5Orcs%UP*g1fv{Vz26Y}i?`Bfl7SZsXvE@Cwse z3Tq~%GN-0|v$I6c&pafYdpKAR3FjUTt^se=4sb14zJ|ngV0m;E*MqgVxLD7ToyN`K zUB<29J;rU}FOAck34CLLli|O_^y#8-R_?znm$8y3ke4x5cTa0!l-k=@G}Z|??{Fw6 z9rd|O&F7tWIOIyFi?c*0puS!&Qz{GhF;-vCxq&N3rGd|t9M-^vGiisVKh5OiFEq=z zA*`j@Wzk*?Uu)bIzR|e1+W*@upbp_0<3aF7V-4)xYkUs8#aNGE=Ms(s%!HpYxgM`C z8tY-R-S`?gc|z`q)Q1pj6HIh;j# zOMed>Gu{W6F+Kp7GuD$fUD*N$3Dh+H5!P$D64F!a1mh!cE8~CQ_QpB1o|*(Lef0-V zG?ragKVuCt4>oQLpPu1#s3`$?W!KIv;B$>z!4r(zz*CI%G@E7I5x&@1PqszI-QX*X zWgzCv>Pi1(c!kL|Dt)VFqUyls63|p{u{w}<8f%rNdyFrDA2OZ+ml$6JJ9~TfT>v|K zdst6SXKxQ*2|IIpcs2Z??Yn6VfzK>(FTCIQe%RUDvx983|B#$lC!V106-~@#59P)( zayFr)v2Lxh#$|(at!Rzl?Iq=MgK8_oWrKg9k~2P!znWl#w)Qu1jnK|u6P`IV7_>gW ze6Z^q1^}7|1>d@;bG7LoJgm){AnQ96KRqiL_Fa57Lq6LHdd*8Je;;Sr&SAmgABgQU zHW;&sWWMsFc7U_&MKKZtFQBtD>(bgNATq2RAwu_|eeO0guG7WNCz(VrE4Ip)7ZlmS^{#PI`i z!w}^dG+4V%wSpzl_MpiSi3}j4qcYG0lNc(I4CcZ-xmnQja z{J6OIMa71~dJ;z3{{|-CP!98O;zl}Ncn8UaM}L4UXVlx$tcW1D$5mkBOEMkJ?R}wA zQ*)Pll1t5Bgm!Kp-Mx`e_!3giJySVJG(nQeJ?kOqpU$7$QNoZvH<xcCD;0X zBxN1}BhhwcIf>M2)s>X4wU?Tq+@!8Wkd^8qom7}RCp(qoZ%#^H=yFrd(Tt|lV8v1z z=3sD7kH=K($ps|m4jkJm4Ee1{F81sqFycQIRQ)VgA-PE9wqw0w{nvu-h|+4XW1mSR zk1WblS^NRM%{H}L`A=~ZlQ+GUhEqq$RaWW(IFeFFJ3A%2`kYi9baGRz$yGE}iG0RV zo6unaIr*}Sw_Acz8M!`X1>N72@;K7LmWRJ_FZDYKg+t+gg1w)`nziV|9vl3hT9Jx> zgfs9DS5-Fr>c}%3@joOD|FH5L_4@_gKabT;tHk{ywK=Nq_mlpSMLLAP!^xlLZ=tWL zwEhw8R^&!#{Uf>@mHZXt%>PYX#n%FS{vQkZTdiQM4pZLK4mApD{TrxRu!fxb6ZA?2ffaH`8HTxFhv0P|w~kc7c-$p3~X1cG`UkZdXh@mv;XG^{f5% zE-;{Apc3um)CLu3imu<)1%?$|q@=sK!0>|G{|$^RxR^8XdnhIy8dFfLH;$h^icDMd#b#13pAk0LUU=&WSn2nPdknAG+}dL!7tJ~$5RbiQgDu9#(J6vxwPO@ z?R1`}&g-%Q^(_4Hp1i+aU(j7=G|ipsjRoBh`7>PLmIBScWT`sNHMFuoGXng%u7KAR zXtc1dKhK5M7O3Cv&v$`!1_7I#YI~;#)xU^U=<6!-zT+zLK51D+ z-j}J!%art2y2DtDOfND1TZ%|r!4*s;|5BOzrw8dTVzox8o+sn(4B`dzDa2%1Pq&`d zBGdfI#FfYj^V&(4CznZi?UfW42zg!yaoB&9Q_SnAqKo)DIK{k9;;8>m@DEuIf0G*& z?Tb~dyOlEV)Z++E;J>%>NwEA#^5m6BU%&E;f-_L%u|9WStfBX1ux?+hWokdi^1hYM zUjA3TKiI!7*2H@zD7!yauIvoX&#UDw++kvCEDGB0k5%{f2dD3k)xw$Fy#29O;f|5u zf&HqKb`$dM6@y3P@PI| z);a%5^>A$>4-Uz}o*JwsKl3L!%<&1=aaQpai%_|dJ>qJILrwJ(oqEQdYO}jamVOo6 znG=2~5)5pZsQ!Pkx{MpCZ=&^JYL)an7+W6suZQ8~v9sjcpJU@9B@Z2qb#9oxLFdE; z)USoEgDx&V-MgjBzG!h{k8{vZFU~T8*RQpTk5@?j)R!n->Seo_w?O?g##K5vuc4;W z!TCEl?y-UQ^_hTsp}N~xFH{d0>%pZOs>e)a_$lL>@HXSx@GHjk;5UsM!S5M2fj>5G z3hyp;rlfb&0~XN|{?@o1{G)L<_z&YAum)(9p+0cwg4cRsD`WCg;c~|6AXPUW0oOGi zNyk6b&;ny&H)_u%P^tEi9OmsZ)W=vQp%z*4O7Jk_8t@s$I{PukdgVONxGk*ur+quX z8n{U-pqKC27SS8NQcU-qsE}&0SeN`JV_h0OWh5_v-Jm@zD~>x&-T;1FOuZ?DpE-_S zH0ET|q1P>-v)N&+gg!B@27h5(1OD1r*Y5{oT{6wc)4{sIx@q&taS@y_RyyU4>%i(G z=a-@U8xzpYs~uEaZt-jQMEC@gYe=G(nCqfT;|AD)TCiJ{1#STkHyx#Op_rmlDNZ|% z)i0Nv0t;Q@2G|KG(Zv>_JLgK{^6-tuDxuqrtHW!Jb-mR|Q=%&N`;1jW4;wd!^$}Y- zs?^UKr#lgN!2*3?^`JF^qYUXGEOwW`SVi}>aeep)9bq@X4tIgw06W|bb_48iPuLBx!~J15zzz?D-3l%6aQGx0j2fy- z;KtNp#`!}-O|F68&=IN~i{Wk+IJB5@Qwh^`?Yn9W_ml;Zfo@S);hOlx**7d+4! z>7d}B*6}%fS)bE}(8}NyZEguFwB@z=wV-EPUYj2e&Tq?Wb9DyUCI%~zgs-lfvC(;} zZ9L`O5$q;9{C-{cmRzkJqNzc%c8KcM3&ynJ&3Q7i@YjQbh3(??c&Fc_xI-rdI|$~C zq3v9i72MJ#?|;4lYo+$??XefQC1a4YhLZ5+(k3^}tklle{^jcm3y0{l1Mjz2Yjey5 z=Sq}$zwIeuB=@GwW{!kjvKC5EA8)7H6-uo>F^n&q#pd5Zg`ZKS4Byfw zoIOsO(d0}p366ILNp z;oPah75Wn$!(WCFoWWHzqHlRowPBI0R-Y0R(>Il@;?*F^S?rwE{sl0u?=-k)k`CAO zX&qn|f3inyMiTAIv1{QFbvjxxji~2r6^3HniO6|&G8l<%_JRdJ$1`3ta(1Xn`7wPm z$aznkOlVE(OeFF8$Tm(&O8tYix~q8S&V z>|`fpA-N0Abz>dTWG!N2$$ca{As)(WNK7Pm+80VszAi#ZJ@!0$|D+en(^r#7?n?Do zk}vZol)RNcTDROIrc54b^~#NS`RtR@+2-atzty2sOO$hc?<*yrDh%cyju(xbO(3^M zMmco>yW;SuEjtyaevo!!Zz6%B)csOx<1HspC8fqVx2LCatC4z5ffKzCiK&zNR)N0W z0ya%jdBLH>@rn&TB~!VldS9VXoO(tIr+X^84k;CH?g$s?nCcz0_$6Mk%3x(-oRf7; z9hUY?PmjHBp>WIKykD>cJ2klamw0h_ix+JBC0@Jg1|oAmSjpd<$U6QOO1nUX6AIT2 zyd&`uX?D-#ro>(@}HI=OvIO&S+?))6<=>px&A&K)V~>d%->GB z3Dl1oH#TfeJh>yk*w*Puw+*E+fje7)*i;vYaV-@lI?F7`F-IM3HQLKpdZ z@0shr%wBW+Gmy;oyU4leEMG0$OrOtk*jBdT?{xoD{!a6&auRrf(5iB|qn;*h9Au2h zLD}z*2wO|N(O_%2o`baXA0^2;ei3_ehL52To%)%}ZghET?W3i*7H0cHQTF^bNVbLi zdBJa~M74@Ko#mh zMFgs(Mg%SL6BP$;lR{tba*og>wNJbDcaF@Omz^|oRtQ`1mBh46=?N1%)%%cLi&MWS zaJut5(IIu25*XnE9aD#bTk{k3%Cu6pXL@&_-7OTpF?cIKQHLeJf6h-7dw&EC3J5I= zPANz%OixAyuOWIAx~d#+V^vvw{dngv@@O0^jQ7=GXzT)x7VoFB55>lbPZ4`Dt@sol zsJw=gYIWm-#gSMIc8U)b=fq@}5g#G_XzWRfB7Wv_-ITG|FPwJ#EGfm48X=31R(|~0 zb=qN!m}dBPj!=}iFr+fnz(iuQyF{_ODU8HaaU}K#wJkA2D$&^A{7uYLqJB)z^~8K# z#Z+t@e-jJDg`u2adto9G{wXK;s4&s0#p@(n#qakY9pWVZ_@_uM6w}AW3`b&H$*b>9 zIvV>Z=u|FIJMHeifgQ9%Ovm;I>l{-tT^WCfxNPiw@oD1x*jRGo4;L52o+jV^$OL;8 z#?F*{l%DlPp}b4kM3SwvhMcbt+adpZ{(8P%VHo4rxPP*pa$3RL8SE~XsFWN{Qr?M5 zHdJX_kW)S}#Cts$Q9e->yWlzH6P;<;A1$9~?9B=GmQU0#J4EWA$uMW1?ufdnpkjqY z&2WS4pi_lJMQ=fHT7^Wze5Y03liCIB5NfnASYCnZ(?58uLZWsxW#1d%DgWNkCOQE^ zIs)=L7FQYYP~afxJu%bnq|d}s3XLT zP3fnWxYVNgKVk|>KMf_84y;}#c7qc{>{p>jy5ImlqJ*xE*)`g1(%MvYG|i)#8u-< zr6aB#;L;J-POx6Vl+kYRH^#kTnaWAt50-hHc!(NjnZzk@23$JgIuedj?2?a#^Ngp! zGLMs7HLa5IJh+DOGPs_xy2wq8Z-!eM-v+mXou_%-JY6he72L~s1MEg(IoL+njl{xw zmvkesurvEtXMq-ga1*gXv#mtS_$VqN1qj4Q%!^cA@V z#vhSf?E$|hkK^Zz*@WEaD>_os=T{jD>$mqf)-9bct7IKmPkSlpz<(I4;&{|*$!o!G z#1*as7fEh+fov#BxsEYO;C&$@rvO6Dja2}}#@hWPH#nMr_?@B%?bI zXna2WnDNE1p4-x22tRMU41U#kIsBIKT6kya`q&Q=*kut9!+VTXp}sVJ9sbeyWB91? zUifcg_qGtpWa23D0^=Od23g6R50@@|T}JnROH-;#L?`3=a1Y~#a9?AYO$;)Yd6ctU zCQ%J-oG&?#EHze>j8$RXz$$XpAvdrJtMPFb%32?*ApvKi3^#$Dg)*!LXSpS$MkX-s z39H+wgAInAg)%$_zT4y%!p=e&xki=TAS*l_e#vyyex&uTqlA_ac*pp1_(S7c;LnWj zfcF}&h7XHr+u4MEH|7T6R>{a!mvfBu2yn*7Kp|XJ@-n=P=^m|}2{?A^U6-LdfV`Q> zRix^m==()J+|gJGY3NGw3UGg8-MV_|kzB<-!niJcuCYpelCw%iN$vj(i)aqJb*+)B z{denH!=2#Lb*;JN$CkBLCRS=+a#y01)BY56dx%`nf6+(`cp^UUPD8MAp`n{f82 zR0D>2-l0-3%rhdmZe^l;hZo5ZD#JLN+M&`exIOREP_yO7-)6UR*sMYpeP zL?9k>?>IL6qNj88z2(R={yd|*#Cfrkf=R0qmC_H9FGh7Zu;}ny*e2HPDyUc#Vv5 z>TCt-d3sJ4rcP!bzP|M#P?S0?n7TSqv2__GccS+&vO1~q%5Ld6jvSZ8j~~a;XFLwt zG8GM;Vb=yKw|MEGjaFEWpXt3vIvH1>@lo#muS=>R_-A#ZlGiw>w1!vPt+_$BHHlW< zRl!Vxyw%^jCb6)tj7cMj*0~f!Ow~A1I)bx=h(zZf_#2%WbXl9|Sa-f8HW;H0SHcEk z_KMHcm5ary2CLR4DwMf`JSD~`QHEX)hJzQ^CTex=%>Mq2?+`{~H*hH5hF8>y_icD( zp}2H-CGr!xx$0(z2L`p*C7RXMhjN^gwjlY~bEqZ{|IT4Uv6~QNI1*bC%t1A&tCc)b zQ^#SL@8NZc+7+*1@8l7cWTluayOY0(tHf>z4z5cSH5`d9o=9~fHB-~bY_ft2)G8~> z!eo(i<5?&5csOXjK2f)IBWYA~!se+}RQF_kZxj0#mz^|YMkv|PU9vVQO_IZ1=@w$z zr3M98txvQ_>)DC7QXQ#xDvzs;w^9X8N@<)GZ>7@ilhPPyvYn@4+Wx7(5GC8Yz<^W- z9j=p88b!}$4z{-?vLHw7hfY+qfke_-uFjZ)6 zY9@biV5-2nlwQklV5$Jdgx|)4QFkUz_qqp<-O1gvCmwuyXQFzW)f_o_d6=q_6WI$# zb17C_m};f(OqJYwT$OaJD%oOVX_efklUJ3Tv@ub;g{zYLGF9?poz7SC;DObkCIu$n zQbcqe;Lux(?aR-PIFJeVr0@znU>$Bl`Cv$f(uVVQQlDFfkn}R>tw9Mfoyq*!AMD-KN zB78Y}RoC1lua}co)-CJx&hV6QaPnP=9^ub&O0K;tvB+a}fz;+itETr+8@!7n%Cb`I$ z?}wD5*S zpC0mxvh@6IHI_swzeZ-f16eNlR+mbWqnv-8rJPrpIccgm6MN$^{i7z?a!mh^+eXu!vmdx0axY=u6S34h4cfq~>c`Lg< z1A8aSo3X|X=;Do6LG)9_Gr{tp%Oi=Ft-DF?M)ggu>gNqo379~2@P7#2VOfVq5;f|m zf_p<%w5B;x**Nt$!Mr0g#Y*sI%_D)`|ljZ9iS z+JSi|xz?e_3J!t6w-W9#55>GeUVl!c$8Pq_8|Vn$97)))_7O>4f3xS;NR#h-(JG~f>houAN$bZGP4aT%W!1lX z`F}YiUBZy`oB5G*>A04hwkz>=R!O7J6XUY~+u`Yw>-Ht)j8AJ6wRHSh1DmDe$xFpF zPx|R?rF3{$qi3ZnUQAY<<-GL!o!h|%yf@c|D1J5_sh|WUe4H|z~p#Vj# zB66+^VU4D&HaYi0#^x2d&iEdaSBD=m*4dXB>+GL4ZUnzztdmrWuVeLs--Oc&3?Q&a z5!6STrHt!BxGwyi$s58y88?NG8aIdkHdc}9^Ow@m!SvQH?g7gbKs*@MJEoWtO^5W_ zsK7`fY8j7)8yG(ZH!_%_l%i+^aeiM9_@on(AjwyeQ zs!gznHL#wTN@ydjk9Xp`VL6Ww-wUfpBz^>5Vf+MqtMQxgYU9u0JB|0k_ZaVoA4*%` zD*`3P-@vjQ(*eGRo$W0A6a1RV)zN<2IE&lred8GXiE#qfYmd??fMxh3t_Xi`oUTql zQwkK(2>#9Z1UM4O$Xme)<6dxCV_7a%FdhO|H9iZjZLF?NL*prMGh>-lwsyw399WY{ zJ6gmNxV!Q7@JYruz|O!H{Ttz-CfD~TXJ3o_R@m9s!Yko%rn4Hp(0CoJ_a>e2of?0h zV}W~!xWriFCX0<9g0D5!cd1*A--gwNS3>W=n~gt$-GUUzzl7a_6!5q3Hq+6F$s6J{ z5x)}98?F*k>-U+lT0cF!Bu~NmlqaqVA2hB7|6;6`@DJk?U^VN~Zw~7%SKJCt8n=ei z1r|^fSZ)4->Hk*7%H+^lj-v<7eS%#%jUz_?6BZup1tR--Z{P{2lm8WAy>9Gu{Q?WGoAy z+uisxJO78kT8mJNx5@Y*thZeqOs(I;#=pQ%7^@}J+pTmw?w*&7)e>$u*87)6`J__- ze`s7;_y1=Wr~!X%TnBbz$n2oj&n-3qyT>m*3MJG9d9HC^SYK`>?++Ik4}{epmt3u4 zb>m^M#>~=+P!rkMBF=!_ViU;KT)M?3;B(;4rZWz9i%lRuAMR`NiSR&U4ZodgJRNqf z6w#jrk2CokeSf;p0x}AiX1oxdW2|OZ6K9ptweVtNeH^>eSRco3G?r=7?Z(f-YmMK5 zHyM8j-{+Y7Kbu=dW3@U!6n?^3E#9-nYVlq&ZUApLo&djNJQ4oTcnYk^h)Po{Sl4lXuM>pM^<3&{MWhq3PMQ;anib(nE4Snn#z&=B|><1=7Q+Linq_#!dA6+I); z#;3xU8Ov^ise??;KCmOeb-GUV8s5y5FQovncw;%=F9ZtIiDF|rr*DXi^4}#r-6!37^El2^M4bQRz zEP!P*rnAvRtwqKk!)`$esPzc>L-Yv)f1OtA=c7-tzD zfG;-IXM;t?-@y8Gp?wd*D~u1rcNqT(Z!-31i0(613tM7LtD4R@cO)To7+>wUB>Do?lC?Y{?b@|#P5vHg?}>E;$%mSFNXhi%>A!#2>K|avsngf9bs_* zmp5JmS2dP(Vjbf<;l{>y!7Yu|7u3M7(zzGzYJ5N3*I0eTq0T=eO0o|aVG%FDqm5sK z#~FVDPc{A;o@1;wU&GltnEHi_jgP__0Fzwi0oNJ-4c}y(Rwr?#1+w6E#@X;)#tHa- z<2?8gV;S{7X{>(abH-ZC>}BI3_zmNl@Vmx!;E$YtMsio5z~>gB1qAjRt5f-n@nHCn z@#*jpV?AvCG*;g+%qzUkcrF|@z8KaQ49OS3g~pev|5wQZmlILLco|&J_zt*&PBt!q2N}!c;54z?|ECFzvM!!N|q*W4Hq#%8p1vK$sjjOVfqFGjn!2<%{UH^G_C_{g1^!ksjks@3rv8WV@M*@eVS?V`S3hr z=`S>11Yd5v7+z+41$=|?mGCXb*TJidwJz8O*tP%aZf&-RTj2+dSHh1O>)YX05mJ1>+RtTWursG?ObD=)_PtWEui(h?lpcF-eRoJ36C4^fuAw{ z8h+7OD;~dY{0qFpSe=^>jAb43sc{1s50;t!e`5m9g(YXEUXOEO3Acd{+YWMp{kw5T zSWD^Y+IN9-jJw0W@o-p!o6@-uE;612S2JEH`_noWxRQuQ#y7&vjc z@$*&26XENPXTmodtDmsacqyD-X8}2QzRS1-zTa5S_eYGMg`YHj0e;SS2dqz-D(b`V z8^(GpziV8PCi^4fny`%b(NBj?BCy{g^r-yCcp&UtWRmDm_=w5Jz<(Mqfx~qL z$5_vqhm0q|C9rG%<$?HVi?|ei)p#lVp7AyC$Hsb~>^8mwK47c|%D2W3z|KV`rTaMS zTx7ye!Ole{{EW}_&*HO(F2Qp|#Ef5n%NV}|mot6^u5A1oT+{e%xW4iG@Cn9x=(IBa z6mD<48}90u>wkd2i5Bq{+|O7pjt3hbf=@U889vKc52JI9HRIv}<0w4MxDcLWtOw8~ z#?|4)S`}Gmucy(K#&UyvopF8mCgVo%?Zzj-YmHmLn~Yn*_Zhc_A2x0WKWW?%-e#PZ zKV|1flT3_+ogYnDZj+rKP569xm+hd3)E?tm@K?t3VCPa3{e|#PCSL|;^LZz~DvxVg z?bvZlu;i5S{(-Wr|Ilh4i+&Xbi?x{=yr+#H)V|ms82)o^F!N%6K=`|K@FH8j`4`Oh zPY+|ldf@`J)&x&2@aK5X1+CJAW(JG3X%M`Z_8TYjI9Tb=3(5>jRbaix+6(>q*{==e zK^zQR=+_R{A8n3b7W%1h`_UO&(q6p2GLCcXH!UL%cA>;Uz9kLs?x0{1n^%L5Y?6mB zq)DeMsBe{BoGs}vz6_q2m055xb&$qCE2z@v_~j;8AAtoIi;Gu$Ublfav*hYNdDn&0 z-?P7W7k{erzZ?Hcf8j5o@XqG^WfrhLb75Wjo@CdM)BkfBMt%Ooiu7TpmaQ=<3{7{s zP3|gfBH6PtDQzd@<`ZutoYOCIy~JKrlm2re`Z$&=Nlrw0iiR)Iz8TxoA{`=kvD71x zb=pKDH)`XB^^ujSto!@1G{nh4qJLh!hU1CxCS1i|dIzC%6rDs{^x%J@ zad1ZeyawLY!Q%dTT^m+YBR316QP`l(!HpuT(=tzereH+1I$X6K4KM4;P^&dk5sTi{|RPW;j&; ze`jHk`fp4Ln48}=nEG2hQqp#KUOL=;(zr>Jrpz7}nmA{|)Y0Q-PMJI-GdaZw$Ay}WpE7A&v-8iMb?*4Hr;lwmcHHb{<7P~4*RoBU(rw`8>hC)vZ$b8q3G>E{ z4hC;7w;~uiGVe5h;*`nf*KmI(OfI>9WL~Obx>zO2`>lSbi@5{!n5k6?98WZ^hdeDN6Vh>0 z>0lm@nW=QJTBqfv!?1Z~Djo6)@Es5R>BD8bj38G@Xee7cg|LRN#WH=+@U^%$tgn$`oxQ$5y0yi0 zma?@`gwE1UlY?bJqTy@Fb;cKp?b=T}j^`M&$xM?&N10x1a%K8TV`cg}V=|r2ELg@4 z%C*M9bpYjht+8^gCFmtrt~Cu-+ys8uxH7(79&W5B-zejw4U?%Y{Fkx3LamWr$wuB7a^S(oUYBtU=y40i&cZ8i}GfB z1sAP4|2hl*yjG%dEhnOaWJi4=&57JVAonue=;0YUN>+cN#*(ZX zaF?W+)kO4MJGPogd2Ty;1Pw_gdO62o9?CcD8hZw%Nb!9j>1b>#(*7MVuK9kJEDfyn4TpL5evAh)0& z%9l9{OM(0XNAiC|#8My%L<`QJ6JjY4v7cX_qp}i+c2DK21z;tRooov8HN3@2ATg3D z%Kw%FvJyyB0#)+$=`hyQEt@c^M*e7>(23qmqUz+Iuc*G>GSY66f4`FJ@2w-yyuelm zQJ`gh7pa}<>1$1K{+kM%?x}=3V#pxd`1MMj+4b-%9Az_8o3l0MydQ?fkWmqk-rWkT*~Teud)xE@~R zmdaO$EN{%QbUS3Zt5oIkV8`lYMQ=**&FW-jFBasiVXDAW!J;+H{@4`UwkBDxtUHG{ z-Bnd;Rb#&O^wlFwn}z*9BYFCeNs^kxlQY>niaUM})>$tGcv~ zy0p~CAgxyGl5JWymU3}erJ;_iw{kp!>Q1@X6@WLSsw=)?R|bdKwb`s-)w*Q+3RS2; z-n8R()i>GdQ-UAXC9AL`Z(@D2ecLOP$D6}?)ae0%VK0w_OkQqw$?N%4HKt75><-XQ z;opLb*C(qra+hjtro5ZE!e5sO)YDVW+Yr{(3ojqk^0nZF^~slOx?C5!Q=G>MWs)uL z>DsBkPO_UfB&*d_CH9J3C7wX`s_xXaEAr+dUaxTZ;6EFZRm$pvuy=(^?JvYF-wev% znVfo(o6~hrmr!L^-JR5W`W9{6(OpCr3Z{)a%E8&hk5u<7@@@|n zZc4U3Sr$3oHu6o?cFA4DYkTHmZgaKH8&X~OGdjATy`iC|A8VIASzG|i!E5ZoLH^m4 zY+vsqm6X=uq)~C{X+$@ZUQR?s;0*~C-xiFzE7>sLo&F6ulIAJ3;xfUl$jd&ZVw|t# znnJ^ox118ZcUQ7&;~RDSyPdkU`@7`-re9Xo?QnO_G9*D}ht79%!drsLcPA@mlAGXS z?`%$1 z8SojB#F97AQz|UmId&t@e*(Iml zle{S}_ZH4S9Qylz_uf}EI5iTj8a(r4vdn+CV04R;wofJBjs7>Ui|*yltq@(~#bn<| zkm~5=lnp{2 z<>L2}m@?CEq$&~pcm1wWy)K|Duix_us4ve?-&0FX-J7Vkm(R#A5?9D@o^Cbkmo*lv zJI+t0P?}AlpT5C)dXM8L*Eyvo^R>B1r6%*UWr^Zd%P5uSj7wieWe`-#Cx}Tv^RIYjMdiYQ6>G_@ae|&;IoXI>-^8PfLaYbmbHT(jPt}?0+rrEW1XnF zb&~7kmKp0E1#$(|>jn9LVR3gnP(65G=3aHCb z?>KH^EVY)#dT-PdO8P3rF2<@-y&O~iH3;;v2wj^q#nfsY;A~@6at#Yfu4{jxv5I|x zm?KfSLrae1tBk3)AsH4)hkRsKeTMb?-sE28NKrfACne*DjkWU=#@g9gsH3BuWm%+^ z4;Sy2h>G#>O4s7S0l#9Xb7NEaB~B9uWF6Obj6c3uj{jIV+Lk+Vc6@H2tSEaFf27I8u5wsfu$VcMvSYeZP> z!DiFpZVWwWOjPJGW8HpkkrRZfjBiTLLyiK^Sfz_~ZPZnk0k0yyF;**f$XEwEVyuJx zY0NGm%{JD)x~HSYI#{VCdUd#x$#qNDFs=v7>!GtmZ%)8joRei`qO+7>Wx`pU!^%Wo zlPeR>;vBg$;VjN!wT5S!P7U}x;|8#-kCdj`24`>%x8V4pC5qs=u3TSbTo=CHSWl{( zjn&B9VchipA?`h(tE%3$-?esj%HCPKL4bq=2q6%VK8{-5Rnd( z5@~`o1+1~4fP%d@6f7VpcI*}Pd!D)8h4Y;8-7)@WoN@2n1G0Ya?0wBOEBJ0#GjIta7Kt%slW};Ue&7!mKr4 z3*QOm$|Xi@4fwL~1~6AEQGF9Qi7SgJ6hp`$v!TYXlEECHpgLZN7Eev(-E1jfy?`9d z>}V(ID9i5chEQi=>?P{#E9k}KP-i(9Eb4J4>2MJmfO#S$x1RDh*WiW133Smn7ul#Xrg*H_($O?;NOL-vi<)@gzC`XGA3H62~HKR1&#@`pm7-! z%`^e))#Tu2;M!D26~M#dsj*~cO*3I;O&eimO($|3h8XG|qQTH{i4raItu4>GZ|55cIXA+;2>Bp2nRD2^@4CPQ_)r>hcd!TVi=@tTodE4d)1Pf z;kmpgfYM>rJ0GL6zjagF^D&THU0s5RI|*9X!oyN4`6yB!^RY`6)P~U`JylU{81<{y zAi`*+0;nGAt)>@*d--1Ot&ZVo=c`B^=yp&e_*kwE;o&=5xS=le7O38J5rTWwb9JG& zK$WWpX*upafRXpES7+MVMs{L1*F9x=ii$K0UpZwO z=SMaS|Mz+cHqsu4Mo*oM;nbuXAYn+iE+7B>)&jm*F&*52dRJ~O zm`6oBh4W(n*INsEY6pEI-CGO#{jYB===Ps}JqE9xHgjyjtl6Vx&r+2RrhTv8T$9#B z6(*J$ytcwu_p4Tqr^QNUHN#B$|MpG;pOd6krKFZsFW&4J!}Gk;%YuRD@H=hpaWKB7VT7!Ig zoF=%3IH5f2JBIJMdj-Sy34Hk7^TGYe$?U!A@%yT-wKS}=k@HfSG z5pBP})>yFbbvTgdUaH7}oW#V&R9z2m6Wu!%%hN#0O;n}ViGdaPi+TPcIE_(#eokVP z{wOR2FiJn0Q=P%FP{c{da4#PG7=LdHa^1ng;6vaA!No8;Kgd4ljluqC>23(V1KoMS zwg~gwU;}_?_34ZtyPX)NxA90Cej0uS{KIJ`qkQ-#z5Rv!=29l>Un6R|^6fNTBq98i($xq-=^O&2E#sMGfXiC+x|e0tH);2%!gXtD#bm- z_JcORE=%Ktj|UmcG;R7pMl$VZ_0d`uX8d(QCivTXNaQR)Sh+D)s?hTkyfe<_C z>Fb?)a8TRP^-hNCOJ^Mkjt^xlR$r`lKEUdbXE!)c`fB*qZOYk)1vv#9o&EMJ>hwk@ z&(}CXC2VrqR+>bwzw#Y~9|6Rr2HuVq;;eTGWd?le+D%TC8tlN^XPE?iAvH%9*u_im zo~m^RlkzM}R~{_aTC5Ija;~yBtJ9mDO7)N7=%NO?ct2I>YAjWyZgFbb!&KufPQ}&* z=vQZO|DwG{docuZ)(p#Dhsg@OwIOBdSgqH(+$xY+p0V7ZM>^ zBjAU<&Bx$!y?z#o*A5@uN_SxQJNmR!>kfciqZ*9i)5E%*M#ow9Zf#VlD#f_i@`x%d zc5>3WEDd2JQTjq|OyP z6?_#!8}7oH{jFf$Y8Y0P?s6(dxWX-cwq?f?7}%w%$6Zbx%$6*?%gKp91U)9o8m-5H z>`X3Vx7Q}HpmU_LD)ag(l!0pY)xHJboGmO>#}l{;1?gqIOoPQ6VKw6fQ|P#MDj!xe zUZGRR6UxF^H^`a%vaz2@U=`yU-^?$sq6Jrh>u}w)aAl5c)*K6yBiZ%Lsja$fb#mie z_?pp{&LsGkL%kQ|be)0;y3Q@7{$^L7DS8AlGb+)~Kyb}w=0glrk~Zx}waDc6fSs)6 zW|TQ;Xs2koC*-QbB@ET~0@=VuhZfb52? zo_4JYx~6W}TzM>HrhNk)wr!9acT?lHIfbR41XuhDY4Z_*YD=sy0j4XKcg zR@P00d;NRh({#wLPxoq{_Csc@5Bizo7a+N@Jfy?rL$^4yD`RODVmUEQ_aDc@)~jJbh$PX{6n*$u>dIuHk_ud~9HPeE?HN`154sh9H` zSVxeG71>vyYges;N0m9Xlb)v9?r`elbA-zNSUcAUmC2nqJnOHCWEoAFN8{R`X#MT# zt{qO7tBaWa7hF&#EIkojSgx1kCn1m1|$1VVwfkC9*Ik zq9ixo%5W@EtcjMrRqKs}l-(Up)WQF(Q7EfAq#I~xv}GUC(mqvvw^If4cs+I_EFVKJ z9ZT>m`-Jv9H3MPW0qc;NyoHprktrgtflLpd)w(Z3%3RIT@tl@_g?!cXu$IaGhW(;e ztc%a>s-2+7a#;;;qJ{X|7tJ0TKlROSC)uv1enwa-_lCg?jvm_|C-OZwA95yVz3g*| ztoskECigf~T5>^DU@aS}AQHJb`$<--Hb|ks`fN|3Ikl};Qxj2Qd(@?>R%nT%lkRcS zGUrmgE0x#~YQ5%sLt3qOsOTPCseEfmtvya9CwLKRICEg(wu>cQAH<^J(p2Cg^uXov z3ls1s2RctaIdsh9hdiK?QrDLEz2e&syWG0snKZ#Imxr5;O9pfMwWt2B%NXsz@3f(D&frfC{@P$}GV&Z^pUYz&8SF79cRl85gC3VL zID`9sJPr2NJkB?`zQNpn?U~_*Bahn|%#NI=-rwM%29NYu&HB4^W=r-4Ju5{9FEyAO ztvxgBG9@|ZiFJ+5qU zeS;et+|S^F26Gd$=gze*yZO#OtEVx~V0K|W_2mY$BkQSigR;l$*LwW0!7m#8s=*h6 zM!tV;X#8z3HxPS{#tbfFFwgPy%w!wP4z)M0T;Jek9y9*jDC`;1r%dA2%U@SRojq~S z%m{tSxdg~lPcoQa6rOsP!8{q(Q_nNFwr02d;p(n`4RL#wXQ8FR z9SrVbFh5;9^Fs_CZSXjQxz)-uKO=4k^9){K@T~@O3zlbD8N9_{ej<7IUmrC1VS^tN z*6}}PXq+&Z-&USOrwsns;PVE5Y4EoO|7!4`2ICVger0t<7%y0Fr9}lYdV>5_HkjLT zJ@tHp`R(SZw==k_!95M`cZCuEfmZ}f(UQ?;;BT_QGY#hUTh9!)*?P>KO&+f`SQ)(4 z;GN{Sc*c)PZw<+#1|K!}8G~Oj_zi>KGniXzJvaH8>hTW-$N9bLX(SpPGB{>%hQSpK z=EtkI@P#{NJ#K7pbA$PD3p2fRQgeH(XMuM}c|6EqZjbfU#~3`>;Av`Gvrv_m-1O=h zU19KggEtwx!{FWOA2<=`R#wl669#iDtEc|D!KVy9WAG;ie`fHv2LE6%KjGoN+bkuz zf=(iS#e0j1(haU;a212Od(|^v*We}ww=kHa0-pJ+xeeA6xP8^*VFr&knA=x9Gcygo z-e3+hcxILx%-yS=y7HLu-|7kAod)kS_&$Ra4SqSw zLWplf{@+?x>la~B!fc+$2?~IIRNEZ$TPUM!SxJoYH&+~ zI~d%>;NAxJHJAfkanIw4hQ@S*XB&Kz!M7NEyTNxF%&{}i{ksj`|1Za_gZ~N^2iUw- zi^mOq-r$!F<~W>Z{u6`0F!&pT|1mfL!*6z`TmJO?i04pPYv>|jaYWDN7(RZv2G=yW zz~Dv(H#4|{!CegQ9XEu&1`jrPxWOD!^a3>7;Q0odZr*OF-)ZnVVIBXCh6cwdJr52V z{IJ1~8T^dF&l&ux!EYMOQBTkP&r7jyCdqrr(D>0{4vTtbk__f`;+}fUU=Evl>bVAU z_|#Kxe1#GJ)>j0q_c6Ge!MzOTkf^tWcC^704ZhA`4#0Zm7a6>a%oPy)tuQp!8@$P2 zj@)_<9WwY4gC95exWOk4<{+BqPW+4^d}8ot26I^0vvAqq1dPvm>d6MD8=Pfu6@&9! zcIzJpkv)f+8Qj+3jt2KOxUaz+eD>TKXYh3f&oFpln!2?^sBCorNQ4C%rS7!A-}d(WZU3|?;VDub25TMXW9@IHeN z8~l*L&w}Hg$1fThZy5ZJ!JIv?vk_waJ~#N1!9N;afy901>vy4T>j~) zXBb?;;9P^N8_ek~&u(*r+ZmkE$q;%P+(#Ylh7a}0hSD^HuQ&KcgBKaR#9+>%d12XX z@IHg@Gx%|r-Rk+2q4BK2FB;6LI6K!ZhvyCc(%^3m{@LK)4dxV{=T0O8{(C}ML!-RG zl?|?HaDl<~4Q^&|D}y;#==s&pW5$1oCxAy7JkH=r2G1~fj=>8IE;5*tlAgyK4899I z(bL#rXdE#3kim}_{J6oK=k(lp-QZIOpHT%pQQSBM>KXmh;ADKLdg`eLmoqriV9uI) z<{KK^!r(RrbMn;Ii-M8Jygvtm{tY&GxWQu$o@g*2e&c-w^e>~*E+v3+K0gUMw{`7Jp%mwhb zl_1CwuR7vNY2*InFCB&aGrAGQ5IJZy{vm znZ7IZ!iy7Aol@tl>+%1@cqJrz$L<55cP!cNU*yv z5$VsQ@HQs0V0#-AS(wk#0@55`b?y~BV3x0MM4d(CN8xNRch}Q=4)`D8Trf|MqIxxO zs&I91OmFdnMokFiM58vif^c0hmkZKz12C7KksE`#o{)@Ww76oIC$um!orRGY?gi>l zhcTA`Gc$+W_;BH~2 z+yPj2-RVem{pW#CD1ObXpt~e ziz~gUUI)zMQpokd@iii_UTzd-;o2(9q}wIj61-oyBlxgz7w{v({lLeBSruOtX7zhr z_*(GWF2}7I5H3={i-GTkZ-m)I|0v9A_PemITEe+dPehAHyDT3(6`jmBBPLu3E+^a{ zTtS#^LlwQn4;C1@8lo{3Tp)ZsxS{X@aC70M;I_h>!JUPR!M%jHfq94x1GEdw>o~}J zz@vosqx@MDM7S5s^;9%;2s~T(0q~8&4}pt>9|7}(beew@%oEbdPk`45KLy?-d>p(@ z_yl-2I8F=CK{y~9uY$R8g6eOBc|%8?C+x!u zPT!~lHxq5tze}PKgofV82n`3UH!_0Lz&^Z{7zplwND)aZgi%|pLSgn(cp)Xtuyq?M%(vxeVOGTn!Yr^; zg{y*R3s(o<$a9bAP)!I$!nMJAvm?|Sfc0iaa3k;*G1CIPQ@8_ouW)DZLE(X5?(?Ag zgTapr4+9?)j*o=!tO%pQFAGlqzb-rltgp*K)R`(@P#s(bd`XxMk-jYl>e*l(!A>*P z!0c0!8RKMOMmVIm_(7u<1btl&xB#p-JA&(h^=3zK6L3|r+zMPE+!fqVxI4J1a3Qz{ znaw4BR~tM~*vIx?Z*YVmKMai#4aRbkFy5ZlY+(m{qcF?FO~MGDds|K#06!kz&h5PX zZ4pLyz&c1~vf=kAnU{_6_q1rx4%eU45DU@U!WF@%g_)#hg=>H>2s254C8Ja_0{SK# zFayd%VW`f4@`N68IyfE@Asa$A8LuX$L^WZ)`}2iU!S#gk-m;nqqh?yIgc+X>!j-|@ zgjwo=0#$Ow-cLSqIs5Htp2t}x!P)~?LIv%x7^Xx;c6Rrf_ zEX=aCL%0!mk1)&AefqK)SZ)U4LDAq_?lIwB;G@E0!6${sgSniN9!~m0XxF$z@>#1I74_lI9GT#m`AzO z?jCSm;lp6Apo~-FAqe^g8}Q@c_G0KLSl?y?_2b~dR7W$%tl_drGTIhvoG@S6Q-oPy zd9FLnR0rQA%+AQI!gb)k#rwHws3|m733ml=5FQTZB1@VX3EnBp#%izd81O-1X3ayw z6Ty!QGi#0sPXj-z8EHNf!W*J78~mQ|^k8*5}z7O>MFwhz_o-2gSm*5W`=^B3XcG{ z7G^nOf%T;_U^LP0r7~bmxaIN?u5m3mjhlSym^@MOu@NwbVU@j=7 zWj3YyA{lTK@JCe7cH4#v!Yqtm3Ny>}-7zo|26H_r%wui`0vD9hGGmOh6*a?MD=b_C z%pR+M&GEzb!vo4yim2L2u#r=d0wxImQL9sHN@XfTh> zr}`vtY2oSM4B`1;9-B`yH-W1N7l9j*5lWW)7Q!rNTpvnxq(t26A_7v(>Lr{9<^oX~ zss$b_%$SW7W&s>0%<4KrxEpw$aBuJe;Q`>4Wb{N>Bon1MSkd=>aP;aXs>38e>2-nWEXg5MWz4gOfT1NgihDw#4lv?c?m75sB+sJHLs znX2%%5D!7vc3X(&;a=jSNVQ&!$1b%J58v0b)Y-)_(?b<3f$F^~&d06lH9i`sN=sqp z#9TFfDa>HcE<}5zirkLJ6g7g6Rq7BPzW3&BSOz26s`oM|W~ps>_)gr2QxTv#MYX;| z8(qmqn);ZJBdTCI9#IwNV}W`N58sJ}D*Mh*FW=`2)pR`VTy^wLx_*<2u7FsmMy!B% zHm(j++@=0m5$bEdrTVT6RY{z_1FeFZ542aRZ7V~02o93gsomk-?vb zREJfe92_1o9!iPh`{8w<)~*Uw@iian-WKrcs!-JKsXm6beVa;NjmH>Oz{fB(fRDW@ z&PT4=!v_)!kEAKTU@R#~U2YOgSI%u=C#7k<2KDRLZ6ZfH)K`^vrq#fe2CcKwDy6PN zp4p9Uw!x}y>u~1yN#K8v3RnmUd;R&@iK7Bm@{y7VD|jR?k*1g8D8wwVLltf_lAaN;Q<9_=>c=qzisD9PUBO zzBD)kjoAyABNQ4Y0h9Vtlc$^|CNCoL0F=a(SwI}2Fb|D8 zj!^hBpe}C=XT`ajDyiw$&HIoKc(Rshz<{rvz!Vr8+X{zzsaG) z3!M$%1DkeNh>2LdNKZ1|HMZdOlmszN>BaF7ir7jQ@;*kl&`4_}75KXu0EV29@gSx$+% zv^iW6%}aPoxPJK|u$>yRd9y=G4Cm;khBZ6U7HY_ra7N@c>esY+vu=7cD_Jet60Yhi zovik63AaX=zT6TXXMc$km&1eXKh%NZ@UE)c;CY(U^*GWg&<(bo?t2)+0B@UedXnux znwouAxWJd6tajsxhU=ZX!c{^e5Z2Hvo_U-Sn5h!ChDX?ssIgna?J~Gat9qml&$0;c zT&PH22G|Nbp^j}0cdk1V+L1wYIvU_vp^+gEfJ+5Yzw!` z=1~{6!+Sb?4o3(Q9KPrM&QYXpqE+q%wRv0kW_zs4+8)lwi3eS`hnxGpODS2rJv`C& zHBVCC?+6dGx2oPd!;O&?OLm3}VtP;c5nl+ApMwASW7Tsz!?k>El1hHr8NSwrvBA3# z;jU`gu5cenAM6S*x9h2iyThAs@Oi>L;ih!B)ji>^c5}7#p72=v(~@uR374`<{fhA0 zNr@{Edv>3!CZ|=Yz2V`oHDfOhxS6ds?uDg$)F*qx?d>O3wSD1n>4k_|psKwJ(JFl% z(nbH_>8iW-h3n=2jwq+*`)tJaeVW(S^`W|CQhe5&$*aK?YOy)s=Lej@z;!j8=bC6|tbZwvp2J2tDUIWL9(Qn%Ew>#E@= zOII(+eam6W(O3%83-t+ z1sOhluL78UEEIKiDHjQ|FQg~rU?vB=nd%4>J6_v;@Sj3s2=|BvJ399Yw*x;Q+yVTk za1ZcP!t9Zq5bgthQFsLSbz!#2`d$V2bsJdUs{mfg_@5U;Z0mWN3kF5Fd3XptcnsWH z_-SxQ;pf2mE(Mr*1+4E<0KW_7&F-}O9++nlkxzps34h4WvYwrT#sz5T**WlcU_B!T zz6@R_7W`=WRtYD8HwdSIc?d5(2!VGBbD({%a60&)a3=U6%}DJ{70w6iBS&Gr0azb73g!_Ad>PXn9_sL|Fpq2cNq8ps58>Hh_b^Ua;K^C3sB*Nt z9ITHS1+M_>V@AQ-z!k*|4{@(5d<2{?{5-h6Fz;SzEc_9;8yQt59sU*y$2q`tjR^cy z&@*)~lnvH1b>J#sJyQp+2G%om;2K~(QwMGU=Ew^@U~ST~bKs_6Jv#?(0oIdq;I?2) z&S9_wLVE~$b`IPbyjvV91RoH-27JHpATW=Nq1~b2Cxyp=pAnu2)-!c5GaLMdsPl`C zJr3HvmF>?*BCyJ_H%E=t;BSRlwSE)c4E|e~)rnnUn%@p)gG9auoF%*;oFjZ6xVrFz zV0}7h92Opd&`dNQ1-B7?0?gz4>FAT-9>T}KR|~T$4ir8C9xnVGc&zXXV7`&*&a2?* z!moqlizuLJ&4+NiF!O1pa2N1;VSY&S%o&>R1>PadR&S4RKk$9R1HcaoGn*b0o&-MX za$M)lNzu3w8ZQYKf!`2j)|?XF0%k{)f!GQ@FMK!n3*m>rdJ+z1n7TiUIvc1zh0lSL zc#I4k`T{}-Z#MEJus%){{1-Sw)B~tOm4wTJs|vGWsVU5vIz90QyPTcVr;LJm^p-wl z6x;@^PZ+?_%qID2%6O9evJB0UwR|~TNSuf1j^jLSwRUU+_#}&P&V_9tgfkcrf@@VYY6|ghzr` z3A3!|89aD49;|2TA^`mEqnWq}dZrG{1a7&_Rx9LGRR^^?j%|<`P(2F= z#y@u!4!}aEC*i#Gf!xtLk!Vw+q$1jU&A<82M-82oId? z-Xzl7cla7r+ytsk)K?VW9;8|{g*Z~((G=p|A?gE&zK4dW{AN&|pyoG&-P!6TK8jRk zb3CGIG9P2q6MQUHsVyRNeLoFTx3+-I3!~KAEh5+ZewwLzw1f+}YD-I)+Ni#28L4Ue zRJm4>D)v0pyj3JGaVBb5h8jmDTMsJpd)BD}k#yx`e#a5B%RqwQMCpbM# zTi|9`ViH1I|GwU+{H-I^8a71bDNcDe5KlP!ijqBBK0f+vxd?r;i2x z%Au;6$Xpz%x*CT59UeW0AY#Oh*G6F&kL4A^qdb-!!=r4gF+4heM*Q6AfB{j!2*mu# z?Pm}BEY+u1Fs;f@a8_M90elGcSG0T^gJ+?_l4lXGn&C@AMe=k@zs~qa355hU-8GE)PxR&E+h0-=pp>E zL)&RC^f&zq@q5G{DuPO4=#FN1B!wPfu7?tlW+@>q1Pz3~fi>J4=1*?O<=3HI%*7Bp zm+?>+xO-FRM3NdY0O2o!n>U2|;qSZ{Vs%JWRjlnBp1OnHeM4D!1r<@K##CzyeFro2TC#UnGvjVw2c3N3|ha;O#?yp+(JNQ^*;L&6v_-39$X6Z+?bS!{yE1L<@h zIAMkK&>Mm_97@p{@U278Z}Tpt7}Fz|Y}2e2 zZilJVV79g&jXmR23kF5ftXsmT z21knQrmFRjNI|tq@Mc%)5BnJ03P{w{pO`9EkZ*OD{lRZ+wQfkHc8F=1`U@8`I>D{# zwIPuzgThRsUl_6S`e5GF-^p3J2&Mk{3xZuS))Wq=menE5j&XsZ2$f?TTSzUZb#hA0 zzHX+Knr^d6$c-hc?n5KhBFAZwec>o-;y^BLniWDBDIB9D~vJmQ(J4HU+g@BoZ3bU17lzP zD-4aTq_^#~&d69rCPjOj%OS_ay3n-_S{N4#QKyp@CdA4!6}#xXo)nu;L6?HKH6_OL z`BHmpb5moZsCBj79pRoH<10V4uNG#<{L~p}vr^5A=X<=h5 z6U8!hwib$G`x)_hIyJY)KBq@FYGFr=AHb;#w6HVwE3-MSg}W`^O96G`@W=q%FsI%c z9;xCx7Er$pk5p>>4CAwyi)oYm>^lVpA!gaBAG4^q1@N3v05$6Z*lUDW0M9e}EP%I< zh*YVo3*h;EZUKZw>cwerJInHY$xy#ajleLZN7VU?sT&-qJ{%Fr$zknsf;zCl7>7We znEe#=9R!Qx#et5h^2kWJss|9xaG_p)Erbdj4l&OC>0F_VZMa33y z+dfas#&M;e(yhv2I`bLrB8YrccsoVy85ODHd)lww9~H^0^A}xjnM}P*ejIzu^_O<5 zWX3)R&b3L_O@5UzDtC0GK`e~{-AcdM8dd!yQB52jsp0o4P^Bj+?1?!fbrOofA{5S*R z%Fj|}p4%0&bfRB`LhZRz$9M*kVYc8!U&)DUBg1T5W1BNJvcmVGPdzX;axHq3WyeKk zRnqCXSeKdiktI#up!-X8)wBvu1=QYgk&3Y&15mtOSKeQdWCg9&sd14?tsZ0U+@TwS z?~r;0I&RCgTfCgL$z+7bzEjIq0k5;LS8BujJS=Fh8jO!rkJV%*7Hj2X_|jCV>&8c# zm*N;2jxlF0aiv|s=ZWg^@sVnke`a(C_^3V-HVf7=2m`gu;{^+HOY8}eS~f0RD3}RX5(Ufw-l_UFYUCvkMc?Cw@;w!Z&QAU<)ed^FG6mm z)UOjGciDs0`bls+Lmi#uI{x>h$PKw8N7lE$z7ac5>t?-|0kka7ttW zJ_>4H7pZzR>zjQ<=kGy$ow()0m0zRG*OmQbGTXuLDD#yiau&M6wb(S+PwDc&(`ITt zmaKMO=a$6Ru8T~BH%+HTI%n#vxTs5ofGsPpE@`@l3~wP&(DrD?@<%3q(pUT?^g+i*#4w3;%V%nTAdz+ zelXw14%Q^Y9KSLCVx_%_B8 z=f23Yxp7YNd$Wy0nIS0j{ILh(%`g^`5ljC5BBS*4*NGJZ%^H6k9`j}rxs)3-iTbym z%&#{5ngqc3w&(8>*}Hv_Ul-m?;tp~d*APF!ye*iQ;ixpBSSqF~mnPUhWAn2K6@D{M%;|bmh zzF92q0528Z2VNn30K884Ab7LzVeoe0N5S_9KLx(e<+#2h<3Z6l1r2s18H4x1$AnoU zo)u=J^osCT;J1YT0KYF>%9cAR;Z6qlQ&Hy@zORKjG58~IKcr_|O!Fz&Gz2}n3`1kU z<;4)cUn>jqkO94~6K44K()&8Ww}9)58NSGx2`>TjQg?cMC%6w8ofEcP-}G3)ZvCFvGXp98vEMo-fQ+?`Gja;3dLrOY}@L?2Z7h74^|zE*fJX z;$tCjktBr);N8M}MII1lBXhrJ0bC+{EBHy_<=|(8SAbs-UJHIr_+BtSqUhH%;4?|c ze+th-I42q}fiDWb0{%w$P4JJx?|^?7=9}{$;Sa%ycZ8k`N*Q`X?BU_E6G&I9WyYcSvOFN>p{ zz;6m)4Sr8}AovsEVPHLb9YOw&fxz(shLi>H7vbx`mxZT+eJQS)+29mmzRSbHH-poJ zSui;+NjF!4vxT>VbA@+;8|$r^Q8$TNQUlEH5!dIE)4*MYv%r0YnL>kvD}%XMnC6*6 zV};ox(zDl4Zx7acPQk2xbH&V9ls|XVDKsX77m3C!@a@9$z+9_M4{iZ(CnJ>1*L#F9 zOKROK%ske!*D%9?J}&AEoZeds^_pPrCympgS`a=ELj~aT!gax43bTRwPPiHP7vVPG z%fcPOKJ@?SW*2aZa4#^g_1fUqg;_q{C39afxi|W z1^$N&Uq^yD=}R6DP8FU6<|QUnp8_r?JQZ9)cs4j*MT8q6)DT_>=8Yz_Tm)_?d>goh z@G@{a;T7O6!fV03g;}Wk3*Q6gg(h@!AD9z3;JC#)Jy|qZs(GUc4LuH?D|`gJP?)86 zsqoL>mBN35*9#}w_=pkaV8ssMAb5{(2z(z`o6$3F;CN6p_`&d)Fux0q3iG?*q%gk= zUK7p-^VSo(!*7HSg&TlB7v}un55ld$mxVjA|Igb^XqhAaA>m=*(!%4wJO_+sIEbDt zyb)YY_#SY+F#80Yb*K50;3mQ^fm;c`0qy{f)6hE*x{1bn;6mZk;A@2WjWAUBESR^R z&@R^&=*_F(Bp*IDL>+y5>^E}FM8G!+mjM?EXR`mlScHnuST4-I{TgA8WNj4Yfn!^R zYl3$P*9G%76b7g<_f{#}@3LjMR?OhDD;_di2j8A7TskK&36R|A(5&Ij|x6q>&Y ztarhJxpt?PsB>L@ec|QcrovpK-(Gkfn75?Ro%m)514Q^1JWBXGFmFkrp&!B53I7hB zCHyz|Mq!SG+$_vx^Sm*Ic0*vjBNoghTf&PZEBH=dR_!1G?LD)=22(rN2 zg)4*a5zYnQE1VC0K$tzEM}_NwpAv2kenGe`_-*0A;E!C6>+bO9qA>;bE}8Uf3Yr`RZtIJ6$P0x~PkM3{{Q$;;~#U;p3P( z#m6p{*AI^#YAznW&zGy``@#Ops$74l=Bx3199AWK^iavy;4xhd;A6YmgNN^@_3F24 zU}K)@JOHZCE5*kVb#Xv+F0Z#A2x*r(#K%bW&%kI+ySJ)2C|U)9>OCl$m-yl+eC?|F zR4JpjLB*Fh)m<<6(V%G59-%Hl4fBZQ21D!8RMlp1w3^*UjfZHTP%H6BnvF^{U2d$$ z9`3=>|9)frf7#v~WG}*=jz4AA7G5k9UxnnuBEC_0{JV&6HWlp@zFM(}Z!sSnhT|i- z3?+YJYyC*R%Y6)|o}I!SKmT=)a}o7Z{QS7bV!I)DBtHSODUJAN0eb9h8ev0Y@BiJV z=Wq6vY55;+`pZ+kqF^btaai>Gk_V=wU2B*092vdS_y4@v`5t$(^DDC1`D5AaoUS)J z`##5fY%=bu|F%(b^vZ7MTgF6<-OgBJSuU|o-3ASstUX(`nmwW9<1x{H%BSkbuUJaLj%#QqJ4f&)NO8DEDBx5B2eC0M*pNLU z$AM@$S0e~<=UMPg4nKByQU|wZpFx=CJ1zN|boj+}gL4c1&U3gcX0CG(iFUm+8fi4g z;X8cotvRkl<0f~KW(QNI>#-C`@e3%D$9HXJasS6 zdQJHiO3~EEhj4Rc;1KMj9wAx*js&G1y&32ad_(ikKLAb%ahYZ6OXOgH18u3V?*Ti3 z>k!e@x5%LoS6QZ>CWiytQ&rI|(F$=6e_2jB#L;$m<(IXtAp zMgh7ErO0Uny@TVo*{_H)XilOL6H`T+UmjMa**qmy9HnQiZd-8L9H zQRfy#bA2a*D*4uEeS4N_ek;U_jv94qG+ymE{PBkxa!Ns0fKVf;0R5p6YRO&RDR)Oy z(rwXZ-Fi}~FY_ZQPz4$xy&pX#z>)XR5QZ)g;4ouon9j4%teMwZp%HZ22~%BtkwdIUGpLM8n6)GNM9PY0tN8SWq;ta1l4hIwDwfyYh*wtIN=r{Z#z9_M7-_v z{8I9^({nU5GV&apOAnN$d)G3GS%D-JugF+(g#eFricIBx{_Ma$+MRwg)N`$rG3wmn zXgS}>1oa2}zGe+7QuJy@FD1awf~Zc$U|B7tzJKxH-vB-B?$hlL4+Y zighEW1$f3&tUEb9a3gHRdT{efR)AZ|Vm;4*^8?(_7wg3acuIf^zGA%@jIRT{dn`7X zr^bC}m0>8(q^Mm>qwV5nlOZ_QLBn=-B0fIn9w;O@t&vuKXBn&|I_2R(l5+?-k?gzy zl@zA~R07VIsK7yIGEyhi*#Q;D3ctvAM5zXI;I&f~PTN-a7P?fb)debX@-)Nw6e`bH z&T**3Pgu_P@cg9ZY=JwkTh9CN=1;xr$-3Qko`l{G+i8l&PTOe*51&YIM#7Uf5}X$h z*E3;<+l9WU;nauXgC~)-Yv8QkUeX%wIJ02HayYPII~(Dv&*_M~NpQG5)$cS6SOXEg z>hL%TOD-9`8ajF$M+i9WVJPTyg*!nf6Dp}zdOH}xb>GZ0`(|ilY{MT>$k6w@ZB5kb zmTIvqS~0HRzrEiz&x6Zc^R9wxe!pwJ7=FPZbH+-?2eySfy|ph@;jwGWRd8)R=h|Yc z3WID&%V+Y_50`#dg$w1g*s`NL`u?(LG`flT@VeH3UWH}q_hr$XR{E00FA_VUTxRN! zf2G~bK(oV%BZKoUEtdzuvme15lFh7aqHS=cX*aFcAFl`oV zK^uG{Nrje2t7hof5AZSe8QS*&KKhJ)bX{i z&b?UinN!%?g1qha*{Ojp(!`hbpREvV<}qZNOMl1QY1=XW>MzboPX8eaF^Ciuqo%b8FaA-1PF%oiS zy88Lf=(#4A_?s9+#cCAw8JP;@5}r>0}jN8O`>68c=ms zM$_G}oJn9<)Y7hcrJkH58L-=l8UU3PVF+0#h|P|8wN_V;uKFP#c;7Ol=U+l z#fVrQr!DMsKl7av$$2`XGW;xgX)njld7u;x^bp1iG$`R2rGq*07?9p{x^YrcFnaVTOq{OZN$?Lvj;qw#^`nfQ%rh@7G7m``}$@L$pRh`TG$t zyNqq$j?aF#_?FRrqsSI+ie}{L;PlW+Zi((;yCoWptKCZn%gT8J`p21+5Oew;%FH+ZywckaUlj!P|)8kj@FEKgyZ(BK9=0d z_-_{vui%w{SGD19V7N9%K<3rNpAOs}VmC<02lI2!E`AF`F>Z-zpi3_Au&JkG(kOv_ zl7!WV<8Hb))`heq^>ra_foNv+DN z_2hykJ927bGqK`ENmaKjFZx?@f zB`gWXcVB0f#7feZH|uz6!jw(g3K@?D)o*M79^tMk0T|0uPb zG5guA#vRtv)Z2PF>^*S7E>5q`#AoC9t~P!a#j!0{B_PH6?1^r+KUH7viDvjvERy#| z8}ZqCZ?p~uL8kAGma*5U#e1V|(z$r2bWMK^exEaLN&dItZuhzBqrK58zK?M})V^r5 z($}L#+B*hcG1c?Muqxaatr&U$2~sAt7#4d!?Wp zpmC4f#$2j!m9iH{%Z5s`sVU9&$m%&%eZMbSCe$2uBXjvhPb@H- zO6T(LP)Vgr(K60|It=BX(`3YK75g=sSE`k(Z@qSTxaY0Wj+0r-@jHw56Sv`DED^u? zeBoyU@Oy-uA0RZ*`2t|?p> zTvxaOxUq0Ya692H;I6{m;lI^egg(&dFFXi5M0hNCl<-9Gc;U(5>xB7LJ4=`)>IUIk zz;WTFU{04YGP}X6HN*6N2ud^_0pBHj1iVN1Y48KWXTV2<)sEXg;rZ()G({FFbh;qVf4YQzQX9$Si^)_ z#m5V?pmEzW-DFvrBis~>#l@HqMA2o9(yNQX4Z(VKF}N{UuPz3+25+Zja9glmT?}py zzE{*)xHz~%Gu^_x)^5I z@%dWRi@-k$F9GYt#W1rBY@v;y$9Dpggjaz%f>$aIjkOR;i^e9fUR@0Jo#0$i=ex5& z_+fA};U~cyCZh+>fx8L63@#LY4XhUt!_52Op`!jBI6hhgmeL8rHVOzwK)pJX)O2)tdGQ)Lg5(RgIT{l|o>g7xuJX-I!IKKl467-9pWkDmfJ z0_)?az-__$_$e^+TOU6K?gTzBj&=p>j>8dHxjM`ZXt|bqt#BBEiCs})6JG_ zt$K-22Glf}5=Lz2ozDam7_*UWNVBQC#?`l{L zVU=j?1#b{O04^53AG}kTt>k^eY$YEOJ_&wYn1%6}Fbm_e!f$}}aaD-qd+^_SQ#9DD z^3FcS{v7xt;m^SrguexUDf|=oJK^8KzX-Egy)66>*oRh)Zn7Y!2#3L8%}9Sv2Jk6|4Y%B~bR!iY*aC_k#a980xaBpFjkN(1J=!URML=V`~ zjS_AO*5_D3y*rpAXf)FctQQ4?2ZQH|I?Du)@Ze1cO~9*!TY~kXV5qkS>qWufw&0y& zCeG@l7X?G3BUmpA26qPQMZsWt{J2=?2R z`mP>$z-IDm(O3!oQFsmbcj3+8e}s#{i79T3*-EAg?*^9=-V4qUJ_ODdW=pCsqJh5; zv;F5TYbM>J(5Nr`D!7^ODR2kj&%nKf`M&3L9qqC(juHMFJV`hKfE>l=4Tmig7m`$uc#|q}#fH#YJJMea4u3|qRTnK(dn6u!=geQSt6y`T# z{B03#gusIr7@LJ)P6hA+hz^Ji2inPfz&wwH+!y?pFdM6cfU7@=A3RmK1Uy^#aqxWMC&9M}p8(%3{498-@C)Ge!Y_e~g;}Jz%K~p zw*3|1Cg8V(TYx!HMf0t|AIC*#2jNp;R=KZ)yMn(LX8+(<;r`&igxUTlq`Hod0SAO9 zf+NB+!92)`9N!WX~;gueje6chCSVVR} zA?Knj!JUl;Zx!wi^6 zd{%fm_%mU4(Y_|f5z<>B{2&^u!G8(w0CTp0v0-baZ>|CF2bU4``@osP>_Jr){v4bq z%=V*}@Xz4-!oPvz%|!SELK|TV$;dHPdh7@H6ix>B6%K$039~!Ji3^(Nn{BKxCvPST z^T5^_!t8O)bvbUah0<5uz%pAXPI%LyJn-$p%vXKw4b*Fc*Nb{X@D}0b;2py4!Fz21j3q13m`U*WrLqg7tMcV6LeQitsGBy)Z|zx(dGrE);$Te2wr~@KE9N z;L*a|RzE?Q2eMBU<^b1hVGe5D7-svAkox^dCeh$X)-qv^Uab@60bW~$Yk>C(*90FD zt^x_Az&-PvRbYU|fzJtZ9^<0$Wbik_*MWZ&=7D^_ z3v;yVA7O5>PegBnZY}_)3f}~dk=gziK}Z*k?chqnTqagk_#tpjVUB9m6@CKTMEE$k zl`yx@w-bI1+@FkI4A9~#05Tp{e3S@fAdD9-2fj|Y0(h2iCGcF~%HU<>YObTJgz@gO zHVC6UTgAe7SUZK;~gHA&!;!p*@i3AY5lA>0OhO1L}tLt(zY z&kK(Ne<93X(09Ufz`sP0|MUzG>#}I@!^0PK)t7<;!pp!>;dS70!U|kLm>;QCgxRy= zvLL#@4O}3+8{AO%06lIl!a;CbVRo~+3$tt0NBDJcf8lq*6Uf!wd4Z|I%!1j%%z_(* z5kIR)I0jw}j?;23gyo`91H4AK0K8F{JzXv|rTNC-ox;qHhsm(WEPFz@F8H``eeiR_ z4Z*Jn!=1SGmIxg6;0ggc#QgqPxC8i8;cnosgol8C5FQTZ0%zKt0Oqn1@-(m?Q6KSq|C@-vjO?d@p#AFthbq z;ite8<03o*VVW>MBIgLd4xTUkHuz@YQ{W}SAABnaNIe%Ou&2BoLAi zQmG*%p$btt(m|S3X=0%xMRd~?MJx!Og&2AhMMOZiC@Ke0!3OpMiYN+-C|I#_-*@dV zfX8z2od0w0dG6iMlgw}RHQ%gR^;>Ih75)aiK=?HHZedR8E)(Y3!F|GE_SYU3Lly8- z!ZpBqgd2ii6Q;`jTf!HBKO@&bO89+y4*x8S*4S|3g{Pl5uum8jq>pK z7489^B+QECI$@3rW(f}j-z-X=2sD}2ChS{1^M%N7e@_xfvB-C1Y?v5*8#T{E(UiH zZVc`w+!Wkf_#*HCVU~lT!kxfZ2zLdK;rcIfq4(QfBMz)grU?%P&lDa5zDam0_%>lg zVJsBB8N66{A$YkkALUwMKC%tMYZ!R57#;vWD!duIU3e>aFS#}(0)FEh-Y?t&_J@R7 zkdFwnzoRVmJ=Y75)}nTi8S;-cZ;HZZ6D$X$xU)0Pab~yP5*A_dAC#6RraL%Y}2nqe86z z5g;Fi3F3g}z?dqGH`ACdTnN5VxClH?xCDHsa1-z%VZQfEh1-Bv3wHzGCwvL(|4m{T z35Q37uL5ru9tYkj%)Y{IVfG1L7QO@gy6^(cr)uBRj*IsIbN;frBHp(%OKU}GX~wG7JUXk3)K-Ak#>V)xb#zVcqvqG;bQIH ztMWfb^q;?~CgY0zjL&@z*K(D7-1num<8|fw0=8wU*B8D?)?ugG2cvbkvC8}s)`QgO zFMTy}%;r5``na?4sV{wbv0veNJfx1n##*IR(g|O#^{|R=ZR~Wymt-ARY7i|C3|BYt zx@M8u#OsL>>TO&ho6{$Jd2tIL;9y*hxy#JL*3Hf*eP?zz7Abs4G$p1e`tbs5!bbJ; zMV`u$_fPs7dc7qOzWI+>bXyHo?H6BK_(%*UWi2PQ8qRL7dmk?qHpo4Pju5uj6*_To z#qOh}%gHJRd+IpPf<1MwAu7G6j;l}z(gT+`$2>e?C!37u_BxlvW4Gj^1wYbb^rHng z@t`I7YC$VxAjUZvVXRAZLYD*ab<*W-TwIP{kq7tPhy`CU_zo@})?$w0Z9s%Lf;b(G z*uV60BNe`4KtI@P$IVIcj<}Lp@NGB5tr|8D{0`eZCFgp(l%Msx&gOd3G@DD**V=dC z|5UpGUZ>bR6l}8H2sxW%H$pzHu^)x&M4O6A6YOu1rSUeG_r}@$vdUPShe%;l+)Kzp z+<;w3EZ*Vl6JgRkSQFnK7=nQK_5ka=6FxNJ@Jha&Oo?0?V}b!oo5VTO&G$yZkFU zuSNp+$U#el;3m3LuuJ+G?k(L8lN%y@yhXRuq-Vod3(>gaO^yy6J|0TowzWZjHo%>r zIo`Jte%+Zmc#^LOk+^fsI=BRVJTk^z(;Narif<;%QmF~89-~H@kCTe-iVq3krNRN^ z<~wl8_E`+n&b$$JwR{)fwalNI@2BTW%unH7>~k|8y-Z5Av2QVM#N9_5n)rI(^?^S% zjT3Y3VcM#>FUUKXV6H~UGQ;YwLZ(04Y_A%d{$lfh8g2Toz?YieHT|u_HiSDp$zFte zy)nuD440ir_BLD|o^IJULqyYOTJ|6$_uj3RZ6Vr^?y&3|;C>j_?TG&bt}S7{bG~I? zitB1z^KmaP;<_H;U&1vF=8tf_2D11bSDvXk9rAO3$D`lkyiGU2qkhS}7w~S8`D(a# zM|>0Nz<78Rb9_@GrgvB2-y6VW4r5Kj{S4wxi|nfPAxVR%*`t@z$#%X25W z4@Kwj%*VaN_a(=AteC+tTuM)Io-2`Je7^;V+T~eGPyLyz1WzjM2hiT{xtjI^X`f;w z&OmC8_>m)!vONWNZrV@626r_VAzXHAYLa*ssBnxwExZpoa3mD3qTiWF&NLDyFu2+I zWm<88+ORnVY1)0^)w17!7-H;G`0p^>JmfCL>{|^k31bbjKV!y6Baj*MGS)*54-2z7 zgvHn8`H=QQnc;ZPBZ$BpwhH}Qvbzpec04ZWAK+_(Y>o9 z5Aj!rIVlfk-FJ7ey5pX;f_GJqIRB@~E5K%5iw}w&Dzkh({NwI>IQ_Z2gK19O!F096 z=})bv@1TvvJNS$#Xc@M#m|`V7mYfcV#cXTwJfe$e-lL8<{T;0;v8vSN&#kJx54EPj z+3a3NM>f>raX)5{Ivvy$mp`-gF6Mcy$(F`E0hdDUYpt%p3U&Ck`jHjt!m!q?N-MqnKIpT%{7V$I2vfTK{0JndjIXH>8zr>*vJZe`Y zxI{mtXo5?$L<>)_Breg?HfpiNnj3W}7OMg+xEm5EIC|TtgDZH->7&xU{&D6ZHQyVR z=QgjuzPUsl_xe+jQ)j%tsyRyK#QXnQliQljH8IReF(Tw*ky-vVW>%t+u8E=Cc|sZ@ z_1XTa^$HkZT?}P#8rHe*;>nr!>Ikj`=2FAYTpzgH9XP3CVGVb z$P<3lJ$QZ?3Qkm2ll-lduZ91lR#`~9`Edv%ie+!9sY(8tI6q@;l0TzTEo9O+Xca+cj7d-)`E z8X^Zv{Fj=l=ks<#q*DX`qE2BB+$wbM6Zr`6^!Rh&u1tuCze8k}DEzWmGEpM=n?pwN z;_nl3VwCexVVWEv;MbFkN5~)hP$nJ+e@$6dse8%aLo6~FLGZ^lQL3~vJ&q*F4F9lj zGMJ-E+H)}XPvK1PbHZ8RmxQyyuLK3H)Wdo36~7Kb|EW5T82lfq?SE{`(d zmf&B6+ks;sIofvtb974X3)W|)h<4c0ph zz?_}BQ~X>8);kPfe>Hfi*iQzp7UtW-*Me!S1#>x-yq?*o4z{3@8|VljaOV6NAaKLN9S1iKG=0+*goJ^VKH0^!^OfBFI!Q1 z8U*eoJRIC#cno-mFee1qv!{QSr_sVQz!QZz32?3O{oolD{x`zF5d;Ho0pBW2QFE&; z?IU0=_L3h1FB5(Oyhiw0@cqJjzz+$(2xh;4VP6J6A^ZyXX?++w9A1ZEk2t&w=3*}c zdpH87i3+UHVUmBhgJo~MG* zfzP!z88safs4vWSmt#iSvr?)!ADuluj&fxx(L99}9M2YZf9=!ZA+pfH4L zgxO`S*lWQ72ASg8DVz!3Et~~@SvVW~x^OP|UEzH2`@%KAC&>^S?Rmxvxe@r6C`0!S zh8R?rbSMKS2wwya3HJb35$+GJCd@Y?Pk1!Aw(t~iec@Zcjf8IlHy54{ZWW8?&x9Ak zaIrWja980~;9kOO!2N|E01pw~1inI;Z^T&P-QdZ>FM{$}+R1Py2gNCMDm`fWgg#%!2d840n@J8WG@K)hm@MFT9-Ps|`EfUXy!wk%|wHL*K zv&pXtb8YQyVa_JMFU$exVd2)`&xG58*&$_|RCD@HcqsT6;Zfiilt}uyhPs#hWF-xg z;gBeN9XKRB9b83tAvi<099%?BiZ-teh51_QbI@TQ1h*9XTyT3~7Dm0}Aqn!Y1H)iC zAS7OWgCk5b--4@!Gr;48`4;FeZo^Lwn1gEiXFN9xvxF}YZVJ|CpTkdc@G`My0bf%Q zxD^cdi$fQ%KKmR2x`XxE=U}!G`s{P?P_RDx96ScB&proF1HUBk%mjZxMwcrE{E2Wi z@aMwe0vNs$Lv8RW;X?4Q!mN}W=&v!NmSB%?M{trbUrT+)Is6O(r;GhCaJKOE;2Ofy z!I*r++&co?07J1jEC=he%<=Gw!5wK2W{;)2@CD#L!W8L1;S0gTgjq9QDNN~(6=u_X zt*~Buy+I5VFFz^7#CQkug|7m0&_w&u;3dM>fmaF70k0RH3w}_z9Q?5G3h?8?tXKX? z=Gw~|81xnf@Mf^y!T{b1eoX>A0@hm?V80W5Q0$)te=PhO_?YmU;FH4dfWH?$0OqH; zcr#yv!!fR?;U^e4J4FYsMg@hv;L5_Ra59BMV7-L_{y9|R2bdU^l~0jyb#O!B9B?z? z0W#Hk$t-x0aw*ik6?g*YN%!=rG;r`&+!o$IH z^)M>~Kdj#J(htcVgyPXCw)a z09OL*!+5WN0Y@=uPgjGpgjuoZtq=%24qRXCr-GXa&j#}(IVLa%+)0>kK@Z_u!F`4A z1P>Kni1ee|91wUt95|Q6h}c7&CcF_mQ+O*_pM4HLJHQLYeh-*4Weoc=m;|CG%oo0WbZg4uZ$ zF0<7#UZ$%raj`z=t;$yWxrB84YS@lZ2YK0~>aM}Xp~ARW-}F=a*7&im)L&(;gtGs)0S*<4B>#u3;b*pbmJy~W@J$J7^FK${> z6cdm7=3al6b$J`LqXg>!b=Lcn%&DsRdQ4YrRwH<+rEcfNuOhr`R|k11Q_lNvS+45h z5_|MM)P!p9Nl!?PxzC?e@$Kv)RaTOa98*}o;ez*b^3BZ13-|d0j_{=p4Bn4Hh=VNr zJLhMyMm_W8?A#bM{FbvXE@u~`I3UBy62E_i#V8hTEJiJZu(25R056VME(l{Wid{Or z7{vj^nJ;H!!*v)Y7Ahwj5i2$pzGFHNdoaQ1Vw8z2$^sP2WsK``{&zU;)+sqS+;H)% zMJzyl1{b%D{5Xmq03x1`=+NjDpodF6Sb(A*EI`eNgZBo$RV*IyY#S`Yx*E1|10IFJ z)exCUaz4h&!p-ieb2|}>73Aouj(a97oV!=jyFSv;`2yMF_9F|e2;egP&8}T1c#^i5Uz=DB2^>t6dy%mC;Yb(hrv1~k<&E}e2)_OPHe`UCB}oD ziCe&~!~k5~i60_bkHLdB4Tozi?!idpbc2zo=YSGf5nD#m!(2~uxXL*}pZGTHjKs(B zj|)5*u=0I@7+bm=W+ASA-mS}N(y!rXt?P<6*}Zl6e_+D4Hn;-#F0?B_bG*M39Wr(B zBtLh0yK>D-U>Edr;mlRjoZAGZ6#qU%<0>^Dgdxq3*Vk~#0*@bgFdXKn40 zFGc&0In?bB`BU&?gTKhCiO|0~gVNDINrRLeH| z2Usf&b$YWuxiL$a**IFla90$b*_c|ptoT#g7gMjOtYN5*eqLZ-s-Ole;;o3Pp`6s33$)=OV<95Mg3j(uz!p>MSbzG zzf$$}ysx@Bh&4Lrntum<7M`^zs2|CD#9!uea*${lHUIWBC_h12HIns|zqKQ7{J_CO zhmKP#wz~2o-#?8i@qA}TBU#V-^W0{AwXbck%7Vq!Dn+i{>u-}BZiuH`F&oNv)x;h0 z_bD0OCjO>V6liMsTTe#2z~3f{(i3HlmN*Bb08*#{mcjD}5*oM@j8zFzFJ!Lx4{Hzi*PEKi3F(c1Q!p31@++=)(ZKOIDO*W++9NU9WWE+Td(qW}=306L6t0GgJx=GolM&XethE zz+7vieLFDM^vGSoTo@zw0P{;<Shx~WL@V0n* z9{j#AtKGxG$HAWop8!*5f_MBC_&Z_N_Ig$o_P>B*@TSlYdsQA`?&V4n=9r_3o(+X3 zwozH)5CrE7r-JJU=YUIuIkUlSGfbc+_#$DpdmV)f!R!*yPYIYE5i*rc1__6|!Z2J6 z-N9_k>FE;i1mVlTtm$dbnT=b>c$fH2vr9mRQ}jF+Fkfj_pS1UaR|xYp-b#iCI2mX+ zFd7!a4sl?8!YwhhuL6EixGMNn;S4aBTIr`6_;Ln8fz-%z+zXn*J=K?N7 z`q9rz!cYVUeTWOVEjSIg#{liYxx(yb6$*C-Hx}*&ZYewntY3oFRu=Ml@KoUq;OW9F?KcW<1P8ik?elL=I z-2aQitRD$|<$u6}YTVP`phJJT3Ve&Pz^NQH^jkd!*a9PZ>M`GO6p^obeCL1GOjA|9 zhwTn^<@Xp7%u^rHv|ZIdg-dTW>y*D{?uSUK;)jx{f2<#AT^2PON1LeEPx*6eEK9+P zC|Zq*AN}*M4*c`VN}az0Rii_R#y|LztS_o+Z8m>9@f7uf5|OUD3=dXPbKwcfaQFOx z!9*wZG%vH&Fvaq)W&pA0h&ao0Xc3G2PEeFVBeI<PILd%VZs<{U2MPl&|cb}b1 z`b+qQmRkny0$OfOQsA-3eiMPR?F-?ATetv$++#STLZJCvHMKe43N5!2a4~J}z=W1t z4|OOukbVINdG77hOEElri`-Am06IK8vBbT58#t=v=6-?f@nj;G?tL4<@t%iO)3`uU zcmM<2oABSXH^AAle+0+aa}c$|wh>XReGoQrHajIwI}dqx*__mL+x&zJ^xINkk4qqJpuxU$r2<|Pt4l{_W zk2mLan)Gb=j>5(pZ?a8u_}Kn>ZEeu{ZQcaU@xD|<=FQZ>lYCsA^X8gdX$tx{E9b3g zvK~xP< z8Hgvo3m>z~^9n=~-;M0{aN;z+hb{^U6UPrT;(O|%0O@nR$o)Msh=b~9GHSk8cIhqz z!-HeHXkRdre)?UE`*S5+{5(BVl*|2J{2_Ck1kw_JB1s)>zp1 zRH7-*vMW+3mUUjW6km}RPg;i6OT?GJPc zcm8z z4FqCs7thqOjMo1}hpEoU3RH5R@3h4kr7mw|Tg^b&5xKiC@NJWDcZwS#<*x^yDc*Jd zILTKrBf*m(D|EQnu2kr7O(x@k@~59EgvVb$#8;uW^&FX11AaV<%?w1j5nrbY9j@1D z2t^%+Ap>TmC?6$r76CuD*qo_gl{QqkGWZH%j%vmTXM(R0&H_&p&I8XBE&$)ec}Ygf zfxaFxgNwm>$P8`()0*)5vHt;CPD}iy~ zFb!*9h!=--V88HsaEkB-aJukjaJKLsa1G%X!Fmc8alQVV0vw!l~fvgxRXh63zhMESv?tT{s7PmuATA z0vPTQhvwjlG4@4Zc3zl?mSC-+1m-h-lJ*#vF+;n~;oZV4r2EP7>;M>A3zE#S-Vw1! zm24c>vlejRoAb3eaB$B#S_a@taax!!0-GY*^De!@yi1>OHE^l)W5R{t?}UrM26Dv&SWkF_Dbk=YC7LcwiRKG)qOeH#LNL1s zex3x~5{72t&>F1ILxp`?aC@=u2<{@>72HGk5-^8(R3hQsbNou?qZu#ELOMm5k0~t7 zj!F0iF;GOe2vbD!g|opN%rH_)Xo+x5@G4 zVm}8Q6228&MfeVIHQ`0zJk5F09x`VznD9zC)Dz}2Zb^nn;?)b&+||Qbu<9pPd;u;K zt`ELkm{q|j;fCM|!Yq?hg`0w>3$t3dQTRgeJmHq$JB9glz7w~<; ztO_;>4+dB03k?JB6#Eh2-NK{5FAB5#YqcQMnn_@-76fLKqt$}IX{;=@UJwk~aL{@| z;M!oV7X&T=YqcOSUwM9Tkskza15Om?-Bc3h4WtQQ0?rcd3+84JhP@12m&~t^^A1YH z;VN(w;mP0&g|7p1%*wzwf;$Q?1a}v{8_dm~^s^M)UwAoKpLkjcPaUjJJO!h07-4XrQpTFEECIxStiyBvrKR=1LG_MKPucB{A85F zdhKYJIPfLm{!#{@h~5+)3O*oA34I{U=lwMq50!WJ!#R9f7_E*Gi{_qwSQ&eT)4@J{ z*eD#T!H_Htx{o283;XKA`QQR!mXEr^Mc@))-n~8v6>;+JFBJQB;5Nb?z+HuVGtypS zU;(*In35PNJP>?!WCWCKZ0xhX|Hc4!ZvTXf^?57RVNQS_yS--)Y^SMXxLCVdtEM*v zI$1|st9iI$pZNZpVEtknRqJM0Z)m5c@w%hEdJ$L5N>!N)=M5dyRdcoP$L9u?nVIU+ zTM*?<>i$~-laOrnc`#k2#^Yk`9<6rG3)ILNhYVKyP~Ykw2hk2tS=4AuR)Jdsd9icq zqD4{7Zw*wpo@x-CL7#eSAjx{AL39S4J<*CN7}DKPEid&{QqRK^?sh-oFvqHx+X8vk zsq0m(+XA_EOH?zk+4xbcK2*FOHrkr?Y9NaQQFzz@+Lb< z3yT_ZGTr)lRwU*2z=O`X=5gq;nChi}U^bnrO_)uehRDy(rWaaldR!T}XUwM0MlA!~ z9L|_v3VlAZjwy7`y>+Gg5OtaZ+UTeRO|5NQcNN$SV1?f$;3y`x(_D=Y@#^%Z55`_x%v>@DDGy{2{oXfkXg?M_~+bCG(6OB)@ncwOneSh)lU18 z6ILSDip|__%VzF1ivm@`+mH>zt`BF^z66=E>`~wt`*DiE=BEZ@ZB7`**?c9P_ASV~ z%iaikw>=Gc@feBa$RBoaa}B~~8(`SI@ZYpg!2&bfRJO+s?%8yvdWvDULugKL&*d43 zNuR)-JGeQ{^6@^fgPWcWUk+~M%nt4{IRD`e?!EAf9o!6_Urfes138JY%=QGc6yD1WYpLqK7<1Jt)#SyPx!$cFplP#ubunhH zIfakU?{37!!!qY7-G=*y)HvYrT)?4@y9@Fi^t9t3!1F4S^IW`$UOX?6JCaS$7Tl<( z^D2f+7&vmM;ps|tcxocAp6=v0kAYly^zLaFe1k7t1RG3&cT}(36G(19mhOX?l_bMm z9bQtrSF*f&sIlQ4rJsoBFeKp}Lw0!H$5Zr|c<}SUdpSXp-HYM-%AcYr-h}7=E^gVhS-|neiFAt=K%XzD|J}|&8Ls;n1)kdTV4Qt@PyDI*< z5=-XOhM$d1EM*LahoVSqK(_RW2#F2J4o^?Sme`0KXC(fBBppf3xFm1ih^(aA)R}$S zu)jv=U51?q&;6e<>`A!pGi+*p{AAb%;CQoX--b{tOxq2)tTgS;uzVcb#3ATcVrkC9J6tk&Ne~ zG!LO9n#ElM_qS<2iMY(>am?2#m~-`gTom`T7t%x)Yu$n;Xts!B2KdRmd}d%h9CPX0 z?5qRwG>hCfnA5K6%@u+B%pPjSN}Z)`D+6^e)LBZ=S-KjsjAkiCXX!SYb(T_emN*cK zW+_$2{4vbAqg25v#CC%k%*$AH_o_f$pN`a{BYgt5T=Nt4&Z@v*e7UsAYVB$K>IzRq z+S4lBCp>Lg4Nn)V&>HQjXNme`O`vr> zR&cRn@u)FWzXsWJFVYop;UM^Qmus$fKf-#avogd|FQC|3si3L7BU%J#c%PR<5k&{Jk#gDD?dCgpP{&drlZs%-?hTU$F;!9~pAuZy(a)^eoDs@n)36dbaX_I6z>= zhr$@08b^gODlxtg=I}u)>LH?PV6CSI&IUuGI)NI%IN?IDN4NxBl?>tIAsN;5oV^%| z>3}U!5i>@fZ zZ~=IZcxnaKiwLl93%*H!{IT#%@aMu|D(8MBhI!yq z!d&y!3VF!TBCrGR3njD_oFM!tI7RqraJujd;A~;O=QV`i0T&7%1Ea@_X(&W`9EPUi z@Fn;nVJ^0^>82mP^PPmb|AA}Mv_B2*E6n$ty>i+of%PH+I1Q{95x^zjapI?p@Bd^m z@IAj?n1ij^!ac!rg$IGz+%W;Z+y4-z9&NesMDSYS$>2@GY>plgo(I+o4gBWCoiOYa zhjOr9X@EW7?U%)VBUrCAzMhT(p8(cd zp23sAt)hOy`p99ux^eG7Pth!^dFuT^Zm6_-^5E!OMiH6t_m0n#A`Db6Wf%;Y4sm z*bja}I0gK)aAp{WJz~fLbNIuAsXhFLFzbf>!VSQOgf9Xg5$+8>F3e8!*TSqDeia@L z=2(;QUk>Jn-NH1Cg25*aH-M9cx$}Ty9{RZxTwQn}m;)l(vp%RNOufev;XPozVqr&{ zTD@WcM*9?9u>fPPE^G{B02or?pjRotY-;r?1vnF|S1G_*V0JX=zYxp~B3uAO;Wlm; zMr&@|C0rSNk8m1znK0woOy-&A4E(4zM2AFA3N!F7VFunU%)tA}NztCnAz^f4jU&SB z$Q&2OT^bzY(Ldw)K{x|^nnPAPT zVk+S>Pj%;It6Ig&2K6;A);Aqg%j94u93r$J8MaN;N6EouI8bs{3QQ^LwUpo_>+ON6 zLn{1~se4jko2`ze25VvuUvlMOb;R1FaxgD8rw&A;M!?2;uc4ki^zX5vQbNA;@5_eK zb0vSgK0YZfuMvAIDk&VFW#+4xD#5&x(Xg685z7z7LjOKOJhU#08jXe1qW;)LtFIED zu&P*xFHqB}1asqBUc`+rQ3YL>dZ9}2Tm{{dgcNL_skSm$fL(*l(}Ee^b%?=a?U7}u zdd(B6j9{C1b~=%79Oef3B4IjFPawMRg23c*UdM|gwmsb(&U>PlD`{zBLl>TqVMpM!8@JH;m?+i9nU1CFh)v|-ZEvoO=L0_anwcza*_Fgr=H8=^|IqNpU-nG^0_FEIG z$L3k+dZ?#9!gGBgMxAhabFKGc)V@a8>XxFyw*~X8qYhP=6U;?<*n3+rMUBo0`p#~@ zENtLRj%iS@NN>N~9(f@rcyElg%u*8zg4Nr#KnLQiv{`LHc#q;yL)vVGFnP;92c*qi z5s)@}2$1#{2&X4+S!W{19(AZ7Sgj_fhh>J=2_G^m%SG5Tv$9jP!Ldixt`V#n=CZ1z z(W{WZ_kBj0p!bC00c}R&oPM2*^z0Up0Q8uVRM>8X5ZCJo%v zoIJ%|ia?X?pAc%2*5bX!J_6T?b~V^cu-77<@%DrGKd!&cMd7jbvv9`r>rrGOZooDq z#&bG1FlkP}K!f=p7#hse;jhTP8)35TF^B-gMk}X{nFV(q)488v*osb%wY#X?n!yHk z8NyVY#@Rq!Q4=rhc-6cK&e>X1GZ<*H0tsM))bz27w=lThsvoNw)C=x1H$;A}7aU>QKQbrN zDTn%AhZ<8qm~MR?tL~^DEHz(N&(#k$sX~3E1U>Ka1Uj4N%)6BAQ&o$DMex_97!UMr zHL*BY*W4FbQyiQa>s*RpmND$#n%rm{`KobnM(p`kg5Gv5I(8Y@@{(=?yLVGb9h|iy z_mu@JTala=!Mjt#T89_+#~(+pCIrGCHH|qFQ3mFVMs5YZS(wf4?ZWNBcL{d@vss}3 zZr~Ndmw?v^_X6`d!B1FkzS|-W{ot@ocp&&G;lbc%gol7Rd}0FZO1~mJ4*ZrdyV80K zBmA(%`bg|=2Y)Ke9`iR9r^>B{fs<*BbS>C~B*+`UPGMG2Ty3HKOW=U;8{kyo{a|kX zrJtkV9N}Z&n!?|K`JpZPvDnr&5Cglw7YJv7Sp(A(hs5oKYk@lpHwIrK+!lPPFdOH= z!k2v4q^6gmGD2nON5t$R|&J%yIz=& z^g&_vdLI_v%Tt9o_TpW>4u@xjIotJ;Fk1o6!_d#iU_A{3{u!*NVZeU4v%5q;m2v%! zjDnm3QU5HQ3AXUo(LM|8(v041HVg^kPyh}J*8*1-Mla9cG8F?i0Otxf2iFpA2`&y(w-oLIZm(xI;EAtF7jftTW@XI8dV~834**{#JQRGn@KxYZ!t=mf z=Vn+1o+`W;JY9GR_(tJ^RWQsG!&>m2!jFOVJPab*2VN36I4F29#(Y#gH58rUa+Np? zmqltIFCEl_!_Z;gSETZX>rVBg;jrzYo*Rx1^GfygaCD;%m#UNz!Cahci|3|VjDXe2 z^yqT?^&^5wW{kRhL@>{6q9VK`se`;+teltQlCSFWQm%U8Vjaj*(=SJ_cXCs_Bc|GS z(i2oWE)SlwkdD(Y{-w|BnS*;UlTl_u3%VQ$g*kA;I{IK-&UVEn(9(4A9Ya^_MqbWz ze8(U>x?;^~>2NNIUid^1fG^^2JHFF(SO?!q^sD$1IvnQW)goO*zk8M)T9t-*-*)xK-S^qOqudMpt5KR1C zxjuIwt;_YfQfEtgR_Ze-t20txpe2VEibb?yAT8xu>XSb$*SG%HNL`({Dd>sB+?>$D zj4YiST>cm1=ku>gqJ!vvowO)Y7dG+~pp7|Ohp-n?QNsV-I)wGme|;Si!Fugbb=>{G6Y3vKTe9n3^0%ig?}!%J-%eXzA1$)~=cX-H;>KX$ zZ{@zs~PI6-*w&2ZQ}l)?^yU1TDLpdrHG(QvHX9cH-Sp{JQ_E4 z($1%Gi*~{OxFf-#`G0@oHg4x%H*QO!6~`{AINp?sBPrT9{vg^nK2vdccAu>{>ht8o z6!W*WvF@gEuuvi05#Y1kG){Az?WWD6>0dj?tRo;+PIjD?E=L)^ZR?^#Mn@-lGp)hV zw(gSuxUKtJ>mARjQ7;5bbj1mk9y7Ae=(?Hw)hlUKR9wG)y@n$8Kd3tXzv;0>CBFHM;P$^7^(~Le`Z|gZ5AWT4+gvaRUhQMsuC&OYRTcV1HL~O_@DQaqwAd& zHS=#p%n5ST9e*m~8|eOjyI&W%^XOmqlb1%z?=w<<-}+r$q1Qy?qq7znU!3h+{E;{V zD_DN1%1{@7{8{;GY+5idf{GYt%kdP7#&p%=#ftr{kmn*41|wX_f^gV(Ad?ezWIkn0YI-_Lq|G2mt+W)S&tC*256GONE zRc-P&gnnk+0(tYOzmihDJJIYCnH3D>JL6t)V5^I%9;y;b3l~8p3yUf|r}1oEa*}1* zbWvRkMSCzV)}>fuI&;(?*Jd#v8tq!gay=i_3e`olstXln5n8#RB;w$LmXj5}#m&lc z#-9rzqDTCNtvB7^=w`*%2q6Xp@577 zV*qNbSwpaf=9)^~?}R3}byroB>`7-H+&=cLyvyOm?QaV-yxjWb4p3>zVR&vt$}V?< z_4M>35^^`B9;)G;iKN|)$POd^K^PpKaI+NTU+ zr>KU4om0=_f}K-WB8%8LbrLzl&MB(fVdvBd#DtwwkHQT*r`o^`JEwT+(3WSQZU_Al zs^QxUaR-Kd2>A`$2e@JRdOIKX*V)%2y=nG*#C5H`6aS~$FCmLl^lrt;c4Ndd$>xr@ zYwS#Dh}xc}eJgBm;Nehp zb9yK}^(n~O-hMTbHoV_3@nhhfx(eD_B}?1=dL$7`4oaFt5_Lpu3`@P(*66K(md(M*v zF()bB8n}<7A@rZ-rJ8ioFmhGzy~uRZD4wvB={?CL$IO6zHXei%N>n4hg;kv6C;WFM zDU}%thN=7NGB=elj`72W80JIz#q+L!B+SiZyb9xxJD2%rCwTHM2AfY%g9y^&;Uk6S z^9Sj`1#j~OX2bB_i+|>eWYfC>j^;~b%R7g$zD$nsa`&>?a<5LpOGQ<4AKB$?M1F-F z@AV+7=Btc9$@?$^yhcv(HefDZCue$JR->w+4jd0dy!mk^LVHIr*b#2UidHA)r+gv~ z?;b|+In#F;fxZY}rdeh;gb22xOPbb+3*l?4>G~T5m6CVxrm`)1q2wrm6zbBMo*0wc zic&`oolL_zo;V(S6p6q<#X6(%dQ~|qRMoswHOvYXng38%W`*+1hH61pDBE129_8f? z^MkO{owBL%2iF}wH@|f6mdpswUV?L$= zxuFSp+&W;k&?nCoAgo#56(u99J&FNrDS+#NS-sW9+)y)Y+&`Why3QP}uFMOK?*2OO zW0yr~AQF9iI?grx!3o>F7H#mb5aCqRWQALBudtItuE6nB^xBMb?u{?v%xflX?~Dv(%Bzm$_){>xx9rB7)5kjVNo5WYJ}5?#;=u%eu*(r= zY7&%Gj7Ce;!!_|F6V?8jC=8qaQ7aTm{|@DF2D~VE(JiE#0&`ErKclp_s$VNq9ZDa) zYK2-<8V0XPlUaWRQTW`ep+Ego4Id~NBN6?$V#4dzmL!-26jC#Ko%HyYzpKFDV zyInjA)iR#8)XCCN`rpt4&OZ;ozU>K(&%;~2dmRnbm>)dJ=hM4Iob@8d8-~iydt0GC z2{3}syPk-RoNOKH(>%P7r`wORAdOBKZ`}yA7$>ItvJ+gB{>+i z|AiciGLKfOIMa_C4=T>|<7NXh1u9qk@swmUH5y;201;- zJP*gri1Ksf%qZ{2jZp=Q`937fJRK2ckmGtoAv|Tl@U=MPgMSdN0X{8U8|;CE7-=Ec zCtM$#EKHRey({UzG-84nWXU2+3F?Jop#bVt4zJWiN5Ihh=WLmwEf7YDxg zvxTn&a{$M{)4_KL&jRa@e8TJg?oLL3iIGy>zk>B9G}xoMH-?ElUUB0}VU~%}!fdN%l2fDc+;k4#rVjyx z1NtHQOgwr@0529#EKo~@k-zBoHPJXRLlN2wKOxKv>F;a8o|)Jq_Kb6{FeUMtu%C7E zCp6%Jxxk+bGechqGw>;42L4qTj~|EWM8iU=(Qj%Z9tg&$B=(iTJW-tSvrJ?O^DW2+ zhv|?BLoIP&O~=8WA5AUqpp|eU_+nv-lxJeo4<5h4^MJ^d&}G6*c%(2B9xcqmI8nG3 z(l>a#2m=?xVICP-qa^eJc*2Xso{}gR=A(I#oDqE-51+&O{5#mg%6LlbSw3DRXGZ<= zG;e=q{ixx6alpkmEX)9(2~+G}2>Vqy%ay72&JHP4*O!Dk z;ae8fmxelY>czv{^ zTFvXej_Mm;zv-x2u0Sws4_tvr+p3RnvC2EC;+3$irDm;!Z9nxIFViE{SA~*IYjtPU zbTwQH)h(+-ldJ>7lw%Ey4%K}P!tNNYR<8-w$oZQ0D?d}N5tMBf?=mruQD3hK<;6}# z1u?J3#Y2>YoT59dZ{{$ zE6zvrt_$VaTbrPYM`33D(Ncfh!*LN>5i7cPu!EYlE_CjU-|yDaqk7a-K8os5>#1tX z*&h~Xr~Jmigh{((UaZ*Y5&-Hgm|V^=yf|WO(9Pi#Wv2nOG+jBc{Ox+pGhoe+ zt#SDyJ?h5)*?Pj?)uaAGrQ9F#QA?!mg8M%ktCdgdtb_9D=Nm$E|7zOxZ|Ot*FKH29 AHUIzs delta 211473 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 diff --git a/tools/sdk/lib/liblwip2-536-feat.a b/tools/sdk/lib/liblwip2-536-feat.a index fe3218fa72d856ef020356c408a00d744b074f6d..05098cd31090e6fec7ff6b0aacb7163c76c235d6 100644 GIT binary patch delta 234627 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 z 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

    =fRRPQarc7 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 delta 236579 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 diff --git a/tools/sdk/lib/liblwip2-536.a b/tools/sdk/lib/liblwip2-536.a index 85c3db3bd3c678cdc4e3f0fdf262805af169eab9..e7c15646ca7c06e0c7958134cc039fa053d4112f 100644 GIT binary patch delta 209150 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 <=%E#b*v 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 yb2%FFVp<(=`FrZr&HlZ0g}b6)yu3| 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 OcU1h_JMy^n^1lE;CDC92 diff --git a/tools/sdk/lib/liblwip6-1460-feat.a b/tools/sdk/lib/liblwip6-1460-feat.a index 995020bde52d4c0f5e856fdbb4f95a6ddd193c10..8f9f42ca0c43f048faa02b5ee633147877783590 100644 GIT binary patch delta 283429 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

    *jHp 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 L5slQ#RU0v1r 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

    h5^?_dH?2J0P6 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

    uM;c{P`+#jK6;1V;1!#ZG9EiHS0vHNn`vO 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%0D-o7?_><8668_p< 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�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 KO1V~6?0*3(#*bM5 diff --git a/tools/sdk/lib/liblwip6-536-feat.a b/tools/sdk/lib/liblwip6-536-feat.a index 5e901a6879613f007ebdffd7b80db9b36fa5c02d..592df0cbf66ed748d5d258d13061c17b67ab1619 100644 GIT binary patch delta 282947 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⪻ETR{LyMTWo=MFc;etU@j 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 delta 286659 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

    Rx zr{|Xnb9#QcFsJ9&2y=RVz3|uI&BC0j-y!@1cyEZ&|ATN?EDR6ot}xYPP78Ctpo_u| z_)Fnv@OQ%85Bf*pV&J>NCBXckn~yIA>`4r978si!g@lF>qQoKxTu7MvI~Nnq11Aep z#U@Rd8!=WA?g8dk5R9xBxQ;OQWXu-s2W}=jl=J_2B5?7bi||-*U*SpM!NT*v!-Tp2 z!D!(n;EBTAlySN+Kb4&=%)J-q3Fm_sf`d%yyAWOwi=*IW!pFd`37-JJDa=KM4Z`Qa zTZONHcL{S%VZZP<;3L9R@jNb!PyB<%X%Qk|aZZ>YeO?mgMvUJIQ{VGPVeZL)S2z`X zUzm#x9yIp6vFcz;xF$GCn0lavG~@YaLntN|O~A>*&A@5GEx;9pxxqp$;kMug!rT)h zNBALdOX0!b&cZ{$y@bbt`v*nf#tTD)xii>s;c4Kp!qgR=EIbQ5Lzv2?^MsdxpBLta zj?0Cqw!KDp19+Wqa4Uq(B2dwEhcI=!_X?i`9~S-?d{p>6_;YfjP)GEta2%T0Z-n^@ z{y~^;+Mk3o!1(14E$UAz18ww$4g8mU_*QB}uD`8$R_(*Z6LUhvwD7m_Wa9K=Uh__< zO}OI3guh$B)tjnqOStNFMy+fK(;?~>MC|z1u$8uamX~wtGA?R*9ySK6ljqOD&gsEC zcuN0NoyyZeCAEgLk!l1l*VR5=?x^TCaAV**r#6TdUxRPrHR0R~ZE?vsr`qva@0?nR zE4F68)fTR;&s26h7Lf0ngsUpK15BS+BRarz zhuX)>1r^;9mtm?8E}pEf)TWLI(D>$sPH>9dx;nvfwpziQ zpkWV$liEuy>;cQ8>T(Z%1$-Q5_r!J+BUP22{<7iiF}%E{dcwq$xkAn8=}+@CTcNj? z-`mq4V@_8m;R>n#m6r`Fsh7X3r<+o_z2LCkE;SO@i1qJb8YMh*0N9smUoU?VHLIHa zz@84+ZKH^%W-V1J4WA(|%fDRBw!?$(Vfb!xyObm0q26?;D52Un0sXU~16wa_=)fxK zd7FP0vjhC^-VTrB#e>}%vJeRNc6bghuqy|rFtDM+$8d>VIWAN3Mi%#C3c?GZu6jh! zyAZaK@VX-cqIWqYi`^v;_l^k*>q=aXG|GnZKf`xeL~ok1{R)eGoaogNu~&_4J-GcQ zwtYy2f8WnA^3TK{1kL`8{R>=Tvw2P}Ie){Tx;K-3B!tlsk$7w01KM@)2fNKTE^riH zj~Lq2AiTjUb$Lx=R@*KYT!YxKo&2kGJIz8oEjSw#+WIS^)hdQ$c&P6kkw;Yn zZ1qqLdE|+KNo4Pnu=Y^j+G@;xq|H;3EUVc~26h^OS# zF=F=w-i1L}U@cPS4J7;UfC3fpHzL3z4kH8o8GT?pOnia4U_bVdfQK0WE^gAJBlhnm zVj~Fz=ifII=yeWrKF)uX(TDcK_a7ts9lm${r&iIu!w;eTXUL)L@crk>Q4aO({1?gG z4xh;`24X5D&u1G};2wa2^8AydwD{u)R z`T{>N?!Zs*UjW_s}Pzu zzz>AO4bLxXa$UPhRUSin#l1~6Tc;b`x_9W-aCnGBh!Y%sgW=wx?+mA^I$zf=jRRx; zs%sbN`Wf81cbg)_dd@z?=ia5OY=^UQ?p@|tV~6L(yZ6XBI?vocCc>my{A2Lt#+&qz z8&9nY5n9AgP{ZokMTf4S$KrYqf_CxzSlCT8>%d*7_~lG!2`zMqr(&gBQakGw--3xq zF{!lKEB;{yT2_DSKdcBhF>uT2PvVBhb7IG>sQrwL52%~nqf8g5T>4@%*V z__rwZ#h0|4W&Dfo`kO2%`{NJOW}x{rY!1iAQy6SEhj27Lo=F&D4ufz!z9k;6J4)a9 zsd%1J>W`nb z&Z1c?H%N%C&9Ko5z&yGR+2i!ZlZdXXv(4F#NTchKEvKLQJKHYe3G=ChMs|s`yGWXu zk3jWd;pVb~2(xHe_%-wCp1-2+gsGm5>_mKIJ+ToU-5&K?BfDBmGu*BDJsn-e|Kyj{ zmyPU-=4ut&*e)D56}NAu=<>p0d}#!c)z~g+Hd9?2<4l@+UNxn$UCpedHZ-++FF5@{Arn=?WX`bjvH6zEaR{BXK z*qqF_QRr*k;zxN$Q<*L?7^ObWvAcA>0TZ+JLvDe&RS#H^6Vc|lG5?yjfU`>V5vc#~ z$6;ETkFW|qX^U!a9!Ahr>U+VRG-oVTGn(3kT<(DvyI0?BHNSebDe`J?{@$kc9}$s% z!nMa(_rGxV#iQ!R9V;b&ZfpCxHylTn3>v9ktzs3;|FnaB{=Yg7uhF2y^!&|TZKq;z z2Xh1W${$;*g6~RMnhU-kJwtXvZjzbT(IM_h=1e$#bI7qF{)x=N1%BLf9p8`Y-yU+I z5OY!kc^uU9gPh1|{9Oc-!OM2jREtL+*S>PhV+2B&bjltA8q?=rD72$T^8p55yb%oj2 zWUE5=-NCJddtqD~I*yC#wz_?DI77-|A+-^&PHLZ@)^5?nL`JJlfdr@^Lc+L z%xpg+%pCemI0JlHn1%W);b1)o--?h6zE4I9m~|f1S2828ge!uhgsXsy3iIxhg&Ag= zaAR;q;T&)^VV1Kj{aq;{Xak{vShNG@2zLOt6z&9WC(P>9MVQrzyW%mSJ;D8j2ZDzP zKMWowJQ5rvqvZ0trqJOJ5YY4+^TmRXW05ezHIy(PhyIon?pa9JiFq>kZQ*iY?)kt7 z_y~@YQ3Y5I*yAHJuCwHB5E$8cv0!9ZgkfcTEzF2-2=me07G|mbO}GsBzHoW9qGfz? z{<^2^Z+sZsFJ5eOJN;|C997kyx7+$+FwS}tKI*9v!wM&nBO=9E6iP#UV_Vg zHHMd^>JTq;Rp13&E~o)7*ta~XwN)o&x8a8U3a0zj&&r-)W~xV*+HF0B+N#}4?KYm6 zcFK9tF7GkJRo07kikYE$zG#;XzmG((Qcu2Um-ehk=eGRgQRdKJ`RDg`x}Kl-?nOJM z{I@94ED{AjwBGvnIQ-yVb#>PUT~+E!c3O?v$o|=|EBF<-=j7Xi?kA!Q(H+Zb9hG#} zQye$(@=JEvh#4r#(H=e75pllRV^7wr!dV;@5@G8QJ$eSF>^SuZx7RR+Gd(as6U}=lu#78h{1B%%6sE1%MEvh_v zlvAUo!|9YLeuXA-Fa5*80@1F5dU%+-;R2ae;8^zs)MZw}D1Me|M%{@}_a-85x~RlS z{_e%n;Ip06ej`%t{~if)I&wwJ@LPWM>?D7)s@zz?>AoCBS^f*KaeDp-T*u!SafP^^ z|5I|`b1={Lk5&^FSF)t<@zSthpVp5ia!-tr;gmzXZE|AHDg#orOB-!a9X?(z$2$NYI7F8(3v z2RKgZLdW-+V9O|640mtN_b^m!TZPsA1FG~?%%rwfZKwL5F(1vpG}Rw4%>;FOn!h$q z%&s`yKfpv&xG?BHZO&5zX27((dS!-xP}KKGtNES|$|(MjikXRX>@TQUGyTKOtNB08 z#3}Ed!x8y4ANRlQLBnzV3IFp&xkIPfC!Bd3&r|fwzTsRdjxVp4%)tSVx7Fu!{1vhP znDwN;b}8-Hj-ca)h^mD4;6%{FBb3Xl{!jYrnZM_+coO%Ix!|AY`fJB(Uw3sB&C%~D z(LmLI%D>QzS0|oAimIu&dH&`J{D3l|&9lghx}2Jd7-f>{9af{}VLJJ7wPc>Zs;97} zj?VL!G$*TX=J_|`<)G$cwmwN+nD58tcRp2Qfxk6|n)!nl_+N{O+=|FN#-0C#vCw~g zp18t!;??qtEcZvms=*umAO6=3+V5?x68Y^m`5Si&nkcLVgD6%^6FJFWJ{f&d{`@Rs zQ6bJp_7)75hM?9K42r^0_6qjVaaj`hs>iP{8LABYEg`cjh~HtdR*r?BH`~Fg4S&xe z-wXE0;X*9O9Vi_}oG`j3MuIToC@IWvIlE2wrNQNeYl6Ae0nJ;0YYMX?R`G=|b)7K#r+QoibKcECn&Z*%>2NKTjDEPm^;0r4@~kjB+gv}Tc`EpdaCz|8 z;2cs-!qdSmg=d003(o;_6i>e|f%^%s0Y5DK2KX`I_24nWTfvhwqZ7Ry zf*#8tpq*e&2Qbjx;HQN@11}b4Kj}r`OW+m4H^7`5q~BZMb;7s7Zwvnc&KEY&+V3fd zm>Rx^#DdE(?+a7I_pERN_>yo5upS5@vLx{LVx9r!90n6X?U7%FTZ8`;9t7qowsaGu znutE_4#FegXt5X#E-cIyD=tvc!%T1~VJ>f#6@Cg_Ntj=WRu_H-%nk$nJ`Zjv%sypP zJ&1zEN(imQf?dS+!moq73U38JB)k(mK=@toP~oHC5yB_H)F_%|@uvv>!br`7`}a0F@t-(@tnzz4~whisJezzNK%s0U8qG_W2xfm!|Z zxG4ys5(GVP0&`rd2TtHFU_EdGvtOnMPT=8SJ#Ye#0{rweZc>(lgLo)6ZW_JCPC>WG_z;B4WKz?^JlIA_6m!dJju zguev$3USc*0YYD~xC0(TE);6rCJC2>c~H0vSa0S7H|gNTVqO{iqA(k)6~fKIYlPc? zHwm`~zoR$jK|tLg91x4%;P-_4flmt$0P{3lCXU(rjqoDy55g~ie->uWaZho&-v<6u zn7J5(7X>$bn2q+wh!^2Wu-@ed7W2R?*i)$e_(zL zoG0dI!I;62`}5X{}MX-+jWpYSbkwD29U-W3RL z?t+VpIcIH43I74sI|RWE+X?PB%rISWb-g(cgct~VhahlKaJD#N??&$s1atOo+KG96 za1Y^T;J(7_tPB?J4CYc9BkKnqEzG6MiNe8=5T=VT20UAMJb0ckwZrsoK?sOzC3?3Y zFg44TiJSRgo-oeH*t>aCcnNr;@GIbL!YjcTu0c5y9$tfRKrA+aseVE8L*NgEkAu$$ ze+vFgn7td0{^*_>VP6Sf2Y)a8J@~fp9q@0$_t^jYON75+;k7~maa6@2I0G#WjuB1+ z#|u{mmlDndrwi8s>m7yQw+UG9C@STm#HiIVOVhpL%y8aC7h>G3Pwz`@*BZJk|v6gT^EXAB)9P;Ln8@fjM)*KwkiV zBfK2U?euB>CisrWyg!U-V$Ote%Tl^$;<%kNxg7Xu;Y{#KGKNIRkkGC}!g|*sFb5=?;U-8&=)mZ` zPsxmUw|ItVoFW$wCGJzNbfoXZV1-94uP8? zeyklM0tk7q;6|%t&YBhz?g&m6?gHj?4&C$tR}>xut|p8@1txMrZYF>m3(o-am=*e6 z0B)lh`Tslw?%__07r}jn`Klc(yd2EkUg?JK_0hsRz!QZ}gJ%hU27X%jJFpTC$Gbpp zO@wf$0KPgX7KI?J6D|ra*s`cNxM0hoQs6z}rZo7Fa2oi1;dJmxVU7hp7Ut{tbKx4` ztHQxr5O@p%?~L#1AB3BLe->^Dz9-xb{I@X23*jh9^g9e}3-k3IE6i!fBEnSlDj^&M zm$upd!!rjJ<-~%6iYmf$z%_*Dg6j%%fYC^p0}M_KF&w`8TMMrTcNE?R?k>Co%rj}| z{s8;`gG4wEi($eXFpL)d1UyNY^MiUnC3xm!=o~SpZrVIy_6c4gCxzw-Rte)ZWBf-L zmB-jfw%`a?V@v+8MeQxoSm9V#4$Bakz1974SfhAewJHzM>7$mFhd5h(PcdyKxLPacSv{O@fS)h(p#Ja|Bs!1hCBl8zmvY#~(VCgClhp91DV7W|v z56P3WLeUlcmpnL(O9e#z5I z9*^u)?vRCyJ@8a7@G)q>NYPthQ7CUEIJCT}3?w2e{_sZ&__`H6#8)m(Gk( zkG*au2DvT@olUMZ{=32v@W9ZtrlE_DnRcqiV1*-`hF0WjxCyLq@VG#%aBzB2D{6j& zFeAKAAcgSA-6JEs98g8l#M76Ngt1BUEJ9w1nkE&pf?<&pY5XvaE#?)JGr40Slr#BK zw-zBD=wf~c8y~x)-psdv@WEc1xAsRs>k^`aDrPZQYd7Kbh=z|-*5M5lFvjrH8++vT z2tb(OSpomyO-lX($>9p5H=<@ISi4`t9A`bjB*J}7#ZD?*Cdlb7sBbQ1`0QCmw4h7F zcL9GQS}}a6Z}Kf3kv9ewSjw3IA2C=~VBgi{9FXB!@ECLz{!VxM!g!iH9z4};0;f~l z!T3AbO+a1Z+%qAO3pW6#NZy zsh#0ZA{w;^z!+XLzJyQN`kyF*~&7|vPTXzJ~X1Et& zVY)?;4V)0{vxV5lOH2q$a`fmZg zy8MwQ9UB2Y55Hsbd3XZ>wf8@ELXc&^A7^q)*}MRiVEu*7-yp0HOr_UiCX2+T06T>K z;#$}r7)n3MI)YOHc02uP=C_FLY@j)vRWLgufOCPa44|r662gT5k2CStG$-P6B`^%P zf?0RubRST1^*87Ev(NmuJ8U)M=^IR-wepB+8g&$>99kk>% z3v)Ww-`Pw+7!hXSYm~a0`oWonxv8hWyOvzD@MfmBr^%6Ulo_8peB5J(zqhuGF$-6q z(|%ftH48tCnEZpZ6lWHG7kB6%YjW4$LT2HzjAD|>I-Y<-wlY=XIy=>K6sJ0@v#Vou zZTvbr6AL5jXnHPOeYDPgKgbC$uU(@TqSp1zt|@i$Yoy7p-vs8?abO|4#SOaog#|^| z6CBJ&Zp}L~ee;n@yRFWL*giuC8g_fK*T;!fyCXTmw+V6C-Ra)qTf(q=vI^ng<$NsW zzGcS;sYz@1Wm;kk>n420I5ooIM5oNDsapaAry8P^)?;ccjwjNcc7y1KdnG#^bn1N_ z$eltd;O5Ov7seXlqb9M_jU1^J_M9FJ-tsYGr)PT#3D8|es5$PVM;Pjd@Zk>EW!3iv zQsj;#dwq>y>5icj%NIkQ@DctxzC}ouJDD-Z_%tAtN$aoP z+F;i;m#c3#K!FaXu^a9B=0mFOMtfoHQwYY)X@FqD5fQq=RLV88d5a-~(q)m@@Xma{ z8~c%u3AvDs)Q?ocCcC%!lA5r|PByEmrJL-e;#~fSZorm{hw&w_0Z!2OQs$5R)0^!1 z-pD6m>h;7nl-<+KhQ{8k4y z67$;N=E8hMv=+_=w-;^(9!N%tEm}Mx0#nTy7g}%>#aR|IM_Qa&Au~aoU4^ z^HuRH=)^hIRQ44+Eu7o=EL4xe$oyJ8dj-9*--_#w+=(l848pq%BP{bqUd5%Is)CEh zo2j~8wbSqoQxIbK#AMX1kV?Zcb@HnHuS&xUU)a=W;3qYnLNDj%d|^l15&ST1fTgiE+^RPfv`)9Q4GNpigc^vkmiBzl9Ke zAE4NIGdBYfd|f#5*8T+O=RV`!h`AiH*&Ik1HU>4&K4lB%F|3DRf{t1PSnD9d>k*A6 zK8{kt8!Q6H7!HGN19Mk;Y8(`XH({9m9k^3(WX*|S`$HHTc0HIx*n7Nc`%ODF$g@up zBAf1pz0ZYxWD7bqd^hnYvK7NePmQzFk$E949uFt!V!T#n8RU`8RXoGyYL*GTTqN24 z7#wDEY~!`-z$D!MgqdqoH!adG59gN6cGqXm$1Mc?HZ>n?dmFsrqmh{i+{!uu-!Wca zjTMYygO1`^xC+`6kizM9OYk(iG$J?L6 zcAU-iy|H#9_#I={#^2F4W(XcOM%mOCM`w_Sr}}#DK>}mE{_fQKwRgbTO~d{XeBQAA za97P{&o|ZPL9Dpjv+&~|xfb^Dvxu2tQ=NTAxXtNm(=G{<1BU&ZM>YJ>E?2cT4E@s4 zY=z|ek1k_=#G<`5{Kv?CAK$zFQ_OhB_p^HON4tD*5uAGMQCtJG&mefi9;`$E776m$ zZz1+Do3q(oI~z{I4L^^T^4iNjpa)Lw8aC&q*ik$MqqyquooL!#lj|vg$7mmE(y zaj+ee#lahRo&H@@HU7y?3Q{-OE^JbdWPM;X{S-4<7B>aHrL*E%*dIuskgP*K6?hi+ zg6cj6(VPu*z|%o1@)d-0fgA=<)#O^rg#hO+?V6?^L0<`QYSz|0M060pptGjtOMD4j z4>X~>7Umon-Y?9v_v}2AnI=6%v{aPPLtMtFI%o+!#Mc>OXLBt)pohpMT)V5OGuSN5 z84A0*me51|km>Dd?t)A75ZOV*yI$8Z(^I&L>UY~Np45i%jy2P8L1&QdtUbwW2Z^1* zKO)r2x6vRLR{L(F7dTqo;Uz+q``J!4%cwR#+aI*(jeEwazWh{0S81oFv$5sBQ_sT^mBAgG<-+Nt(hXzw?*~4V;4!Q0QXKu-gtzsHn}^kEnhEWmeZ5$ z^PTpp<#*8YyG#4Nj2S&Y>g&e1HP|`S>2Pc6rp552Asuc##%(=>j7)di{Yp2Sz;`?7 zwEDh5`rJ;8)azr3aJw*~2w!nzhue)D>iM}n7`)|U#BR@a6wveIw$?dOk8*0-cL_x( zYPc?ZJ}&V`jU;=0Kcm1#jiD3Ew}w1n0B^|0S=p$`j5)?vNIm`wzR>tZz4eQoS(QWZ z+~`)^kKK0#5kmONixI-@{`ZB}u2?JB_+>9jqQoaJL_i0(kH?VE-~ zM(f+E zj*yc(tBSv&YW7zhf3r&^vPCfe)Ar{HTqSA1+@He|$F&$34$$*IEn1)vP zpV3*J`3-OQj_Qx!FyOf#rb^wjQ%W+I%r+*o$?RC2`2>j>Wp*+ul~(=k*|kbVFdJ6u zARmYGDmoEsbVEc9>ME1f&U>QX8DDFw+EYMKu!4rzIB57Yu}dxMUkh){zM0;rW*PuO3{aE z@t^ocr-nN6r(HUE4iaEK>*4L19Wxs6HWqofr12Gsi;=ZS`Tnw-RpZ2JWSdiPRA(JB zENTWTO5MHq<5kQc?)90#8iS9I@VD*<;cDhz=-EtGFa2fr^jr>Czy4+CWfemJ_#AmM zG9ft*j`{9MU`a~Quh;sl5zrK@MLRmue~fyi)s(;OV!g^EsZdsAd_xLc81<<2W>@sk z>vWpZ+Nk>~l2ND;4Hwgp%i2a=-8-(&TYZa$v`siqn+AWYpZ>NB$8%}NTFj?+jt=wu2E&!hyneERT(#C(C9H$9_}}8z>t2U2UfxVaYKiXD7f|+ zG-hO0wVE}n$hFe22}4IJb5Z<){I^}l!FjR;Bbt1SXeLDbw~A`{+moEJIYG|G77SoG z-%>DWp)yRtpvB}9XZ6I7niwXYEq^?4$mG+*Pk#jFgqR=4n(U6_cMeayK);RQ6?`N# zmK=k37k_+n7bw0(A)g!BLgk#JO`2Qc8mV2+|WLgFBVBQ0_=SU)as3fPNBLG$up zzi>@(jBq1xe1VQyE`&s}Xa!CYrcy(?Fc(e=KIrKH=Ijb1=mf4O+y$%$Ffi{9)&m%D ze{e@}^Dvl0Li!!U`|l&dRPZ3-C%}&iKLZ{mybwG=cqw?A@P05y(hT%En7uplUGOsD zd*D^Ve}gxG^>m~MS-4FsY%nL)=(#fZfN(wVd&1e^4~08}St;qh5BM|Te&EZ(REqja zm^x8hGN${X)KU0JgyFFGLwF?EL;<9uvEWGI$zWHQiyQ&rS>PhV9FdnNqu{aJWIVu} zx}%kir@jlS>WNqEGt^^4G~}_-IyWf{lN2u z`Rut>0TVF}{EF65gTR;18)C5%td-hez5)D>n12L5B770dSuqB51$5Kacy z6=u#g6=u%iyB(}i!2`3dr&uuSI3S{VZ}4Nn1Ht@ikmjSo`ePk1TW_vy(|kF2vG98E zE5aXwR||gz=5#RKUts&gNkIzCuI<7%z`KNRg4z3}E;}y9nFsiiFmvv@FdL;?!j-|l z3)cZ_6*7d=7|dzGI1WeKLx`mS=AgESa3639;r`%K!h^tCRZN;*ttuwmfNsF3NJe`y zQkD+xD$IrRhlJ~agZi*HILd)AR4mvUju2*RI9a$g_z7XQbk7PiaZ7{;fmaGY0)9hy z3|OE0hPc?!Z3&qN^&5aE%`&A=!QzlGYs3lRSHK?&?*@M^%x8I3_)D->8$&qXg0`PGdZy=PV2)(TuYpGjuLn;Srgr8N!pFc1 zh0lUt68;ptMwk;18-?$KsU4

    AJ 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

    "; s += "

    Local HTTP services are :

    "; s += "
      "; - // hMDNSServiceQuery-> + // hMDNSServiceQuery-> if (hMDNSServiceQuery) { @@ -354,73 +323,69 @@ void handleHTTPRequest() /* setup */ -void setup(void) -{ - Serial.begin(115200); - // nd.printDump(Serial, Packet::PacketDetail::NONE, [](const Packet& p){return(p.getInOut() && p.isMDNS());}); - Serial.setDebugOutput(false); - - // Connect to WiFi network - WiFi.mode(WIFI_AP_STA); - WiFi.softAP("Soft1"); - WiFi.begin(STASSID, STAPSK); - Serial.println(""); - - // Wait for connection - while (WiFi.status() != WL_CONNECTED) - { - delay(500); - Serial.print("."); - } - Serial.println(""); - Serial.print("Connected to "); - Serial.println(STASSID); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - - // Setup HTTP server - server.on("/", handleHTTPRequest); - - // Setup MDNS responders - MDNSv2.setProbeResultCallback(hostProbeResult); - - // Init the (currently empty) host domain string with 'esp8266' - MDNSv2.begin("esp8266_v2"); - /* - if ((!clsLEAMDNSHost::indexDomain(pcHostDomain, 0, "esp8266")) || - (!MDNSv2.begin(pcHostDomain))) { - Serial.println(" Error setting up MDNS responder!"); - while (1) { // STOP - delay(1000); - } - } - */ - Serial.println("MDNS responder started"); +void setup(void) { + Serial.begin(115200); + // nd.printDump(Serial, Packet::PacketDetail::NONE, [](const Packet& p){return(p.getInOut() && p.isMDNS());}); + Serial.setDebugOutput(false); + + // Connect to WiFi network + WiFi.mode(WIFI_AP_STA); + WiFi.softAP("Soft1"); + WiFi.begin(STASSID, STAPSK); + Serial.println(""); + + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.print("Connected to "); + Serial.println(STASSID); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + // Setup HTTP server + server.on("/", handleHTTPRequest); + + // Setup MDNS responders + MDNSv2.setProbeResultCallback(hostProbeResult); + + // Init the (currently empty) host domain string with 'esp8266' + MDNSv2.begin("esp8266_v2"); + /* + if ((!clsLEAMDNSHost::indexDomain(pcHostDomain, 0, "esp8266")) || + (!MDNSv2.begin(pcHostDomain))) { + Serial.println(" Error setting up MDNS responder!"); + while (1) { // STOP + delay(1000); + } + } + */ + Serial.println("MDNS responder started"); - // Start HTTP server - server.begin(); - Serial.println("HTTP server started"); + // Start HTTP server + server.begin(); + Serial.println("HTTP server started"); #if END - nd.printDump(Serial, Packet::PacketDetail::FULL); + nd.printDump(Serial, Packet::PacketDetail::FULL); #endif } -void loop(void) -{ - // Check if a request has come in - server.handleClient(); - // Allow MDNS processing - MDNSv2.update(); +void loop(void) { + // Check if a request has come in + server.handleClient(); + // Allow MDNS processing + MDNSv2.update(); - static esp8266::polledTimeout::periodicMs timeout(10000); - if (timeout.expired()) - { - Serial.printf("up=%lumn heap=%u\n", millis() / 1000 / 60, ESP.getFreeHeap()); - } + static esp8266::polledTimeout::periodicMs timeout(10000); + if (timeout.expired()) { + Serial.printf("up=%lumn heap=%u\n", millis() / 1000 / 60, ESP.getFreeHeap()); + } } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 5c439bfb17..f0e33465c6 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -265,24 +265,24 @@ bool clsLEAMDNSHost::begin(const char* p_pcHostName, if (bResult) { - if (!LwipIntf::stateUpCB([this](netif* nif) + if (!LwipIntf::stateUpCB([this](netif * nif) + { + (void)nif; + // This called after a new interface appears: + // resend announces on all available interfaces. + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s a new interface %c%c/%d is up, restarting mDNS\n"), + _DH(), nif->name[0], nif->name[1], netif_get_index(nif));); + if (restart()) { - (void)nif; - // This called after a new interface appears: - // resend announces on all available interfaces. - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s a new interface %c%c/%d is up, restarting mDNS\n"), - _DH(), nif->name[0], nif->name[1], netif_get_index(nif));); - if (restart()) - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s restart: success!\n"), _DH())); - } - else - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s restart failed!\n"), _DH())); - } - // No need to react when an interface disappears, - // because mDNS always loop on all available interfaces. - })) + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s restart: success!\n"), _DH())); + } + else + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s restart failed!\n"), _DH())); + } + // No need to react when an interface disappears, + // because mDNS always loop on all available interfaces. + })) { DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s begin: could not add netif status callback\n"), _DH())); } @@ -468,15 +468,15 @@ bool clsLEAMDNSHost::removeService(clsLEAMDNSHost::clsService* p_pService) if (p_pService && (m_Services.end() != std::find(m_Services.begin(), m_Services.end(), p_pService))) { - bResult = _announceService(*p_pService, false); - /* - for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) - if (netif_is_up(pNetIf) && - (_announceService(pNetIf, *p_pService, false))) - { - bResult = true; - } -*/ + bResult = _announceService(*p_pService, false); + /* + for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + if (netif_is_up(pNetIf) && + (_announceService(pNetIf, *p_pService, false))) + { + bResult = true; + } + */ } if (bResult) @@ -561,20 +561,20 @@ clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryService { std::list queries; - clsQuery* pQuery = 0; - if (((pQuery = _allocQuery(clsQuery::enuQueryType::Service))) && - (_buildDomainForService(p_pcService, p_pcProtocol, pQuery->m_Domain))) - { - if (((pQuery->m_bStaticQuery = true)) && (_sendQuery(*pQuery))) - { - queries.push_back(pQuery); - } - else - { - // FAILED to send query - _removeQuery(pQuery); - } - } + clsQuery* pQuery = 0; + if (((pQuery = _allocQuery(clsQuery::enuQueryType::Service))) && + (_buildDomainForService(p_pcService, p_pcProtocol, pQuery->m_Domain))) + { + if (((pQuery->m_bStaticQuery = true)) && (_sendQuery(*pQuery))) + { + queries.push_back(pQuery); + } + else + { + // FAILED to send query + _removeQuery(pQuery); + } + } if (queries.size()) { @@ -616,20 +616,20 @@ clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryHost(co { std::list queries; - clsQuery* pQuery = 0; - if (((pQuery = _allocQuery(clsQuery::enuQueryType::Host))) && - (_buildDomainForHost(p_pcHostName, pQuery->m_Domain))) - { - if (((pQuery->m_bStaticQuery = true)) && (_sendQuery(*pQuery))) - { - queries.push_back(pQuery); - } - else - { - // FAILED to send query - _removeQuery(pQuery); - } - } + clsQuery* pQuery = 0; + if (((pQuery = _allocQuery(clsQuery::enuQueryType::Host))) && + (_buildDomainForHost(p_pcHostName, pQuery->m_Domain))) + { + if (((pQuery->m_bStaticQuery = true)) && (_sendQuery(*pQuery))) + { + queries.push_back(pQuery); + } + else + { + // FAILED to send query + _removeQuery(pQuery); + } + } if (queries.size()) @@ -691,10 +691,10 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcSe clsLEAMDNSHost::clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer) { clsQuery* pQuery = 0; - if ((pQuery = _installServiceQuery(p_pcService, p_pcProtocol))) - { - pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; - } + if ((pQuery = _installServiceQuery(p_pcService, p_pcProtocol))) + { + pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; + } return pQuery; } @@ -707,10 +707,10 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcSe clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor) { clsQuery* pQuery = 0; - if ((pQuery = _installServiceQuery(p_pcService, p_pcProtocol))) - { - pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; - } + if ((pQuery = _installServiceQuery(p_pcService, p_pcProtocol))) + { + pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; + } return pQuery; } @@ -723,13 +723,13 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installHostQuery(const char* p_pcHostN clsQuery* pQuery = 0; if ((p_pcHostName) && (*p_pcHostName)) { - clsRRDomain domain; - if ((pQuery = ((_buildDomainForHost(p_pcHostName, domain)) - ? _installDomainQuery(domain, clsQuery::enuQueryType::Host) - : 0))) - { - pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; - } + clsRRDomain domain; + if ((pQuery = ((_buildDomainForHost(p_pcHostName, domain)) + ? _installDomainQuery(domain, clsQuery::enuQueryType::Host) + : 0))) + { + pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; + } } return pQuery; } @@ -742,15 +742,15 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installHostQuery(const char* p_pcHostN { clsQuery* pQuery = 0; if ((p_pcHostName) && (*p_pcHostName)) - { - clsRRDomain domain; - if ((pQuery = ((_buildDomainForHost(p_pcHostName, domain)) - ? _installDomainQuery(domain, clsQuery::enuQueryType::Host) - : 0))) - { - pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; - } - } + { + clsRRDomain domain; + if ((pQuery = ((_buildDomainForHost(p_pcHostName, domain)) + ? _installDomainQuery(domain, clsQuery::enuQueryType::Host) + : 0))) + { + pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; + } + } return pQuery; } @@ -796,7 +796,7 @@ bool clsLEAMDNSHost::update(void) bool clsLEAMDNSHost::announce(bool p_bAnnounce /*= true*/, bool p_bIncludeServices /*= true*/) { - return _announce(p_bAnnounce, p_bIncludeServices); + return _announce(p_bAnnounce, p_bIncludeServices); } /* @@ -807,7 +807,7 @@ bool clsLEAMDNSHost::announceService(clsService * p_pService, bool p_bAnnounce /*= true*/) { - return _announceService(*p_pService, p_bAnnounce); + return _announceService(*p_pService, p_bAnnounce); } @@ -1273,8 +1273,8 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_findNextQueryByDomain(const clsLEAMDN */ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installServiceQuery( - const char* p_pcService, - const char* p_pcProtocol) + const char* p_pcService, + const char* p_pcProtocol) { clsQuery* pMDNSQuery = 0; @@ -1304,8 +1304,8 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installServiceQuery( clsLEAmDNS2_Host::_installDomainQuery */ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installDomainQuery( - clsLEAMDNSHost::clsRRDomain & p_Domain, - clsLEAMDNSHost::clsQuery::enuQueryType p_QueryType) + clsLEAMDNSHost::clsRRDomain & p_Domain, + clsLEAMDNSHost::clsQuery::enuQueryType p_QueryType) { clsQuery* pQuery = 0; diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index 81510e2bb0..43120be709 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -944,7 +944,7 @@ class clsLEAMDNSHost ServiceDomain = 0x01, // Service domain HostDomain = 0x02, // Host domain Port = 0x04, // Port - HostDomainPort = 0x06, + HostDomainPort = 0x06, Txts = 0x08, // TXT items #ifdef MDNS_IPV4_SUPPORT IPv4Address = 0x10, // IPv4 address @@ -1260,15 +1260,15 @@ class clsLEAMDNSHost it no more returns a single query but a boolean until the API is adapted */ clsQuery* installServiceQuery(const char* p_pcServiceType, - const char* p_pcProtocol, - clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer); + const char* p_pcProtocol, + clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer); clsQuery* installServiceQuery(const char* p_pcServiceType, - const char* p_pcProtocol, - clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor); + const char* p_pcProtocol, + clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor); clsQuery* installHostQuery(const char* p_pcHostName, - clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer); + clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer); clsQuery* installHostQuery(const char* p_pcHostName, - clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor); + clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor); // Remove a dynamic service query bool removeQuery(clsQuery* p_pQuery); diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index c321551f36..de808bfa96 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -1290,21 +1290,21 @@ bool clsLEAMDNSHost::_updateProbeStatus() // // Probe host domain if ((clsProbeInformation_Base::enuProbingStatus::ReadyToStart == m_ProbeInformation.m_ProbingStatus))// && // Ready to get started AND -/* - (( -#ifdef MDNS_IPV4_SUPPORT - _getResponderIPAddress(pNetIf, enuIPProtocolType::V4).isSet() // AND has IPv4 address -#else - true -#endif - ) || ( -#ifdef MDNS_IPV6_SUPPORT - _getResponderIPAddress(pNetIf, enuIPProtocolType::V6).isSet() // OR has IPv6 address -#else - true -#endif - ))) // Has IP address -*/ + /* + (( + #ifdef MDNS_IPV4_SUPPORT + _getResponderIPAddress(pNetIf, enuIPProtocolType::V4).isSet() // AND has IPv4 address + #else + true + #endif + ) || ( + #ifdef MDNS_IPV6_SUPPORT + _getResponderIPAddress(pNetIf, enuIPProtocolType::V6).isSet() // OR has IPv6 address + #else + true + #endif + ))) // Has IP address + */ { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Starting host probing...\n"), _DH());); @@ -1683,7 +1683,7 @@ bool clsLEAMDNSHost::_announce(bool p_bAnnounce, Serial.printf("Announce : %d\r\n", m_ProbeInformation.m_ProbingStatus); #if 0 if ((clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == m_ProbeInformation.m_ProbingStatus) || - (clsProbeInformation_Base::enuProbingStatus::DoneFinally == m_ProbeInformation.m_ProbingStatus)) + (clsProbeInformation_Base::enuProbingStatus::DoneFinally == m_ProbeInformation.m_ProbingStatus)) #endif { bResult = true; @@ -1713,7 +1713,7 @@ bool clsLEAMDNSHost::_announce(bool p_bAnnounce, { #if 0 if ((clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == pService->m_ProbeInformation.m_ProbingStatus) || - (clsProbeInformation_Base::enuProbingStatus::DoneFinally == pService->m_ProbeInformation.m_ProbingStatus)) + (clsProbeInformation_Base::enuProbingStatus::DoneFinally == pService->m_ProbeInformation.m_ProbingStatus)) #endif { pService->m_u32ReplyMask = (static_cast(enuContentFlag::PTR_TYPE) | diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index f54e6fa475..3e4d455b6a 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -52,11 +52,11 @@ namespace experimental */ bool clsLEAMDNSHost::_sendMessage(clsLEAMDNSHost::clsSendParameter& p_rSendParameter) { - bool bResult = true; + bool bResult = true; for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) if (netif_is_up(pNetIf)) { - bResult = bResult && _sendMessage(pNetIf, p_rSendParameter); + bResult = bResult && _sendMessage(pNetIf, p_rSendParameter); } // Finally clear service reply masks @@ -269,8 +269,8 @@ bool clsLEAMDNSHost::_prepareMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParam // Two step sequence: 'Count' and 'Send' for (typeSequence sequence = static_cast(enuSequence::Count); ((bResult) && (sequence <= static_cast(enuSequence::Send))); ++sequence) { - /* - DEBUG_EX_INFO( + /* + DEBUG_EX_INFO( if (static_cast(enuSequence::Send) == sequence) DEBUG_OUTPUT.printf_P(PSTR("%s _prepareMDNSMessage: ID:%u QR:%u OP:%u AA:%u TC:%u RD:%u RA:%u R:%u QD:%u AN:%u NS:%u AR:%u\n"), _DH(), @@ -281,7 +281,7 @@ bool clsLEAMDNSHost::_prepareMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParam (unsigned)msgHeader.m_u16ANCount, (unsigned)msgHeader.m_u16NSCount, (unsigned)msgHeader.m_u16ARCount); - ); + ); */ // Count/send // Header From 64abb8c22043ca48096e06714456ae1bc3c5f75f Mon Sep 17 00:00:00 2001 From: david gauchard Date: Mon, 22 Jun 2020 13:35:03 +0200 Subject: [PATCH 075/152] false alarm #if0defines removed, log messages added --- .../LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino | 6 +++--- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 1 + .../ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 16 ++++++++++------ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino index 2390e6f5c5..dd4e4840c4 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino @@ -195,11 +195,10 @@ void setup(void) { // useless informative callback if (!LwipIntf::stateUpCB([](netif * nif) { - Serial.printf("New interface %c%c(%d) is up(%d)\n", + Serial.printf("New interface %c%c/%d is up\n", nif->name[0], nif->name[1], - netif_get_index(nif), - netif_is_up(nif)); + netif_get_index(nif)); })) { Serial.println("Error: could not add useless informative callback\n"); } @@ -260,6 +259,7 @@ void loop(void) { if (hMDNSService) { // Just trigger a new MDNS announcement, this will lead to a call to // 'MDNSDynamicServiceTxtCallback', which will update the time TXT item + Serial.printf("Announce trigger from user\n"); responder.announce(); } } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index f0e33465c6..2592533212 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -796,6 +796,7 @@ bool clsLEAMDNSHost::update(void) bool clsLEAMDNSHost::announce(bool p_bAnnounce /*= true*/, bool p_bIncludeServices /*= true*/) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s ::announce() externally called\n"), _DH());); return _announce(p_bAnnounce, p_bIncludeServices); } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index de808bfa96..86dc677d7a 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -1343,6 +1343,7 @@ bool clsLEAMDNSHost::_updateProbeStatus() else if ((clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == m_ProbeInformation.m_ProbingStatus) && (m_ProbeInformation.m_Timeout.expired())) { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: ReadyToAnnounce => Announce (without services), now\n"), _DH());); if ((bResult = _announce(true, false))) { // Don't announce services here @@ -1680,11 +1681,16 @@ bool clsLEAMDNSHost::_announce(bool p_bAnnounce, bool bResult = false; clsSendParameter sendParameter; - Serial.printf("Announce : %d\r\n", m_ProbeInformation.m_ProbingStatus); -#if 0 + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s ::announce() status=%d (waiting4data:%d ready2start:%d inprogress:%d ready2announce:%d done:%d\r\n"), + _DH(), m_ProbeInformation.m_ProbingStatus, + clsProbeInformation_Base::enuProbingStatus::WaitingForData, + clsProbeInformation_Base::enuProbingStatus::ReadyToStart, + clsProbeInformation_Base::enuProbingStatus::InProgress, + clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce, + clsProbeInformation_Base::enuProbingStatus::DoneFinally);); + if ((clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == m_ProbeInformation.m_ProbingStatus) || (clsProbeInformation_Base::enuProbingStatus::DoneFinally == m_ProbeInformation.m_ProbingStatus)) -#endif { bResult = true; @@ -1711,10 +1717,8 @@ bool clsLEAMDNSHost::_announce(bool p_bAnnounce, // Announce services (service type, name, SRV (location) and TXTs) for (clsService* pService : m_Services) { -#if 0 if ((clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == pService->m_ProbeInformation.m_ProbingStatus) || (clsProbeInformation_Base::enuProbingStatus::DoneFinally == pService->m_ProbeInformation.m_ProbingStatus)) -#endif { pService->m_u32ReplyMask = (static_cast(enuContentFlag::PTR_TYPE) | static_cast(enuContentFlag::PTR_NAME) | @@ -1725,7 +1729,7 @@ bool clsLEAMDNSHost::_announce(bool p_bAnnounce, } } } - DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _announce: FAILED!\n"), _DH());); + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _announce: FAILED!\n\n"), _DH());); return ((bResult) && (_sendMessage(sendParameter))); } From 166745d7e0950b4c46f2eb5ba38a63f9fb9198d4 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Mon, 22 Jun 2020 18:15:04 +0200 Subject: [PATCH 076/152] style --- .../LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino | 30 +++++++++++-------- .../mDNS_ServiceMonitor_v2.ino | 7 ++--- .../ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 12 ++++---- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino index dd4e4840c4..f9a6b86e50 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino @@ -65,7 +65,7 @@ #define DST_OFFSET 1 // CEST #define UPDATE_CYCLE (1 * 1000) // every second -#define START_AP_AFTER_MS 10000 //60000 // start AP after delay +#define START_AP_AFTER_MS 10000 // start AP after delay #define SERVICE_PORT 80 // HTTP port #ifndef STASSID @@ -81,7 +81,7 @@ const char* ssid = STASSID; const char* password = STAPSK; -clsLEAMDNSHost responder; // MDNS responder +clsLEAMDNSHost MDNS; // MDNS responder bool bHostDomainConfirmed = false; // Flags the confirmation of the host domain clsLEAMDNSHost::clsService* hMDNSService = 0; // The handle of the clock service in the MDNS responder @@ -144,7 +144,7 @@ bool setStationHostname(const char* p_pcHostname) { 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 responder.announce(). + This can be triggered by calling MDNS.announce(). */ void MDNSDynamicServiceTxtCallback(const clsLEAMDNSHost::hMDNSService& p_hService) { @@ -223,14 +223,18 @@ void setup(void) { // Setup MDNS responder // Init the (currently empty) host domain string with 'esp8266' - if (responder.begin("leamdnsv2", [](clsLEAMDNSHost & p_rMDNSHost, - const char* p_pcDomainName, - bool p_bProbeResult)->void { - Serial.printf("mDNSHost_AP::ProbeResultCallback: '%s' is %s\n", p_pcDomainName, (p_bProbeResult ? "FREE" : "USED!")); - // Unattended added service - hMDNSService = p_rMDNSHost.addService(0, "espclk", "tcp", 80); - hMDNSService->addDynamicServiceTxt("curtime", getTimeString()); - hMDNSService->setDynamicServiceTxtCallback(MDNSDynamicServiceTxtCallback); + if (MDNS.begin("leamdnsv2", + [](clsLEAMDNSHost & p_rMDNSHost, const char* p_pcDomainName, bool p_bProbeResult)->void { + if (p_bProbeResult) { + Serial.printf("mDNSHost_AP::ProbeResultCallback: '%s' is %s\n", p_pcDomainName, (p_bProbeResult ? "FREE" : "USED!")); + // Unattended added service + hMDNSService = p_rMDNSHost.addService(0, "espclk", "tcp", 80); + hMDNSService->addDynamicServiceTxt("curtime", getTimeString()); + hMDNSService->setDynamicServiceTxtCallback(MDNSDynamicServiceTxtCallback); + } else { + // Change hostname, use '-' as divider between base name and index + MDNS.setHostName(MDNSResponder::indexDomainName(p_pcDomainName, "-", 0)); + } })) { Serial.println("mDNS-AP started"); } else { @@ -251,7 +255,7 @@ void loop(void) { // Check if a request has come in server.handleClient(); // Allow MDNS processing - responder.update(); + MDNS.update(); static esp8266::polledTimeout::periodicMs timeout(UPDATE_CYCLE); if (timeout.expired()) { @@ -260,7 +264,7 @@ void loop(void) { // Just trigger a new MDNS announcement, this will lead to a call to // 'MDNSDynamicServiceTxtCallback', which will update the time TXT item Serial.printf("Announce trigger from user\n"); - responder.announce(); + MDNS.announce(); } } diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino index fac5d693d6..a1e05b333e 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino @@ -149,6 +149,7 @@ void serviceProbeResult(MDNSResponder::clsService& p_rMDNSService, void hostProbeResult(clsLEAMDNSHost & p_rMDNSHost, String p_pcDomainName, bool p_bProbeResult) { + (void)p_rMDNSHost; Serial.printf("MDNSHostProbeResultCallback: Host domain '%s.local' is %s\n", p_pcDomainName.c_str(), (p_bProbeResult ? "free" : "already USED!")); if (true == p_bProbeResult) { @@ -187,11 +188,7 @@ void hostProbeResult(clsLEAMDNSHost & p_rMDNSHost, String p_pcDomainName, bool p } } else { // Change hostname, use '-' as divider between base name and index - if (MDNSResponder::indexDomainName(pcHostDomain, "-", 0)) { - MDNS.setHostName(pcHostDomain); - } else { - Serial.println("MDNSProbeResultCallback: FAILED to update hostname!"); - } + MDNS.setHostName(MDNSResponder::indexDomainName(p_pcDomainName.c_str(), "-", 0)); } } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index 86dc677d7a..00e260cd76 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -1682,12 +1682,12 @@ bool clsLEAMDNSHost::_announce(bool p_bAnnounce, clsSendParameter sendParameter; DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s ::announce() status=%d (waiting4data:%d ready2start:%d inprogress:%d ready2announce:%d done:%d\r\n"), - _DH(), m_ProbeInformation.m_ProbingStatus, - clsProbeInformation_Base::enuProbingStatus::WaitingForData, - clsProbeInformation_Base::enuProbingStatus::ReadyToStart, - clsProbeInformation_Base::enuProbingStatus::InProgress, - clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce, - clsProbeInformation_Base::enuProbingStatus::DoneFinally);); + _DH(), m_ProbeInformation.m_ProbingStatus, + clsProbeInformation_Base::enuProbingStatus::WaitingForData, + clsProbeInformation_Base::enuProbingStatus::ReadyToStart, + clsProbeInformation_Base::enuProbingStatus::InProgress, + clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce, + clsProbeInformation_Base::enuProbingStatus::DoneFinally);); if ((clsProbeInformation_Base::enuProbingStatus::ReadyToAnnounce == m_ProbeInformation.m_ProbingStatus) || (clsProbeInformation_Base::enuProbingStatus::DoneFinally == m_ProbeInformation.m_ProbingStatus)) From 7bbd93006255b204f3fb8bad1fe4412e5cc866b4 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Mon, 22 Jun 2020 18:17:20 +0200 Subject: [PATCH 077/152] remove test example --- .../mDNS_ServiceMonitor_v2_test.ino | 392 ------------------ 1 file changed, 392 deletions(-) delete mode 100644 libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino deleted file mode 100644 index b387b80112..0000000000 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2_test/mDNS_ServiceMonitor_v2_test.ino +++ /dev/null @@ -1,392 +0,0 @@ -/* - 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'._sapuducul._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. - -*/ - - -#define END 0 // enable netdump - -#ifndef STASSID -#define STASSID "ssid" -#define STAPSK "psk" -#endif - - -#include -#include -#include - -#if END -#include -using namespace NetCapture; -Netdump nd; -#endif - - - -/* - Include the clsLEAMDNSHost (the library needs to be included also) - As LEA clsLEAMDNSHost is experimantal in the ESP8266 environment currently, the - legacy clsLEAMDNSHost is defaulted in th include file. - There are two ways to access LEA clsLEAMDNSHost: - 1. Prepend every declaration and call to global declarations or functions with the namespace, like: - 'LEAmDNS:clsLEAMDNSHost::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. - -*/ - -#define MDNS2_EXPERIMENTAL -#include -//#include "LocalDefines.h" -//#include "Netdump.h" -//using namespace NetCapture; -/* - Global defines and vars -*/ - -clsLEAMDNSHost MDNSv2; - -#define SERVICE_PORT 80 // HTTP port - -char* pcHostDomain = 0; // Negociated host domain -bool bHostDomainConfirmed = false; // Flags the confirmation of the host domain -clsLEAMDNSHost::clsService* hMDNSService = 0; // The handle of the sapuducu service in the MDNS responder -clsLEAMDNSHost::clsQuery* hMDNSServiceQuery = 0; // The handle of the 'espclk.tcp' service query in the MDNS responder - -const String cstrNoHTTPServices = "Currently no 'http.tcp' services in the local network!
      "; -String strHTTPServices = cstrNoHTTPServices; - -// HTTP server at port 'SERVICE_PORT' will respond to HTTP requests -ESP8266WebServer server(SERVICE_PORT); - - -/* - setStationHostname -*/ -bool setStationHostname(const char* p_pcHostname) { - - if (p_pcHostname) { - WiFi.hostname(p_pcHostname); - Serial.printf("setStationHostname: Station hostname is set to '%s'\n", p_pcHostname); - return true; - } - return false; -} - -void MDNSServiceQueryCallback(const clsLEAMDNSHost::clsQuery& p_Query, - const clsLEAMDNSHost::clsQuery::clsAnswer& p_Answer, - clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, - bool p_bSetContent) { - Serial.printf("CB MDNSServiceQueryCallback\n"); - - String answerInfo; - clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType mask = 0; - if (p_QueryAnswerTypeFlags & (uint8_t)clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain) { - answerInfo += "(ServiceDomain "; - answerInfo += p_Answer.m_ServiceDomain.c_str(); - answerInfo += ")"; - mask |= (uint8_t)clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain; - } - if (p_QueryAnswerTypeFlags & (uint8_t)clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::HostDomainPort) { - answerInfo += "(HostDomainAndPort "; - answerInfo += p_Answer.m_HostDomain.c_str(); - answerInfo += ")"; - mask |= (uint8_t)clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::HostDomainPort; - } - if (p_QueryAnswerTypeFlags & (uint8_t)clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address) { - answerInfo += "(IP4Address "; - for (auto ip : p_Answer.m_IPv4Addresses) { - answerInfo += "- "; - answerInfo += ip->m_IPAddress.toString(); - } - answerInfo += ")"; - mask |= (uint8_t)clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address; - } -#ifdef MDNS_IPV6_SUPPORT - if (p_QueryAnswerTypeFlags & (uint8_t)clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address) { - answerInfo += "(IP6Address "; - for (auto ip : p_Answer.m_IPv6Addresses) { - answerInfo += "- "; - answerInfo += ip->m_IPAddress.toString(); - } - answerInfo += ")"; - mask |= (uint8_t)clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address; - } -#endif // MDNS_IPV6_SUPPORT - if (p_QueryAnswerTypeFlags & (uint8_t)clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Txts) { - answerInfo += "(TXT "; - for (auto kv : p_Answer.m_Txts.m_Txts) { - answerInfo += "kv: "; - answerInfo += kv->m_pcKey; - answerInfo += ": "; - answerInfo += kv->m_pcValue; - } - answerInfo += ")"; - mask |= (uint8_t)clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Txts; - } - if (p_QueryAnswerTypeFlags & ~mask) { - answerInfo += "(other flags are set)"; - } - -#if 0 - switch (p_QueryAnswerTypeFlags) { - case static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain): - answerInfo = "ServiceDomain " + String(p_Answer.m_ServiceDomain.c_str()); - break; - - case static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::HostDomainPort): - answerInfo = "HostDomainAndPort " + String(p_Answer.m_HostDomain.c_str()) + ":" + String(p_Answer.m_u16Port); - break; - case static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address): - answerInfo = "IP4Address "; - for (auto ip : p_Answer.m_IPv4Addresses) { - answerInfo += "- " + ip->m_IPAddress.toString(); - }; - break; - case static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Txts): - answerInfo = "TXT "; - for (auto kv : p_Answer.m_Txts.m_Txts) { - answerInfo += "\nkv : " + String(kv->m_pcKey) + " : " + String(kv->m_pcValue); - } - break; - default : - answerInfo = "Unknown Answertype " + String(p_QueryAnswerTypeFlags); - - } -#endif - - Serial.printf("Answer %s %s\n", answerInfo.c_str(), p_bSetContent ? "Modified" : "Deleted"); -} - -/* - MDNSServiceProbeResultCallback - Probe result callback for Services -*/ - -void serviceProbeResult(clsLEAMDNSHost::clsService& p_rMDNSService, - const char* p_pcInstanceName, - bool p_bProbeResult) { - Serial.printf("MDNSServiceProbeResultCallback: Service %s probe %s\n", p_pcInstanceName, (p_bProbeResult ? "succeeded." : "failed!")); -} - -/* - MDNSHostProbeResultCallback - - Probe result callback for the host domain. - If the domain is free, the host domain is set and the http service is - added. - If the domain is already used, a new name is created and the probing is - restarted via p_pclsLEAMDNSHost->setHostname(). - -*/ - -void hostProbeResult(clsLEAMDNSHost & p_rMDNSHost, String p_pcDomainName, bool p_bProbeResult) { - - Serial.printf("MDNSHostProbeResultCallback: Host domain '%s.local' is %s\n", p_pcDomainName.c_str(), (p_bProbeResult ? "free" : "already USED!")); - - if (true == p_bProbeResult) { - // Set station hostname - setStationHostname(pcHostDomain); - Serial.printf("setting hostname = '%s'\n", pcHostDomain ? : "nullptr"); - - if (!bHostDomainConfirmed) { - // Hostname free -> setup clock service - bHostDomainConfirmed = true; - - if (!hMDNSService) { - Serial.printf("adding service tcp.sapuducu port %d\n", SERVICE_PORT); - // Add a 'sapuducu.tcp' service to port 'SERVICE_PORT', using the host domain as instance domain - hMDNSService = MDNSv2.addService(0, "sapuducu", "tcp", SERVICE_PORT, serviceProbeResult); - - if (hMDNSService) { - Serial.printf("hMDNSService\n"); - hMDNSService->setProbeResultCallback(serviceProbeResult); - // MDNSv2.setServiceProbeResultCallback(hMDNSService, serviceProbeResult); - - // Add some '_sapuducu._tcp' protocol specific MDNS service TXT items - // See: http://www.dns-sd.org/txtrecords.html#http - hMDNSService->addServiceTxt("user", "x"); - hMDNSService->addServiceTxt("password", "y"); - hMDNSService->addServiceTxt("path", "/"); - } else { - Serial.printf("hMDNSService=0 ??\n"); - } - - // Install dynamic 'espclk.tcp' service query - if (!hMDNSServiceQuery) { - Serial.printf("installing hMDNSServiceQuery\n"); - hMDNSServiceQuery = MDNSv2.installServiceQuery("espclk", "tcp", MDNSServiceQueryCallback); - if (hMDNSServiceQuery) { - Serial.printf("MDNSProbeResultCallback: Service query for 'espclk.tcp' services installed.\n"); - } else { - Serial.printf("MDNSProbeResultCallback: FAILED to install service query for 'espclk.tcp' services!\n"); - } - } - } - } - } else { - // Change hostname, use '-' as divider between base name and index - if (clsLEAMDNSHost::indexDomainName(pcHostDomain, "-", 0)) { - MDNSv2.setHostName(pcHostDomain); - } else { - Serial.println("MDNSProbeResultCallback: FAILED to update hostname!"); - } - } -} - -/* - HTTP request function (not found is handled by server) -*/ -void handleHTTPRequest() { - Serial.println(""); - Serial.println("HTTP Request"); - - IPAddress ip = server.client().localIP(); - String ipStr = ip.toString(); - String s = "\r\n

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

      "; - s += "

      Local HTTP services are :

      "; - s += "
        "; - // hMDNSServiceQuery-> - - - if (hMDNSServiceQuery) { - for (auto info : hMDNSServiceQuery->answerAccessors()) { - s += "
      1. "; - s += info.serviceDomain(); - - if (info.hostDomainAvailable()) { - s += "
        Hostname: "; - s += String(info.hostDomain()); - s += (info.hostPortAvailable()) ? (":" + String(info.hostPort())) : ""; - } - if (info.IPv4AddressAvailable()) { - s += "
        IPv4:"; - for (auto ip : info.IPv4Addresses()) { - s += " " + ip.toString(); - } - } -#ifdef MDNS_IPV6_SUPPORT - if (info.IPv6AddressAvailable()) { - s += "
        IPv6:"; - for (auto ip : info.IPv6Addresses()) { - s += " " + ip.toString(); - } - } -#endif - if (info.txtsAvailable()) { - s += "
        TXT:
        "; - for (auto kv : info.txtKeyValues()) { - s += "\t" + String(kv.first) + " : " + String(kv.second) + "
        "; - } - } - s += "
      2. "; - } - } - s += "

      "; - - Serial.println("Sending 200"); - server.send(200, "text/html", s); - Serial.println("Done with request"); -} - - - -/* - setup -*/ -void setup(void) { - Serial.begin(115200); - // nd.printDump(Serial, Packet::PacketDetail::NONE, [](const Packet& p){return(p.getInOut() && p.isMDNS());}); - Serial.setDebugOutput(false); - - // Connect to WiFi network - WiFi.mode(WIFI_AP_STA); - WiFi.softAP("Soft1"); - WiFi.begin(STASSID, STAPSK); - Serial.println(""); - - // Wait for connection - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - Serial.println(""); - Serial.print("Connected to "); - Serial.println(STASSID); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - - // Setup HTTP server - server.on("/", handleHTTPRequest); - - // Setup MDNS responders - MDNSv2.setProbeResultCallback(hostProbeResult); - - // Init the (currently empty) host domain string with 'esp8266' - MDNSv2.begin("esp8266_v2"); - /* - if ((!clsLEAMDNSHost::indexDomain(pcHostDomain, 0, "esp8266")) || - (!MDNSv2.begin(pcHostDomain))) { - Serial.println(" Error setting up MDNS responder!"); - while (1) { // STOP - delay(1000); - } - } - */ - Serial.println("MDNS responder started"); - - // Start HTTP server - server.begin(); - Serial.println("HTTP server started"); - -#if END - nd.printDump(Serial, Packet::PacketDetail::FULL); -#endif - -} - - - -void loop(void) { - // Check if a request has come in - server.handleClient(); - // Allow MDNS processing - MDNSv2.update(); - - static esp8266::polledTimeout::periodicMs timeout(10000); - if (timeout.expired()) { - Serial.printf("up=%lumn heap=%u\n", millis() / 1000 / 60, ESP.getFreeHeap()); - } -} - - - From 1309974f75ebeb62df7d3be18804b04c6227c445 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 23 Jun 2020 10:29:19 +0200 Subject: [PATCH 078/152] revert/fix ESP8266mDNS.h --- .../ArduinoOTA/examples/BasicOTA/BasicOTA.ino | 4 +- .../ArduinoOTA/examples/OTALeds/OTALeds.ino | 4 +- .../CaptivePortalAdvanced.ino | 4 +- .../Arduino_Wifi_AVRISP.ino | 4 +- .../SecureBearSSLUpdater.ino | 4 +- .../SecureHTTPSUpdater/SecureHTTPSUpdater.ino | 4 +- .../SecureWebUpdater/SecureWebUpdater.ino | 4 +- .../examples/WebUpdater/WebUpdater.ino | 4 +- .../AdvancedWebServer/AdvancedWebServer.ino | 2 - .../examples/FSBrowser/FSBrowser.ino | 4 +- .../ESP8266WebServer/examples/Graph/Graph.ino | 4 +- .../examples/HelloServer/HelloServer.ino | 2 - .../HelloServerBearSSL/HelloServerBearSSL.ino | 2 - .../HelloServerSecure/HelloServerSecure.ino | 2 - .../HttpAdvancedAuth/HttpAdvancedAuth.ino | 4 +- .../examples/HttpBasicAuth/HttpBasicAuth.ino | 4 +- .../examples/PathArgServer/PathArgServer.ino | 2 - .../examples/PostServer/PostServer.ino | 2 - .../ServerSentEvents/ServerSentEvents.ino | 4 +- .../examples/WebUpdate/WebUpdate.ino | 2 - .../OTA-mDNS-LittleFS/OTA-mDNS-LittleFS.ino | 2 - .../mDNS-SD_Extended/mDNS-SD_Extended.ino | 2 - .../mDNS_Web_Server/mDNS_Web_Server.ino | 4 +- libraries/ESP8266mDNS/src/ESP8266mDNS.h | 38 +++++++------------ 24 files changed, 28 insertions(+), 84 deletions(-) diff --git a/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino b/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino index b7973d4517..ddc5629741 100644 --- a/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino +++ b/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino @@ -1,10 +1,8 @@ #include +#include #include #include -//#define MDNS2_EXPERIMENTAL_V1COMPAT -#include - #ifndef STASSID #define STASSID "your-ssid" #define STAPSK "your-password" diff --git a/libraries/ArduinoOTA/examples/OTALeds/OTALeds.ino b/libraries/ArduinoOTA/examples/OTALeds/OTALeds.ino index 9fe1a8798e..7d05074e15 100644 --- a/libraries/ArduinoOTA/examples/OTALeds/OTALeds.ino +++ b/libraries/ArduinoOTA/examples/OTALeds/OTALeds.ino @@ -1,10 +1,8 @@ #include +#include #include #include -//#define MDNS2_EXPERIMENTAL_V1COMPAT -#include - #ifndef STASSID #define STASSID "your-ssid" #define STAPSK "your-password" diff --git a/libraries/DNSServer/examples/CaptivePortalAdvanced/CaptivePortalAdvanced.ino b/libraries/DNSServer/examples/CaptivePortalAdvanced/CaptivePortalAdvanced.ino index 2435ff9cfa..5f1f5020a1 100644 --- a/libraries/DNSServer/examples/CaptivePortalAdvanced/CaptivePortalAdvanced.ino +++ b/libraries/DNSServer/examples/CaptivePortalAdvanced/CaptivePortalAdvanced.ino @@ -2,10 +2,8 @@ #include #include #include -#include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include +#include /* This example serves a "hello world" on a WLAN and a SoftAP at the same time. diff --git a/libraries/ESP8266AVRISP/examples/Arduino_Wifi_AVRISP/Arduino_Wifi_AVRISP.ino b/libraries/ESP8266AVRISP/examples/Arduino_Wifi_AVRISP/Arduino_Wifi_AVRISP.ino index 362e0f1b7b..fdc34cff2b 100644 --- a/libraries/ESP8266AVRISP/examples/Arduino_Wifi_AVRISP/Arduino_Wifi_AVRISP.ino +++ b/libraries/ESP8266AVRISP/examples/Arduino_Wifi_AVRISP/Arduino_Wifi_AVRISP.ino @@ -1,9 +1,7 @@ #include #include -#include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include +#include #ifndef STASSID #define STASSID "your-ssid" diff --git a/libraries/ESP8266HTTPUpdateServer/examples/SecureBearSSLUpdater/SecureBearSSLUpdater.ino b/libraries/ESP8266HTTPUpdateServer/examples/SecureBearSSLUpdater/SecureBearSSLUpdater.ino index 534643a8e1..a2514e171d 100644 --- a/libraries/ESP8266HTTPUpdateServer/examples/SecureBearSSLUpdater/SecureBearSSLUpdater.ino +++ b/libraries/ESP8266HTTPUpdateServer/examples/SecureBearSSLUpdater/SecureBearSSLUpdater.ino @@ -16,10 +16,8 @@ #include #include #include -#include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include +#include #ifndef STASSID #define STASSID "your-ssid" diff --git a/libraries/ESP8266HTTPUpdateServer/examples/SecureHTTPSUpdater/SecureHTTPSUpdater.ino b/libraries/ESP8266HTTPUpdateServer/examples/SecureHTTPSUpdater/SecureHTTPSUpdater.ino index b0d0d1f70d..a28d48de81 100644 --- a/libraries/ESP8266HTTPUpdateServer/examples/SecureHTTPSUpdater/SecureHTTPSUpdater.ino +++ b/libraries/ESP8266HTTPUpdateServer/examples/SecureHTTPSUpdater/SecureHTTPSUpdater.ino @@ -49,15 +49,13 @@ #include #include #include +#include #include #include #include #include #include -//#define MDNS2_EXPERIMENTAL_V1COMPAT -#include - #pragma GCC diagnostic pop #ifndef STASSID diff --git a/libraries/ESP8266HTTPUpdateServer/examples/SecureWebUpdater/SecureWebUpdater.ino b/libraries/ESP8266HTTPUpdateServer/examples/SecureWebUpdater/SecureWebUpdater.ino index 4e73983cf8..11e23a929d 100644 --- a/libraries/ESP8266HTTPUpdateServer/examples/SecureWebUpdater/SecureWebUpdater.ino +++ b/libraries/ESP8266HTTPUpdateServer/examples/SecureWebUpdater/SecureWebUpdater.ino @@ -5,10 +5,8 @@ #include #include #include -#include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include +#include #ifndef STASSID #define STASSID "your-ssid" diff --git a/libraries/ESP8266HTTPUpdateServer/examples/WebUpdater/WebUpdater.ino b/libraries/ESP8266HTTPUpdateServer/examples/WebUpdater/WebUpdater.ino index cee1b69c03..fea3c4023a 100644 --- a/libraries/ESP8266HTTPUpdateServer/examples/WebUpdater/WebUpdater.ino +++ b/libraries/ESP8266HTTPUpdateServer/examples/WebUpdater/WebUpdater.ino @@ -5,10 +5,8 @@ #include #include #include -#include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include +#include #ifndef STASSID #define STASSID "your-ssid" diff --git a/libraries/ESP8266WebServer/examples/AdvancedWebServer/AdvancedWebServer.ino b/libraries/ESP8266WebServer/examples/AdvancedWebServer/AdvancedWebServer.ino index 92cc76044d..2fc8508ca0 100644 --- a/libraries/ESP8266WebServer/examples/AdvancedWebServer/AdvancedWebServer.ino +++ b/libraries/ESP8266WebServer/examples/AdvancedWebServer/AdvancedWebServer.ino @@ -31,8 +31,6 @@ #include #include #include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include #ifndef STASSID diff --git a/libraries/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino b/libraries/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino index fe3b50f2c4..f6aff52c7d 100644 --- a/libraries/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino +++ b/libraries/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino @@ -40,10 +40,8 @@ #include #include #include -#include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include +#include #ifdef INCLUDE_FALLBACK_INDEX_HTM #include "extras/index_htm.h" diff --git a/libraries/ESP8266WebServer/examples/Graph/Graph.ino b/libraries/ESP8266WebServer/examples/Graph/Graph.ino index c17b043f18..4f73455676 100644 --- a/libraries/ESP8266WebServer/examples/Graph/Graph.ino +++ b/libraries/ESP8266WebServer/examples/Graph/Graph.ino @@ -33,10 +33,8 @@ #include #include #include -#include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include +#include #if defined USE_SPIFFS #include diff --git a/libraries/ESP8266WebServer/examples/HelloServer/HelloServer.ino b/libraries/ESP8266WebServer/examples/HelloServer/HelloServer.ino index 5f902d1d07..1335c347af 100644 --- a/libraries/ESP8266WebServer/examples/HelloServer/HelloServer.ino +++ b/libraries/ESP8266WebServer/examples/HelloServer/HelloServer.ino @@ -1,8 +1,6 @@ #include #include #include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include #ifndef STASSID diff --git a/libraries/ESP8266WebServer/examples/HelloServerBearSSL/HelloServerBearSSL.ino b/libraries/ESP8266WebServer/examples/HelloServerBearSSL/HelloServerBearSSL.ino index daa5d8f381..4b786dc1a5 100644 --- a/libraries/ESP8266WebServer/examples/HelloServerBearSSL/HelloServerBearSSL.ino +++ b/libraries/ESP8266WebServer/examples/HelloServerBearSSL/HelloServerBearSSL.ino @@ -12,8 +12,6 @@ #include #include #include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include #ifndef STASSID diff --git a/libraries/ESP8266WebServer/examples/HelloServerSecure/HelloServerSecure.ino b/libraries/ESP8266WebServer/examples/HelloServerSecure/HelloServerSecure.ino index a9b21d472d..87c34ccb58 100644 --- a/libraries/ESP8266WebServer/examples/HelloServerSecure/HelloServerSecure.ino +++ b/libraries/ESP8266WebServer/examples/HelloServerSecure/HelloServerSecure.ino @@ -44,8 +44,6 @@ #include #include #include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include #ifndef STASSID diff --git a/libraries/ESP8266WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino b/libraries/ESP8266WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino index 0b175c05bf..857048cf0b 100644 --- a/libraries/ESP8266WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino +++ b/libraries/ESP8266WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino @@ -5,12 +5,10 @@ */ #include +#include #include #include -//#define MDNS2_EXPERIMENTAL_V1COMPAT -#include - #ifndef STASSID #define STASSID "your-ssid" #define STAPSK "your-password" diff --git a/libraries/ESP8266WebServer/examples/HttpBasicAuth/HttpBasicAuth.ino b/libraries/ESP8266WebServer/examples/HttpBasicAuth/HttpBasicAuth.ino index 26899ea71f..7c06637caf 100644 --- a/libraries/ESP8266WebServer/examples/HttpBasicAuth/HttpBasicAuth.ino +++ b/libraries/ESP8266WebServer/examples/HttpBasicAuth/HttpBasicAuth.ino @@ -1,10 +1,8 @@ #include +#include #include #include -//#define MDNS2_EXPERIMENTAL_V1COMPAT -#include - #ifndef STASSID #define STASSID "your-ssid" #define STAPSK "your-password" diff --git a/libraries/ESP8266WebServer/examples/PathArgServer/PathArgServer.ino b/libraries/ESP8266WebServer/examples/PathArgServer/PathArgServer.ino index 0d37972f51..dc998986e5 100644 --- a/libraries/ESP8266WebServer/examples/PathArgServer/PathArgServer.ino +++ b/libraries/ESP8266WebServer/examples/PathArgServer/PathArgServer.ino @@ -1,8 +1,6 @@ #include #include #include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include #include diff --git a/libraries/ESP8266WebServer/examples/PostServer/PostServer.ino b/libraries/ESP8266WebServer/examples/PostServer/PostServer.ino index 5a963bd131..da1703807a 100644 --- a/libraries/ESP8266WebServer/examples/PostServer/PostServer.ino +++ b/libraries/ESP8266WebServer/examples/PostServer/PostServer.ino @@ -1,8 +1,6 @@ #include #include #include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include #ifndef STASSID diff --git a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino index 3c35dc3dd9..77ae1e958e 100644 --- a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino +++ b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino @@ -37,10 +37,8 @@ extern "C" { #include #include #include -#include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include +#include #ifndef STASSID #define STASSID "your-ssid" diff --git a/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino b/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino index e71d771890..17646fd172 100644 --- a/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino +++ b/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino @@ -5,8 +5,6 @@ #include #include #include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include #ifndef STASSID diff --git a/libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-LittleFS.ino b/libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-LittleFS.ino index a39518efa3..5e4a5a0810 100644 --- a/libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-LittleFS.ino +++ b/libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-LittleFS.ino @@ -28,8 +28,6 @@ #include #include #include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include diff --git a/libraries/ESP8266mDNS/examples/mDNS-SD_Extended/mDNS-SD_Extended.ino b/libraries/ESP8266mDNS/examples/mDNS-SD_Extended/mDNS-SD_Extended.ino index 9b180fcc2d..fda2fe6562 100644 --- a/libraries/ESP8266mDNS/examples/mDNS-SD_Extended/mDNS-SD_Extended.ino +++ b/libraries/ESP8266mDNS/examples/mDNS-SD_Extended/mDNS-SD_Extended.ino @@ -10,8 +10,6 @@ */ #include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include #ifndef STASSID diff --git a/libraries/ESP8266mDNS/examples/mDNS_Web_Server/mDNS_Web_Server.ino b/libraries/ESP8266mDNS/examples/mDNS_Web_Server/mDNS_Web_Server.ino index 66a10b4c5a..7560864ce1 100644 --- a/libraries/ESP8266mDNS/examples/mDNS_Web_Server/mDNS_Web_Server.ino +++ b/libraries/ESP8266mDNS/examples/mDNS_Web_Server/mDNS_Web_Server.ino @@ -17,10 +17,8 @@ #include -#include - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include +#include #ifndef STASSID #define STASSID "your-ssid" diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS.h b/libraries/ESP8266mDNS/src/ESP8266mDNS.h index 4b0721fa42..55b209f9bd 100644 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS.h +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS.h @@ -42,32 +42,22 @@ */ -// mDNS on esp8266 Arduino, 4 available versions, select one with a define -// to add before `#include `: -// -// - original (default until core-2.4.2): add `#define MDNS_ORIGINAL` -// - LEAmDNS (v1, from core-2.5.0 to core 2.7.2): default -// - LEAmDNSv2, with V1 API compatibility: add `#define MDNS2_EXPERIMENTAL_V1COMPAT` -// - LEAmDNSv2, new API: add `#define MDNS2_EXPERIMENTAL` +enum class MDNSApiVersion { Legacy, LEA, LEAv2Compat, LEAv2 }; -#if defined(MDNS_ORIGINAL) -#include "ESP8266mDNS_Legacy.h" +#include "ESP8266mDNS_Legacy.h" // Legacy +#include "LEAmDNS.h" // LEA +#include "LEAmDNS2_Legacy.h" // LEAv2Compat - replacement for LEA using v2 +#include "LEAmDNS2Host.h" // LEAv2 - API updated -#elif defined(MDNS2_EXPERIMENTAL_V1COMPAT) -#include "LEAmDNS2_Legacy.h" -using MDNSResponder = experimental::MDNSImplementation::clsLEAMDNSHost_Legacy; - -#elif defined(MDNS2_EXPERIMENTAL) -#include "LEAmDNS2Host.h" -using esp8266::experimental::clsLEAMDNSHost; -using MDNSResponder = clsLEAMDNSHost; - -#else // default -#include "LEAmDNS.h" -using esp8266::MDNSImplementation::MDNSResponder; - -#endif // version selection +// clsLEAMDNSHost replaces MDNSResponder in LEAv2 +using clsLEAMDNSHost = esp8266::experimental::clsLEAMDNSHost; #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) +// Maps the implementation to use to the global namespace type +//using MDNSResponder = Legacy_MDNSResponder::MDNSResponder; // Legacy +//using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; // LEA +//using MDNSResponder = experimental::MDNSImplementation::clsLEAMDNSHost_Legacy; // LEAv2Compat +using MDNSResponder = clsLEAMDNSHost; // LEAv2 + extern MDNSResponder MDNS; -#endif // global instance +#endif From 2cdc9027971b0cb4181838d6e39a92be89c228ce Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 30 Jun 2020 02:26:45 +0200 Subject: [PATCH 079/152] style --- tests/host/common/user_interface.cpp | 904 +++++++++++++-------------- 1 file changed, 452 insertions(+), 452 deletions(-) diff --git a/tests/host/common/user_interface.cpp b/tests/host/common/user_interface.cpp index 34015e33f9..fccb9dbfa4 100644 --- a/tests/host/common/user_interface.cpp +++ b/tests/host/common/user_interface.cpp @@ -1,32 +1,32 @@ /* - Arduino emulation - espressif sdk host implementation - Copyright (c) 2018 david gauchard. All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal with 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: - - - Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimers. - - - Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimers in the - documentation and/or other materials provided with the distribution. - - - The names of its contributors may not be used to endorse or promote - products derived from this Software without specific prior written - permission. - - 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 CONTRIBUTORS 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 WITH THE SOFTWARE. + Arduino emulation - espressif sdk host implementation + Copyright (c) 2018 david gauchard. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal with 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: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + - The names of its contributors may not be used to endorse or promote + products derived from this Software without specific prior written + permission. + + 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 CONTRIBUTORS 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 WITH THE SOFTWARE. */ #include @@ -44,446 +44,446 @@ extern "C" { -uint8 wifi_get_opmode(void) -{ - return STATION_MODE; -} - -phy_mode_t wifi_get_phy_mode(void) -{ - return PHY_MODE_11N; -} - -uint8 wifi_get_channel (void) -{ - return 1; -} - -uint8 wifi_station_get_current_ap_id (void) -{ - return 0; -} - -station_status_t wifi_station_get_connect_status (void) -{ - return STATION_GOT_IP; -} - -uint8 wifi_station_get_auto_connect (void) -{ - return 1; -} - -bool wifi_station_get_config (struct station_config *config) -{ - strcpy((char*)config->ssid, "emulated-ssid"); - strcpy((char*)config->password, "emulated-ssid-password"); - config->bssid_set = 0; - for (int i = 0; i < 6; i++) - config->bssid[i] = i; - config->threshold.rssi = 1; - config->threshold.authmode = AUTH_WPA_PSK; + uint8 wifi_get_opmode(void) + { + return STATION_MODE; + } + + phy_mode_t wifi_get_phy_mode(void) + { + return PHY_MODE_11N; + } + + uint8 wifi_get_channel(void) + { + return 1; + } + + uint8 wifi_station_get_current_ap_id(void) + { + return 0; + } + + station_status_t wifi_station_get_connect_status(void) + { + return STATION_GOT_IP; + } + + uint8 wifi_station_get_auto_connect(void) + { + return 1; + } + + bool wifi_station_get_config(struct station_config *config) + { + strcpy((char*)config->ssid, "emulated-ssid"); + strcpy((char*)config->password, "emulated-ssid-password"); + config->bssid_set = 0; + for (int i = 0; i < 6; i++) + config->bssid[i] = i; + config->threshold.rssi = 1; + config->threshold.authmode = AUTH_WPA_PSK; #ifdef NONOSDK3V0 - config->open_and_wep_mode_disable = true; + config->open_and_wep_mode_disable = true; #endif - return true; -} - -void wifi_fpm_close(void) -{ -} - -sint8 wifi_fpm_do_sleep (uint32 sleep_time_in_us) -{ - usleep(sleep_time_in_us); - return 1; -} - -void wifi_fpm_do_wakeup (void) -{ -} - -void wifi_fpm_open (void) -{ -} - -void wifi_fpm_set_sleep_type (sleep_type_t type) -{ - (void)type; -} - -uint32_t global_ipv4_netfmt = 0; // global binding - -netif netif0; -uint32_t global_source_address = INADDR_ANY; - -bool wifi_get_ip_info (uint8 if_index, struct ip_info *info) -{ - // emulate wifi_get_ip_info() - // ignore if_index - // use global option -i (host_interface) to select bound interface/address - - struct ifaddrs * ifAddrStruct = NULL, * ifa = NULL; - uint32_t ipv4 = lwip_htonl(0x7f000001); - uint32_t mask = lwip_htonl(0xff000000); - global_source_address = INADDR_ANY; // =0 - - if (getifaddrs(&ifAddrStruct) != 0) - { - perror("getifaddrs"); - exit(EXIT_FAILURE); - } - if (host_interface) - mockverbose("host: looking for interface '%s':\n", host_interface); - else - mockverbose("host: looking the first for non-local IPv4 interface:\n"); - for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) - { - mockverbose("host: interface: %s", ifa->ifa_name); - if ( ifa->ifa_addr - && ifa->ifa_addr->sa_family == AF_INET // ip_info is IPv4 only - ) - { - auto test_ipv4 = lwip_ntohl(*(uint32_t*)&((struct sockaddr_in*)ifa->ifa_addr)->sin_addr); - mockverbose(" IPV4 (0x%08lx)", test_ipv4); - if ((test_ipv4 & 0xff000000) == 0x7f000000) - // 127./8 - mockverbose(" (local, ignored)"); - else - { - if (!host_interface || (host_interface && strcmp(ifa->ifa_name, host_interface) == 0)) - { - // use the first non-local interface, or, if specified, the one selected by user on cmdline - ipv4 = *(uint32_t*)&((struct sockaddr_in*)ifa->ifa_addr)->sin_addr; - mask = *(uint32_t*)&((struct sockaddr_in*)ifa->ifa_netmask)->sin_addr; - mockverbose(" (selected)\n"); - global_source_address = ntohl(ipv4); - break; - } - } - } - mockverbose("\n"); - } - if (ifAddrStruct != NULL) - freeifaddrs(ifAddrStruct); - - (void)if_index; - //if (if_index != STATION_IF) - // fprintf(stderr, "we are not AP"); - - if (global_ipv4_netfmt == NO_GLOBAL_BINDING) - global_ipv4_netfmt = ipv4; - - if (info) - { - info->ip.addr = ipv4; - info->netmask.addr = mask; - info->gw.addr = ipv4; - - netif0.ip_addr.addr = ipv4; - netif0.netmask.addr = mask; - netif0.gw.addr = ipv4; - netif0.flags = NETIF_FLAG_IGMP | NETIF_FLAG_UP | NETIF_FLAG_LINK_UP; - netif0.next = nullptr; - } - - return true; -} - -uint8 wifi_get_listen_interval (void) -{ - return 1; -} - -bool wifi_get_macaddr(uint8 if_index, uint8 *macaddr) -{ - (void)if_index; - macaddr[0] = 0xde; - macaddr[1] = 0xba; - macaddr[2] = 0x7a; - macaddr[3] = 0xb1; - macaddr[4] = 0xe0; - macaddr[5] = 0x42; - return true; -} - -uint8 wifi_get_opmode_default (void) -{ - return STATION_MODE; -} + return true; + } + + void wifi_fpm_close(void) + { + } + + sint8 wifi_fpm_do_sleep(uint32 sleep_time_in_us) + { + usleep(sleep_time_in_us); + return 1; + } + + void wifi_fpm_do_wakeup(void) + { + } + + void wifi_fpm_open(void) + { + } + + void wifi_fpm_set_sleep_type(sleep_type_t type) + { + (void)type; + } + + uint32_t global_ipv4_netfmt = 0; // global binding + + netif netif0; + uint32_t global_source_address = INADDR_ANY; + + bool wifi_get_ip_info(uint8 if_index, struct ip_info *info) + { + // emulate wifi_get_ip_info() + // ignore if_index + // use global option -i (host_interface) to select bound interface/address + + struct ifaddrs * ifAddrStruct = NULL, * ifa = NULL; + uint32_t ipv4 = lwip_htonl(0x7f000001); + uint32_t mask = lwip_htonl(0xff000000); + global_source_address = INADDR_ANY; // =0 + + if (getifaddrs(&ifAddrStruct) != 0) + { + perror("getifaddrs"); + exit(EXIT_FAILURE); + } + if (host_interface) + mockverbose("host: looking for interface '%s':\n", host_interface); + else + mockverbose("host: looking the first for non-local IPv4 interface:\n"); + for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) + { + mockverbose("host: interface: %s", ifa->ifa_name); + if (ifa->ifa_addr + && ifa->ifa_addr->sa_family == AF_INET // ip_info is IPv4 only + ) + { + auto test_ipv4 = lwip_ntohl(*(uint32_t*) & ((struct sockaddr_in*)ifa->ifa_addr)->sin_addr); + mockverbose(" IPV4 (0x%08lx)", test_ipv4); + if ((test_ipv4 & 0xff000000) == 0x7f000000) + // 127./8 + mockverbose(" (local, ignored)"); + else + { + if (!host_interface || (host_interface && strcmp(ifa->ifa_name, host_interface) == 0)) + { + // use the first non-local interface, or, if specified, the one selected by user on cmdline + ipv4 = *(uint32_t*) & ((struct sockaddr_in*)ifa->ifa_addr)->sin_addr; + mask = *(uint32_t*) & ((struct sockaddr_in*)ifa->ifa_netmask)->sin_addr; + mockverbose(" (selected)\n"); + global_source_address = ntohl(ipv4); + break; + } + } + } + mockverbose("\n"); + } + if (ifAddrStruct != NULL) + freeifaddrs(ifAddrStruct); + + (void)if_index; + //if (if_index != STATION_IF) + // fprintf(stderr, "we are not AP"); + + if (global_ipv4_netfmt == NO_GLOBAL_BINDING) + global_ipv4_netfmt = ipv4; + + if (info) + { + info->ip.addr = ipv4; + info->netmask.addr = mask; + info->gw.addr = ipv4; + + netif0.ip_addr.addr = ipv4; + netif0.netmask.addr = mask; + netif0.gw.addr = ipv4; + netif0.flags = NETIF_FLAG_IGMP | NETIF_FLAG_UP | NETIF_FLAG_LINK_UP; + netif0.next = nullptr; + } + + return true; + } + + uint8 wifi_get_listen_interval(void) + { + return 1; + } + + bool wifi_get_macaddr(uint8 if_index, uint8 *macaddr) + { + (void)if_index; + macaddr[0] = 0xde; + macaddr[1] = 0xba; + macaddr[2] = 0x7a; + macaddr[3] = 0xb1; + macaddr[4] = 0xe0; + macaddr[5] = 0x42; + return true; + } + + uint8 wifi_get_opmode_default(void) + { + return STATION_MODE; + } #ifdef NONOSDK3V0 -sleep_level_t wifi_get_sleep_level (void) -{ - return MIN_SLEEP_T; -} + sleep_level_t wifi_get_sleep_level(void) + { + return MIN_SLEEP_T; + } #endif // nonos-sdk-pre-3 -sleep_type_t wifi_get_sleep_type (void) -{ - return NONE_SLEEP_T; -} - -bool wifi_set_channel (uint8 channel) -{ - (void)channel; - return true; -} - -wifi_event_handler_cb_t wifi_event_handler_cb_emu = nullptr; -void wifi_set_event_handler_cb (wifi_event_handler_cb_t cb) -{ - wifi_event_handler_cb_emu = cb; - mockverbose("TODO: wifi_set_event_handler_cb set\n"); -} - -bool wifi_set_ip_info (uint8 if_index, struct ip_info *info) -{ - (void)if_index; - (void)info; - return false; -} - -bool wifi_set_listen_interval (uint8 interval) -{ - (void)interval; - return true; -} - -bool wifi_set_opmode (uint8 opmode) -{ - return opmode == STATION_MODE || opmode == STATIONAP_MODE; -} - -bool wifi_set_opmode_current (uint8 opmode) -{ - return opmode == STATION_MODE || opmode == STATIONAP_MODE; -} - -bool wifi_set_phy_mode (phy_mode_t mode) -{ - (void)mode; - return true; -} + sleep_type_t wifi_get_sleep_type(void) + { + return NONE_SLEEP_T; + } + + bool wifi_set_channel(uint8 channel) + { + (void)channel; + return true; + } + + wifi_event_handler_cb_t wifi_event_handler_cb_emu = nullptr; + void wifi_set_event_handler_cb(wifi_event_handler_cb_t cb) + { + wifi_event_handler_cb_emu = cb; + mockverbose("TODO: wifi_set_event_handler_cb set\n"); + } + + bool wifi_set_ip_info(uint8 if_index, struct ip_info *info) + { + (void)if_index; + (void)info; + return false; + } + + bool wifi_set_listen_interval(uint8 interval) + { + (void)interval; + return true; + } + + bool wifi_set_opmode(uint8 opmode) + { + return opmode == STATION_MODE || opmode == STATIONAP_MODE; + } + + bool wifi_set_opmode_current(uint8 opmode) + { + return opmode == STATION_MODE || opmode == STATIONAP_MODE; + } + + bool wifi_set_phy_mode(phy_mode_t mode) + { + (void)mode; + return true; + } #ifdef NONOSDK3V0 -bool wifi_set_sleep_level (sleep_level_t level) -{ - (void)level; - return true; -} + bool wifi_set_sleep_level(sleep_level_t level) + { + (void)level; + return true; + } #endif -bool wifi_set_sleep_type (sleep_type_t type) -{ - (void)type; - return true; -} - -bool wifi_station_connect (void) -{ - return true; -} - -bool wifi_station_dhcpc_start (void) -{ - return true; -} - -bool wifi_station_dhcpc_stop (void) -{ - return true; -} - -bool wifi_station_disconnect (void) -{ - return true; -} - -bool wifi_station_get_config_default (struct station_config *config) -{ - return wifi_station_get_config(config); -} - -char wifi_station_get_hostname_str [128]; -const char* wifi_station_get_hostname (void) -{ - return strcpy(wifi_station_get_hostname_str, "esposix"); -} - -bool wifi_station_get_reconnect_policy () -{ - return true; -} - -sint8 wifi_station_get_rssi (void) -{ - return 5; -} - -bool wifi_station_set_auto_connect (uint8 set) -{ - return set != 0; -} - -bool wifi_station_set_config (struct station_config *config) -{ - (void)config; - return true; -} - -bool wifi_station_set_config_current (struct station_config *config) -{ - (void)config; - return true; -} - -bool wifi_station_set_hostname (const char *name) -{ - (void)name; - return true; -} - -bool wifi_station_set_reconnect_policy (bool set) -{ - (void)set; - return true; -} - -void system_phy_set_max_tpw (uint8 max_tpw) -{ - (void)max_tpw; -} - -bool wifi_softap_dhcps_start(void) -{ - return true; -} - -enum dhcp_status wifi_softap_dhcps_status(void) -{ - return DHCP_STARTED; -} - -bool wifi_softap_dhcps_stop(void) -{ - return true; -} - -bool wifi_softap_get_config(struct softap_config *config) -{ - strcpy((char*)config->ssid, "apssid"); - strcpy((char*)config->password, "appasswd"); - config->ssid_len = strlen("appasswd"); - config->channel = 1; - config->authmode = AUTH_WPA2_PSK; - config->ssid_hidden = 0; - config->max_connection = 4; - config->beacon_interval = 100; - return true; -} - -bool wifi_softap_get_config_default(struct softap_config *config) -{ - return wifi_softap_get_config(config); -} - -uint8 wifi_softap_get_station_num(void) -{ - return 2; -} - -bool wifi_softap_set_config(struct softap_config *config) -{ - (void)config; - return true; -} - -bool wifi_softap_set_config_current(struct softap_config *config) -{ - (void)config; - return true; -} - -bool wifi_softap_set_dhcps_lease(struct dhcps_lease *please) -{ - (void)please; - return true; -} - -bool wifi_softap_set_dhcps_lease_time(uint32 minute) -{ - (void)minute; - return true; -} - -bool wifi_softap_set_dhcps_offer_option(uint8 level, void* optarg) -{ - (void)level; - (void)optarg; - return true; -} - -bool wifi_station_scan(struct scan_config *config, scan_done_cb_t cb) -{ - (void)config; - cb(nullptr, FAIL); - return false; -} - -uint32_t core_version = 1; - -/////////////////////////////////////// -// not user_interface - -void ets_isr_mask (int intr) -{ - (void)intr; -} - -void ets_isr_unmask (int intr) -{ - (void)intr; -} - -void esp_schedule (void) -{ -} - -void dns_setserver (u8_t numdns, ip_addr_t *dnsserver) -{ - (void)numdns; - (void)dnsserver; -} - -ip_addr_t dns_getserver (u8_t numdns) -{ - (void)numdns; - ip_addr_t addr = { 0x7f000001 }; - return addr; -} + bool wifi_set_sleep_type(sleep_type_t type) + { + (void)type; + return true; + } + + bool wifi_station_connect(void) + { + return true; + } + + bool wifi_station_dhcpc_start(void) + { + return true; + } + + bool wifi_station_dhcpc_stop(void) + { + return true; + } + + bool wifi_station_disconnect(void) + { + return true; + } + + bool wifi_station_get_config_default(struct station_config *config) + { + return wifi_station_get_config(config); + } + + char wifi_station_get_hostname_str [128]; + const char* wifi_station_get_hostname(void) + { + return strcpy(wifi_station_get_hostname_str, "esposix"); + } + + bool wifi_station_get_reconnect_policy() + { + return true; + } + + sint8 wifi_station_get_rssi(void) + { + return 5; + } + + bool wifi_station_set_auto_connect(uint8 set) + { + return set != 0; + } + + bool wifi_station_set_config(struct station_config *config) + { + (void)config; + return true; + } + + bool wifi_station_set_config_current(struct station_config *config) + { + (void)config; + return true; + } + + bool wifi_station_set_hostname(const char *name) + { + (void)name; + return true; + } + + bool wifi_station_set_reconnect_policy(bool set) + { + (void)set; + return true; + } + + void system_phy_set_max_tpw(uint8 max_tpw) + { + (void)max_tpw; + } + + bool wifi_softap_dhcps_start(void) + { + return true; + } + + enum dhcp_status wifi_softap_dhcps_status(void) + { + return DHCP_STARTED; + } + + bool wifi_softap_dhcps_stop(void) + { + return true; + } + + bool wifi_softap_get_config(struct softap_config *config) + { + strcpy((char*)config->ssid, "apssid"); + strcpy((char*)config->password, "appasswd"); + config->ssid_len = strlen("appasswd"); + config->channel = 1; + config->authmode = AUTH_WPA2_PSK; + config->ssid_hidden = 0; + config->max_connection = 4; + config->beacon_interval = 100; + return true; + } + + bool wifi_softap_get_config_default(struct softap_config *config) + { + return wifi_softap_get_config(config); + } + + uint8 wifi_softap_get_station_num(void) + { + return 2; + } + + bool wifi_softap_set_config(struct softap_config *config) + { + (void)config; + return true; + } + + bool wifi_softap_set_config_current(struct softap_config *config) + { + (void)config; + return true; + } + + bool wifi_softap_set_dhcps_lease(struct dhcps_lease *please) + { + (void)please; + return true; + } + + bool wifi_softap_set_dhcps_lease_time(uint32 minute) + { + (void)minute; + return true; + } + + bool wifi_softap_set_dhcps_offer_option(uint8 level, void* optarg) + { + (void)level; + (void)optarg; + return true; + } + + bool wifi_station_scan(struct scan_config *config, scan_done_cb_t cb) + { + (void)config; + cb(nullptr, FAIL); + return false; + } + + uint32_t core_version = 1; + + /////////////////////////////////////// + // not user_interface + + void ets_isr_mask(int intr) + { + (void)intr; + } + + void ets_isr_unmask(int intr) + { + (void)intr; + } + + void esp_schedule(void) + { + } + + void dns_setserver(u8_t numdns, ip_addr_t *dnsserver) + { + (void)numdns; + (void)dnsserver; + } + + ip_addr_t dns_getserver(u8_t numdns) + { + (void)numdns; + ip_addr_t addr = { 0x7f000001 }; + return addr; + } #include -bool smartconfig_start (sc_callback_t cb, ...) -{ - //XXXFIXME ... -> ptr - cb(SC_STATUS_LINK, NULL); - return true; -} - -bool smartconfig_stop (void) -{ - return true; -} - -sleep_type_t wifi_fpm_get_sleep_type(void) -{ - return NONE_SLEEP_T; -} + bool smartconfig_start(sc_callback_t cb, ...) + { + //XXXFIXME ... -> ptr + cb(SC_STATUS_LINK, NULL); + return true; + } + + bool smartconfig_stop(void) + { + return true; + } + + sleep_type_t wifi_fpm_get_sleep_type(void) + { + return NONE_SLEEP_T; + } } // extern "C" From 3539444d41188fd038ab9d46e43b8ed04f4d9134 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Wed, 12 Aug 2020 19:06:20 +0200 Subject: [PATCH 080/152] fix multiple call to ::begin() --- cores/esp8266/LwipIntfCB.cpp | 1 + libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/cores/esp8266/LwipIntfCB.cpp b/cores/esp8266/LwipIntfCB.cpp index 4eb81cb6b8..c0de24a233 100644 --- a/cores/esp8266/LwipIntfCB.cpp +++ b/cores/esp8266/LwipIntfCB.cpp @@ -1,6 +1,7 @@ #include #include +#include #define NETIF_STATUS_CB_SIZE 3 diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 2592533212..d090984232 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -257,6 +257,9 @@ bool clsLEAMDNSHost::begin(const char* p_pcHostName, bool bResult = false; + if (m_pUDPContext) + close(); + bResult = (setHostName(p_pcHostName)) && (_joinMulticastGroups()) && (p_fnCallback ? setProbeResultCallback(p_fnCallback) : true) && From 468fa8c5498e20a75b60749a222f18de03e6c0ba Mon Sep 17 00:00:00 2001 From: david gauchard Date: Wed, 12 Aug 2020 19:34:02 +0200 Subject: [PATCH 081/152] ditto --- tests/host/common/ArduinoMain.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/host/common/ArduinoMain.cpp b/tests/host/common/ArduinoMain.cpp index bb91e76cd0..6372359681 100644 --- a/tests/host/common/ArduinoMain.cpp +++ b/tests/host/common/ArduinoMain.cpp @@ -145,7 +145,6 @@ void help (const char* argv0, int exitcode) "\t-1 - run loop once then exit (for host testing)\n" "\t-v - verbose\n" , argv0, MOCK_PORT_SHIFTER, argv0, spiffs_kb, littlefs_kb); ->>>>>>> master exit(exitcode); } From 057ceb5673a870f4d85ee932e061f7f8859ed98d Mon Sep 17 00:00:00 2001 From: david gauchard Date: Thu, 20 Aug 2020 15:17:54 +0200 Subject: [PATCH 082/152] enable LEA v1 by default (= no change to arduino master) --- libraries/ESP8266mDNS/src/ESP8266mDNS.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS.h b/libraries/ESP8266mDNS/src/ESP8266mDNS.h index 55b209f9bd..922a3c5f3f 100644 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS.h +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS.h @@ -54,10 +54,10 @@ using clsLEAMDNSHost = esp8266::experimental::clsLEAMDNSHost; #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) // Maps the implementation to use to the global namespace type -//using MDNSResponder = Legacy_MDNSResponder::MDNSResponder; // Legacy -//using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; // LEA +//using MDNSResponder = Legacy_MDNSResponder::MDNSResponder; // Legacy +using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; // LEA //using MDNSResponder = experimental::MDNSImplementation::clsLEAMDNSHost_Legacy; // LEAv2Compat -using MDNSResponder = clsLEAMDNSHost; // LEAv2 +//using MDNSResponder = clsLEAMDNSHost; // LEAv2 extern MDNSResponder MDNS; #endif From bd6b0eb25c01889983605de7a2aaed1e0e91b41f Mon Sep 17 00:00:00 2001 From: david gauchard Date: Thu, 20 Aug 2020 16:29:07 +0200 Subject: [PATCH 083/152] style and fixes for CI --- .../LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino | 4 +- .../mDNS_ServiceMonitor_v2.ino | 41 ++++++++++--------- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 2 + libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 2 +- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino index f9a6b86e50..662cbd0cb8 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino @@ -52,7 +52,7 @@ 2. Open 'ESP8266mDNS.h' and set LEAmDNS to default. */ -#define MDNS2_EXPERIMENTAL +#define NO_GLOBAL_MDNS // our MDNS is defined below #include #include @@ -233,7 +233,7 @@ void setup(void) { hMDNSService->setDynamicServiceTxtCallback(MDNSDynamicServiceTxtCallback); } else { // Change hostname, use '-' as divider between base name and index - MDNS.setHostName(MDNSResponder::indexDomainName(p_pcDomainName, "-", 0)); + MDNS.setHostName(clsLEAMDNSHost::indexDomainName(p_pcDomainName, "-", 0)); } })) { Serial.println("mDNS-AP started"); diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino index a1e05b333e..85c3902f14 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino @@ -1,7 +1,7 @@ /* ESP8266 mDNS Responder Service Monitor - This example demonstrates two features of the LEA MDNSResponder: + This example demonstrates two features of the LEA clsLEAMDNSHost: 1. The host and service domain negotiation process that ensures the uniqueness of the finally choosen host and service domain name. 2. The dynamic MDNS service lookup/query feature. @@ -44,18 +44,18 @@ #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: + Include the clsLEAMDNSHost (the library needs to be included also) + As LEA clsLEAMDNSHost is experimental in the ESP8266 environment currently, the + legacy clsLEAMDNSHost is defaulted in th include file. + There are two ways to access LEA clsLEAMDNSHost: 1. Prepend every declaration and call to global declarations or functions with the namespace, like: - 'LEAmDNS:MDNSResponder::hMDNSService hMDNSService;' + 'LEAmDNS:clsLEAMDNSHost::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. */ -#define MDNS2_EXPERIMENTAL +#define NO_GLOBAL_MDNS // our MDNS is defined below #include /* @@ -63,11 +63,12 @@ */ #define SERVICE_PORT 80 // HTTP port +clsLEAMDNSHost MDNS; // MDNS responder char* pcHostDomain = 0; // Negociated host domain bool bHostDomainConfirmed = false; // Flags the confirmation of the host domain -MDNSResponder::clsService* hMDNSService = 0; // The handle of the http service in the MDNS responder -MDNSResponder::clsQuery* hMDNSServiceQuery = 0; // The handle of the 'http.tcp' service query in the MDNS responder +clsLEAMDNSHost::clsService* hMDNSService = 0; // The handle of the http service in the MDNS responder +clsLEAMDNSHost::clsQuery* hMDNSServiceQuery = 0; // The handle of the 'http.tcp' service query in the MDNS responder const String cstrNoHTTPServices = "Currently no 'http.tcp' services in the local network!
      "; String strHTTPServices = cstrNoHTTPServices; @@ -90,28 +91,28 @@ bool setStationHostname(const char* p_pcHostname) { } -void MDNSServiceQueryCallback(const MDNSResponder::clsQuery& p_Query, - const MDNSResponder::clsQuery::clsAnswer& p_Answer, - MDNSResponder::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, +void MDNSServiceQueryCallback(const clsLEAMDNSHost::clsQuery& p_Query, + const clsLEAMDNSHost::clsQuery::clsAnswer& p_Answer, + clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, bool p_bSetContent) { (void)p_Query; String answerInfo; switch (p_QueryAnswerTypeFlags) { - case static_cast(MDNSResponder::clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain): + case static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain): answerInfo = "ServiceDomain " + String(p_Answer.m_ServiceDomain.c_str()); break; - case static_cast(MDNSResponder::clsQuery::clsAnswer::enuQueryAnswerType::HostDomainPort): + case static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::HostDomainPort): answerInfo = "HostDomainAndPort " + String(p_Answer.m_HostDomain.c_str()) + ":" + String(p_Answer.m_u16Port); break; - case static_cast(MDNSResponder::clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address): + case static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address): answerInfo = "IP4Address "; for (auto ip : p_Answer.m_IPv4Addresses) { answerInfo += "- " + ip->m_IPAddress.toString(); }; break; - case static_cast(MDNSResponder::clsQuery::clsAnswer::enuQueryAnswerType::Txts): + case static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Txts): answerInfo = "TXT "; for (auto kv : p_Answer.m_Txts.m_Txts) { answerInfo += "\nkv : " + String(kv->m_pcKey) + " : " + String(kv->m_pcValue); @@ -129,7 +130,7 @@ void MDNSServiceQueryCallback(const MDNSResponder::clsQuery& p_Query, Probe result callback for Services */ -void serviceProbeResult(MDNSResponder::clsService& p_rMDNSService, +void serviceProbeResult(clsLEAMDNSHost::clsService& p_rMDNSService, const char* p_pcInstanceName, bool p_bProbeResult) { (void)p_rMDNSService; @@ -143,7 +144,7 @@ void serviceProbeResult(MDNSResponder::clsService& p_rMDNSService, 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(). + restarted via p_pclsLEAMDNSHost->setHostname(). */ @@ -188,7 +189,7 @@ void hostProbeResult(clsLEAMDNSHost & p_rMDNSHost, String p_pcDomainName, bool p } } else { // Change hostname, use '-' as divider between base name and index - MDNS.setHostName(MDNSResponder::indexDomainName(p_pcDomainName.c_str(), "-", 0)); + MDNS.setHostName(clsLEAMDNSHost::indexDomainName(p_pcDomainName.c_str(), "-", 0)); } } @@ -270,7 +271,7 @@ void setup(void) { // Init the (currently empty) host domain string with 'esp8266' MDNS.begin("esp8266_v2"); /* - if ((!MDNSResponder::indexDomain(pcHostDomain, 0, "esp8266")) || + if ((!clsLEAMDNSHost::indexDomain(pcHostDomain, 0, "esp8266")) || (!MDNS.begin(pcHostDomain))) { Serial.println(" Error setting up MDNS responder!"); while (1) { // STOP diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index d090984232..5e4409a715 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -258,7 +258,9 @@ bool clsLEAMDNSHost::begin(const char* p_pcHostName, bool bResult = false; if (m_pUDPContext) + { close(); + } bResult = (setHostName(p_pcHostName)) && (_joinMulticastGroups()) && diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index 01fe26b33e..af9b48c353 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -525,7 +525,7 @@ bool clsLEAMDNSHost_Legacy::removeServiceTxt(const hMDNSService p_hService, ? 0 : (clsLEAMDNSHost::clsService*)m_HostInformations.front().m_HandleToPtr[p_hService]))) && ((pTxt = pService->findServiceTxt(p_pcKey))) - && (removeServiceTxt(p_hService, (const hMDNSTxt)pTxt))); + && (removeServiceTxt(p_hService, (hMDNSTxt)pTxt))); } /* From fd5d2771c676fcb4e23121d0a2cf65691be949cc Mon Sep 17 00:00:00 2001 From: david gauchard Date: Thu, 20 Aug 2020 16:39:51 +0200 Subject: [PATCH 084/152] remove lwIP-1.4 specifics --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 12 +------ .../ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 4 --- .../ESP8266mDNS/src/LEAmDNS2_Backbone.cpp | 31 ------------------- libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h | 18 ----------- 4 files changed, 1 insertion(+), 64 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 5e4409a715..d2997648d6 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -940,13 +940,7 @@ bool clsLEAMDNSHost::_joinMulticastGroups(void) } } - if ( -#if LWIP_VERSION_MAJOR == 1 - (ERR_OK == igmp_joingroup(&pNetIf->ip_addr, &multicast_addr_V4)) -#else - (ERR_OK == igmp_joingroup_netif(pNetIf, ip_2_ip4(&multicast_addr_V4))) -#endif - ) + if ((ERR_OK == igmp_joingroup_netif(pNetIf, ip_2_ip4(&multicast_addr_V4)))) { bResult = true; } @@ -987,11 +981,7 @@ bool clsLEAMDNSHost::_leaveMulticastGroups() // Leave multicast group(s) #ifdef MDNS_IPV4_SUPPORT ip_addr_t multicast_addr_V4 = DNS_MQUERY_IPV4_GROUP_INIT; -#if LWIP_VERSION_MAJOR == 1 - if (ERR_OK != igmp_leavegroup(ip_2_ip4(pNetIf->ip_addr), ip_2_ip4(&multicast_addr_V4))) -#else if (ERR_OK != igmp_leavegroup_netif(pNetIf, ip_2_ip4(&multicast_addr_V4))) -#endif { DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("\n"));); } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 3e4d455b6a..07bad03583 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -641,11 +641,7 @@ IPAddress clsLEAMDNSHost::_getResponderIPAddress(netif* pNetIf, enuIPProtocolTyp #ifdef MDNS_IPV4_SUPPORT if (enuIPProtocolType::V4 == p_IPProtocolType) { -#if LWIP_VERSION_MAJOR == 1 - ipResponder = ip_2_ip4(pNetIf->ip_addr); -#else ipResponder = netif_ip_addr4(pNetIf); -#endif } #endif #ifdef MDNS_IPV6_SUPPORT diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp index d3ae03ad1f..def32bdea7 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp @@ -256,37 +256,6 @@ const char* clsLEAMDNSHost::clsBackbone::_DH(void) const #endif -#if LWIP_VERSION_MAJOR == 1 - -/* - netif_get_by_index - - Extracted (and slightly changed) from: https://github.com/yarrick/lwip/blob/master/src/core/netif.c -*/ -extern "C" -struct netif* netif_get_by_index(u8_t idx) -{ - struct netif *netif; - - //LWIP_ASSERT_CORE_LOCKED(); - - if (idx != 0) // <- NETIF_NO_INDEX - { - for ((netif) = netif_list; (netif) != NULL; (netif) = (netif)->next) // <- NETIF_FOREACH(netif) - { - if (idx == netif_get_index(netif)) - { - return netif; /* found! */ - } - } - } - - return NULL; -} - -#endif // LWIP_VERSION_MAJOR == 1 - - } // namespace MDNSImplementation diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h b/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h index 0aa1712a81..a24cf62ba1 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_lwIPdefs.h @@ -26,24 +26,6 @@ #define LEAMDNS2_LWIPDEFS_H #include -#if LWIP_VERSION_MAJOR == 1 - -#include // DNS_RRTYPE_xxx - -// cherry pick from lwip1 dns.c/mdns.c source files: -#define DNS_MQUERY_PORT 5353 -#define DNS_MQUERY_IPV4_GROUP_INIT IPAddress(224,0,0,251) /* resolver1.opendns.com */ -#define DNS_RRCLASS_ANY 255 /* any class */ - -#ifdef __cplusplus -extern "C" -#endif -struct netif* netif_get_by_index(u8_t idx); - -#else // lwIP > 1 - #include // DNS_RRTYPE_xxx, DNS_MQUERY_PORT -#endif - #endif // LEAMDNS2_LWIPDEFS_H From 27225e792f694c6c54f09a8ecb0fb0b580625920 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 15:10:11 +0200 Subject: [PATCH 085/152] _P(PSTR()) --- .../ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp index d638bf209a..b94fb1f80b 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp @@ -171,17 +171,17 @@ const char* clsLEAMDNSHost::_RRType2Name(uint16_t p_u16RRType) const switch (p_u16RRType & (~0x8000)) // Topmost bit might carry 'cache flush' flag { #ifdef MDNS_IPV4_SUPPORT - case DNS_RRTYPE_A: strcpy(acRRName, "A"); break; + case DNS_RRTYPE_A: strcpy_P(acRRName, PSTR("A")); break; #endif - case DNS_RRTYPE_PTR: strcpy(acRRName, "PTR"); break; - case DNS_RRTYPE_TXT: strcpy(acRRName, "TXT"); break; + case DNS_RRTYPE_PTR: strcpy_P(acRRName, PSTR("PTR")); break; + case DNS_RRTYPE_TXT: strcpy_P(acRRName, PSTR("TXT")); break; #ifdef MDNS_IPV6_SUPPORT - case DNS_RRTYPE_AAAA: strcpy(acRRName, "AAAA"); break; + case DNS_RRTYPE_AAAA: strcpy_P(acRRName, PSTR("AAAA")); break; #endif - case DNS_RRTYPE_SRV: strcpy(acRRName, "SRV"); break; - case clsConsts::u8DNS_RRTYPE_NSEC: strcpy(acRRName, "NSEC"); break; - case DNS_RRTYPE_ANY: strcpy(acRRName, "ANY"); break; - default: sprintf(acRRName, "Unknown(0x%04X)", p_u16RRType); // MAX 15! + case DNS_RRTYPE_SRV: strcpy_P(acRRName, PSTR("SRV")); break; + case clsConsts::u8DNS_RRTYPE_NSEC: strcpy_P(acRRName, PSTR("NSEC")); break; + case DNS_RRTYPE_ANY: strcpy_P(acRRName, PSTR("ANY")); break; + default: sprintf_P(acRRName, PSTR("Unknown(0x%04X)", p_u16RRType); // MAX 15! } return acRRName; } @@ -198,11 +198,11 @@ const char* clsLEAMDNSHost::_RRClass2String(uint16_t p_u16RRClass, if (p_u16RRClass & 0x0001) { - strcat(acClassString, "IN "); // 3 + strcat_P(acClassString, PSTR("IN ")); // 3 } if (p_u16RRClass & 0x8000) { - strcat(acClassString, (p_bIsQuery ? "UNICAST " : "FLUSH ")); // 8/6 + strcat_P(acClassString, (p_bIsQuery ? PSTR("UNICAST ") : PSTR("FLUSH "))); // 8/6 } return acClassString; // 11 @@ -219,44 +219,44 @@ const char* clsLEAMDNSHost::_replyFlags2String(uint32_t p_u32ReplyFlags) const *acFlagsString = 0; if (p_u32ReplyFlags & static_cast(enuContentFlag::A)) { - strcat(acFlagsString, "A "); // 2 + strcat_P(acFlagsString, PSTR("A ")); // 2 } if (p_u32ReplyFlags & static_cast(enuContentFlag::PTR_IPv4)) { - strcat(acFlagsString, "PTR_IPv4 "); // 7 + strcat_P(acFlagsString, PSTR("PTR_IPv4 ")); // 7 } if (p_u32ReplyFlags & static_cast(enuContentFlag::PTR_IPv6)) { - strcat(acFlagsString, "PTR_IPv6 "); // 7 + strcat_P(acFlagsString, PSTR("PTR_IPv6 ")); // 7 } if (p_u32ReplyFlags & static_cast(enuContentFlag::AAAA)) { - strcat(acFlagsString, "AAAA "); // 5 + strcat_P(acFlagsString, PSTR("AAAA ")); // 5 } if (p_u32ReplyFlags & static_cast(enuContentFlag::PTR_TYPE)) { - strcat(acFlagsString, "PTR_TYPE "); // 9 + strcat_P(acFlagsString, PSTR("PTR_TYPE ")); // 9 } if (p_u32ReplyFlags & static_cast(enuContentFlag::PTR_NAME)) { - strcat(acFlagsString, "PTR_NAME "); // 9 + strcat_P(acFlagsString, PSTR("PTR_NAME ")); // 9 } if (p_u32ReplyFlags & static_cast(enuContentFlag::TXT)) { - strcat(acFlagsString, "TXT "); // 4 + strcat_P(acFlagsString, PSTR("TXT ")); // 4 } if (p_u32ReplyFlags & static_cast(enuContentFlag::SRV)) { - strcat(acFlagsString, "SRV "); // 4 + strcat_P(acFlagsString, PSTR("SRV ")); // 4 } if (p_u32ReplyFlags & static_cast(enuContentFlag::NSEC)) { - strcat(acFlagsString, "NSEC "); // 5 + strcat_P(acFlagsString, PSTR("NSEC ")); // 5 } if (0 == p_u32ReplyFlags) { - strcpy(acFlagsString, "none"); + strcpy_P(acFlagsString, PSTR("none")); } // Remove trailing spaces @@ -281,35 +281,35 @@ const char* clsLEAMDNSHost::_NSECBitmap2String(const clsNSECBitmap* p_pNSECBitma #ifdef MDNS_IPV4_SUPPORT if (p_pNSECBitmap->getBit(DNS_RRTYPE_A)) { - strcat(acFlagsString, "A "); // 2 + strcat_P(acFlagsString, PSTR("A ")); // 2 } #endif if (p_pNSECBitmap->getBit(DNS_RRTYPE_PTR)) { - strcat(acFlagsString, "PTR "); // 4 + strcat_P(acFlagsString, PSTR("PTR ")); // 4 } #ifdef MDNS_IPV6_SUPPORT if (p_pNSECBitmap->getBit(DNS_RRTYPE_AAAA)) { - strcat(acFlagsString, "AAAA "); // 5 + strcat_P(acFlagsString, PSTR("AAAA ")); // 5 } #endif if (p_pNSECBitmap->getBit(DNS_RRTYPE_TXT)) { - strcat(acFlagsString, "TXT "); // 4 + strcat_P(acFlagsString, PSTR("TXT ")); // 4 } if (p_pNSECBitmap->getBit(DNS_RRTYPE_SRV)) { - strcat(acFlagsString, "SRV "); // 4 + strcat_P(acFlagsString, PSTR("SRV ")); // 4 } if (p_pNSECBitmap->getBit(clsConsts::u8DNS_RRTYPE_NSEC)) { - strcat(acFlagsString, "NSEC "); // 5 + strcat_P(acFlagsString, PSTR("NSEC ")); // 5 } if (!*acFlagsString) { - strcpy(acFlagsString, "none"); + strcpy_P(acFlagsString, PSTR("none")); } return acFlagsString; // 31 From 32cb828143d722658cf384366e4b66bd61be0846 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 15:16:24 +0200 Subject: [PATCH 086/152] sprintf always -> strcpy once --- libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp index def32bdea7..852068df19 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp @@ -246,11 +246,11 @@ bool clsLEAMDNSHost::clsBackbone::_processUDPInput(void) */ const char* clsLEAMDNSHost::clsBackbone::_DH(void) const { - static char acBuffer[24]; - - *acBuffer = 0; - sprintf_P(acBuffer, PSTR("[mDNS::backbone]")); - + static char acBuffer[20] = { 0, }; + if (!acBuffer[0]) + { + strcpy_P(acBuffer, PSTR("[mDNS::backbone]")); + } return acBuffer; } From 6fede9be325706c0646b24a52a5e0036712e5ca2 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 15:18:02 +0200 Subject: [PATCH 087/152] comment --- .../examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino index 662cbd0cb8..4007979fa2 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino @@ -41,9 +41,9 @@ // uses API MDNSApiVersion::LEAv2 /* - Include the clsLEAMDNSHost (the library needs to be included also) + Include the clsLEAMDNSHost (the library needs also to be included) As LEA clsLEAMDNSHost is experimantal in the ESP8266 environment currently, the - legacy clsLEAMDNSHost is defaulted in th include file. + legacy clsLEAMDNSHost is defaulted in the include file. There are two ways to access LEA clsLEAMDNSHost: 1. Prepend every declaration and call to global declarations or functions with the namespace, like: 'LEAmDNS::clsLEAMDNSHost::hMDNSService hMDNSService;' From 91166a227906d56fd7dc056fde60e3cd71e4b2f8 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 15:18:50 +0200 Subject: [PATCH 088/152] fix message --- .../examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino index 4007979fa2..e71fd014a1 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino @@ -200,7 +200,7 @@ void setup(void) { nif->name[1], netif_get_index(nif)); })) { - Serial.println("Error: could not add useless informative callback\n"); + Serial.println("Error: could not add informative callback\n"); } WiFi.mode(WIFI_STA); From 57a2faf8e43408016c600f970f2e27209b52b6b7 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 15:23:13 +0200 Subject: [PATCH 089/152] fix host domain name and comment --- .../examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino | 2 +- .../LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino index e71fd014a1..58b111c4b1 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino @@ -222,7 +222,7 @@ void setup(void) { setClock(); // Setup MDNS responder - // Init the (currently empty) host domain string with 'esp8266' + // Init the (currently empty) host domain string with 'leamdnsv2' if (MDNS.begin("leamdnsv2", [](clsLEAMDNSHost & p_rMDNSHost, const char* p_pcDomainName, bool p_bProbeResult)->void { if (p_bProbeResult) { diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino index 85c3902f14..bc061ceced 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino @@ -268,8 +268,8 @@ void setup(void) { // Setup MDNS responders MDNS.setProbeResultCallback(hostProbeResult); - // Init the (currently empty) host domain string with 'esp8266' - MDNS.begin("esp8266_v2"); + // Init the (currently empty) host domain string with 'leamdnsv2' + MDNS.begin("leamdnsv2"); /* if ((!clsLEAMDNSHost::indexDomain(pcHostDomain, 0, "esp8266")) || (!MDNS.begin(pcHostDomain))) { From 4b0f6e7676fc211bd2df4833f59ae9005b3c53e1 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 15:57:26 +0200 Subject: [PATCH 090/152] add TODO --- .../mDNS_ServiceMonitor_v2.ino | 41 ++++--------------- 1 file changed, 8 insertions(+), 33 deletions(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino index bc061ceced..b14df03ba2 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino @@ -29,6 +29,8 @@ */ +// THIS IS A WORK IN PROGRESS: some TODOs need completion + #ifndef STASSID #define STASSID "ssid" #define STAPSK "psk" @@ -206,31 +208,9 @@ void handleHTTPRequest() { s += WiFi.hostname() + ".local at " + server.client().localIP().toString() + ""; s += "

      Local HTTP services are :

      "; s += "
        "; - /* - for (auto info : MDNS.answerInfo(hMDNSServiceQuery)) { - s += "
      1. "; - s += info.serviceDomain(); - if (info.hostDomainAvailable()) { - s += "
        Hostname: "; - s += String(info.hostDomain()); - s += (info.hostPortAvailable()) ? (":" + String(info.hostPort())) : ""; - } - if (info.IP4AddressAvailable()) { - s += "
        IP4:"; - for (auto ip : info.IP4Adresses()) { - s += " " + ip.toString(); - } - } - if (info.txtAvailable()) { - s += "
        TXT:
        "; - for (auto kv : info.keyValues()) { - s += "\t" + String(kv.first) + " : " + String(kv.second) + "
        "; - } - } - s += "
      2. "; - } - */ + // TODO: list services + s += "

      "; Serial.println("Sending 200"); @@ -245,6 +225,10 @@ void setup(void) { Serial.begin(115200); Serial.setDebugOutput(false); + Serial.println(""); + Serial.println("THIS IS A WORK IN PROGRESS: some TODOs need completion"); + Serial.println(""); + // Connect to WiFi network WiFi.mode(WIFI_AP_STA); WiFi.softAP(APSSID); @@ -270,15 +254,6 @@ void setup(void) { // Init the (currently empty) host domain string with 'leamdnsv2' MDNS.begin("leamdnsv2"); - /* - if ((!clsLEAMDNSHost::indexDomain(pcHostDomain, 0, "esp8266")) || - (!MDNS.begin(pcHostDomain))) { - Serial.println(" Error setting up MDNS responder!"); - while (1) { // STOP - delay(1000); - } - } - */ Serial.println("MDNS responder started"); // Start HTTP server From 0b1cbbab27742e0c871f676841d46ee954ab4f02 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 15:59:57 +0200 Subject: [PATCH 091/152] remove comment --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index d2997648d6..a85d495771 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -111,7 +111,6 @@ namespace experimental if no default is given, 'ESP8266' is used. */ -//static clsLEAMDNSHost::fnProbeResultCallback clsLEAMDNSHost::stProbeResultCallback = nullptr; From 07ca39513b9062dcf567633255b129804d61deee Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 16:03:14 +0200 Subject: [PATCH 092/152] use lower case esp8266 --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index a85d495771..fa84a3b18c 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -108,7 +108,7 @@ namespace experimental is incremented. If not, the delimiter and index '2' is added. If 'p_pcDomainName' is empty (==0), the given default name 'p_pcDefaultDomainName' is used, - if no default is given, 'ESP8266' is used. + if no default is given, 'esp8266' is used. */ @@ -161,7 +161,7 @@ const char* clsLEAMDNSHost::indexDomainName(const char* p_pcDomainName, else { // No given domain, use base or default - const char* cpcDefaultName = (p_pcDefaultDomainName ? : "ESP8266"); + const char* cpcDefaultName = (p_pcDefaultDomainName ? : "esp8266"); size_t stLength = strlen(cpcDefaultName) + 1; // '\0' strncpy(acResultDomainName, cpcDefaultName, stLength); } From 2839d592821d98cb3f7961ea63677402d1de585f Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 16:06:35 +0200 Subject: [PATCH 093/152] fix strncpy size --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index fa84a3b18c..2d6ac39f81 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -162,8 +162,7 @@ const char* clsLEAMDNSHost::indexDomainName(const char* p_pcDomainName, { // No given domain, use base or default const char* cpcDefaultName = (p_pcDefaultDomainName ? : "esp8266"); - size_t stLength = strlen(cpcDefaultName) + 1; // '\0' - strncpy(acResultDomainName, cpcDefaultName, stLength); + strncpy(acResultDomainName, cpcDefaultName, sizeof(acResultDomainName) - 1); } DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[mDNS] indexDomainName: From '%s' to '%s'\n"), (p_pcDomainName ? : ""), acResultDomainName);); return acResultDomainName; From eda2e0b333e11c284a4634104e6826c2a55129d9 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 16:16:21 +0200 Subject: [PATCH 094/152] astyle has issues with lambdas: rework logic to hide formatting issue --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 44 +++++++++++----------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 2d6ac39f81..9e3458b245 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -266,35 +266,35 @@ bool clsLEAMDNSHost::begin(const char* p_pcHostName, ((m_pUDPContext = _allocBackbone())) && (restart()); - if (bResult) + if (!bResult) { - if (!LwipIntf::stateUpCB([this](netif * nif) + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s begin: FAILED for '%s'!\n"), _DH(), (p_pcHostName ? : "-"));); + return false; + } + + bResult = LwipIntf::stateUpCB([this](netif * nif) { (void)nif; - // This called after a new interface appears: - // resend announces on all available interfaces. - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s a new interface %c%c/%d is up, restarting mDNS\n"), - _DH(), nif->name[0], nif->name[1], netif_get_index(nif));); - if (restart()) - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s restart: success!\n"), _DH())); - } - else - { - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s restart failed!\n"), _DH())); - } - // No need to react when an interface disappears, - // because mDNS always loop on all available interfaces. - })) + // This called after a new interface appears: + // resend announces on all available interfaces. + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s a new interface %c%c/%d is up, restarting mDNS\n"), + _DH(), nif->name[0], nif->name[1], netif_get_index(nif));); + if (restart()) { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s begin: could not add netif status callback\n"), _DH())); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s restart: success!\n"), _DH())); } - } - else + else + { + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s restart failed!\n"), _DH())); + } + // No need to react when an interface disappears, + // because mDNS always loop on all available interfaces. + }); + + if (!bResult) { - DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s begin: FAILED for '%s'!\n"), _DH(), (p_pcHostName ? : "-"));); + DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s begin: could not add netif status callback\n"), _DH())); } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s begin: %s to init with hostname %s!\n"), _DH(), (bResult ? "Succeeded" : "FAILED"), (p_pcHostName ? : "-"));); return bResult; } From 3c34fca178fc443a99dea0220c5b5033df2b43de Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 16:24:30 +0200 Subject: [PATCH 095/152] remove old code --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 9e3458b245..0b09181492 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -466,20 +466,12 @@ clsLEAMDNSHost::clsService* clsLEAMDNSHost::addService(const char* p_pcInstanceN */ bool clsLEAMDNSHost::removeService(clsLEAMDNSHost::clsService* p_pService) { - bool bResult = false; + bool bResult = true; if (p_pService && (m_Services.end() != std::find(m_Services.begin(), m_Services.end(), p_pService))) { - bResult = _announceService(*p_pService, false); - /* - for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) - if (netif_is_up(pNetIf) && - (_announceService(pNetIf, *p_pService, false))) - { - bResult = true; - } - */ + bResult = bResult && _announceService(*p_pService, false); } if (bResult) From 29a2a88190a373d9cc46e00891f5e3ddc99f604f Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 16:24:45 +0200 Subject: [PATCH 096/152] remove service on request --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 0b09181492..8714f15124 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -474,11 +474,9 @@ bool clsLEAMDNSHost::removeService(clsLEAMDNSHost::clsService* p_pService) bResult = bResult && _announceService(*p_pService, false); } - if (bResult) - { - m_Services.remove(p_pService); - delete p_pService; - } + m_Services.remove(p_pService); + delete p_pService; + DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _removeService: FAILED!\n"), _DH(p_pService));); return bResult; } From 357a3438c09bdbf99083f3f2acd9646d00c3789a Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 16:26:29 +0200 Subject: [PATCH 097/152] remove dead code --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 8714f15124..506e87113c 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -961,10 +961,6 @@ bool clsLEAMDNSHost::_leaveMulticastGroups() if (netif_is_up(pNetIf)) { bResult = true; - /* _resetProbeStatus(false); // Stop probing - _releaseQueries(); - _releaseServices(); - _releaseHostName();*/ // Leave multicast group(s) #ifdef MDNS_IPV4_SUPPORT From 01c1774e93e7a25e9e352c3763a293c00c2a8d2b Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 16:28:34 +0200 Subject: [PATCH 098/152] simplify use of tolower() --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 506e87113c..b690423aeb 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -1026,7 +1026,7 @@ bool clsLEAMDNSHost::_allocDomainName(const char* p_pcNewDomainName, size_t i = 0; for (; i < stLength; ++i) { - p_rpcDomainName[i] = (isupper(p_pcNewDomainName[i]) ? tolower(p_pcNewDomainName[i]) : p_pcNewDomainName[i]); + p_rpcDomainName[i] = tolower(p_pcNewDomainName[i]); } p_rpcDomainName[i] = 0; #else From 3b1447c608556bd1f2d8b9f2d209e952ac79fe35 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 16:31:09 +0200 Subject: [PATCH 099/152] strncpy: fix max len --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index b690423aeb..e0a77e87ba 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -1030,7 +1030,7 @@ bool clsLEAMDNSHost::_allocDomainName(const char* p_pcNewDomainName, } p_rpcDomainName[i] = 0; #else - strncpy(p_rpcDomainName, p_pcNewDomainName, (stLength + 1)); + strncpy(p_rpcDomainName, p_pcNewDomainName, stLength); #endif } } From a296be0c13dc7470141c2788747904dd5e45962d Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 16:40:53 +0200 Subject: [PATCH 100/152] knwon typo --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index 00e260cd76..48083933d3 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -1482,7 +1482,7 @@ bool clsLEAMDNSHost::_hasProbesWaitingForAnswers(void) const - (eg. esp8266.local) To allow 'tiebreaking' (see '_parseQuery'), the answers for these questions are delivered in - the 'knwon answers' section of the query. + the 'known answers' section of the query. Host domain: - A/AAAA (eg. esp8266.esp -> 192.168.2.120) @@ -1536,7 +1536,7 @@ bool clsLEAMDNSHost::_sendHostProbe() - (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. + the 'known answers' section of the query. Service domain: - SRV (eg. MyESP._http._tcp.local -> 5000 esp8266.local) - PTR NAME (eg. _http._tcp.local -> MyESP._http._tcp.local) (TODO: Check if needed, maybe TXT is better) From 174e20ecf3c76527c74aaec3c114b9c4a3becc9b Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 16:42:16 +0200 Subject: [PATCH 101/152] fix bResult logic --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index 48083933d3..4d5404721c 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -1585,7 +1585,7 @@ bool clsLEAMDNSHost::_sendServiceProbe(clsService& p_rService) */ bool clsLEAMDNSHost::_cancelProbingForHost(void) { - bool bResult = false; + bool bResult; m_ProbeInformation.clear(false); @@ -1594,7 +1594,7 @@ bool clsLEAMDNSHost::_cancelProbingForHost(void) for (clsService* pService : m_Services) { - bResult = _cancelProbingForService(*pService); + bResult = bResult && _cancelProbingForService(*pService); } return bResult; } From f3655f9cacc76595fe730dae96dfefee0f13cb9b Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 16:49:48 +0200 Subject: [PATCH 102/152] m_u8SentCount -> m_u32SentCount --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 4 +- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 4 +- .../ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 38 +++++++++---------- .../ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 8 ++-- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index e0a77e87ba..46950cc0ea 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -1267,7 +1267,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installServiceQuery( if (_sendQuery(*pMDNSQuery)) { - pMDNSQuery->m_u8SentCount = 1; + pMDNSQuery->m_u32SentCount = 1; pMDNSQuery->m_ResendTimeout.reset(clsConsts::u32DynamicQueryResendDelay); } else @@ -1296,7 +1296,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installDomainQuery( if (_sendQuery(*pQuery)) { - pQuery->m_u8SentCount = 1; + pQuery->m_u32SentCount = 1; pQuery->m_ResendTimeout.reset(clsConsts::u32DynamicQueryResendDelay); } else diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index 43120be709..97de2ff2a3 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -383,7 +383,7 @@ class clsLEAMDNSHost }; enuProbingStatus m_ProbingStatus; - uint8_t m_u8SentCount; // Used for probes and announcements + uint32_t m_u32SentCount; // Used for probes and announcements esp8266::polledTimeout::oneShot m_Timeout; // Used for probes and announcements bool m_bConflict; bool m_bTiebreakNeeded; @@ -1145,7 +1145,7 @@ class clsLEAMDNSHost QueryCallbackAnswerFn m_fnCallbackAnswer; QueryCallbackAccessorFn m_fnCallbackAccessor; bool m_bStaticQuery; - uint8_t m_u8SentCount; + uint32_t m_u32SentCount; esp8266::polledTimeout::oneShot m_ResendTimeout; bool m_bAwaitingAnswers; clsAnswer::list m_Answers; diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index 4d5404721c..3177bea50d 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -1315,14 +1315,14 @@ bool clsLEAMDNSHost::_updateProbeStatus() else if ((clsProbeInformation_Base::enuProbingStatus::InProgress == m_ProbeInformation.m_ProbingStatus) && // Probing AND (m_ProbeInformation.m_Timeout.expired())) // Time for next probe { - if (clsConsts::u32ProbeCount > m_ProbeInformation.m_u8SentCount) + if (clsConsts::u32ProbeCount > m_ProbeInformation.m_u32SentCount) { // Send next probe if ((bResult = _sendHostProbe())) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Did sent host probe for '%s.local'\n\n"), _DH(), (m_pcHostName ? : ""));); m_ProbeInformation.m_Timeout.reset(clsConsts::u32ProbeDelay); - ++m_ProbeInformation.m_u8SentCount; + ++m_ProbeInformation.m_u32SentCount; } } else @@ -1335,7 +1335,7 @@ bool clsLEAMDNSHost::_updateProbeStatus() _callHostProbeResultCallback(true); // Prepare to announce host - m_ProbeInformation.m_u8SentCount = 0; + m_ProbeInformation.m_u32SentCount = 0; m_ProbeInformation.m_Timeout.reset(clsConsts::u32AnnounceDelay); DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Prepared host announcing.\n\n"), _DH());); } @@ -1347,12 +1347,12 @@ bool clsLEAMDNSHost::_updateProbeStatus() if ((bResult = _announce(true, false))) { // Don't announce services here - ++m_ProbeInformation.m_u8SentCount; // 1.. + ++m_ProbeInformation.m_u32SentCount; // 1.. - if (clsConsts::u32AnnounceCount > m_ProbeInformation.m_u8SentCount) + if (clsConsts::u32AnnounceCount > m_ProbeInformation.m_u32SentCount) { - m_ProbeInformation.m_Timeout.reset(clsConsts::u32AnnounceDelay * pow(2, (m_ProbeInformation.m_u8SentCount - 1))); // 2^(0..) -> 1, 2, 4, ... - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Announcing host '%s.local' (%lu).\n\n"), _DH(), (m_pcHostName ? : ""), m_ProbeInformation.m_u8SentCount);); + m_ProbeInformation.m_Timeout.reset(clsConsts::u32AnnounceDelay * pow(2, (m_ProbeInformation.m_u32SentCount - 1))); // 2^(0..) -> 1, 2, 4, ... + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Announcing host '%s.local' (%lu).\n\n"), _DH(), (m_pcHostName ? : ""), m_ProbeInformation.m_u32SentCount);); } else { @@ -1380,14 +1380,14 @@ bool clsLEAMDNSHost::_updateProbeStatus() else if ((clsProbeInformation_Base::enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) && // Probing AND (pService->m_ProbeInformation.m_Timeout.expired())) // Time for next probe { - if (clsConsts::u32ProbeCount > pService->m_ProbeInformation.m_u8SentCount) + if (clsConsts::u32ProbeCount > pService->m_ProbeInformation.m_u32SentCount) { // Send next probe if ((bResult = _sendServiceProbe(*pService))) { - DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Did sent service probe for '%s' (%u)\n\n"), _DH(), _service2String(pService), (pService->m_ProbeInformation.m_u8SentCount + 1));); + DEBUG_EX_INFO2(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Did sent service probe for '%s' (%u)\n\n"), _DH(), _service2String(pService), (pService->m_ProbeInformation.m_u32SentCount + 1));); pService->m_ProbeInformation.m_Timeout.reset(clsConsts::u32ProbeDelay); - ++pService->m_ProbeInformation.m_u8SentCount; + ++pService->m_ProbeInformation.m_u32SentCount; } } else @@ -1400,7 +1400,7 @@ bool clsLEAMDNSHost::_updateProbeStatus() _callServiceProbeResultCallback(*pService, true); // Prepare to announce service - pService->m_ProbeInformation.m_u8SentCount = 0; + pService->m_ProbeInformation.m_u32SentCount = 0; pService->m_ProbeInformation.m_Timeout.reset(clsConsts::u32AnnounceDelay); DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Prepared service announcing.\n\n"), _DH());); } @@ -1412,12 +1412,12 @@ bool clsLEAMDNSHost::_updateProbeStatus() if ((bResult = _announceService(*pService))) { // Announce service - ++pService->m_ProbeInformation.m_u8SentCount; // 1.. + ++pService->m_ProbeInformation.m_u32SentCount; // 1.. - if (clsConsts::u32AnnounceCount > pService->m_ProbeInformation.m_u8SentCount) + if (clsConsts::u32AnnounceCount > pService->m_ProbeInformation.m_u32SentCount) { - pService->m_ProbeInformation.m_Timeout.reset(clsConsts::u32AnnounceDelay * pow(2, (pService->m_ProbeInformation.m_u8SentCount - 1))); // 2^(0..) -> 1, 2, 4, ... - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Announcing service '%s' (%lu)\n\n"), _DH(), _service2String(pService), pService->m_ProbeInformation.m_u8SentCount);); + pService->m_ProbeInformation.m_Timeout.reset(clsConsts::u32AnnounceDelay * pow(2, (pService->m_ProbeInformation.m_u32SentCount - 1))); // 2^(0..) -> 1, 2, 4, ... + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _updateProbeStatus: Announcing service '%s' (%lu)\n\n"), _DH(), _service2String(pService), pService->m_ProbeInformation.m_u32SentCount);); } else { @@ -1463,14 +1463,14 @@ bool clsLEAMDNSHost::_resetProbeStatus(bool p_bRestart /*= true*/) bool clsLEAMDNSHost::_hasProbesWaitingForAnswers(void) const { bool bResult = ((clsProbeInformation_Base::enuProbingStatus::InProgress == m_ProbeInformation.m_ProbingStatus) && // Probing - (0 < m_ProbeInformation.m_u8SentCount)); // And really probing + (0 < m_ProbeInformation.m_u32SentCount)); // And really probing for (clsService::list::const_iterator it = m_Services.cbegin(); ((!bResult) && (it != m_Services.cend())); it++) { clsService* pService = *it; bResult = ((clsProbeInformation_Base::enuProbingStatus::InProgress == pService->m_ProbeInformation.m_ProbingStatus) && // Probing - (0 < pService->m_ProbeInformation.m_u8SentCount)); // And really probing + (0 < pService->m_ProbeInformation.m_u32SentCount)); // And really probing } return bResult; } @@ -1801,8 +1801,8 @@ bool clsLEAMDNSHost::_checkQueryCache() if ((bResult = _sendQuery(*pQuery))) { // The re-query rate is increased to more than one hour (RFC 6762 5.2) - ++pQuery->m_u8SentCount; - uint32_t u32NewDelay = (clsConsts::u32DynamicQueryResendDelay * pow(2, std::min((pQuery->m_u8SentCount - 1), 12))); + ++pQuery->m_u32SentCount; + uint32_t u32NewDelay = (clsConsts::u32DynamicQueryResendDelay * pow(2, std::min((pQuery->m_u32SentCount - 1), (uint32_t)12))); DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _checkQueryCache: Next query in %u seconds!\n"), _DH(), (u32NewDelay));); pQuery->m_ResendTimeout.reset(u32NewDelay); } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp index 3cae0ca3bc..95bc34c322 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -702,7 +702,7 @@ bool clsLEAMDNSHost::clsServiceTxts::operator!=(const clsServiceTxts& p_Other) c */ clsLEAMDNSHost::clsProbeInformation_Base::clsProbeInformation_Base(void) : m_ProbingStatus(enuProbingStatus::WaitingForData), - m_u8SentCount(0), + m_u32SentCount(0), m_Timeout(std::numeric_limits::max()), m_bConflict(false), m_bTiebreakNeeded(false) @@ -715,7 +715,7 @@ clsLEAMDNSHost::clsProbeInformation_Base::clsProbeInformation_Base(void) bool clsLEAMDNSHost::clsProbeInformation_Base::clear(void) { m_ProbingStatus = enuProbingStatus::WaitingForData; - m_u8SentCount = 0; + m_u32SentCount = 0; m_Timeout.reset(std::numeric_limits::max()); m_bConflict = false; m_bTiebreakNeeded = false; @@ -3055,7 +3055,7 @@ clsLEAMDNSHost::clsQuery::clsQuery(const enuQueryType p_QueryType) m_fnCallbackAnswer(0), m_fnCallbackAccessor(0), m_bStaticQuery(false), - m_u8SentCount(0), + m_u32SentCount(0), m_ResendTimeout(std::numeric_limits::max()), m_bAwaitingAnswers(true) { @@ -3082,7 +3082,7 @@ bool clsLEAMDNSHost::clsQuery::clear(void) m_fnCallbackAnswer = 0; m_fnCallbackAccessor = 0; m_bStaticQuery = false; - m_u8SentCount = 0; + m_u32SentCount = 0; m_ResendTimeout.reset(std::numeric_limits::max()); m_bAwaitingAnswers = true; for (clsAnswer* pAnswer : m_Answers) From b8383e504145cb8f24b5e623ae1c9bfcb1ba4175 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 16:51:05 +0200 Subject: [PATCH 103/152] relavent -> relevant --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 2 +- libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index 3177bea50d..88356f40e1 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -2062,7 +2062,7 @@ bool clsLEAMDNSHost::_checkQueryCache() /* clsLEAmDNS2_Host::_replyMaskForHost - Determines the relavant host answers for the given question. + Determines the relevant host answers for the given question. - A question for the hostname (eg. esp8266.local) will result in an A/AAAA (eg. 192.168.2.129) reply. - A question for the reverse IP address (eg. 192-168.2.120.inarpa.arpa) will result in an PTR_IPv4 (eg. esp8266.local) reply. diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp index 0e46651d5f..03ade10c78 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Control.cpp @@ -1983,7 +1983,7 @@ bool MDNSResponder::_checkServiceQueryCache(void) /* MDNSResponder::_replyMaskForHost - Determines the relavant host answers for the given question. + Determines the relevant host answers for the given question. - A question for the hostname (eg. esp8266.local) will result in an A/AAAA (eg. 192.168.2.129) reply. - A question for the reverse IP address (eg. 192-168.2.120.inarpa.arpa) will result in an PTR_IP4 (eg. esp8266.local) reply. From bdcfa73c744689e035799ac3361d08e803538cbb Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 17:16:04 +0200 Subject: [PATCH 104/152] fix strncpy --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp index 95bc34c322..06bfa32039 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -547,12 +547,12 @@ bool clsLEAMDNSHost::clsServiceTxts::c_str(char* p_pcBuffer) { *pcCursor++ = ';'; } - strncpy(pcCursor, pTxt->m_pcKey, stLength); pcCursor[stLength] = 0; + strcpy(pcCursor, pTxt->m_pcKey); pcCursor += stLength; *pcCursor++ = '='; if ((stLength = (pTxt->m_pcValue ? strlen(pTxt->m_pcValue) : 0))) { - strncpy(pcCursor, pTxt->m_pcValue, stLength); pcCursor[stLength] = 0; + strcpy(pcCursor, pTxt->m_pcValue); pcCursor += stLength; } } @@ -561,7 +561,6 @@ bool clsLEAMDNSHost::clsServiceTxts::c_str(char* p_pcBuffer) break; } } - *pcCursor++ = 0; } return bResult; } From 69834c1b14b6b041586f22bb3d1610f5ab3b20d4 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 17:20:34 +0200 Subject: [PATCH 105/152] memcpy->strcpy to always have the ending \0 --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp index 06bfa32039..640cc09978 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -593,7 +593,7 @@ size_t clsLEAMDNSHost::clsServiceTxts::bufferLength(void) const } /* - clsLEAMDNSHost::clsServiceTxts::toBuffer + clsLEAMDNSHost::clsServiceTxts::buffer */ bool clsLEAMDNSHost::clsServiceTxts::buffer(char* p_pcBuffer) @@ -611,12 +611,12 @@ bool clsLEAMDNSHost::clsServiceTxts::buffer(char* p_pcBuffer) size_t stLength; if ((bResult = (0 != (stLength = (pTxt->m_pcKey ? strlen(pTxt->m_pcKey) : 0))))) { - memcpy(p_pcBuffer, pTxt->m_pcKey, stLength); + strcpy(p_pcBuffer, pTxt->m_pcKey); p_pcBuffer += stLength; *p_pcBuffer++ = '='; if ((stLength = (pTxt->m_pcValue ? strlen(pTxt->m_pcValue) : 0))) { - memcpy(p_pcBuffer, pTxt->m_pcValue, stLength); + strcpy(p_pcBuffer, pTxt->m_pcValue); p_pcBuffer += stLength; } } @@ -625,7 +625,6 @@ bool clsLEAMDNSHost::clsServiceTxts::buffer(char* p_pcBuffer) break; } } - *p_pcBuffer++ = 0; } return bResult; } From 42b05ce04cc5b41b38485b3fce1ca1093593e608 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 17:28:16 +0200 Subject: [PATCH 106/152] remove redundant strcmp --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp index 640cc09978..d90b8af28e 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -659,8 +659,8 @@ bool clsLEAMDNSHost::clsServiceTxts::compare(const clsLEAMDNSHost::clsServiceTxt if (!((bResult = ((pTxt) && (pOtherTxt->m_pcValue) && (pTxt->m_pcValue) && - (strlen(pOtherTxt->m_pcValue) == strlen(pTxt->m_pcValue)) && - (0 == strcmp(pOtherTxt->m_pcValue, pTxt->m_pcValue)))))) + (strlen(pOtherTxt->m_pcValue) == strlen(pTxt->m_pcValue)) /* && + redundant: (0 == strcmp(pOtherTxt->m_pcValue, pTxt->m_pcValue)) */ )))) { break; } From ab2d4372576d7dc3de186a0c115927557ff17f1d Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 17:30:19 +0200 Subject: [PATCH 107/152] simplify fix strcpy --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp index d90b8af28e..af1b1271fe 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -830,9 +830,7 @@ bool clsLEAMDNSHost::clsService::setInstanceName(const char* p_pcInstanceName) { if ((bResult = (0 != (m_pcInstanceName = new char[stLength + 1])))) { - strncpy(m_pcInstanceName, p_pcInstanceName, stLength); - m_pcInstanceName[stLength] = 0; - + strcpy(m_pcInstanceName, p_pcInstanceName); _resetProbeStatus(); } } From 60f6e157bae15b49fa2e02796c8a349efc063a20 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 17:31:56 +0200 Subject: [PATCH 108/152] ditto --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp index af1b1271fe..e8d81a0873 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -890,9 +890,7 @@ bool clsLEAMDNSHost::clsService::setType(const char* p_pcType) { if ((bResult = (0 != (m_pcType = new char[stLength + 1])))) { - strncpy(m_pcType, p_pcType, stLength); - m_pcType[stLength] = 0; - + strcpy(m_pcType, p_pcType); _resetProbeStatus(); } } From 096094f8e8d8e28cde696db9e5aaaecf7128ec1f Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 17:32:38 +0200 Subject: [PATCH 109/152] ditto --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp index e8d81a0873..b1cf849563 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -935,9 +935,7 @@ bool clsLEAMDNSHost::clsService::setProtocol(const char* p_pcProtocol) { if ((bResult = (0 != (m_pcProtocol = new char[stLength + 1])))) { - strncpy(m_pcProtocol, p_pcProtocol, stLength); - m_pcProtocol[stLength] = 0; - + strcpy(m_pcProtocol, p_pcProtocol); _resetProbeStatus(); } } From ee087150d84b7d38ad70463efe9e8d7941440bb0 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 17:37:48 +0200 Subject: [PATCH 110/152] remove useless overloads --- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 12 ---- .../ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 61 ------------------- 2 files changed, 73 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index 97de2ff2a3..b5cf75b86c 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -477,21 +477,9 @@ class clsLEAMDNSHost clsServiceTxt* _addServiceTxt(const char* p_pcKey, uint32_t p_u32Value, bool p_bTemp); - clsServiceTxt* _addServiceTxt(const char* p_pcKey, - uint16_t p_u16Value, - bool p_bTemp); - clsServiceTxt* _addServiceTxt(const char* p_pcKey, - uint8_t p_u8Value, - bool p_bTemp); clsServiceTxt* _addServiceTxt(const char* p_pcKey, int32_t p_i32Value, bool p_bTemp); - clsServiceTxt* _addServiceTxt(const char* p_pcKey, - int16_t p_i16Value, - bool p_bTemp); - clsServiceTxt* _addServiceTxt(const char* p_pcKey, - int8_t p_i8Value, - bool p_bTemp); public: bool setInstanceName(const char* p_pcInstanceName); diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp index b1cf849563..f06882f21e 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -1252,36 +1252,6 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const return _addServiceTxt(p_pcKey, acValueBuffer, p_bTemp); } -/* - clsLEAMDNSHost::clsService::_addServiceTxt (uint16_t) - -*/ -clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, - uint16_t p_u16Value, - bool p_bTemp) -{ - char acValueBuffer[8]; // 16-bit max 5 digits - *acValueBuffer = 0; - sprintf(acValueBuffer, "%hu", p_u16Value); - - return _addServiceTxt(p_pcKey, acValueBuffer, p_bTemp); -} - -/* - clsLEAMDNSHost::clsService::_addServiceTxt (uint8_t) - -*/ -clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, - uint8_t p_u8Value, - bool p_bTemp) -{ - char acValueBuffer[8]; // 8-bit max 3 digits - *acValueBuffer = 0; - sprintf(acValueBuffer, "%hhu", p_u8Value); - - return _addServiceTxt(p_pcKey, acValueBuffer, p_bTemp); -} - /* clsLEAMDNSHost::clsService::_addServiceTxt (int32_t) @@ -1297,37 +1267,6 @@ clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const return _addServiceTxt(p_pcKey, acValueBuffer, p_bTemp); } -/* - clsLEAMDNSHost::clsService::_addServiceTxt (int16_t) - -*/ -clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, - int16_t p_i16Value, - bool p_bTemp) -{ - char acValueBuffer[8]; // 16-bit max 5 digits - *acValueBuffer = 0; - sprintf(acValueBuffer, "%hi", p_i16Value); - - return _addServiceTxt(p_pcKey, acValueBuffer, p_bTemp); -} - -/* - clsLEAMDNSHost::clsService::_addServiceTxt (int8_t) - -*/ -clsLEAMDNSHost::clsServiceTxt* clsLEAMDNSHost::clsService::_addServiceTxt(const char* p_pcKey, - int8_t p_i8Value, - bool p_bTemp) -{ - char acValueBuffer[8]; // 8-bit max 3 digits - *acValueBuffer = 0; - sprintf(acValueBuffer, "%hhi", p_i8Value); - - return _addServiceTxt(p_pcKey, acValueBuffer, p_bTemp); -} - - /** clsLEAMDNSHost::clsMsgHeader From c86b2d84046e342d79e6902d1e7d32bccd9baa76 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 17:41:12 +0200 Subject: [PATCH 111/152] fix strncpy -> strcpy --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp index f06882f21e..b22d9fc5e7 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -1411,7 +1411,7 @@ bool clsLEAMDNSHost::clsRRDomain::addLabel(const char* p_pcLabel, m_acName[m_u16NameLength++] = '_'; --stLength; } - strncpy(&(m_acName[m_u16NameLength]), p_pcLabel, stLength); m_acName[m_u16NameLength + stLength] = 0; + strcpy(&(m_acName[m_u16NameLength]), p_pcLabel); m_u16NameLength += stLength; } bResult = clearNameCache(); From 5c3f74655a27040d0a7bfd0d053f713328a7f4a0 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 17:43:02 +0200 Subject: [PATCH 112/152] lenght typo --- libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp | 6 +++--- libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 2 +- libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp b/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp index f7fe8c9030..be5666df7d 100644 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp @@ -411,11 +411,11 @@ int MDNSResponder::queryService(char *service, char *proto) // Only supports sending one PTR query // Send the Name field (eg. "_http._tcp.local") - _conn->append(reinterpret_cast(&serviceNameLen), 1); // lenght of "_" + service + _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_" + service _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_" + service - _conn->append(reinterpret_cast(&protoNameLen), 1); // lenght of "_" + proto + _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_" + proto _conn->append(reinterpret_cast(protoName), protoNameLen); // "_" + proto - _conn->append(reinterpret_cast(&localNameLen), 1); // lenght of "local" + _conn->append(reinterpret_cast(&localNameLen), 1); // length of "local" _conn->append(reinterpret_cast(localName), localNameLen); // "local" _conn->append(reinterpret_cast(&terminator), 1); // terminator diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp index b22d9fc5e7..5b89c8f895 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -1438,7 +1438,7 @@ bool clsLEAMDNSHost::clsRRDomain::compare(const clsRRDomain& p_Other) const { if (*((unsigned char*)pT)) // Not 0 { - pT += (1 + * ((unsigned char*)pT)); // Shift by length byte and lenght + pT += (1 + * ((unsigned char*)pT)); // Shift by length byte and length pO += (1 + * ((unsigned char*)pO)); } else // Is 0 -> Successfully reached the end diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp index 18c4a95eb3..8f0d2f7cf2 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Structs.cpp @@ -788,7 +788,7 @@ bool MDNSResponder::stcMDNS_RRDomain::compare(const stcMDNS_RRDomain& p_Other) c { if (*((unsigned char*)pT)) // Not 0 { - pT += (1 + * ((unsigned char*)pT)); // Shift by length byte and lenght + pT += (1 + * ((unsigned char*)pT)); // Shift by length byte and length pO += (1 + * ((unsigned char*)pO)); } else // Is 0 -> Successfully reached the end From 2270b839eb8f116c4d94a36bf6c0490367748ae9 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 17:51:02 +0200 Subject: [PATCH 113/152] rework debug macros not nicely dealt by astyle --- .../ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 07bad03583..dbc422f400 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -767,11 +767,10 @@ bool clsLEAMDNSHost::_readRRAnswer(clsLEAMDNSHost::clsRRAnswer*& p_rpRRAnswer) bResult = _readRRAnswerGeneric(*(clsRRAnswerGeneric*&)p_rpRRAnswer, u16RDLength); break; } - DEBUG_EX_INFO( - if ((bResult) && - (p_rpRRAnswer)) - { - DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswer: "), _DH()); + + DEBUG_EX_INFO_IF((bResult) && (p_rpRRAnswer), + { + DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswer: "), _DH()); _printRRDomain(p_rpRRAnswer->m_Header.m_Domain); DEBUG_OUTPUT.printf_P(PSTR(" Type:%s Class:0x%04X TTL:%u, RDLength:%u "), _RRType2Name(p_rpRRAnswer->m_Header.m_Attributes.m_u16Type), @@ -826,12 +825,12 @@ bool clsLEAMDNSHost::_readRRAnswer(clsLEAMDNSHost::clsRRAnswer*& p_rpRRAnswer) break; } DEBUG_OUTPUT.printf_P(PSTR("\n")); - } - else + }); // DEBUG_EX_INFO + + DEBUG_EX_INFO_IF(!((bResult) && (p_rpRRAnswer)), { DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswer: FAILED to read specific answer of type 0x%04X!\n"), _DH(), p_rpRRAnswer->m_Header.m_Attributes.m_u16Type); - } - ); // DEBUG_EX_INFO + }); } DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswer: FAILED!\n"), _DH());); return bResult; From 5c7808afecd34d5426ae35d1e31c2144515df95f Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 17:53:57 +0200 Subject: [PATCH 114/152] clarify comment --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index dbc422f400..6bbf16a4f8 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -1073,7 +1073,7 @@ bool clsLEAMDNSHost::_readRRDomain(clsLEAMDNSHost::clsRRDomain& p_rRRDomain) 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. + the maximum recursion depth is set by clsConsts::u8DomainMaxRedirections. */ bool clsLEAMDNSHost::_readRRDomain_Loop(clsLEAMDNSHost::clsRRDomain& p_rRRDomain, From 37d0300c1e6926394071b6cdfe25b5a6ba03a960 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 17:58:21 +0200 Subject: [PATCH 115/152] align comment --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 6bbf16a4f8..5bc115ec53 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -1503,7 +1503,7 @@ bool clsLEAMDNSHost::_udpDump(unsigned p_uOffset, MDNSResponder::_readMDNSMsgHeader Read a MDNS header from the UDP input buffer. - | 8 | 8 | 8 | 8 | + | 8 | 8 | 8 | 8 | 00| Identifier | Flags & Codes | 01| Question count | Answer count | 02| NS answer count | Ad answer count | From 05b1581761af33963944c738a1b349f6a5eb5304 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 17:59:52 +0200 Subject: [PATCH 116/152] typo: form -> from --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 5bc115ec53..7d81762fcb 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -1508,7 +1508,7 @@ bool clsLEAMDNSHost::_udpDump(unsigned p_uOffset, 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) + All 16-bit and 32-bit elements need to be translated from network coding to host coding (done in _udpRead16 and _udpRead32) In addition, bitfield memory order is undefined in C standard (GCC doesn't order them in the coded direction...), so they need some mapping here @@ -1591,7 +1591,7 @@ bool clsLEAMDNSHost::_write32(uint32_t p_u32Value, 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) + All 16-bit and 32-bit elements need to be translated from host coding to network coding (done in _udpAppend16 and _udpAppend32) In addition, bitfield memory order is undefined in C standard (GCC doesn't order them in the coded direction...), so they need some mapping here From 3431a01ab302e42d925adf25ca88d980b25617d7 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 18:02:18 +0200 Subject: [PATCH 117/152] typo: supportet -> supported --- libraries/ESP8266mDNS/src/ESP8266mDNS.h | 2 +- libraries/ESP8266mDNS/src/LEAmDNS.h | 2 +- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 2 +- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS.h b/libraries/ESP8266mDNS/src/ESP8266mDNS.h index 922a3c5f3f..fd09760410 100644 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS.h +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS.h @@ -10,7 +10,7 @@ - Presenting a DNS-SD service to interested observers, eg. a http server by presenting _http._tcp service - Support for multi-level compressed names in input; in output only a very simple one-leven full-name compression is implemented - Probing host and service domains for uniqueness in the local network - - Tiebreaking while probing is supportet in a very minimalistic way (the 'higher' IP address wins the tiebreak) + - Tiebreaking while probing is supported in a very minimalistic way (the 'higher' IP address wins the tiebreak) - Announcing available services after successful probing - Using fixed service TXT items or - Using dynamic service TXT items for presented services (via callback) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS.h b/libraries/ESP8266mDNS/src/LEAmDNS.h index e223002abb..6037f0d13f 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS.h @@ -14,7 +14,7 @@ - Presenting a DNS-SD service to interested observers, eg. a http server by presenting _http._tcp service - Support for multi-level compressed names in input; in output only a very simple one-leven full-name compression is implemented - Probing host and service domains for uniqueness in the local network - - Tiebreaking while probing is supportet in a very minimalistic way (the 'higher' IP address wins the tiebreak) + - Tiebreaking while probing is supported in a very minimalistic way (the 'higher' IP address wins the tiebreak) - Announcing available services after successful probing - Using fixed service TXT items or - Using dynamic service TXT items for presented services (via callback) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index b5cf75b86c..5f1473ffe9 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -10,7 +10,7 @@ - Announcing a DNS-SD service to interested observers, eg. a http server by announcing a esp8266._http._tcp.local. service - Support for multi-level compressed names in input; in output only a very simple one-leven full-name compression is implemented - Probing host and service domains for uniqueness in the local network - - Tiebreaking while probing is supportet in a very minimalistic way (the 'higher' IP address wins the tiebreak) + - Tiebreaking while probing is supported in a very minimalistic way (the 'higher' IP address wins the tiebreak) - Announcing available services after successful probing - Using fixed service TXT items or - Using dynamic service TXT items for presented services (via callback) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h index b0ace4d371..2967c8a0c8 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h @@ -14,7 +14,7 @@ - Presenting a DNS-SD service to interested observers, eg. a http server by presenting _http._tcp service - Support for multi-level compressed names in input; in output only a very simple one-leven full-name compression is implemented - Probing host and service domains for uniqueness in the local network - - Tiebreaking while probing is supportet in a very minimalistic way (the 'higher' IP address wins the tiebreak) + - Tiebreaking while probing is supported in a very minimalistic way (the 'higher' IP address wins the tiebreak) - Announcing available services after successful probing - Using fixed service TXT items or - Using dynamic service TXT items for presented services (via callback) From 04a01f0a2079e976518f083b8f67041102702a70 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 18:16:08 +0200 Subject: [PATCH 118/152] style --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp index 5b89c8f895..913665deae 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -660,7 +660,7 @@ bool clsLEAMDNSHost::clsServiceTxts::compare(const clsLEAMDNSHost::clsServiceTxt (pOtherTxt->m_pcValue) && (pTxt->m_pcValue) && (strlen(pOtherTxt->m_pcValue) == strlen(pTxt->m_pcValue)) /* && - redundant: (0 == strcmp(pOtherTxt->m_pcValue, pTxt->m_pcValue)) */ )))) + redundant: (0 == strcmp(pOtherTxt->m_pcValue, pTxt->m_pcValue)) */)))) { break; } From 16504b2c339abf4c18babeabc6a96dd3cdd9e2a0 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 23 Aug 2020 18:31:47 +0200 Subject: [PATCH 119/152] fix typo in debug mode --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp index b94fb1f80b..186d913a38 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Debug.cpp @@ -181,7 +181,7 @@ const char* clsLEAMDNSHost::_RRType2Name(uint16_t p_u16RRType) const case DNS_RRTYPE_SRV: strcpy_P(acRRName, PSTR("SRV")); break; case clsConsts::u8DNS_RRTYPE_NSEC: strcpy_P(acRRName, PSTR("NSEC")); break; case DNS_RRTYPE_ANY: strcpy_P(acRRName, PSTR("ANY")); break; - default: sprintf_P(acRRName, PSTR("Unknown(0x%04X)", p_u16RRType); // MAX 15! + default: sprintf_P(acRRName, PSTR("Unknown(0x%04X"), p_u16RRType); // MAX 15! } return acRRName; } From 21a9096f8f006e5897752e5e2d46da782a2cb0f3 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Wed, 26 Aug 2020 16:41:52 +0200 Subject: [PATCH 120/152] simplification --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp index 88356f40e1..c3b79c02b2 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Control.cpp @@ -1451,7 +1451,7 @@ bool clsLEAMDNSHost::_resetProbeStatus(bool p_bRestart /*= true*/) for (clsService* pService : m_Services) { pService->m_ProbeInformation.clear(false); - pService->m_ProbeInformation.m_ProbingStatus = (p_bRestart ? clsProbeInformation_Base::enuProbingStatus::ReadyToStart : clsProbeInformation_Base::enuProbingStatus::DoneFinally); + pService->m_ProbeInformation.m_ProbingStatus = m_ProbeInformation.m_ProbingStatus; } return true; } From bae56127f250ea29225479a6045bfa416f867bb7 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Wed, 26 Aug 2020 16:44:48 +0200 Subject: [PATCH 121/152] add missing MIT licenses --- libraries/ESP8266mDNS/src/ESP8266mDNS.cpp | 22 +++++++++++++++++++ libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 18 +++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS.cpp b/libraries/ESP8266mDNS/src/ESP8266mDNS.cpp index c784a0d790..b5d7aac277 100644 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS.cpp +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS.cpp @@ -1,3 +1,25 @@ +/* + + License (MIT license): + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ #include /* diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index af9b48c353..1aa900dda3 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -1,6 +1,24 @@ /* LEAmDNS2_Legacy.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. */ From dd4c44f70a6226a84e4f66cd322833f3b60f8978 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Wed, 26 Aug 2020 16:49:30 +0200 Subject: [PATCH 122/152] fix previous fix from review: comparing with strlen is superfluous when strings are strcmp anyway --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp index 913665deae..7533b07ec8 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -646,7 +646,6 @@ bool clsLEAMDNSHost::clsServiceTxts::compare(const clsLEAMDNSHost::clsServiceTxt if (!((bResult = ((pOtherTxt) && (pTxt->m_pcValue) && (pOtherTxt->m_pcValue) && - (strlen(pTxt->m_pcValue) == strlen(pOtherTxt->m_pcValue)) && (0 == strcmp(pTxt->m_pcValue, pOtherTxt->m_pcValue)))))) { break; @@ -659,8 +658,7 @@ bool clsLEAMDNSHost::clsServiceTxts::compare(const clsLEAMDNSHost::clsServiceTxt if (!((bResult = ((pTxt) && (pOtherTxt->m_pcValue) && (pTxt->m_pcValue) && - (strlen(pOtherTxt->m_pcValue) == strlen(pTxt->m_pcValue)) /* && - redundant: (0 == strcmp(pOtherTxt->m_pcValue, pTxt->m_pcValue)) */)))) + (0 == strcmp(pOtherTxt->m_pcValue, pTxt->m_pcValue)))))) { break; } From 2741d41f57a12a8126fcf24311f52c30d7e9f867 Mon Sep 17 00:00:00 2001 From: "TAURI20\\Herman" Date: Sun, 13 Sep 2020 12:33:39 +0200 Subject: [PATCH 123/152] Revert "strncpy: fix max len" This reverts commit 3b1447c608556bd1f2d8b9f2d209e952ac79fe35. --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 46950cc0ea..3906dce484 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -1030,7 +1030,7 @@ bool clsLEAMDNSHost::_allocDomainName(const char* p_pcNewDomainName, } p_rpcDomainName[i] = 0; #else - strncpy(p_rpcDomainName, p_pcNewDomainName, stLength); + strncpy(p_rpcDomainName, p_pcNewDomainName, (stLength + 1)); #endif } } From 79931081b4d4a2ce0c59fe6d9de6f11ba0f6ad4c Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 09:43:25 +0200 Subject: [PATCH 124/152] fix more strncpy --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 2 +- libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp | 4 ++-- libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 3906dce484..d3bb7a7544 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -162,7 +162,7 @@ const char* clsLEAMDNSHost::indexDomainName(const char* p_pcDomainName, { // No given domain, use base or default const char* cpcDefaultName = (p_pcDefaultDomainName ? : "esp8266"); - strncpy(acResultDomainName, cpcDefaultName, sizeof(acResultDomainName) - 1); + strncpy(acResultDomainName, cpcDefaultName, sizeof(acResultDomainName)); } DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[mDNS] indexDomainName: From '%s' to '%s'\n"), (p_pcDomainName ? : ""), acResultDomainName);); return acResultDomainName; diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp index 7533b07ec8..dcf3c4b76e 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Structs.cpp @@ -139,7 +139,7 @@ bool clsLEAMDNSHost::clsServiceTxt::setKey(const char* p_pcKey, { if (allocKey(p_stLength)) { - strncpy(m_pcKey, p_pcKey, p_stLength); + strncpy(m_pcKey, p_pcKey, p_stLength + 1); m_pcKey[p_stLength] = 0; bResult = true; } @@ -198,7 +198,7 @@ bool clsLEAMDNSHost::clsServiceTxt::setValue(const char* p_pcValue, { if (allocValue(p_stLength)) { - strncpy(m_pcValue, p_pcValue, p_stLength); + strncpy(m_pcValue, p_pcValue, p_stLength + 1); m_pcValue[p_stLength] = 0; bResult = true; } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 7d81762fcb..38f10b1db1 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -898,9 +898,11 @@ bool clsLEAMDNSHost::_readRRAnswerTXT(clsLEAMDNSHost::clsRRAnswerTXT& p_rRRAnswe if (ucLength) { DEBUG_EX_INFO( - static char sacBuffer[64]; *sacBuffer = 0; + 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; + os_strncpy(sacBuffer, (const char*)pucCursor, u8MaxLength + 1); + sacBuffer[u8MaxLength] = 0; DEBUG_OUTPUT.printf_P(PSTR("%s _readRRAnswerTXT: Item(%u): %s\n"), _DH(), ucLength, sacBuffer); ); From 60f74561a475e10f2a3cb0340b84bf77d4f6c944 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 09:53:36 +0200 Subject: [PATCH 125/152] comments for lwIPIntf-callback --- cores/esp8266/LwipIntf.h | 2 +- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/LwipIntf.h b/cores/esp8266/LwipIntf.h index cbf2927315..ba31e912e6 100644 --- a/cores/esp8266/LwipIntf.h +++ b/cores/esp8266/LwipIntf.h @@ -10,7 +10,7 @@ class LwipIntf { private: - LwipIntf () { } + LwipIntf () { } // private, cannot be directly allocated protected: diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index d3bb7a7544..971b764643 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -275,7 +275,7 @@ bool clsLEAMDNSHost::begin(const char* p_pcHostName, bResult = LwipIntf::stateUpCB([this](netif * nif) { (void)nif; - // This called after a new interface appears: + // This is called when a new interface appears: // resend announces on all available interfaces. DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s a new interface %c%c/%d is up, restarting mDNS\n"), _DH(), nif->name[0], nif->name[1], netif_get_index(nif));); From 99de12ab3071970fba01ac91bb25c09b6caba60a Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 10:01:01 +0200 Subject: [PATCH 126/152] using type alias LwipIntf::CBType --- cores/esp8266/LwipIntf.h | 13 +++++++------ cores/esp8266/LwipIntfCB.cpp | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/cores/esp8266/LwipIntf.h b/cores/esp8266/LwipIntf.h index ba31e912e6..cb8bb92dcc 100644 --- a/cores/esp8266/LwipIntf.h +++ b/cores/esp8266/LwipIntf.h @@ -8,18 +8,19 @@ class LwipIntf { -private: +public: - LwipIntf () { } // private, cannot be directly allocated + using CBType = std::function ; -protected: + static bool stateUpCB (LwipIntf::CBType&& cb); - static bool stateChangeSysCB (std::function&& cb); +private: -public: + LwipIntf () { } // private, cannot be directly allocated - static bool stateUpCB (std::function&& cb); +protected: + static bool stateChangeSysCB (LwipIntf::CBType&& cb); }; #endif // _LWIPINTF_H diff --git a/cores/esp8266/LwipIntfCB.cpp b/cores/esp8266/LwipIntfCB.cpp index c0de24a233..b978918a72 100644 --- a/cores/esp8266/LwipIntfCB.cpp +++ b/cores/esp8266/LwipIntfCB.cpp @@ -6,7 +6,7 @@ #define NETIF_STATUS_CB_SIZE 3 static int netifStatusChangeListLength = 0; -std::function netifStatusChangeList [NETIF_STATUS_CB_SIZE]; +LwipIntf::CBType netifStatusChangeList [NETIF_STATUS_CB_SIZE]; extern "C" void netif_status_changed (struct netif* netif) { @@ -15,7 +15,7 @@ extern "C" void netif_status_changed (struct netif* netif) netifStatusChangeList[i](netif); } -bool LwipIntf::stateChangeSysCB (std::function&& cb) +bool LwipIntf::stateChangeSysCB (LwipIntf::CBType&& cb) { if (netifStatusChangeListLength >= NETIF_STATUS_CB_SIZE) { @@ -29,7 +29,7 @@ bool LwipIntf::stateChangeSysCB (std::function&& cb) return true; } -bool LwipIntf::stateUpCB (std::function&& cb) +bool LwipIntf::stateUpCB (LwipIntf::CBType&& cb) { return stateChangeSysCB([cb](netif* nif) { From 3a22818cad69429e4f809c45624f1c936ad7761d Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 10:36:50 +0200 Subject: [PATCH 127/152] udpContext: move trySend to private section --- .../ESP8266WiFi/src/include/UdpContext.h | 67 +++++++++---------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/libraries/ESP8266WiFi/src/include/UdpContext.h b/libraries/ESP8266WiFi/src/include/UdpContext.h index 11d9ae82bb..0c43cd2adb 100644 --- a/libraries/ESP8266WiFi/src/include/UdpContext.h +++ b/libraries/ESP8266WiFi/src/include/UdpContext.h @@ -400,14 +400,30 @@ class UdpContext _tx_buf_offset = 0; } - err_t trySend(const ip_addr_t* addr = 0, uint16_t port = 0, bool keepBuffer = true) + bool send(const ip_addr_t* addr = 0, uint16_t port = 0) + { + return trySend(addr, port, /* don't keep buffer */false) == ERR_OK; + } + + bool sendTimeout(const ip_addr_t* addr, uint16_t port, + esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs) + { + err_t err; + esp8266::polledTimeout::oneShotFastMs timeout(timeoutMs); + while (((err = trySend(addr, port, /* keep buffer on error */true)) != ERR_OK) && !timeout) + delay(0); + if (err != ERR_OK) + cancelBuffer(); // get rid of buffer kept on error after timeout + return err == ERR_OK; + } + +private: + + err_t trySend(const ip_addr_t* addr, uint16_t port, bool keepBufferOnError) { size_t data_size = _tx_buf_offset; pbuf* tx_copy = pbuf_alloc(PBUF_TRANSPORT, data_size, PBUF_RAM); - if(!tx_copy){ - DEBUGV("failed pbuf_alloc"); - } - else{ + if (tx_copy) { uint8_t* dst = reinterpret_cast(tx_copy->payload); for (pbuf* p = _tx_buf_head; p; p = p->next) { size_t will_copy = (data_size < p->len) ? data_size : p->len; @@ -416,9 +432,12 @@ class UdpContext data_size -= will_copy; } } - if (!keepBuffer) + + if (!keepBufferOnError) cancelBuffer(); - if(!tx_copy){ + + if (!tx_copy){ + DEBUGV("failed pbuf_alloc"); return ERR_MEM; } @@ -426,44 +445,20 @@ class UdpContext addr = &_pcb->remote_ip; port = _pcb->remote_port; } -#ifdef LWIP_MAYBE_XCC - uint16_t old_ttl = _pcb->ttl; - if (ip_addr_ismulticast(addr)) { - _pcb->ttl = _mcast_ttl; - } -#endif + err_t err = udp_sendto(_pcb, tx_copy, addr, port); if (err != ERR_OK) { DEBUGV(":ust rc=%d\r\n", (int) err); } -#ifdef LWIP_MAYBE_XCC - _pcb->ttl = old_ttl; -#endif + pbuf_free(tx_copy); - if (err == ERR_OK) - cancelBuffer(); - return err; - } - bool send(const ip_addr_t* addr = 0, uint16_t port = 0) - { - return trySend(addr, port, /* don't keep buffer */false) == ERR_OK; - } + if (err == ERR_OK) + cancelBuffer(); // no error: get rid of buffer - bool sendTimeout(const ip_addr_t* addr, uint16_t port, - esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs) - { - err_t err; - esp8266::polledTimeout::oneShotFastMs timeout(timeoutMs); - while (((err = trySend(addr, port)) != ERR_OK) && !timeout) - delay(0); - if (err != ERR_OK) - cancelBuffer(); - return err == ERR_OK; + return err; } -private: - size_t _processSize (const pbuf* pb) { size_t ret = 0; From 9047d2d073685354fa9311bda9b11da563c27581 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 10:53:31 +0200 Subject: [PATCH 128/152] typos in comments --- .../examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino index 58b111c4b1..d70e2b2235 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino @@ -3,7 +3,7 @@ This example demonstrates two features of the LEA clsLEAMDNSHost: 1. The host and service domain negotiation process that ensures - the uniqueness of the finally choosen host and service domain name. + the uniqueness of the finally chosen host and service domain name. 2. The dynamic MDNS service TXT feature A 'clock' service in announced via the MDNS responder and the current @@ -11,10 +11,10 @@ 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. + is already used in the local network, another host domain is negotiated. Keep an + eye on the serial output to learn the final host domain for the clock service. The service itself is is announced as 'host domain'._espclk._tcp.local. - As the service uses port 80, a very simple HTTP server is installed also to deliver + As the service uses port 80, a very simple HTTP server is also installed to deliver a small web page containing a greeting and the current time (not updated). The web server code is taken nearly 1:1 from the 'mDNS_Web_Server.ino' example. Point your browser to 'host domain'.local to see this web page. From cdd0a64b5b427112bcc04eab4fbca4134cd0a377 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 10:53:56 +0200 Subject: [PATCH 129/152] rename local mDNS instance in example --- .../examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino index d70e2b2235..5faaae8ff8 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino @@ -81,7 +81,7 @@ const char* ssid = STASSID; const char* password = STAPSK; -clsLEAMDNSHost MDNS; // MDNS responder +clsLEAMDNSHost MDNSRESP; // MDNS responder bool bHostDomainConfirmed = false; // Flags the confirmation of the host domain clsLEAMDNSHost::clsService* hMDNSService = 0; // The handle of the clock service in the MDNS responder @@ -144,7 +144,7 @@ bool setStationHostname(const char* p_pcHostname) { 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(). + This can be triggered by calling MDNSRESP.announce(). */ void MDNSDynamicServiceTxtCallback(const clsLEAMDNSHost::hMDNSService& p_hService) { @@ -223,7 +223,7 @@ void setup(void) { // Setup MDNS responder // Init the (currently empty) host domain string with 'leamdnsv2' - if (MDNS.begin("leamdnsv2", + if (MDNSRESP.begin("leamdnsv2", [](clsLEAMDNSHost & p_rMDNSHost, const char* p_pcDomainName, bool p_bProbeResult)->void { if (p_bProbeResult) { Serial.printf("mDNSHost_AP::ProbeResultCallback: '%s' is %s\n", p_pcDomainName, (p_bProbeResult ? "FREE" : "USED!")); @@ -233,7 +233,7 @@ void setup(void) { hMDNSService->setDynamicServiceTxtCallback(MDNSDynamicServiceTxtCallback); } else { // Change hostname, use '-' as divider between base name and index - MDNS.setHostName(clsLEAMDNSHost::indexDomainName(p_pcDomainName, "-", 0)); + MDNSRESP.setHostName(clsLEAMDNSHost::indexDomainName(p_pcDomainName, "-", 0)); } })) { Serial.println("mDNS-AP started"); @@ -255,7 +255,7 @@ void loop(void) { // Check if a request has come in server.handleClient(); // Allow MDNS processing - MDNS.update(); + MDNSRESP.update(); static esp8266::polledTimeout::periodicMs timeout(UPDATE_CYCLE); if (timeout.expired()) { @@ -264,7 +264,7 @@ void loop(void) { // Just trigger a new MDNS announcement, this will lead to a call to // 'MDNSDynamicServiceTxtCallback', which will update the time TXT item Serial.printf("Announce trigger from user\n"); - MDNS.announce(); + MDNSRESP.announce(); } } From a71e1c90b29f622c683bb4ba0747dbe9ffc877e1 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 10:55:39 +0200 Subject: [PATCH 130/152] memory reservation for string in example --- .../ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino index 5faaae8ff8..6e9e61e467 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino @@ -171,6 +171,7 @@ void handleHTTPRequest() { gmtime_r(&now, &timeinfo); String s; + s.reserve(300); s = "\r\nHello from "; s += WiFi.hostname() + " at " + WiFi.localIP().toString(); From fcd214bbe3e6984d785129f3350c76335706363c Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 11:04:52 +0200 Subject: [PATCH 131/152] string reservation --- .../LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino index b14df03ba2..59f213fd83 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino @@ -204,7 +204,9 @@ void handleHTTPRequest() { IPAddress ip = server.client().localIP(); String ipStr = ip.toString(); - String s = "\r\n

      Hello from "; + String s; + s.reserve(200 /* + service listed */); + s = "\r\n

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

      "; s += "

      Local HTTP services are :

      "; s += "
        "; From 6f92ac5ab450e735792a8913de9e5a4bdd7bab0c Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 11:11:25 +0200 Subject: [PATCH 132/152] move strrstr() to stdlib_noniso.h --- cores/esp8266/core_esp8266_noniso.cpp | 32 +++++++++++++ cores/esp8266/stdlib_noniso.h | 3 ++ libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 40 +---------------- libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp | 45 +------------------ 4 files changed, 37 insertions(+), 83 deletions(-) diff --git a/cores/esp8266/core_esp8266_noniso.cpp b/cores/esp8266/core_esp8266_noniso.cpp index c742904d21..b341599b0f 100644 --- a/cores/esp8266/core_esp8266_noniso.cpp +++ b/cores/esp8266/core_esp8266_noniso.cpp @@ -117,4 +117,36 @@ char * dtostrf(double number, signed char width, unsigned char prec, char *s) { return s; } +/* + strrstr (static) + + Backwards search for p_pcPattern in p_pcString + Based on: https://stackoverflow.com/a/1634398/2778898 + +*/ +const char* strrstr(const char*__restrict p_pcString, + const char*__restrict p_pcPattern) +{ + const char* pcResult = 0; + + size_t stStringLength = (p_pcString ? strlen(p_pcString) : 0); + size_t stPatternLength = (p_pcPattern ? strlen(p_pcPattern) : 0); + + if ((stStringLength) && + (stPatternLength) && + (stPatternLength <= stStringLength)) + { + // Pattern is shorter or has the same length than the string + for (const char* s = (p_pcString + stStringLength - stPatternLength); s >= p_pcString; --s) + { + if (0 == strncmp(s, p_pcPattern, stPatternLength)) + { + pcResult = s; + break; + } + } + } + return pcResult; +} + }; diff --git a/cores/esp8266/stdlib_noniso.h b/cores/esp8266/stdlib_noniso.h index 636cbf437d..053ea47948 100644 --- a/cores/esp8266/stdlib_noniso.h +++ b/cores/esp8266/stdlib_noniso.h @@ -44,6 +44,9 @@ char* dtostrf (double val, signed char width, unsigned char prec, char *s); void reverse(char* begin, char* end); +const char* strrstr(const char*__restrict p_pcString, + const char*__restrict p_pcPattern); + #ifdef __cplusplus } // extern "C" #endif diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 971b764643..353cb820a9 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -24,6 +24,7 @@ #include #include // LwipIntf::stateUpCB() +#include // strrstr() #include "ESP8266mDNS.h" #include "LEAmDNS2Host.h" @@ -46,45 +47,6 @@ #define STRINGIZE_VALUE_OF(x) STRINGIZE(x) #endif -namespace // anonymous -{ - -/* - strrstr (static) - - Backwards search for p_pcPattern in p_pcString - Based on: https://stackoverflow.com/a/1634398/2778898 - -*/ -const char* strrstr(const char*__restrict p_pcString, - const char*__restrict p_pcPattern) -{ - const char* pcResult = 0; - - size_t stStringLength = (p_pcString ? strlen(p_pcString) : 0); - size_t stPatternLength = (p_pcPattern ? strlen(p_pcPattern) : 0); - - if ((stStringLength) && - (stPatternLength) && - (stPatternLength <= stStringLength)) - { - // Pattern is shorter or has the same length than the string - for (const char* s = (p_pcString + stStringLength - stPatternLength); s >= p_pcString; --s) - { - if (0 == strncmp(s, p_pcPattern, stPatternLength)) - { - pcResult = s; - break; - } - } - } - return pcResult; -} - - -} // anonymous - - namespace esp8266 { diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp b/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp index d5a0ccd762..7dc3773de5 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp @@ -23,55 +23,12 @@ */ #include +#include // strrstr() #include "ESP8266mDNS.h" #include "LEAmDNS_lwIPdefs.h" #include "LEAmDNS_Priv.h" - -namespace -{ - -/* - strrstr (static) - - Backwards search for p_pcPattern in p_pcString - Based on: https://stackoverflow.com/a/1634398/2778898 - -*/ -const char* strrstr(const char*__restrict p_pcString, const char*__restrict p_pcPattern) -{ - - const char* pcResult = 0; - - size_t stStringLength = (p_pcString ? strlen(p_pcString) : 0); - size_t stPatternLength = (p_pcPattern ? strlen(p_pcPattern) : 0); - - if ((stStringLength) && - (stPatternLength) && - (stPatternLength <= stStringLength)) - { - // Pattern is shorter or has the same length tham the string - - for (const char* s = (p_pcString + stStringLength - stPatternLength); s >= p_pcString; --s) - { - if (0 == strncmp(s, p_pcPattern, stPatternLength)) - { - pcResult = s; - break; - } - } - } - return pcResult; -} - - -} // anonymous - - - - - namespace esp8266 { From 53d54778bf65d85a705110313ad3d3fd95043240 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 11:25:19 +0200 Subject: [PATCH 133/152] "good practices" --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 353cb820a9..d63086c884 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -920,6 +920,7 @@ bool clsLEAMDNSHost::_leaveMulticastGroups() bool bResult = false; for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + { if (netif_is_up(pNetIf)) { bResult = true; @@ -940,6 +941,7 @@ bool clsLEAMDNSHost::_leaveMulticastGroups() } #endif } + } return bResult; } From 281f67e31c0ac4a8f89778b117138a523845e640 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 11:27:01 +0200 Subject: [PATCH 134/152] "good practices" --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index d63086c884..a4b7fb2371 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -142,11 +142,13 @@ bool clsLEAMDNSHost::setNetIfHostName(const char* p_pcHostName) { if (p_pcHostName) for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + { if (netif_is_up(pNetIf)) { netif_set_hostname(pNetIf, p_pcHostName); DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[mDNS] setNetIfHostName host name: %s on " NETIFID_STR "!\n"), p_pcHostName, NETIFID_VAL(pNetIf));); } + } return true; } @@ -875,6 +877,7 @@ bool clsLEAMDNSHost::_joinMulticastGroups(void) // Join multicast group(s) for (netif* pNetIf = netif_list; pNetIf; pNetIf = pNetIf->next) + { if (netif_is_up(pNetIf)) { #ifdef MDNS_IPV4_SUPPORT @@ -909,6 +912,7 @@ bool clsLEAMDNSHost::_joinMulticastGroups(void) _DH(), NETIFID_VAL(pNetIf))); #endif } + } return bResult; } From 9a1ad4beea643d45709adff9815a6ea11e2cab4f Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 11:38:09 +0200 Subject: [PATCH 135/152] _instanceName() return 0 when 0 has to be returned --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 9 ++++----- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index a4b7fb2371..3c25af2f6a 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -419,8 +419,8 @@ clsLEAMDNSHost::clsService* clsLEAMDNSHost::addService(const char* p_pcInstanceN } } } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s addService: %s to add service '%s.%s.%s.local'!\n"), _DH(pService), (pService ? "Succeeded" : "FAILED"), _instanceName(p_pcInstanceName, false), (p_pcType ? : ""), (p_pcProtocol ? : ""));); - DEBUG_EX_ERR(if (!pService) DEBUG_OUTPUT.printf_P(PSTR("%s addService: FAILED to add service '%s.%s.%s.local'!\n"), _DH(pService), _instanceName(p_pcInstanceName, false), (p_pcType ? : ""), (p_pcProtocol ? : ""));); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s addService: %s to add service '%s.%s.%s.local'!\n"), _DH(pService), (pService ? "Succeeded" : "FAILED"), _instanceName(p_pcInstanceName)? : "-", (p_pcType ? : ""), (p_pcProtocol ? : ""));); + DEBUG_EX_ERR(if (!pService) DEBUG_OUTPUT.printf_P(PSTR("%s addService: FAILED to add service '%s.%s.%s.local'!\n"), _DH(pService), _instanceName(p_pcInstanceName)? : "-", (p_pcType ? : ""), (p_pcProtocol ? : ""));); return pService; } @@ -1056,10 +1056,9 @@ bool clsLEAMDNSHost::_releaseDefaultInstanceName(void) /* clsLEAmDNS2_Host::_instanceName */ -const char* clsLEAMDNSHost::_instanceName(const char* p_pcInstanceName, - bool p_bReturnZero /*= true*/) const +const char* clsLEAMDNSHost::_instanceName(const char* p_pcInstanceName) const { - return (p_pcInstanceName ? : (m_pcDefaultInstanceName ? : (m_pcHostName ? : (p_bReturnZero ? 0 : "-")))); + return (p_pcInstanceName ? : (m_pcDefaultInstanceName ? : (m_pcHostName ? : nullptr))); } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index 5f1473ffe9..d6516be74f 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -1296,8 +1296,7 @@ class clsLEAMDNSHost bool _allocDefaultInstanceName(const char* p_pcInstanceName); bool _releaseDefaultInstanceName(void); - const char* _instanceName(const char* p_pcInstanceName, - bool p_bReturnZero = true) const; + const char* _instanceName(const char* p_pcInstanceName) const; // SERVICE clsService* _allocService(const char* p_pcName, From 844abbaa9d34db55db6c33ae72a025eb5f708a0e Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 11:45:09 +0200 Subject: [PATCH 136/152] made some 0 into nullptr --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 50 +++++++++++----------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 3c25af2f6a..386a2c3709 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -92,7 +92,7 @@ const char* clsLEAMDNSHost::indexDomainName(const char* p_pcDomainName, const char* pFoundDivider = strrstr(p_pcDomainName, pcDivider); if (pFoundDivider) // maybe already extended { - char* pEnd = 0; + char* pEnd = nullptr; unsigned long ulIndex = strtoul((pFoundDivider + strlen(pcDivider)), &pEnd, 10); if ((ulIndex) && ((pEnd - p_pcDomainName) == (ptrdiff_t)strlen(p_pcDomainName)) && @@ -109,7 +109,7 @@ const char* clsLEAMDNSHost::indexDomainName(const char* p_pcDomainName, } else { - pFoundDivider = 0; // Flag the need to (base) extend the hostname + pFoundDivider = nullptr; // Flag the need to (base) extend the hostname } } @@ -270,7 +270,7 @@ bool clsLEAMDNSHost::close(void) { DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s close\n"), _DH());); - m_pUDPContext = 0; + m_pUDPContext = nullptr; return ((_leaveMulticastGroups()) && (_releaseBackbone())); } @@ -397,7 +397,7 @@ clsLEAMDNSHost::clsService* clsLEAMDNSHost::addService(const char* p_pcInstanceN uint16_t p_u16Port, clsLEAMDNSHost::clsService::fnProbeResultCallback p_fnCallback /*= 0*/) { - clsService* pService = 0; + clsService* pService = nullptr; if (!((pService = findService(_instanceName(p_pcInstanceName), p_pcType, p_pcProtocol, p_u16Port)))) { @@ -415,7 +415,7 @@ clsLEAMDNSHost::clsService* clsLEAMDNSHost::addService(const char* p_pcInstanceN else { delete pService; - pService = 0; + pService = nullptr; } } } @@ -454,7 +454,7 @@ const clsLEAMDNSHost::clsService* clsLEAMDNSHost::findService(const char* p_pcIn const char* p_pcProtocol, uint16_t p_u16Port/*= (uint16_t)(-1)*/) const { - clsService* pFoundService = 0; + clsService* pFoundService = nullptr; for (clsService* pService : m_Services) { @@ -518,7 +518,7 @@ clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryService { std::list queries; - clsQuery* pQuery = 0; + clsQuery* pQuery = nullptr; if (((pQuery = _allocQuery(clsQuery::enuQueryType::Service))) && (_buildDomainForService(p_pcService, p_pcProtocol, pQuery->m_Domain))) { @@ -573,7 +573,7 @@ clsLEAMDNSHost::clsQuery::clsAnswerAccessor::vector clsLEAMDNSHost::queryHost(co { std::list queries; - clsQuery* pQuery = 0; + clsQuery* pQuery = nullptr; if (((pQuery = _allocQuery(clsQuery::enuQueryType::Host))) && (_buildDomainForHost(p_pcHostName, pQuery->m_Domain))) { @@ -647,7 +647,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcSe const char* p_pcProtocol, clsLEAMDNSHost::clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer) { - clsQuery* pQuery = 0; + clsQuery* pQuery = nullptr; if ((pQuery = _installServiceQuery(p_pcService, p_pcProtocol))) { pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; @@ -663,7 +663,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcSe const char* p_pcProtocol, clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor) { - clsQuery* pQuery = 0; + clsQuery* pQuery = nullptr; if ((pQuery = _installServiceQuery(p_pcService, p_pcProtocol))) { pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; @@ -677,13 +677,13 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installServiceQuery(const char* p_pcSe clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installHostQuery(const char* p_pcHostName, clsLEAMDNSHost::clsQuery::QueryCallbackAnswerFn p_fnCallbackAnswer) { - clsQuery* pQuery = 0; + clsQuery* pQuery = nullptr; if ((p_pcHostName) && (*p_pcHostName)) { clsRRDomain domain; if ((pQuery = ((_buildDomainForHost(p_pcHostName, domain)) ? _installDomainQuery(domain, clsQuery::enuQueryType::Host) - : 0))) + : nullptr))) { pQuery->m_fnCallbackAnswer = p_fnCallbackAnswer; } @@ -697,13 +697,13 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installHostQuery(const char* p_pcHostN clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::installHostQuery(const char* p_pcHostName, clsLEAMDNSHost::clsQuery::QueryCallbackAccessorFn p_fnCallbackAccessor) { - clsQuery* pQuery = 0; + clsQuery* pQuery = nullptr; if ((p_pcHostName) && (*p_pcHostName)) { clsRRDomain domain; if ((pQuery = ((_buildDomainForHost(p_pcHostName, domain)) ? _installDomainQuery(domain, clsQuery::enuQueryType::Host) - : 0))) + : nullptr))) { pQuery->m_fnCallbackAccessor = p_fnCallbackAccessor; } @@ -793,7 +793,7 @@ clsLEAMDNSHost::clsService* clsLEAMDNSHost::enableArduino(uint16_t p_u16Port, || (!svc->addServiceTxt("auth_upload", (p_bAuthUpload) ? "yes" : "no"))) { removeService(svc); - svc = 0; + svc = nullptr; } } return svc; @@ -816,7 +816,7 @@ clsLEAMDNSHost::clsService* clsLEAMDNSHost::enableArduino(uint16_t p_u16Port, */ UdpContext* clsLEAMDNSHost::_allocBackbone(void) { - UdpContext* pUDPContext = 0; + UdpContext* pUDPContext = nullptr; if (!clsBackbone::sm_pBackbone) { @@ -830,7 +830,7 @@ UdpContext* clsLEAMDNSHost::_allocBackbone(void) DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("%s _allocBackbone: FAILED to init backbone!\n"), _DH());); delete clsBackbone::sm_pBackbone; - clsBackbone::sm_pBackbone = 0; + clsBackbone::sm_pBackbone = nullptr; } } if (clsBackbone::sm_pBackbone) @@ -854,7 +854,7 @@ bool clsLEAMDNSHost::_releaseBackbone(void) (0 == clsBackbone::sm_pBackbone->hostCount())) { delete clsBackbone::sm_pBackbone; - clsBackbone::sm_pBackbone = 0; + clsBackbone::sm_pBackbone = nullptr; DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _releaseBackbone: Released backbone."), _DH());); } DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s _releaseBackbone: %s to remove host from backbone."), _DH(), (bResult ? "Succeeded" : "FAILED"));); @@ -1011,10 +1011,10 @@ bool clsLEAMDNSHost::_allocDomainName(const char* p_pcNewDomainName, bool clsLEAMDNSHost::_releaseDomainName(char*& p_rpcDomainName) { bool bResult; - if ((bResult = (0 != p_rpcDomainName))) + if ((bResult = (nullptr != p_rpcDomainName))) { delete[] p_rpcDomainName; - p_rpcDomainName = 0; + p_rpcDomainName = nullptr; } return bResult; } @@ -1139,7 +1139,7 @@ bool clsLEAMDNSHost::_removeQuery(clsLEAMDNSHost::clsQuery * p_pQuery) */ bool clsLEAMDNSHost::_removeLegacyQuery(void) { - clsQuery* pLegacyQuery = 0; + clsQuery* pLegacyQuery = nullptr; return (((pLegacyQuery = _findLegacyQuery())) ? _removeQuery(pLegacyQuery) : true); @@ -1151,7 +1151,7 @@ bool clsLEAMDNSHost::_removeLegacyQuery(void) */ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_findLegacyQuery(void) { - clsQuery* pLegacyQuery = 0; + clsQuery* pLegacyQuery = nullptr; for (clsQuery* pQuery : m_Queries) { @@ -1186,7 +1186,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_findNextQueryByDomain(const clsLEAMDN const clsLEAMDNSHost::clsQuery::enuQueryType p_QueryType, const clsQuery * p_pPrevQuery) { - clsQuery* pMatchingQuery = 0; + clsQuery* pMatchingQuery = nullptr; clsQuery::list::iterator it(m_Queries.begin()); if (p_pPrevQuery) @@ -1223,7 +1223,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installServiceQuery( const char* p_pcService, const char* p_pcProtocol) { - clsQuery* pMDNSQuery = 0; + clsQuery* pMDNSQuery = nullptr; if ((p_pcService) && (*p_pcService) && (p_pcProtocol) && (*p_pcProtocol) && @@ -1254,7 +1254,7 @@ clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_installDomainQuery( clsLEAMDNSHost::clsRRDomain & p_Domain, clsLEAMDNSHost::clsQuery::enuQueryType p_QueryType) { - clsQuery* pQuery = 0; + clsQuery* pQuery = nullptr; if ((pQuery = _allocQuery(p_QueryType))) { From b27c540d48b943daefe628a5811c6d7dda1d7832 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 11:57:17 +0200 Subject: [PATCH 137/152] typo --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 38f10b1db1..e31de4d9b6 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -1530,7 +1530,7 @@ bool clsLEAMDNSHost::_readMDNSMsgHeader(clsLEAMDNSHost::clsMsgHeader& p_rMsgHead (_udpRead16(p_rMsgHeader.m_u16ARCount))) { - p_rMsgHeader.m_1bQR = (u8B1 & 0x80); // Query/Responde flag + p_rMsgHeader.m_1bQR = (u8B1 & 0x80); // Query/Response flag p_rMsgHeader.m_4bOpcode = (u8B1 & 0x78); // Operation code (0: Standard query, others ignored) p_rMsgHeader.m_1bAA = (u8B1 & 0x04); // Authorative answer p_rMsgHeader.m_1bTC = (u8B1 & 0x02); // Truncation flag From a5082020805af12230fba9308aa3cc9756d9eb7b Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 12:05:36 +0200 Subject: [PATCH 138/152] typos --- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index d6516be74f..24cc10b36b 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -8,7 +8,7 @@ Supported mDNS features (in some cases somewhat limited): - Announcing a DNS-SD service to interested observers, eg. a http server by announcing a esp8266._http._tcp.local. service - - Support for multi-level compressed names in input; in output only a very simple one-leven full-name compression is implemented + - Support for multi-level compressed names in input; in output only a very simple one-level full-name compression is implemented - Probing host and service domains for uniqueness in the local network - Tiebreaking while probing is supported in a very minimalistic way (the 'higher' IP address wins the tiebreak) - Announcing available services after successful probing @@ -27,7 +27,7 @@ In 'setup()': Create an clsLEAMDNSHost instance for every netif you plan to use. Call 'begin' on every instance with the intended hostname and the associated netif (or WiFi mode, WIFI_STA). - The given hostname is the 'probed' for uniqueness in the netifs local link. If domain name conflicts occure, the host name + The given hostname is the 'probed' for uniqueness in the netifs local link. If domain name conflicts occur, the host name will be automatically changed until it is unique in the local link. Optionally a callback can be registered in 'begin', to control the probing process manually. Next you can register DNS-SD services with 'addService("MyESP", "http", "tcp", 5000)' From da72802621207b0f9bc81ad128e6250ee81a1aeb Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 12:09:42 +0200 Subject: [PATCH 139/152] static const -> constexpr --- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 50 ++++++++++++------------ 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index 24cc10b36b..b78afe4134 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -134,37 +134,36 @@ class clsLEAMDNSHost { public: #ifdef MDNS_IPV4_SUPPORT - static const uint16_t u16IPv4Size = 4; // IPv4 address size in bytes + static constexpr uint16_t u16IPv4Size = 4; // IPv4 address size in bytes #endif #ifdef MDNS_IPV6_SUPPORT - static const uint16_t u16IPv6Size = 16; // IPv6 address size in bytes + static constexpr uint16_t u16IPv6Size = 16; // IPv6 address size in bytes #endif - static const size_t stServiceTxtMaxLength = 1300; // Maximum length for all service txts for one service - static const size_t stDomainMaxLength = 256; // Maximum length for a full domain name eg. MyESP._http._tcp.local - static const size_t stDomainLabelMaxLength = 63; // Maximum length of on label in a domain name (length info fits into 6 bits) - static const size_t stServiceTypeMaxLength = 15; // Maximum length of a service name eg. http - static const size_t stServiceProtocolMaxLength = 3; // Maximum length of a service protocol name eg. tcp - - static const uint32_t u32LegacyTTL = 10; // Legacy DNS record TTL - static const uint32_t u32HostTTL = 120; // Host level records are set to 2min (120s) - static const uint32_t u32ServiceTTL = 4500; // Service level records are set to 75min (4500s) - - static const uint16_t u16SRVPriority = 0; // Default service priority and weight in SRV answers - static const uint16_t u16SRVWeight = 0; // - static const uint8_t u8DomainCompressMark = 0xC0; // Compressed labels are flaged by the two topmost bits of the length byte being set - static const uint8_t u8DomainMaxRedirections = 6; // Avoid endless recursion because of malformed compressed labels - - static const uint32_t u32ProbeDelay = 1000; // Default 250, but ESP is slow...; delay between and number of probes for host and service domains - static const uint32_t u32ProbeCount = 3; - static const uint32_t u32AnnounceDelay = 1000; // Delay between and number of announces for host and service domains - static const uint32_t u32AnnounceCount = 3; - static const uint32_t u32DynamicQueryResendDelay = 1000; // Delay between and number of queries; the delay is multiplied by the resent number in '_checkQueryCache' + static constexpr size_t stServiceTxtMaxLength = 1300; // Maximum length for all service txts for one service + static constexpr size_t stDomainMaxLength = 256; // Maximum length for a full domain name eg. MyESP._http._tcp.local + static constexpr size_t stDomainLabelMaxLength = 63; // Maximum length of on label in a domain name (length info fits into 6 bits) + static constexpr size_t stServiceTypeMaxLength = 15; // Maximum length of a service name eg. http + static constexpr size_t stServiceProtocolMaxLength = 3; // Maximum length of a service protocol name eg. tcp + + static constexpr uint32_t u32LegacyTTL = 10; // Legacy DNS record TTL + static constexpr uint32_t u32HostTTL = 120; // Host level records are set to 2min (120s) + static constexpr uint32_t u32ServiceTTL = 4500; // Service level records are set to 75min (4500s) + + static constexpr uint16_t u16SRVPriority = 0; // Default service priority and weight in SRV answers + static constexpr uint16_t u16SRVWeight = 0; // + static constexpr uint8_t u8DomainCompressMark = 0xC0; // Compressed labels are flaged by the two topmost bits of the length byte being set + static constexpr uint8_t u8DomainMaxRedirections = 6; // Avoid endless recursion because of malformed compressed labels + + static constexpr uint32_t u32ProbeDelay = 1000; // Default 250, but ESP is slow...; delay between and number of probes for host and service domains + static constexpr uint32_t u32ProbeCount = 3; + static constexpr uint32_t u32AnnounceDelay = 1000; // Delay between and number of announces for host and service domains + static constexpr uint32_t u32AnnounceCount = 3; + static constexpr uint32_t u32DynamicQueryResendDelay = 1000; // Delay between and number of queries; the delay is multiplied by the resent number in '_checkQueryCache' static const char* pcLocal; // "local"; static const char* pcServices; // "services"; static const char* pcDNSSD; // "dns-sd"; static const char* pcUDP; // "udp"; - //static const char* pcTCP; // "tcp"; #ifdef MDNS_IPV4_SUPPORT static const char* pcReverseIPv4Domain; // "in-addr"; @@ -175,11 +174,10 @@ class clsLEAMDNSHost static const char* pcReverseTopDomain; // "arpa"; #ifdef DNS_RRTYPE_NSEC - static const uint8_t u8DNS_RRTYPE_NSEC = DNS_RRTYPE_NSEC; + static constexpr uint8_t u8DNS_RRTYPE_NSEC = DNS_RRTYPE_NSEC; #else - static const uint8_t u8DNS_RRTYPE_NSEC = 0x2F; + static constexpr uint8_t u8DNS_RRTYPE_NSEC = 0x2F; #endif - //static const uint32_t u32SendCooldown = 50; // Delay (ms) between to 'UDPContext->send()' calls static constexpr uint32_t u32SendTimeoutMs = 50; // timeout (ms) for a call to `UDPContext->send()` (12ms=1460B@1Mb/s) }; From 140c45f3311a78af14cac19fe5744fb5b3ac65f0 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 12:13:38 +0200 Subject: [PATCH 140/152] nsec comment --- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index b78afe4134..184a6274fb 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -710,7 +710,7 @@ class clsLEAMDNSHost TXT, AAAA, SRV, - //NSEC, + //NSEC, not used - https://tools.ietf.org/html/rfc6762#section-6.1 Generic }; From fb1224c52fe3eab2da0a88dbc379c524585b58d5 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 12:19:52 +0200 Subject: [PATCH 141/152] typos --- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 2 +- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index 184a6274fb..23d009827e 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -1243,7 +1243,7 @@ class clsLEAMDNSHost /* install*Query() creates several queries on the interfaces. - it no more returns a single query but a boolean until the API is adapted + it does not return a single query but a boolean until the API is adapted */ clsQuery* installServiceQuery(const char* p_pcServiceType, const char* p_pcProtocol, diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h index 2967c8a0c8..7052269762 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h @@ -8,11 +8,11 @@ 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. + A lot of the additions were basically 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 + - Support for multi-level compressed names in input; in output only a very simple one-level full-name compression is implemented - Probing host and service domains for uniqueness in the local network - Tiebreaking while probing is supported in a very minimalistic way (the 'higher' IP address wins the tiebreak) - Announcing available services after successful probing From 0a18d0fc522e53c9984b01336b6a987261347d56 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 12:20:06 +0200 Subject: [PATCH 142/152] typedef -> using --- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h | 37 ++++++++++++--------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h index 7052269762..2df7fe4940 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h @@ -167,7 +167,7 @@ class clsLEAMDNSHost_Legacy /** hMDNSService (opaque handle to access the service) */ - typedef const void* hMDNSService; + using hMDNSService = const void*; // 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 @@ -206,7 +206,7 @@ class clsLEAMDNSHost_Legacy /** hMDNSTxt (opaque handle to access the TXT items) */ - typedef void* hMDNSTxt; + using hMDNSTxt = void*; // Add a (static) MDNS TXT item ('key' = 'value') to the service hMDNSTxt addServiceTxt(const hMDNSService p_hService, @@ -254,7 +254,7 @@ class clsLEAMDNSHost_Legacy MDNSDynamicServiceTxtCallbackFn Callback function for dynamic MDNS TXT items */ - typedef std::function MDNSDynamicServiceTxtCallbackFn; + using MDNSDynamicServiceTxtCallbackFn = std::function; // 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. @@ -314,12 +314,12 @@ class clsLEAMDNSHost_Legacy /** hMDNSServiceQuery (opaque handle to access dynamic service queries) */ - typedef const void* hMDNSServiceQuery; + using hMDNSServiceQuery = const void*; /** enuServiceQueryAnswerType */ - typedef enum _enuServiceQueryAnswerType + using enuServiceQueryAnswerType = enum _enuServiceQueryAnswerType { ServiceQueryAnswerType_Unknown = 0, ServiceQueryAnswerType_ServiceDomain = (1 << 0), // Service instance name @@ -331,7 +331,7 @@ class clsLEAMDNSHost_Legacy #ifdef MDNS_IP6_SUPPORT ServiceQueryAnswerType_IP6Address = (1 << 4), // IP6 address #endif - } enuServiceQueryAnswerType; + }; /** AnswerType (std::map compatible version) @@ -504,10 +504,11 @@ class clsLEAMDNSHost_Legacy Callback function for received answers for dynamic service queries */ - typedef std::function MDNSServiceQueryCallbackFn; + )>; // 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 @@ -566,12 +567,14 @@ class clsLEAMDNSHost_Legacy MDNSHostProbeResultCallbackFn/2 Callback function for host domain probe results */ - typedef std::function MDNSHostProbeResultCallbackFn; + using MDNSHostProbeResultCallbackFn = + std::function; - typedef std::function MDNSHostProbeResultCallbackFn2; + bool p_bProbeResult)>; // Set a callback function for host probe results // The callback function is called, when the probeing for the host domain @@ -584,14 +587,16 @@ class clsLEAMDNSHost_Legacy MDNSServiceProbeResultCallbackFn/2 Callback function for service domain probe results */ - typedef std::function MDNSServiceProbeResultCallbackFn; + bool p_bProbeResult)>; - typedef std::function MDNSServiceProbeResultCallbackFn2; + bool p_bProbeResult)>; // Set a service specific probe result callcack bool setServiceProbeResultCallback(const hMDNSService p_hService, From e50ff189d43c8ddb8d7ef61be0df5e431115c0d8 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 12:21:05 +0200 Subject: [PATCH 143/152] cleanup --- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h index 2df7fe4940..92ecb3d95e 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h @@ -484,9 +484,6 @@ class clsLEAMDNSHost_Legacy { m_KeyValueMap.emplace(std::pair(kv.first, kv.second)); } - //for (auto kv=m_rMDNSResponder._answerKeyValue(m_hServiceQuery, m_u32AnswerIndex); kv!=nullptr; kv=kv->m_pNext) { - // m_KeyValueMap.emplace(std::pair(kv->m_pcKey, kv->m_pcValue)); - //} } return m_KeyValueMap; } From 691c083cb353bcb9e8b3caeddbbb20f134fedc77 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 12:27:10 +0200 Subject: [PATCH 144/152] const for some function members --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 4 ++-- libraries/ESP8266mDNS/src/LEAmDNS2Host.h | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 386a2c3709..d7eb40e02e 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -634,7 +634,7 @@ bool clsLEAMDNSHost::hasQuery(void) clsLEAmDNS2_Host::getQuery */ -clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::getQuery(void) +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::getQuery(void) const { return _findLegacyQuery(); } @@ -1149,7 +1149,7 @@ bool clsLEAMDNSHost::_removeLegacyQuery(void) clsLEAmDNS2_Host::_findLegacyQuery */ -clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_findLegacyQuery(void) +clsLEAMDNSHost::clsQuery* clsLEAMDNSHost::_findLegacyQuery(void) const { clsQuery* pLegacyQuery = nullptr; diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h index 23d009827e..ddc5fc1adb 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.h @@ -206,7 +206,7 @@ class clsLEAMDNSHost size_t hostCount(void) const; bool setDelayUDPProcessing(bool p_bDelayProcessing); - clsLEAMDNSHost* getUniqueHost() + clsLEAMDNSHost* getUniqueHost() const { return m_uniqueHost; } @@ -1226,7 +1226,7 @@ class clsLEAMDNSHost const uint16_t p_u16Timeout); bool removeQuery(void); bool hasQuery(void); - clsQuery* getQuery(void); + clsQuery* getQuery(void) const; // - DYNAMIC // Install a dynamic service/host query. For every received answer (part) the given callback @@ -1330,7 +1330,7 @@ class clsLEAMDNSHost clsQuery* _allocQuery(clsQuery::enuQueryType p_QueryType); bool _removeQuery(clsQuery* p_pQuery); bool _removeLegacyQuery(void); - clsQuery* _findLegacyQuery(void); + clsQuery* _findLegacyQuery(void) const; bool _releaseQueries(void); clsQuery* _findNextQueryByDomain(const clsRRDomain& p_Domain, const clsQuery::enuQueryType p_QueryType, From 0dab814fc2e14a8a564ff1d9bb09b3e6f654eb7d Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 12:30:45 +0200 Subject: [PATCH 145/152] 0 -> nullptr --- libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp index 852068df19..87f263eff5 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Backbone.cpp @@ -70,7 +70,7 @@ bool clsLEAMDNSHost::clsBackbone::init(void) */ UdpContext* clsLEAMDNSHost::clsBackbone::addHost(clsLEAMDNSHost* p_pHost) { - UdpContext* pUDPContext = 0; + UdpContext* pUDPContext = nullptr; if ((m_pUDPContext) && (p_pHost) && (m_uniqueHost == nullptr)) { @@ -193,7 +193,7 @@ bool clsLEAMDNSHost::clsBackbone::_releaseUDPContext(void) if (m_pUDPContext) { m_pUDPContext->unref(); - m_pUDPContext = 0; + m_pUDPContext = nullptr; } return true; } From d4bf122552645be961ff4ffb917fd567c67f9758 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 12:33:16 +0200 Subject: [PATCH 146/152] (*it). => it-> --- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp index 1aa900dda3..2bfc4f1ab2 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp @@ -120,10 +120,10 @@ bool clsLEAMDNSHost_Legacy::close(void) for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { - if ((bResult = (*it).m_pHost->close())) + if ((bResult = it->m_pHost->close())) { - delete (*it).m_pHost; - (*it).m_pHost = 0; + delete it->m_pHost; + it->m_pHost = 0; } } return ((bResult) @@ -177,7 +177,7 @@ bool clsLEAMDNSHost_Legacy::setHostname(const char* p_pcHostname) for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { - bResult = (*it).m_pHost->setHostName(p_pcHostname); + bResult = it->m_pHost->setHostName(p_pcHostname); } return bResult; } @@ -275,11 +275,11 @@ bool clsLEAMDNSHost_Legacy::removeService(const hMDNSService p_hService) for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { - clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService]; + clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)it->m_HandleToPtr[p_hService]; if ((bResult = ((pService) - && ((*it).m_pHost->removeService(pService))))) + && (it->m_pHost->removeService(pService))))) { - (*it).m_HandleToPtr.erase(p_hService); + it->m_HandleToPtr.erase(p_hService); } } return bResult; @@ -311,7 +311,7 @@ bool clsLEAMDNSHost_Legacy::setServiceName(const hMDNSService p_hService, for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { - clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService]; + clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)it->m_HandleToPtr[p_hService]; bResult = ((pService) && (pService->setInstanceName(p_pcInstanceName))); } @@ -518,13 +518,13 @@ bool clsLEAMDNSHost_Legacy::removeServiceTxt(const hMDNSService p_hService, for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { - clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService]; - clsLEAMDNSHost::clsServiceTxt* pTxt = (clsLEAMDNSHost::clsServiceTxt*)(*it).m_HandleToPtr[p_hTxt]; + clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)it->m_HandleToPtr[p_hService]; + clsLEAMDNSHost::clsServiceTxt* pTxt = (clsLEAMDNSHost::clsServiceTxt*)it->m_HandleToPtr[p_hTxt]; if ((bResult = ((pService) && (pTxt) && (pService->removeServiceTxt(pTxt))))) { - (*it).m_HandleToPtr.erase(p_hTxt); + it->m_HandleToPtr.erase(p_hTxt); } } return bResult; @@ -595,7 +595,7 @@ bool clsLEAMDNSHost_Legacy::setDynamicServiceTxtCallback(const hMDNSService p_hS for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { - clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService]; + clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)it->m_HandleToPtr[p_hService]; bResult = pService->setDynamicServiceTxtCallback([p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/)->void { if (p_fnCallback) // void(const hMDNSService p_hService) @@ -720,7 +720,7 @@ bool clsLEAMDNSHost_Legacy::removeQuery(void) for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { - bResult = (*it).m_pHost->removeQuery(); + bResult = it->m_pHost->removeQuery(); } return bResult; } @@ -850,9 +850,9 @@ bool clsLEAMDNSHost_Legacy::removeServiceQuery(clsLEAMDNSHost_Legacy::hMDNSServi for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { - if ((bResult = (*it).m_pHost->removeQuery((clsLEAMDNSHost::clsQuery*)(*it).m_HandleToPtr[p_hServiceQuery]))) + if ((bResult = it->m_pHost->removeQuery((clsLEAMDNSHost::clsQuery*)it->m_HandleToPtr[p_hServiceQuery]))) { - (*it).m_HandleToPtr.erase(p_hServiceQuery); + it->m_HandleToPtr.erase(p_hServiceQuery); } } return bResult; @@ -1101,7 +1101,7 @@ bool clsLEAMDNSHost_Legacy::setHostProbeResultCallback(clsLEAMDNSHost_Legacy::MD for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { - bResult = (*it).m_pHost->setProbeResultCallback(clsLEAMDNSHost::stProbeResultCallback); + bResult = it->m_pHost->setProbeResultCallback(clsLEAMDNSHost::stProbeResultCallback); } return bResult; } @@ -1127,7 +1127,7 @@ bool clsLEAMDNSHost_Legacy::setHostProbeResultCallback(clsLEAMDNSHost_Legacy::MD for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { - bResult = (*it).m_pHost->setProbeResultCallback(clsLEAMDNSHost::stProbeResultCallback); + bResult = it->m_pHost->setProbeResultCallback(clsLEAMDNSHost::stProbeResultCallback); } return bResult; } @@ -1144,7 +1144,7 @@ bool clsLEAMDNSHost_Legacy::setServiceProbeResultCallback(const clsLEAMDNSHost_L for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { clsLEAMDNSHost::clsService* pService = 0; - bResult = (((pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService])) + bResult = (((pService = (clsLEAMDNSHost::clsService*)it->m_HandleToPtr[p_hService])) && (pService->setProbeResultCallback( [this, p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/, @@ -1173,7 +1173,7 @@ bool clsLEAMDNSHost_Legacy::setServiceProbeResultCallback(const clsLEAMDNSHost_L for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { clsLEAMDNSHost::clsService* pService = 0; - bResult = (((pService = (clsLEAMDNSHost::clsService*)(*it).m_HandleToPtr[p_hService])) + bResult = (((pService = (clsLEAMDNSHost::clsService*)it->m_HandleToPtr[p_hService])) && (pService->setProbeResultCallback([this, p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/, const char* p_pcInstanceName, bool p_bProbeResult)->void @@ -1204,7 +1204,7 @@ bool clsLEAMDNSHost_Legacy::notifyAPChange(void) for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { - bResult = (*it).m_pHost->restart(); + bResult = it->m_pHost->restart(); } return bResult; } @@ -1219,7 +1219,7 @@ bool clsLEAMDNSHost_Legacy::update(void) for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { - bResult = (*it).m_pHost->update(); + bResult = it->m_pHost->update(); } return bResult; } @@ -1234,7 +1234,7 @@ bool clsLEAMDNSHost_Legacy::announce(void) for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) { - bResult = (*it).m_pHost->announce(true, true); + bResult = it->m_pHost->announce(true, true); } return bResult; } From 00453f1c62dee0ed0fbef29043ad3219de1e2f6f Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 12:39:50 +0200 Subject: [PATCH 147/152] cleanup --- .../ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index e31de4d9b6..4f94afff0c 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -116,13 +116,6 @@ bool clsLEAMDNSHost::_sendMessage(netif* pNetIf, clsLEAMDNSHost::clsSendParamete (m_pUDPContext->sendTimeout(ipRemote, m_pUDPContext->getRemotePort(), clsConsts::u32SendTimeoutMs)) /*&& (Serial.println("Did send UC"), true)*/); DEBUG_EX_ERR(if (!bResult) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage (V4): FAILED!\n"), _DH());); -#if 0 - if ((clsConsts::u32SendCooldown) && - 1)//(can_yield())) - { - delay(clsConsts::u32SendCooldown); - } -#endif } else { @@ -186,13 +179,6 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(netif* pNetIf, clsLEAMDNSHost::clsSe (m_pUDPContext->setMulticastInterface(0), true) /*&& (Serial.println("Did send MC V4"), true)*/); DEBUG_EX_ERR(if (!bIPv4Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast (V4): FAILED!\n"), _DH());); -#if 0 - if ((clsConsts::u32SendCooldown) && - 1)//(can_yield())) - { - delay(clsConsts::u32SendCooldown); - } -#endif } #endif @@ -213,13 +199,6 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(netif* pNetIf, clsLEAMDNSHost::clsSe (m_pUDPContext->setMulticastInterface(0), true) /*&& (Serial.println("Did send MC V6"), true)*/); DEBUG_EX_ERR(if (!bIPv6Result) DEBUG_OUTPUT.printf_P(PSTR("%s _sendMessage_Multicast (IPv6): FAILED! (%s, %s, %s)\n"), _DH(), (_getResponderIPAddress(pNetIf, enuIPProtocolType::V6).isSet() ? "1" : "0"), (bPrepareMessage ? "1" : "0"), (bUDPContextSend ? "1" : "0"));); -#if 0 - if ((clsConsts::u32SendCooldown) && - 1)//(can_yield())) - { - delay(clsConsts::u32SendCooldown); - } -#endif } #endif From 0c98b60dc8b8f32e903bd57588ee6fea6c470a92 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 12:53:00 +0200 Subject: [PATCH 148/152] style --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 4 +-- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h | 32 ++++++++++----------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index d7eb40e02e..7801b64d78 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -419,8 +419,8 @@ clsLEAMDNSHost::clsService* clsLEAMDNSHost::addService(const char* p_pcInstanceN } } } - DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s addService: %s to add service '%s.%s.%s.local'!\n"), _DH(pService), (pService ? "Succeeded" : "FAILED"), _instanceName(p_pcInstanceName)? : "-", (p_pcType ? : ""), (p_pcProtocol ? : ""));); - DEBUG_EX_ERR(if (!pService) DEBUG_OUTPUT.printf_P(PSTR("%s addService: FAILED to add service '%s.%s.%s.local'!\n"), _DH(pService), _instanceName(p_pcInstanceName)? : "-", (p_pcType ? : ""), (p_pcProtocol ? : ""));); + DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("%s addService: %s to add service '%s.%s.%s.local'!\n"), _DH(pService), (pService ? "Succeeded" : "FAILED"), _instanceName(p_pcInstanceName) ? : "-", (p_pcType ? : ""), (p_pcProtocol ? : ""));); + DEBUG_EX_ERR(if (!pService) DEBUG_OUTPUT.printf_P(PSTR("%s addService: FAILED to add service '%s.%s.%s.local'!\n"), _DH(pService), _instanceName(p_pcInstanceName) ? : "-", (p_pcType ? : ""), (p_pcProtocol ? : ""));); return pService; } diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h index 92ecb3d95e..33668e11d1 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h @@ -502,10 +502,10 @@ class clsLEAMDNSHost_Legacy Callback function for received answers for dynamic service queries */ using MDNSServiceQueryCallbackFn = - std::function; + std::function; // 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 @@ -565,13 +565,13 @@ class clsLEAMDNSHost_Legacy Callback function for host domain probe results */ using MDNSHostProbeResultCallbackFn = - std::function; + std::function; using MDNSHostProbeResultCallbackFn2 = - std::function; + std::function; // Set a callback function for host probe results // The callback function is called, when the probeing for the host domain @@ -585,15 +585,15 @@ class clsLEAMDNSHost_Legacy Callback function for service domain probe results */ using MDNSServiceProbeResultCallbackFn = - std::function; + std::function; using MDNSServiceProbeResultCallbackFn2 = - std::function; + std::function; // Set a service specific probe result callcack bool setServiceProbeResultCallback(const hMDNSService p_hService, From 15861cae6aaf0f62a34799b9508a4b09c1c6ac72 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Tue, 22 Sep 2020 12:53:10 +0200 Subject: [PATCH 149/152] typo --- libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp index 4f94afff0c..f77e0a5e12 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host_Transfer.cpp @@ -212,7 +212,7 @@ bool clsLEAMDNSHost::_sendMessage_Multicast(netif* pNetIf, clsLEAMDNSHost::clsSe 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 + while in the second loop, the header and all queries and answers are written to the UDP output buffer. */ From fb1a026ee5990a5d201b7f0f9dae869b30fa0f9b Mon Sep 17 00:00:00 2001 From: "TAURI20\\Herman" Date: Thu, 24 Sep 2020 16:42:12 +0200 Subject: [PATCH 150/152] OTA service using hostname --- libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp index 7801b64d78..f74739ae6f 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS2Host.cpp @@ -784,7 +784,7 @@ bool clsLEAMDNSHost::restart(void) clsLEAMDNSHost::clsService* clsLEAMDNSHost::enableArduino(uint16_t p_u16Port, bool p_bAuthUpload /*= false*/) { - clsLEAMDNSHost::clsService* svc = addService("arduino", "arduino", "tcp", p_u16Port); + clsLEAMDNSHost::clsService* svc = addService(nullptr, "arduino", "tcp", p_u16Port); if (svc) { if ((!svc->addServiceTxt("tcp_check", "no")) From df56854110058600aa434fab213cecbdd22e6b0f Mon Sep 17 00:00:00 2001 From: david gauchard Date: Thu, 24 Sep 2020 23:10:49 +0200 Subject: [PATCH 151/152] remove: original mDNS implementation, and v2-to-v1=legacy adaptation implementation --- libraries/ESP8266mDNS/src/ESP8266mDNS.h | 6 +- .../ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp | 1523 ---------------- .../ESP8266mDNS/src/ESP8266mDNS_Legacy.h | 167 -- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp | 1531 ----------------- libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h | 700 -------- 5 files changed, 1 insertion(+), 3926 deletions(-) delete mode 100644 libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp delete mode 100644 libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.h delete mode 100644 libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp delete mode 100644 libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS.h b/libraries/ESP8266mDNS/src/ESP8266mDNS.h index fd09760410..2e0e1fcec0 100644 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS.h +++ b/libraries/ESP8266mDNS/src/ESP8266mDNS.h @@ -42,11 +42,9 @@ */ -enum class MDNSApiVersion { Legacy, LEA, LEAv2Compat, LEAv2 }; +enum class MDNSApiVersion { LEA, LEAv2 }; -#include "ESP8266mDNS_Legacy.h" // Legacy #include "LEAmDNS.h" // LEA -#include "LEAmDNS2_Legacy.h" // LEAv2Compat - replacement for LEA using v2 #include "LEAmDNS2Host.h" // LEAv2 - API updated // clsLEAMDNSHost replaces MDNSResponder in LEAv2 @@ -54,9 +52,7 @@ using clsLEAMDNSHost = esp8266::experimental::clsLEAMDNSHost; #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) // Maps the implementation to use to the global namespace type -//using MDNSResponder = Legacy_MDNSResponder::MDNSResponder; // Legacy using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; // LEA -//using MDNSResponder = experimental::MDNSImplementation::clsLEAMDNSHost_Legacy; // LEAv2Compat //using MDNSResponder = clsLEAMDNSHost; // LEAv2 extern MDNSResponder MDNS; diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp b/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp deleted file mode 100644 index be5666df7d..0000000000 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.cpp +++ /dev/null @@ -1,1523 +0,0 @@ -/* - - ESP8266 Multicast DNS (port of CC3000 Multicast DNS library) - Version 1.1 - Copyright (c) 2013 Tony DiCola (tony@tonydicola.com) - ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com) - MDNS-SD Suport 2015 Hristo Gochkov - Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com) - - - License (MIT license): - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -*/ - -// Important RFC's for reference: -// - DNS request and response: http://www.ietf.org/rfc/rfc1035.txt -// - Multicast DNS: http://www.ietf.org/rfc/rfc6762.txt -// - MDNS-SD: https://tools.ietf.org/html/rfc6763 - -#ifndef LWIP_OPEN_SRC -#define LWIP_OPEN_SRC -#endif - -#include -#include - -#include "debug.h" - -extern "C" { -#include "osapi.h" -#include "ets_sys.h" -#include "user_interface.h" -} - -#include "WiFiUdp.h" -#include "lwip/opt.h" -#include "lwip/udp.h" -#include "lwip/inet.h" -#include "lwip/igmp.h" -#include "lwip/mem.h" -#include "include/UdpContext.h" - - - -namespace Legacy_MDNSResponder -{ - - -#ifdef DEBUG_ESP_MDNS -#define DEBUG_ESP_MDNS_ERR -#define DEBUG_ESP_MDNS_TX -#define DEBUG_ESP_MDNS_RX -#endif - -#define MDNS_NAME_REF 0xC000 - -#define MDNS_TYPE_AAAA 0x001C -#define MDNS_TYPE_A 0x0001 -#define MDNS_TYPE_PTR 0x000C -#define MDNS_TYPE_SRV 0x0021 -#define MDNS_TYPE_TXT 0x0010 - -#define MDNS_CLASS_IN 0x0001 -#define MDNS_CLASS_IN_FLUSH_CACHE 0x8001 - -#define MDNS_ANSWERS_ALL 0x0F -#define MDNS_ANSWER_PTR 0x08 -#define MDNS_ANSWER_TXT 0x04 -#define MDNS_ANSWER_SRV 0x02 -#define MDNS_ANSWER_A 0x01 - -#define _conn_read32() (((uint32_t)_conn->read() << 24) | ((uint32_t)_conn->read() << 16) | ((uint32_t)_conn->read() << 8) | _conn->read()) -#define _conn_read16() (((uint16_t)_conn->read() << 8) | _conn->read()) -#define _conn_read8() _conn->read() -#define _conn_readS(b,l) _conn->read((char*)(b),l); - -static const IPAddress MDNS_MULTICAST_ADDR(224, 0, 0, 251); -static const int MDNS_MULTICAST_TTL = 1; -static const int MDNS_PORT = 5353; - -struct MDNSService -{ - MDNSService* _next; - char _name[32]; - char _proto[4]; - uint16_t _port; - uint16_t _txtLen; // length of all txts - struct MDNSTxt * _txts; -}; - -struct MDNSTxt -{ - MDNSTxt * _next; - String _txt; -}; - -struct MDNSAnswer -{ - MDNSAnswer* next; - uint8_t ip[4]; - uint16_t port; - char *hostname; -}; - -struct MDNSQuery -{ - char _service[32]; - char _proto[4]; -}; - - -MDNSResponder::MDNSResponder() : _conn(0) -{ - _services = 0; - _instanceName = ""; - _answers = 0; - _query = 0; - _newQuery = false; - _waitingForAnswers = false; -} -MDNSResponder::~MDNSResponder() -{ - if (_query != 0) - { - os_free(_query); - _query = 0; - } - - // Clear answer list - MDNSAnswer *answer; - int numAnswers = _getNumAnswers(); - for (int n = numAnswers - 1; n >= 0; n--) - { - answer = _getAnswerFromIdx(n); - os_free(answer->hostname); - os_free(answer); - answer = 0; - } - _answers = 0; - - if (_conn) - { - _conn->unref(); - } -} - -bool MDNSResponder::begin(const char* hostname) -{ - size_t n = strlen(hostname); - if (n > 63) // max size for a single label. - { - return false; - } - - // Copy in hostname characters as lowercase - _hostName = hostname; - _hostName.toLowerCase(); - - // If instance name is not already set copy hostname to instance name - if (_instanceName.equals("")) - { - _instanceName = hostname; - } - - _gotIPHandler = WiFi.onStationModeGotIP([this](const WiFiEventStationModeGotIP & event) - { - (void) event; - _restart(); - }); - - _disconnectedHandler = WiFi.onStationModeDisconnected([this](const WiFiEventStationModeDisconnected & event) - { - (void) event; - _restart(); - }); - - return _listen(); -} - -void MDNSResponder::notifyAPChange() -{ - _restart(); -} - -void MDNSResponder::_restart() -{ - if (_conn) - { - _conn->unref(); - _conn = nullptr; - } - _listen(); -} - -bool MDNSResponder::_listen() -{ - // Open the MDNS socket if it isn't already open. - if (!_conn) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.println("MDNS listening"); -#endif - - IPAddress mdns(MDNS_MULTICAST_ADDR); - - if (igmp_joingroup(IP4_ADDR_ANY4, mdns) != ERR_OK) - { - return false; - } - - _conn = new UdpContext; - _conn->ref(); - - if (!_conn->listen(IP_ADDR_ANY, MDNS_PORT)) - { - return false; - } - _conn->setMulticastTTL(MDNS_MULTICAST_TTL); - _conn->onRx(std::bind(&MDNSResponder::update, this)); - _conn->connect(mdns, MDNS_PORT); - } - return true; -} - -void MDNSResponder::update() -{ - if (!_conn || !_conn->next()) - { - return; - } - _parsePacket(); -} - - -void MDNSResponder::setInstanceName(String name) -{ - if (name.length() > 63) - { - return; - } - _instanceName = name; -} - - -bool MDNSResponder::addServiceTxt(char *name, char *proto, char *key, char *value) -{ - MDNSService* servicePtr; - - uint8_t txtLen = os_strlen(key) + os_strlen(value) + 1; // Add one for equals sign - txtLen += 1; //accounts for length byte added when building the txt responce - //Find the service - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - //Checking Service names - if (strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) - { - //found a service name match - if (servicePtr->_txtLen + txtLen > 1300) - { - return false; //max txt record size - } - MDNSTxt *newtxt = new MDNSTxt; - newtxt->_txt = String(key) + '=' + String(value); - newtxt->_next = 0; - if (servicePtr->_txts == 0) //no services have been added - { - //Adding First TXT to service - servicePtr->_txts = newtxt; - servicePtr->_txtLen += txtLen; - return true; - } - else - { - MDNSTxt * txtPtr = servicePtr->_txts; - while (txtPtr->_next != 0) - { - txtPtr = txtPtr->_next; - } - //adding another TXT to service - txtPtr->_next = newtxt; - servicePtr->_txtLen += txtLen; - return true; - } - } - } - return false; -} - -void MDNSResponder::addService(char *name, char *proto, uint16_t port) -{ - if (_getServicePort(name, proto) != 0) - { - return; - } - if (os_strlen(name) > 32 || os_strlen(proto) != 3) - { - return; //bad arguments - } - struct MDNSService *srv = (struct MDNSService*)(os_malloc(sizeof(struct MDNSService))); - os_strcpy(srv->_name, name); - os_strcpy(srv->_proto, proto); - srv->_port = port; - srv->_next = 0; - srv->_txts = 0; - srv->_txtLen = 0; - - if (_services == 0) - { - _services = srv; - } - else - { - MDNSService* servicePtr = _services; - while (servicePtr->_next != 0) - { - servicePtr = servicePtr->_next; - } - servicePtr->_next = srv; - } - -} - -int MDNSResponder::queryService(char *service, char *proto) -{ -#ifdef DEBUG_ESP_MDNS_TX - DEBUG_ESP_PORT.printf("queryService %s %s\n", service, proto); -#endif - while (_answers != 0) - { - MDNSAnswer *currAnswer = _answers; - _answers = _answers->next; - os_free(currAnswer->hostname); - os_free(currAnswer); - currAnswer = 0; - } - if (_query != 0) - { - os_free(_query); - _query = 0; - } - _query = (struct MDNSQuery*)(os_malloc(sizeof(struct MDNSQuery))); - os_strcpy(_query->_service, service); - os_strcpy(_query->_proto, proto); - _newQuery = true; - - char underscore[] = "_"; - - // build service name with _ - char serviceName[os_strlen(service) + 2]; - os_strcpy(serviceName, underscore); - os_strcat(serviceName, service); - size_t serviceNameLen = os_strlen(serviceName); - - //build proto name with _ - char protoName[5]; - os_strcpy(protoName, underscore); - os_strcat(protoName, proto); - size_t protoNameLen = 4; - - //local string - char localName[] = "local"; - size_t localNameLen = 5; - - //terminator - char terminator[] = "\0"; - - // Only supports sending one PTR query - uint8_t questionCount = 1; - - _waitingForAnswers = true; - for (int itfn = 0; itfn < 2; itfn++) - { - struct ip_info ip_info; - - wifi_get_ip_info((!itfn) ? SOFTAP_IF : STATION_IF, &ip_info); - if (!ip_info.ip.addr) - { - continue; - } - _conn->setMulticastInterface(IPAddress(ip_info.ip.addr)); - - // Write the header - _conn->flush(); - uint8_t head[12] = - { - 0x00, 0x00, //ID = 0 - 0x00, 0x00, //Flags = response + authoritative answer - 0x00, questionCount, //Question count - 0x00, 0x00, //Answer count - 0x00, 0x00, //Name server records - 0x00, 0x00 //Additional records - }; - _conn->append(reinterpret_cast(head), 12); - - // Only supports sending one PTR query - // Send the Name field (eg. "_http._tcp.local") - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_" + service - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_" + service - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_" + proto - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_" + proto - _conn->append(reinterpret_cast(&localNameLen), 1); // length of "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type and class - uint8_t ptrAttrs[4] = - { - 0x00, 0x0c, //PTR record query - 0x00, 0x01 //Class IN - }; - _conn->append(reinterpret_cast(ptrAttrs), 4); - _conn->send(); - } - -#ifdef DEBUG_ESP_MDNS_TX - DEBUG_ESP_PORT.println("Waiting for answers.."); -#endif - delay(1000); - - _waitingForAnswers = false; - - return _getNumAnswers(); -} - -String MDNSResponder::hostname(int idx) -{ - MDNSAnswer *answer = _getAnswerFromIdx(idx); - if (answer == 0) - { - return String(); - } - return answer->hostname; -} - -IPAddress MDNSResponder::IP(int idx) -{ - MDNSAnswer *answer = _getAnswerFromIdx(idx); - if (answer == 0) - { - return IPAddress(); - } - return IPAddress(answer->ip); -} - -uint16_t MDNSResponder::port(int idx) -{ - MDNSAnswer *answer = _getAnswerFromIdx(idx); - if (answer == 0) - { - return 0; - } - return answer->port; -} - -MDNSAnswer* MDNSResponder::_getAnswerFromIdx(int idx) -{ - MDNSAnswer *answer = _answers; - while (answer != 0 && idx-- > 0) - { - answer = answer->next; - } - if (idx > 0) - { - return 0; - } - return answer; -} - -int MDNSResponder::_getNumAnswers() -{ - int numAnswers = 0; - MDNSAnswer *answer = _answers; - while (answer != 0) - { - numAnswers++; - answer = answer->next; - } - return numAnswers; -} - -MDNSTxt * MDNSResponder::_getServiceTxt(char *name, char *proto) -{ - MDNSService* servicePtr; - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - if (servicePtr->_port > 0 && strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) - { - if (servicePtr->_txts == 0) - { - return nullptr; - } - return servicePtr->_txts; - } - } - return nullptr; -} - -uint16_t MDNSResponder::_getServiceTxtLen(char *name, char *proto) -{ - MDNSService* servicePtr; - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - if (servicePtr->_port > 0 && strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) - { - if (servicePtr->_txts == 0) - { - return false; - } - return servicePtr->_txtLen; - } - } - return 0; -} - -uint16_t MDNSResponder::_getServicePort(char *name, char *proto) -{ - MDNSService* servicePtr; - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - if (servicePtr->_port > 0 && strcmp(servicePtr->_name, name) == 0 && strcmp(servicePtr->_proto, proto) == 0) - { - return servicePtr->_port; - } - } - return 0; -} - -IPAddress MDNSResponder::_getRequestMulticastInterface() -{ - struct ip_info ip_info; - bool match_ap = false; - if (wifi_get_opmode() & SOFTAP_MODE) - { - const IPAddress& remote_ip = _conn->getRemoteAddress(); - wifi_get_ip_info(SOFTAP_IF, &ip_info); - IPAddress infoIp(ip_info.ip); - IPAddress infoMask(ip_info.netmask); - if (ip_info.ip.addr && ip_addr_netcmp((const ip_addr_t*)remote_ip, (const ip_addr_t*)infoIp, ip_2_ip4((const ip_addr_t*)infoMask))) - { - match_ap = true; - } - } - if (!match_ap) - { - wifi_get_ip_info(STATION_IF, &ip_info); - } - return IPAddress(ip_info.ip.addr); -} - -void MDNSResponder::_parsePacket() -{ - int i; - char tmp; - bool serviceParsed = false; - bool protoParsed = false; - bool localParsed = false; - - char hostName[255]; - uint8_t hostNameLen; - - char serviceName[32]; - uint8_t serviceNameLen; - uint16_t servicePort = 0; - - char protoName[32]; - protoName[0] = 0; - uint8_t protoNameLen = 0; - - uint16_t packetHeader[6]; - - for (i = 0; i < 6; i++) - { - packetHeader[i] = _conn_read16(); - } - - if ((packetHeader[1] & 0x8000) != 0) // Read answers - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("Reading answers RX: REQ, ID:%u, Q:%u, A:%u, NS:%u, ADD:%u\n", packetHeader[0], packetHeader[2], packetHeader[3], packetHeader[4], packetHeader[5]); -#endif - - if (!_waitingForAnswers) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.println("Not expecting any answers right now, returning"); -#endif - _conn->flush(); - return; - } - - int numAnswers = packetHeader[3] + packetHeader[5]; - // Assume that the PTR answer always comes first and that it is always accompanied by a TXT, SRV, AAAA (optional) and A answer in the same packet. - if (numAnswers < 4) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("Expected a packet with 4 or more answers, got %u\n", numAnswers); -#endif - _conn->flush(); - return; - } - - uint8_t tmp8; - uint16_t answerPort = 0; - uint8_t answerIp[4] = { 0, 0, 0, 0 }; - char answerHostName[255]; - bool serviceMatch = false; - MDNSAnswer *answer; - uint8_t partsCollected = 0; - uint8_t stringsRead = 0; - - answerHostName[0] = '\0'; - - // Clear answer list - if (_newQuery) - { - int oldAnswers = _getNumAnswers(); - for (int n = oldAnswers - 1; n >= 0; n--) - { - answer = _getAnswerFromIdx(n); - os_free(answer->hostname); - os_free(answer); - answer = 0; - } - _answers = 0; - _newQuery = false; - } - - while (numAnswers--) - { - // Read name - stringsRead = 0; - size_t last_bufferpos = 0; - do - { - tmp8 = _conn_read8(); - if (tmp8 == 0x00) // End of name - { - break; - } - if (tmp8 & 0xC0) // Compressed pointer - { - uint16_t offset = ((((uint16_t)tmp8) & ~0xC0) << 8) | _conn_read8(); - if (_conn->isValidOffset(offset)) - { - if (0 == last_bufferpos) - { - last_bufferpos = _conn->tell(); - } -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Compressed pointer, jumping from "); - DEBUG_ESP_PORT.print(last_bufferpos); - DEBUG_ESP_PORT.print(" to "); - DEBUG_ESP_PORT.println(offset); -#endif - _conn->seek(offset); - tmp8 = _conn_read8(); - } - else - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Skipping malformed compressed pointer"); -#endif - tmp8 = _conn_read8(); - break; - } - } - if (stringsRead > 3) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.println("failed to read the response name"); -#endif - _conn->flush(); - return; - } - _conn_readS(serviceName, tmp8); - serviceName[tmp8] = '\0'; -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf(" %d ", tmp8); - for (int n = 0; n < tmp8; n++) - { - DEBUG_ESP_PORT.printf("%c", serviceName[n]); - } - DEBUG_ESP_PORT.println(); -#endif - if (serviceName[0] == '_') - { - if (strcmp(&serviceName[1], _query->_service) == 0) - { - serviceMatch = true; -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("found matching service: %s\n", _query->_service); -#endif - } - } - stringsRead++; - } while (true); - if (last_bufferpos > 0) - { - _conn->seek(last_bufferpos); -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Compressed pointer, jumping back to "); - DEBUG_ESP_PORT.println(last_bufferpos); -#endif - } - - uint16_t answerType = _conn_read16(); // Read type - uint16_t answerClass = _conn_read16(); // Read class - uint32_t answerTtl = _conn_read32(); // Read ttl - uint16_t answerRdlength = _conn_read16(); // Read rdlength - - (void) answerClass; - (void) answerTtl; - - if (answerRdlength > 255) - { - if (answerType == MDNS_TYPE_TXT && answerRdlength < 1460) - { - while (--answerRdlength) - { - _conn->read(); - } - } - else - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("Data len too long! %u\n", answerRdlength); -#endif - _conn->flush(); - return; - } - } - -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("type: %04x rdlength: %d\n", answerType, answerRdlength); -#endif - - if (answerType == MDNS_TYPE_PTR) - { - partsCollected |= 0x01; - _conn_readS(hostName, answerRdlength); // Read rdata - if (hostName[answerRdlength - 2] & 0xc0) - { - memcpy(answerHostName, hostName + 1, answerRdlength - 3); - answerHostName[answerRdlength - 3] = '\0'; - } -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("PTR %d ", answerRdlength); - for (int n = 0; n < answerRdlength; n++) - { - DEBUG_ESP_PORT.printf("%c", hostName[n]); - } - DEBUG_ESP_PORT.println(); -#endif - } - - else if (answerType == MDNS_TYPE_TXT) - { - partsCollected |= 0x02; - _conn_readS(hostName, answerRdlength); // Read rdata -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("TXT %d ", answerRdlength); - for (int n = 0; n < answerRdlength; n++) - { - DEBUG_ESP_PORT.printf("%c", hostName[n]); - } - DEBUG_ESP_PORT.println(); -#endif - } - - else if (answerType == MDNS_TYPE_SRV) - { - partsCollected |= 0x04; - uint16_t answerPrio = _conn_read16(); // Read priority - uint16_t answerWeight = _conn_read16(); // Read weight - answerPort = _conn_read16(); // Read port - last_bufferpos = 0; - - (void) answerPrio; - (void) answerWeight; - - // Read hostname - tmp8 = _conn_read8(); - if (tmp8 & 0xC0) // Compressed pointer - { - uint16_t offset = ((((uint16_t)tmp8) & ~0xC0) << 8) | _conn_read8(); - if (_conn->isValidOffset(offset)) - { - last_bufferpos = _conn->tell(); -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Compressed pointer, jumping from "); - DEBUG_ESP_PORT.print(last_bufferpos); - DEBUG_ESP_PORT.print(" to "); - DEBUG_ESP_PORT.println(offset); -#endif - _conn->seek(offset); - tmp8 = _conn_read8(); - } - else - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Skipping malformed compressed pointer"); -#endif - tmp8 = _conn_read8(); - break; - } - } - _conn_readS(answerHostName, tmp8); - answerHostName[tmp8] = '\0'; -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("SRV %d ", tmp8); - for (int n = 0; n < tmp8; n++) - { - DEBUG_ESP_PORT.printf("%02x ", answerHostName[n]); - } - DEBUG_ESP_PORT.printf("\n%s\n", answerHostName); -#endif - if (last_bufferpos > 0) - { - _conn->seek(last_bufferpos); - tmp8 = 2; // Size of compression octets -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.print("Compressed pointer, jumping back to "); - DEBUG_ESP_PORT.println(last_bufferpos); -#endif - } - if (answerRdlength - (6 + 1 + tmp8) > 0) // Skip any remaining rdata - { - _conn_readS(hostName, answerRdlength - (6 + 1 + tmp8)); - } - } - - else if (answerType == MDNS_TYPE_A) - { - partsCollected |= 0x08; - for (int i = 0; i < 4; i++) - { - answerIp[i] = _conn_read8(); - } - } - else - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("Ignoring unsupported type %02x\n", tmp8); -#endif - for (int n = 0; n < answerRdlength; n++) - { - (void)_conn_read8(); - } - } - - if ((partsCollected == 0x0F) && serviceMatch) - { -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.println("All answers parsed, adding to _answers list.."); -#endif - // Add new answer to answer list - if (_answers == 0) - { - _answers = (struct MDNSAnswer*)(os_malloc(sizeof(struct MDNSAnswer))); - answer = _answers; - } - else - { - answer = _answers; - while (answer->next != 0) - { - answer = answer->next; - } - answer->next = (struct MDNSAnswer*)(os_malloc(sizeof(struct MDNSAnswer))); - answer = answer->next; - } - answer->next = 0; - answer->hostname = 0; - - // Populate new answer - answer->port = answerPort; - for (int i = 0; i < 4; i++) - { - answer->ip[i] = answerIp[i]; - } - answer->hostname = (char *)os_malloc(strlen(answerHostName) + 1); - os_strcpy(answer->hostname, answerHostName); - _conn->flush(); - return; - } - } - - _conn->flush(); - return; - } - - // PARSE REQUEST NAME - - hostNameLen = _conn_read8() % 255; - _conn_readS(hostName, hostNameLen); - hostName[hostNameLen] = '\0'; - - if (hostName[0] == '_') - { - serviceParsed = true; - memcpy(serviceName, hostName + 1, hostNameLen); - serviceNameLen = hostNameLen - 1; - hostNameLen = 0; - } - - if (hostNameLen > 0 && !_hostName.equals(hostName) && !_instanceName.equals(hostName)) - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_NO_HOST: %s\n", hostName); - DEBUG_ESP_PORT.printf("hostname: %s\n", _hostName.c_str()); - DEBUG_ESP_PORT.printf("instance: %s\n", _instanceName.c_str()); -#endif - _conn->flush(); - return; - } - - if (!serviceParsed) - { - serviceNameLen = _conn_read8() % 255; - _conn_readS(serviceName, serviceNameLen); - serviceName[serviceNameLen] = '\0'; - - if (serviceName[0] == '_') - { - memmove(serviceName, serviceName + 1, serviceNameLen); - serviceNameLen--; - serviceParsed = true; - } - else if (serviceNameLen == 5 && strcmp("local", serviceName) == 0) - { - tmp = _conn_read8(); - if (tmp == 0) - { - serviceParsed = true; - serviceNameLen = 0; - protoParsed = true; - protoNameLen = 0; - localParsed = true; - } - else - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_FQDN: %s\n", serviceName); -#endif - _conn->flush(); - return; - } - } - else - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_SERVICE: %s\n", serviceName); -#endif - _conn->flush(); - return; - } - } - - if (!protoParsed) - { - protoNameLen = _conn_read8() % 255; - _conn_readS(protoName, protoNameLen); - protoName[protoNameLen] = '\0'; - if (protoNameLen == 4 && protoName[0] == '_') - { - memmove(protoName, protoName + 1, protoNameLen); - protoNameLen--; - protoParsed = true; - } - else if (strcmp("services", serviceName) == 0 && strcmp("_dns-sd", protoName) == 0) - { - _conn->flush(); - IPAddress interface = _getRequestMulticastInterface(); - _replyToTypeEnumRequest(interface); - return; - } - else - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_PROTO: %s\n", protoName); -#endif - _conn->flush(); - return; - } - } - - if (!localParsed) - { - char localName[32]; - uint8_t localNameLen = _conn_read8() % 31; - _conn_readS(localName, localNameLen); - localName[localNameLen] = '\0'; - tmp = _conn_read8(); - if (localNameLen == 5 && strcmp("local", localName) == 0 && tmp == 0) - { - localParsed = true; - } - else - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_FQDN: %s\n", localName); -#endif - _conn->flush(); - return; - } - } - - if (serviceNameLen > 0 && protoNameLen > 0) - { - servicePort = _getServicePort(serviceName, protoName); - if (servicePort == 0) - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_NO_SERVICE: %s\n", serviceName); -#endif - _conn->flush(); - return; - } - } - else if (serviceNameLen > 0 || protoNameLen > 0) - { -#ifdef DEBUG_ESP_MDNS_ERR - DEBUG_ESP_PORT.printf("ERR_SERVICE_PROTO: %s\n", serviceName); -#endif - _conn->flush(); - return; - } - - // RESPOND - -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("RX: REQ, ID:%u, Q:%u, A:%u, NS:%u, ADD:%u\n", packetHeader[0], packetHeader[2], packetHeader[3], packetHeader[4], packetHeader[5]); -#endif - - uint16_t currentType; - uint16_t currentClass; - - int numQuestions = packetHeader[2]; - if (numQuestions > 4) - { - numQuestions = 4; - } - uint16_t questions[4]; - int question = 0; - - while (numQuestions--) - { - currentType = _conn_read16(); - if (currentType & MDNS_NAME_REF) //new header handle it better! - { - currentType = _conn_read16(); - } - currentClass = _conn_read16(); - if (currentClass & MDNS_CLASS_IN) - { - questions[question++] = currentType; - } - - if (numQuestions > 0) - { - if (_conn_read16() != 0xC00C) //new question but for another host/service - { - _conn->flush(); - numQuestions = 0; - } - } - -#ifdef DEBUG_ESP_MDNS_RX - DEBUG_ESP_PORT.printf("REQ: "); - if (hostNameLen > 0) - { - DEBUG_ESP_PORT.printf("%s.", hostName); - } - if (serviceNameLen > 0) - { - DEBUG_ESP_PORT.printf("_%s.", serviceName); - } - if (protoNameLen > 0) - { - DEBUG_ESP_PORT.printf("_%s.", protoName); - } - DEBUG_ESP_PORT.printf("local. "); - - if (currentType == MDNS_TYPE_AAAA) - { - DEBUG_ESP_PORT.printf(" AAAA "); - } - else if (currentType == MDNS_TYPE_A) - { - DEBUG_ESP_PORT.printf(" A "); - } - else if (currentType == MDNS_TYPE_PTR) - { - DEBUG_ESP_PORT.printf(" PTR "); - } - else if (currentType == MDNS_TYPE_SRV) - { - DEBUG_ESP_PORT.printf(" SRV "); - } - else if (currentType == MDNS_TYPE_TXT) - { - DEBUG_ESP_PORT.printf(" TXT "); - } - else - { - DEBUG_ESP_PORT.printf(" 0x%04X ", currentType); - } - - if (currentClass == MDNS_CLASS_IN) - { - DEBUG_ESP_PORT.printf(" IN "); - } - else if (currentClass == MDNS_CLASS_IN_FLUSH_CACHE) - { - DEBUG_ESP_PORT.printf(" IN[F] "); - } - else - { - DEBUG_ESP_PORT.printf(" 0x%04X ", currentClass); - } - - DEBUG_ESP_PORT.printf("\n"); -#endif - } - uint8_t questionMask = 0; - uint8_t responseMask = 0; - for (i = 0; i < question; i++) - { - if (questions[i] == MDNS_TYPE_A) - { - questionMask |= 0x1; - responseMask |= 0x1; - } - else if (questions[i] == MDNS_TYPE_SRV) - { - questionMask |= 0x2; - responseMask |= 0x3; - } - else if (questions[i] == MDNS_TYPE_TXT) - { - questionMask |= 0x4; - responseMask |= 0x4; - } - else if (questions[i] == MDNS_TYPE_PTR) - { - questionMask |= 0x8; - responseMask |= 0xF; - } - } - - IPAddress interface = _getRequestMulticastInterface(); - return _replyToInstanceRequest(questionMask, responseMask, serviceName, protoName, servicePort, interface); -} - - -/** - STRINGIZE -*/ -#ifndef STRINGIZE -#define STRINGIZE(x) #x -#endif -#ifndef STRINGIZE_VALUE_OF -#define STRINGIZE_VALUE_OF(x) STRINGIZE(x) -#endif - - -void MDNSResponder::enableArduino(uint16_t port, bool auth) -{ - - addService("arduino", "tcp", port); - addServiceTxt("arduino", "tcp", "tcp_check", "no"); - addServiceTxt("arduino", "tcp", "ssh_upload", "no"); - addServiceTxt("arduino", "tcp", "board", STRINGIZE_VALUE_OF(ARDUINO_BOARD)); - addServiceTxt("arduino", "tcp", "auth_upload", (auth) ? "yes" : "no"); -} - -void MDNSResponder::_replyToTypeEnumRequest(IPAddress multicastInterface) -{ - MDNSService* servicePtr; - for (servicePtr = _services; servicePtr; servicePtr = servicePtr->_next) - { - if (servicePtr->_port > 0) - { - char *service = servicePtr->_name; - char *proto = servicePtr->_proto; - //uint16_t port = servicePtr->_port; - -#ifdef DEBUG_ESP_MDNS_TX - DEBUG_ESP_PORT.printf("TX: service:%s, proto:%s\n", service, proto); -#endif - - char sdHostName[] = "_services"; - size_t sdHostNameLen = 9; - char sdServiceName[] = "_dns-sd"; - size_t sdServiceNameLen = 7; - char sdProtoName[] = "_udp"; - size_t sdProtoNameLen = 4; - - char underscore[] = "_"; - - // build service name with _ - char serviceName[os_strlen(service) + 2]; - os_strcpy(serviceName, underscore); - os_strcat(serviceName, service); - size_t serviceNameLen = os_strlen(serviceName); - - //build proto name with _ - char protoName[5]; - os_strcpy(protoName, underscore); - os_strcat(protoName, proto); - size_t protoNameLen = 4; - - //local string - char localName[] = "local"; - size_t localNameLen = 5; - - //terminator - char terminator[] = "\0"; - - //Write the header - _conn->flush(); - uint8_t head[12] = - { - 0x00, 0x00, //ID = 0 - 0x84, 0x00, //Flags = response + authoritative answer - 0x00, 0x00, //Question count - 0x00, 0x01, //Answer count - 0x00, 0x00, //Name server records - 0x00, 0x00, //Additional records - }; - _conn->append(reinterpret_cast(head), 12); - - // Send the Name field (ie. "_services._dns-sd._udp.local") - _conn->append(reinterpret_cast(&sdHostNameLen), 1); // length of "_services" - _conn->append(reinterpret_cast(sdHostName), sdHostNameLen); // "_services" - _conn->append(reinterpret_cast(&sdServiceNameLen), 1); // length of "_dns-sd" - _conn->append(reinterpret_cast(sdServiceName), sdServiceNameLen);// "_dns-sd" - _conn->append(reinterpret_cast(&sdProtoNameLen), 1); // length of "_udp" - _conn->append(reinterpret_cast(sdProtoName), sdProtoNameLen); // "_udp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type, class, ttl and rdata length - uint8_t ptrDataLen = serviceNameLen + protoNameLen + localNameLen + 4; // 4 is three label sizes and the terminator - uint8_t ptrAttrs[10] = - { - 0x00, 0x0c, //PTR record query - 0x00, 0x01, //Class IN - 0x00, 0x00, 0x11, 0x94, //TTL 4500 - 0x00, ptrDataLen, //RData length - }; - _conn->append(reinterpret_cast(ptrAttrs), 10); - - //Send the RData (ie. "_http._tcp.local") - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - _conn->setMulticastInterface(multicastInterface); - _conn->send(); - } - } -} - -void MDNSResponder::_replyToInstanceRequest(uint8_t questionMask, uint8_t responseMask, char * service, char *proto, uint16_t port, IPAddress multicastInterface) -{ - int i; - if (questionMask == 0) - { - return; - } - if (responseMask == 0) - { - return; - } - -#ifdef DEBUG_ESP_MDNS_TX - DEBUG_ESP_PORT.printf("TX: qmask:%01X, rmask:%01X, service:%s, proto:%s, port:%u\n", questionMask, responseMask, service, proto, port); -#endif - - - String instanceName = _instanceName; - size_t instanceNameLen = instanceName.length(); - - String hostName = _hostName; - size_t hostNameLen = hostName.length(); - - char underscore[] = "_"; - - // build service name with _ - char serviceName[os_strlen(service) + 2]; - os_strcpy(serviceName, underscore); - os_strcat(serviceName, service); - size_t serviceNameLen = os_strlen(serviceName); - - //build proto name with _ - char protoName[5]; - os_strcpy(protoName, underscore); - os_strcat(protoName, proto); - size_t protoNameLen = 4; - - //local string - char localName[] = "local"; - size_t localNameLen = 5; - - //terminator - char terminator[] = "\0"; - - uint8_t answerMask = responseMask & questionMask; - uint8_t answerCount = 0; - uint8_t additionalMask = responseMask & ~questionMask; - uint8_t additionalCount = 0; - for (i = 0; i < 4; i++) - { - if (answerMask & (1 << i)) - { - answerCount++; - } - if (additionalMask & (1 << i)) - { - additionalCount++; - } - } - - - //Write the header - _conn->flush(); - uint8_t head[12] = - { - 0x00, 0x00, //ID = 0 - 0x84, 0x00, //Flags = response + authoritative answer - 0x00, 0x00, //Question count - 0x00, answerCount, //Answer count - 0x00, 0x00, //Name server records - 0x00, additionalCount, //Additional records - }; - _conn->append(reinterpret_cast(head), 12); - - for (int responseSection = 0; responseSection < 2; ++responseSection) - { - - // PTR Response - if ((responseSection == 0 ? answerMask : additionalMask) & 0x8) - { - // Send the Name field (ie. "_http._tcp.local") - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type, class, ttl and rdata length - uint8_t ptrDataLen = instanceNameLen + serviceNameLen + protoNameLen + localNameLen + 5; // 5 is four label sizes and the terminator - uint8_t ptrAttrs[10] = - { - 0x00, 0x0c, //PTR record query - 0x00, 0x01, //Class IN - 0x00, 0x00, 0x00, 0x78, //TTL 120 - 0x00, ptrDataLen, //RData length - }; - _conn->append(reinterpret_cast(ptrAttrs), 10); - - //Send the RData (ie. "My IOT device._http._tcp.local") - _conn->append(reinterpret_cast(&instanceNameLen), 1); // length of "My IOT device" - _conn->append(reinterpret_cast(instanceName.c_str()), instanceNameLen);// "My IOT device" - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - } - - //TXT Responce - if ((responseSection == 0 ? answerMask : additionalMask) & 0x4) - { - //Send the name field (ie. "My IOT device._http._tcp.local") - _conn->append(reinterpret_cast(&instanceNameLen), 1); // length of "My IOT device" - _conn->append(reinterpret_cast(instanceName.c_str()), instanceNameLen);// "My IOT device" - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type, class, ttl and rdata length - uint8_t txtDataLen = _getServiceTxtLen(service, proto); - uint8_t txtAttrs[10] = - { - 0x00, 0x10, //TXT record query - 0x80, 0x01, //Class IN, with cache flush - 0x00, 0x00, 0x11, 0x94, //TTL 4500 - 0x00, txtDataLen, //RData length - }; - _conn->append(reinterpret_cast(txtAttrs), 10); - - //Send the RData - MDNSTxt * txtPtr = _getServiceTxt(service, proto); - while (txtPtr != 0) - { - uint8_t txtLen = txtPtr->_txt.length(); - _conn->append(reinterpret_cast(&txtLen), 1); // length of txt - _conn->append(reinterpret_cast(txtPtr->_txt.c_str()), txtLen);// the txt - txtPtr = txtPtr->_next; - } - } - - - //SRV Responce - if ((responseSection == 0 ? answerMask : additionalMask) & 0x2) - { - //Send the name field (ie. "My IOT device._http._tcp.local") - _conn->append(reinterpret_cast(&instanceNameLen), 1); // length of "My IOT device" - _conn->append(reinterpret_cast(instanceName.c_str()), instanceNameLen);// "My IOT device" - _conn->append(reinterpret_cast(&serviceNameLen), 1); // length of "_http" - _conn->append(reinterpret_cast(serviceName), serviceNameLen); // "_http" - _conn->append(reinterpret_cast(&protoNameLen), 1); // length of "_tcp" - _conn->append(reinterpret_cast(protoName), protoNameLen); // "_tcp" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - //Send the type, class, ttl, rdata length, priority and weight - uint8_t srvDataSize = hostNameLen + localNameLen + 3; // 3 is 2 lable size bytes and the terminator - srvDataSize += 6; // Size of Priority, weight and port - uint8_t srvAttrs[10] = - { - 0x00, 0x21, //Type SRV - 0x80, 0x01, //Class IN, with cache flush - 0x00, 0x00, 0x00, 0x78, //TTL 120 - 0x00, srvDataSize, //RData length - }; - _conn->append(reinterpret_cast(srvAttrs), 10); - - //Send the RData Priority weight and port - uint8_t srvRData[6] = - { - 0x00, 0x00, //Priority 0 - 0x00, 0x00, //Weight 0 - (uint8_t)((port >> 8) & 0xFF), (uint8_t)(port & 0xFF) - }; - _conn->append(reinterpret_cast(srvRData), 6); - //Send the RData (ie. "esp8266.local") - _conn->append(reinterpret_cast(&hostNameLen), 1); // length of "esp8266" - _conn->append(reinterpret_cast(hostName.c_str()), hostNameLen);// "esp8266" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - } - - // A Response - if ((responseSection == 0 ? answerMask : additionalMask) & 0x1) - { - //Send the RData (ie. "esp8266.local") - _conn->append(reinterpret_cast(&hostNameLen), 1); // length of "esp8266" - _conn->append(reinterpret_cast(hostName.c_str()), hostNameLen);// "esp8266" - _conn->append(reinterpret_cast(&localNameLen), 1); // length "local" - _conn->append(reinterpret_cast(localName), localNameLen); // "local" - _conn->append(reinterpret_cast(&terminator), 1); // terminator - - uint8_t aaaAttrs[10] = - { - 0x00, 0x01, //TYPE A - 0x80, 0x01, //Class IN, with cache flush - 0x00, 0x00, 0x00, 0x78, //TTL 120 - 0x00, 0x04, //DATA LEN - }; - _conn->append(reinterpret_cast(aaaAttrs), 10); - - // Send RData - uint32_t ip = multicastInterface; - uint8_t aaaRData[4] = - { - (uint8_t)(ip & 0xFF), //IP first octet - (uint8_t)((ip >> 8) & 0xFF), //IP second octet - (uint8_t)((ip >> 16) & 0xFF), //IP third octet - (uint8_t)((ip >> 24) & 0xFF) //IP fourth octet - }; - _conn->append(reinterpret_cast(aaaRData), 4); - } - } - - _conn->setMulticastInterface(multicastInterface); - _conn->send(); -} - -#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) -MDNSResponder MDNS; -#endif - -} // namespace Legacy_MDNSResponder - - - - diff --git a/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.h b/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.h deleted file mode 100644 index 70bfd58e23..0000000000 --- a/libraries/ESP8266mDNS/src/ESP8266mDNS_Legacy.h +++ /dev/null @@ -1,167 +0,0 @@ -/* - ESP8266 Multicast DNS (port of CC3000 Multicast DNS library) - Version 1.1 - Copyright (c) 2013 Tony DiCola (tony@tonydicola.com) - ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com) - Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com) - - This is a simple implementation of multicast DNS query support for an Arduino - running on ESP8266 chip. Only support for resolving address queries is currently - implemented. - - Requirements: - - ESP8266WiFi library - - Usage: - - Include the ESP8266 Multicast DNS library in the sketch. - - Call the begin method in the sketch's setup and provide a domain name (without - the '.local' suffix, i.e. just provide 'foo' to resolve 'foo.local'), and the - Adafruit CC3000 class instance. Optionally provide a time to live (in seconds) - for the DNS record--the default is 1 hour. - - Call the update method in each iteration of the sketch's loop function. - - License (MIT license): - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -*/ -#ifndef ESP8266MDNS_LEGACY_H -#define ESP8266MDNS_LEGACY_H - -#include "LEAmDNS2Host.h" -#include "WiFiUdp.h" - -//this should be defined at build time -#ifndef ARDUINO_BOARD -#define ARDUINO_BOARD "generic" -#endif - -class UdpContext; - - -namespace Legacy_MDNSResponder -{ - - -struct MDNSService; -struct MDNSTxt; -struct MDNSAnswer; - -class MDNSResponder -{ -public: - - MDNSResponder(); - ~MDNSResponder(); - bool begin(const char* hostName); - bool begin(const String& hostName) - { - return begin(hostName.c_str()); - } - //for compatibility - bool begin(const char* hostName, IPAddress ip, uint32_t ttl = 120) - { - (void) ip; - (void) ttl; - return begin(hostName); - } - bool begin(const String& hostName, IPAddress ip, uint32_t ttl = 120) - { - return begin(hostName.c_str(), ip, ttl); - } - /* Application should call this whenever AP is configured/disabled */ - void notifyAPChange(); - void update(); - - void addService(char *service, char *proto, uint16_t port); - void addService(const char *service, const char *proto, uint16_t port) - { - addService((char *)service, (char *)proto, port); - } - void addService(const String& service, const String& proto, uint16_t port) - { - addService(service.c_str(), proto.c_str(), port); - } - - bool addServiceTxt(char *name, char *proto, char * key, char * value); - bool addServiceTxt(const char *name, const char *proto, const char *key, const char * value) - { - return addServiceTxt((char *)name, (char *)proto, (char *)key, (char *)value); - } - bool addServiceTxt(const String& name, const String& proto, const String& key, const String& value) - { - return addServiceTxt(name.c_str(), proto.c_str(), key.c_str(), value.c_str()); - } - - int queryService(char *service, char *proto); - int queryService(const char *service, const char *proto) - { - return queryService((char *)service, (char *)proto); - } - int queryService(const String& service, const String& proto) - { - return queryService(service.c_str(), proto.c_str()); - } - String hostname(int idx); - IPAddress IP(int idx); - uint16_t port(int idx); - - void enableArduino(uint16_t port, bool auth = false); - - void setInstanceName(String name); - void setInstanceName(const char * name) - { - setInstanceName(String(name)); - } - void setInstanceName(char * name) - { - setInstanceName(String(name)); - } - -private: - struct MDNSService * _services; - UdpContext* _conn; - String _hostName; - String _instanceName; - struct MDNSAnswer * _answers; - struct MDNSQuery * _query; - bool _newQuery; - bool _waitingForAnswers; - WiFiEventHandler _disconnectedHandler; - WiFiEventHandler _gotIPHandler; - - - uint16_t _getServicePort(char *service, char *proto); - MDNSTxt * _getServiceTxt(char *name, char *proto); - uint16_t _getServiceTxtLen(char *name, char *proto); - IPAddress _getRequestMulticastInterface(); - void _parsePacket(); - void _replyToTypeEnumRequest(IPAddress multicastInterface); - void _replyToInstanceRequest(uint8_t questionMask, uint8_t responseMask, char * service, char *proto, uint16_t port, IPAddress multicastInterface); - MDNSAnswer* _getAnswerFromIdx(int idx); - int _getNumAnswers(); - bool _listen(); - void _restart(); -}; - -} // namespace Legacy_MDNSResponder - -#endif //ESP8266MDNS_H - - - diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp deleted file mode 100644 index 2bfc4f1ab2..0000000000 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.cpp +++ /dev/null @@ -1,1531 +0,0 @@ -/* - LEAmDNS2_Legacy.cpp - - License (MIT license): - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -*/ - -#include "ESP8266mDNS.h" -#include "LEAmDNS2_Legacy.h" - - -namespace experimental // esp8266 -{ - -/** - LEAmDNS -*/ -namespace MDNSImplementation -{ - -/** - STRINGIZE -*/ -#ifndef STRINGIZE -#define STRINGIZE(x) #x -#endif -#ifndef STRINGIZE_VALUE_OF -#define STRINGIZE_VALUE_OF(x) STRINGIZE(x) -#endif - - -/* - clsLEAMDNSHost_Legacy::clsLEAMDNSHost_Legacy constructor - -*/ -clsLEAMDNSHost_Legacy::clsLEAMDNSHost_Legacy(void) -{ -} - -/* - clsLEAMDNSHost_Legacy::clsLEAMDNSHost_Legacy destructor - -*/ -clsLEAMDNSHost_Legacy::~clsLEAMDNSHost_Legacy(void) -{ -} - -/* - - HOST SETUP - -*/ - -/* - clsLEAMDNSHost_Legacy::begin - -*/ -bool clsLEAMDNSHost_Legacy::begin(const char* p_pcHostname) -{ - return addHostForNetIf(p_pcHostname) - && (0 != m_HostInformations.size()); -} - - -/* - clsLEAMDNSHost_Legacy::begin (String) - -*/ -bool clsLEAMDNSHost_Legacy::begin(const String& p_strHostname) -{ - return begin(p_strHostname.c_str()); -} - -/* - clsLEAMDNSHost_Legacy::begin (Ignored Options) - -*/ -bool clsLEAMDNSHost_Legacy::begin(const char* p_pcHostname, - IPAddress /*p_IPAddress = INADDR_ANY*/, // ignored - uint32_t /*p_u32TTL = 120*/) // ignored -{ - return begin(p_pcHostname); -} - -/* - clsLEAMDNSHost_Legacy::begin (String & Ignored Options) - -*/ -bool clsLEAMDNSHost_Legacy::begin(const String& p_strHostname, - IPAddress /*p_IPAddress = INADDR_ANY*/, // ignored - uint32_t /*p_u32TTL = 120*/) // ignored -{ - return begin(p_strHostname.c_str()); -} - -/* - clsLEAMDNSHost_Legacy::close - -*/ -bool clsLEAMDNSHost_Legacy::close(void) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - if ((bResult = it->m_pHost->close())) - { - delete it->m_pHost; - it->m_pHost = 0; - } - } - return ((bResult) - && (m_HostInformations.clear(), true)); -} - -/* - clsLEAMDNSHost_Legacy::end - -*/ -bool clsLEAMDNSHost_Legacy::end(void) -{ - return close(); -} - -/* - clsLEAMDNSHost_Legacy::addHostForNetIf - - NEW! - -*/ -bool clsLEAMDNSHost_Legacy::addHostForNetIf(const char* p_pcHostname) -{ - bool bResult = true; - - if (m_HostInformations.size() > 0) - { - //XXXFIXME only one pHost instance, many things can be simplified - bResult = false; - } - else - { - clsLEAMDNSHost* pHost = new esp8266::experimental::clsLEAMDNSHost; - if (pHost - && (!((pHost->begin(p_pcHostname /*, default callback*/)) - && (m_HostInformations.push_back(stcHostInformation(pHost)), true)))) - { - bResult = false; - } - } - return bResult; -} - -/* - clsLEAMDNSHost_Legacy::setHostname - -*/ -bool clsLEAMDNSHost_Legacy::setHostname(const char* p_pcHostname) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - bResult = it->m_pHost->setHostName(p_pcHostname); - } - return bResult; -} - -/* - clsLEAMDNSHost_Legacy::setHostname - -*/ -bool clsLEAMDNSHost_Legacy::setHostname(String p_strHostname) -{ - return setHostname(p_strHostname.c_str()); -} - -/* - clsLEAMDNSHost_Legacy::hostname - -*/ -const char* clsLEAMDNSHost_Legacy::hostname(void) const -{ - return (m_HostInformations.empty() - ? 0 - : m_HostInformations.front().m_pHost->hostName()); -} - -/* - clsLEAMDNSHost_Legacy::status - -*/ -bool clsLEAMDNSHost_Legacy::status(void) const -{ - bool bStatus = true; - - for (const stcHostInformation& hostInformation : m_HostInformations) - { - if (!((bStatus = hostInformation.m_pHost->probeStatus()))) - { - break; - } - } - return bStatus; -} - - -/* - - SERVICE MANAGEMENT - -*/ - -/* - clsLEAMDNSHost_Legacy::addService - -*/ -clsLEAMDNSHost_Legacy::hMDNSService clsLEAMDNSHost_Legacy::addService(const char* p_pcName, - const char* p_pcService, - const char* p_pcProtocol, - uint16_t p_u16Port) -{ - hMDNSService hResult = 0; - - for (stcHostInformation& hostInformation : m_HostInformations) - { - clsLEAMDNSHost::clsService* pService = hostInformation.m_pHost->addService(p_pcName, p_pcService, p_pcProtocol, p_u16Port /*, default callback*/); - if (pService) - { - if (!hResult) - { - // Store first service handle as result and key - hResult = (hMDNSService)pService; - } - hostInformation.m_HandleToPtr[hResult] = pService; - } - } - return hResult; -} - -/* - clsLEAMDNSHost_Legacy::addService (String) - -*/ -bool clsLEAMDNSHost_Legacy::addService(String p_strServiceName, - String p_strProtocol, - uint16_t p_u16Port) -{ - return (0 != addService(0, p_strServiceName.c_str(), p_strProtocol.c_str(), p_u16Port)); -} - -/* - clsLEAMDNSHost_Legacy::removeService (hService) - -*/ -bool clsLEAMDNSHost_Legacy::removeService(const hMDNSService p_hService) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)it->m_HandleToPtr[p_hService]; - if ((bResult = ((pService) - && (it->m_pHost->removeService(pService))))) - { - it->m_HandleToPtr.erase(p_hService); - } - } - return bResult; -} - -/* - clsLEAMDNSHost_Legacy::removeService (name) - -*/ -bool clsLEAMDNSHost_Legacy::removeService(const char* p_pcInstanceName, - const char* p_pcServiceName, - const char* p_pcProtocol) -{ - hMDNSService hService = 0; - return (((hService = (m_HostInformations.empty() - ? 0 - : (hMDNSService)m_HostInformations.front().m_pHost->findService(p_pcInstanceName, p_pcServiceName, p_pcProtocol)))) - && (removeService(hService))); -} - -/* - clsLEAMDNSHost_Legacy::setServiceName - -*/ -bool clsLEAMDNSHost_Legacy::setServiceName(const hMDNSService p_hService, - const char* p_pcInstanceName) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)it->m_HandleToPtr[p_hService]; - bResult = ((pService) - && (pService->setInstanceName(p_pcInstanceName))); - } - return bResult; -} - -/* - clsLEAMDNSHost_Legacy::setInstanceName - -*/ -void clsLEAMDNSHost_Legacy::setInstanceName(const char* p_pcInstanceName) -{ - for (stcHostInformation& hostInformation : m_HostInformations) - { - hostInformation.m_pHost->setDefaultInstanceName(p_pcInstanceName); - } -} - -/* - clsLEAMDNSHost_Legacy::setInstanceName (String) - -*/ -void clsLEAMDNSHost_Legacy::setInstanceName(const String& p_strHostname) -{ - setInstanceName(p_strHostname.c_str()); -} - -/* - clsLEAMDNSHost_Legacy::serviceName - -*/ -const char* clsLEAMDNSHost_Legacy::serviceName(const hMDNSService p_hService) const -{ - const clsLEAMDNSHost::clsService* pService = 0; - return (m_HostInformations.empty() - ? 0 - : (((pService = (const clsLEAMDNSHost::clsService*)(m_HostInformations.front().m_HandleToPtr.at(p_hService)))) - ? pService->instanceName() - : 0)); -} - -/* - clsLEAMDNSHost_Legacy::service - -*/ -const char* clsLEAMDNSHost_Legacy::service(const hMDNSService p_hService) const -{ - const clsLEAMDNSHost::clsService* pService = 0; - return (m_HostInformations.empty() - ? 0 - : (((pService = (const clsLEAMDNSHost::clsService*)m_HostInformations.front().m_HandleToPtr.at(p_hService))) - ? pService->type() - : 0)); -} - -/* - clsLEAMDNSHost_Legacy::serviceProtocol - -*/ -const char* clsLEAMDNSHost_Legacy::serviceProtocol(const hMDNSService p_hService) const -{ - const clsLEAMDNSHost::clsService* pService = 0; - return (m_HostInformations.empty() - ? 0 - : (((pService = (const clsLEAMDNSHost::clsService*)m_HostInformations.front().m_HandleToPtr.at(p_hService))) - ? pService->protocol() - : 0)); -} - -/* - clsLEAMDNSHost_Legacy::serviceStatus - -*/ -bool clsLEAMDNSHost_Legacy::serviceStatus(const hMDNSService p_hService) const -{ - const clsLEAMDNSHost::clsService* pService = 0; - return (m_HostInformations.empty() - ? false - : (((pService = (const clsLEAMDNSHost::clsService*)m_HostInformations.front().m_HandleToPtr.at(p_hService))) - ? pService->probeStatus() - : false)); -} - - -/* - - SERVICE TXT MANAGEMENT - -*/ - -/* - clsLEAMDNSHost_Legacy::addServiceTxt (char*) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - const char* p_pcValue) -{ - return _addServiceTxt(p_hService, p_pcKey, p_pcValue, false); -} - -/* - clsLEAMDNSHost_Legacy::addServiceTxt (uint32_t) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint32_t p_u32Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_u32Value, false); -} - -/* - clsLEAMDNSHost_Legacy::addServiceTxt (uint16_t) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint16_t p_u16Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_u16Value, false); -} - -/* - clsLEAMDNSHost_Legacy::addServiceTxt (uint8_t) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint8_t p_u8Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_u8Value, false); -} - -/* - clsLEAMDNSHost_Legacy::addServiceTxt (int32_t) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int32_t p_i32Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_i32Value, false); -} - -/* - clsLEAMDNSHost_Legacy::addServiceTxt (int16_t) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int16_t p_i16Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_i16Value, false); -} - -/* - clsLEAMDNSHost_Legacy::addServiceTxt (int8_t) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int8_t p_i8Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_i8Value, false); -} - -/* - clsLEAMDNSHost_Legacy::addServiceTxt (legacy) - -*/ -bool clsLEAMDNSHost_Legacy::addServiceTxt(const char* p_pcService, - const char* p_pcProtocol, - const char* p_pcKey, - const char* p_pcValue) -{ - hMDNSService hService = 0; - return (((hService = (m_HostInformations.empty() - ? 0 - : (hMDNSService)m_HostInformations.front().m_pHost->findService(0, p_pcService, p_pcProtocol)))) - && (_addServiceTxt(hService, p_pcKey, p_pcValue, false))); -} - -/* - clsLEAMDNSHost_Legacy::addServiceTxt (legacy, String) - -*/ -bool clsLEAMDNSHost_Legacy::addServiceTxt(String p_strService, - String p_strProtocol, - String p_strKey, - String p_strValue) -{ - return addServiceTxt(p_strService.c_str(), p_strProtocol.c_str(), p_strKey.c_str(), p_strValue.c_str()); -} - -/* - clsLEAMDNSHost_Legacy::removeServiceTxt (hTxt) - -*/ -bool clsLEAMDNSHost_Legacy::removeServiceTxt(const hMDNSService p_hService, - const hMDNSTxt p_hTxt) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)it->m_HandleToPtr[p_hService]; - clsLEAMDNSHost::clsServiceTxt* pTxt = (clsLEAMDNSHost::clsServiceTxt*)it->m_HandleToPtr[p_hTxt]; - if ((bResult = ((pService) - && (pTxt) - && (pService->removeServiceTxt(pTxt))))) - { - it->m_HandleToPtr.erase(p_hTxt); - } - } - return bResult; -} - -/* - clsLEAMDNSHost_Legacy::removeServiceTxt (char*) - -*/ -bool clsLEAMDNSHost_Legacy::removeServiceTxt(const hMDNSService p_hService, - const char* p_pcKey) -{ - clsLEAMDNSHost::clsService* pService = 0; - clsLEAMDNSHost::clsServiceTxt* pTxt = 0; - return (((pService = (m_HostInformations.empty() - ? 0 - : (clsLEAMDNSHost::clsService*)m_HostInformations.front().m_HandleToPtr[p_hService]))) - && ((pTxt = pService->findServiceTxt(p_pcKey))) - && (removeServiceTxt(p_hService, (hMDNSTxt)pTxt))); -} - -/* - clsLEAMDNSHost_Legacy::removeServiceTxt (char*) - -*/ -bool clsLEAMDNSHost_Legacy::removeServiceTxt(const char* p_pcInstanceName, - const char* p_pcServiceName, - const char* p_pcProtocol, - const char* p_pcKey) -{ - hMDNSService hService = 0; - return (((hService = (m_HostInformations.empty() - ? 0 - : (hMDNSService)m_HostInformations.front().m_pHost->findService(p_pcInstanceName, p_pcServiceName, p_pcProtocol)))) - && (removeServiceTxt(hService, p_pcKey))); -} - -/* - clsLEAMDNSHost_Legacy::setDynamicServiceTxtCallback (global) - -*/ -bool clsLEAMDNSHost_Legacy::setDynamicServiceTxtCallback(MDNSDynamicServiceTxtCallbackFn p_fnCallback) -{ - bool bResult = true; - - if ((bResult = m_HostInformations.empty())) - { - // The service handles of the first host are the keys in the HostInformations.HandleToPtr map - for (const clsLEAMDNSHost::clsService* pService : m_HostInformations.front().m_pHost->services()) - { - if (!((bResult = setDynamicServiceTxtCallback((hMDNSService)pService, p_fnCallback)))) - { - break; - } - } - } - return bResult; -} - -/* - clsLEAMDNSHost_Legacy::setDynamicServiceTxtCallback (service) - -*/ -bool clsLEAMDNSHost_Legacy::setDynamicServiceTxtCallback(const hMDNSService p_hService, - MDNSDynamicServiceTxtCallbackFn p_fnCallback) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - clsLEAMDNSHost::clsService* pService = (clsLEAMDNSHost::clsService*)it->m_HandleToPtr[p_hService]; - bResult = pService->setDynamicServiceTxtCallback([p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/)->void - { - if (p_fnCallback) // void(const hMDNSService p_hService) - { - p_fnCallback(p_hService); - } - }); - } - return bResult; -} - -/* - clsLEAMDNSHost_Legacy::addDynamicServiceTxt (char*) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, - const char* p_pcKey, - const char* p_pcValue) -{ - return _addServiceTxt(p_hService, p_pcKey, p_pcValue, true); -} - -/* - clsLEAMDNSHost_Legacy::addDynamicServiceTxt (uint32) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, - const char* p_pcKey, - uint32_t p_u32Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_u32Value, true); -} - -/* - clsLEAMDNSHost_Legacy::addDynamicServiceTxt (uint16) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, - const char* p_pcKey, - uint16_t p_u16Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_u16Value, true); -} - -/* - clsLEAMDNSHost_Legacy::addDynamicServiceTxt (uint8) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, - const char* p_pcKey, - uint8_t p_u8Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_u8Value, true); -} - -/* - clsLEAMDNSHost_Legacy::addDynamicServiceTxt (int32) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, - const char* p_pcKey, - int32_t p_i32Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_i32Value, true); -} - -/* - clsLEAMDNSHost_Legacy::addDynamicServiceTxt (int16) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, - const char* p_pcKey, - int16_t p_i16Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_i16Value, true); -} - -/* - clsLEAMDNSHost_Legacy::addDynamicServiceTxt (int8) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::addDynamicServiceTxt(hMDNSService p_hService, - const char* p_pcKey, - int8_t p_i8Value) -{ - return _addServiceTxt(p_hService, p_pcKey, p_i8Value, true); -} - - -/* - - STATIC QUERY - -*/ - -/* - clsLEAMDNSHost_Legacy::queryService - - This will take p_u16Timeout millisec for every host! - -*/ -uint32_t clsLEAMDNSHost_Legacy::queryService(const char* p_pcService, - const char* p_pcProtocol, - const uint16_t p_u16Timeout /*= MDNS_QUERYSERVICES_WAIT_TIME*/) -{ - uint32_t u32Answers = 0; - - for (stcHostInformation& hostInformation : m_HostInformations) - { - u32Answers += (hostInformation.m_pHost->queryService(p_pcService, p_pcProtocol, p_u16Timeout)).size(); - } - return u32Answers; -} - -/* - clsLEAMDNSHost_Legacy::removeQuery - -*/ -bool clsLEAMDNSHost_Legacy::removeQuery(void) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - bResult = it->m_pHost->removeQuery(); - } - return bResult; -} - -/* - clsLEAMDNSHost_Legacy::queryService - -*/ -uint32_t clsLEAMDNSHost_Legacy::queryService(String p_strService, - String p_strProtocol) -{ - return queryService(p_strService.c_str(), p_strProtocol.c_str()); -} - -/* - clsLEAMDNSHost_Legacy::answerHostname - -*/ -const char* clsLEAMDNSHost_Legacy::answerHostname(const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_u32AnswerIndex); - return (answerAccessor.serviceDomainAvailable() - ? answerAccessor.serviceDomain() - : 0); -} - -/* - clsLEAMDNSHost_Legacy::answerIP - -*/ -IPAddress clsLEAMDNSHost_Legacy::answerIP(const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_u32AnswerIndex); - return (answerAccessor.IPv4AddressAvailable() - ? answerAccessor.IPv4Addresses()[0] - : IPAddress()); -} - -/* - clsLEAMDNSHost_Legacy::answerPort - -*/ -uint16_t clsLEAMDNSHost_Legacy::answerPort(const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_u32AnswerIndex); - return (answerAccessor.hostPortAvailable() - ? answerAccessor.hostPort() - : 0); -} - -/* - clsLEAMDNSHost_Legacy::hostname - -*/ -String clsLEAMDNSHost_Legacy::hostname(const uint32_t p_u32AnswerIndex) -{ - return String(answerHostname(p_u32AnswerIndex)); -} - -/* - clsLEAMDNSHost_Legacy::IP - -*/ -IPAddress clsLEAMDNSHost_Legacy::IP(const uint32_t p_u32AnswerIndex) -{ - return answerIP(p_u32AnswerIndex); -} - -/* - clsLEAMDNSHost_Legacy::port - -*/ -uint16_t clsLEAMDNSHost_Legacy::port(const uint32_t p_u32AnswerIndex) -{ - return answerPort(p_u32AnswerIndex); -} - - -/* - - DYNAMIC QUERY - -*/ - -/* - clsLEAMDNSHost_Legacy::installServiceQuery - -*/ -clsLEAMDNSHost_Legacy::hMDNSServiceQuery clsLEAMDNSHost_Legacy::installServiceQuery(const char* p_pcService, - const char* p_pcProtocol, - MDNSServiceQueryCallbackFn p_fnCallback) -{ - hMDNSServiceQuery hResult = 0; - - for (stcHostInformation& hostInformation : m_HostInformations) - { - clsLEAMDNSHost::clsQuery* pQuery = hostInformation.m_pHost->installServiceQuery(p_pcService, p_pcProtocol, [this, p_fnCallback](const clsLEAMDNSHost::clsQuery& /*p_Query*/, - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor & p_AnswerAccessor, - clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags, // flags for the updated answer item - bool p_bSetContent)->void - { - if (p_fnCallback) // void(const stcMDNSServiceInfo& p_MDNSServiceInfo, MDNSResponder::AnswerType p_AnswerType, bool p_bSetContent) - { - p_fnCallback(stcMDNSServiceInfo(p_AnswerAccessor), _answerFlagsToAnswerType(p_QueryAnswerTypeFlags), p_bSetContent); - } - }); - if (pQuery) - { - if (!hResult) - { - // Store first query as result and key - hResult = (hMDNSServiceQuery)pQuery; - } - hostInformation.m_HandleToPtr[hResult] = pQuery; - } - } - return hResult; -} - -/* - clsLEAMDNSHost_Legacy::removeServiceQuery - -*/ -bool clsLEAMDNSHost_Legacy::removeServiceQuery(clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - if ((bResult = it->m_pHost->removeQuery((clsLEAMDNSHost::clsQuery*)it->m_HandleToPtr[p_hServiceQuery]))) - { - it->m_HandleToPtr.erase(p_hServiceQuery); - } - } - return bResult; -} - -/* - clsLEAMDNSHost_Legacy::answerCount - -*/ -uint32_t clsLEAMDNSHost_Legacy::answerCount(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery) -{ - uint32_t u32AnswerCount = 0; - - for (stcHostInformation& hostInformation : m_HostInformations) - { - clsLEAMDNSHost::clsQuery* pQuery = (clsLEAMDNSHost::clsQuery*)hostInformation.m_HandleToPtr[p_hServiceQuery]; - if (pQuery) - { - u32AnswerCount += pQuery->answerCount(); - } - else - { - u32AnswerCount = 0; - break; - } - } - return u32AnswerCount; -} - -/* - clsLEAMDNSHost_Legacy::answerInfo - -*/ -std::vector clsLEAMDNSHost_Legacy::answerInfo(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery) -{ - std::vector serviceInfos; - - if (p_hServiceQuery) - { - for (stcHostInformation& hostInformation : m_HostInformations) - { - clsLEAMDNSHost::clsQuery* pQuery = (clsLEAMDNSHost::clsQuery*)hostInformation.m_HandleToPtr[p_hServiceQuery]; - if (pQuery) - { - for (clsLEAMDNSHost::clsQuery::clsAnswerAccessor& answerAccessor : pQuery->answerAccessors()) - { - serviceInfos.push_back(stcMDNSServiceInfo(answerAccessor)); - } - } - else - { - serviceInfos.clear(); - break; - } - } - } - return serviceInfos; -} - -/* - clsLEAMDNSHost_Legacy::answerServiceDomain - -*/ -const char* clsLEAMDNSHost_Legacy::answerServiceDomain(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); - return (answerAccessor.serviceDomainAvailable() - ? answerAccessor.serviceDomain() - : 0); -} - -/* - clsLEAMDNSHost_Legacy::hasAnswerHostDomain - -*/ -bool clsLEAMDNSHost_Legacy::hasAnswerHostDomain(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).hostDomainAvailable(); -} - -/* - clsLEAMDNSHost_Legacy::answerHostDomain - -*/ -const char* clsLEAMDNSHost_Legacy::answerHostDomain(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); - return (answerAccessor.hostDomainAvailable() - ? answerAccessor.hostDomain() - : 0); -} - -#ifdef MDNS_IP4_SUPPORT -/* - clsLEAMDNSHost_Legacy::hasAnswerIP4Address - -*/ -bool clsLEAMDNSHost_Legacy::hasAnswerIP4Address(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).IPv4AddressAvailable(); -} - -/* - clsLEAMDNSHost_Legacy::answerIP4AddressCount - -*/ -uint32_t clsLEAMDNSHost_Legacy::answerIP4AddressCount(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); - return (answerAccessor.IPv4AddressAvailable() - ? answerAccessor.IPv4Addresses().size() - : 0); -} - -/* - clsLEAMDNSHost_Legacy::answerIP4Address - -*/ -IPAddress clsLEAMDNSHost_Legacy::answerIP4Address(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex, - const uint32_t p_u32AddressIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); - return (answerAccessor.IPv4AddressAvailable() - ? answerAccessor.IPv4Addresses()[p_u32AddressIndex] - : IPAddress()); -} -#endif -#ifdef MDNS_IP6_SUPPORT -/* - clsLEAMDNSHost_Legacy::hasAnswerIP6Address - -*/ -bool clsLEAMDNSHost_Legacy::hasAnswerIP6Address(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).IPv6AddressAvailable(); -} - -/* - clsLEAMDNSHost_Legacy::answerIP6AddressCount - -*/ -uint32_t clsLEAMDNSHost_Legacy::answerIP6AddressCount(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); - return (answerAccessor.IPv6AddressAvailable() - ? answerAccessor.IPv6Addresses().size() - : 0); -} - -/* - clsLEAMDNSHost_Legacy::answerIP6Address - -*/ -IPAddress clsLEAMDNSHost_Legacy::answerIP6Address(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex, - const uint32_t p_u32AddressIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); - return (answerAccessor.IPv6AddressAvailable() - ? answerAccessor.IPv6Addresses()[p_u32AddressIndex] - : IPAddress()); -} -#endif - -/* - clsLEAMDNSHost_Legacy::hasAnswerPort - -*/ -bool clsLEAMDNSHost_Legacy::hasAnswerPort(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).hostPortAvailable(); -} - -/* - clsLEAMDNSHost_Legacy::answerPort - -*/ -uint16_t clsLEAMDNSHost_Legacy::answerPort(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); - return (answerAccessor.hostPortAvailable() - ? answerAccessor.hostPort() - : 0); -} - -/* - clsLEAMDNSHost_Legacy::hasAnswerTxts - -*/ -bool clsLEAMDNSHost_Legacy::hasAnswerTxts(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - return _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex).txtsAvailable(); -} - -/* - clsLEAMDNSHost_Legacy::answerHostDomain - - Get the TXT items as a ';'-separated string -*/ -const char* clsLEAMDNSHost_Legacy::answerTxts(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor answerAccessor = _getAnswerAccessor(p_hServiceQuery, p_u32AnswerIndex); - return (answerAccessor.txtsAvailable() - ? answerAccessor.txts() - : 0); -} - - -/* - - HOST/SERVICE PROBE CALLBACKS - -*/ - -/* - clsLEAMDNSHost_Legacy::setHostProbeResultCallback - -*/ -bool clsLEAMDNSHost_Legacy::setHostProbeResultCallback(clsLEAMDNSHost_Legacy::MDNSHostProbeResultCallbackFn p_fnCallback) -{ - bool bResult = true; - - clsLEAMDNSHost::stProbeResultCallback = - [p_fnCallback](clsLEAMDNSHost& /*p_rHost*/, - const char* p_pcDomainName, - bool p_bProbeResult)->void - { - if (p_fnCallback) // void(const char* p_pcDomainName, bool p_bProbeResult) - { - p_fnCallback(p_pcDomainName, p_bProbeResult); - } - }; - - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - bResult = it->m_pHost->setProbeResultCallback(clsLEAMDNSHost::stProbeResultCallback); - } - return bResult; -} - -/* - clsLEAMDNSHost_Legacy::setHostProbeResultCallback - -*/ -bool clsLEAMDNSHost_Legacy::setHostProbeResultCallback(clsLEAMDNSHost_Legacy::MDNSHostProbeResultCallbackFn2 p_fnCallback) -{ - bool bResult = true; - - clsLEAMDNSHost::stProbeResultCallback = - [this, p_fnCallback](clsLEAMDNSHost& /*p_rHost*/, - const char* p_pcDomainName, - bool p_bProbeResult)->void - { - if (p_fnCallback) // void(clsLEAMDNSHost_Legacy* p_pMDNSResponder, const char* p_pcDomainName, bool p_bProbeResult) - { - p_fnCallback(this, p_pcDomainName, p_bProbeResult); - } - }; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - bResult = it->m_pHost->setProbeResultCallback(clsLEAMDNSHost::stProbeResultCallback); - } - return bResult; -} - -/* - clsLEAMDNSHost_Legacy::setServiceProbeResultCallback - -*/ -bool clsLEAMDNSHost_Legacy::setServiceProbeResultCallback(const clsLEAMDNSHost_Legacy::hMDNSService p_hService, - clsLEAMDNSHost_Legacy::MDNSServiceProbeResultCallbackFn p_fnCallback) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - clsLEAMDNSHost::clsService* pService = 0; - bResult = (((pService = (clsLEAMDNSHost::clsService*)it->m_HandleToPtr[p_hService])) - && (pService->setProbeResultCallback( - - [this, p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/, - const char* p_pcInstanceName, - bool p_bProbeResult)->void - { - if (p_fnCallback) // void(const char* p_pcServiceName, const hMDNSService p_hMDNSService, bool p_bProbeResult) - { - p_fnCallback(p_pcInstanceName, p_hService, p_bProbeResult); - } - } - ))); - } - return bResult; -} - -/* - clsLEAMDNSHost_Legacy::setServiceProbeResultCallback - -*/ -bool clsLEAMDNSHost_Legacy::setServiceProbeResultCallback(const clsLEAMDNSHost_Legacy::hMDNSService p_hService, - clsLEAMDNSHost_Legacy::MDNSServiceProbeResultCallbackFn2 p_fnCallback) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - clsLEAMDNSHost::clsService* pService = 0; - bResult = (((pService = (clsLEAMDNSHost::clsService*)it->m_HandleToPtr[p_hService])) - && (pService->setProbeResultCallback([this, p_hService, p_fnCallback](clsLEAMDNSHost::clsService& /*p_rMDNSService*/, - const char* p_pcInstanceName, - bool p_bProbeResult)->void - { - if (p_fnCallback) // void((clsLEAMDNSHost_Legacy* p_pMDNSResponder, const char* p_pcServiceName, const hMDNSService p_hMDNSService, bool p_bProbeResult) - { - p_fnCallback(this, p_pcInstanceName, p_hService, p_bProbeResult); - } - }))); - } - return bResult; -} - - -/* - - PROCESS - -*/ - -/* - clsLEAMDNSHost_Legacy::notifyAPChange - -*/ -bool clsLEAMDNSHost_Legacy::notifyAPChange(void) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - bResult = it->m_pHost->restart(); - } - return bResult; -} - -/* - clsLEAMDNSHost_Legacy::update - -*/ -bool clsLEAMDNSHost_Legacy::update(void) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - bResult = it->m_pHost->update(); - } - return bResult; -} - -/* - clsLEAMDNSHost_Legacy::announce - -*/ -bool clsLEAMDNSHost_Legacy::announce(void) -{ - bool bResult = true; - - for (stcHostInformation::list::iterator it = m_HostInformations.begin(); ((bResult) && (it != m_HostInformations.end())); ++it) - { - bResult = it->m_pHost->announce(true, true); - } - return bResult; -} - -/* - clsLEAMDNSHost_Legacy::enableArduino - -*/ -clsLEAMDNSHost_Legacy::hMDNSService clsLEAMDNSHost_Legacy::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; -} - -/* - clsLEAMDNSHost_Legacy::indexDomain - -*/ -bool clsLEAMDNSHost_Legacy::indexDomain(char*& p_rpcDomain, - const char* p_pcDivider /*= "-"*/, - const char* p_pcDefaultDomain /*= 0*/) -{ - bool bResult = false; - - const char* cpcDomainName = clsLEAMDNSHost::indexDomainName(p_rpcDomain, p_pcDivider, p_pcDefaultDomain); - delete[] p_rpcDomain; - p_rpcDomain = 0; - if ((cpcDomainName) - && ((p_rpcDomain = new char[strlen(cpcDomainName) + 1]))) - { - strcpy(p_rpcDomain, cpcDomainName); - bResult = true; - } - return bResult; -} - - -/* - - INTERNAL HELPERS - -*/ - -/* - clsLEAMDNSHost_Legacy::_addServiceTxt (char*) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - const char* p_pcValue, - bool p_bDynamic) -{ - hMDNSTxt hResult = 0; - - for (stcHostInformation& hostInformation : m_HostInformations) - { - clsLEAMDNSHost::clsService* pService = 0; - clsLEAMDNSHost::clsServiceTxt* pTxt = 0; - if (((pService = (clsLEAMDNSHost::clsService*)hostInformation.m_HandleToPtr[p_hService])) - && ((pTxt = (p_bDynamic - ? pService->addDynamicServiceTxt(p_pcKey, p_pcValue) - : pService->addServiceTxt(p_pcKey, p_pcValue))))) - { - if (!hResult) - { - hResult = (hMDNSTxt)pTxt; - } - hostInformation.m_HandleToPtr[hResult] = pTxt; - } - } - return hResult; -} - -/* - clsLEAMDNSHost_Legacy::_addServiceTxt (uint32_t) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint32_t p_u32Value, - bool p_bDynamic) -{ - char acValueBuffer[16]; // 32-bit max 10 digits - *acValueBuffer = 0; - sprintf(acValueBuffer, "%u", p_u32Value); - - return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); -} - -/* - clsLEAMDNSHost_Legacy::_addServiceTxt (uint16_t) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint16_t p_u16Value, - bool p_bDynamic) -{ - char acValueBuffer[8]; // 16-bit max 5 digits - *acValueBuffer = 0; - sprintf(acValueBuffer, "%hu", p_u16Value); - - return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); -} - -/* - clsLEAMDNSHost_Legacy::_addServiceTxt (uint8_t) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint8_t p_u8Value, - bool p_bDynamic) -{ - char acValueBuffer[8]; // 8-bit max 3 digits - *acValueBuffer = 0; - sprintf(acValueBuffer, "%hhu", p_u8Value); - - return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); -} - -/* - clsLEAMDNSHost_Legacy::_addServiceTxt (int32_t) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int32_t p_i32Value, - bool p_bDynamic) -{ - char acValueBuffer[16]; // 32-bit max 11 digits - *acValueBuffer = 0; - sprintf(acValueBuffer, "%i", p_i32Value); - - return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); -} - -/* - clsLEAMDNSHost_Legacy::_addServiceTxt (int16_t) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int16_t p_i16Value, - bool p_bDynamic) -{ - char acValueBuffer[8]; // 16-bit max 6 digits - *acValueBuffer = 0; - sprintf(acValueBuffer, "%hi", p_i16Value); - - return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); -} - -/* - clsLEAMDNSHost_Legacy::_addServiceTxt (int8_t) - -*/ -clsLEAMDNSHost_Legacy::hMDNSTxt clsLEAMDNSHost_Legacy::_addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int8_t p_i8Value, - bool p_bDynamic) -{ - char acValueBuffer[8]; // 8-bit max 4 digits - *acValueBuffer = 0; - sprintf(acValueBuffer, "%hhi", p_i8Value); - - return _addServiceTxt(p_hService, p_pcKey, acValueBuffer, p_bDynamic); -} - -/* - clsLEAMDNSHost_Legacy::_answerFlagsToAnswerType - -*/ -clsLEAMDNSHost_Legacy::AnswerType clsLEAMDNSHost_Legacy::_answerFlagsToAnswerType(clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags) const -{ - AnswerType answerType = AnswerType::Unknown; - - if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Unknown) & p_QueryAnswerTypeFlags) - { - answerType = AnswerType::Unknown; - } - else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::ServiceDomain) & p_QueryAnswerTypeFlags) - { - answerType = AnswerType::ServiceDomain; - } - else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::HostDomain) & p_QueryAnswerTypeFlags) - { - answerType = AnswerType::HostDomainAndPort; - } - else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Port) & p_QueryAnswerTypeFlags) - { - answerType = AnswerType::HostDomainAndPort; - } - else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::Txts) & p_QueryAnswerTypeFlags) - { - answerType = AnswerType::Txt; - } -#ifdef MDNS_IP4_SUPPORT - else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv4Address) & p_QueryAnswerTypeFlags) - { - answerType = AnswerType::IP4Address; - } -#endif -#ifdef MDNS_IP6_SUPPORT - else if (static_cast(clsLEAMDNSHost::clsQuery::clsAnswer::enuQueryAnswerType::IPv6Address) & p_QueryAnswerTypeFlags) - { - answerType = AnswerType::IP6Address; - } -#endif - return answerType; -} - -/* - clsLEAMDNSHost_Legacy::_getAnswerAccessor - -*/ -clsLEAMDNSHost_Legacy::clsLEAMDNSHost::clsQuery::clsAnswerAccessor clsLEAMDNSHost_Legacy::_getAnswerAccessor(const uint32_t p_u32AnswerIndex) -{ - uint32_t u32AnswerIndexWithoutOffset = p_u32AnswerIndex; - for (stcHostInformation& hostInformation : m_HostInformations) - { - clsLEAMDNSHost::clsQuery* pQuery = hostInformation.m_pHost->getQuery(); - if (pQuery) - { - if (pQuery->answerCount() > u32AnswerIndexWithoutOffset) - { - return pQuery->answerAccessor(u32AnswerIndexWithoutOffset); - } - else - { - u32AnswerIndexWithoutOffset -= pQuery->answerCount(); - } - } - else - { - break; - } - } - return clsLEAMDNSHost::clsQuery::clsAnswerAccessor(0); -} - -/* - clsLEAMDNSHost_Legacy::_getAnswerAccessor - -*/ -clsLEAMDNSHost_Legacy::clsLEAMDNSHost::clsQuery::clsAnswerAccessor clsLEAMDNSHost_Legacy::_getAnswerAccessor(const clsLEAMDNSHost_Legacy::hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex) -{ - uint32_t u32AnswerIndexWithoutOffset = p_u32AnswerIndex; - for (stcHostInformation& hostInformation : m_HostInformations) - { - clsLEAMDNSHost::clsQuery* pQuery = (clsLEAMDNSHost::clsQuery*)hostInformation.m_HandleToPtr[p_hServiceQuery]; - if (pQuery) - { - if (pQuery->answerCount() > u32AnswerIndexWithoutOffset) - { - return pQuery->answerAccessor(u32AnswerIndexWithoutOffset); - } - else - { - u32AnswerIndexWithoutOffset -= pQuery->answerCount(); - } - } - else - { - break; - } - } - return clsLEAMDNSHost::clsQuery::clsAnswerAccessor(0); -} - - -} // namespace MDNSImplementation - - -} // namespace experimental // esp8266 - - - - - - diff --git a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h b/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h deleted file mode 100644 index 33668e11d1..0000000000 --- a/libraries/ESP8266mDNS/src/LEAmDNS2_Legacy.h +++ /dev/null @@ -1,700 +0,0 @@ -/* - LEAmDNS2_Legacy.h - (c) 2020, 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 basically 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-level full-name compression is implemented - - Probing host and service domains for uniqueness in the local network - - Tiebreaking while probing is supported in a very minimalistic way (the 'higher' IP address wins the tiebreak) - - Announcing available services after successful probing - - Using fixed service TXT items or - - Using dynamic service TXT items for presented services (via callback) - - Remove services (and un-announcing them to the observers by sending goodbye-messages) - - Static queries for 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 'clsLEAMDNSHost_Legacy::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(clsLEAMDNSHost_Legacy* p_MDNSResponder, const char* p_pcDomain, clsLEAMDNSHost_Legacy: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(clsLEAMDNSHost_Legacy* 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; - - // 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); - - // 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); - - // 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 = clsConsts::u16StaticQueryWaitTime); - 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) - */ - using hMDNSServiceQuery = const void*; - - /** - enuServiceQueryAnswerType - */ - using enuServiceQueryAnswerType = enum _enuServiceQueryAnswerType - { - ServiceQueryAnswerType_Unknown = 0, - 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 - }; - - /** - AnswerType (std::map compatible version) - */ - enum class AnswerType : uint32_t - { - Unknown = ServiceQueryAnswerType_Unknown, - ServiceDomain = ServiceQueryAnswerType_ServiceDomain, - HostDomainAndPort = ServiceQueryAnswerType_HostDomainAndPort, - Txt = ServiceQueryAnswerType_Txts, -#ifdef MDNS_IP4_SUPPORT - IP4Address = ServiceQueryAnswerType_IP4Address, -#endif -#ifdef MDNS_IP6_SUPPORT - IP6Address = ServiceQueryAnswerType_IP6Address -#endif - }; - - /** - stcMDNSServiceInfo - */ - struct stcMDNSServiceInfo - { - stcMDNSServiceInfo(const clsLEAMDNSHost::clsQuery::clsAnswerAccessor& p_rAnswerAccessor) - : m_rAnswerAccessor(p_rAnswerAccessor) {}; - /** - stcCompareKey - */ - struct stcCompareKey - { - /* - operator () - */ - bool operator()(char const* p_pA, char const* p_pB) const - { - return (0 > strcmp(p_pA, p_pB)); - } - }; - /** - clsKeyValueMap - */ - using clsKeyValueMap = std::map; - - protected: - const clsLEAMDNSHost::clsQuery::clsAnswerAccessor& m_rAnswerAccessor; - clsKeyValueMap m_KeyValueMap; - - public: - /* - serviceDomain - */ - const char* serviceDomain(void) const - { - return (m_rAnswerAccessor.serviceDomainAvailable() - ? m_rAnswerAccessor.serviceDomain() - : nullptr); - } - /* - hostDomainAvailable - */ - bool hostDomainAvailable(void) const - { - return m_rAnswerAccessor.hostDomainAvailable(); - } - /* - hostDomain - */ - const char* hostDomain(void) const - { - return (hostDomainAvailable() - ? m_rAnswerAccessor.hostDomain() - : nullptr); - } - /* - hostPortAvailable - */ - bool hostPortAvailable(void) const - { - return m_rAnswerAccessor.hostPortAvailable(); - } - /* - hostPort - */ - uint16_t hostPort(void) const - { - return (hostPortAvailable() - ? m_rAnswerAccessor.hostPort() - : 0); - } -#ifdef MDNS_IP4_SUPPORT - /* - IP4AddressAvailable - */ - bool IP4AddressAvailable(void) const - { - return m_rAnswerAccessor.IPv4AddressAvailable(); - } - /* - IP4Addresses - */ - std::vector IP4Adresses(void) const - { - return (IP4AddressAvailable() - ? m_rAnswerAccessor.IPv4Addresses() - : std::vector()); - } -#endif -#ifdef MDNS_IP6_SUPPORT - /* - IP6AddressAvailable - */ - bool IP6AddressAvailable(void) const - { - return m_rAnswerAccessor.IPv6AddressAvailable(); - } - /* - IP6Addresses - */ - std::vector IP6Adresses(void) const - { - return (IP6AddressAvailable() - ? m_rAnswerAccessor.IPv6Addresses() - : std::vector()); - } -#endif - /* - txtAvailable - */ - bool txtAvailable(void) const - { - return m_rAnswerAccessor.txtsAvailable(); - } - /* - strKeyValue -> abc=def;hij=klm; - */ - const char* strKeyValue(void) const - { - // TODO - return nullptr; - } - /* - keyValues -> abc=def hij=klm ... - */ - const clsKeyValueMap& keyValues(void) - { - if ((txtAvailable()) && - (0 == m_KeyValueMap.size())) - { - for (auto kv : m_rAnswerAccessor.txtKeyValues()) - { - m_KeyValueMap.emplace(std::pair(kv.first, kv.second)); - } - } - return m_KeyValueMap; - } - /* - value (abc)->def - */ - const char* value(const char* p_pcKey) const - { - return m_rAnswerAccessor.txtValue(p_pcKey); - } - }; - - /** - MDNSServiceQueryCallbackFn - - Callback function for received answers for dynamic service queries - */ - using MDNSServiceQueryCallbackFn = - std::function; - - // 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); - // Remove a dynamic service query - bool removeServiceQuery(hMDNSServiceQuery p_hServiceQuery); - - uint32_t answerCount(const hMDNSServiceQuery p_hServiceQuery); - std::vector answerInfo(const clsLEAMDNSHost_Legacy::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); - - /** - MDNSHostProbeResultCallbackFn/2 - Callback function for host domain probe results - */ - using MDNSHostProbeResultCallbackFn = - std::function; - - using MDNSHostProbeResultCallbackFn2 = - std::function; - - // Set a callback function for host probe results - // The callback function is called, when the probeing for the host domain - // succeededs or fails. - // In case of failure, the failed domain name should be changed. - bool setHostProbeResultCallback(MDNSHostProbeResultCallbackFn p_fnCallback); - bool setHostProbeResultCallback(MDNSHostProbeResultCallbackFn2 p_fnCallback); - - /** - MDNSServiceProbeResultCallbackFn/2 - Callback function for service domain probe results - */ - using MDNSServiceProbeResultCallbackFn = - std::function; - - using MDNSServiceProbeResultCallbackFn2 = - std::function; - - // Set a service specific probe result callcack - bool setServiceProbeResultCallback(const hMDNSService p_hService, - MDNSServiceProbeResultCallbackFn p_fnCallback); - bool setServiceProbeResultCallback(const hMDNSService p_hService, - MDNSServiceProbeResultCallbackFn2 p_fnCallback); - - // 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: - /** - stcHostInformation - */ - struct stcHostInformation - { - /** - clsHandleToPtrMap - */ - using clsHandleToPtrMap = std::map; - - clsLEAMDNSHost* m_pHost; - clsHandleToPtrMap m_HandleToPtr; - - stcHostInformation(clsLEAMDNSHost* p_pHost) - : m_pHost(p_pHost) - {} - - /** - list - */ - using list = std::list; - }; - - stcHostInformation::list m_HostInformations; - - // HELPERS - hMDNSTxt _addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - const char* p_pcValue, - bool p_bDynamic); - hMDNSTxt _addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint32_t p_u32Value, - bool p_bDynamic); - hMDNSTxt _addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint16_t p_u16Value, - bool p_bDynamic); - hMDNSTxt _addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - uint8_t p_u8Value, - bool p_bDynamic); - hMDNSTxt _addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int32_t p_i32Value, - bool p_bDynamic); - hMDNSTxt _addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int16_t p_i16Value, - bool p_bDynamic); - hMDNSTxt _addServiceTxt(const hMDNSService p_hService, - const char* p_pcKey, - int8_t p_i8Value, - bool p_bDynamic); - - AnswerType _answerFlagsToAnswerType(clsLEAMDNSHost::clsQuery::clsAnswer::typeQueryAnswerType p_QueryAnswerTypeFlags) const; - - clsLEAMDNSHost::clsQuery::clsAnswerAccessor _getAnswerAccessor(const uint32_t p_u32AnswerIndex); - clsLEAMDNSHost::clsQuery::clsAnswerAccessor _getAnswerAccessor(const hMDNSServiceQuery p_hServiceQuery, - const uint32_t p_u32AnswerIndex); - -}; - - -} // namespace MDNSImplementation - - -} // namespace experimental - - -#endif // __LEAMDNS2HOST_LEGACY_H__ - - - - - - From 4e97d1e864f9908cfe85d99c2f25b2cbcb1d95db Mon Sep 17 00:00:00 2001 From: david gauchard Date: Thu, 24 Sep 2020 23:47:05 +0200 Subject: [PATCH 152/152] remove obsolete comments --- libraries/ArduinoOTA/ArduinoOTA.cpp | 1 - .../examples/LEAmDNS/mDNS_Clock/mDNS_Clock.ino | 15 --------------- .../LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino | 16 +--------------- .../mDNS_ServiceMonitor/mDNS_ServiceMonitor.ino | 13 ------------- .../mDNS_ServiceMonitor_v2.ino | 12 ------------ 5 files changed, 1 insertion(+), 56 deletions(-) diff --git a/libraries/ArduinoOTA/ArduinoOTA.cpp b/libraries/ArduinoOTA/ArduinoOTA.cpp index 00a60f214f..83cc5c7905 100644 --- a/libraries/ArduinoOTA/ArduinoOTA.cpp +++ b/libraries/ArduinoOTA/ArduinoOTA.cpp @@ -21,7 +21,6 @@ extern "C" { #include "include/UdpContext.h" #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS) -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include #endif diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock/mDNS_Clock.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock/mDNS_Clock.ino index c657d49bdd..ec0320fdb2 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock/mDNS_Clock.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock/mDNS_Clock.ino @@ -36,22 +36,7 @@ #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 - -//#define MDNS2_EXPERIMENTAL_V1COMPAT #include /* diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino index 6e9e61e467..a098bcb3e1 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock_v2/mDNS_Clock_v2.ino @@ -37,30 +37,16 @@ #include #include #include +#include // uses API MDNSApiVersion::LEAv2 - -/* - Include the clsLEAMDNSHost (the library needs also to be included) - As LEA clsLEAMDNSHost is experimantal in the ESP8266 environment currently, the - legacy clsLEAMDNSHost is defaulted in the include file. - There are two ways to access LEA clsLEAMDNSHost: - 1. Prepend every declaration and call to global declarations or functions with the namespace, like: - 'LEAmDNS::clsLEAMDNSHost::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. - -*/ #define NO_GLOBAL_MDNS // our MDNS is defined below #include -#include /* Global defines and vars */ - #define TIMEZONE_OFFSET 1 // CET #define DST_OFFSET 1 // CEST #define UPDATE_CYCLE (1 * 1000) // every second diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor/mDNS_ServiceMonitor.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor/mDNS_ServiceMonitor.ino index 98269fb48e..167a968c71 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor/mDNS_ServiceMonitor.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor/mDNS_ServiceMonitor.ino @@ -33,19 +33,6 @@ #include #include #include - -/* - Include the MDNSResponder (the library needs to be included also) - As LEA MDNSResponder is experimantal in the ESP8266 environment currently, the - legacy MDNSResponder is defaulted in th include file. - There are two ways to access LEA MDNSResponder: - 1. Prepend every declaration and call to global declarations or functions with the namespace, like: - 'LEAmDNS:MDNSResponder::hMDNSService hMDNSService;' - This way is used in the example. But be careful, if the namespace declaration is missing - somewhere, the call might go to the legacy implementation... - 2. Open 'ESP8266mDNS.h' and set LEAmDNS to default. - -*/ #include /* diff --git a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino index 59f213fd83..1d24990be0 100644 --- a/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino +++ b/libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_ServiceMonitor_v2/mDNS_ServiceMonitor_v2.ino @@ -45,18 +45,6 @@ #include #include -/* - Include the clsLEAMDNSHost (the library needs to be included also) - As LEA clsLEAMDNSHost is experimental in the ESP8266 environment currently, the - legacy clsLEAMDNSHost is defaulted in th include file. - There are two ways to access LEA clsLEAMDNSHost: - 1. Prepend every declaration and call to global declarations or functions with the namespace, like: - 'LEAmDNS:clsLEAMDNSHost::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. - -*/ #define NO_GLOBAL_MDNS // our MDNS is defined below #include