diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index f1b1eb0cd..3eba447c2 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -201,6 +201,7 @@ ot_option(OT_DNS_CLIENT OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE "DNS client") ot_option(OT_DNS_CLIENT_OVER_TCP OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE "Enable dns query over tcp") ot_option(OT_DNS_DSO OPENTHREAD_CONFIG_DNS_DSO_ENABLE "DNS Stateful Operations (DSO)") ot_option(OT_DNS_UPSTREAM_QUERY OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE "Allow sending DNS queries to upstream") +ot_option(OT_DNSSD_DISCOVERY_PROXY OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE "DNS-SD discovery proxy") ot_option(OT_DNSSD_SERVER OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE "DNS-SD server") ot_option(OT_DUA OPENTHREAD_CONFIG_DUA_ENABLE "Domain Unicast Address (DUA)") ot_option(OT_ECDSA OPENTHREAD_CONFIG_ECDSA_ENABLE "ECDSA") diff --git a/examples/platforms/simulation/dnssd.c b/examples/platforms/simulation/dnssd.c index 7967dd487..84fd8a6cf 100644 --- a/examples/platforms/simulation/dnssd.c +++ b/examples/platforms/simulation/dnssd.c @@ -104,4 +104,64 @@ void otPlatDnssdUnregisterKey(otInstance *aInstance, OT_UNUSED_VARIABLE(aCallback); } +void otPlatDnssdStartBrowser(otInstance *aInstance, const otPlatDnssdBrowser *aBrowser) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aBrowser); +} + +void otPlatDnssdStopBrowser(otInstance *aInstance, const otPlatDnssdBrowser *aBrowser) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aBrowser); +} + +void otPlatDnssdStartSrvResolver(otInstance *aInstance, const otPlatDnssdSrvResolver *aResolver) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aResolver); +} + +void otPlatDnssdStopSrvResolver(otInstance *aInstance, const otPlatDnssdSrvResolver *aResolver) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aResolver); +} + +void otPlatDnssdStartTxtResolver(otInstance *aInstance, const otPlatDnssdTxtResolver *aResolver) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aResolver); +} + +void otPlatDnssdStopTxtResolver(otInstance *aInstance, const otPlatDnssdTxtResolver *aResolver) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aResolver); +} + +void otPlatDnssdStartIp6AddressResolver(otInstance *aInstance, const otPlatDnssdAddressResolver *aResolver) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aResolver); +} + +void otPlatDnssdStopIp6AddressResolver(otInstance *aInstance, const otPlatDnssdAddressResolver *aResolver) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aResolver); +} + +void otPlatDnssdStartIp4AddressResolver(otInstance *aInstance, const otPlatDnssdAddressResolver *aResolver) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aResolver); +} + +void otPlatDnssdStopIp4AddressResolver(otInstance *aInstance, const otPlatDnssdAddressResolver *aResolver) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aResolver); +} + #endif // OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE diff --git a/include/openthread/instance.h b/include/openthread/instance.h index b6a0cafab..5c69dfe8a 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (419) +#define OPENTHREAD_API_VERSION (420) /** * @addtogroup api-instance diff --git a/include/openthread/mdns.h b/include/openthread/mdns.h index 56ffff09c..c2a63f0fc 100644 --- a/include/openthread/mdns.h +++ b/include/openthread/mdns.h @@ -500,158 +500,94 @@ otError otMdnsGetNextService(otInstance *aInstance, */ otError otMdnsGetNextKey(otInstance *aInstance, otMdnsIterator *aIterator, otMdnsKey *aKey, otMdnsEntryState *aState); -typedef struct otMdnsBrowseResult otMdnsBrowseResult; -typedef struct otMdnsSrvResult otMdnsSrvResult; -typedef struct otMdnsTxtResult otMdnsTxtResult; -typedef struct otMdnsAddressResult otMdnsAddressResult; - -/** - * Represents the callback function used to report a browse result. - * - * @param[in] aInstance The OpenThread instance. - * @param[in] aResult The browse result. - * - */ -typedef void (*otMdnsBrowseCallback)(otInstance *aInstance, const otMdnsBrowseResult *aResult); - -/** - * Represents the callback function used to report an SRV resolve result. - * - * @param[in] aInstance The OpenThread instance. - * @param[in] aResult The SRV resolve result. - * - */ -typedef void (*otMdnsSrvCallback)(otInstance *aInstance, const otMdnsSrvResult *aResult); - -/** - * Represents the callback function used to report a TXT resolve result. - * - * @param[in] aInstance The OpenThread instance. - * @param[in] aResult The TXT resolve result. - * - */ -typedef void (*otMdnsTxtCallback)(otInstance *aInstance, const otMdnsTxtResult *aResult); - -/** - * Represents the callback function use to report a IPv6/IPv4 address resolve result. - * - * @param[in] aInstance The OpenThread instance. - * @param[in] aResult The address resolve result. - * - */ -typedef void (*otMdnsAddressCallback)(otInstance *aInstance, const otMdnsAddressResult *aResult); - /** * Represents a service browser. * + * Refer to `otPlatDnssdBrowser` for documentation of member fields and `otMdnsStartBrowser()` for how they are used. + * */ -typedef struct otMdnsBrowser -{ - const char *mServiceType; ///< The service type (e.g., "_mt._udp"). MUST NOT include domain name. - const char *mSubTypeLabel; ///< The sub-type label if browsing for sub-type, NULL otherwise. - uint32_t mInfraIfIndex; ///< The infrastructure network interface index. - otMdnsBrowseCallback mCallback; ///< The callback to report result. -} otMdnsBrowser; +typedef otPlatDnssdBrowser otMdnsBrowser; + +/** + * Represents the callback function pointer type used to report a browse result. + * + */ +typedef otPlatDnssdBrowseCallback otMdnsBrowseCallback; /** * Represents a browse result. * */ -struct otMdnsBrowseResult -{ - const char *mServiceType; ///< The service type (e.g., "_mt._udp"). - const char *mSubTypeLabel; ///< The sub-type label if browsing for sub-type, NULL otherwise. - const char *mServiceInstance; ///< Service instance label. - uint32_t mTtl; ///< TTL in seconds. Zero TTL indicates that service is removed. - uint32_t mInfraIfIndex; ///< The infrastructure network interface index. -}; +typedef otPlatDnssdBrowseResult otMdnsBrowseResult; /** * Represents an SRV service resolver. * + * Refer to `otPlatDnssdSrvResolver` for documentation of member fields and `otMdnsStartSrvResolver()` for how they are + * used. + * */ -typedef struct otMdnsSrvResolver -{ - const char *mServiceInstance; ///< The service instance label. - const char *mServiceType; ///< The service type. - uint32_t mInfraIfIndex; ///< The infrastructure network interface index. - otMdnsSrvCallback mCallback; ///< The callback to report result. -} otMdnsSrvResolver; +typedef otPlatDnssdSrvResolver otMdnsSrvResolver; + +/** + * Represents the callback function pointer type used to report an SRV resolve result. + * + */ +typedef otPlatDnssdSrvCallback otMdnsSrvCallback; /** * Represents an SRV resolver result. * */ -struct otMdnsSrvResult -{ - const char *mServiceInstance; ///< The service instance name label. - const char *mServiceType; ///< The service type. - const char *mHostName; ///< The host name (e.g., "myhost"). Can be NULL when `mTtl` is zero. - uint16_t mPort; ///< The service port number. - uint16_t mPriority; ///< The service priority. - uint16_t mWeight; ///< The service weight. - uint32_t mTtl; ///< The service TTL in seconds. Zero TTL indicates SRV record is removed. - uint32_t mInfraIfIndex; ///< The infrastructure network interface index. -}; +typedef otPlatDnssdSrvResult otMdnsSrvResult; /** * Represents a TXT service resolver. * + * Refer to `otPlatDnssdTxtResolver` for documentation of member fields and `otMdnsStartTxtResolver()` for how they are + * used. + * */ -typedef struct otMdnsTxtResolver -{ - const char *mServiceInstance; ///< Service instance label. - const char *mServiceType; ///< Service type. - uint32_t mInfraIfIndex; ///< The infrastructure network interface index. - otMdnsTxtCallback mCallback; -} otMdnsTxtResolver; +typedef otPlatDnssdTxtResolver otMdnsTxtResolver; + +/** + * Represents the callback function pointer type used to report a TXT resolve result. + * + */ +typedef otPlatDnssdTxtCallback otMdnsTxtCallback; /** * Represents a TXT resolver result. * */ -struct otMdnsTxtResult -{ - const char *mServiceInstance; ///< The service instance name label. - const char *mServiceType; ///< The service type. - const uint8_t *mTxtData; ///< Encoded TXT data bytes. Can be NULL when `mTtl` is zero. - uint16_t mTxtDataLength; ///< Length of TXT data. - uint32_t mTtl; ///< The TXT data TTL in seconds. Zero TTL indicates record is removed. - uint32_t mInfraIfIndex; ///< The infrastructure network interface index. -}; +typedef otPlatDnssdTxtResult otMdnsTxtResult; /** * Represents an address resolver. * + * Refer to `otPlatDnssdAddressResolver` for documentation of member fields and `otMdnsStartIp6AddressResolver()` or + * `otMdnsStartIp4AddressResolver()` for how they are used. + * */ -typedef struct otMdnsAddressResolver -{ - const char *mHostName; ///< The host name (e.g., "myhost"). MUST NOT contain domain name. - uint32_t mInfraIfIndex; ///< The infrastructure network interface index. - otMdnsAddressCallback mCallback; ///< The callback to report result. -} otMdnsAddressResolver; +typedef otPlatDnssdAddressResolver otMdnsAddressResolver; + +/** + * Represents the callback function pointer type use to report an IPv6/IPv4 address resolve result. + * + */ +typedef otPlatDnssdAddressCallback otMdnsAddressCallback; /** * Represents a discovered host address and its TTL. * */ -typedef struct otMdnsAddressAndTtl -{ - otIp6Address mAddress; ///< The IPv6 address. For IPv4 address the IPv4-mapped IPv6 address format is used. - uint32_t mTtl; ///< The TTL in seconds. -} otMdnsAddressAndTtl; +typedef otPlatDnssdAddressAndTtl otMdnsAddressAndTtl; /** * Represents address resolver result. * */ -struct otMdnsAddressResult -{ - const char *mHostName; ///< The host name. - const otMdnsAddressAndTtl *mAddresses; ///< Array of host addresses and their TTL. Can be NULL if empty. - uint16_t mAddressesLength; ///< Number of entries in `mAddresses` array. - uint32_t mInfraIfIndex; ///< The infrastructure network interface index. -}; +typedef otPlatDnssdAddressResult otMdnsAddressResult; /** * Starts a service browser. diff --git a/include/openthread/platform/dnssd.h b/include/openthread/platform/dnssd.h index e7a2a7f0b..894dff561 100644 --- a/include/openthread/platform/dnssd.h +++ b/include/openthread/platform/dnssd.h @@ -65,7 +65,7 @@ extern "C" { */ typedef enum otPlatDnssdState { - OT_PLAT_DNSSD_STOPPED, ///< Stopped and unable to register any service or host. + OT_PLAT_DNSSD_STOPPED, ///< Stopped and unable to register any service or host, or start any browser/resolver. OT_PLAT_DNSSD_READY, ///< Running and ready to register service or host. } otPlatDnssdState; @@ -149,6 +149,9 @@ typedef struct otPlatDnssdKey * The OpenThread stack will call `otPlatDnssdGetState()` (from this callback or later) to get the new state. The * platform MUST therefore ensure that the returned state from `otPlatDnssdGetState()` is updated before calling this. * + * When the platform signals a state change to `OT_PLAT_DNSSD_STOPPED` using this callback, all active browsers and + * resolvers are considered to be stopped, and any previously registered host, service, key entries as removed. + * * @param[in] aInstance The OpenThread instance structure. * */ @@ -415,6 +418,345 @@ void otPlatDnssdUnregisterKey(otInstance *aInstance, otPlatDnssdRequestId aRequestId, otPlatDnssdRegisterCallback aCallback); +//====================================================================================================================== + +/** + * Represents a browse result. + * + */ +typedef struct otPlatDnssdBrowseResult +{ + const char *mServiceType; ///< The service type (e.g., "_mt._udp"). + const char *mSubTypeLabel; ///< The sub-type label if browsing for sub-type, NULL otherwise. + const char *mServiceInstance; ///< Service instance label. + uint32_t mTtl; ///< TTL in seconds. Zero TTL indicates that service is removed. + uint32_t mInfraIfIndex; ///< The infrastructure network interface index. +} otPlatDnssdBrowseResult; + +/** + * Represents the callback function used to report a browse result. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aResult The browse result. + * + */ +typedef void (*otPlatDnssdBrowseCallback)(otInstance *aInstance, const otPlatDnssdBrowseResult *aResult); + +/** + * Represents a service browser. + * + */ +typedef struct otPlatDnssdBrowser +{ + const char *mServiceType; ///< The service type (e.g., "_mt._udp"). MUST NOT include domain name. + const char *mSubTypeLabel; ///< The sub-type label if browsing for sub-type, NULL otherwise. + uint32_t mInfraIfIndex; ///< The infrastructure network interface index. + otPlatDnssdBrowseCallback mCallback; ///< The callback to report result. +} otPlatDnssdBrowser; + +/** + * Represents an SRV resolver result. + * + */ +typedef struct otPlatDnssdSrvResult +{ + const char *mServiceInstance; ///< The service instance name label. + const char *mServiceType; ///< The service type. + const char *mHostName; ///< The host name (e.g., "myhost"). Can be NULL when `mTtl` is zero. + uint16_t mPort; ///< The service port number. + uint16_t mPriority; ///< The service priority. + uint16_t mWeight; ///< The service weight. + uint32_t mTtl; ///< The service TTL in seconds. Zero TTL indicates SRV record is removed. + uint32_t mInfraIfIndex; ///< The infrastructure network interface index. +} otPlatDnssdSrvResult; + +/** + * Represents the callback function used to report an SRV resolve result. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aResult The SRV resolve result. + * + */ +typedef void (*otPlatDnssdSrvCallback)(otInstance *aInstance, const otPlatDnssdSrvResult *aResult); + +/** + * Represents an SRV service resolver. + * + */ +typedef struct otPlatDnssdSrvResolver +{ + const char *mServiceInstance; ///< The service instance label. + const char *mServiceType; ///< The service type. + uint32_t mInfraIfIndex; ///< The infrastructure network interface index. + otPlatDnssdSrvCallback mCallback; ///< The callback to report result. +} otPlatDnssdSrvResolver; + +/** + * Represents a TXT resolver result. + * + */ +typedef struct otPlatDnssdTxtResult +{ + const char *mServiceInstance; ///< The service instance name label. + const char *mServiceType; ///< The service type. + const uint8_t *mTxtData; ///< Encoded TXT data bytes. Can be NULL when `mTtl` is zero. + uint16_t mTxtDataLength; ///< Length of TXT data. + uint32_t mTtl; ///< The TXT data TTL in seconds. Zero TTL indicates record is removed. + uint32_t mInfraIfIndex; ///< The infrastructure network interface index. +} otPlatDnssdTxtResult; + +/** + * Represents the callback function used to report a TXT resolve result. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aResult The TXT resolve result. + * + */ +typedef void (*otPlatDnssdTxtCallback)(otInstance *aInstance, const otPlatDnssdTxtResult *aResult); + +/** + * Represents a TXT service resolver. + * + */ +typedef struct otPlatDnssdTxtResolver +{ + const char *mServiceInstance; ///< Service instance label. + const char *mServiceType; ///< Service type. + uint32_t mInfraIfIndex; ///< The infrastructure network interface index. + otPlatDnssdTxtCallback mCallback; +} otPlatDnssdTxtResolver; + +/** + * Represents a discovered host address and its TTL. + * + */ +typedef struct otPlatDnssdAddressAndTtl +{ + otIp6Address mAddress; ///< The IPv6 address. For IPv4 address the IPv4-mapped IPv6 address format is used. + uint32_t mTtl; ///< The TTL in seconds. +} otPlatDnssdAddressAndTtl; + +/** + * Represents address resolver result. + * + */ +typedef struct otPlatDnssdAddressResult +{ + const char *mHostName; ///< The host name. + const otPlatDnssdAddressAndTtl *mAddresses; ///< Array of host addresses and their TTL. Can be NULL if empty. + uint16_t mAddressesLength; ///< Number of entries in `mAddresses` array. + uint32_t mInfraIfIndex; ///< The infrastructure network interface index. +} otPlatDnssdAddressResult; + +/** + * Represents the callback function use to report a IPv6/IPv4 address resolve result. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aResult The address resolve result. + * + */ +typedef void (*otPlatDnssdAddressCallback)(otInstance *aInstance, const otPlatDnssdAddressResult *aResult); + +/** + * Represents an address resolver. + * + */ +typedef struct otPlatDnssdAddressResolver +{ + const char *mHostName; ///< The host name (e.g., "myhost"). MUST NOT contain domain name. + uint32_t mInfraIfIndex; ///< The infrastructure network interface index. + otPlatDnssdAddressCallback mCallback; ///< The callback to report result. +} otPlatDnssdAddressResolver; + +/** + * Starts a service browser. + * + * Initiates a continuous search for the specified `mServiceType` in @p aBrowser. For sub-type services, + * `mSubTypeLabel` specifies the sub-type, for base services, `mSubTypeLabel` is set to NULL. + * + * Discovered services should be reported through the `mCallback` function in @p aBrowser. Services that have been + * removed are reported with a TTL value of zero. The callback may be invoked immediately with cached information + * (if available) and potentially before this function returns. When cached results are used, the reported TTL value + * should reflect the original TTL from the last received response. + * + * Multiple browsers can be started for the same service, provided they use different callback functions. + * + * The @p aBrowser and all its contained information (strings) are only valid during this call. The platform MUST save + * a copy of the information if it wants to retain the information after returning from this function. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aBrowser The browser to be started. + * + */ +void otPlatDnssdStartBrowser(otInstance *aInstance, const otPlatDnssdBrowser *aBrowser); + +/** + * Stops a service browser. + * + * No action is performed if no matching browser with the same service and callback is currently active. + * + * The @p aBrowser and all its contained information (strings) are only valid during this call. The platform MUST save + * a copy of the information if it wants to retain the information after returning from this function. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aBrowser The browser to stop. + * + */ +void otPlatDnssdStopBrowser(otInstance *aInstance, const otPlatDnssdBrowser *aBrowser); + +/** + * Starts an SRV record resolver. + * + * Initiates a continuous SRV record resolver for the specified service in @p aResolver. + * + * Discovered information should be reported through the `mCallback` function in @p aResolver. When the service is + * removed it is reported with a TTL value of zero. In this case, `mHostName` may be NULL and other result fields (such + * as `mPort`) will be ignored by the OpenThread stack. + * + * The callback may be invoked immediately with cached information (if available) and potentially before this function + * returns. When cached result is used, the reported TTL value should reflect the original TTL from the last received + * response. + * + * Multiple resolvers can be started for the same service, provided they use different callback functions. + * + * The @p aResolver and all its contained information (strings) are only valid during this call. The platform MUST save + * a copy of the information if it wants to retain the information after returning from this function. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aResolver The resolver to be started. + * + */ +void otPlatDnssdStartSrvResolver(otInstance *aInstance, const otPlatDnssdSrvResolver *aResolver); + +/** + * Stops an SRV record resolver. + * + * No action is performed if no matching resolver with the same service and callback is currently active. + * + * The @p aResolver and all its contained information (strings) are only valid during this call. The platform MUST save + * a copy of the information if it wants to retain the information after returning from this function. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aResolver The resolver to stop. + * + */ +void otPlatDnssdStopSrvResolver(otInstance *aInstance, const otPlatDnssdSrvResolver *aResolver); + +/** + * Starts a TXT record resolver. + * + * Initiates a continuous TXT record resolver for the specified service in @p aResolver. + * + * Discovered information should be reported through the `mCallback` function in @p aResolver. When the TXT record is + * removed it is reported with a TTL value of zero. In this case, `mTxtData` may be NULL, and other result fields + * (such as `mTxtDataLength`) will be ignored by the OpenThread stack. + * + * The callback may be invoked immediately with cached information (if available) and potentially before this function + * returns. When cached result is used, the reported TTL value should reflect the original TTL from the last received + * response. + * + * Multiple resolvers can be started for the same service, provided they use different callback functions. + * + * The @p aResolver and all its contained information (strings) are only valid during this call. The platform MUST save + * a copy of the information if it wants to retain the information after returning from this function. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aResolver The resolver to be started. + * + */ +void otPlatDnssdStartTxtResolver(otInstance *aInstance, const otPlatDnssdTxtResolver *aResolver); + +/** + * Stops a TXT record resolver. + * + * No action is performed if no matching resolver with the same service and callback is currently active. + * + * The @p aResolver and all its contained information (strings) are only valid during this call. The platform MUST save + * a copy of the information if it wants to retain the information after returning from this function. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aResolver The resolver to stop. + * + */ +void otPlatDnssdStopTxtResolver(otInstance *aInstance, const otPlatDnssdTxtResolver *aResolver); + +/** + * Starts an IPv6 address resolver. + * + * Initiates a continuous IPv6 address resolver for the specified host name in @p aResolver. + * + * Discovered addresses should be reported through the `mCallback` function in @p aResolver. The callback should be + * invoked whenever addresses are added or removed, providing an updated list. If all addresses are removed, the + * callback should be invoked with an empty list (`mAddressesLength` set to zero). + * + * The callback may be invoked immediately with cached information (if available) and potentially before this function + * returns. When cached result is used, the reported TTL values should reflect the original TTL from the last received + * response. + * + * Multiple resolvers can be started for the same host name, provided they use different callback functions. + * + * The @p aResolver and all its contained information (strings) are only valid during this call. The platform MUST save + * a copy of the information if it wants to retain the information after returning from this function. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aResolver The resolver to be started. + * + */ +void otPlatDnssdStartIp6AddressResolver(otInstance *aInstance, const otPlatDnssdAddressResolver *aResolver); + +/** + * Stops an IPv6 address resolver. + * + * No action is performed if no matching resolver with the same host name and callback is currently active. + * + * The @p aResolver and all its contained information (strings) are only valid during this call. The platform MUST save + * a copy of the information if it wants to retain the information after returning from this function. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aResolver The resolver to stop. + * + */ +void otPlatDnssdStopIp6AddressResolver(otInstance *aInstance, const otPlatDnssdAddressResolver *aResolver); + +/** + * Starts an IPv4 address resolver. + * + * Initiates a continuous IPv4 address resolver for the specified host name in @p aResolver. + * + * Discovered addresses should be reported through the `mCallback` function in @p aResolver. The IPv4 addresses are + * represented using the IPv4-mapped IPv6 address format in `mAddresses` array. The callback should be invoked + * whenever addresses are added or removed, providing an updated list. If all addresses are removed, the callback + * should be invoked with an empty list (`mAddressesLength` set to zero). + * + * The callback may be invoked immediately with cached information (if available) and potentially before this function + * returns. When cached result is used, the reported TTL values will reflect the original TTL from the last received + * response. + * + * Multiple resolvers can be started for the same host name, provided they use different callback functions. + * + * The @p aResolver and all its contained information (strings) are only valid during this call. The platform MUST save + * a copy of the information if it wants to retain the information after returning from this function. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aResolver The resolver to be started. + * + */ +void otPlatDnssdStartIp4AddressResolver(otInstance *aInstance, const otPlatDnssdAddressResolver *aResolver); + +/** + * Stops an IPv4 address resolver. + * + * No action is performed if no matching resolver with the same host name and callback is currently active. + * + * The @p aResolver and all its contained information (strings) are only valid during this call. The platform MUST save + * a copy of the information if it wants to retain the information after returning from this function. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aResolver The resolver to stop. + * + */ +void otPlatDnssdStopIp4AddressResolver(otInstance *aInstance, const otPlatDnssdAddressResolver *aResolver); + /** * @} * diff --git a/script/check-scan-build b/script/check-scan-build index 56925d08a..2d855fb16 100755 --- a/script/check-scan-build +++ b/script/check-scan-build @@ -52,6 +52,8 @@ OT_BUILD_OPTIONS=( "-DOT_DHCP6_CLIENT=ON" "-DOT_DHCP6_SERVER=ON" "-DOT_DIAGNOSTIC=ON" + "-DOT_DNSSD_DISCOVERY_PROXY=ON" + "-DOT_DNSSD_SERVER=ON" "-DOT_DNS_CLIENT=ON" "-DOT_DNS_DSO=ON" "-DOT_ECDSA=ON" diff --git a/script/make-pretty b/script/make-pretty index 84b6204c5..67885d8b0 100755 --- a/script/make-pretty +++ b/script/make-pretty @@ -110,6 +110,7 @@ OT_CLANG_TIDY_BUILD_OPTS=( '-DOT_DNS_CLIENT=ON' '-DOT_DNS_DSO=ON' '-DOT_DNS_UPSTREAM_QUERY=ON' + "-DOT_DNSSD_DISCOVERY_PROXY=ON" '-DOT_DNSSD_SERVER=ON' '-DOT_DUA=ON' '-DOT_MLR=ON' diff --git a/src/core/border_router/infra_if.cpp b/src/core/border_router/infra_if.cpp index 0b44ee24f..25efee5b8 100644 --- a/src/core/border_router/infra_if.cpp +++ b/src/core/border_router/infra_if.cpp @@ -156,6 +156,10 @@ Error InfraIf::HandleStateChanged(uint32_t aIfIndex, bool aIsRunning) Get().HandleInfraIfStateChanged(); #endif +#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE && OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + Get().HandleInfraIfStateChanged(); +#endif + #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE && OPENTHREAD_CONFIG_MULTICAST_DNS_AUTO_ENABLE_ON_INFRA_IF Get().HandleInfraIfStateChanged(); #endif diff --git a/src/core/config/dnssd_server.h b/src/core/config/dnssd_server.h index 36b5620f3..fb5aa3f00 100644 --- a/src/core/config/dnssd_server.h +++ b/src/core/config/dnssd_server.h @@ -85,6 +85,16 @@ #define OPENTHREAD_CONFIG_DNSSD_QUERY_TIMEOUT 6000 #endif +/** + * @def OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + * + * Define to 1 to enable DNS-SD Discovery Proxy support. + * + */ +#ifndef OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE +#define OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE 0 +#endif + /** * @def OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE * diff --git a/src/core/net/dnssd.cpp b/src/core/net/dnssd.cpp index 5e068b906..6d54b5e99 100644 --- a/src/core/net/dnssd.cpp +++ b/src/core/net/dnssd.cpp @@ -255,6 +255,237 @@ exit: return; } +void Dnssd::StartBrowser(const Browser &aBrowser) +{ + VerifyOrExit(IsReady()); + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ALLOW_RUN_TIME_SELECTION + if (mUseNativeMdns) +#endif +#if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE + { + IgnoreError(Get().StartBrowser(aBrowser)); + ExitNow(); + } +#endif + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE + otPlatDnssdStartBrowser(&GetInstance(), &aBrowser); +#endif + +exit: + return; +} + +void Dnssd::StopBrowser(const Browser &aBrowser) +{ + VerifyOrExit(IsReady()); + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ALLOW_RUN_TIME_SELECTION + if (mUseNativeMdns) +#endif +#if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE + { + IgnoreError(Get().StopBrowser(aBrowser)); + ExitNow(); + } +#endif + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE + otPlatDnssdStopBrowser(&GetInstance(), &aBrowser); +#endif + +exit: + return; +} + +void Dnssd::StartSrvResolver(const SrvResolver &aResolver) +{ + VerifyOrExit(IsReady()); + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ALLOW_RUN_TIME_SELECTION + if (mUseNativeMdns) +#endif +#if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE + { + IgnoreError(Get().StartSrvResolver(aResolver)); + ExitNow(); + } +#endif + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE + otPlatDnssdStartSrvResolver(&GetInstance(), &aResolver); +#endif + +exit: + return; +} + +void Dnssd::StopSrvResolver(const SrvResolver &aResolver) +{ + VerifyOrExit(IsReady()); + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ALLOW_RUN_TIME_SELECTION + if (mUseNativeMdns) +#endif +#if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE + { + IgnoreError(Get().StopSrvResolver(aResolver)); + ExitNow(); + } +#endif + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE + otPlatDnssdStopSrvResolver(&GetInstance(), &aResolver); +#endif + +exit: + return; +} + +void Dnssd::StartTxtResolver(const TxtResolver &aResolver) +{ + VerifyOrExit(IsReady()); + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ALLOW_RUN_TIME_SELECTION + if (mUseNativeMdns) +#endif +#if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE + { + IgnoreError(Get().StartTxtResolver(aResolver)); + ExitNow(); + } +#endif + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE + otPlatDnssdStartTxtResolver(&GetInstance(), &aResolver); +#endif + +exit: + return; +} + +void Dnssd::StopTxtResolver(const TxtResolver &aResolver) +{ + VerifyOrExit(IsReady()); + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ALLOW_RUN_TIME_SELECTION + if (mUseNativeMdns) +#endif +#if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE + { + IgnoreError(Get().StopTxtResolver(aResolver)); + ExitNow(); + } +#endif + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE + otPlatDnssdStopTxtResolver(&GetInstance(), &aResolver); +#endif + +exit: + return; +} + +void Dnssd::StartIp6AddressResolver(const AddressResolver &aResolver) +{ + VerifyOrExit(IsReady()); + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ALLOW_RUN_TIME_SELECTION + if (mUseNativeMdns) +#endif +#if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE + { + IgnoreError(Get().StartIp6AddressResolver(aResolver)); + ExitNow(); + } +#endif + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE + otPlatDnssdStartIp6AddressResolver(&GetInstance(), &aResolver); +#endif + +exit: + return; +} + +void Dnssd::StopIp6AddressResolver(const AddressResolver &aResolver) +{ + VerifyOrExit(IsReady()); + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ALLOW_RUN_TIME_SELECTION + if (mUseNativeMdns) +#endif +#if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE + { + IgnoreError(Get().StopIp6AddressResolver(aResolver)); + ExitNow(); + } +#endif + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE + otPlatDnssdStopIp6AddressResolver(&GetInstance(), &aResolver); +#endif + +exit: + return; +} + +void Dnssd::StartIp4AddressResolver(const AddressResolver &aResolver) +{ + VerifyOrExit(IsReady()); + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ALLOW_RUN_TIME_SELECTION + if (mUseNativeMdns) +#endif +#if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE + { + IgnoreError(Get().StartIp4AddressResolver(aResolver)); + ExitNow(); + } +#endif + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE + otPlatDnssdStartIp4AddressResolver(&GetInstance(), &aResolver); +#endif + +exit: + return; +} + +void Dnssd::StopIp4AddressResolver(const AddressResolver &aResolver) +{ + VerifyOrExit(IsReady()); + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ALLOW_RUN_TIME_SELECTION + if (mUseNativeMdns) +#endif +#if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE + { + IgnoreError(Get().StopIp4AddressResolver(aResolver)); + ExitNow(); + } +#endif + +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE + otPlatDnssdStopIp4AddressResolver(&GetInstance(), &aResolver); +#endif + +exit: + return; +} + +void Dnssd::HandleStateChange(void) +{ +#if OPENTHREAD_CONFIG_SRP_SERVER_ADVERTISING_PROXY_ENABLE + Get().HandleDnssdPlatformStateChange(); +#endif + +#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE && OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + Get().HandleDnssdPlatformStateChange(); +#endif +} + #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE void Dnssd::HandleMdnsCoreStateChange(void) { @@ -267,17 +498,12 @@ void Dnssd::HandleMdnsCoreStateChange(void) } #endif -void Dnssd::HandleStateChange(void) -{ -#if OPENTHREAD_CONFIG_SRP_SERVER_ADVERTISING_PROXY_ENABLE - Get().HandleDnssdPlatformStateChange(); -#endif -} - +#if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE extern "C" void otPlatDnssdStateHandleStateChange(otInstance *aInstance) { AsCoreType(aInstance).Get().HandleStateChange(); } +#endif } // namespace ot diff --git a/src/core/net/dnssd.hpp b/src/core/net/dnssd.hpp index 9bf19aa81..1864a377c 100644 --- a/src/core/net/dnssd.hpp +++ b/src/core/net/dnssd.hpp @@ -93,6 +93,15 @@ public: typedef otPlatDnssdRequestId RequestId; ///< A request ID. typedef otPlatDnssdRegisterCallback RegisterCallback; ///< The registration request callback + typedef otPlatDnssdBrowseCallback BrowseCallback; ///< Browser callback. + typedef otPlatDnssdSrvCallback SrvCallback; ///< SRV callback. + typedef otPlatDnssdTxtCallback TxtCallback; ///< TXT callback. + typedef otPlatDnssdAddressCallback AddressCallback; ///< Address callback + typedef otPlatDnssdBrowseResult BrowseResult; ///< Browser result. + typedef otPlatDnssdSrvResult SrvResult; ///< SRV result. + typedef otPlatDnssdTxtResult TxtResult; ///< TXT result. + typedef otPlatDnssdAddressResult AddressResult; ///< Address result. + typedef otPlatDnssdAddressAndTtl AddressAndTtl; ///< Address and TTL. class Host : public otPlatDnssdHost, public Clearable ///< Host information. { @@ -106,6 +115,22 @@ public: { }; + class Browser : public otPlatDnssdBrowser, public Clearable ///< Browser. + { + }; + + class SrvResolver : public otPlatDnssdSrvResolver, public Clearable ///< SRV resolver. + { + }; + + class TxtResolver : public otPlatDnssdTxtResolver, public Clearable ///< TXT resolver. + { + }; + + class AddressResolver : public otPlatDnssdAddressResolver, public Clearable ///< Address resolver. + { + }; + /** * Represents a range of `RequestId` values. * @@ -274,6 +299,116 @@ public: */ void UnregisterKey(const Key &aKey, RequestId aRequestId, RegisterCallback aCallback); + /** + * Starts a service browser. + * + * Refer to the documentation for `otPlatDnssdStartBrowser()` for a more detailed description of the behavior + * of this method. + * + * @param[in] aBrowser The browser to be started. + * + */ + void StartBrowser(const Browser &aBrowser); + + /** + * Stops a service browser. + * + * Refer to the documentation for `otPlatDnssdStopBrowser()` for a more detailed description of the behavior + * of this method. + * + * @param[in] aBrowser The browser to stop. + * + */ + void StopBrowser(const Browser &aBrowser); + + /** + * Starts an SRV record resolver. + * + * Refer to the documentation for `otPlatDnssdStartSrvResolver()` for a more detailed description of the behavior + * of this method. + * + * @param[in] aResolver The resolver to be started. + * + */ + void StartSrvResolver(const SrvResolver &aResolver); + + /** + * Stops an SRV record resolver. + * + * Refer to the documentation for `otPlatDnssdStopSrvResolver()` for a more detailed description of the behavior + * of this method. + * + * @param[in] aResolver The resolver to stop. + * + */ + void StopSrvResolver(const SrvResolver &aResolver); + + /** + * Starts a TXT record resolver. + * + * Refer to the documentation for `otPlatDnssdStartTxtResolver()` for a more detailed description of the behavior + * of this method. + * + * @param[in] aResolver The resolver to be started. + * + */ + void StartTxtResolver(const TxtResolver &aResolver); + + /** + * Stops a TXT record resolver. + * + * Refer to the documentation for `otPlatDnssdStopTxtResolver()` for a more detailed description of the behavior + * of this method. + * + * @param[in] aResolver The resolver to stop. + * + */ + void StopTxtResolver(const TxtResolver &aResolver); + + /** + * Starts an IPv6 address resolver. + * + * Refer to the documentation for `otPlatDnssdStartIp6AddressResolver()` for a more detailed description of the + * behavior of this method. + * + * @param[in] aResolver The resolver to be started. + * + */ + void StartIp6AddressResolver(const AddressResolver &aResolver); + + /** + * Stops an IPv6 address resolver. + * + * Refer to the documentation for `otPlatDnssdStopIp6AddressResolver()` for a more detailed description of the + * behavior of this method. + * + * @param[in] aResolver The resolver to stop. + * + */ + void StopIp6AddressResolver(const AddressResolver &aResolver); + + /** + * Starts an IPv4 address resolver. + * + * Refer to the documentation for `otPlatDnssdStartIp4AddressResolver()` for a more detailed description of the + * behavior of this method. + * + * @param[in] aResolver The resolver to be started. + * + */ + void StartIp4AddressResolver(const AddressResolver &aResolver); + + /** + * Stops an IPv4 address resolver. + * + * Refer to the documentation for `otPlatDnssdStopIp4AddressResolver()` for a more detailed description of the + * behavior of this method. + * + * @param[in] aResolver The resolver to stop. + * + */ + void StopIp4AddressResolver(const AddressResolver &aResolver); + #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE /** * Handles native mDNS state change. diff --git a/src/core/net/dnssd_server.cpp b/src/core/net/dnssd_server.cpp index af24fe95e..9f177db4b 100644 --- a/src/core/net/dnssd_server.cpp +++ b/src/core/net/dnssd_server.cpp @@ -64,6 +64,9 @@ const char *Server::kBlockedDomains[] = {"ipv4only.arpa."}; Server::Server(Instance &aInstance) : InstanceLocator(aInstance) , mSocket(aInstance) +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + , mDiscoveryProxy(aInstance) +#endif #if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE , mEnableUpstreamQuery(false) #endif @@ -88,6 +91,10 @@ Error Server::Start(void) LogInfo("Started"); +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + mDiscoveryProxy.UpdateState(); +#endif + exit: if (error != kErrorNone) { @@ -104,6 +111,10 @@ void Server::Stop(void) Finalize(query, Header::kResponseServerFailure); } +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + mDiscoveryProxy.Stop(); +#endif + #if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE for (UpstreamQueryTransaction &txn : mUpstreamQueryTransactions) { @@ -586,22 +597,30 @@ Error Server::Response::AppendHostAddresses(const Ip6::Address *aAddrs, uint16_t for (uint16_t index = 0; index < aAddrsLength; index++) { - AaaaRecord aaaaRecord; - - aaaaRecord.Init(); - aaaaRecord.SetTtl(aTtl); - aaaaRecord.SetAddress(aAddrs[index]); - - SuccessOrExit(error = Name::AppendPointerLabel(mOffsets.mHostName, *mMessage)); - SuccessOrExit(error = mMessage->Append(aaaaRecord)); - - IncResourceRecordCount(); + SuccessOrExit(error = AppendAaaaRecord(aAddrs[index], aTtl)); } exit: return error; } +Error Server::Response::AppendAaaaRecord(const Ip6::Address &aAddress, uint32_t aTtl) +{ + Error error; + AaaaRecord aaaaRecord; + + aaaaRecord.Init(); + aaaaRecord.SetTtl(aTtl); + aaaaRecord.SetAddress(aAddress); + + SuccessOrExit(error = Name::AppendPointerLabel(mOffsets.mHostName, *mMessage)); + SuccessOrExit(error = mMessage->Append(aaaaRecord)); + IncResourceRecordCount(); + +exit: + return error; +} + #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE Error Server::Response::AppendTxtRecord(const Srp::Server::Service &aService) { @@ -915,9 +934,12 @@ void Server::ResolveByProxy(Response &aResponse, const Ip6::MessageInfo &aMessag { ProxyQuery *query; ProxyQueryInfo info; - Name::Buffer name; +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + VerifyOrExit(mQuerySubscribe.IsSet() || mDiscoveryProxy.IsRunning()); +#else VerifyOrExit(mQuerySubscribe.IsSet()); +#endif // We try to convert `aResponse.mMessage` to a `ProxyQuery` by // appending `ProxyQueryInfo` to it. @@ -927,6 +949,10 @@ void Server::ResolveByProxy(Response &aResponse, const Ip6::MessageInfo &aMessag info.mExpireTime = TimerMilli::GetNow() + kQueryTimeout; info.mOffsets = aResponse.mOffsets; +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + info.mAction = kNoAction; +#endif + if (aResponse.mMessage->Append(info) != kErrorNone) { aResponse.SetResponseCode(Header::kResponseServerFailure); @@ -943,8 +969,21 @@ void Server::ResolveByProxy(Response &aResponse, const Ip6::MessageInfo &aMessag mTimer.FireAtIfEarlier(info.mExpireTime); - ReadQueryName(*query, name); - mQuerySubscribe.Invoke(name); +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + if (mQuerySubscribe.IsSet()) +#endif + { + Name::Buffer name; + + ReadQueryName(*query, name); + mQuerySubscribe.Invoke(name); + } +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + else + { + mDiscoveryProxy.Resolve(*query, info); + } +#endif exit: return; @@ -964,6 +1003,91 @@ bool Server::QueryNameMatches(const Message &aQuery, const char *aName) return (Name::CompareName(aQuery, offset, aName) == kErrorNone); } +void Server::ReadQueryInstanceName(const ProxyQuery &aQuery, const ProxyQueryInfo &aInfo, Name::Buffer &aName) +{ + uint16_t offset = aInfo.mOffsets.mInstanceName; + + IgnoreError(Name::ReadName(aQuery, offset, aName, sizeof(aName))); +} + +void Server::ReadQueryInstanceName(const ProxyQuery &aQuery, + const ProxyQueryInfo &aInfo, + Name::LabelBuffer &aInstanceLabel, + Name::Buffer &aServiceType) +{ + // Reads the service instance label and service type with domain + // name stripped. + + uint16_t offset = aInfo.mOffsets.mInstanceName; + uint8_t labelLength = sizeof(aInstanceLabel); + + IgnoreError(Dns::Name::ReadLabel(aQuery, offset, aInstanceLabel, labelLength)); + IgnoreError(Dns::Name::ReadName(aQuery, offset, aServiceType)); + IgnoreError(StripDomainName(aServiceType)); +} + +bool Server::QueryInstanceNameMatches(const ProxyQuery &aQuery, const ProxyQueryInfo &aInfo, const char *aName) +{ + uint16_t offset = aInfo.mOffsets.mInstanceName; + + return (Name::CompareName(aQuery, offset, aName) == kErrorNone); +} + +void Server::ReadQueryHostName(const ProxyQuery &aQuery, const ProxyQueryInfo &aInfo, Name::Buffer &aName) +{ + uint16_t offset = aInfo.mOffsets.mHostName; + + IgnoreError(Name::ReadName(aQuery, offset, aName, sizeof(aName))); +} + +bool Server::QueryHostNameMatches(const ProxyQuery &aQuery, const ProxyQueryInfo &aInfo, const char *aName) +{ + uint16_t offset = aInfo.mOffsets.mHostName; + + return (Name::CompareName(aQuery, offset, aName) == kErrorNone); +} + +Error Server::StripDomainName(Name::Buffer &aName) +{ + // In-place removes the domain name from `aName`. + + return Name::StripName(aName, kDefaultDomainName); +} + +Error Server::StripDomainName(const char *aFullName, Name::Buffer &aLabels) +{ + // Remove the domain name from `aFullName` and copies + // the result into `aLabels`. + + return Name::ExtractLabels(aFullName, kDefaultDomainName, aLabels, sizeof(aLabels)); +} + +void Server::ConstructFullName(const char *aLabels, Name::Buffer &aFullName) +{ + // Construct a full name by appending the default domain name + // to `aLabels`. + + StringWriter fullName(aFullName, sizeof(aFullName)); + + fullName.Append("%s.%s", aLabels, kDefaultDomainName); +} + +void Server::ConstructFullInstanceName(const char *aInstanceLabel, const char *aServiceType, Name::Buffer &aFullName) +{ + StringWriter fullName(aFullName, sizeof(aFullName)); + + fullName.Append("%s.%s.%s", aInstanceLabel, aServiceType, kDefaultDomainName); +} + +void Server::ConstructFullServiceSubTypeName(const char *aServiceType, + const char *aSubTypeLabel, + Name::Buffer &aFullName) +{ + StringWriter fullName(aFullName, sizeof(aFullName)); + + fullName.Append("%s._sub.%s.%s", aSubTypeLabel, aServiceType, kDefaultDomainName); +} + void Server::ProxyQueryInfo::ReadFrom(const ProxyQuery &aQuery) { SuccessOrAssert(aQuery.Read(aQuery.GetLength() - sizeof(ProxyQueryInfo), *this)); @@ -987,15 +1111,22 @@ Error Server::Response::ExtractServiceInstanceLabel(const char *aInstanceName, N return Name::ExtractLabels(aInstanceName, serviceName, aLabel); } -void Server::RemoveQueryAndPrepareResponse(ProxyQuery &aQuery, const ProxyQueryInfo &aInfo, Response &aResponse) +void Server::RemoveQueryAndPrepareResponse(ProxyQuery &aQuery, ProxyQueryInfo &aInfo, Response &aResponse) { - Name::Buffer name; +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + mDiscoveryProxy.CancelAction(aQuery, aInfo); +#endif mProxyQueries.Dequeue(aQuery); aInfo.RemoveFrom(aQuery); - ReadQueryName(aQuery, name); - mQueryUnsubscribe.InvokeIfSet(name); + if (mQueryUnsubscribe.IsSet()) + { + Name::Buffer name; + + ReadQueryName(aQuery, name); + mQueryUnsubscribe.Invoke(name); + } aResponse.InitFrom(aQuery, aInfo); } @@ -1282,6 +1413,706 @@ void Server::ResetUpstreamQueryTransaction(UpstreamQueryTransaction &aTxn, Error } #endif +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + +Server::DiscoveryProxy::DiscoveryProxy(Instance &aInstance) + : InstanceLocator(aInstance) + , mIsRunning(false) +{ +} + +void Server::DiscoveryProxy::UpdateState(void) +{ + if (Get().IsRunning() && Get().IsReady() && Get().IsRunning()) + { + Start(); + } + else + { + Stop(); + } +} + +void Server::DiscoveryProxy::Start(void) +{ + VerifyOrExit(!mIsRunning); + mIsRunning = true; + LogInfo("Started discovery proxy"); + +exit: + return; +} + +void Server::DiscoveryProxy::Stop(void) +{ + VerifyOrExit(mIsRunning); + + for (ProxyQuery &query : Get().mProxyQueries) + { + Get().Finalize(query, Header::kResponseSuccess); + } + + mIsRunning = false; + LogInfo("Stopped discovery proxy"); + +exit: + return; +} + +void Server::DiscoveryProxy::Resolve(ProxyQuery &aQuery, ProxyQueryInfo &aInfo) +{ + ProxyAction action = kNoAction; + + switch (aInfo.mType) + { + case kPtrQuery: + action = kBrowsing; + break; + + case kSrvQuery: + case kSrvTxtQuery: + action = kResolvingSrv; + break; + + case kTxtQuery: + action = kResolvingTxt; + break; + + case kAaaaQuery: + action = kResolvingIp6Address; + break; + } + + Perform(action, aQuery, aInfo); +} + +void Server::DiscoveryProxy::Perform(ProxyAction aAction, ProxyQuery &aQuery, ProxyQueryInfo &aInfo) +{ + bool shouldStart; + Name::Buffer name; + + VerifyOrExit(aAction != kNoAction); + + // The order of the steps below is crucial. First, we read the + // name associated with the action. Then we check if another + // query has an active browser/resolver for the same name. This + // helps us determine if a new browser/resolver is needed. Then, + // we update the `ProxyQueryInfo` within `aQuery` to reflect the + // `aAction` being performed. Finally, if necessary, we start the + // proper browser/resolver on DNS-SD/mDNS. Placing this last + // ensures correct processing even if a DNS-SD/mDNS callback is + // invoked immediately. + + ReadNameFor(aAction, aQuery, aInfo, name); + + shouldStart = !HasActive(aAction, name); + + aInfo.mAction = aAction; + aInfo.UpdateIn(aQuery); + + VerifyOrExit(shouldStart); + UpdateProxy(kStart, aAction, aQuery, aInfo, name); + +exit: + return; +} + +void Server::DiscoveryProxy::ReadNameFor(ProxyAction aAction, + ProxyQuery &aQuery, + ProxyQueryInfo &aInfo, + Name::Buffer &aName) const +{ + // Read the name corresponding to `aAction` from `aQuery`. + + switch (aAction) + { + case kNoAction: + break; + case kBrowsing: + ReadQueryName(aQuery, aName); + break; + case kResolvingSrv: + case kResolvingTxt: + ReadQueryInstanceName(aQuery, aInfo, aName); + break; + case kResolvingIp6Address: + ReadQueryHostName(aQuery, aInfo, aName); + break; + } +} + +void Server::DiscoveryProxy::CancelAction(ProxyQuery &aQuery, ProxyQueryInfo &aInfo) +{ + // Cancel the current action for a given `aQuery`, then + // determine if we need to stop any browser/resolver + // on infrastructure. + + ProxyAction action = aInfo.mAction; + Name::Buffer name; + + VerifyOrExit(mIsRunning); + VerifyOrExit(action != kNoAction); + + // We first update the `aInfo` on `aQuery` before calling + // `HasActive()`. This ensures that the current query is not + // taken into account when we try to determine if any query + // is waiting for same `aAction` browser/resolver. + + ReadNameFor(action, aQuery, aInfo, name); + + aInfo.mAction = kNoAction; + aInfo.UpdateIn(aQuery); + + VerifyOrExit(!HasActive(action, name)); + UpdateProxy(kStop, action, aQuery, aInfo, name); + +exit: + return; +} + +void Server::DiscoveryProxy::UpdateProxy(Command aCommand, + ProxyAction aAction, + const ProxyQuery &aQuery, + const ProxyQueryInfo &aInfo, + Name::Buffer &aName) +{ + // Start or stop browser/resolver corresponding to `aAction`. + // `aName` may be changed. + + switch (aAction) + { + case kNoAction: + break; + case kBrowsing: + StartOrStopBrowser(aCommand, aName); + break; + case kResolvingSrv: + StartOrStopSrvResolver(aCommand, aQuery, aInfo); + break; + case kResolvingTxt: + StartOrStopTxtResolver(aCommand, aQuery, aInfo); + break; + case kResolvingIp6Address: + StartOrStopIp6Resolver(aCommand, aName); + break; + } +} + +void Server::DiscoveryProxy::StartOrStopBrowser(Command aCommand, Name::Buffer &aServiceName) +{ + // Start or stop a service browser for a given service type + // or sub-type. + + static const char kFullSubLabel[] = "._sub."; + + Dnssd::Browser browser; + char *ptr; + + browser.Clear(); + + IgnoreError(StripDomainName(aServiceName)); + + // Check if the service name is a sub-type with name + // format: "._sub.. + + ptr = AsNonConst(StringFind(aServiceName, kFullSubLabel, kStringCaseInsensitiveMatch)); + + if (ptr != nullptr) + { + *ptr = kNullChar; + ptr += sizeof(kFullSubLabel) - 1; + + browser.mServiceType = ptr; + browser.mSubTypeLabel = aServiceName; + } + else + { + browser.mServiceType = aServiceName; + browser.mSubTypeLabel = nullptr; + } + + browser.mInfraIfIndex = Get().GetIfIndex(); + browser.mCallback = HandleBrowseResult; + + switch (aCommand) + { + case kStart: + Get().StartBrowser(browser); + break; + + case kStop: + Get().StopBrowser(browser); + break; + } +} + +void Server::DiscoveryProxy::StartOrStopSrvResolver(Command aCommand, + const ProxyQuery &aQuery, + const ProxyQueryInfo &aInfo) +{ + // Start or stop an SRV record resolver for a given query. + + Dnssd::SrvResolver resolver; + Name::LabelBuffer instanceLabel; + Name::Buffer serviceType; + + ReadQueryInstanceName(aQuery, aInfo, instanceLabel, serviceType); + + resolver.Clear(); + + resolver.mServiceInstance = instanceLabel; + resolver.mServiceType = serviceType; + resolver.mInfraIfIndex = Get().GetIfIndex(); + resolver.mCallback = HandleSrvResult; + + switch (aCommand) + { + case kStart: + Get().StartSrvResolver(resolver); + break; + + case kStop: + Get().StopSrvResolver(resolver); + break; + } +} + +void Server::DiscoveryProxy::StartOrStopTxtResolver(Command aCommand, + const ProxyQuery &aQuery, + const ProxyQueryInfo &aInfo) +{ + // Start or stop a TXT record resolver for a given query. + + Dnssd::TxtResolver resolver; + Name::LabelBuffer instanceLabel; + Name::Buffer serviceType; + + ReadQueryInstanceName(aQuery, aInfo, instanceLabel, serviceType); + + resolver.Clear(); + + resolver.mServiceInstance = instanceLabel; + resolver.mServiceType = serviceType; + resolver.mInfraIfIndex = Get().GetIfIndex(); + resolver.mCallback = HandleTxtResult; + + switch (aCommand) + { + case kStart: + Get().StartTxtResolver(resolver); + break; + + case kStop: + Get().StopTxtResolver(resolver); + break; + } +} + +void Server::DiscoveryProxy::StartOrStopIp6Resolver(Command aCommand, Name::Buffer &aHostName) +{ + // Start or stop an IPv6 address resolver for a given host name. + + Dnssd::AddressResolver resolver; + + IgnoreError(StripDomainName(aHostName)); + + resolver.mHostName = aHostName; + resolver.mInfraIfIndex = Get().GetIfIndex(); + resolver.mCallback = HandleIp6AddressResult; + + switch (aCommand) + { + case kStart: + Get().StartIp6AddressResolver(resolver); + break; + + case kStop: + Get().StopIp6AddressResolver(resolver); + break; + } +} + +bool Server::DiscoveryProxy::QueryMatches(const ProxyQuery &aQuery, + const ProxyQueryInfo &aInfo, + ProxyAction aAction, + const Name::Buffer &aName) const +{ + // Check whether `aQuery` is performing `aAction` and + // its name matches `aName`. + + bool matches = false; + + VerifyOrExit(aInfo.mAction == aAction); + + switch (aAction) + { + case kBrowsing: + VerifyOrExit(QueryNameMatches(aQuery, aName)); + break; + case kResolvingSrv: + case kResolvingTxt: + VerifyOrExit(QueryInstanceNameMatches(aQuery, aInfo, aName)); + break; + case kResolvingIp6Address: + VerifyOrExit(QueryHostNameMatches(aQuery, aInfo, aName)); + break; + case kNoAction: + ExitNow(); + } + + matches = true; + +exit: + return matches; +} + +bool Server::DiscoveryProxy::HasActive(ProxyAction aAction, const Name::Buffer &aName) const +{ + // Determine whether or not we have an active browser/resolver + // corresponding to `aAction` for `aName`. + + bool has = false; + + for (const ProxyQuery &query : Get().mProxyQueries) + { + ProxyQueryInfo info; + + info.ReadFrom(query); + + if (QueryMatches(query, info, aAction, aName)) + { + has = true; + break; + } + } + + return has; +} + +void Server::DiscoveryProxy::HandleBrowseResult(otInstance *aInstance, const otPlatDnssdBrowseResult *aResult) +{ + AsCoreType(aInstance).Get().mDiscoveryProxy.HandleBrowseResult(*aResult); +} + +void Server::DiscoveryProxy::HandleBrowseResult(const Dnssd::BrowseResult &aResult) +{ + Name::Buffer serviceName; + + VerifyOrExit(mIsRunning); + VerifyOrExit(aResult.mTtl != 0); + VerifyOrExit(aResult.mInfraIfIndex == Get().GetIfIndex()); + + if (aResult.mSubTypeLabel != nullptr) + { + ConstructFullServiceSubTypeName(aResult.mServiceType, aResult.mSubTypeLabel, serviceName); + } + else + { + ConstructFullName(aResult.mServiceType, serviceName); + } + + HandleResult(kBrowsing, serviceName, &Response::AppendPtrRecord, ProxyResult(aResult)); + +exit: + return; +} + +void Server::DiscoveryProxy::HandleSrvResult(otInstance *aInstance, const otPlatDnssdSrvResult *aResult) +{ + AsCoreType(aInstance).Get().mDiscoveryProxy.HandleSrvResult(*aResult); +} + +void Server::DiscoveryProxy::HandleSrvResult(const Dnssd::SrvResult &aResult) +{ + Name::Buffer instanceName; + + VerifyOrExit(mIsRunning); + VerifyOrExit(aResult.mTtl != 0); + VerifyOrExit(aResult.mInfraIfIndex == Get().GetIfIndex()); + + ConstructFullInstanceName(aResult.mServiceInstance, aResult.mServiceType, instanceName); + HandleResult(kResolvingSrv, instanceName, &Response::AppendSrvRecord, ProxyResult(aResult)); + +exit: + return; +} + +void Server::DiscoveryProxy::HandleTxtResult(otInstance *aInstance, const otPlatDnssdTxtResult *aResult) +{ + AsCoreType(aInstance).Get().mDiscoveryProxy.HandleTxtResult(*aResult); +} + +void Server::DiscoveryProxy::HandleTxtResult(const Dnssd::TxtResult &aResult) +{ + Name::Buffer instanceName; + + VerifyOrExit(mIsRunning); + VerifyOrExit(aResult.mTtl != 0); + VerifyOrExit(aResult.mInfraIfIndex == Get().GetIfIndex()); + + ConstructFullInstanceName(aResult.mServiceInstance, aResult.mServiceType, instanceName); + HandleResult(kResolvingTxt, instanceName, &Response::AppendTxtRecord, ProxyResult(aResult)); + +exit: + return; +} + +void Server::DiscoveryProxy::HandleIp6AddressResult(otInstance *aInstance, const otPlatDnssdAddressResult *aResult) +{ + AsCoreType(aInstance).Get().mDiscoveryProxy.HandleIp6AddressResult(*aResult); +} + +void Server::DiscoveryProxy::HandleIp6AddressResult(const Dnssd::AddressResult &aResult) +{ + bool hasValidAddress = false; + Name::Buffer fullHostName; + + VerifyOrExit(mIsRunning); + VerifyOrExit(aResult.mInfraIfIndex == Get().GetIfIndex()); + + for (uint16_t index = 0; index < aResult.mAddressesLength; index++) + { + const Dnssd::AddressAndTtl &entry = aResult.mAddresses[index]; + const Ip6::Address &address = AsCoreType(&entry.mAddress); + + if (entry.mTtl == 0) + { + continue; + } + + if (IsProxyAddressValid(address)) + { + hasValidAddress = true; + break; + } + } + + VerifyOrExit(hasValidAddress); + + ConstructFullName(aResult.mHostName, fullHostName); + HandleResult(kResolvingIp6Address, fullHostName, &Response::AppendHostAddresses, ProxyResult(aResult)); + +exit: + return; +} + +void Server::DiscoveryProxy::HandleResult(ProxyAction aAction, + const Name::Buffer &aName, + ResponseAppender aAppender, + const ProxyResult &aResult) +{ + // Common method that handles result from DNS-SD/mDNS. It + // iterates over all `ProxyQuery` entries and checks if any entry + // is waiting for the result of `aAction` for `aName`. Matching + // queries are updated using the `aAppender` method pointer, + // which appends the corresponding record(s) to the response. We + // then determine the next action to be performed for the + // `ProxyQuery` or if it can be finalized. + + ProxyQueryList nextActionQueries; + ProxyQueryInfo info; + ProxyAction nextAction; + + for (ProxyQuery &query : Get().mProxyQueries) + { + Response response(GetInstance()); + bool shouldFinalize; + + info.ReadFrom(query); + + if (!QueryMatches(query, info, aAction, aName)) + { + continue; + } + + CancelAction(query, info); + + nextAction = kNoAction; + + switch (aAction) + { + case kBrowsing: + nextAction = kResolvingSrv; + break; + case kResolvingSrv: + nextAction = (info.mType == kSrvQuery) ? kResolvingIp6Address : kResolvingTxt; + break; + case kResolvingTxt: + nextAction = (info.mType == kTxtQuery) ? kNoAction : kResolvingIp6Address; + break; + case kNoAction: + case kResolvingIp6Address: + break; + } + + shouldFinalize = (nextAction == kNoAction); + + if ((Get().mTestMode & kTestModeEmptyAdditionalSection) && + IsActionForAdditionalSection(nextAction, info.mType)) + { + shouldFinalize = true; + } + + Get().mProxyQueries.Dequeue(query); + info.RemoveFrom(query); + response.InitFrom(query, info); + + if ((response.*aAppender)(aResult) != kErrorNone) + { + response.SetResponseCode(Header::kResponseServerFailure); + shouldFinalize = true; + } + + if (shouldFinalize) + { + response.Send(info.mMessageInfo); + continue; + } + + // The `query` is not yet finished and we need to perform + // the `nextAction` for it. + + // Reinitialize `response` as a `ProxyQuey` by updating + // and appending `info` to it after the newly appended + // records from `aResult` and saving the `mHeader`. + + info.mOffsets = response.mOffsets; + info.mAction = nextAction; + response.mMessage->Write(0, response.mHeader); + + if (response.mMessage->Append(info) != kErrorNone) + { + response.SetResponseCode(Header::kResponseServerFailure); + response.Send(info.mMessageInfo); + continue; + } + + // Take back ownership of `response.mMessage` as we still + // treat it as a `ProxyQuery`. + + response.mMessage.Release(); + + // We place the `query` in a separate list and add it back to + // the main `mProxyQueries` list after we are done with the + // current iteration. This ensures that other entries in the + // `mProxyQueries` list are not updated or removed due to the + // DNS-SD platform callback being invoked immediately when we + // potentially start a browser or resolver to perform the + // `nextAction` for `query`. + + nextActionQueries.Enqueue(query); + } + + for (ProxyQuery &query : nextActionQueries) + { + nextActionQueries.Dequeue(query); + + info.ReadFrom(query); + + nextAction = info.mAction; + + info.mAction = kNoAction; + info.UpdateIn(query); + + Get().mProxyQueries.Enqueue(query); + Perform(nextAction, query, info); + } +} + +bool Server::DiscoveryProxy::IsActionForAdditionalSection(ProxyAction aAction, QueryType aQueryType) +{ + bool isForAddnlSection = false; + + switch (aAction) + { + case kResolvingSrv: + VerifyOrExit((aQueryType == kSrvQuery) || (aQueryType == kSrvTxtQuery)); + break; + case kResolvingTxt: + VerifyOrExit((aQueryType == kTxtQuery) || (aQueryType == kSrvTxtQuery)); + break; + + case kResolvingIp6Address: + VerifyOrExit(aQueryType == kAaaaQuery); + break; + + case kNoAction: + case kBrowsing: + ExitNow(); + } + + isForAddnlSection = true; + +exit: + return isForAddnlSection; +} + +Error Server::Response::AppendPtrRecord(const ProxyResult &aResult) +{ + const Dnssd::BrowseResult *browseResult = aResult.mBrowseResult; + + mSection = kAnswerSection; + + return AppendPtrRecord(browseResult->mServiceInstance, browseResult->mTtl); +} + +Error Server::Response::AppendSrvRecord(const ProxyResult &aResult) +{ + const Dnssd::SrvResult *srvResult = aResult.mSrvResult; + Name::Buffer fullHostName; + + mSection = ((mType == kSrvQuery) || (mType == kSrvTxtQuery)) ? kAnswerSection : kAdditionalDataSection; + + ConstructFullName(srvResult->mHostName, fullHostName); + + return AppendSrvRecord(fullHostName, srvResult->mTtl, srvResult->mPriority, srvResult->mWeight, srvResult->mPort); +} + +Error Server::Response::AppendTxtRecord(const ProxyResult &aResult) +{ + const Dnssd::TxtResult *txtResult = aResult.mTxtResult; + + mSection = ((mType == kTxtQuery) || (mType == kSrvTxtQuery)) ? kAnswerSection : kAdditionalDataSection; + + return AppendTxtRecord(txtResult->mTxtData, txtResult->mTxtDataLength, txtResult->mTtl); +} + +Error Server::Response::AppendHostAddresses(const ProxyResult &aResult) +{ + Error error = kErrorNone; + const Dnssd::AddressResult *addrResult = aResult.mAddressResult; + + mSection = (mType == kAaaaQuery) ? kAnswerSection : kAdditionalDataSection; + + for (uint16_t index = 0; index < addrResult->mAddressesLength; index++) + { + const Dnssd::AddressAndTtl &entry = addrResult->mAddresses[index]; + const Ip6::Address &address = AsCoreType(&entry.mAddress); + + if (entry.mTtl == 0) + { + continue; + } + + if (!IsProxyAddressValid(address)) + { + continue; + } + + SuccessOrExit(error = AppendAaaaRecord(address, entry.mTtl)); + } + +exit: + return error; +} + +bool Server::IsProxyAddressValid(const Ip6::Address &aAddress) +{ + return !aAddress.IsLinkLocal() && !aAddress.IsMulticast() && !aAddress.IsUnspecified() && !aAddress.IsLoopback(); +} + +#endif // OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + } // namespace ServiceDiscovery } // namespace Dns } // namespace ot diff --git a/src/core/net/dnssd_server.hpp b/src/core/net/dnssd_server.hpp index 4afde1d7d..692239ecb 100644 --- a/src/core/net/dnssd_server.hpp +++ b/src/core/net/dnssd_server.hpp @@ -33,8 +33,20 @@ #if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + +#if !OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE && !OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE +#error "OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE requires either PLATFORM_DNSSD_ENABLE or MULTICAST_DNS_ENABLE" +#endif +#if !OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +#error "OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE requires OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE" +#endif + +#endif // OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + #include +#include "border_router/infra_if.hpp" #include "common/as_core_type.hpp" #include "common/callback.hpp" #include "common/message.hpp" @@ -42,6 +54,7 @@ #include "common/owned_ptr.hpp" #include "common/timer.hpp" #include "net/dns_types.hpp" +#include "net/dnssd.hpp" #include "net/ip6.hpp" #include "net/netif.hpp" #include "net/srp_server.hpp" @@ -71,6 +84,10 @@ namespace ServiceDiscovery { class Server : public InstanceLocator, private NonCopyable { friend class Srp::Server; +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + friend class ot::Dnssd; + friend class ot::BorderRouter::InfraIf; +#endif public: /** @@ -316,6 +333,17 @@ private: kAdditionalDataSection, }; +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + enum ProxyAction : uint8_t + { + kNoAction, + kBrowsing, + kResolvingSrv, + kResolvingTxt, + kResolvingIp6Address, + }; +#endif + struct Request { ResponseCode ParseQuestions(uint8_t aTestMode); @@ -336,6 +364,21 @@ private: uint16_t mHostName; }; +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + union ProxyResult + { + explicit ProxyResult(const Dnssd::BrowseResult &aBrowseResult) { mBrowseResult = &aBrowseResult; } + explicit ProxyResult(const Dnssd::SrvResult &aSrvResult) { mSrvResult = &aSrvResult; } + explicit ProxyResult(const Dnssd::TxtResult &aTxtResult) { mTxtResult = &aTxtResult; } + explicit ProxyResult(const Dnssd::AddressResult &aAddressResult) { mAddressResult = &aAddressResult; } + + const Dnssd::BrowseResult *mBrowseResult; + const Dnssd::SrvResult *mSrvResult; + const Dnssd::TxtResult *mTxtResult; + const Dnssd::AddressResult *mAddressResult; + }; +#endif + class Response : public InstanceLocator, private NonCopyable { public: @@ -360,6 +403,7 @@ private: Error AppendHostAddresses(const HostInfo &aHostInfo); Error AppendHostAddresses(const ServiceInstanceInfo &aInstanceInfo); Error AppendHostAddresses(const Ip6::Address *aAddrs, uint16_t aAddrsLength, uint32_t aTtl); + Error AppendAaaaRecord(const Ip6::Address &aAddress, uint32_t aTtl); void UpdateRecordLength(ResourceRecord &aRecord, uint16_t aOffset); void IncResourceRecordCount(void); void Send(const Ip6::MessageInfo &aMessageInfo); @@ -373,6 +417,14 @@ private: Error AppendTxtRecord(const Srp::Server::Service &aService); Error AppendHostAddresses(const Srp::Server::Host &aHost); #endif +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + Error AppendPtrRecord(const ProxyResult &aResult); + Error AppendSrvRecord(const ProxyResult &aResult); + Error AppendTxtRecord(const ProxyResult &aResult); + + Error AppendHostAddresses(const ProxyResult &aResult); +#endif + #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) void Log(void) const; static const char *QueryTypeToString(QueryType aType); @@ -395,18 +447,106 @@ private: Ip6::MessageInfo mMessageInfo; TimeMilli mExpireTime; NameOffsets mOffsets; +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + ProxyAction mAction; +#endif }; +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + class DiscoveryProxy : public InstanceLocator, private NonCopyable + { + friend ot::Dnssd; + + public: + explicit DiscoveryProxy(Instance &aInstance); + + bool IsRunning(void) const { return mIsRunning; } + void UpdateState(void); + void Start(void); + void Stop(void); + void Resolve(ProxyQuery &aQuery, ProxyQueryInfo &aInfo); + void CancelAction(ProxyQuery &aQuery, ProxyQueryInfo &aInfo); + + private: + enum Command : uint8_t + { + kStart, + kStop, + }; + + typedef Error (Response::*ResponseAppender)(const ProxyResult &aResult); + + void Perform(ProxyAction aAction, ProxyQuery &aQuery, ProxyQueryInfo &aInfo); + void ReadNameFor(ProxyAction aAction, ProxyQuery &aQuery, ProxyQueryInfo &aInfo, Name::Buffer &aName) const; + bool HasActive(ProxyAction aAction, const Name::Buffer &aName) const; + bool QueryMatches(const ProxyQuery &aQuery, + const ProxyQueryInfo &aInfo, + ProxyAction aAction, + const Name::Buffer &aName) const; + void UpdateProxy(Command aCommand, + ProxyAction aAction, + const ProxyQuery &aQuery, + const ProxyQueryInfo &aInfo, + Name::Buffer &aName); + void StartOrStopBrowser(Command aCommand, Name::Buffer &aServiceName); + void StartOrStopSrvResolver(Command aCommand, const ProxyQuery &aQuery, const ProxyQueryInfo &aInfo); + void StartOrStopTxtResolver(Command aCommand, const ProxyQuery &aQuery, const ProxyQueryInfo &aInfo); + void StartOrStopIp6Resolver(Command aCommand, Name::Buffer &aHostName); + + static void HandleBrowseResult(otInstance *aInstance, const otPlatDnssdBrowseResult *aResult); + static void HandleSrvResult(otInstance *aInstance, const otPlatDnssdSrvResult *aResult); + static void HandleTxtResult(otInstance *aInstance, const otPlatDnssdTxtResult *aResult); + static void HandleIp6AddressResult(otInstance *aInstance, const otPlatDnssdAddressResult *aResult); + + void HandleBrowseResult(const Dnssd::BrowseResult &aResult); + void HandleSrvResult(const Dnssd::SrvResult &aResult); + void HandleTxtResult(const Dnssd::TxtResult &aResult); + void HandleIp6AddressResult(const Dnssd::AddressResult &aResult); + void HandleResult(ProxyAction aAction, + const Name::Buffer &aName, + ResponseAppender aAppender, + const ProxyResult &aResult); + + static bool IsActionForAdditionalSection(ProxyAction aAction, QueryType aQueryType); + + bool mIsRunning; + }; +#endif + bool IsRunning(void) const { return mSocket.IsBound(); } static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); void HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); void ProcessQuery(Request &aRequest); - void ResolveByProxy(Response &aResponse, const Ip6::MessageInfo &aMessageInfo); - void RemoveQueryAndPrepareResponse(ProxyQuery &aQuery, const ProxyQueryInfo &aInfo, Response &aResponse); - void Finalize(ProxyQuery &aQuery, ResponseCode aResponseCode); - static void ReadQueryName(const Message &aQuery, Name::Buffer &aName); - static bool QueryNameMatches(const Message &aQuery, const char *aName); + void ResolveByProxy(Response &aResponse, const Ip6::MessageInfo &aMessageInfo); + void RemoveQueryAndPrepareResponse(ProxyQuery &aQuery, ProxyQueryInfo &aInfo, Response &aResponse); + void Finalize(ProxyQuery &aQuery, ResponseCode aResponseCode); + + static void ReadQueryName(const Message &aQuery, Name::Buffer &aName); + static bool QueryNameMatches(const Message &aQuery, const char *aName); + static void ReadQueryInstanceName(const ProxyQuery &aQuery, const ProxyQueryInfo &aInfo, Name::Buffer &aName); + static void ReadQueryInstanceName(const ProxyQuery &aQuery, + const ProxyQueryInfo &aInfo, + Name::LabelBuffer &aInstanceLabel, + Name::Buffer &aServiceType); + static bool QueryInstanceNameMatches(const ProxyQuery &aQuery, const ProxyQueryInfo &aInfo, const char *aName); + static void ReadQueryHostName(const ProxyQuery &aQuery, const ProxyQueryInfo &aInfo, Name::Buffer &aName); + static bool QueryHostNameMatches(const ProxyQuery &aQuery, const ProxyQueryInfo &aInfo, const char *aName); + static Error StripDomainName(const char *aFullName, Name::Buffer &aLabels); + static Error StripDomainName(Name::Buffer &aName); + static void ConstructFullName(const char *aLabels, Name::Buffer &aFullName); + static void ConstructFullInstanceName(const char *aInstanceLabel, + const char *aServiceType, + Name::Buffer &aFullName); + static void ConstructFullServiceSubTypeName(const char *aServiceType, + const char *aSubTypeLabel, + Name::Buffer &aFullName); + +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + void HandleInfraIfStateChanged(void) { mDiscoveryProxy.UpdateState(); } + void HandleDnssdPlatformStateChange(void) { mDiscoveryProxy.UpdateState(); } + static bool IsProxyAddressValid(const Ip6::Address &aAddress); +#endif #if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE static bool ShouldForwardToUpstream(const Request &aRequest); @@ -434,6 +574,10 @@ private: Callback mQuerySubscribe; Callback mQueryUnsubscribe; +#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + DiscoveryProxy mDiscoveryProxy; +#endif + #if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE bool mEnableUpstreamQuery; UpstreamQueryTransaction mUpstreamQueryTransactions[kMaxConcurrentUpstreamQueries]; diff --git a/tests/toranj/openthread-core-toranj-config-simulation.h b/tests/toranj/openthread-core-toranj-config-simulation.h index e0f030e57..88b958615 100644 --- a/tests/toranj/openthread-core-toranj-config-simulation.h +++ b/tests/toranj/openthread-core-toranj-config-simulation.h @@ -76,6 +76,8 @@ #define OPENTHREAD_CONFIG_SRP_SERVER_ADVERTISING_PROXY_ENABLE OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +#define OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + #define OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE 1 #define OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE 1 diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index a8364f7a7..92aad7622 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -176,6 +176,7 @@ ot_unit_test(data) ot_unit_test(dataset) ot_unit_test(dns) ot_unit_test(dns_client) +ot_unit_test(dnssd_discovery_proxy) ot_unit_test(dso) ot_unit_test(ecdsa) ot_unit_test(flash) diff --git a/tests/unit/test_dnssd_discovery_proxy.cpp b/tests/unit/test_dnssd_discovery_proxy.cpp new file mode 100644 index 000000000..c2dfc0d04 --- /dev/null +++ b/tests/unit/test_dnssd_discovery_proxy.cpp @@ -0,0 +1,2723 @@ +/* + * Copyright (c) 2023, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "test_platform.h" +#include "test_util.hpp" + +#include +#include +#include +#include +#include + +#include "common/arg_macros.hpp" +#include "common/array.hpp" +#include "common/clearable.hpp" +#include "common/string.hpp" +#include "common/time.hpp" +#include "instance/instance.hpp" + +#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE && OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE && \ + OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE && OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE && \ + OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE && OPENTHREAD_CONFIG_SRP_SERVER_ENABLE && \ + OPENTHREAD_CONFIG_PLATFORM_DNSSD_ALLOW_RUN_TIME_SELECTION && OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE && \ + !OPENTHREAD_CONFIG_TIME_SYNC_ENABLE && !OPENTHREAD_PLATFORM_POSIX +#define ENABLE_DISCOVERY_PROXY_TEST 1 +#else +#define ENABLE_DISCOVERY_PROXY_TEST 0 +#endif + +#if ENABLE_DISCOVERY_PROXY_TEST + +using namespace ot; + +// Logs a message and adds current time (sNow) as "::." +#define Log(...) \ + printf("%02u:%02u:%02u.%03u " OT_FIRST_ARG(__VA_ARGS__) "\n", (sNow / 36000000), (sNow / 60000) % 60, \ + (sNow / 1000) % 60, sNow % 1000 OT_REST_ARGS(__VA_ARGS__)) + +static constexpr uint16_t kMaxRaSize = 800; + +static ot::Instance *sInstance; + +static uint32_t sNow = 0; +static uint32_t sAlarmTime; +static bool sAlarmOn = false; + +static otRadioFrame sRadioTxFrame; +static uint8_t sRadioTxFramePsdu[OT_RADIO_FRAME_MAX_SIZE]; +static bool sRadioTxOngoing = false; + +//---------------------------------------------------------------------------------------------------------------------- +// Function prototypes + +void ProcessRadioTxAndTasklets(void); +void AdvanceTime(uint32_t aDuration); + +//---------------------------------------------------------------------------------------------------------------------- +// `otPlatRadio` + +extern "C" { + +otRadioCaps otPlatRadioGetCaps(otInstance *) { return OT_RADIO_CAPS_ACK_TIMEOUT | OT_RADIO_CAPS_CSMA_BACKOFF; } + +otError otPlatRadioTransmit(otInstance *, otRadioFrame *) +{ + sRadioTxOngoing = true; + + return OT_ERROR_NONE; +} + +otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *) { return &sRadioTxFrame; } + +//---------------------------------------------------------------------------------------------------------------------- +// `otPlatAlaram` + +void otPlatAlarmMilliStop(otInstance *) { sAlarmOn = false; } + +void otPlatAlarmMilliStartAt(otInstance *, uint32_t aT0, uint32_t aDt) +{ + sAlarmOn = true; + sAlarmTime = aT0 + aDt; +} + +uint32_t otPlatAlarmMilliGetNow(void) { return sNow; } + +//---------------------------------------------------------------------------------------------------------------------- + +Array sHeapAllocatedPtrs; + +#if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE +void *otPlatCAlloc(size_t aNum, size_t aSize) +{ + void *ptr = calloc(aNum, aSize); + + SuccessOrQuit(sHeapAllocatedPtrs.PushBack(ptr)); + + return ptr; +} + +void otPlatFree(void *aPtr) +{ + if (aPtr != nullptr) + { + void **entry = sHeapAllocatedPtrs.Find(aPtr); + + VerifyOrQuit(entry != nullptr, "A heap allocated item is freed twice"); + sHeapAllocatedPtrs.Remove(*entry); + } + + free(aPtr); +} +#endif + +#if OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_PLATFORM_DEFINED +void otPlatLog(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aFormat, ...) +{ + OT_UNUSED_VARIABLE(aLogLevel); + OT_UNUSED_VARIABLE(aLogRegion); + + va_list args; + + printf(" "); + va_start(args, aFormat); + vprintf(aFormat, args); + va_end(args); + printf("\n"); +} +#endif + +} // extern "C" + +//--------------------------------------------------------------------------------------------------------------------- + +void ProcessRadioTxAndTasklets(void) +{ + do + { + if (sRadioTxOngoing) + { + sRadioTxOngoing = false; + otPlatRadioTxStarted(sInstance, &sRadioTxFrame); + otPlatRadioTxDone(sInstance, &sRadioTxFrame, nullptr, OT_ERROR_NONE); + } + + otTaskletsProcess(sInstance); + } while (otTaskletsArePending(sInstance)); +} + +void AdvanceTime(uint32_t aDuration) +{ + uint32_t time = sNow + aDuration; + + Log("AdvanceTime for %u.%03u", aDuration / 1000, aDuration % 1000); + + while (TimeMilli(sAlarmTime) <= TimeMilli(time)) + { + ProcessRadioTxAndTasklets(); + sNow = sAlarmTime; + otPlatAlarmMilliFired(sInstance); + } + + ProcessRadioTxAndTasklets(); + sNow = time; +} + +void InitTest(void) +{ + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Initialize OT instance. + + sNow = 0; + sAlarmOn = false; + sInstance = static_cast(testInitInstance()); + + memset(&sRadioTxFrame, 0, sizeof(sRadioTxFrame)); + sRadioTxFrame.mPsdu = sRadioTxFramePsdu; + sRadioTxOngoing = false; + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Initialize Border Router and start Thread operation. + + otOperationalDataset dataset; + otOperationalDatasetTlvs datasetTlvs; + + SuccessOrQuit(otDatasetCreateNewNetwork(sInstance, &dataset)); + otDatasetConvertToTlvs(&dataset, &datasetTlvs); + SuccessOrQuit(otDatasetSetActiveTlvs(sInstance, &datasetTlvs)); + + SuccessOrQuit(otIp6SetEnabled(sInstance, true)); + SuccessOrQuit(otThreadSetEnabled(sInstance, true)); + + // Configure the `Dnssd` module to use `otPlatDnssd` APIs. + + sInstance->Get().SetUseNativeMdns(false); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Ensure device starts as leader. + + AdvanceTime(10000); + + VerifyOrQuit(otThreadGetDeviceRole(sInstance) == OT_DEVICE_ROLE_LEADER); +} + +void FinalizeTest(void) +{ + SuccessOrQuit(otIp6SetEnabled(sInstance, false)); + SuccessOrQuit(otThreadSetEnabled(sInstance, false)); + // Make sure there is no message/buffer leak + VerifyOrQuit(sInstance->Get().GetFreeBufferCount() == + sInstance->Get().GetTotalBufferCount()); + SuccessOrQuit(otInstanceErasePersistentInfo(sInstance)); + testFreeInstance(sInstance); +} + +//--------------------------------------------------------------------------------------------------------------------- +// DNS Client callback + +void LogServiceInfo(const Dns::Client::ServiceInfo &aInfo) +{ + Log(" TTL: %u", aInfo.mTtl); + Log(" Port: %u", aInfo.mPort); + Log(" Weight: %u", aInfo.mWeight); + Log(" HostName: %s", aInfo.mHostNameBuffer); + Log(" HostAddr: %s", AsCoreType(&aInfo.mHostAddress).ToString().AsCString()); + Log(" TxtDataLength: %u", aInfo.mTxtDataSize); + Log(" TxtDataTTL: %u", aInfo.mTxtDataTtl); +} + +const char *ServiceModeToString(Dns::Client::QueryConfig::ServiceMode aMode) +{ + static const char *const kServiceModeStrings[] = { + "unspec", // kServiceModeUnspecified (0) + "srv", // kServiceModeSrv (1) + "txt", // kServiceModeTxt (2) + "srv_txt", // kServiceModeSrvTxt (3) + "srv_txt_sep", // kServiceModeSrvTxtSeparate (4) + "srv_txt_opt", // kServiceModeSrvTxtOptimize (5) + }; + + static_assert(Dns::Client::QueryConfig::kServiceModeUnspecified == 0, "Unspecified value is incorrect"); + static_assert(Dns::Client::QueryConfig::kServiceModeSrv == 1, "Srv value is incorrect"); + static_assert(Dns::Client::QueryConfig::kServiceModeTxt == 2, "Txt value is incorrect"); + static_assert(Dns::Client::QueryConfig::kServiceModeSrvTxt == 3, "SrvTxt value is incorrect"); + static_assert(Dns::Client::QueryConfig::kServiceModeSrvTxtSeparate == 4, "SrvTxtSeparate value is incorrect"); + static_assert(Dns::Client::QueryConfig::kServiceModeSrvTxtOptimize == 5, "SrvTxtOptimize value is incorrect"); + + return kServiceModeStrings[aMode]; +} + +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +struct BrowseInfo +{ + void Reset(void) { mCallbackCount = 0; } + + uint16_t mCallbackCount; + Error mError; + char mServiceName[Dns::Name::kMaxNameSize]; + char mInstanceLabel[Dns::Name::kMaxLabelSize]; + uint16_t mNumInstances; + Dns::Client::ServiceInfo mServiceInfo; + char mHostNameBuffer[Dns::Name::kMaxNameSize]; + uint8_t mTxtBuffer[255]; +}; + +static BrowseInfo sBrowseInfo; + +void BrowseCallback(otError aError, const otDnsBrowseResponse *aResponse, void *aContext) +{ + const Dns::Client::BrowseResponse &response = AsCoreType(aResponse); + + Log("BrowseCallback"); + Log(" Error: %s", ErrorToString(aError)); + + VerifyOrQuit(aContext == sInstance); + + sBrowseInfo.mCallbackCount++; + sBrowseInfo.mError = aError; + + SuccessOrExit(aError); + + SuccessOrQuit(response.GetServiceName(sBrowseInfo.mServiceName, sizeof(sBrowseInfo.mServiceName))); + Log(" ServiceName: %s", sBrowseInfo.mServiceName); + + for (uint16_t index = 0;; index++) + { + Error error; + + error = response.GetServiceInstance(index, sBrowseInfo.mInstanceLabel, sizeof(sBrowseInfo.mInstanceLabel)); + + if (error == kErrorNotFound) + { + sBrowseInfo.mNumInstances = index; + break; + } + + SuccessOrQuit(error); + + Log(" %2u) %s", index + 1, sBrowseInfo.mInstanceLabel); + } + + if (sBrowseInfo.mNumInstances == 1) + { + sBrowseInfo.mServiceInfo.mHostNameBuffer = sBrowseInfo.mHostNameBuffer; + sBrowseInfo.mServiceInfo.mHostNameBufferSize = sizeof(sBrowseInfo.mHostNameBuffer); + sBrowseInfo.mServiceInfo.mTxtData = sBrowseInfo.mTxtBuffer; + sBrowseInfo.mServiceInfo.mTxtDataSize = sizeof(sBrowseInfo.mTxtBuffer); + + SuccessOrExit(response.GetServiceInfo(sBrowseInfo.mInstanceLabel, sBrowseInfo.mServiceInfo)); + LogServiceInfo(sBrowseInfo.mServiceInfo); + } + +exit: + return; +} + +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static constexpr uint8_t kMaxHostAddresses = 10; +static constexpr uint16_t kMaxTxtBuffer = 256; + +struct ResolveServiceInfo +{ + void Reset(void) + { + memset(this, 0, sizeof(*this)); + mInfo.mHostNameBuffer = mNameBuffer; + mInfo.mHostNameBufferSize = sizeof(mNameBuffer); + mInfo.mTxtData = mTxtBuffer; + mInfo.mTxtDataSize = sizeof(mTxtBuffer); + }; + + uint16_t mCallbackCount; + Error mError; + Dns::Client::ServiceInfo mInfo; + char mNameBuffer[Dns::Name::kMaxNameSize]; + uint8_t mTxtBuffer[kMaxTxtBuffer]; + Ip6::Address mHostAddresses[kMaxHostAddresses]; + uint8_t mNumHostAddresses; +}; + +static ResolveServiceInfo sResolveServiceInfo; + +void ServiceCallback(otError aError, const otDnsServiceResponse *aResponse, void *aContext) +{ + const Dns::Client::ServiceResponse &response = AsCoreType(aResponse); + char instLabel[Dns::Name::kMaxLabelSize]; + char serviceName[Dns::Name::kMaxNameSize]; + + Log("ServiceCallback"); + Log(" Error: %s", ErrorToString(aError)); + + VerifyOrQuit(aContext == sInstance); + + SuccessOrQuit(response.GetServiceName(instLabel, sizeof(instLabel), serviceName, sizeof(serviceName))); + Log(" InstLabel: %s", instLabel); + Log(" ServiceName: %s", serviceName); + + sResolveServiceInfo.mCallbackCount++; + sResolveServiceInfo.mError = aError; + + SuccessOrExit(aError); + + aError = response.GetServiceInfo(sResolveServiceInfo.mInfo); + + if (aError == kErrorNotFound) + { + sResolveServiceInfo.mError = aError; + ExitNow(); + } + + SuccessOrQuit(aError); + + for (uint8_t index = 0; index < kMaxHostAddresses; index++) + { + Error error; + uint32_t ttl; + + error = response.GetHostAddress(sResolveServiceInfo.mInfo.mHostNameBuffer, index, + sResolveServiceInfo.mHostAddresses[index], ttl); + + if (error == kErrorNotFound) + { + sResolveServiceInfo.mNumHostAddresses = index; + break; + } + + SuccessOrQuit(error); + } + + LogServiceInfo(sResolveServiceInfo.mInfo); + Log(" NumHostAddresses: %u", sResolveServiceInfo.mNumHostAddresses); + + for (uint8_t index = 0; index < sResolveServiceInfo.mNumHostAddresses; index++) + { + Log(" %s", sResolveServiceInfo.mHostAddresses[index].ToString().AsCString()); + } + +exit: + return; +} + +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +struct ResolveAddressInfo +{ + void Reset(void) { memset(this, 0, sizeof(*this)); }; + + uint16_t mCallbackCount; + Error mError; + char mHostName[Dns::Name::kMaxNameSize]; + Ip6::Address mHostAddresses[kMaxHostAddresses]; + uint8_t mNumHostAddresses; + uint32_t mTtl; +}; + +static ResolveAddressInfo sResolveAddressInfo; + +void AddressCallback(otError aError, const otDnsAddressResponse *aResponse, void *aContext) +{ + const Dns::Client::AddressResponse &response = AsCoreType(aResponse); + + Log("AddressCallback"); + Log(" Error: %s", ErrorToString(aError)); + + VerifyOrQuit(aContext == sInstance); + + sResolveAddressInfo.mCallbackCount++; + sResolveAddressInfo.mError = aError; + + SuccessOrExit(aError); + + SuccessOrQuit(response.GetHostName(sResolveAddressInfo.mHostName, sizeof(sResolveAddressInfo.mHostName))); + Log(" HostName: %s", sResolveAddressInfo.mHostName); + + for (uint8_t index = 0; index < kMaxHostAddresses; index++) + { + Error error; + uint32_t ttl; + + error = response.GetAddress(index, sResolveAddressInfo.mHostAddresses[index], sResolveAddressInfo.mTtl); + + if (error == kErrorNotFound) + { + sResolveAddressInfo.mNumHostAddresses = index; + break; + } + + SuccessOrQuit(error); + } + + Log(" NumHostAddresses: %u", sResolveAddressInfo.mNumHostAddresses); + + for (uint8_t index = 0; index < sResolveAddressInfo.mNumHostAddresses; index++) + { + Log(" %s", sResolveAddressInfo.mHostAddresses[index].ToString().AsCString()); + } + +exit: + return; +} + +//---------------------------------------------------------------------------------------------------------------------- +// otPlatDnssd APIs + +constexpr uint32_t kInfraIfIndex = 1; + +static otPlatDnssdState sDnssdState = OT_PLAT_DNSSD_READY; + +template void CopyString(char (&aStringBuffer)[kSize], const char *aString) +{ + if (aString == nullptr) + { + memset(aStringBuffer, 0, kSize); + } + else + { + uint16_t length = StringLength(aString, kSize); + + VerifyOrQuit(length < kSize); + memcpy(aStringBuffer, aString, length + 1); + } +} + +struct BrowserInfo : public Clearable +{ + bool ServiceTypeMatches(const char *aType) const { return !strcmp(mServiceType, aType); } + bool SubTypeMatches(const char *aSubType) const { return !strcmp(mSubTypeLabel, aSubType); } + + void UpdateFrom(const otPlatDnssdBrowser *aBrowser) + { + mCallCount++; + CopyString(mServiceType, aBrowser->mServiceType); + CopyString(mSubTypeLabel, aBrowser->mSubTypeLabel); + mCallback = aBrowser->mCallback; + } + + uint16_t mCallCount; + char mServiceType[Dns::Name::kMaxNameSize]; + char mSubTypeLabel[Dns::Name::kMaxNameSize]; + otPlatDnssdBrowseCallback mCallback; +}; + +struct SrvResolverInfo : public Clearable +{ + bool ServiceTypeMatches(const char *aType) const { return !strcmp(mServiceType, aType); } + bool ServiceInstanceMatches(const char *aInstance) const { return !strcmp(mServiceInstance, aInstance); } + + void UpdateFrom(const otPlatDnssdSrvResolver *aResolver) + { + mCallCount++; + CopyString(mServiceInstance, aResolver->mServiceInstance); + CopyString(mServiceType, aResolver->mServiceType); + mCallback = aResolver->mCallback; + } + + uint16_t mCallCount; + char mServiceInstance[Dns::Name::kMaxLabelSize]; + char mServiceType[Dns::Name::kMaxNameSize]; + otPlatDnssdSrvCallback mCallback; +}; + +struct TxtResolverInfo : public Clearable +{ + bool ServiceTypeMatches(const char *aType) const { return !strcmp(mServiceType, aType); } + bool ServiceInstanceMatches(const char *aInstance) const { return !strcmp(mServiceInstance, aInstance); } + + void UpdateFrom(const otPlatDnssdTxtResolver *aResolver) + { + mCallCount++; + CopyString(mServiceInstance, aResolver->mServiceInstance); + CopyString(mServiceType, aResolver->mServiceType); + mCallback = aResolver->mCallback; + } + + uint16_t mCallCount; + char mServiceInstance[Dns::Name::kMaxLabelSize]; + char mServiceType[Dns::Name::kMaxNameSize]; + otPlatDnssdTxtCallback mCallback; +}; + +struct Ip6AddrResolverInfo : public Clearable +{ + bool HostNameMatches(const char *aName) const { return !strcmp(mHostName, aName); } + + void UpdateFrom(const otPlatDnssdAddressResolver *aResolver) + { + mCallCount++; + CopyString(mHostName, aResolver->mHostName); + mCallback = aResolver->mCallback; + } + + uint16_t mCallCount; + char mHostName[Dns::Name::kMaxNameSize]; + otPlatDnssdAddressCallback mCallback; +}; + +struct InvokeOnStart : public Clearable +{ + // When not null, these entries are used to invoke callback + // directly from `otPlatDnssdStart{Browser/Resolver}()` APIs. + // This is used in `TestProxyInvokeCallbackFromStartApi()`. + + const otPlatDnssdBrowseResult *mBrowseResult; + const otPlatDnssdSrvResult *mSrvResult; + const otPlatDnssdTxtResult *mTxtResult; + const otPlatDnssdAddressResult *mIp6AddrResult; +}; + +static BrowserInfo sStartBrowserInfo; +static BrowserInfo sStopBrowserInfo; +static SrvResolverInfo sStartSrvResolverInfo; +static SrvResolverInfo sStopSrvResolverInfo; +static TxtResolverInfo sStartTxtResolverInfo; +static TxtResolverInfo sStopTxtResolverInfo; +static Ip6AddrResolverInfo sStartIp6AddrResolverInfo; +static Ip6AddrResolverInfo sStopIp6AddrResolverInfo; + +static InvokeOnStart sInvokeOnStart; + +void ResetPlatDnssdApiInfo(void) +{ + sStartBrowserInfo.Clear(); + sStopBrowserInfo.Clear(); + sStartSrvResolverInfo.Clear(); + sStopSrvResolverInfo.Clear(); + sStartTxtResolverInfo.Clear(); + sStopTxtResolverInfo.Clear(); + sStartIp6AddrResolverInfo.Clear(); + sStopIp6AddrResolverInfo.Clear(); + sInvokeOnStart.Clear(); +} + +const char *StringNullCheck(const char *aString) { return (aString != nullptr) ? aString : "(null)"; } + +void InvokeBrowserCallback(otPlatDnssdBrowseCallback aCallback, const otPlatDnssdBrowseResult &aResult) +{ + Log("Invoking browser callback"); + Log(" serviceType : \"%s\"", StringNullCheck(aResult.mServiceType)); + Log(" subType : \"%s\"", StringNullCheck(aResult.mSubTypeLabel)); + Log(" serviceInstance: \"%s\"", StringNullCheck(aResult.mServiceInstance)); + Log(" ttl : %u", aResult.mTtl); + Log(" if-index : %u", aResult.mInfraIfIndex); + + aCallback(sInstance, &aResult); +} + +void InvokeSrvResolverCallback(otPlatDnssdSrvCallback aCallback, const otPlatDnssdSrvResult &aResult) +{ + Log("Invoking SRV resolver callback"); + Log(" serviceInstance: %s", StringNullCheck(aResult.mServiceInstance)); + Log(" serviceType : %s", StringNullCheck(aResult.mServiceType)); + Log(" hostName : %s", StringNullCheck(aResult.mHostName)); + Log(" port : %u", aResult.mPort); + Log(" priority : %u", aResult.mPriority); + Log(" weight : %u", aResult.mWeight); + Log(" ttl : %u", aResult.mTtl); + Log(" if-index : %u", aResult.mInfraIfIndex); + + aCallback(sInstance, &aResult); +} + +void InvokeTxtResolverCallback(otPlatDnssdTxtCallback aCallback, const otPlatDnssdTxtResult &aResult) +{ + Log("Invoking TXT resolver callback"); + Log(" serviceInstance: %s", StringNullCheck(aResult.mServiceInstance)); + Log(" serviceType : %s", StringNullCheck(aResult.mServiceType)); + Log(" txt-data-len : %u", aResult.mTxtDataLength); + Log(" ttl : %u", aResult.mTtl); + Log(" if-index : %u", aResult.mInfraIfIndex); + + aCallback(sInstance, &aResult); +} + +void InvokeIp6AddrResolverCallback(const otPlatDnssdAddressCallback aCallback, const otPlatDnssdAddressResult &aResult) +{ + Log("Invoking Ip6 resolver callback"); + Log(" hostName : %s", aResult.mHostName); + Log(" if-index : %u", aResult.mInfraIfIndex); + Log(" numAddresses : %u", aResult.mAddressesLength); + for (uint16_t index = 0; index < aResult.mAddressesLength; index++) + { + Log(" address[%u] : %s", index, AsCoreType(&aResult.mAddresses[index].mAddress).ToString().AsCString()); + Log(" ttl[%u] : %u", index, aResult.mAddresses[index].mTtl); + } + + aCallback(sInstance, &aResult); +} + +otPlatDnssdState otPlatDnssdGetState(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + + return sDnssdState; +} + +void otPlatDnssdStartBrowser(otInstance *aInstance, const otPlatDnssdBrowser *aBrowser) +{ + VerifyOrQuit(aBrowser != nullptr); + + Log("otPlatDnssdStartBrowser(\"%s\", sub-type:\"%s\")", aBrowser->mServiceType, aBrowser->mSubTypeLabel); + + VerifyOrQuit(aInstance == sInstance); + VerifyOrQuit(aBrowser->mInfraIfIndex == kInfraIfIndex); + + sStartBrowserInfo.UpdateFrom(aBrowser); + + if (sInvokeOnStart.mBrowseResult != nullptr) + { + InvokeBrowserCallback(aBrowser->mCallback, *sInvokeOnStart.mBrowseResult); + } +} + +void otPlatDnssdStopBrowser(otInstance *aInstance, const otPlatDnssdBrowser *aBrowser) +{ + VerifyOrQuit(aBrowser != nullptr); + + Log("otPlatDnssdStopBrowser(\"%s\", sub-type:\"%s\")", aBrowser->mServiceType, aBrowser->mSubTypeLabel); + + VerifyOrQuit(aInstance == sInstance); + VerifyOrQuit(aBrowser->mInfraIfIndex == kInfraIfIndex); + + sStopBrowserInfo.UpdateFrom(aBrowser); +} + +void otPlatDnssdStartSrvResolver(otInstance *aInstance, const otPlatDnssdSrvResolver *aResolver) +{ + VerifyOrQuit(aResolver != nullptr); + + Log("otPlatDnssdStartSrvResolver(\"%s\", \"%s\")", aResolver->mServiceInstance, aResolver->mServiceType); + + VerifyOrQuit(aInstance == sInstance); + VerifyOrQuit(aResolver->mInfraIfIndex == kInfraIfIndex); + + sStartSrvResolverInfo.UpdateFrom(aResolver); + + if (sInvokeOnStart.mSrvResult != nullptr) + { + InvokeSrvResolverCallback(aResolver->mCallback, *sInvokeOnStart.mSrvResult); + } +} + +void otPlatDnssdStopSrvResolver(otInstance *aInstance, const otPlatDnssdSrvResolver *aResolver) +{ + VerifyOrQuit(aResolver != nullptr); + + Log("otPlatDnssdStopSrvResolver(\"%s\", \"%s\")", aResolver->mServiceInstance, aResolver->mServiceType); + + VerifyOrQuit(aInstance == sInstance); + VerifyOrQuit(aResolver->mInfraIfIndex == kInfraIfIndex); + + sStopSrvResolverInfo.UpdateFrom(aResolver); +} + +void otPlatDnssdStartTxtResolver(otInstance *aInstance, const otPlatDnssdTxtResolver *aResolver) +{ + VerifyOrQuit(aResolver != nullptr); + + Log("otPlatDnssdStartTxtResolver(\"%s\", \"%s\")", aResolver->mServiceInstance, aResolver->mServiceType); + + VerifyOrQuit(aInstance == sInstance); + VerifyOrQuit(aResolver->mInfraIfIndex == kInfraIfIndex); + + sStartTxtResolverInfo.UpdateFrom(aResolver); + + if (sInvokeOnStart.mTxtResult != nullptr) + { + InvokeTxtResolverCallback(aResolver->mCallback, *sInvokeOnStart.mTxtResult); + } +} + +void otPlatDnssdStopTxtResolver(otInstance *aInstance, const otPlatDnssdTxtResolver *aResolver) +{ + VerifyOrQuit(aResolver != nullptr); + + Log("otPlatDnssdStopTxtResolver(\"%s\", \"%s\")", aResolver->mServiceInstance, aResolver->mServiceType); + + VerifyOrQuit(aInstance == sInstance); + VerifyOrQuit(aResolver->mInfraIfIndex == kInfraIfIndex); + + sStopTxtResolverInfo.UpdateFrom(aResolver); +} + +void otPlatDnssdStartIp6AddressResolver(otInstance *aInstance, const otPlatDnssdAddressResolver *aResolver) +{ + VerifyOrQuit(aResolver != nullptr); + + Log("otPlatDnssdStartIp6AddressResolver(\"%s\")", aResolver->mHostName); + + VerifyOrQuit(aInstance == sInstance); + VerifyOrQuit(aResolver->mInfraIfIndex == kInfraIfIndex); + + sStartIp6AddrResolverInfo.UpdateFrom(aResolver); + + if (sInvokeOnStart.mIp6AddrResult != nullptr) + { + InvokeIp6AddrResolverCallback(aResolver->mCallback, *sInvokeOnStart.mIp6AddrResult); + } +} + +void otPlatDnssdStopIp6AddressResolver(otInstance *aInstance, const otPlatDnssdAddressResolver *aResolver) +{ + VerifyOrQuit(aResolver != nullptr); + + Log("otPlatDnssdStopIp6AddressResolver(\"%s\")", aResolver->mHostName); + + VerifyOrQuit(aInstance == sInstance); + VerifyOrQuit(aResolver->mInfraIfIndex == kInfraIfIndex); + + sStopIp6AddrResolverInfo.UpdateFrom(aResolver); + + if (sInvokeOnStart.mIp6AddrResult != nullptr) + { + InvokeIp6AddrResolverCallback(aResolver->mCallback, *sInvokeOnStart.mIp6AddrResult); + } +} + +//---------------------------------------------------------------------------------------------------------------------- + +void TestProxyBasic(void) +{ + static constexpr uint32_t kTtl = 300; + + const uint8_t kTxtData[] = {3, 'A', '=', '1', 0}; + + Srp::Server *srpServer; + Srp::Client *srpClient; + Dns::Client *dnsClient; + Dns::ServiceDiscovery::Server *dnsServer; + Dnssd::BrowseResult browseResult; + Dnssd::SrvResult srvResult; + Dnssd::TxtResult txtResult; + Dnssd::AddressResult ip6AddrrResult; + Dnssd::AddressAndTtl addressAndTtl; + + Log("--------------------------------------------------------------------------------------------"); + Log("TestProxyBasic"); + + InitTest(); + + srpServer = &sInstance->Get(); + srpClient = &sInstance->Get(); + dnsClient = &sInstance->Get(); + dnsServer = &sInstance->Get(); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start SRP server. + + SuccessOrQuit(otBorderRoutingInit(sInstance, /* aInfraIfIndex */ kInfraIfIndex, /* aInfraIfIsRunning */ true)); + + SuccessOrQuit(srpServer->SetAddressMode(Srp::Server::kAddressModeUnicast)); + VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateDisabled); + + srpServer->SetEnabled(true); + VerifyOrQuit(srpServer->GetState() != Srp::Server::kStateDisabled); + + AdvanceTime(10000); + VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateRunning); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start SRP client. + + srpClient->EnableAutoStartMode(nullptr, nullptr); + VerifyOrQuit(srpClient->IsAutoStartModeEnabled()); + + AdvanceTime(2000); + VerifyOrQuit(srpClient->IsRunning()); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + + sBrowseInfo.Reset(); + ResetPlatDnssdApiInfo(); + + Log("Browse()"); + SuccessOrQuit(dnsClient->Browse("_avenger._udp.default.service.arpa.", BrowseCallback, sInstance)); + AdvanceTime(10); + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStartBrowserInfo.ServiceTypeMatches("_avenger._udp")); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke Browser callback"); + + browseResult.mServiceType = "_avenger._udp"; + browseResult.mSubTypeLabel = nullptr; + browseResult.mServiceInstance = "hulk"; + browseResult.mTtl = kTtl; + browseResult.mInfraIfIndex = kInfraIfIndex; + + InvokeBrowserCallback(sStartBrowserInfo.mCallback, browseResult); + + AdvanceTime(10); + + // Check that browser is stopped and a service resolver is started + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStopBrowserInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStopBrowserInfo.mCallback == sStartBrowserInfo.mCallback); + + VerifyOrQuit(sStartSrvResolverInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStartSrvResolverInfo.ServiceInstanceMatches("hulk")); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke SRV resolver callback"); + + srvResult.mServiceInstance = "hulk"; + srvResult.mServiceType = "_avenger._udp"; + srvResult.mHostName = "compound"; + srvResult.mPort = 7777; + srvResult.mTtl = kTtl; + srvResult.mInfraIfIndex = kInfraIfIndex; + + InvokeSrvResolverCallback(sStartSrvResolverInfo.mCallback, srvResult); + + AdvanceTime(10); + + // Check that SRV resolver is stopped and a TXT resolver is started + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStopSrvResolverInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStopSrvResolverInfo.ServiceInstanceMatches("hulk")); + VerifyOrQuit(sStopSrvResolverInfo.mCallback == sStartSrvResolverInfo.mCallback); + + VerifyOrQuit(sStartTxtResolverInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStartTxtResolverInfo.ServiceInstanceMatches("hulk")); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke TXT resolver callback"); + + txtResult.mServiceInstance = "hulk"; + txtResult.mServiceType = "_avenger._udp"; + txtResult.mTxtData = kTxtData; + txtResult.mTxtDataLength = sizeof(kTxtData); + txtResult.mTtl = kTtl; + txtResult.mInfraIfIndex = kInfraIfIndex; + + InvokeTxtResolverCallback(sStartTxtResolverInfo.mCallback, txtResult); + + AdvanceTime(10); + + // Check that TXT resolver is stopped and an address resolver is started + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStopTxtResolverInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStopTxtResolverInfo.ServiceInstanceMatches("hulk")); + VerifyOrQuit(sStopTxtResolverInfo.mCallback == sStartTxtResolverInfo.mCallback); + + VerifyOrQuit(sStartIp6AddrResolverInfo.HostNameMatches("compound")); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke Address Resolver callback"); + + SuccessOrQuit(AsCoreType(&addressAndTtl.mAddress).FromString("fd00::1234")); + addressAndTtl.mTtl = kTtl; + + ip6AddrrResult.mHostName = "compound"; + ip6AddrrResult.mInfraIfIndex = kInfraIfIndex; + ip6AddrrResult.mAddresses = &addressAndTtl; + ip6AddrrResult.mAddressesLength = 1; + + InvokeIp6AddrResolverCallback(sStartIp6AddrResolverInfo.mCallback, ip6AddrrResult); + + AdvanceTime(10); + + // Check that address resolver is stopped + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 1); + + VerifyOrQuit(sStopIp6AddrResolverInfo.HostNameMatches("compound")); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallback == sStartIp6AddrResolverInfo.mCallback); + + // Check that response is sent to client and validate it + + VerifyOrQuit(sBrowseInfo.mCallbackCount == 1); + SuccessOrQuit(sBrowseInfo.mError); + VerifyOrQuit(sBrowseInfo.mNumInstances == 1); + + VerifyOrQuit(!strcmp(sBrowseInfo.mServiceName, "_avenger._udp.default.service.arpa.")); + VerifyOrQuit(!strcmp(sBrowseInfo.mInstanceLabel, "hulk")); + VerifyOrQuit(!strcmp(sBrowseInfo.mServiceInfo.mHostNameBuffer, "compound.default.service.arpa.")); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mPort == 7777); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mTtl == kTtl); + VerifyOrQuit(AsCoreType(&sBrowseInfo.mServiceInfo.mHostAddress) == AsCoreType(&addressAndTtl.mAddress)); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mHostAddressTtl == kTtl); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mTxtDataSize == sizeof(kTxtData)); + VerifyOrQuit(!memcmp(sBrowseInfo.mServiceInfo.mTxtData, kTxtData, sizeof(kTxtData))); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mTxtDataTtl == kTtl); + VerifyOrQuit(!sBrowseInfo.mServiceInfo.mTxtDataTruncated); + + Log("--------------------------------------------------------------------------------------------"); + + ResetPlatDnssdApiInfo(); + sResolveServiceInfo.Reset(); + + Log("ResolveService() with dot `.` character in service instance label"); + SuccessOrQuit( + dnsClient->ResolveService("iron.man", "_avenger._udp.default.service.arpa.", ServiceCallback, sInstance)); + + AdvanceTime(10); + + // Check that a service resolver is started + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStartSrvResolverInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStartSrvResolverInfo.ServiceInstanceMatches("iron.man")); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke SRV Resolver callback for wrong name"); + + srvResult.mServiceInstance = "hulk"; + srvResult.mServiceType = "_avenger._udp"; + srvResult.mHostName = "compound"; + srvResult.mPort = 7777; + srvResult.mTtl = kTtl; + srvResult.mInfraIfIndex = kInfraIfIndex; + + InvokeSrvResolverCallback(sStartSrvResolverInfo.mCallback, srvResult); + + AdvanceTime(10); + + // Check that no changes to browsers/resolvers + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 0); + + Log("Invoke SRV Resolver callback for correct name"); + + srvResult.mServiceInstance = "iron.man"; + srvResult.mServiceType = "_avenger._udp"; + srvResult.mHostName = "starktower"; + srvResult.mPort = 1024; + srvResult.mTtl = kTtl; + srvResult.mInfraIfIndex = kInfraIfIndex; + + InvokeSrvResolverCallback(sStartSrvResolverInfo.mCallback, srvResult); + + AdvanceTime(10); + + // Check that SRV resolver is stopped and TXT resolver is started + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 0); + + VerifyOrQuit(sStopSrvResolverInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStopSrvResolverInfo.ServiceInstanceMatches("iron.man")); + VerifyOrQuit(sStopSrvResolverInfo.mCallback == sStartSrvResolverInfo.mCallback); + + VerifyOrQuit(sStartTxtResolverInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStartTxtResolverInfo.ServiceInstanceMatches("iron.man")); + + Log("Invoke TXT Resolver callback"); + + txtResult.mServiceInstance = "iron.man"; + txtResult.mServiceType = "_avenger._udp"; + txtResult.mTxtData = kTxtData; + txtResult.mTxtDataLength = sizeof(kTxtData); + txtResult.mTtl = kTtl; + txtResult.mInfraIfIndex = kInfraIfIndex; + + InvokeTxtResolverCallback(sStartTxtResolverInfo.mCallback, txtResult); + AdvanceTime(10); + + // Check that TXT resolver is stopped and addr resolver is started + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 0); + + VerifyOrQuit(sStopTxtResolverInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStopTxtResolverInfo.ServiceInstanceMatches("iron.man")); + VerifyOrQuit(sStopTxtResolverInfo.mCallback == sStartTxtResolverInfo.mCallback); + + VerifyOrQuit(sStartIp6AddrResolverInfo.HostNameMatches("starktower")); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke Address Resolver callback"); + + ip6AddrrResult.mHostName = "starktower"; + + InvokeIp6AddrResolverCallback(sStartIp6AddrResolverInfo.mCallback, ip6AddrrResult); + AdvanceTime(10); + + // Check that address resolver is stopped + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 1); + + VerifyOrQuit(sStopIp6AddrResolverInfo.HostNameMatches("starktower")); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallback == sStartIp6AddrResolverInfo.mCallback); + + // Check that response is sent to client and validate it + + VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); + VerifyOrQuit(sResolveServiceInfo.mError == kErrorNone); + + VerifyOrQuit(!strcmp(sResolveServiceInfo.mInfo.mHostNameBuffer, "starktower.default.service.arpa.")); + VerifyOrQuit(sResolveServiceInfo.mInfo.mPort == 1024); + VerifyOrQuit(sResolveServiceInfo.mInfo.mTtl == kTtl); + VerifyOrQuit(AsCoreType(&sResolveServiceInfo.mInfo.mHostAddress) == AsCoreType(&addressAndTtl.mAddress)); + VerifyOrQuit(sResolveServiceInfo.mInfo.mHostAddressTtl == kTtl); + VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataSize == sizeof(kTxtData)); + VerifyOrQuit(!memcmp(sResolveServiceInfo.mInfo.mTxtData, kTxtData, sizeof(kTxtData))); + VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataTtl == kTtl); + VerifyOrQuit(!sResolveServiceInfo.mInfo.mTxtDataTruncated); + + Log("--------------------------------------------------------------------------------------------"); + + ResetPlatDnssdApiInfo(); + sResolveAddressInfo.Reset(); + + Log("ResolveAddress()"); + SuccessOrQuit(dnsClient->ResolveAddress("earth.default.service.arpa.", AddressCallback, sInstance)); + AdvanceTime(10); + + // Check that an address resolver is started + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStartIp6AddrResolverInfo.HostNameMatches("earth")); + + VerifyOrQuit(sResolveAddressInfo.mCallbackCount == 0); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke Address Resolver callback"); + + SuccessOrQuit(AsCoreType(&addressAndTtl.mAddress).FromString("fd00::7777")); + addressAndTtl.mTtl = kTtl; + ip6AddrrResult.mHostName = "earth"; + ip6AddrrResult.mInfraIfIndex = kInfraIfIndex; + ip6AddrrResult.mAddresses = &addressAndTtl; + ip6AddrrResult.mAddressesLength = 1; + + InvokeIp6AddrResolverCallback(sStartIp6AddrResolverInfo.mCallback, ip6AddrrResult); + + AdvanceTime(10); + + // Check that the address resolver is stopped + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 1); + + VerifyOrQuit(sStopIp6AddrResolverInfo.HostNameMatches("earth")); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallback == sStartIp6AddrResolverInfo.mCallback); + + // Check that response is sent to client and validate it + + VerifyOrQuit(sResolveAddressInfo.mCallbackCount == 1); + SuccessOrQuit(sResolveAddressInfo.mError); + + VerifyOrQuit(!strcmp(sResolveAddressInfo.mHostName, "earth.default.service.arpa.")); + VerifyOrQuit(sResolveAddressInfo.mNumHostAddresses == 1); + VerifyOrQuit(sResolveAddressInfo.mHostAddresses[0] == AsCoreType(&addressAndTtl.mAddress)); + VerifyOrQuit(sResolveAddressInfo.mTtl == kTtl); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Stop DNS-SD server"); + + dnsServer->Stop(); + + AdvanceTime(10); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Finalize OT instance and validate all heap allocations are freed. + + Log("Finalizing OT instance"); + FinalizeTest(); + + Log("End of TestProxyBasic"); +} + +//---------------------------------------------------------------------------------------------------------------------- + +void TestProxySubtypeBrowse(void) +{ + static constexpr uint32_t kTtl = 300; + + const uint8_t kTxtData[] = {3, 'G', '=', '0', 0}; + + Srp::Server *srpServer; + Srp::Client *srpClient; + Dns::Client *dnsClient; + Dns::ServiceDiscovery::Server *dnsServer; + Dnssd::BrowseResult browseResult; + Dnssd::SrvResult srvResult; + Dnssd::TxtResult txtResult; + Dnssd::AddressResult ip6AddrrResult; + Dnssd::AddressAndTtl addressAndTtl; + + Log("--------------------------------------------------------------------------------------------"); + Log("TestProxySubtypeBrowse"); + + InitTest(); + + srpServer = &sInstance->Get(); + srpClient = &sInstance->Get(); + dnsClient = &sInstance->Get(); + dnsServer = &sInstance->Get(); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start SRP server. + + SuccessOrQuit(otBorderRoutingInit(sInstance, /* aInfraIfIndex */ kInfraIfIndex, /* aInfraIfIsRunning */ true)); + + SuccessOrQuit(srpServer->SetAddressMode(Srp::Server::kAddressModeUnicast)); + VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateDisabled); + + srpServer->SetEnabled(true); + VerifyOrQuit(srpServer->GetState() != Srp::Server::kStateDisabled); + + AdvanceTime(10000); + VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateRunning); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start SRP client. + + srpClient->EnableAutoStartMode(nullptr, nullptr); + VerifyOrQuit(srpClient->IsAutoStartModeEnabled()); + + AdvanceTime(2000); + VerifyOrQuit(srpClient->IsRunning()); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + + sBrowseInfo.Reset(); + ResetPlatDnssdApiInfo(); + + Log("Browse() for sub-type service"); + SuccessOrQuit(dnsClient->Browse("_god._sub._avenger._udp.default.service.arpa.", BrowseCallback, sInstance)); + AdvanceTime(10); + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStartBrowserInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStartBrowserInfo.SubTypeMatches("_god")); + + VerifyOrQuit(sBrowseInfo.mCallbackCount == 0); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke Browser callback"); + + browseResult.mServiceType = "_avenger._udp"; + browseResult.mSubTypeLabel = "_god"; + browseResult.mServiceInstance = "thor"; + browseResult.mTtl = kTtl; + browseResult.mInfraIfIndex = kInfraIfIndex; + + InvokeBrowserCallback(sStartBrowserInfo.mCallback, browseResult); + + AdvanceTime(10); + + // Check that browser is stopped and a service resolver is started + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStopBrowserInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStopBrowserInfo.SubTypeMatches("_god")); + VerifyOrQuit(sStopBrowserInfo.mCallback == sStartBrowserInfo.mCallback); + + // Check that the SRV resolver is correctly using the base service type + // and not the sub-type name + + VerifyOrQuit(sStartSrvResolverInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStartSrvResolverInfo.ServiceInstanceMatches("thor")); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke Service Resolver callback"); + + srvResult.mServiceInstance = "thor"; + srvResult.mServiceType = "_avenger._udp"; + srvResult.mHostName = "asgard"; + srvResult.mPort = 1234; + srvResult.mTtl = kTtl; + srvResult.mInfraIfIndex = kInfraIfIndex; + + InvokeSrvResolverCallback(sStartSrvResolverInfo.mCallback, srvResult); + + AdvanceTime(10); + + // Check that SRV resolver is stopped and a TXT resolver is started + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStopSrvResolverInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStopSrvResolverInfo.ServiceInstanceMatches("thor")); + VerifyOrQuit(sStopSrvResolverInfo.mCallback == sStartSrvResolverInfo.mCallback); + + VerifyOrQuit(sStartTxtResolverInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStartTxtResolverInfo.ServiceInstanceMatches("thor")); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke Service Resolver callback"); + + txtResult.mServiceInstance = "thor"; + txtResult.mServiceType = "_avenger._udp"; + txtResult.mTxtData = kTxtData; + txtResult.mTxtDataLength = sizeof(kTxtData); + txtResult.mTtl = kTtl; + txtResult.mInfraIfIndex = kInfraIfIndex; + + InvokeTxtResolverCallback(sStartTxtResolverInfo.mCallback, txtResult); + AdvanceTime(10); + + // Check that TXT resolver is stopped and an address resolver is started + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStopTxtResolverInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStopTxtResolverInfo.ServiceInstanceMatches("thor")); + VerifyOrQuit(sStopTxtResolverInfo.mCallback == sStartTxtResolverInfo.mCallback); + + VerifyOrQuit(sStartIp6AddrResolverInfo.HostNameMatches("asgard")); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke Address Resolver callback"); + + SuccessOrQuit(AsCoreType(&addressAndTtl.mAddress).FromString("fd00::1234")); + addressAndTtl.mTtl = kTtl; + + ip6AddrrResult.mHostName = "asgard"; + ip6AddrrResult.mInfraIfIndex = kInfraIfIndex; + ip6AddrrResult.mAddresses = &addressAndTtl; + ip6AddrrResult.mAddressesLength = 1; + + InvokeIp6AddrResolverCallback(sStartIp6AddrResolverInfo.mCallback, ip6AddrrResult); + + AdvanceTime(10); + + // Check that address resolver is stopped + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 1); + + VerifyOrQuit(sStopIp6AddrResolverInfo.HostNameMatches("asgard")); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallback == sStartIp6AddrResolverInfo.mCallback); + + // Check that response is sent to client and validate it + + VerifyOrQuit(sBrowseInfo.mCallbackCount == 1); + SuccessOrQuit(sBrowseInfo.mError); + VerifyOrQuit(sBrowseInfo.mNumInstances == 1); + + VerifyOrQuit(!strcmp(sBrowseInfo.mServiceName, "_god._sub._avenger._udp.default.service.arpa.")); + VerifyOrQuit(!strcmp(sBrowseInfo.mInstanceLabel, "thor")); + VerifyOrQuit(!strcmp(sBrowseInfo.mServiceInfo.mHostNameBuffer, "asgard.default.service.arpa.")); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mPort == 1234); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mTtl == kTtl); + VerifyOrQuit(AsCoreType(&sBrowseInfo.mServiceInfo.mHostAddress) == AsCoreType(&addressAndTtl.mAddress)); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mHostAddressTtl == kTtl); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mTxtDataSize == sizeof(kTxtData)); + VerifyOrQuit(!memcmp(sBrowseInfo.mServiceInfo.mTxtData, kTxtData, sizeof(kTxtData))); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mTxtDataTtl == kTtl); + VerifyOrQuit(!sBrowseInfo.mServiceInfo.mTxtDataTruncated); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Stop DNS-SD server"); + + dnsServer->Stop(); + + AdvanceTime(10); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Finalize OT instance and validate all heap allocations are freed. + + Log("Finalizing OT instance"); + FinalizeTest(); + + Log("End of TestProxySubtypeBrowse"); +} + +//---------------------------------------------------------------------------------------------------------------------- + +void TestProxyTimeout(void) +{ + static constexpr uint32_t kTtl = 300; + + Srp::Server *srpServer; + Srp::Client *srpClient; + Dns::Client *dnsClient; + Dns::ServiceDiscovery::Server *dnsServer; + Dns::Client::QueryConfig config; + Dnssd::BrowseResult browseResult; + + Log("--------------------------------------------------------------------------------------------"); + Log("TestProxyTimeout"); + + InitTest(); + + srpServer = &sInstance->Get(); + srpClient = &sInstance->Get(); + dnsClient = &sInstance->Get(); + dnsServer = &sInstance->Get(); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start SRP server. + + SuccessOrQuit(otBorderRoutingInit(sInstance, /* aInfraIfIndex */ kInfraIfIndex, /* aInfraIfIsRunning */ true)); + + SuccessOrQuit(srpServer->SetAddressMode(Srp::Server::kAddressModeUnicast)); + VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateDisabled); + + srpServer->SetEnabled(true); + VerifyOrQuit(srpServer->GetState() != Srp::Server::kStateDisabled); + + AdvanceTime(10000); + VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateRunning); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start SRP client. + + srpClient->EnableAutoStartMode(nullptr, nullptr); + VerifyOrQuit(srpClient->IsAutoStartModeEnabled()); + + AdvanceTime(2000); + VerifyOrQuit(srpClient->IsRunning()); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Increase default response retry timeout on DNS client"); + + config.Clear(); + config.mResponseTimeout = 120 * 1000; // 2 minutes (in msec) + dnsClient->SetDefaultConfig(config); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + + sBrowseInfo.Reset(); + ResetPlatDnssdApiInfo(); + + Log("Browse()"); + SuccessOrQuit(dnsClient->Browse("_game._ps5.default.service.arpa.", BrowseCallback, sInstance)); + AdvanceTime(10); + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStartBrowserInfo.ServiceTypeMatches("_game._ps5")); + + VerifyOrQuit(sBrowseInfo.mCallbackCount == 0); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Wait for timeout and check empty response on client"); + + AdvanceTime(10 * 1000); + + VerifyOrQuit(sBrowseInfo.mCallbackCount == 1); + VerifyOrQuit(sBrowseInfo.mNumInstances == 0); + + // Check that the browser is stopped + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + + VerifyOrQuit(sStopBrowserInfo.ServiceTypeMatches("_game._ps5")); + VerifyOrQuit(sStopBrowserInfo.mCallback == sStartBrowserInfo.mCallback); + + Log("--------------------------------------------------------------------------------------------"); + Log("Timeout during service resolution"); + + sBrowseInfo.Reset(); + ResetPlatDnssdApiInfo(); + + SuccessOrQuit(dnsClient->Browse("_avenger._udp.default.service.arpa.", BrowseCallback, sInstance)); + AdvanceTime(10); + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStartBrowserInfo.ServiceTypeMatches("_avenger._udp")); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke Browser callback"); + + browseResult.mServiceType = "_avenger._udp"; + browseResult.mSubTypeLabel = nullptr; + browseResult.mServiceInstance = "spiderman"; + browseResult.mTtl = kTtl; + browseResult.mInfraIfIndex = kInfraIfIndex; + + InvokeBrowserCallback(sStartBrowserInfo.mCallback, browseResult); + + AdvanceTime(10); + + // Check that browser is stopped and a service resolver is started + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStopBrowserInfo.ServiceTypeMatches("_avenger._udp")); + + VerifyOrQuit(sStartSrvResolverInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStartSrvResolverInfo.ServiceInstanceMatches("spiderman")); + + VerifyOrQuit(sBrowseInfo.mCallbackCount == 0); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Wait for timeout"); + + AdvanceTime(10 * 1000); + + VerifyOrQuit(sBrowseInfo.mCallbackCount == 1); + VerifyOrQuit(sBrowseInfo.mNumInstances == 1); + + // Check that the browser is stopped + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStopBrowserInfo.ServiceTypeMatches("_avenger._udp")); + + // Validate the response received by client + + VerifyOrQuit(!strcmp(sBrowseInfo.mServiceName, "_avenger._udp.default.service.arpa.")); + VerifyOrQuit(!strcmp(sBrowseInfo.mInstanceLabel, "spiderman")); + + Log("--------------------------------------------------------------------------------------------"); + Log("Timeout during multiple requests"); + + sBrowseInfo.Reset(); + sResolveServiceInfo.Reset(); + sResolveAddressInfo.Reset(); + ResetPlatDnssdApiInfo(); + + Log("Browse()"); + SuccessOrQuit(dnsClient->Browse("_avenger._udp.default.service.arpa.", BrowseCallback, sInstance)); + AdvanceTime(10); + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStartBrowserInfo.ServiceTypeMatches("_avenger._udp")); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Another Browse()"); + SuccessOrQuit(dnsClient->Browse("_game._udp.default.service.arpa.", BrowseCallback, sInstance)); + AdvanceTime(10); + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 2); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStartBrowserInfo.ServiceTypeMatches("_game._udp")); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("ResolveService()"); + SuccessOrQuit( + dnsClient->ResolveService("wanda", "_avenger._udp.default.service.arpa.", ServiceCallback, sInstance)); + AdvanceTime(10); + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 2); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStartSrvResolverInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStartSrvResolverInfo.ServiceInstanceMatches("wanda")); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("ResolveAddress()"); + SuccessOrQuit(dnsClient->ResolveAddress("earth.default.service.arpa.", AddressCallback, sInstance)); + AdvanceTime(10); + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 2); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStartIp6AddrResolverInfo.HostNameMatches("earth")); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Wait for timeout for all requests"); + + AdvanceTime(10 * 1000); + + VerifyOrQuit(sBrowseInfo.mCallbackCount == 2); + VerifyOrQuit(sBrowseInfo.mNumInstances == 0); + + VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); + VerifyOrQuit(sResolveServiceInfo.mError == kErrorNotFound); + + VerifyOrQuit(sResolveAddressInfo.mCallbackCount == 1); + VerifyOrQuit(sResolveAddressInfo.mNumHostAddresses == 0); + + // Check that all browsers/resolvers are stopped. + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 2); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 2); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 1); + + VerifyOrQuit(sStopSrvResolverInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStopSrvResolverInfo.ServiceInstanceMatches("wanda")); + VerifyOrQuit(sStopIp6AddrResolverInfo.HostNameMatches("earth")); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Stop DNS-SD server"); + + dnsServer->Stop(); + + AdvanceTime(10); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Finalize OT instance and validate all heap allocations are freed. + + Log("Finalizing OT instance"); + FinalizeTest(); + + Log("End of TestProxyTimeout"); +} + +void TestProxySharedResolver(void) +{ + static constexpr uint32_t kTtl = 300; + + const uint8_t kTxtData[] = {3, 'A', '=', '1', 0}; + + Srp::Server *srpServer; + Srp::Client *srpClient; + Dns::Client *dnsClient; + Dns::ServiceDiscovery::Server *dnsServer; + Dnssd::BrowseResult browseResult; + Dnssd::SrvResult srvResult; + Dnssd::TxtResult txtResult; + Dnssd::AddressResult ip6AddrrResult; + Dnssd::AddressAndTtl addressAndTtl[2]; + + Log("--------------------------------------------------------------------------------------------"); + Log("TestProxySharedResolver"); + + InitTest(); + + srpServer = &sInstance->Get(); + srpClient = &sInstance->Get(); + dnsClient = &sInstance->Get(); + dnsServer = &sInstance->Get(); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start SRP server. + + SuccessOrQuit(otBorderRoutingInit(sInstance, /* aInfraIfIndex */ kInfraIfIndex, /* aInfraIfIsRunning */ true)); + + SuccessOrQuit(srpServer->SetAddressMode(Srp::Server::kAddressModeUnicast)); + VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateDisabled); + + srpServer->SetEnabled(true); + VerifyOrQuit(srpServer->GetState() != Srp::Server::kStateDisabled); + + AdvanceTime(10000); + VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateRunning); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start SRP client. + + srpClient->EnableAutoStartMode(nullptr, nullptr); + VerifyOrQuit(srpClient->IsAutoStartModeEnabled()); + + AdvanceTime(2000); + VerifyOrQuit(srpClient->IsRunning()); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + + sBrowseInfo.Reset(); + sResolveServiceInfo.Reset(); + sResolveAddressInfo.Reset(); + ResetPlatDnssdApiInfo(); + + Log("ResolveAddress()"); + SuccessOrQuit(dnsClient->ResolveAddress("knowhere.default.service.arpa.", AddressCallback, sInstance)); + AdvanceTime(10); + + Log("ResolveService()"); + SuccessOrQuit( + dnsClient->ResolveService("starlord", "_guardian._glaxy.default.service.arpa.", ServiceCallback, sInstance)); + AdvanceTime(10); + + Log("Browse()"); + SuccessOrQuit(dnsClient->Browse("_guardian._glaxy.default.service.arpa.", BrowseCallback, sInstance)); + AdvanceTime(10); + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStartBrowserInfo.ServiceTypeMatches("_guardian._glaxy")); + + VerifyOrQuit(sStartSrvResolverInfo.ServiceTypeMatches("_guardian._glaxy")); + VerifyOrQuit(sStartSrvResolverInfo.ServiceInstanceMatches("starlord")); + + VerifyOrQuit(sStartIp6AddrResolverInfo.HostNameMatches("knowhere")); + + VerifyOrQuit(sBrowseInfo.mCallbackCount == 0); + VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 0); + VerifyOrQuit(sResolveAddressInfo.mCallbackCount == 0); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke Browser callback"); + + browseResult.mServiceType = "_guardian._glaxy"; + browseResult.mSubTypeLabel = nullptr; + browseResult.mServiceInstance = "starlord"; + browseResult.mTtl = kTtl; + browseResult.mInfraIfIndex = kInfraIfIndex; + + InvokeBrowserCallback(sStartBrowserInfo.mCallback, browseResult); + + AdvanceTime(10); + + // Check that browser is stopped and since the service instance + // name matches an existing resolver, we should not see any new + // resolver starting. + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStopBrowserInfo.ServiceTypeMatches("_guardian._glaxy")); + + VerifyOrQuit(sBrowseInfo.mCallbackCount == 0); + VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 0); + VerifyOrQuit(sResolveAddressInfo.mCallbackCount == 0); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke Service Resolver callback"); + + srvResult.mServiceInstance = "starlord"; + srvResult.mServiceType = "_guardian._glaxy"; + srvResult.mHostName = "knowhere"; + srvResult.mPort = 3333; + srvResult.mTtl = kTtl; + srvResult.mInfraIfIndex = kInfraIfIndex; + + InvokeSrvResolverCallback(sStartSrvResolverInfo.mCallback, srvResult); + + AdvanceTime(10); + + // Check that SRV resolver is now stopped and a TXT resolver + // is started for same service. + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStopSrvResolverInfo.ServiceTypeMatches("_guardian._glaxy")); + VerifyOrQuit(sStopSrvResolverInfo.ServiceInstanceMatches("starlord")); + + txtResult.mServiceInstance = "starlord"; + txtResult.mServiceType = "_guardian._glaxy"; + txtResult.mTxtData = kTxtData; + txtResult.mTxtDataLength = sizeof(kTxtData); + txtResult.mTtl = kTtl; + txtResult.mInfraIfIndex = kInfraIfIndex; + + InvokeTxtResolverCallback(sStartTxtResolverInfo.mCallback, txtResult); + + AdvanceTime(10); + + // Check that TXT resolver is now stopped but again since the + // host name matches an existing address resolver we should not + // see any new address resolver. + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStopTxtResolverInfo.ServiceTypeMatches("_guardian._glaxy")); + VerifyOrQuit(sStopTxtResolverInfo.ServiceInstanceMatches("starlord")); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke Address Resolver callback"); + + SuccessOrQuit(AsCoreType(&addressAndTtl[0].mAddress).FromString("fd00::5555")); + SuccessOrQuit(AsCoreType(&addressAndTtl[1].mAddress).FromString("fd00::1234")); + addressAndTtl[0].mTtl = kTtl; + addressAndTtl[1].mTtl = kTtl; + + ip6AddrrResult.mHostName = "knowhere"; + ip6AddrrResult.mInfraIfIndex = kInfraIfIndex; + ip6AddrrResult.mAddresses = addressAndTtl; + ip6AddrrResult.mAddressesLength = 2; + + InvokeIp6AddrResolverCallback(sStartIp6AddrResolverInfo.mCallback, ip6AddrrResult); + + AdvanceTime(10); + + // Check that the address resolver is now stopped. + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 1); + + VerifyOrQuit(sStopIp6AddrResolverInfo.HostNameMatches("knowhere")); + + // Check the browse response received on client + + VerifyOrQuit(sBrowseInfo.mCallbackCount == 1); + SuccessOrQuit(sBrowseInfo.mError); + VerifyOrQuit(sBrowseInfo.mNumInstances == 1); + + VerifyOrQuit(!strcmp(sBrowseInfo.mServiceName, "_guardian._glaxy.default.service.arpa.")); + VerifyOrQuit(!strcmp(sBrowseInfo.mInstanceLabel, "starlord")); + VerifyOrQuit(!strcmp(sBrowseInfo.mServiceInfo.mHostNameBuffer, "knowhere.default.service.arpa.")); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mPort == 3333); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mTtl == kTtl); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mHostAddressTtl == kTtl); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mTxtDataSize == sizeof(kTxtData)); + VerifyOrQuit(!memcmp(sBrowseInfo.mServiceInfo.mTxtData, kTxtData, sizeof(kTxtData))); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mTxtDataTtl == kTtl); + VerifyOrQuit(!sBrowseInfo.mServiceInfo.mTxtDataTruncated); + + // Check the service resolve response received on client + + VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); + SuccessOrQuit(sResolveServiceInfo.mError); + + VerifyOrQuit(!strcmp(sResolveServiceInfo.mInfo.mHostNameBuffer, "knowhere.default.service.arpa.")); + VerifyOrQuit(sResolveServiceInfo.mInfo.mPort == 3333); + VerifyOrQuit(sResolveServiceInfo.mInfo.mTtl == kTtl); + VerifyOrQuit(sResolveServiceInfo.mInfo.mHostAddressTtl == kTtl); + VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataSize == sizeof(kTxtData)); + VerifyOrQuit(!memcmp(sResolveServiceInfo.mInfo.mTxtData, kTxtData, sizeof(kTxtData))); + VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataTtl == kTtl); + VerifyOrQuit(!sResolveServiceInfo.mInfo.mTxtDataTruncated); + VerifyOrQuit(sResolveServiceInfo.mNumHostAddresses == 2); + for (uint16_t index = 0; index < 2; index++) + { + VerifyOrQuit(sResolveServiceInfo.mHostAddresses[index] == AsCoreType(&addressAndTtl[0].mAddress) || + sResolveServiceInfo.mHostAddresses[index] == AsCoreType(&addressAndTtl[1].mAddress)); + } + + // Check the address resolve response received on client + + VerifyOrQuit(sResolveAddressInfo.mCallbackCount == 1); + SuccessOrQuit(sResolveAddressInfo.mError); + + VerifyOrQuit(!strcmp(sResolveAddressInfo.mHostName, "knowhere.default.service.arpa.")); + VerifyOrQuit(sResolveAddressInfo.mTtl == kTtl); + VerifyOrQuit(sResolveAddressInfo.mNumHostAddresses == 2); + for (uint16_t index = 0; index < 2; index++) + { + VerifyOrQuit(sResolveAddressInfo.mHostAddresses[index] == AsCoreType(&addressAndTtl[0].mAddress) || + sResolveAddressInfo.mHostAddresses[index] == AsCoreType(&addressAndTtl[1].mAddress)); + } + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Stop DNS-SD server"); + + dnsServer->Stop(); + + AdvanceTime(10); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Finalize OT instance and validate all heap allocations are freed. + + Log("Finalizing OT instance"); + FinalizeTest(); + + Log("End of TestProxySharedResolver"); +} + +void TestProxyFilterInvalidAddresses(void) +{ + static constexpr uint32_t kTtl = 300; + + Srp::Server *srpServer; + Srp::Client *srpClient; + Dns::Client *dnsClient; + Dns::ServiceDiscovery::Server *dnsServer; + Dnssd::AddressResult ip6AddrrResult; + Dnssd::AddressAndTtl addressAndTtl[10]; + + Log("--------------------------------------------------------------------------------------------"); + Log("TestProxyFilterInvalidAddresses"); + + InitTest(); + + srpServer = &sInstance->Get(); + srpClient = &sInstance->Get(); + dnsClient = &sInstance->Get(); + dnsServer = &sInstance->Get(); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start SRP server. + + SuccessOrQuit(otBorderRoutingInit(sInstance, /* aInfraIfIndex */ kInfraIfIndex, /* aInfraIfIsRunning */ true)); + + SuccessOrQuit(srpServer->SetAddressMode(Srp::Server::kAddressModeUnicast)); + VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateDisabled); + + srpServer->SetEnabled(true); + VerifyOrQuit(srpServer->GetState() != Srp::Server::kStateDisabled); + + AdvanceTime(10000); + VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateRunning); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start SRP client. + + srpClient->EnableAutoStartMode(nullptr, nullptr); + VerifyOrQuit(srpClient->IsAutoStartModeEnabled()); + + AdvanceTime(2000); + VerifyOrQuit(srpClient->IsRunning()); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + + sResolveAddressInfo.Reset(); + ResetPlatDnssdApiInfo(); + + Log("ResolveAddress()"); + SuccessOrQuit(dnsClient->ResolveAddress("host.default.service.arpa.", AddressCallback, sInstance)); + AdvanceTime(10); + + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStartIp6AddrResolverInfo.HostNameMatches("host")); + + VerifyOrQuit(sResolveAddressInfo.mCallbackCount == 0); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke Address Resolver callback with invalid addresses"); + + SuccessOrQuit(AsCoreType(&addressAndTtl[0].mAddress).FromString("::")); // Unspecified + SuccessOrQuit(AsCoreType(&addressAndTtl[1].mAddress).FromString("fe80::1234")); // Link local + SuccessOrQuit(AsCoreType(&addressAndTtl[2].mAddress).FromString("ff00::1234")); // Multicast + SuccessOrQuit(AsCoreType(&addressAndTtl[3].mAddress).FromString("::1")); // Lookback + + for (uint16_t index = 0; index < 5; index++) + { + addressAndTtl[index].mTtl = kTtl; + } + + ip6AddrrResult.mHostName = "host"; + ip6AddrrResult.mInfraIfIndex = kInfraIfIndex; + ip6AddrrResult.mAddresses = addressAndTtl; + ip6AddrrResult.mAddressesLength = 4; + + InvokeIp6AddrResolverCallback(sStartIp6AddrResolverInfo.mCallback, ip6AddrrResult); + + AdvanceTime(10); + + // Check that the address resolver is not stopped, since all addresses where + // invalid address. + + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sResolveAddressInfo.mCallbackCount == 0); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke Address Resolver callback with invalid addresses with one valid"); + + SuccessOrQuit(AsCoreType(&addressAndTtl[4].mAddress).FromString("f00::1234")); + + ip6AddrrResult.mAddressesLength = 5; + + InvokeIp6AddrResolverCallback(sStartIp6AddrResolverInfo.mCallback, ip6AddrrResult); + + AdvanceTime(10); + + // Check that address resolver is not stopped + + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 1); + + VerifyOrQuit(sStopIp6AddrResolverInfo.HostNameMatches("host")); + + // Check that response received on client is valid and only contains + // the valid two addresses and filters all others. + + VerifyOrQuit(sResolveAddressInfo.mCallbackCount == 1); + SuccessOrQuit(sResolveAddressInfo.mError); + + VerifyOrQuit(!strcmp(sResolveAddressInfo.mHostName, "host.default.service.arpa.")); + VerifyOrQuit(sResolveAddressInfo.mTtl == kTtl); + VerifyOrQuit(sResolveAddressInfo.mNumHostAddresses == 1); + VerifyOrQuit(sResolveAddressInfo.mHostAddresses[0] == AsCoreType(&addressAndTtl[4].mAddress)); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Stop DNS-SD server"); + + dnsServer->Stop(); + + AdvanceTime(10); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Finalize OT instance and validate all heap allocations are freed. + + Log("Finalizing OT instance"); + FinalizeTest(); + + Log("End of TestProxyFilterInvalidAddresses"); +} + +void TestProxyStateChanges(void) +{ + static constexpr uint32_t kTtl = 300; + + Srp::Server *srpServer; + Srp::Client *srpClient; + Dns::Client *dnsClient; + Dns::ServiceDiscovery::Server *dnsServer; + + Log("--------------------------------------------------------------------------------------------"); + Log("TestProxyStateChanges"); + + InitTest(); + + srpServer = &sInstance->Get(); + srpClient = &sInstance->Get(); + dnsClient = &sInstance->Get(); + dnsServer = &sInstance->Get(); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start SRP server. + + SuccessOrQuit(otBorderRoutingInit(sInstance, /* aInfraIfIndex */ kInfraIfIndex, /* aInfraIfIsRunning */ true)); + + SuccessOrQuit(srpServer->SetAddressMode(Srp::Server::kAddressModeUnicast)); + VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateDisabled); + + srpServer->SetEnabled(true); + VerifyOrQuit(srpServer->GetState() != Srp::Server::kStateDisabled); + + AdvanceTime(10000); + VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateRunning); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start SRP client. + + srpClient->EnableAutoStartMode(nullptr, nullptr); + VerifyOrQuit(srpClient->IsAutoStartModeEnabled()); + + AdvanceTime(2000); + VerifyOrQuit(srpClient->IsRunning()); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Signal DNS-SD platform state is stopped and not yet ready"); + + sDnssdState = OT_PLAT_DNSSD_STOPPED; + otPlatDnssdStateHandleStateChange(sInstance); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + + sResolveAddressInfo.Reset(); + ResetPlatDnssdApiInfo(); + + Log("ResolveAddress()"); + SuccessOrQuit(dnsClient->ResolveAddress("host.default.service.arpa.", AddressCallback, sInstance)); + AdvanceTime(10); + + // Check that none of the DNS-SD resolver/browser APIs are called + // since the platform is not yet ready + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sResolveAddressInfo.mCallbackCount == 1); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Signal DNS-SD platform state is now ready"); + + sDnssdState = OT_PLAT_DNSSD_READY; + otPlatDnssdStateHandleStateChange(sInstance); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + + sResolveAddressInfo.Reset(); + ResetPlatDnssdApiInfo(); + + Log("ResolveAddress()"); + SuccessOrQuit(dnsClient->ResolveAddress("host.default.service.arpa.", AddressCallback, sInstance)); + AdvanceTime(10); + + // Check that address resolver is started + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStartIp6AddrResolverInfo.HostNameMatches("host")); + + VerifyOrQuit(sResolveAddressInfo.mCallbackCount == 0); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + + sBrowseInfo.Reset(); + + Log("Browse()"); + SuccessOrQuit(dnsClient->Browse("_magic._udp.default.service.arpa.", BrowseCallback, sInstance)); + AdvanceTime(10); + + // Check that browser is also started + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + + VerifyOrQuit(sStartBrowserInfo.ServiceTypeMatches("_magic._udp")); + + VerifyOrQuit(sResolveAddressInfo.mCallbackCount == 0); + VerifyOrQuit(sBrowseInfo.mCallbackCount == 0); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Signal infra-if is not running"); + + SuccessOrQuit(otPlatInfraIfStateChanged(sInstance, kInfraIfIndex, /* aIsRunning */ false)); + + AdvanceTime(10); + + // Check that both address resolver and browser are stopped + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 1); + + VerifyOrQuit(sStopIp6AddrResolverInfo.HostNameMatches("host")); + VerifyOrQuit(sStopBrowserInfo.ServiceTypeMatches("_magic._udp")); + + // And response is sent to client + + VerifyOrQuit(sResolveAddressInfo.mCallbackCount == 1); + VerifyOrQuit(sBrowseInfo.mCallbackCount == 1); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + + sResolveAddressInfo.Reset(); + + Log("ResolveAddress()"); + SuccessOrQuit(dnsClient->ResolveAddress("earth.default.service.arpa.", AddressCallback, sInstance)); + AdvanceTime(10); + + // Check that no resolver is started. + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 1); + + VerifyOrQuit(sResolveAddressInfo.mCallbackCount == 1); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Signal that infra-if is running again "); + + SuccessOrQuit(otPlatInfraIfStateChanged(sInstance, kInfraIfIndex, /* aIsRunning */ true)); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + sResolveServiceInfo.Reset(); + Log("ResolveService()"); + SuccessOrQuit(dnsClient->ResolveService("captain.america", "_avenger._udp.default.service.arpa.", ServiceCallback, + sInstance)); + AdvanceTime(10); + + // The proxy should be started again so check that a service resolver + // is started for new request + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 1); + + VerifyOrQuit(sStartSrvResolverInfo.ServiceTypeMatches("_avenger._udp")); + VerifyOrQuit(sStartSrvResolverInfo.ServiceInstanceMatches("captain.america")); + + VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 0); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Signal DNS-SD platform state is stopped"); + + sDnssdState = OT_PLAT_DNSSD_STOPPED; + otPlatDnssdStateHandleStateChange(sInstance); + + AdvanceTime(10); + + // This should stop proxy but since DNS-SD platform is stopped + // we assume all browsers/resolvers are also stopped, so there + // should be no explicit call to stop it. + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 1); + + // Check that response is sent to client + + VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Signal DNS-SD platform state is ready again"); + + sDnssdState = OT_PLAT_DNSSD_READY; + otPlatDnssdStateHandleStateChange(sInstance); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + + sBrowseInfo.Reset(); + + Log("Browse()"); + SuccessOrQuit(dnsClient->Browse("_magical._udp.default.service.arpa.", BrowseCallback, sInstance)); + AdvanceTime(10); + + // Proxy should be started again and we should see a new browser started + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 2); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 1); + + VerifyOrQuit(sStartBrowserInfo.ServiceTypeMatches("_magical._udp")); + + VerifyOrQuit(sBrowseInfo.mCallbackCount == 0); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Stop DNS-SD server"); + + dnsServer->Stop(); + + AdvanceTime(10); + + // Check that the browser is stopped + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 2); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 2); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 1); + + VerifyOrQuit(sStopBrowserInfo.ServiceTypeMatches("_magical._udp")); + + // And response is sent to client + + VerifyOrQuit(sBrowseInfo.mCallbackCount == 1); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Finalize OT instance and validate all heap allocations are freed. + + Log("Finalizing OT instance"); + FinalizeTest(); + + Log("End of TestProxyStateChanges"); +} + +void TestProxyInvokeCallbackFromStartApi(void) +{ + static constexpr uint32_t kTtl = 300; + + const uint8_t kTxtData[] = {3, 'A', '=', '1', 0}; + + Srp::Server *srpServer; + Srp::Client *srpClient; + Dns::Client *dnsClient; + Dns::ServiceDiscovery::Server *dnsServer; + Dnssd::BrowseResult browseResult; + Dnssd::SrvResult srvResult; + Dnssd::TxtResult txtResult; + Dnssd::AddressResult ip6AddrrResult; + Dnssd::AddressAndTtl addressAndTtl[2]; + + Log("--------------------------------------------------------------------------------------------"); + Log("TestProxyInvokeCallbackFromStartApi"); + + InitTest(); + + srpServer = &sInstance->Get(); + srpClient = &sInstance->Get(); + dnsClient = &sInstance->Get(); + dnsServer = &sInstance->Get(); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start SRP server. + + SuccessOrQuit(otBorderRoutingInit(sInstance, /* aInfraIfIndex */ kInfraIfIndex, /* aInfraIfIsRunning */ true)); + + SuccessOrQuit(srpServer->SetAddressMode(Srp::Server::kAddressModeUnicast)); + VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateDisabled); + + srpServer->SetEnabled(true); + VerifyOrQuit(srpServer->GetState() != Srp::Server::kStateDisabled); + + AdvanceTime(10000); + VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateRunning); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start SRP client. + + srpClient->EnableAutoStartMode(nullptr, nullptr); + VerifyOrQuit(srpClient->IsAutoStartModeEnabled()); + + AdvanceTime(2000); + VerifyOrQuit(srpClient->IsRunning()); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Enable invoking of callback directly from otPlatDnssdStart{Browsers/Resolver} APIs"); + + ResetPlatDnssdApiInfo(); + + sInvokeOnStart.mBrowseResult = &browseResult; + sInvokeOnStart.mSrvResult = &srvResult; + sInvokeOnStart.mTxtResult = &txtResult; + sInvokeOnStart.mIp6AddrResult = &ip6AddrrResult; + + browseResult.mServiceType = "_guardian._glaxy"; + browseResult.mSubTypeLabel = nullptr; + browseResult.mServiceInstance = "mantis"; + browseResult.mTtl = kTtl; + browseResult.mInfraIfIndex = kInfraIfIndex; + + srvResult.mServiceInstance = "mantis"; + srvResult.mServiceType = "_guardian._glaxy"; + srvResult.mHostName = "nova"; + srvResult.mPort = 3333; + srvResult.mTtl = kTtl; + srvResult.mInfraIfIndex = kInfraIfIndex; + + txtResult.mServiceInstance = "mantis"; + txtResult.mServiceType = "_guardian._glaxy"; + txtResult.mTxtData = kTxtData; + txtResult.mTxtDataLength = sizeof(kTxtData); + txtResult.mTtl = kTtl; + txtResult.mInfraIfIndex = kInfraIfIndex; + + SuccessOrQuit(AsCoreType(&addressAndTtl[0].mAddress).FromString("fd00::5555")); + SuccessOrQuit(AsCoreType(&addressAndTtl[1].mAddress).FromString("fd00::1234")); + addressAndTtl[0].mTtl = kTtl; + addressAndTtl[1].mTtl = kTtl; + + ip6AddrrResult.mHostName = "nova"; + ip6AddrrResult.mInfraIfIndex = kInfraIfIndex; + ip6AddrrResult.mAddresses = addressAndTtl; + ip6AddrrResult.mAddressesLength = 2; + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + sBrowseInfo.Reset(); + + Log("Browse()"); + SuccessOrQuit(dnsClient->Browse("_guardian._glaxy.default.service.arpa.", BrowseCallback, sInstance)); + AdvanceTime(10); + + // All browsers/resolvers should be started and stopped + // (since the callbacks are invoked directly from API) + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 1); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 1); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 1); + + VerifyOrQuit(sStartBrowserInfo.ServiceTypeMatches("_guardian._glaxy")); + VerifyOrQuit(sStopBrowserInfo.ServiceTypeMatches("_guardian._glaxy")); + + VerifyOrQuit(sStartSrvResolverInfo.ServiceTypeMatches("_guardian._glaxy")); + VerifyOrQuit(sStartSrvResolverInfo.ServiceInstanceMatches("mantis")); + VerifyOrQuit(sStopSrvResolverInfo.ServiceTypeMatches("_guardian._glaxy")); + VerifyOrQuit(sStopSrvResolverInfo.ServiceInstanceMatches("mantis")); + + VerifyOrQuit(sStartTxtResolverInfo.ServiceTypeMatches("_guardian._glaxy")); + VerifyOrQuit(sStartTxtResolverInfo.ServiceInstanceMatches("mantis")); + VerifyOrQuit(sStopTxtResolverInfo.ServiceTypeMatches("_guardian._glaxy")); + VerifyOrQuit(sStopTxtResolverInfo.ServiceInstanceMatches("mantis")); + + VerifyOrQuit(sStartIp6AddrResolverInfo.HostNameMatches("nova")); + VerifyOrQuit(sStopIp6AddrResolverInfo.HostNameMatches("nova")); + + // Check that response is revived by client and validate it + + VerifyOrQuit(sBrowseInfo.mCallbackCount == 1); + + VerifyOrQuit(!strcmp(sBrowseInfo.mServiceName, "_guardian._glaxy.default.service.arpa.")); + VerifyOrQuit(!strcmp(sBrowseInfo.mInstanceLabel, "mantis")); + VerifyOrQuit(!strcmp(sBrowseInfo.mServiceInfo.mHostNameBuffer, "nova.default.service.arpa.")); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mPort == 3333); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mTtl == kTtl); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mHostAddressTtl == kTtl); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mTxtDataSize == sizeof(kTxtData)); + VerifyOrQuit(!memcmp(sBrowseInfo.mServiceInfo.mTxtData, kTxtData, sizeof(kTxtData))); + VerifyOrQuit(sBrowseInfo.mServiceInfo.mTxtDataTtl == kTtl); + VerifyOrQuit(!sBrowseInfo.mServiceInfo.mTxtDataTruncated); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + sResolveServiceInfo.Reset(); + Log("ResolveService()"); + SuccessOrQuit( + dnsClient->ResolveService("mantis", "_guardian._glaxy.default.service.arpa.", ServiceCallback, sInstance)); + AdvanceTime(10); + + // Check that new SRV/TXT resolver and address resolvers are + // started and stopped. + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 2); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 2); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 2); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 2); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 2); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 2); + + VerifyOrQuit(sStartSrvResolverInfo.ServiceTypeMatches("_guardian._glaxy")); + VerifyOrQuit(sStartSrvResolverInfo.ServiceInstanceMatches("mantis")); + VerifyOrQuit(sStopSrvResolverInfo.ServiceTypeMatches("_guardian._glaxy")); + VerifyOrQuit(sStopSrvResolverInfo.ServiceInstanceMatches("mantis")); + + VerifyOrQuit(sStartTxtResolverInfo.ServiceTypeMatches("_guardian._glaxy")); + VerifyOrQuit(sStartTxtResolverInfo.ServiceInstanceMatches("mantis")); + VerifyOrQuit(sStopTxtResolverInfo.ServiceTypeMatches("_guardian._glaxy")); + VerifyOrQuit(sStopTxtResolverInfo.ServiceInstanceMatches("mantis")); + + VerifyOrQuit(sStartIp6AddrResolverInfo.HostNameMatches("nova")); + VerifyOrQuit(sStopIp6AddrResolverInfo.HostNameMatches("nova")); + + // Check the service resolve response received on client + + VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); + SuccessOrQuit(sResolveServiceInfo.mError); + + VerifyOrQuit(!strcmp(sResolveServiceInfo.mInfo.mHostNameBuffer, "nova.default.service.arpa.")); + VerifyOrQuit(sResolveServiceInfo.mInfo.mPort == 3333); + VerifyOrQuit(sResolveServiceInfo.mInfo.mTtl == kTtl); + VerifyOrQuit(sResolveServiceInfo.mInfo.mHostAddressTtl == kTtl); + VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataSize == sizeof(kTxtData)); + VerifyOrQuit(!memcmp(sResolveServiceInfo.mInfo.mTxtData, kTxtData, sizeof(kTxtData))); + VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataTtl == kTtl); + VerifyOrQuit(!sResolveServiceInfo.mInfo.mTxtDataTruncated); + VerifyOrQuit(sResolveServiceInfo.mNumHostAddresses == 2); + for (uint16_t index = 0; index < 2; index++) + { + VerifyOrQuit(sResolveServiceInfo.mHostAddresses[index] == AsCoreType(&addressAndTtl[0].mAddress) || + sResolveServiceInfo.mHostAddresses[index] == AsCoreType(&addressAndTtl[1].mAddress)); + } + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + sResolveAddressInfo.Reset(); + Log("ResolveAddress()"); + SuccessOrQuit(dnsClient->ResolveAddress("nova.default.service.arpa.", AddressCallback, sInstance)); + AdvanceTime(10); + + // Check that new address resolver is started and stopped. + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 1); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 2); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 2); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 2); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 2); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 3); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 3); + + VerifyOrQuit(sStartIp6AddrResolverInfo.HostNameMatches("nova")); + VerifyOrQuit(sStopIp6AddrResolverInfo.HostNameMatches("nova")); + + // Check the address resolve response received on client + + VerifyOrQuit(sResolveAddressInfo.mCallbackCount == 1); + SuccessOrQuit(sResolveAddressInfo.mError); + + VerifyOrQuit(!strcmp(sResolveAddressInfo.mHostName, "nova.default.service.arpa.")); + VerifyOrQuit(sResolveAddressInfo.mTtl == kTtl); + VerifyOrQuit(sResolveAddressInfo.mNumHostAddresses == 2); + for (uint16_t index = 0; index < 2; index++) + { + VerifyOrQuit(sResolveAddressInfo.mHostAddresses[index] == AsCoreType(&addressAndTtl[0].mAddress) || + sResolveAddressInfo.mHostAddresses[index] == AsCoreType(&addressAndTtl[1].mAddress)); + } + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Stop DNS-SD server"); + + dnsServer->Stop(); + + AdvanceTime(10); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Finalize OT instance and validate all heap allocations are freed. + + Log("Finalizing OT instance"); + FinalizeTest(); + + Log("End of TestProxyInvokeCallbackFromStartApi"); +} + +#endif // ENABLE_DISCOVERY_PROXY_TEST + +int main(void) +{ +#if ENABLE_DISCOVERY_PROXY_TEST + TestProxyBasic(); + TestProxySubtypeBrowse(); + TestProxyTimeout(); + TestProxySharedResolver(); + TestProxyFilterInvalidAddresses(); + TestProxyStateChanges(); + TestProxyInvokeCallbackFromStartApi(); + + printf("All tests passed\n"); +#else + printf("DISCOVERY_PROXY feature or a related feature required by this unit test is not enabled\n"); +#endif + + return 0; +} diff --git a/tests/unit/test_platform.cpp b/tests/unit/test_platform.cpp index 78a608965..0cef5796e 100644 --- a/tests/unit/test_platform.cpp +++ b/tests/unit/test_platform.cpp @@ -877,6 +877,66 @@ OT_TOOL_WEAK void otPlatDnssdUnregisterKey(otInstance *aInstance OT_UNUSED_VARIABLE(aCallback); } +OT_TOOL_WEAK void otPlatDnssdStartBrowser(otInstance *aInstance, const otPlatDnssdBrowser *aBrowser) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aBrowser); +} + +OT_TOOL_WEAK void otPlatDnssdStopBrowser(otInstance *aInstance, const otPlatDnssdBrowser *aBrowser) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aBrowser); +} + +OT_TOOL_WEAK void otPlatDnssdStartSrvResolver(otInstance *aInstance, const otPlatDnssdSrvResolver *aResolver) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aResolver); +} + +OT_TOOL_WEAK void otPlatDnssdStopSrvResolver(otInstance *aInstance, const otPlatDnssdSrvResolver *aResolver) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aResolver); +} + +OT_TOOL_WEAK void otPlatDnssdStartTxtResolver(otInstance *aInstance, const otPlatDnssdTxtResolver *aResolver) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aResolver); +} + +OT_TOOL_WEAK void otPlatDnssdStopTxtResolver(otInstance *aInstance, const otPlatDnssdTxtResolver *aResolver) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aResolver); +} + +OT_TOOL_WEAK void otPlatDnssdStartIp6AddressResolver(otInstance *aInstance, const otPlatDnssdAddressResolver *aResolver) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aResolver); +} + +OT_TOOL_WEAK void otPlatDnssdStopIp6AddressResolver(otInstance *aInstance, const otPlatDnssdAddressResolver *aResolver) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aResolver); +} + +OT_TOOL_WEAK void otPlatDnssdStartIp4AddressResolver(otInstance *aInstance, const otPlatDnssdAddressResolver *aResolver) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aResolver); +} + +OT_TOOL_WEAK void otPlatDnssdStopIp4AddressResolver(otInstance *aInstance, const otPlatDnssdAddressResolver *aResolver) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aResolver); +} + #endif // OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE #if OPENTHREAD_CONFIG_PLATFORM_LOG_CRASH_DUMP_ENABLE