diff --git a/.github/workflows/android.yaml b/.github/workflows/android.yaml index 1a7c6da45a30dc..0c1a6409f32158 100644 --- a/.github/workflows/android.yaml +++ b/.github/workflows/android.yaml @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest container: - image: connectedhomeip/chip-build-android:0.4.11 + image: connectedhomeip/chip-build-android:0.4.12 volumes: - "/tmp/log_output:/tmp/test_logs" diff --git a/.github/workflows/bloat_check.yaml b/.github/workflows/bloat_check.yaml index ed72656c781376..5d989b22e6f560 100644 --- a/.github/workflows/bloat_check.yaml +++ b/.github/workflows/bloat_check.yaml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest container: - image: connectedhomeip/chip-build:0.4.11 + image: connectedhomeip/chip-build:0.4.12 steps: - name: Checkout diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d8fede3c270ab3..fc56356b868a14 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-latest container: - image: connectedhomeip/chip-build:0.4.11 + image: connectedhomeip/chip-build:0.4.12 volumes: - "/tmp/log_output:/tmp/test_logs" options: diff --git a/.github/workflows/examples-gn.yml b/.github/workflows/examples-gn.yml index 1944ccf3ddbab5..7ab93bc6c6eb35 100644 --- a/.github/workflows/examples-gn.yml +++ b/.github/workflows/examples-gn.yml @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest container: - image: connectedhomeip/chip-build-esp32:0.4.11 + image: connectedhomeip/chip-build-esp32:0.4.12 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" - "/tmp/output_binaries:/tmp/output_binaries" diff --git a/.github/workflows/examples.yaml b/.github/workflows/examples.yaml index 8ac5f7c08c1692..0d727f74530948 100644 --- a/.github/workflows/examples.yaml +++ b/.github/workflows/examples.yaml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest container: - image: connectedhomeip/chip-build-nrf-platform:0.4.11 + image: connectedhomeip/chip-build-nrf-platform:0.4.12 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" - "/tmp/output_binaries:/tmp/output_binaries" @@ -100,7 +100,7 @@ jobs: runs-on: ubuntu-latest container: - image: connectedhomeip/chip-build:0.4.11 + image: connectedhomeip/chip-build:0.4.12 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" - "/tmp/output_binaries:/tmp/output_binaries" @@ -164,7 +164,7 @@ jobs: runs-on: ubuntu-latest container: - image: connectedhomeip/chip-build-efr32:0.4.11 + image: connectedhomeip/chip-build-efr32:0.4.12 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" - "/tmp/output_binaries:/tmp/output_binaries" @@ -222,7 +222,7 @@ jobs: runs-on: ubuntu-latest container: - image: connectedhomeip/chip-build-nrf-platform:0.4.11 + image: connectedhomeip/chip-build-nrf-platform:0.4.12 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" - "/tmp/output_binaries:/tmp/output_binaries" @@ -300,7 +300,7 @@ jobs: runs-on: ubuntu-latest container: - image: connectedhomeip/chip-build:0.4.11 + image: connectedhomeip/chip-build:0.4.12 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" - "/tmp/output_binaries:/tmp/output_binaries" diff --git a/.github/workflows/qemu.yaml b/.github/workflows/qemu.yaml index 6a799f1cf2b249..d299107880ffa3 100644 --- a/.github/workflows/qemu.yaml +++ b/.github/workflows/qemu.yaml @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest container: - image: connectedhomeip/chip-build-esp32-qemu:0.4.11 + image: connectedhomeip/chip-build-esp32-qemu:0.4.12 volumes: - "/tmp/log_output:/tmp/test_logs" diff --git a/.github/workflows/unit_integration_test.yaml b/.github/workflows/unit_integration_test.yaml index 86e45dbaddeee8..dd532dcb50d522 100644 --- a/.github/workflows/unit_integration_test.yaml +++ b/.github/workflows/unit_integration_test.yaml @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-latest container: - image: connectedhomeip/chip-build:0.4.11 + image: connectedhomeip/chip-build:0.4.12 volumes: - "/tmp/log_output:/tmp/test_logs" - "/tmp/happy_test_logs:/tmp/happy_test_logs" diff --git a/integrations/docker/images/chip-build-cirque/version b/integrations/docker/images/chip-build-cirque/version index 5f749c13663ae3..75274d8329449c 100644 --- a/integrations/docker/images/chip-build-cirque/version +++ b/integrations/docker/images/chip-build-cirque/version @@ -1 +1 @@ -0.4.11 +0.4.12 diff --git a/integrations/docker/run.sh b/integrations/docker/run.sh index 578c281959749c..a1b0cb7dc04ee8 100755 --- a/integrations/docker/run.sh +++ b/integrations/docker/run.sh @@ -41,6 +41,9 @@ VERSION=${DOCKER_RUN_VERSION:-$(cat "$here/version")} || DOCKER_RUN_IMAGE DOCKER_RUN_VERSION" +# full image name +FULL_IMAGE_NAME="$ORG/$IMAGE${VERSION:+:${VERSION}}" + # where RUN_DIR=${DOCKER_RUN_DIR:-$(pwd)} @@ -101,4 +104,6 @@ for arg in "$@"; do esac done -docker run "${runargs[@]}" --rm --mount "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" -w "$RUN_DIR" -v "$RUN_DIR:$RUN_DIR" "$ORG/$IMAGE${VERSION:+:${VERSION}}" "$@" +docker pull "$FULL_IMAGE_NAME" || "$here"/build.sh + +docker run "${runargs[@]}" --rm --mount "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" -w "$RUN_DIR" -v "$RUN_DIR:$RUN_DIR" "$FULL_IMAGE_NAME" "$@" diff --git a/scripts/tests/happy_tests.sh b/scripts/tests/happy_tests.sh index 41034b538868fe..283515cb17d7c3 100755 --- a/scripts/tests/happy_tests.sh +++ b/scripts/tests/happy_tests.sh @@ -33,8 +33,9 @@ function happytest_install_packages() { echo "install_packages should be invoked under root" return 1 fi - echo "Install packages: bridge-utils iproute2 net-tools python3-lockfile python3-pip python3-psutil python3-setuptools strace" - apt-get update && apt-get install -y bridge-utils \ + echo "Install packages: avahi-daemon bridge-utils iproute2 net-tools python3-lockfile python3-pip python3-psutil python3-setuptools strace" + apt-get update && apt-get install -y avahi-daemon \ + bridge-utils \ iproute2 \ net-tools \ python3-lockfile \ @@ -47,6 +48,8 @@ function happytest_install_packages() { function happytest_bootstrap() { echo "Bootstrapping Happy Test" set -e + service dbus start + service avahi-daemon start # Bootstrap Happy cd "$REPO_DIR/third_party/happy/repo" diff --git a/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.cpp b/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.cpp index 6ce8e34f272a26..de7512063a3de3 100644 --- a/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.cpp +++ b/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.cpp @@ -30,6 +30,7 @@ // Include the non-inline definitions for the GenericPlatformManagerImpl<> template, // from which the GenericPlatformManagerImpl_POSIX<> template inherits. +#include #include #include @@ -138,6 +139,9 @@ void GenericPlatformManagerImpl_POSIX::SysUpdate() InetLayer.PrepareSelect(mMaxFd, &mReadSet, &mWriteSet, &mErrorSet, mNextTimeout); } #endif // !(CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK) +#if CHIP_ENABLE_MDNS + chip::Protocols::Mdns::UpdateMdnsDataset(mReadSet, mWriteSet, mErrorSet, mMaxFd, mNextTimeout); +#endif } template @@ -173,6 +177,9 @@ void GenericPlatformManagerImpl_POSIX::SysProcess() #endif // !(CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK) ProcessDeviceEvents(); +#if CHIP_ENABLE_MDNS + chip::Protocols::Mdns::ProcessMdns(mReadSet, mWriteSet, mErrorSet); +#endif } template diff --git a/src/lib/core/CHIPError.cpp b/src/lib/core/CHIPError.cpp index 3e3578fdb46945..745652dc5fb05e 100644 --- a/src/lib/core/CHIPError.cpp +++ b/src/lib/core/CHIPError.cpp @@ -591,6 +591,9 @@ bool FormatCHIPError(char * buf, uint16_t bufSize, int32_t err) case CHIP_ERROR_UNSUPPORTED_WIRELESS_OPERATING_LOCATION: desc = "Unsupported wireless operating location"; break; + case CHIP_ERROR_MDNS_COLLISSION: + desc = "mDNS collission"; + break; } #endif // !CHIP_CONFIG_SHORT_ERROR_STR diff --git a/src/lib/core/CHIPError.h b/src/lib/core/CHIPError.h index 74501626b4be94..4c946cb557ba52 100644 --- a/src/lib/core/CHIPError.h +++ b/src/lib/core/CHIPError.h @@ -1690,6 +1690,15 @@ typedef CHIP_CONFIG_ERROR_TYPE CHIP_ERROR; */ #define CHIP_ERROR_UNSUPPORTED_WIRELESS_OPERATING_LOCATION _CHIP_ERROR(179) +/** + * @def CHIP_ERROR_MDNS_COLLISSION + * + * @brief + * The registered service name has collision on the LAN. + * + */ +#define CHIP_ERROR_MDNS_COLLISSION _CHIP_ERROR(180) + /** * @} */ diff --git a/src/lib/protocols/mdns/Mdns.h b/src/lib/protocols/mdns/Mdns.h new file mode 100644 index 00000000000000..6ec29c77148569 --- /dev/null +++ b/src/lib/protocols/mdns/Mdns.h @@ -0,0 +1,170 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * This file defines the platform API to publish and subscribe mDNS + * services. + * + * You can find the implementation in src/platform//MdnsImpl.cpp. + */ + +#pragma once + +#include + +#include "core/CHIPError.h" +#include "inet/IPAddress.h" +#include "inet/InetInterface.h" +#include "lib/core/Optional.h" + +namespace chip { +namespace Protocols { +namespace Mdns { + +static constexpr uint8_t kMdnsNameMaxSize = 32; +static constexpr uint8_t kMdnsTypeMaxSize = 32; +static constexpr uint16_t kMdnsTextMaxSize = 64; + +enum class MdnsServiceProtocol : uint8_t +{ + kMdnsProtocolUdp = 0, + kMdnsProtocolTcp, + kMdnsProtocolUnknown = 255, +}; + +struct TextEntry +{ + const char * mKey; + const uint8_t * mData; + size_t mDataSize; +}; + +struct MdnsService +{ + char mName[kMdnsNameMaxSize + 1]; + char mType[kMdnsTypeMaxSize + 1]; + MdnsServiceProtocol mProtocol; + uint16_t mPort; + chip::Inet::InterfaceId interface; + TextEntry * mTextEntryies; + size_t mTextEntrySize; + Optional mAddress; +}; + +/** + * The callback function for mDNS resolve. + * + * The callback function SHALL NOT take the ownership of the result->mService.mTextEntries + * memory. + * + * @param[in] context The context passed to ChipMdnsBrowse or ChipMdnsResolve. + * @param[in] result The mdns resolve result, can be nullptr if error happens. + * @param[in] error The error code. + * + */ +using MdnsResolveCallback = void (*)(void * context, MdnsService * result, CHIP_ERROR error); + +/** + * The callback function for mDNS browse. + * + * The callback function SHALL NOT take the ownership of the service->mTextEntries + * memory. + * + * @param[in] context The context passed to ChipMdnsBrowse or ChipMdnsResolve. + * @param[in] services The service list, can be nullptr. + * @param[in] serciesSize The size of the service list. + * @param[in] error The error code. + * + */ +using MdnsBrowseCallback = void (*)(void * context, MdnsService * services, size_t servicesSize, CHIP_ERROR error); + +using MdnsAsnycReturnCallback = void (*)(void * context, CHIP_ERROR error); + +/** + * This function intializes the mdns module + * + * @param[in] initCallback The callback for notifying the initialization result. + * @param[in] errorCallback The callback for notifying internal errors. + * @param[in] context The context passed to the callbacks. + * + * @retval CHIP_NO_ERROR The initialization succeeds. + * @retval Error code The initialization fails + * + */ +CHIP_ERROR ChipMdnsInit(MdnsAsnycReturnCallback initCallback, MdnsAsnycReturnCallback errorCallback, void * context); + +/** + * This function publishes an service via mDNS. + * + * Calling the function again with the same service name, type, protocol, + * interface and port but different text will update the text published. + * This function will NOT take the ownership of service->mTextEntries memory. + * + * @param[in] service The service entry. + * + * @retval CHIP_NO_ERROR The publish succeeds. + * @retval CHIP_ERROR_INVALID_ARGUMENT The service is nullptr. + * @retval Error code The publish fails. + * + */ +CHIP_ERROR ChipMdnsPublishService(const MdnsService * service); + +/** + * This function stops publishing service via mDNS. + * + * @retval CHIP_NO_ERROR The publish stops successfully. + * @retval Error code Stopping the publish fails. + * + */ +CHIP_ERROR ChipMdnsStopPublish(); + +/** + * This function browses the services published by mdns + * + * @param[in] type The service type. + * @param[in] protocol The service protocol. + * @param[in] interface The interface to send queries. + * @param[in] callback The callback for found services. + * @param[in] context The user context. + * + * @retval CHIP_NO_ERROR The browse succeeds. + * @retval CHIP_ERROR_INVALID_ARGUMENT The type or callback is nullptr. + * @retval Error code The browse fails. + * + */ +CHIP_ERROR ChipMdnsBrowse(const char * type, MdnsServiceProtocol protocol, chip::Inet::InterfaceId interface, + MdnsBrowseCallback callback, void * context); + +/** + * This function resolves the services published by mdns + * + * @param[in] browseResult The service entry returned by @ref ChipMdnsBrowse + * @param[in] callback The callback for found services. + * @param[in] context The user context. + * + * @retval CHIP_NO_ERROR The resolve succeeds. + * @retval CHIP_ERROR_INVALID_ARGUMENT The name, type or callback is nullptr. + * @retval Error code The resolve fails. + * + */ +CHIP_ERROR ChipMdnsResolve(MdnsService * browseResult, chip::Inet::InterfaceId interface, MdnsResolveCallback callback, + void * context); + +} // namespace Mdns +} // namespace Protocols +} // namespace chip diff --git a/src/platform/BUILD.gn b/src/platform/BUILD.gn index ff188bdf46362a..f2f53ae367f091 100644 --- a/src/platform/BUILD.gn +++ b/src/platform/BUILD.gn @@ -29,6 +29,12 @@ if (chip_device_platform == "nrf5") { import("//build_overrides/nrf5_sdk.gni") } +if (chip_device_platform == "linux" && chip_enable_mdns) { + pkg_config("avahi_client_config") { + packages = [ "avahi-client" ] + } +} + if (chip_device_platform != "none") { declare_args() { # Extra header to include in CHIPDeviceConfig.h for project. @@ -51,6 +57,7 @@ if (chip_device_platform != "none") { defines = [ "CHIP_DEVICE_CONFIG_ENABLE_WPA=${chip_device_config_enable_wpa}", "CHIP_ENABLE_OPENTHREAD=${chip_enable_openthread}", + "CHIP_ENABLE_MDNS=${chip_enable_mdns}", "CHIP_WITH_GIO=${chip_with_gio}", "OPENTHREAD_CONFIG_ENABLE_TOBLE=false", ] @@ -326,6 +333,15 @@ if (chip_device_platform != "none" && chip_device_platform != "external") { "Linux/SystemTimeSupport.cpp", ] + if (chip_enable_mdns) { + sources += [ + "Linux/MdnsImpl.cpp", + "Linux/MdnsImpl.h", + ] + + public_configs += [ ":avahi_client_config" ] + } + if (chip_enable_openthread) { sources += [ "Linux/ThreadStackManagerImpl.cpp", diff --git a/src/platform/Linux/MdnsImpl.cpp b/src/platform/Linux/MdnsImpl.cpp new file mode 100644 index 00000000000000..0d0c144f2ac1c1 --- /dev/null +++ b/src/platform/Linux/MdnsImpl.cpp @@ -0,0 +1,693 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MdnsImpl.h" + +#include +#include +#include +#include +#include + +#include + +#include "support/CHIPMem.h" +#include "support/CodeUtils.h" + +using chip::Protocols::Mdns::kMdnsTypeMaxSize; +using chip::Protocols::Mdns::MdnsServiceProtocol; +using chip::Protocols::Mdns::TextEntry; +using std::chrono::duration_cast; +using std::chrono::microseconds; +using std::chrono::seconds; +using std::chrono::steady_clock; + +namespace { + +CHIP_ERROR MakeAvahiStringListFromTextEntries(TextEntry * entries, size_t size, AvahiStringList ** strListOut) +{ + *strListOut = avahi_string_list_new(nullptr, nullptr); + + for (size_t i = 0; i < size; i++) + { + uint8_t buf[kMdnsTypeMaxSize]; + size_t offset = static_cast(snprintf(reinterpret_cast(buf), sizeof(buf), "%s=", entries[i].mKey)); + + if (offset + entries[i].mDataSize > sizeof(buf)) + { + avahi_string_list_free(*strListOut); + *strListOut = nullptr; + return CHIP_ERROR_INVALID_ARGUMENT; + } + + memcpy(&buf[offset], entries[i].mData, entries[i].mDataSize); + *strListOut = avahi_string_list_add_arbitrary(*strListOut, buf, offset + entries[i].mDataSize); + } + return CHIP_NO_ERROR; +} + +const char * GetProtocolString(MdnsServiceProtocol protocol) +{ + return protocol == MdnsServiceProtocol::kMdnsProtocolUdp ? "_udp" : "_tcp"; +} + +std::string GetFullType(const char * type, MdnsServiceProtocol protocol) +{ + std::ostringstream typeBuilder; + typeBuilder << type << "." << GetProtocolString(protocol); + return typeBuilder.str(); +} + +} // namespace + +namespace chip { +namespace Protocols { +namespace Mdns { + +MdnsAvahi MdnsAvahi::sInstance; + +constexpr uint64_t kUsPerSec = 1000 * 1000; + +Poller::Poller() +{ + mAvahiPoller.userdata = this; + mAvahiPoller.watch_new = WatchNew; + mAvahiPoller.watch_update = WatchUpdate; + mAvahiPoller.watch_get_events = WatchGetEvents; + mAvahiPoller.watch_free = WatchFree; + + mAvahiPoller.timeout_new = TimeoutNew; + mAvahiPoller.timeout_update = TimeoutUpdate; + mAvahiPoller.timeout_free = TimeoutFree; +} + +AvahiWatch * Poller::WatchNew(const struct AvahiPoll * poller, int fd, AvahiWatchEvent event, AvahiWatchCallback callback, + void * context) +{ + return reinterpret_cast(poller->userdata)->WatchNew(fd, event, callback, context); +} + +AvahiWatch * Poller::WatchNew(int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void * context) +{ + VerifyOrDie(callback != nullptr && fd >= 0); + + mWatches.emplace_back(new AvahiWatch{ fd, event, 0, callback, context, this }); + + return mWatches.back().get(); +} + +void Poller::WatchUpdate(AvahiWatch * watch, AvahiWatchEvent event) +{ + watch->mWatchEvents = event; +} + +AvahiWatchEvent Poller::WatchGetEvents(AvahiWatch * watch) +{ + return static_cast(watch->mHappenedEvents); +} + +void Poller::WatchFree(AvahiWatch * watch) +{ + reinterpret_cast(watch->mPoller)->WatchFree(*watch); +} + +void Poller::WatchFree(AvahiWatch & watch) +{ + mWatches.erase(std::remove_if(mWatches.begin(), mWatches.end(), + [&watch](const std::unique_ptr & aValue) { return aValue.get() == &watch; }), + mWatches.end()); +} + +AvahiTimeout * Poller::TimeoutNew(const AvahiPoll * poller, const struct timeval * timeout, AvahiTimeoutCallback callback, + void * context) +{ + VerifyOrDie(poller != nullptr && callback != nullptr); + + return static_cast(poller->userdata)->TimeoutNew(timeout, callback, context); +} + +steady_clock::time_point GetAbsTimeout(const struct timeval * timeout) +{ + steady_clock::time_point now = steady_clock::now(); + steady_clock::time_point absTimeout = now; + + if (timeout != nullptr) + { + absTimeout += seconds(timeout->tv_sec); + absTimeout += microseconds(timeout->tv_usec); + } + + return absTimeout; +} + +AvahiTimeout * Poller::TimeoutNew(const struct timeval * timeout, AvahiTimeoutCallback callback, void * context) +{ + + mTimers.emplace_back(new AvahiTimeout{ GetAbsTimeout(timeout), callback, timeout != nullptr, context, this }); + return mTimers.back().get(); +} + +void Poller::TimeoutUpdate(AvahiTimeout * timer, const struct timeval * timeout) +{ + if (timeout) + { + timer->mAbsTimeout = GetAbsTimeout(timeout); + timer->mEnabled = true; + } + else + { + timer->mEnabled = false; + } +} + +void Poller::TimeoutFree(AvahiTimeout * timer) +{ + static_cast(timer->mPoller)->TimeoutFree(*timer); +} + +void Poller::TimeoutFree(AvahiTimeout & timer) +{ + mTimers.erase(std::remove_if(mTimers.begin(), mTimers.end(), + [&timer](const std::unique_ptr & aValue) { return aValue.get() == &timer; }), + mTimers.end()); +} + +void Poller::UpdateFdSet(fd_set & readFdSet, fd_set & writeFdSet, fd_set & errorFdSet, int & aMaxFd, timeval & timeout) +{ + microseconds timeoutVal = seconds(timeout.tv_sec) + microseconds(timeout.tv_usec); + + for (auto && watch : mWatches) + { + int fd = watch->mFd; + AvahiWatchEvent events = watch->mWatchEvents; + + if (AVAHI_WATCH_IN & events) + { + FD_SET(fd, &readFdSet); + } + + if (AVAHI_WATCH_OUT & events) + { + FD_SET(fd, &writeFdSet); + } + + if (AVAHI_WATCH_ERR & events) + { + FD_SET(fd, &errorFdSet); + } + + if (aMaxFd < fd) + { + aMaxFd = fd; + } + + watch->mHappenedEvents = 0; + } + + for (auto && timer : mTimers) + { + steady_clock::time_point absTimeout = timer->mAbsTimeout; + steady_clock::time_point now = steady_clock::now(); + + if (!timer->mEnabled) + { + continue; + } + if (absTimeout < now) + { + timeoutVal = microseconds(0); + break; + } + else + { + timeoutVal = std::min(timeoutVal, duration_cast(absTimeout - now)); + } + } + + timeout.tv_sec = static_cast(timeoutVal.count()) / kUsPerSec; + timeout.tv_usec = static_cast(timeoutVal.count()) % kUsPerSec; +} + +void Poller::Process(const fd_set & readFdSet, const fd_set & writeFdSet, const fd_set & errorFdSet) +{ + steady_clock::time_point now = steady_clock::now(); + + for (auto && watch : mWatches) + { + int fd = watch->mFd; + AvahiWatchEvent events = watch->mWatchEvents; + + watch->mHappenedEvents = 0; + + if ((AVAHI_WATCH_IN & events) && FD_ISSET(fd, &readFdSet)) + { + watch->mHappenedEvents |= AVAHI_WATCH_IN; + } + + if ((AVAHI_WATCH_OUT & events) && FD_ISSET(fd, &writeFdSet)) + { + watch->mHappenedEvents |= AVAHI_WATCH_OUT; + } + + if ((AVAHI_WATCH_ERR & events) && FD_ISSET(fd, &errorFdSet)) + { + watch->mHappenedEvents |= AVAHI_WATCH_ERR; + } + + if (watch->mHappenedEvents) + { + watch->mCallback(watch.get(), watch->mFd, static_cast(watch->mHappenedEvents), watch->mContext); + } + } + + for (auto && timer : mTimers) + { + if (!timer->mEnabled) + { + continue; + } + if (timer->mAbsTimeout <= now) + { + timer->mCallback(timer.get(), timer->mContext); + } + } +} + +CHIP_ERROR MdnsAvahi::Init(MdnsAsnycReturnCallback initCallback, MdnsAsnycReturnCallback errorCallback, void * context) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + int avahiError = 0; + + VerifyOrExit(initCallback != nullptr, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(errorCallback != nullptr, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(mClient == nullptr && mGroup == nullptr, error = CHIP_ERROR_INCORRECT_STATE); + mInitCallback = initCallback; + mErrorCallback = errorCallback; + mAsyncReturnContext = context; + mClient = avahi_client_new(mPoller.GetAvahiPoll(), AVAHI_CLIENT_NO_FAIL, HandleClientState, this, &avahiError); + VerifyOrExit(mClient != nullptr, error = CHIP_ERROR_OPEN_FAILED); + VerifyOrExit(avahiError == 0, error = CHIP_ERROR_OPEN_FAILED); + +exit: + return error; +} + +void MdnsAvahi::HandleClientState(AvahiClient * client, AvahiClientState state, void * context) +{ + static_cast(context)->HandleClientState(client, state); +} + +void MdnsAvahi::HandleClientState(AvahiClient * client, AvahiClientState state) +{ + switch (state) + { + case AVAHI_CLIENT_S_RUNNING: + ChipLogProgress(DeviceLayer, "Avahi client registered"); + mClient = client; + mGroup = avahi_entry_group_new(client, HandleGroupState, this); + if (mGroup == nullptr) + { + ChipLogError(DeviceLayer, "Failed to create avahi group: %s", avahi_strerror(avahi_client_errno(client))); + mInitCallback(mAsyncReturnContext, CHIP_ERROR_OPEN_FAILED); + } + else + { + mInitCallback(mAsyncReturnContext, CHIP_NO_ERROR); + } + break; + case AVAHI_CLIENT_FAILURE: + ChipLogError(DeviceLayer, "Avahi client failure"); + mErrorCallback(mAsyncReturnContext, CHIP_ERROR_INTERNAL); + break; + case AVAHI_CLIENT_S_COLLISION: + case AVAHI_CLIENT_S_REGISTERING: + ChipLogProgress(DeviceLayer, "Avahi re-register required"); + mErrorCallback(mAsyncReturnContext, CHIP_ERROR_MDNS_COLLISSION); + if (mGroup != nullptr) + { + avahi_entry_group_reset(mGroup); + } + break; + case AVAHI_CLIENT_CONNECTING: + ChipLogProgress(DeviceLayer, "Avahi connecting"); + break; + } +} + +void MdnsAvahi::HandleGroupState(AvahiEntryGroup * group, AvahiEntryGroupState state, void * context) +{ + static_cast(context)->HandleGroupState(group, state); +} + +void MdnsAvahi::HandleGroupState(AvahiEntryGroup * group, AvahiEntryGroupState state) +{ + switch (state) + { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + ChipLogProgress(DeviceLayer, "Avahi group established"); + break; + case AVAHI_ENTRY_GROUP_COLLISION: + ChipLogError(DeviceLayer, "Avahi group collission"); + mErrorCallback(mAsyncReturnContext, CHIP_ERROR_MDNS_COLLISSION); + break; + case AVAHI_ENTRY_GROUP_FAILURE: + ChipLogError(DeviceLayer, "Avahi group internal failure %s", + avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(mGroup)))); + mErrorCallback(mAsyncReturnContext, CHIP_ERROR_INTERNAL); + break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + break; + } +} + +CHIP_ERROR MdnsAvahi::PublishService(const MdnsService & service) +{ + std::ostringstream keyBuilder; + std::string key; + std::string type = GetFullType(service.mType, service.mProtocol); + CHIP_ERROR error = CHIP_NO_ERROR; + AvahiStringList * text = nullptr; + AvahiIfIndex interface = + service.interface == INET_NULL_INTERFACEID ? AVAHI_IF_UNSPEC : static_cast(service.interface); + + keyBuilder << service.mName << "." << type << service.mPort << "." << interface; + key = keyBuilder.str(); + if (mPublishedServices.find(key) == mPublishedServices.end()) + { + SuccessOrExit(error = MakeAvahiStringListFromTextEntries(service.mTextEntryies, service.mTextEntrySize, &text)); + + mPublishedServices.emplace(key); + VerifyOrExit(avahi_entry_group_add_service_strlst(mGroup, interface, AVAHI_PROTO_UNSPEC, static_cast(0), + service.mName, type.c_str(), nullptr, nullptr, service.mPort, text) == 0, + error = CHIP_ERROR_INTERNAL); + } + else + { + SuccessOrExit(error = MakeAvahiStringListFromTextEntries(service.mTextEntryies, service.mTextEntrySize, &text)); + + VerifyOrExit(avahi_entry_group_update_service_txt_strlst(mGroup, interface, AVAHI_PROTO_UNSPEC, + static_cast(0), service.mName, type.c_str(), + nullptr, text) == 0, + error = CHIP_ERROR_INTERNAL); + } + VerifyOrExit(avahi_entry_group_commit(mGroup) == 0, error = CHIP_ERROR_INTERNAL); + +exit: + if (text != nullptr) + { + avahi_string_list_free(text); + } + if (error != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Avahi publish service failed: %d", static_cast(error)); + } + + return error; +} + +CHIP_ERROR MdnsAvahi::StopPublish() +{ + CHIP_ERROR error = CHIP_NO_ERROR; + + VerifyOrExit(avahi_entry_group_reset(mGroup) == 0, error = CHIP_ERROR_INTERNAL); + VerifyOrExit(avahi_entry_group_commit(mGroup) == 0, error = CHIP_ERROR_INTERNAL); +exit: + return error; +} + +CHIP_ERROR MdnsAvahi::Browse(const char * type, MdnsServiceProtocol protocol, chip::Inet::InterfaceId interface, + MdnsBrowseCallback callback, void * context) +{ + AvahiServiceBrowser * browser; + BrowseContext * browseContext = static_cast(chip::Platform::MemoryAlloc(sizeof(BrowseContext))); + AvahiIfIndex avahiInterface = static_cast(interface); + + browseContext->mInstance = this; + browseContext->mContext = context; + browseContext->mCallback = callback; + if (interface == INET_NULL_INTERFACEID) + { + avahiInterface = AVAHI_IF_UNSPEC; + } + browser = avahi_service_browser_new(mClient, avahiInterface, AVAHI_PROTO_UNSPEC, GetFullType(type, protocol).c_str(), nullptr, + static_cast(0), HandleBrowse, browseContext); + // Otherwise the browser will be freed in the callback + if (browser == nullptr) + { + chip::Platform::MemoryFree(browseContext); + } + + return browser == nullptr ? CHIP_ERROR_INTERNAL : CHIP_NO_ERROR; +} + +MdnsServiceProtocol TruncateProtocolInType(char * type) +{ + char * deliminator = strrchr(type, '.'); + MdnsServiceProtocol protocol = MdnsServiceProtocol::kMdnsProtocolUnknown; + + if (deliminator != NULL) + { + if (strcmp("._tcp", deliminator) == 0) + { + protocol = MdnsServiceProtocol::kMdnsProtocolTcp; + *deliminator = 0; + } + else if (strcmp("._udp", deliminator) == 0) + { + protocol = MdnsServiceProtocol::kMdnsProtocolUdp; + *deliminator = 0; + } + } + return protocol; +} + +void MdnsAvahi::HandleBrowse(AvahiServiceBrowser * browser, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, + const char * name, const char * type, const char * domain, AvahiLookupResultFlags /*flags*/, + void * userdata) +{ + BrowseContext * context = static_cast(userdata); + + switch (event) + { + case AVAHI_BROWSER_FAILURE: + context->mCallback(context->mContext, nullptr, 0, CHIP_ERROR_INTERNAL); + avahi_service_browser_free(browser); + chip::Platform::MemoryFree(context); + break; + case AVAHI_BROWSER_NEW: + ChipLogProgress(DeviceLayer, "Avahi browse: cache new"); + if (strcmp("local", domain) == 0) + { + MdnsService service; + + strncpy(service.mName, name, sizeof(service.mName)); + strncpy(service.mType, type, sizeof(service.mType)); + service.mName[kMdnsNameMaxSize] = 0; + service.mType[kMdnsTypeMaxSize] = 0; + service.mProtocol = TruncateProtocolInType(service.mType); + context->mServices.push_back(service); + } + break; + case AVAHI_BROWSER_ALL_FOR_NOW: + ChipLogProgress(DeviceLayer, "Avahi browse: all for now"); + context->mCallback(context->mContext, context->mServices.data(), context->mServices.size(), CHIP_NO_ERROR); + avahi_service_browser_free(browser); + chip::Platform::MemoryFree(context); + break; + case AVAHI_BROWSER_REMOVE: + ChipLogProgress(DeviceLayer, "Avahi browse: remove"); + if (strcmp("local", domain) == 0) + { + std::remove_if(context->mServices.begin(), context->mServices.end(), [name, type](const MdnsService service) { + return strcmp(name, service.mName) == 0 && type == GetFullType(service.mType, service.mProtocol); + }); + } + break; + case AVAHI_BROWSER_CACHE_EXHAUSTED: + ChipLogProgress(DeviceLayer, "Avahi browse: cache exhausted"); + break; + } +} + +CHIP_ERROR MdnsAvahi::Resolve(const char * name, const char * type, MdnsServiceProtocol protocol, chip::Inet::InterfaceId interface, + MdnsResolveCallback callback, void * context) +{ + AvahiServiceResolver * resolver; + AvahiIfIndex avahiInterface = static_cast(interface); + ResolveContext * resolveContext = static_cast(chip::Platform::MemoryAlloc(sizeof(ResolveContext))); + CHIP_ERROR error = CHIP_NO_ERROR; + + resolveContext->mInstance = this; + resolveContext->mCallback = callback; + resolveContext->mContext = context; + if (interface == INET_NULL_INTERFACEID) + { + avahiInterface = AVAHI_IF_UNSPEC; + } + resolver = + avahi_service_resolver_new(mClient, avahiInterface, AVAHI_PROTO_UNSPEC, name, GetFullType(type, protocol).c_str(), nullptr, + AVAHI_PROTO_UNSPEC, static_cast(0), HandleResolve, resolveContext); + // Otherwise the resolver will be freed in the callback + if (resolver == nullptr) + { + error = CHIP_ERROR_INTERNAL; + chip::Platform::MemoryFree(resolver); + } + + return error; +} + +void MdnsAvahi::HandleResolve(AvahiServiceResolver * resolver, AvahiIfIndex interface, AvahiProtocol protocol, + AvahiResolverEvent event, const char * name, const char * type, const char * /*domain*/, + const char * /*host_name*/, const AvahiAddress * address, uint16_t port, AvahiStringList * txt, + AvahiLookupResultFlags flags, void * userdata) +{ + ResolveContext * context = reinterpret_cast(userdata); + std::vector textEntries; + + switch (event) + { + case AVAHI_RESOLVER_FAILURE: + ChipLogError(DeviceLayer, "Avahi resolve failed"); + context->mCallback(context->mContext, nullptr, CHIP_ERROR_INTERNAL); + break; + case AVAHI_RESOLVER_FOUND: + MdnsService result; + + result.mAddress.SetValue(chip::Inet::IPAddress()); + ChipLogError(DeviceLayer, "Avahi resolve found"); + strncpy(result.mName, name, sizeof(result.mName)); + strncpy(result.mType, type, sizeof(result.mType)); + result.mName[kMdnsNameMaxSize] = 0; + result.mType[kMdnsTypeMaxSize] = 0; + result.mProtocol = TruncateProtocolInType(result.mType); + result.mPort = port; + + if (address) + { + switch (address->proto) + { + case AVAHI_PROTO_INET: + struct in_addr addr4; + + memcpy(&addr4, &(address->data.ipv4), sizeof(addr4)); + result.mAddress.SetValue(chip::Inet::IPAddress::FromIPv4(addr4)); + break; + case AVAHI_PROTO_INET6: + struct in6_addr addr6; + + memcpy(&addr6, &(address->data.ipv6), sizeof(addr6)); + result.mAddress.SetValue(chip::Inet::IPAddress::FromIPv6(addr6)); + break; + default: + break; + } + } + + while (txt != nullptr) + { + for (size_t i = 0; i < txt->size; i++) + { + if (txt->text[i] == '=') + { + txt->text[i] = '\0'; + textEntries.push_back(TextEntry{ reinterpret_cast(txt->text), &txt->text[i + 1], txt->size - i - 1 }); + break; + } + } + txt = txt->next; + } + + if (!textEntries.empty()) + { + result.mTextEntryies = textEntries.data(); + } + result.mTextEntrySize = textEntries.size(); + + context->mCallback(context->mContext, &result, CHIP_NO_ERROR); + break; + } + + avahi_service_resolver_free(resolver); + chip::Platform::MemoryFree(context); +} + +MdnsAvahi::~MdnsAvahi() +{ + if (mGroup) + { + avahi_entry_group_free(mGroup); + } + if (mClient) + { + avahi_client_free(mClient); + } +} + +void UpdateMdnsDataset(fd_set & readFdSet, fd_set & writeFdSet, fd_set & errorFdSet, int & maxFd, timeval & timeout) +{ + MdnsAvahi::GetInstance().GetPoller().UpdateFdSet(readFdSet, writeFdSet, errorFdSet, maxFd, timeout); +} + +void ProcessMdns(fd_set & readFdSet, fd_set & writeFdSet, fd_set & errorFdSet) +{ + MdnsAvahi::GetInstance().GetPoller().Process(readFdSet, writeFdSet, errorFdSet); +} + +CHIP_ERROR ChipMdnsInit(MdnsAsnycReturnCallback initCallback, MdnsAsnycReturnCallback errorCallback, void * context) +{ + return MdnsAvahi::GetInstance().Init(initCallback, errorCallback, context); +} + +CHIP_ERROR ChipMdnsPublishService(const MdnsService * service) +{ + return MdnsAvahi::GetInstance().PublishService(*service); +} + +CHIP_ERROR ChipMdnsStopPublish() +{ + return MdnsAvahi::GetInstance().StopPublish(); +} + +CHIP_ERROR ChipMdnsBrowse(const char * type, MdnsServiceProtocol protocol, chip::Inet::InterfaceId interface, + MdnsBrowseCallback callback, void * context) +{ + return MdnsAvahi::GetInstance().Browse(type, protocol, interface, callback, context); +} + +CHIP_ERROR ChipMdnsResolve(MdnsService * browseResult, chip::Inet::InterfaceId interface, MdnsResolveCallback callback, + void * context) + +{ + CHIP_ERROR error; + + if (browseResult != nullptr) + { + error = MdnsAvahi::GetInstance().Resolve(browseResult->mName, browseResult->mType, browseResult->mProtocol, interface, + callback, context); + } + else + { + error = CHIP_ERROR_INVALID_ARGUMENT; + } + return error; +} + +} // namespace Mdns +} // namespace Protocols +} // namespace chip diff --git a/src/platform/Linux/MdnsImpl.h b/src/platform/Linux/MdnsImpl.h new file mode 100644 index 00000000000000..fa36cc797fbf91 --- /dev/null +++ b/src/platform/Linux/MdnsImpl.h @@ -0,0 +1,167 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "lib/protocols/mdns/Mdns.h" + +struct AvahiWatch +{ + int mFd; ///< The file descriptor to watch. + AvahiWatchEvent mWatchEvents; ///< The interested events. + int mHappenedEvents; ///< The events happened. + AvahiWatchCallback mCallback; ///< The function to be called when interested events happened on mFd. + void * mContext; ///< A pointer to application-specific context. + void * mPoller; ///< The poller created this watch. +}; + +struct AvahiTimeout +{ + std::chrono::steady_clock::time_point mAbsTimeout; ///< Absolute time when this timer timeout. + AvahiTimeoutCallback mCallback; ///< The function to be called when timeout. + bool mEnabled; ///< Whether the timeout is enabled. + void * mContext; ///< The pointer to application-specific context. + void * mPoller; ///< The poller created this timer. +}; + +namespace chip { +namespace Protocols { +namespace Mdns { + +class Poller +{ +public: + Poller(void); + + void UpdateFdSet(fd_set & readFdSet, fd_set & writeFdSet, fd_set & errorFdSet, int & maxFd, timeval & timeout); + + void Process(const fd_set & readFdSet, const fd_set & writeFdSet, const fd_set & errorFdSet); + + const AvahiPoll * GetAvahiPoll(void) const { return &mAvahiPoller; } + +private: + static AvahiWatch * WatchNew(const struct AvahiPoll * poller, int fd, AvahiWatchEvent event, AvahiWatchCallback callback, + void * context); + AvahiWatch * WatchNew(int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void * context); + + static void WatchUpdate(AvahiWatch * watch, AvahiWatchEvent event); + + static AvahiWatchEvent WatchGetEvents(AvahiWatch * watch); + + static void WatchFree(AvahiWatch * watch); + void WatchFree(AvahiWatch & watch); + + static AvahiTimeout * TimeoutNew(const AvahiPoll * poller, const struct timeval * timeout, AvahiTimeoutCallback callback, + void * context); + AvahiTimeout * TimeoutNew(const struct timeval * timeout, AvahiTimeoutCallback callback, void * context); + + static void TimeoutUpdate(AvahiTimeout * timer, const struct timeval * timeout); + + static void TimeoutFree(AvahiTimeout * timer); + void TimeoutFree(AvahiTimeout & timer); + + std::vector> mWatches; + std::vector> mTimers; + AvahiPoll mAvahiPoller; +}; + +class MdnsAvahi +{ +public: + MdnsAvahi(const MdnsAvahi &) = delete; + MdnsAvahi & operator=(const MdnsAvahi &) = delete; + + CHIP_ERROR Init(MdnsAsnycReturnCallback initCallback, MdnsAsnycReturnCallback errorCallback, void * context); + CHIP_ERROR PublishService(const MdnsService & service); + CHIP_ERROR StopPublish(); + CHIP_ERROR Browse(const char * type, MdnsServiceProtocol protocol, chip::Inet::InterfaceId interface, + MdnsBrowseCallback callback, void * context); + CHIP_ERROR Resolve(const char * name, const char * type, MdnsServiceProtocol protocol, chip::Inet::InterfaceId interface, + MdnsResolveCallback callback, void * context); + + Poller & GetPoller() { return mPoller; } + + static MdnsAvahi & GetInstance() { return sInstance; } + + ~MdnsAvahi(); + +private: + struct BrowseContext + { + MdnsAvahi * mInstance; + MdnsBrowseCallback mCallback; + void * mContext; + std::vector mServices; + }; + + struct ResolveContext + { + MdnsAvahi * mInstance; + MdnsResolveCallback mCallback; + void * mContext; + }; + + MdnsAvahi() : mClient(nullptr), mGroup(nullptr) {} + static MdnsAvahi sInstance; + + static void HandleClientState(AvahiClient * client, AvahiClientState state, void * context); + void HandleClientState(AvahiClient * client, AvahiClientState state); + + static void HandleGroupState(AvahiEntryGroup * group, AvahiEntryGroupState state, void * context); + void HandleGroupState(AvahiEntryGroup * group, AvahiEntryGroupState state); + + static void HandleBrowse(AvahiServiceBrowser * broswer, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, + const char * name, const char * type, const char * domain, AvahiLookupResultFlags flags, + void * userdata); + static void HandleResolve(AvahiServiceResolver * resolver, AvahiIfIndex interface, AvahiProtocol protocol, + AvahiResolverEvent event, const char * name, const char * type, const char * domain, + const char * host_name, const AvahiAddress * address, uint16_t port, AvahiStringList * txt, + AvahiLookupResultFlags flags, void * userdata); + + MdnsAsnycReturnCallback mInitCallback; + MdnsAsnycReturnCallback mErrorCallback; + void * mAsyncReturnContext; + + std::set mPublishedServices; + AvahiClient * mClient; + AvahiEntryGroup * mGroup; + Poller mPoller; +}; + +void UpdateMdnsDataset(fd_set & readFdSet, fd_set & writeFdSet, fd_set & errorFdSet, int & maxFd, timeval & timeout); + +void ProcessMdns(fd_set & readFdSet, fd_set & writeFdSet, fd_set & errorFdSet); + +} // namespace Mdns +} // namespace Protocols +} // namespace chip diff --git a/src/platform/Linux/PlatformManagerImpl.cpp b/src/platform/Linux/PlatformManagerImpl.cpp index 8cba6a74b4e1df..2b39b8f2e142d6 100644 --- a/src/platform/Linux/PlatformManagerImpl.cpp +++ b/src/platform/Linux/PlatformManagerImpl.cpp @@ -29,6 +29,8 @@ #include +#include "MdnsImpl.h" + namespace chip { namespace DeviceLayer { @@ -60,7 +62,6 @@ CHIP_ERROR PlatformManagerImpl::_InitChipStack() // Initialize the configuration system. err = Internal::PosixConfig::Init(); SuccessOrExit(err); - // Call _InitChipStack() on the generic implementation base class // to finish the initialization process. err = Internal::GenericPlatformManagerImpl_POSIX::_InitChipStack(); diff --git a/src/platform/device.gni b/src/platform/device.gni index 381575b3c7b797..28e5702ace8f67 100644 --- a/src/platform/device.gni +++ b/src/platform/device.gni @@ -42,6 +42,8 @@ declare_args() { # Enable ble support. chip_enable_ble = chip_device_platform == "linux" || chip_device_platform == "darwin" + + chip_enable_mdns = chip_device_platform == "linux" } _chip_device_layer = "none" diff --git a/src/platform/tests/BUILD.gn b/src/platform/tests/BUILD.gn index 1574aa2e1c6d4d..13106ca20bdd7d 100644 --- a/src/platform/tests/BUILD.gn +++ b/src/platform/tests/BUILD.gn @@ -32,6 +32,18 @@ if (chip_device_platform != "none") { "TestPlatformTime.h", ] + tests = [ "TestPlatformMgr" ] + + if (chip_enable_mdns && chip_enable_happy_tests && + chip_device_platform == "linux") { + sources += [ + "TestMdns.cpp", + "TestMdns.h", + ] + + tests += [ "TestMdns" ] + } + public_deps = [ "${chip_root}/src/lib/support", "${chip_root}/src/platform", @@ -39,8 +51,6 @@ if (chip_device_platform != "none") { "${nlunit_test_root}:nlunit-test", ] - tests = [ "TestPlatformMgr" ] - # These tests appear to be broken on Mac. if (current_os != "mac") { tests += [ diff --git a/src/platform/tests/TestMdns.cpp b/src/platform/tests/TestMdns.cpp new file mode 100644 index 00000000000000..5e710c9af07ab3 --- /dev/null +++ b/src/platform/tests/TestMdns.cpp @@ -0,0 +1,115 @@ +#include +#include +#include + +#include + +#include "lib/protocols/mdns/Mdns.h" +#include "platform/CHIPDeviceLayer.h" +#include "support/CHIPMem.h" + +using chip::Protocols::Mdns::MdnsService; +using chip::Protocols::Mdns::MdnsServiceProtocol; +using chip::Protocols::Mdns::TextEntry; + +static void HandleResolve(void * context, MdnsService * result, CHIP_ERROR error) +{ + char addrBuf[100]; + nlTestSuite * suite = static_cast(context); + + NL_TEST_ASSERT(suite, result != nullptr); + NL_TEST_ASSERT(suite, error == CHIP_NO_ERROR); + result->mAddress.Value().ToString(addrBuf, sizeof(addrBuf)); + printf("Service at [%s]:%u\n", addrBuf, result->mPort); + NL_TEST_ASSERT(suite, result->mTextEntrySize == 1); + NL_TEST_ASSERT(suite, strcmp(result->mTextEntryies[0].mKey, "key") == 0); + NL_TEST_ASSERT(suite, strcmp(reinterpret_cast(result->mTextEntryies[0].mData), "val") == 0); + + exit(0); +} + +static void HandleBrowse(void * context, MdnsService * services, size_t servicesSize, CHIP_ERROR error) +{ + nlTestSuite * suite = static_cast(context); + + NL_TEST_ASSERT(suite, error == CHIP_NO_ERROR); + if (services) + { + printf("Mdns service size %zu\n", servicesSize); + printf("Service name %s\n", services->mName); + printf("Service type %s\n", services->mType); + NL_TEST_ASSERT(suite, ChipMdnsResolve(services, INET_NULL_INTERFACEID, HandleResolve, suite) == CHIP_NO_ERROR); + } +} + +static void InitCallback(void * context, CHIP_ERROR error) +{ + MdnsService service; + TextEntry entry; + char key[] = "key"; + char val[] = "val"; + nlTestSuite * suite = static_cast(context); + + NL_TEST_ASSERT(suite, error == CHIP_NO_ERROR); + + service.interface = INET_NULL_INTERFACEID; + service.mPort = 80; + strcpy(service.mName, "test"); + strcpy(service.mType, "_mock"); + service.mProtocol = MdnsServiceProtocol::kMdnsProtocolTcp; + entry.mKey = key; + entry.mData = reinterpret_cast(val); + entry.mDataSize = strlen(reinterpret_cast(entry.mData)); + service.mTextEntryies = &entry; + service.mTextEntrySize = 1; + + NL_TEST_ASSERT(suite, ChipMdnsPublishService(&service) == CHIP_NO_ERROR); + ChipMdnsBrowse("_mock", MdnsServiceProtocol::kMdnsProtocolTcp, INET_NULL_INTERFACEID, HandleBrowse, suite); +} + +static void ErrorCallback(void * context, CHIP_ERROR error) +{ + if (error != CHIP_NO_ERROR) + { + fprintf(stderr, "Mdns error: %d\n", static_cast(error)); + abort(); + } +} + +void TestMdnsPubSub(nlTestSuite * inSuite, void * inContext) +{ + chip::Platform::MemoryInit(); + chip::DeviceLayer::PlatformMgr().InitChipStack(); + NL_TEST_ASSERT(inSuite, chip::Protocols::Mdns::ChipMdnsInit(InitCallback, ErrorCallback, inSuite) == CHIP_NO_ERROR); + + ChipLogProgress(DeviceLayer, "Start EventLoop"); + chip::DeviceLayer::PlatformMgr().RunEventLoop(); +} + +static const nlTest sTests[] = { NL_TEST_DEF("Test Mdns::PubSub", TestMdnsPubSub), NL_TEST_SENTINEL() }; + +int TestMdns() +{ + std::mutex mtx; + std::unique_lock lock(mtx); + std::condition_variable done; + int retVal = EXIT_FAILURE; + + std::thread t([&mtx, &done, &retVal]() { + { + std::lock_guard localLock(mtx); + nlTestSuite theSuite = { "CHIP DeviceLayer mdns tests", &sTests[0], nullptr, nullptr }; + + nlTestRunner(&theSuite, nullptr); + retVal = nlTestRunnerStats(&theSuite); + } + done.notify_all(); + }); + + if (done.wait_for(lock, std::chrono::seconds(5)) == std::cv_status::timeout) + { + fprintf(stderr, "mDNS test timeout, is avahi daemon running?"); + retVal = EXIT_FAILURE; + } + return retVal; +} diff --git a/src/platform/tests/TestMdns.h b/src/platform/tests/TestMdns.h new file mode 100644 index 00000000000000..f1169ec544208b --- /dev/null +++ b/src/platform/tests/TestMdns.h @@ -0,0 +1,20 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +int TestMdns(); diff --git a/src/platform/tests/TestMdnsDriver.cpp b/src/platform/tests/TestMdnsDriver.cpp new file mode 100644 index 00000000000000..07e973db851265 --- /dev/null +++ b/src/platform/tests/TestMdnsDriver.cpp @@ -0,0 +1,41 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "TestMdns.h" + +int main() +{ + std::mutex mtx; + std::unique_lock lock(mtx); + std::condition_variable done; + int retVal = -1; + + std::thread t([&done, &retVal]() { + retVal = TestMdns(); + done.notify_all(); + }); + + if (done.wait_for(lock, std::chrono::seconds(5)) == std::cv_status::timeout) + { + fprintf(stderr, "mDNS test timeout, is avahi daemon running?"); + } + return retVal; +}