diff --git a/Development/nmos-cpp-node/config.json b/Development/nmos-cpp-node/config.json index 4fbc41f99..2e5966597 100644 --- a/Development/nmos-cpp-node/config.json +++ b/Development/nmos-cpp-node/config.json @@ -196,6 +196,9 @@ // proxy_port [registry, node]: forward proxy port //"proxy_port": 8080, + // discovery_mode [node]: whether the discovered host name (1) or resolved addresses (2) are used to construct request URLs for Registration APIs or System APIs + //"discovery_mode": 1, + // href_mode [registry, node]: whether the host name (1), addresses (2) or both (3) are used to construct response headers, and host and URL fields in the data model //"href_mode": 1, diff --git a/Development/nmos/mdns.cpp b/Development/nmos/mdns.cpp index d69ab7511..73b17f1ca 100644 --- a/Development/nmos/mdns.cpp +++ b/Development/nmos/mdns.cpp @@ -36,11 +36,24 @@ namespace nmos const std::string ver_dvc{ "ver_dvc" }; const std::string ver_snd{ "ver_snd" }; const std::string ver_rcv{ "ver_rcv" }; + + const std::string api_selector{ "api_selector" }; + } + + // returns true if the specified service protocol is secure + bool is_service_protocol_secure(const service_protocol& api_proto) + { + return service_protocols::https == api_proto || service_protocols::secure_mqtt == api_proto; } - // returns "http" or "https" depending on settings - service_protocol get_service_protocol(const nmos::settings& settings) + // returns e.g. "http" or "https" depending on settings + service_protocol get_service_protocol(const nmos::service_type& service, const nmos::settings& settings) { + if (nmos::service_types::mqtt == service) { + return nmos::experimental::fields::client_secure(settings) + ? service_protocols::secure_mqtt + : service_protocols::mqtt; + } return nmos::experimental::fields::client_secure(settings) ? service_protocols::https : service_protocols::http; @@ -62,6 +75,7 @@ namespace nmos // find and parse the 'api_proto' TXT record (or return the default) service_protocol parse_api_proto_record(const mdns::structured_txt_records& records) { + // hmm, default not appropriate for nmos::service_types::mqtt return mdns::parse_txt_record(records, txt_record_keys::api_proto, details::parse_api_proto_value, service_protocols::http); } @@ -87,11 +101,16 @@ namespace nmos // find and parse the 'api_ver' TXT record (or return the default) std::set parse_api_ver_record(const mdns::structured_txt_records& records) { + // hm, logically speaking default only appropriate for IS-04 return mdns::parse_txt_record(records, txt_record_keys::api_ver, details::parse_api_ver_value, is04_versions::unspecified); } - bool get_service_authorization(const nmos::settings& settings) + bool get_service_authorization(const nmos::service_type& service, const nmos::settings& settings) { + // IS-09 System API does not use authorization + // See https://github.com/AMWA-TV/is-09/issues/21 + if (nmos::service_types::system == service) return false; + const auto client_authorization = false; return client_authorization; } @@ -161,6 +180,8 @@ namespace nmos { if (service == nmos::service_types::node) { + // see https://specs.amwa.tv/is-04/releases/v1.3.1/docs/3.2._Discovery_-_Peer_to_Peer_Operation.html#dns-sd-txt-records + // IS-04 Node API is not distributed so does not use 'pri' TXT record return { { txt_record_keys::api_proto, details::make_api_proto_value(api_proto) }, @@ -168,8 +189,10 @@ namespace nmos { txt_record_keys::api_auth, details::make_api_auth_value(api_auth) } }; } - else + else if (service == nmos::service_types::query || service == nmos::service_types::registration) { + // see https://specs.amwa.tv/is-04/releases/v1.3.1/docs/3.1._Discovery_-_Registered_Operation.html#dns-sd-txt-records-1 + // and https://specs.amwa.tv/is-04/releases/v1.3.1/docs/3.1._Discovery_-_Registered_Operation.html#dns-sd-txt-records return { { txt_record_keys::api_proto, details::make_api_proto_value(api_proto) }, @@ -178,6 +201,39 @@ namespace nmos { txt_record_keys::pri, details::make_pri_value(pri) } }; } + else if (service == nmos::service_types::system) + { + // see https://specs.amwa.tv/is-09/releases/v1.0.0/docs/3.1._Discovery_-_Operation.html#dns-sd-txt-records + // IS-09 System API does not use authorization + return + { + { txt_record_keys::api_proto, details::make_api_proto_value(api_proto) }, + { txt_record_keys::api_ver, details::make_api_ver_value(api_ver) }, + { txt_record_keys::pri, details::make_pri_value(pri) } + }; + } + else if (service == nmos::service_types::authorization) + { + // see https://specs.amwa.tv/is-10/releases/v1.0.0/docs/3.0._Discovery.html#dns-sd-txt-records + // hm, IS-10 Authorization may also need an 'api_selector' TXT record + return + { + { txt_record_keys::api_proto, details::make_api_proto_value(api_proto) }, + { txt_record_keys::api_ver, details::make_api_ver_value(api_ver) }, + { txt_record_keys::pri, details::make_pri_value(pri) } + }; + } + else if (service == nmos::service_types::mqtt) + { + // see https://specs.amwa.tv/is-07/releases/v1.0.1/docs/5.1._Transport_-_MQTT.html#7-broker-discovery + // the 'pri' TXT record could make sense for the MQTT Broker but that wasn't adopted for IS-07 v1.0 + return + { + { txt_record_keys::api_proto, details::make_api_proto_value(api_proto) }, + { txt_record_keys::api_auth, details::make_api_auth_value(api_auth) } + }; + } + return {}; } // find and parse the Node 'ver_' TXT records @@ -291,7 +347,7 @@ namespace nmos if (0 > instance_port_or_disabled) return; const auto instance_port = (uint16_t)instance_port_or_disabled; const auto api_ver = details::service_versions(service, settings); - const auto records = nmos::make_txt_records(service, nmos::fields::pri(settings), api_ver, nmos::get_service_protocol(settings), nmos::get_service_authorization(settings)); + const auto records = nmos::make_txt_records(service, nmos::fields::pri(settings), api_ver, nmos::get_service_protocol(service, settings), nmos::get_service_authorization(service, settings)); const auto txt_records = mdns::make_txt_records(records); // advertise "_nmos-register._tcp" for v1.3 (and as an experimental extension, for lower versions) @@ -369,7 +425,7 @@ namespace nmos { const auto instance_name = service_name(service, settings); const auto api_ver = details::service_versions(service, settings); - auto records = nmos::make_txt_records(service, nmos::fields::pri(settings), api_ver, nmos::get_service_protocol(settings), nmos::get_service_authorization(settings)); + auto records = nmos::make_txt_records(service, nmos::fields::pri(settings), api_ver, nmos::get_service_protocol(service, settings), nmos::get_service_authorization(service, settings)); records.insert(records.end(), std::make_move_iterator(add_records.begin()), std::make_move_iterator(add_records.end())); const auto txt_records = mdns::make_txt_records(records); @@ -406,13 +462,46 @@ namespace nmos update_service(advertiser, service, domain, settings, std::move(add_records)); } + enum discovery_mode + { + discovery_mode_default = 0, + discovery_mode_name = 1, + discovery_mode_addresses = 2 + }; + namespace details { typedef std::pair api_ver_pri; typedef std::pair resolved_service; typedef std::vector resolved_services; - pplx::task resolve_service(std::shared_ptr results, mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& api_auth, const std::chrono::steady_clock::time_point& timeout, const pplx::cancellation_token& token) + std::vector get_resolved_hosts(const mdns::resolve_result& resolved, const nmos::service_protocol& resolved_proto, discovery_mode mode) + { + std::vector results; + + // by default, use the host name if secure communications are in use + if (mode == discovery_mode_name || (mode == discovery_mode_default && is_service_protocol_secure(resolved_proto))) + { + auto host_name = utility::s2us(resolved.host_name); + // remove a trailing '.' to turn an FQDN into a DNS name, for SSL certificate matching + // hmm, this might be more appropriately done by tweaking the Host header in the client request? + if (!host_name.empty() && U('.') == host_name.back()) host_name.pop_back(); + + results.push_back(host_name); + } + + if (mode == discovery_mode_addresses || (mode == discovery_mode_default && !is_service_protocol_secure(resolved_proto))) + { + for (const auto& ip_address : resolved.ip_addresses) + { + results.push_back(utility::s2us(ip_address)); + } + } + + return results; + } + + pplx::task resolve_service(std::shared_ptr results, mdns::service_discovery& discovery, discovery_mode discovery_mode, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& api_auth, const std::chrono::steady_clock::time_point& timeout, const pplx::cancellation_token& token) { return discovery.browse([=, &discovery](const mdns::browse_result& resolving) { @@ -454,22 +543,12 @@ namespace nmos .set_port(resolved.port) .set_path(U("/x-nmos/") + utility::s2us(details::service_api(service))); - if (nmos::service_protocols::https == resolved_proto) - { - auto host_name = utility::s2us(resolved.host_name); - // remove a trailing '.' to turn an FQDN into a DNS name, for SSL certificate matching - // hmm, this might be more appropriately done by tweaking the Host header in the client request? - if (!host_name.empty() && U('.') == host_name.back()) host_name.pop_back(); + auto resolved_hosts = get_resolved_hosts(resolved, resolved_proto, discovery_mode); - results->push_back({ { *resolved_ver, resolved_pri }, resolved_uri - .set_host(host_name) - .to_uri() - }); - } - else for (const auto& ip_address : resolved.ip_addresses) + for (const auto& host : resolved_hosts) { results->push_back({ { *resolved_ver, resolved_pri }, resolved_uri - .set_host(utility::s2us(ip_address)) + .set_host(host) .to_uri() }); } @@ -486,11 +565,14 @@ namespace nmos // the higher version is preferred; for the same version, the 'higher' priority is preferred return lhs.first > rhs.first || (lhs.first == rhs.first && lhs.second < rhs.second); } + + std::pair service_priorities(const nmos::service_type& service, const nmos::settings& settings) + { + return { nmos::fields::highest_pri(settings), nmos::fields::lowest_pri(settings) }; + } } - // helper function for resolving instances of the specified service (API) - // with the highest version, highest priority instances at the front, and (by default) services with the same priority ordered randomly - pplx::task> resolve_service(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& api_auth, bool randomize, const std::chrono::steady_clock::duration& timeout, const pplx::cancellation_token& token) + pplx::task> resolve_service(mdns::service_discovery& discovery, discovery_mode mode, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& api_auth, bool randomize, const std::chrono::steady_clock::duration& timeout, const pplx::cancellation_token& token) { const auto absolute_timeout = std::chrono::steady_clock::now() + timeout; @@ -518,8 +600,8 @@ namespace nmos }; const std::vector> both_tasks{ - details::resolve_service(both_results[0], discovery, nmos::service_types::register_, browse_domain, api_ver, priorities, api_proto, api_auth, absolute_timeout, linked_token), - details::resolve_service(both_results[1], discovery, service, browse_domain, api_ver, priorities, api_proto, api_auth, absolute_timeout, linked_token) + details::resolve_service(both_results[0], discovery, mode, nmos::service_types::register_, browse_domain, api_ver, priorities, api_proto, api_auth, absolute_timeout, linked_token), + details::resolve_service(both_results[1], discovery, mode, service, browse_domain, api_ver, priorities, api_proto, api_auth, absolute_timeout, linked_token) }; // when either task is completed, cancel and wait for the other to be completed @@ -547,12 +629,12 @@ namespace nmos } else { - resolve_task = details::resolve_service(results, discovery, nmos::service_types::register_, browse_domain, api_ver, priorities, api_proto, api_auth, absolute_timeout, token); + resolve_task = details::resolve_service(results, discovery, mode, nmos::service_types::register_, browse_domain, api_ver, priorities, api_proto, api_auth, absolute_timeout, token); } } else { - resolve_task = details::resolve_service(results, discovery, service, browse_domain, api_ver, priorities, api_proto, api_auth, absolute_timeout, token); + resolve_task = details::resolve_service(results, discovery, mode, service, browse_domain, api_ver, priorities, api_proto, api_auth, absolute_timeout, token); } return resolve_task.then([results, randomize](bool) @@ -591,5 +673,30 @@ namespace nmos })); }); } + + // helper function for resolving instances of the specified service (API) + // with the highest version, highest priority instances at the front, and (by default) services with the same priority ordered randomly + pplx::task> resolve_service(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& api_auth, bool randomize, const std::chrono::steady_clock::duration& timeout, const pplx::cancellation_token& token) + { + return resolve_service(discovery, discovery_mode_default, service, browse_domain, api_ver, priorities, api_proto, api_auth, randomize, timeout, token); + } + + // helper function for resolving instances of the specified service (API) based on the specified settings + // with the highest version, highest priority instances at the front, and services with the same priority ordered randomly + pplx::task> resolve_service(mdns::service_discovery& discovery, const nmos::service_type& service, const nmos::settings& settings, const pplx::cancellation_token& token) + { + const auto mode = discovery_mode(nmos::experimental::fields::discovery_mode(settings)); + const auto browse_domain = utility::us2s(nmos::get_domain(settings)); + const auto versions = details::service_versions(service, settings); + const auto priorities = details::service_priorities(service, settings); + const auto protocols = std::set{ nmos::get_service_protocol(service, settings) }; + const auto authorization = std::set{ nmos::get_service_authorization(service, settings) }; + + // use a short timeout that's long enough to ensure the daemon's cache is exhausted + // when no cancellation token is specified + const auto timeout = token.is_cancelable() ? nmos::fields::discovery_backoff_max(settings) : 1; + + return resolve_service(discovery, mode, service, browse_domain, versions, priorities, protocols, authorization, true, std::chrono::seconds(timeout), token); + } } } diff --git a/Development/nmos/mdns.h b/Development/nmos/mdns.h index 5d11d0186..9a257a1a3 100644 --- a/Development/nmos/mdns.h +++ b/Development/nmos/mdns.h @@ -41,6 +41,10 @@ namespace nmos // IS-10 Authorization API const service_type authorization{ "_nmos-auth._tcp" }; + + // MQTT Broker + // See https://specs.amwa.tv/is-07/releases/v1.0.1/docs/5.1._Transport_-_MQTT.html#7-broker-discovery + const service_type mqtt{ "_nmos-mqtt._tcp" }; } // "The DNS-SD advertisement MUST be accompanied by a TXT record of name 'api_proto' with a value @@ -51,14 +55,26 @@ namespace nmos namespace service_protocols { + // Values for the 'api_proto' TXT record for e.g. IS-04 Registration API, IS-04 Query API, IS-09 System API and IS-10 Authorization API + // See https://specs.amwa.tv/is-04/releases/v1.3.1/docs/3.1._Discovery_-_Registered_Operation.html#dns-sd-txt-records + // and https://specs.amwa.tv/is-09/releases/v1.0.0/docs/3.1._Discovery_-_Operation.html#dns-sd-txt-records + // and https://specs.amwa.tv/is-10/releases/v1.0.0/docs/3.0._Discovery.html#dns-sd-txt-records const service_protocol http{ "http" }; const service_protocol https{ "https" }; + + // Values for the 'api_proto' TXT record for MQTT broker advertisements + // See https://specs.amwa.tv/is-07/releases/v1.0.1/docs/5.1._Transport_-_MQTT.html#7-broker-discovery + const service_protocol mqtt{ "mqtt" }; + const service_protocol secure_mqtt{ "secure-mqtt" }; const std::set all{ nmos::service_protocols::http, nmos::service_protocols::https }; } - // returns "http" or "https" depending on settings - service_protocol get_service_protocol(const nmos::settings& settings); + // returns true if the specified service protocol is secure + bool is_service_protocol_secure(const service_protocol& api_proto); + + // returns e.g. "http" or "https" depending on settings + service_protocol get_service_protocol(const nmos::service_type& service, const nmos::settings& settings); // find and parse the 'api_proto' TXT record (or return the default) service_protocol parse_api_proto_record(const mdns::structured_txt_records& records); @@ -82,7 +98,7 @@ namespace nmos // (This record is added in v1.3, so when it is omitted, "false" should be assumed.) // returns true or false depending on settings - bool get_service_authorization(const nmos::settings& settings); + bool get_service_authorization(const nmos::service_type& service, const nmos::settings& settings); // find and parse the 'api_auth' TXT record (or return the default) bool parse_api_auth_record(const mdns::structured_txt_records& records); @@ -145,11 +161,17 @@ namespace nmos // with the highest version, highest priority instances at the front, and (by default) services with the same priority ordered randomly pplx::task> resolve_service(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& api_auth, bool randomize, const std::chrono::steady_clock::duration& timeout, const pplx::cancellation_token& token = pplx::cancellation_token::none()); + // helper function for resolving instances of the specified service (API) based on the specified options or defaults + // with the highest version, highest priority instances at the front, and (by default) services with the same priority ordered randomly template inline pplx::task> resolve_service(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain = {}, const std::set& api_ver = nmos::is04_versions::all, const std::pair& priorities = { service_priorities::highest_active_priority, service_priorities::no_priority }, const std::set& api_proto = nmos::service_protocols::all, const std::set& api_auth = { false, true }, bool randomize = true, const std::chrono::duration& timeout = std::chrono::seconds(mdns::default_timeout_seconds), const pplx::cancellation_token& token = pplx::cancellation_token::none()) { return resolve_service(discovery, service, browse_domain, api_ver, api_proto, api_auth, randomize, std::chrono::duration_cast(timeout), token); } + + // helper function for resolving instances of the specified service (API) based on the specified settings + // with the highest version, highest priority instances at the front, and services with the same priority ordered randomly + pplx::task> resolve_service(mdns::service_discovery& discovery, const nmos::service_type& service, const nmos::settings& settings, const pplx::cancellation_token& token = pplx::cancellation_token::none()); } } diff --git a/Development/nmos/node_behaviour.cpp b/Development/nmos/node_behaviour.cpp index 2a9f73593..2b009e301 100644 --- a/Development/nmos/node_behaviour.cpp +++ b/Development/nmos/node_behaviour.cpp @@ -225,39 +225,6 @@ namespace nmos advertise_node_service(advertiser, with_read_lock(model.mutex, [&] { return model.settings; })); } - // query DNS Service Discovery for any Registration API in the specified browse domain, having priority in the specified range - // otherwise, after timeout or cancellation, returning the fallback registration service - web::json::value discover_registration_services(mdns::service_discovery& discovery, const std::string& browse_domain, const std::set& versions, const std::pair& priorities, const std::set& protocols, const std::set& authorization, const web::uri& fallback_registration_service, slog::base_gate& gate, const std::chrono::steady_clock::duration& timeout, const pplx::cancellation_token& token = pplx::cancellation_token::none()) - { - std::list registration_services; - - if (nmos::service_priorities::no_priority != priorities.first) - { - slog::log(gate, SLOG_FLF) << "Attempting discovery of a Registration API in domain: " << browse_domain; - - registration_services = nmos::experimental::resolve_service(discovery, nmos::service_types::registration, browse_domain, versions, priorities, protocols, authorization, true, timeout, token).get(); - - if (!registration_services.empty()) - { - slog::log(gate, SLOG_FLF) << "Discovered " << registration_services.size() << " Registration API(s)"; - } - else - { - slog::log(gate, SLOG_FLF) << "Did not discover a suitable Registration API via DNS-SD"; - } - } - - if (registration_services.empty()) - { - if (!fallback_registration_service.is_empty()) - { - registration_services.push_back(fallback_registration_service); - } - } - - return web::json::value_from_elements(registration_services | boost::adaptors::transformed([](const web::uri& u) { return u.to_string(); })); - } - // get the fallback registration service from settings (if present) web::uri get_registration_service(const nmos::settings& settings) { @@ -274,34 +241,55 @@ namespace nmos // query DNS Service Discovery for any Registration API based on settings bool discover_registration_services(nmos::base_model& model, mdns::service_discovery& discovery, slog::base_gate& gate, const pplx::cancellation_token& token) { - std::string browse_domain; - std::set versions; - std::pair priorities; - std::set protocols; - std::set authorization; // yes, this is almost equivalent to a tribool - web::uri fallback_registration_service; - int timeout; - with_read_lock(model.mutex, [&] + slog::log(gate, SLOG_FLF) << "Trying Registration API discovery"; + + // lock to read settings, then unlock to wait for the discovery task to complete + auto registration_services = with_read_lock(model.mutex, [&] { auto& settings = model.settings; - browse_domain = utility::us2s(nmos::get_domain(settings)); - versions = nmos::is04_versions::from_settings(settings); - priorities = { nmos::fields::highest_pri(settings), nmos::fields::lowest_pri(settings) }; - protocols = { nmos::get_service_protocol(settings) }; - authorization = { nmos::get_service_authorization(settings) }; - fallback_registration_service = get_registration_service(settings); - - // use a short timeout that's long enough to ensure the daemon's cache is exhausted - // when no cancellation token is specified - timeout = token.is_cancelable() ? nmos::fields::discovery_backoff_max(settings) : 1; - }); - slog::log(gate, SLOG_FLF) << "Trying Registration API discovery for about " << std::fixed << std::setprecision(3) << (double)timeout << " seconds"; - auto registration_services = discover_registration_services(discovery, browse_domain, versions, priorities, protocols, authorization, fallback_registration_service, gate, std::chrono::seconds(timeout), token); - with_write_lock(model.mutex, [&] { model.settings[nmos::fields::registration_services] = registration_services; }); - model.notify(); + if (nmos::service_priorities::no_priority != nmos::fields::highest_pri(settings)) + { + slog::log(gate, SLOG_FLF) << "Attempting discovery of a Registration API in domain: " << nmos::get_domain(settings); + + return nmos::experimental::resolve_service(discovery, nmos::service_types::registration, settings, token); + } + else + { + return pplx::task_from_result(std::list{}); + } + }).get(); + + with_write_lock(model.mutex, [&] + { + if (!registration_services.empty()) + { + slog::log(gate, SLOG_FLF) << "Discovered " << registration_services.size() << " Registration API(s)"; + } + else + { + slog::log(gate, SLOG_FLF) << "Did not discover a suitable Registration API via DNS-SD"; + + auto fallback_registration_service = get_registration_service(model.settings); + if (!fallback_registration_service.is_empty()) + { + registration_services.push_back(fallback_registration_service); + } + } + + if (!registration_services.empty()) slog::log(gate, SLOG_FLF) << "Using the Registration API(s):" << slog::log_manip([&](slog::log_statement& s) + { + for (auto& registration_service : registration_services) + { + s << '\n' << registration_service.to_string(); + } + }); + + model.settings[nmos::fields::registration_services] = web::json::value_from_elements(registration_services | boost::adaptors::transformed([](const web::uri& u) { return u.to_string(); })); + model.notify(); + }); - return !web::json::empty(registration_services); + return !registration_services.empty(); } bool empty_registration_services(const nmos::settings& settings) diff --git a/Development/nmos/node_system_behaviour.cpp b/Development/nmos/node_system_behaviour.cpp index 6ad8e11e6..2e215824b 100644 --- a/Development/nmos/node_system_behaviour.cpp +++ b/Development/nmos/node_system_behaviour.cpp @@ -125,40 +125,6 @@ namespace nmos // service discovery namespace details { - // query DNS Service Discovery for any System API in the specified browse domain, having priority in the specified range - // otherwise, after timeout or cancellation, returning the fallback system service - // see https://specs.amwa.tv/is-09/releases/v1.0.0/docs/3.0._Discovery.html - web::json::value discover_system_services(mdns::service_discovery& discovery, const std::string& browse_domain, const std::set& versions, const std::pair& priorities, const std::set& protocols, const web::uri& fallback_service, slog::base_gate& gate, const std::chrono::steady_clock::duration& timeout, const pplx::cancellation_token& token = pplx::cancellation_token::none()) - { - std::list system_services; - - if (nmos::service_priorities::no_priority != priorities.first) - { - slog::log(gate, SLOG_FLF) << "Attempting discovery of a System API in domain: " << browse_domain; - - system_services = nmos::experimental::resolve_service(discovery, nmos::service_types::system, browse_domain, versions, priorities, protocols, { false }, true, timeout, token).get(); - - if (!system_services.empty()) - { - slog::log(gate, SLOG_FLF) << "Discovered " << system_services.size() << " System API(s)"; - } - else - { - slog::log(gate, SLOG_FLF) << "Did not discover a suitable System API via DNS-SD"; - } - } - - if (system_services.empty()) - { - if (!fallback_service.is_empty()) - { - system_services.push_back(fallback_service); - } - } - - return web::json::value_from_elements(system_services | boost::adaptors::transformed([](const web::uri& u) { return u.to_string(); })); - } - // get the fallback system service from settings (if present) web::uri get_system_service(const nmos::settings& settings) { @@ -175,32 +141,55 @@ namespace nmos // query DNS Service Discovery for any System API based on settings bool discover_system_services(nmos::base_model& model, mdns::service_discovery& discovery, slog::base_gate& gate, const pplx::cancellation_token& token) { - std::string browse_domain; - std::set versions; - std::pair priorities; - std::set protocols; - web::uri fallback_system_service; - int timeout; - with_read_lock(model.mutex, [&] + slog::log(gate, SLOG_FLF) << "Trying System API discovery"; + + // lock to read settings, then unlock to wait for the discovery task to complete + auto system_services = with_read_lock(model.mutex, [&] { auto& settings = model.settings; - browse_domain = utility::us2s(nmos::get_domain(settings)); - versions = nmos::is09_versions::from_settings(settings); - priorities = { nmos::fields::highest_pri(settings), nmos::fields::lowest_pri(settings) }; - protocols = { nmos::get_service_protocol(settings) }; - fallback_system_service = get_system_service(settings); - - // use a short timeout that's long enough to ensure the daemon's cache is exhausted - // when no cancellation token is specified - timeout = token.is_cancelable() ? nmos::fields::discovery_backoff_max(settings) : 1; - }); - slog::log(gate, SLOG_FLF) << "Trying System API discovery for about " << std::fixed << std::setprecision(3) << (double)timeout << " seconds"; - auto services = discover_system_services(discovery, browse_domain, versions, priorities, protocols, fallback_system_service, gate, std::chrono::seconds(timeout), token); - with_write_lock(model.mutex, [&] { model.settings[nmos::fields::system_services] = services; }); - model.notify(); + if (nmos::service_priorities::no_priority != nmos::fields::highest_pri(settings)) + { + slog::log(gate, SLOG_FLF) << "Attempting discovery of a System API in domain: " << nmos::get_domain(settings); + + return nmos::experimental::resolve_service(discovery, nmos::service_types::system, settings, token); + } + else + { + return pplx::task_from_result(std::list{}); + } + }).get(); + + with_write_lock(model.mutex, [&] + { + if (!system_services.empty()) + { + slog::log(gate, SLOG_FLF) << "Discovered " << system_services.size() << " System API(s)"; + } + else + { + slog::log(gate, SLOG_FLF) << "Did not discover a suitable System API via DNS-SD"; + + auto fallback_system_service = get_system_service(model.settings); + if (!fallback_system_service.is_empty()) + { + system_services.push_back(fallback_system_service); + } + } + + if (!system_services.empty()) slog::log(gate, SLOG_FLF) << "Using the System API(s):" << slog::log_manip([&](slog::log_statement& s) + { + for (auto& system_service : system_services) + { + s << '\n' << system_service.to_string(); + } + }); + + model.settings[nmos::fields::system_services] = web::json::value_from_elements(system_services | boost::adaptors::transformed([](const web::uri& u) { return u.to_string(); })); + model.notify(); + }); - return !web::json::empty(services); + return !system_services.empty(); } bool empty_system_services(const nmos::settings& settings) diff --git a/Development/nmos/settings.h b/Development/nmos/settings.h index 77ffde0b5..504921552 100644 --- a/Development/nmos/settings.h +++ b/Development/nmos/settings.h @@ -260,6 +260,9 @@ namespace nmos // proxy_port [registry, node]: forward proxy port const web::json::field_as_integer_or proxy_port{ U("proxy_port"), 8080 }; + // discovery_mode [node]: whether the discovered host name (1) or resolved addresses (2) are used to construct request URLs for Registration APIs or System APIs + const web::json::field_as_integer_or discovery_mode{ U("discovery_mode"), 0 }; // when omitted, a default heuristic is used + // href_mode [registry, node]: whether the host name (1), addresses (2) or both (3) are used to construct response headers, and host and URL fields in the data model const web::json::field_as_integer_or href_mode{ U("href_mode"), 0 }; // when omitted, a default heuristic is used