diff --git a/src/network.cpp b/src/network.cpp index b7b18de8..5d56866c 100644 --- a/src/network.cpp +++ b/src/network.cpp @@ -206,4 +206,34 @@ namespace net { return mapped_port; } + + /** + * @brief Returns a string for use as the instance name for mDNS. + * @param hostname The hostname to use for instance name generation. + * @return Hostname-based instance name or "Sunshine" if hostname is invalid. + */ + std::string + mdns_instance_name(const std::string_view &hostname) { + // Start with the unmodified hostname + std::string instancename { hostname.data(), hostname.size() }; + + // Truncate to 63 characters per RFC 6763 section 7.2. + if (instancename.size() > 63) { + instancename.resize(63); + } + + for (auto i = 0; i < instancename.size(); i++) { + // Replace any spaces with dashes + if (instancename[i] == ' ') { + instancename[i] = '-'; + } + else if (!std::isalnum(instancename[i]) && instancename[i] != '-') { + // Stop at the first invalid character + instancename.resize(i); + break; + } + } + + return !instancename.empty() ? instancename : "Sunshine"; + } } // namespace net diff --git a/src/network.h b/src/network.h index ffc0b2d2..dbae81b9 100644 --- a/src/network.h +++ b/src/network.h @@ -105,4 +105,12 @@ namespace net { */ int encryption_mode_for_address(boost::asio::ip::address address); + + /** + * @brief Returns a string for use as the instance name for mDNS. + * @param hostname The hostname to use for instance name generation. + * @return Hostname-based instance name or "Sunshine" if hostname is invalid. + */ + std::string + mdns_instance_name(const std::string_view &hostname); } // namespace net diff --git a/src/platform/linux/publish.cpp b/src/platform/linux/publish.cpp index 29641411..91d49248 100644 --- a/src/platform/linux/publish.cpp +++ b/src/platform/linux/publish.cpp @@ -426,7 +426,8 @@ namespace platf::publish { return nullptr; } - name.reset(avahi::strdup(SERVICE_NAME)); + auto instance_name = net::mdns_instance_name(boost::asio::ip::host_name()); + name.reset(avahi::strdup(instance_name.c_str())); client.reset( avahi::client_new(avahi::simple_poll_get(poll.get()), avahi::ClientFlags(0), client_callback, nullptr, &avhi_error)); diff --git a/src/platform/macos/publish.cpp b/src/platform/macos/publish.cpp index ec4f1f45..b8c977c0 100644 --- a/src/platform/macos/publish.cpp +++ b/src/platform/macos/publish.cpp @@ -105,7 +105,8 @@ namespace platf::publish { &serviceRef, 0, // flags 0, // interfaceIndex - SERVICE_NAME, SERVICE_TYPE, + nullptr, // name + SERVICE_TYPE, nullptr, // domain nullptr, // host htons(net::map_port(nvhttp::PORT_HTTP)), diff --git a/src/platform/windows/publish.cpp b/src/platform/windows/publish.cpp index fe3352e2..05208a9c 100644 --- a/src/platform/windows/publish.cpp +++ b/src/platform/windows/publish.cpp @@ -37,7 +37,6 @@ constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1; #define SERVICE_DOMAIN "local" -constexpr auto SERVICE_INSTANCE_NAME = SV(SERVICE_NAME "." SERVICE_TYPE "." SERVICE_DOMAIN); constexpr auto SERVICE_TYPE_DOMAIN = SV(SERVICE_TYPE "." SERVICE_DOMAIN); #ifndef __MINGW32__ @@ -107,10 +106,11 @@ namespace platf::publish { service(bool enable, PDNS_SERVICE_INSTANCE &existing_instance) { auto alarm = safe::make_alarm(); - std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() }; std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() }; - auto host = from_utf8(boost::asio::ip::host_name() + ".local"); + auto hostname = boost::asio::ip::host_name(); + auto name = from_utf8(net::mdns_instance_name(hostname) + '.') + domain; + auto host = from_utf8(hostname + ".local"); DNS_SERVICE_INSTANCE instance {}; instance.pszInstanceName = name.data(); diff --git a/tests/unit/test_network.cpp b/tests/unit/test_network.cpp new file mode 100644 index 00000000..fc8384ad --- /dev/null +++ b/tests/unit/test_network.cpp @@ -0,0 +1,26 @@ +/** + * @file tests/unit/test_network.cpp + * @brief Test src/network.* + */ +#include + +#include "../tests_common.h" + +struct MdnsInstanceNameTest: testing::TestWithParam> {}; + +TEST_P(MdnsInstanceNameTest, Run) { + auto [input, expected] = GetParam(); + ASSERT_EQ(net::mdns_instance_name(input), expected); +} + +INSTANTIATE_TEST_SUITE_P( + MdnsInstanceNameTests, + MdnsInstanceNameTest, + testing::Values( + std::make_tuple("shortname-123", "shortname-123"), + std::make_tuple("space 123", "space-123"), + std::make_tuple("hostname.domain.test", "hostname"), + std::make_tuple("&", "Sunshine"), + std::make_tuple("", "Sunshine"), + std::make_tuple("😁", "Sunshine"), + std::make_tuple(std::string(128, 'a'), std::string(63, 'a'))));