| /* |
| * |
| * 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 <algorithm> |
| #include <sstream> |
| #include <string.h> |
| #include <time.h> |
| #include <vector> |
| |
| #include <netinet/in.h> |
| |
| #include <platform/internal/CHIPDeviceLayerInternal.h> |
| #include <support/CHIPMem.h> |
| #include <support/CHIPMemString.h> |
| #include <support/CodeUtils.h> |
| |
| using chip::Mdns::kMdnsTypeMaxSize; |
| using chip::Mdns::MdnsServiceProtocol; |
| using chip::Mdns::TextEntry; |
| using chip::System::SocketEvents; |
| using std::chrono::duration_cast; |
| using std::chrono::microseconds; |
| using std::chrono::seconds; |
| using std::chrono::steady_clock; |
| |
| namespace { |
| |
| AvahiProtocol ToAvahiProtocol(chip::Inet::IPAddressType addressType) |
| { |
| AvahiProtocol protocol; |
| |
| switch (addressType) |
| { |
| case chip::Inet::IPAddressType::kIPAddressType_IPv4: |
| protocol = AVAHI_PROTO_INET; |
| break; |
| case chip::Inet::IPAddressType::kIPAddressType_IPv6: |
| protocol = AVAHI_PROTO_INET6; |
| break; |
| default: |
| protocol = AVAHI_PROTO_UNSPEC; |
| break; |
| } |
| |
| return protocol; |
| } |
| |
| chip::Inet::IPAddressType ToAddressType(AvahiProtocol protocol) |
| { |
| chip::Inet::IPAddressType type; |
| |
| switch (protocol) |
| { |
| case AVAHI_PROTO_INET: |
| type = chip::Inet::IPAddressType::kIPAddressType_IPv4; |
| break; |
| case AVAHI_PROTO_INET6: |
| type = chip::Inet::IPAddressType::kIPAddressType_IPv6; |
| break; |
| default: |
| type = chip::Inet::IPAddressType::kIPAddressType_Unknown; |
| break; |
| } |
| |
| return type; |
| } |
| |
| AvahiWatchEvent ToAvahiWatchEvent(SocketEvents events) |
| { |
| return static_cast<AvahiWatchEvent>((events.Has(chip::System::SocketEventFlags::kRead) ? AVAHI_WATCH_IN : 0) | |
| (events.Has(chip::System::SocketEventFlags::kWrite) ? AVAHI_WATCH_OUT : 0) | |
| (events.Has(chip::System::SocketEventFlags::kError) ? AVAHI_WATCH_ERR : 0)); |
| } |
| |
| void AvahiWatchCallbackTrampoline(chip::System::WatchableSocket & socket) |
| { |
| AvahiWatch * const watch = reinterpret_cast<AvahiWatch *>(socket.GetCallbackData()); |
| watch->mCallback(watch, socket.GetFD(), ToAvahiWatchEvent(socket.GetPendingEvents()), watch->mContext); |
| } |
| |
| 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[chip::Mdns::kMdnsTextMaxSize]; |
| size_t offset = static_cast<size_t>(snprintf(reinterpret_cast<char *>(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 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; |
| |
| mWatchableEvents = &DeviceLayer::SystemLayer.WatchableEvents(); |
| } |
| |
| AvahiWatch * Poller::WatchNew(const struct AvahiPoll * poller, int fd, AvahiWatchEvent event, AvahiWatchCallback callback, |
| void * context) |
| { |
| return reinterpret_cast<Poller *>(poller->userdata)->WatchNew(fd, event, callback, context); |
| } |
| |
| AvahiWatch * Poller::WatchNew(int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void * context) |
| { |
| VerifyOrDie(callback != nullptr && fd >= 0); |
| |
| auto watch = std::make_unique<AvahiWatch>(); |
| watch->mSocket.Init(*mWatchableEvents) |
| .Attach(fd) |
| .SetCallback(AvahiWatchCallbackTrampoline, reinterpret_cast<intptr_t>(watch.get())) |
| .RequestCallbackOnPendingRead(event & AVAHI_WATCH_IN) |
| .RequestCallbackOnPendingWrite(event & AVAHI_WATCH_OUT); |
| watch->mCallback = callback; |
| watch->mContext = context; |
| watch->mPoller = this; |
| mWatches.emplace_back(std::move(watch)); |
| |
| return mWatches.back().get(); |
| } |
| |
| void Poller::WatchUpdate(AvahiWatch * watch, AvahiWatchEvent event) |
| { |
| if (event & AVAHI_WATCH_IN) |
| { |
| watch->mSocket.RequestCallbackOnPendingRead(); |
| } |
| else |
| { |
| watch->mSocket.ClearCallbackOnPendingRead(); |
| } |
| if (event & AVAHI_WATCH_OUT) |
| { |
| watch->mSocket.RequestCallbackOnPendingWrite(); |
| } |
| else |
| { |
| watch->mSocket.ClearCallbackOnPendingWrite(); |
| } |
| } |
| |
| AvahiWatchEvent Poller::WatchGetEvents(AvahiWatch * watch) |
| { |
| return ToAvahiWatchEvent(watch->mSocket.GetPendingEvents()); |
| } |
| |
| void Poller::WatchFree(AvahiWatch * watch) |
| { |
| reinterpret_cast<Poller *>(watch->mPoller)->WatchFree(*watch); |
| } |
| |
| void Poller::WatchFree(AvahiWatch & watch) |
| { |
| (void) watch.mSocket.ReleaseFD(); |
| mWatches.erase(std::remove_if(mWatches.begin(), mWatches.end(), |
| [&watch](const std::unique_ptr<AvahiWatch> & 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 *>(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<Poller *>(timer->mPoller)->TimeoutFree(*timer); |
| } |
| |
| void Poller::TimeoutFree(AvahiTimeout & timer) |
| { |
| mTimers.erase(std::remove_if(mTimers.begin(), mTimers.end(), |
| [&timer](const std::unique_ptr<AvahiTimeout> & aValue) { return aValue.get() == &timer; }), |
| mTimers.end()); |
| } |
| |
| void Poller::GetTimeout(timeval & timeout) |
| { |
| microseconds timeoutVal = seconds(timeout.tv_sec) + microseconds(timeout.tv_usec); |
| |
| 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<microseconds>(absTimeout - now)); |
| } |
| } |
| |
| timeout.tv_sec = static_cast<uint64_t>(timeoutVal.count()) / kUsPerSec; |
| timeout.tv_usec = static_cast<uint64_t>(timeoutVal.count()) % kUsPerSec; |
| } |
| |
| void Poller::HandleTimeout() |
| { |
| steady_clock::time_point now = steady_clock::now(); |
| |
| for (auto && timer : mTimers) |
| { |
| if (!timer->mEnabled) |
| { |
| continue; |
| } |
| if (timer->mAbsTimeout <= now) |
| { |
| timer->mCallback(timer.get(), timer->mContext); |
| } |
| } |
| } |
| |
| CHIP_ERROR MdnsAvahi::Init(MdnsAsyncReturnCallback initCallback, MdnsAsyncReturnCallback 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; |
| } |
| |
| CHIP_ERROR MdnsAvahi::SetHostname(const char * hostname) |
| { |
| CHIP_ERROR error = CHIP_NO_ERROR; |
| int avahiRet; |
| |
| VerifyOrExit(mClient != nullptr, error = CHIP_ERROR_INCORRECT_STATE); |
| avahiRet = avahi_client_set_host_name(mClient, hostname); |
| if (avahiRet == AVAHI_ERR_ACCESS_DENIED) |
| { |
| ChipLogError(DeviceLayer, "Cannot set hostname on this system, continue anyway..."); |
| } |
| else if (avahiRet != AVAHI_OK && avahiRet != AVAHI_ERR_NO_CHANGE) |
| { |
| error = CHIP_ERROR_INTERNAL; |
| } |
| |
| exit: |
| return error; |
| } |
| |
| void MdnsAvahi::HandleClientState(AvahiClient * client, AvahiClientState state, void * context) |
| { |
| static_cast<MdnsAvahi *>(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"); |
| if (mGroup != nullptr) |
| { |
| avahi_entry_group_reset(mGroup); |
| avahi_entry_group_free(mGroup); |
| } |
| mGroup = avahi_entry_group_new(client, HandleGroupState, this); |
| mPublishedServices.clear(); |
| if (mGroup == nullptr) |
| { |
| ChipLogError(DeviceLayer, "Failed to create avahi group: %s", avahi_strerror(avahi_client_errno(client))); |
| mErrorCallback(mAsyncReturnContext, CHIP_ERROR_OPEN_FAILED); |
| } |
| else |
| { |
| mErrorCallback(mAsyncReturnContext, CHIP_ERROR_FORCED_RESET); |
| } |
| break; |
| case AVAHI_CLIENT_CONNECTING: |
| ChipLogProgress(DeviceLayer, "Avahi connecting"); |
| break; |
| } |
| } |
| |
| void MdnsAvahi::HandleGroupState(AvahiEntryGroup * group, AvahiEntryGroupState state, void * context) |
| { |
| static_cast<MdnsAvahi *>(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.mInterface == INET_NULL_INTERFACEID ? AVAHI_IF_UNSPEC : static_cast<AvahiIfIndex>(service.mInterface); |
| |
| keyBuilder << service.mName << "." << type << service.mPort << "." << interface; |
| key = keyBuilder.str(); |
| ChipLogProgress(DeviceLayer, "PublishService %s", key.c_str()); |
| |
| if (mPublishedServices.find(key) == mPublishedServices.end()) |
| { |
| SuccessOrExit(error = MakeAvahiStringListFromTextEntries(service.mTextEntries, service.mTextEntrySize, &text)); |
| |
| mPublishedServices.emplace(key); |
| VerifyOrExit(avahi_entry_group_add_service_strlst(mGroup, interface, ToAvahiProtocol(service.mAddressType), |
| static_cast<AvahiPublishFlags>(0), service.mName, type.c_str(), nullptr, |
| nullptr, service.mPort, text) == 0, |
| error = CHIP_ERROR_INTERNAL); |
| for (size_t i = 0; i < service.mSubTypeSize; i++) |
| { |
| std::ostringstream sstream; |
| |
| sstream << service.mSubTypes[i] << "._sub." << type; |
| |
| VerifyOrExit(avahi_entry_group_add_service_subtype(mGroup, interface, ToAvahiProtocol(service.mAddressType), |
| static_cast<AvahiPublishFlags>(0), service.mName, type.c_str(), |
| nullptr, sstream.str().c_str()) == 0, |
| error = CHIP_ERROR_INTERNAL); |
| } |
| } |
| else |
| { |
| SuccessOrExit(error = MakeAvahiStringListFromTextEntries(service.mTextEntries, service.mTextEntrySize, &text)); |
| |
| VerifyOrExit(avahi_entry_group_update_service_txt_strlst(mGroup, interface, ToAvahiProtocol(service.mAddressType), |
| static_cast<AvahiPublishFlags>(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: %" CHIP_ERROR_FORMAT, ChipError::FormatError(error)); |
| } |
| |
| return error; |
| } |
| |
| CHIP_ERROR MdnsAvahi::StopPublish() |
| { |
| CHIP_ERROR error = CHIP_NO_ERROR; |
| |
| VerifyOrExit(avahi_entry_group_reset(mGroup) == 0, error = CHIP_ERROR_INTERNAL); |
| exit: |
| return error; |
| } |
| |
| CHIP_ERROR MdnsAvahi::Browse(const char * type, MdnsServiceProtocol protocol, chip::Inet::IPAddressType addressType, |
| chip::Inet::InterfaceId interface, MdnsBrowseCallback callback, void * context) |
| { |
| AvahiServiceBrowser * browser; |
| BrowseContext * browseContext = chip::Platform::New<BrowseContext>(); |
| AvahiIfIndex avahiInterface = static_cast<AvahiIfIndex>(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, ToAvahiProtocol(addressType), GetFullType(type, protocol).c_str(), |
| nullptr, static_cast<AvahiLookupFlags>(0), HandleBrowse, browseContext); |
| // Otherwise the browser will be freed in the callback |
| if (browser == nullptr) |
| { |
| chip::Platform::Delete(browseContext); |
| } |
| |
| return browser == nullptr ? CHIP_ERROR_INTERNAL : CHIP_NO_ERROR; |
| } |
| |
| MdnsServiceProtocol GetProtocolInType(const char * type) |
| { |
| const char * deliminator = strrchr(type, '.'); |
| |
| if (deliminator == NULL) |
| { |
| ChipLogError(Discovery, "Failed to find protocol in type: %s", type); |
| return MdnsServiceProtocol::kMdnsProtocolUnknown; |
| } |
| |
| if (strcmp("._tcp", deliminator) == 0) |
| { |
| return MdnsServiceProtocol::kMdnsProtocolTcp; |
| } |
| if (strcmp("._udp", deliminator) == 0) |
| { |
| return MdnsServiceProtocol::kMdnsProtocolUdp; |
| } |
| |
| ChipLogError(Discovery, "Unknown protocol in type: %s", type); |
| return MdnsServiceProtocol::kMdnsProtocolUnknown; |
| } |
| |
| /// Copies the type from a string containing both type and protocol |
| /// |
| /// e.g. if input is "foo.bar", output is "foo", input is 'a.b._tcp", output is "a.b" |
| template <size_t N> |
| void CopyTypeWithoutProtocol(char (&dest)[N], const char * typeAndProtocol) |
| { |
| const char * dotPos = strrchr(typeAndProtocol, '.'); |
| size_t lengthWithoutProtocol = (dotPos != nullptr) ? static_cast<size_t>(dotPos - typeAndProtocol) : N; |
| |
| Platform::CopyString(dest, typeAndProtocol); |
| |
| /// above copied everything including the protocol. Truncate the protocol away. |
| if (lengthWithoutProtocol < N) |
| { |
| dest[lengthWithoutProtocol] = 0; |
| } |
| } |
| |
| 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<BrowseContext *>(userdata); |
| |
| switch (event) |
| { |
| case AVAHI_BROWSER_FAILURE: |
| context->mCallback(context->mContext, nullptr, 0, CHIP_ERROR_INTERNAL); |
| avahi_service_browser_free(browser); |
| chip::Platform::Delete(context); |
| break; |
| case AVAHI_BROWSER_NEW: |
| ChipLogProgress(DeviceLayer, "Avahi browse: cache new"); |
| if (strcmp("local", domain) == 0) |
| { |
| MdnsService service = {}; |
| |
| Platform::CopyString(service.mName, name); |
| CopyTypeWithoutProtocol(service.mType, type); |
| service.mProtocol = GetProtocolInType(type); |
| service.mAddressType = ToAddressType(protocol); |
| service.mType[kMdnsTypeMaxSize] = 0; |
| 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::Delete(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::IPAddressType addressType, chip::Inet::InterfaceId interface, |
| MdnsResolveCallback callback, void * context) |
| { |
| AvahiServiceResolver * resolver; |
| AvahiIfIndex avahiInterface = static_cast<AvahiIfIndex>(interface); |
| ResolveContext * resolveContext = chip::Platform::New<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, ToAvahiProtocol(addressType), name, |
| GetFullType(type, protocol).c_str(), nullptr, ToAvahiProtocol(addressType), |
| static_cast<AvahiLookupFlags>(0), HandleResolve, resolveContext); |
| // Otherwise the resolver will be freed in the callback |
| if (resolver == nullptr) |
| { |
| error = CHIP_ERROR_INTERNAL; |
| chip::Platform::Delete(resolveContext); |
| } |
| |
| 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<ResolveContext *>(userdata); |
| std::vector<TextEntry> 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"); |
| |
| Platform::CopyString(result.mName, name); |
| CopyTypeWithoutProtocol(result.mType, type); |
| result.mProtocol = GetProtocolInType(type); |
| result.mPort = port; |
| result.mAddressType = ToAddressType(protocol); |
| Platform::CopyString(result.mHostName, host_name); |
| // Returned value is full QName, want only host part. |
| char * dot = strchr(result.mHostName, '.'); |
| if (dot != nullptr) |
| { |
| *dot = '\0'; |
| } |
| |
| 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<char *>(txt->text), &txt->text[i + 1], txt->size - i - 1 }); |
| break; |
| } |
| } |
| txt = txt->next; |
| } |
| |
| if (!textEntries.empty()) |
| { |
| result.mTextEntries = textEntries.data(); |
| } |
| result.mTextEntrySize = textEntries.size(); |
| |
| context->mCallback(context->mContext, &result, CHIP_NO_ERROR); |
| break; |
| } |
| |
| avahi_service_resolver_free(resolver); |
| chip::Platform::Delete(context); |
| } |
| |
| MdnsAvahi::~MdnsAvahi() |
| { |
| if (mGroup) |
| { |
| avahi_entry_group_free(mGroup); |
| } |
| if (mClient) |
| { |
| avahi_client_free(mClient); |
| } |
| } |
| |
| void GetMdnsTimeout(timeval & timeout) |
| { |
| MdnsAvahi::GetInstance().GetPoller().GetTimeout(timeout); |
| } |
| |
| void HandleMdnsTimeout() |
| { |
| MdnsAvahi::GetInstance().GetPoller().HandleTimeout(); |
| } |
| |
| CHIP_ERROR ChipMdnsInit(MdnsAsyncReturnCallback initCallback, MdnsAsyncReturnCallback errorCallback, void * context) |
| { |
| return MdnsAvahi::GetInstance().Init(initCallback, errorCallback, context); |
| } |
| |
| CHIP_ERROR ChipMdnsPublishService(const MdnsService * service) |
| { |
| if (strcmp(service->mHostName, "") != 0) |
| { |
| ReturnErrorOnFailure(MdnsAvahi::GetInstance().SetHostname(service->mHostName)); |
| } |
| return MdnsAvahi::GetInstance().PublishService(*service); |
| } |
| |
| CHIP_ERROR ChipMdnsStopPublish() |
| { |
| return MdnsAvahi::GetInstance().StopPublish(); |
| } |
| |
| CHIP_ERROR ChipMdnsStopPublishService(const MdnsService * service) |
| { |
| return CHIP_ERROR_NOT_IMPLEMENTED; |
| } |
| |
| CHIP_ERROR ChipMdnsBrowse(const char * type, MdnsServiceProtocol protocol, chip::Inet::IPAddressType addressType, |
| chip::Inet::InterfaceId interface, MdnsBrowseCallback callback, void * context) |
| { |
| return MdnsAvahi::GetInstance().Browse(type, protocol, addressType, 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, |
| browseResult->mAddressType, interface, callback, context); |
| } |
| else |
| { |
| error = CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| return error; |
| } |
| |
| } // namespace Mdns |
| } // namespace chip |