#include #ifdef ESP_PLATFORM #include #include #include #include #if CONFIG_LWIP_IPV6 #include #endif #include #include #include namespace tt::network::mdns { constexpr auto* TAG = "Mdns"; static std::string ipAddrToString(const esp_ip_addr_t& ip) { char buf[64]; if (ip.type == ESP_IPADDR_TYPE_V4) { esp_ip4addr_ntoa(&ip.u_addr.ip4, buf, sizeof(buf)); } else { #if CONFIG_LWIP_IPV6 ip6addr_ntoa_r(reinterpret_cast(&ip.u_addr.ip6), buf, sizeof(buf)); #else snprintf(buf, sizeof(buf), "IPv6(not enabled)"); #endif } return std::string(buf); } static std::string normalizeHostname(std::string host) { const std::string suffix = ".local"; if (host.size() > suffix.size() && host.compare(host.size() - suffix.size(), suffix.size(), suffix) == 0) { host.erase(host.size() - suffix.size()); } if (!host.empty() && host.back() == '.') { host.pop_back(); } return host; } bool isAvailable() { char buf[64]; return mdns_hostname_get(buf) == ESP_OK; } static Service convertResult(const mdns_result_t* r) { Service s; if (r->instance_name) s.instanceName = r->instance_name; if (r->service_type) s.serviceType = r->service_type; if (r->proto) s.proto = r->proto; if (r->hostname) s.hostname = r->hostname; s.port = r->port; s.ttl = r->ttl; for (size_t i = 0; i < r->txt_count; i++) { if (r->txt[i].key) { std::string key = r->txt[i].key; std::string value; if (r->txt[i].value && r->txt_value_len) { value = std::string(r->txt[i].value, r->txt_value_len[i]); } else if (r->txt[i].value) { value = r->txt[i].value; } s.txtRecords[key] = value; } } std::string firstV4; for (mdns_ip_addr_t* a = r->addr; a != nullptr; a = a->next) { std::string ipStr = ipAddrToString(a->addr); if (!ipStr.empty()) { s.addresses.push_back(ipStr); if (firstV4.empty() && a->addr.type == ESP_IPADDR_TYPE_V4) { firstV4 = ipStr; } } } if (!firstV4.empty()) { s.primaryAddress = firstV4; } else if (!s.addresses.empty()) { s.primaryAddress = s.addresses.front(); } return s; } bool browse(const std::string& serviceType, const std::string& proto, uint32_t timeoutMs, size_t maxResults, std::vector& outResults) { if (serviceType.empty() || proto.empty()) { return false; } outResults.clear(); mdns_result_t* results = nullptr; esp_err_t err = mdns_query_ptr(serviceType.c_str(), proto.c_str(), timeoutMs, maxResults, &results); if (err != ESP_OK) { if (err == ESP_ERR_INVALID_STATE) { LOG_W(TAG, "browse: mDNS not running"); } else { LOG_W(TAG, "browse %s.%s failed: %s", serviceType.c_str(), proto.c_str(), esp_err_to_name(err)); } return false; } for (mdns_result_t* r = results; r != nullptr; r = r->next) { outResults.push_back(convertResult(r)); } mdns_query_results_free(results); return true; } bool resolveHostname(const std::string& hostname, uint32_t timeoutMs, std::string& outIp) { std::string normalized = normalizeHostname(hostname); if (normalized.empty()) return false; outIp.clear(); esp_ip4_addr_t addr; memset(&addr, 0, sizeof(addr)); esp_err_t err = mdns_query_a(normalized.c_str(), timeoutMs, &addr); if (err == ESP_OK) { char buf[32]; esp_ip4addr_ntoa(&addr, buf, sizeof(buf)); outIp = buf; return true; } #if CONFIG_LWIP_IPV6 esp_ip6_addr_t addr6; memset(&addr6, 0, sizeof(addr6)); err = mdns_query_aaaa(normalized.c_str(), timeoutMs, &addr6); if (err == ESP_OK) { char buf[64]; ip6addr_ntoa_r(reinterpret_cast(&addr6), buf, sizeof(buf)); outIp = buf; return true; } #endif return false; } } // namespace tt::network::mdns #else // !ESP_PLATFORM — POSIX simulator stub namespace tt::network::mdns { bool isAvailable() { return false; } bool browse(const std::string& serviceType, const std::string& proto, uint32_t timeoutMs, size_t maxResults, std::vector& outResults) { (void)serviceType; (void)proto; (void)timeoutMs; (void)maxResults; outResults.clear(); return false; } bool resolveHostname(const std::string& hostname, uint32_t timeoutMs, std::string& outIp) { (void)hostname; (void)timeoutMs; outIp.clear(); return false; } } // namespace tt::network::mdns #endif