diff --git a/examples/all-clusters-app/esp32/main/DeviceCallbacks.cpp b/examples/all-clusters-app/esp32/main/DeviceCallbacks.cpp index f40b1fa013e8c3..6f7e8a48b9339a 100644 --- a/examples/all-clusters-app/esp32/main/DeviceCallbacks.cpp +++ b/examples/all-clusters-app/esp32/main/DeviceCallbacks.cpp @@ -33,7 +33,7 @@ #include "gen/attribute-id.h" #include "gen/cluster-id.h" #include -#include +#include #include static const char * TAG = "app-devicecallbacks"; @@ -92,7 +92,11 @@ void DeviceCallbacks::OnInternetConnectivityChange(const ChipDeviceEvent * event { ESP_LOGI(TAG, "Server ready at: %s:%d", event->InternetConnectivityChange.address, CHIP_PORT); wifiLED.Set(true); - chip::Mdns::DiscoveryManager::GetInstance().StartPublishDevice(); + + if (chip::Mdns::ServiceAdvertiser::Instance().Start(&DeviceLayer::InetLayer, chip::Mdns::kMdnsPort) != CHIP_NO_ERROR) + { + ESP_LOGE(TAG, "Failed to start mDNS advertisement"); + } } else if (event->InternetConnectivityChange.IPv4 == kConnectivity_Lost) { @@ -102,7 +106,10 @@ void DeviceCallbacks::OnInternetConnectivityChange(const ChipDeviceEvent * event if (event->InternetConnectivityChange.IPv6 == kConnectivity_Established) { ESP_LOGI(TAG, "IPv6 Server ready..."); - chip::Mdns::DiscoveryManager::GetInstance().StartPublishDevice(); + if (chip::Mdns::ServiceAdvertiser::Instance().Start(&DeviceLayer::InetLayer, chip::Mdns::kMdnsPort) != CHIP_NO_ERROR) + { + ESP_LOGE(TAG, "Failed to start mDNS advertisement"); + } } else if (event->InternetConnectivityChange.IPv6 == kConnectivity_Lost) { diff --git a/examples/common/chip-app-server/RendezvousServer.cpp b/examples/common/chip-app-server/RendezvousServer.cpp index 1bf3a980285764..9fbd78dc65ca91 100644 --- a/examples/common/chip-app-server/RendezvousServer.cpp +++ b/examples/common/chip-app-server/RendezvousServer.cpp @@ -25,7 +25,7 @@ #if CHIP_ENABLE_OPENTHREAD #include #endif -#include +#include using namespace ::chip::Inet; using namespace ::chip::Transport; @@ -97,7 +97,10 @@ void RendezvousServer::OnRendezvousStatusUpdate(Status status, CHIP_ERROR err) case RendezvousSessionDelegate::NetworkProvisioningSuccess: ChipLogProgress(AppServer, "Device was assigned network credentials"); - chip::Mdns::DiscoveryManager::GetInstance().StartPublishDevice(); + if (chip::Mdns::ServiceAdvertiser::Instance().Start(&DeviceLayer::InetLayer, chip::Mdns::kMdnsPort) != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "Failed to start mDNS advertisement"); + } if (mDelegate != nullptr) { mDelegate->OnRendezvousStopped(); diff --git a/examples/common/chip-app-server/Server.cpp b/examples/common/chip-app-server/Server.cpp index 0667f37c0a0dbb..b33e7223f340e4 100644 --- a/examples/common/chip-app-server/Server.cpp +++ b/examples/common/chip-app-server/Server.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -158,11 +159,30 @@ void InitServer(AppDelegate * delegate) SuccessOrExit(err = gRendezvousServer.Init(params, &gTransports)); } + // TODO: advertise this only when really operational once we support both + // operational and commisioning advertising is supported. + { + constexpr uint64_t kTestFabricId = 5544332211; + err = Mdns::ServiceAdvertiser::Instance().Advertise(Mdns::OperationalAdvertisingParameters() + .SetFabricId(kTestFabricId) + .SetNodeId(chip::kTestDeviceNodeId) + .SetPort(CHIP_PORT) +#if INET_CONFIG_ENABLE_IPV4 + .EnableIpV4(true) +#else + .EnableIpV4(false) +#endif + ); + SuccessOrExit(err); + } + + err = Mdns::ServiceAdvertiser::Instance().Start(&DeviceLayer::InetLayer, chip::Mdns::kMdnsPort); + SuccessOrExit(err); + err = gSessions.NewPairing(peer, chip::kTestControllerNodeId, &gTestPairing); SuccessOrExit(err); gSessions.SetDelegate(&gCallbacks); - chip::Mdns::DiscoveryManager::GetInstance().StartPublishDevice(kIPAddressType_IPv6); exit: if (err != CHIP_NO_ERROR) diff --git a/examples/minimal-mdns/AllInterfaceListener.h b/examples/minimal-mdns/AllInterfaceListener.h index fa87c3889a0b07..844305ee0b2c17 100644 --- a/examples/minimal-mdns/AllInterfaceListener.h +++ b/examples/minimal-mdns/AllInterfaceListener.h @@ -95,7 +95,7 @@ class AllInterfaces : public mdns::Minimal::ListenIterator return true; // not a usable interface } char name[64]; - if (!mIterator.GetInterfaceName(name, sizeof(name)) == CHIP_NO_ERROR) + if (mIterator.GetInterfaceName(name, sizeof(name)) != CHIP_NO_ERROR) { printf("!!!! FAILED TO GET INTERFACE NAME\n"); return true; diff --git a/examples/minimal-mdns/client.cpp b/examples/minimal-mdns/client.cpp index e9ffd1409111f1..a200a1a3e78ca8 100644 --- a/examples/minimal-mdns/client.cpp +++ b/examples/minimal-mdns/client.cpp @@ -277,7 +277,7 @@ void BroadcastPacket(mdns::Minimal::ServerBase * server) return; } - if (server->BroadcastSend(buffer.Release_ForNow(), gOptions.querySendPort) != CHIP_NO_ERROR) + if (server->BroadcastSend(std::move(buffer), gOptions.querySendPort) != CHIP_NO_ERROR) { printf("Error sending\n"); return; diff --git a/src/lib/mdns/Advertiser.h b/src/lib/mdns/Advertiser.h new file mode 100644 index 00000000000000..f9a7700cb3a887 --- /dev/null +++ b/src/lib/mdns/Advertiser.h @@ -0,0 +1,88 @@ +/* + * + * 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 + +namespace chip { +namespace Mdns { + +static constexpr uint16_t kMdnsPort = 5353; + +/// Defines parameters required for advertising a CHIP node +/// over mDNS as an 'operationally ready' node. +class OperationalAdvertisingParameters +{ +public: + OperationalAdvertisingParameters & SetFabricId(uint64_t fabricId) + { + mFabricId = fabricId; + return *this; + } + uint64_t GetFabricId() const { return mFabricId; } + + OperationalAdvertisingParameters & SetNodeId(uint64_t nodeId) + { + mNodeId = nodeId; + return *this; + } + uint64_t GetNodeId() const { return mNodeId; } + + OperationalAdvertisingParameters & SetPort(uint16_t port) + { + mPort = port; + return *this; + } + uint64_t GetPort() const { return mPort; } + + OperationalAdvertisingParameters & EnableIpV4(bool enable) + { + mEnableIPv4 = enable; + return *this; + } + bool IsIPv4Enabled() const { return mEnableIPv4; } + +private: + uint64_t mFabricId = 0; + uint64_t mNodeId = 0; + uint16_t mPort = CHIP_PORT; + bool mEnableIPv4 = true; +}; + +/// Handles advertising of CHIP nodes +class ServiceAdvertiser +{ +public: + virtual ~ServiceAdvertiser() {} + + /// Starts the advertiser. Items 'Advertised' will become visible. + /// May be called before OR after Advertise() calls. + virtual CHIP_ERROR Start(chip::Inet::InetLayer * inetLayer, uint16_t port) = 0; + + /// Advertises the CHIP node as an operational node + virtual CHIP_ERROR Advertise(const OperationalAdvertisingParameters & params) = 0; + + /// Provides the system-wide implementation of the service advertiser + static ServiceAdvertiser & Instance(); +}; + +} // namespace Mdns +} // namespace chip diff --git a/src/lib/mdns/Advertiser_ImplMinimalMdns.cpp b/src/lib/mdns/Advertiser_ImplMinimalMdns.cpp new file mode 100644 index 00000000000000..87daa191ac3da0 --- /dev/null +++ b/src/lib/mdns/Advertiser_ImplMinimalMdns.cpp @@ -0,0 +1,331 @@ +/* + * + * 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 "Advertiser.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace Mdns { +namespace { + +const char * ToString(mdns::Minimal::QClass qClass) +{ + switch (qClass) + { + case mdns::Minimal::QClass::IN: + return "IN"; + default: + return "???"; + } +} + +const char * ToString(mdns::Minimal::QType qType) +{ + switch (qType) + { + case mdns::Minimal::QType::ANY: + return "ANY"; + case mdns::Minimal::QType::A: + return "A"; + case mdns::Minimal::QType::AAAA: + return "AAAA"; + case mdns::Minimal::QType::TXT: + return "TXT"; + case mdns::Minimal::QType::SRV: + return "SRV"; + case mdns::Minimal::QType::PTR: + return "PTR"; + default: + return "???"; + } +} + +void LogQuery(const mdns::Minimal::QueryData & data) +{ + StringBuilder<128> logString; + + logString.Add("QUERY ").Add(ToString(data.GetClass())).Add("/").Add(ToString(data.GetType())).Add(": "); + + mdns::Minimal::SerializedQNameIterator name = data.GetName(); + while (name.Next()) + { + logString.Add(name.Value()).Add("."); + } + + ChipLogDetail(Discovery, "%s", logString.c_str()); +} + +class AllInterfaces : public mdns::Minimal::ListenIterator +{ +public: + AllInterfaces() {} + + bool Next(chip::Inet::InterfaceId * id, chip::Inet::IPAddressType * type) override + { +#if INET_CONFIG_ENABLE_IPV4 + if (mState == State::kIpV4) + { + *id = INET_NULL_INTERFACEID; + *type = chip::Inet::kIPAddressType_IPv4; + mState = State::kIpV6; + + SkipToFirstValidInterface(); + return true; + } +#else + mState = State::kIpV6; + SkipToFirstValidInterface(); +#endif + + if (!mIterator.HasCurrent()) + { + return false; + } + + *id = mIterator.GetInterfaceId(); + *type = chip::Inet::kIPAddressType_IPv6; + + for (mIterator.Next(); SkipCurrentInterface(); mIterator.Next()) + { + } + return true; + } + +private: + enum class State + { + kIpV4, + kIpV6, + }; + State mState; + chip::Inet::InterfaceIterator mIterator; + + void SkipToFirstValidInterface() + { + if (SkipCurrentInterface()) + { + while (mIterator.Next()) + { + if (!SkipCurrentInterface()) + { + break; + } + } + } + } + + bool SkipCurrentInterface() + { + if (!mIterator.HasCurrent()) + { + return false; // nothing to try. + } + + if (!mIterator.IsUp() || !mIterator.SupportsMulticast()) + { + return true; // not a usable interface + } + char name[64]; + if (mIterator.GetInterfaceName(name, sizeof(name)) != CHIP_NO_ERROR) + { + ChipLogError(Discovery, "Interface iterator failed to get interface name."); + return true; + } + + if (strncmp(name, "lo", 2) == 0) + { + ChipLogDetail(Discovery, "Skipping interface '%s' (assume local loopback)", name); + return true; + } + return false; + } +}; + +class AdvertiserMinMdns : public ServiceAdvertiser, + public mdns::Minimal::ServerDelegate, // gets queries + public mdns::Minimal::ParserDelegate // parses queries +{ +public: + AdvertiserMinMdns() : + mResponseSender(&mServer, &mQueryResponder), mPtrResponder(mOperationalServiceQName, mOperationalServerQName), + mSrvResponder(mOperationalServerQName, mdns::Minimal::SrvResourceRecord(mOperationalServerQName, mServerQName, CHIP_PORT)), + mIPv4Responder(mServerQName), mIPv6Responder(mServerQName), + mTxtResponder(mdns::Minimal::TxtResourceRecord(mOperationalServerQName, mEmptyTextEntries)) + + { + mServer.SetDelegate(this); + } + + // Service advertiser + CHIP_ERROR Start(chip::Inet::InetLayer * inetLayer, uint16_t port) override; + CHIP_ERROR Advertise(const OperationalAdvertisingParameters & params) override; + + // ServerDelegate + void OnQuery(const mdns::Minimal::BytesRange & data, const chip::Inet::IPPacketInfo * info) override; + void OnResponse(const mdns::Minimal::BytesRange & data, const chip::Inet::IPPacketInfo * info) override {} + + // ParserDelegate + void OnHeader(mdns::Minimal::ConstHeaderRef & header) override { mMessageId = header.GetMessageId(); } + void OnResource(mdns::Minimal::ResourceType type, const mdns::Minimal::ResourceData & data) override {} + void OnQuery(const mdns::Minimal::QueryData & data) override; + +private: + static constexpr size_t kMaxEndPoints = 10; + static constexpr size_t kMaxRecords = 16; + + mdns::Minimal::Server mServer; + mdns::Minimal::QueryResponder mQueryResponder; + mdns::Minimal::ResponseSender mResponseSender; + + // current request handling + const chip::Inet::IPPacketInfo * mCurrentSource = nullptr; + uint32_t mMessageId = 0; + + /// data members for variable things + char mServerName[64] = ""; + + mdns::Minimal::QNamePart mOperationalServiceQName[3] = { "_chip", "_tcp", "local" }; + mdns::Minimal::QNamePart mOperationalServerQName[4] = { mServerName, "_chip", "_tcp", "local" }; + mdns::Minimal::QNamePart mServerQName[2] = { mServerName, "local" }; + + /// responders + mdns::Minimal::PtrResponder mPtrResponder; + mdns::Minimal::SrvResponder mSrvResponder; + mdns::Minimal::IPv4Responder mIPv4Responder; + mdns::Minimal::IPv6Responder mIPv6Responder; + mdns::Minimal::TxtResponder mTxtResponder; + + const char * mEmptyTextEntries[1] = { + "=", + }; +}; + +void AdvertiserMinMdns::OnQuery(const mdns::Minimal::BytesRange & data, const chip::Inet::IPPacketInfo * info) +{ + ChipLogDetail(Discovery, "MinMdns received a query."); + + mCurrentSource = info; + if (!mdns::Minimal::ParsePacket(data, this)) + { + ChipLogError(Discovery, "Failed to parse mDNS query"); + } + mCurrentSource = nullptr; +} + +void AdvertiserMinMdns::OnQuery(const mdns::Minimal::QueryData & data) +{ + if (mCurrentSource == nullptr) + { + ChipLogError(Discovery, "INTERNAL CONSISTENCY ERROR: missing query source"); + return; + } + + LogQuery(data); + + CHIP_ERROR err = mResponseSender.Respond(mMessageId, data, mCurrentSource); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Discovery, "Failed to reply to query: %s", ErrorStr(err)); + } +} + +CHIP_ERROR AdvertiserMinMdns::Start(chip::Inet::InetLayer * inetLayer, uint16_t port) +{ + mServer.Shutdown(); + + AllInterfaces allInterfaces; + + ReturnErrorOnFailure(mServer.Listen(inetLayer, &allInterfaces, port)); + + ChipLogProgress(Discovery, "CHIP minimal mDNS started advertising."); + return CHIP_NO_ERROR; +} + +CHIP_ERROR AdvertiserMinMdns::Advertise(const OperationalAdvertisingParameters & params) +{ + mQueryResponder.Init(); // start fresh + + /// need to set server name + size_t len = snprintf(mServerName, sizeof(mServerName), "%" PRIX64 "-%" PRIX64, params.GetFabricId(), params.GetNodeId()); + if (len >= sizeof(mServerName)) + { + return CHIP_ERROR_NO_MEMORY; + } + if (!mQueryResponder.AddResponder(&mPtrResponder) + .SetReportAdditional(mOperationalServerQName) + .SetReportInServiceListing(true) + .IsValid()) + { + ChipLogError(Discovery, "Failed to add service PTR record mDNS responder"); + return CHIP_ERROR_NO_MEMORY; + } + + mSrvResponder.SetRecord(mdns::Minimal::SrvResourceRecord(mOperationalServerQName, mServerQName, params.GetPort())); + if (!mQueryResponder.AddResponder(&mSrvResponder).SetReportAdditional(mServerQName).IsValid()) + { + ChipLogError(Discovery, "Failed to add SRV record mDNS responder"); + return CHIP_ERROR_NO_MEMORY; + } + + if (!mQueryResponder.AddResponder(&mTxtResponder).IsValid()) + { + ChipLogError(Discovery, "Failed to add TXT record mDNS responder"); + return CHIP_ERROR_NO_MEMORY; + } + + if (!mQueryResponder.AddResponder(&mIPv6Responder).IsValid()) + { + ChipLogError(Discovery, "Failed to add IPv6 mDNS responder"); + return CHIP_ERROR_NO_MEMORY; + } + + if (params.IsIPv4Enabled()) + { + if (!mQueryResponder.AddResponder(&mIPv4Responder).IsValid()) + { + ChipLogError(Discovery, "Failed to add IPv4 mDNS responder"); + return CHIP_ERROR_NO_MEMORY; + } + } + + ChipLogProgress(Discovery, "CHIP minimal mDNS configured as 'Operational device'."); + + return CHIP_NO_ERROR; +} + +AdvertiserMinMdns gAdvertiser; +} // namespace + +ServiceAdvertiser & ServiceAdvertiser::Instance() +{ + return gAdvertiser; +} + +} // namespace Mdns +} // namespace chip diff --git a/src/lib/mdns/Advertiser_ImplNone.cpp b/src/lib/mdns/Advertiser_ImplNone.cpp new file mode 100644 index 00000000000000..eec877770b89b1 --- /dev/null +++ b/src/lib/mdns/Advertiser_ImplNone.cpp @@ -0,0 +1,52 @@ +/* + * + * 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 "Advertiser.h" + +#include + +namespace chip { +namespace Mdns { +namespace { + +class NoneAdvertiser : public ServiceAdvertiser +{ +public: + CHIP_ERROR Start(chip::Inet::InetLayer * inetLayet, uint16_t port) override + { + ChipLogError(Discovery, "mDNS advertising not available. mDNS start disabled."); + return CHIP_ERROR_NOT_IMPLEMENTED; + } + + CHIP_ERROR Advertise(const OperationalAdvertisingParameters & params) override + { + ChipLogError(Discovery, "mDNS advertising not available. Operational Advertisement failed."); + return CHIP_ERROR_NOT_IMPLEMENTED; + } +}; + +NoneAdvertiser gAdvertiser; + +} // namespace + +ServiceAdvertiser & ServiceAdvertiser::Instance() +{ + return gAdvertiser; +} + +} // namespace Mdns +} // namespace chip diff --git a/src/lib/mdns/BUILD.gn b/src/lib/mdns/BUILD.gn index 0704363be4be50..69fdba410ad0ab 100644 --- a/src/lib/mdns/BUILD.gn +++ b/src/lib/mdns/BUILD.gn @@ -14,6 +14,11 @@ import("//build_overrides/chip.gni") +declare_args() { + # Set up what advertiser to use for mDNS advertisement + chip_mdns_avertiser = "minimal-mdns" +} + source_set("platform_header") { sources = [ "platform/Mdns.h" ] } @@ -27,7 +32,17 @@ static_library("mdns") { ] sources = [ + "Advertiser.h", "DiscoveryManager.cpp", "DiscoveryManager.h", ] + + if (chip_mdns_avertiser == "none") { + sources += [ "Advertiser_ImplNone.cpp" ] + } else if (chip_mdns_avertiser == "minimal-mdns") { + sources += [ "Advertiser_ImplMinimalMdns.cpp" ] + public_deps += [ "${chip_root}/src/lib/mdns/minimal" ] + } else { + assert(false, "Unknown mDNS advertiser implementation.") + } } diff --git a/src/lib/mdns/minimal/ResponseBuilder.h b/src/lib/mdns/minimal/ResponseBuilder.h index e583b703e9d862..cfa563c3bcb847 100644 --- a/src/lib/mdns/minimal/ResponseBuilder.h +++ b/src/lib/mdns/minimal/ResponseBuilder.h @@ -53,7 +53,7 @@ class ResponseBuilder } CHECK_RETURN_VALUE - chip::System::PacketBufferHandle && ReleasePacket() + chip::System::PacketBufferHandle ReleasePacket() { mHeader = HeaderRef(nullptr); mBuildOk = false; diff --git a/src/lib/mdns/minimal/Server.cpp b/src/lib/mdns/minimal/Server.cpp index 453a3f1510da03..026a8fe24496d2 100644 --- a/src/lib/mdns/minimal/Server.cpp +++ b/src/lib/mdns/minimal/Server.cpp @@ -156,7 +156,7 @@ CHIP_ERROR ServerBase::DirectSend(chip::System::PacketBufferHandle && data, cons return CHIP_ERROR_NOT_CONNECTED; } -CHIP_ERROR ServerBase::BroadcastSend(chip::System::PacketBufferHandle && data, uint16_t port, chip::Inet::InterfaceId interface) +CHIP_ERROR ServerBase::BroadcastSend(chip::System::PacketBufferHandle data, uint16_t port, chip::Inet::InterfaceId interface) { for (size_t i = 0; i < mEndpointCount; i++) { @@ -172,19 +172,23 @@ CHIP_ERROR ServerBase::BroadcastSend(chip::System::PacketBufferHandle && data, u continue; } - // data may be sent over multiple packets. Keep the one ref active all the time - chip::System::PacketBufferHandle extraCopy = data.Retain(); - CHIP_ERROR err; + /// The same packet needs to be sent over potentially multiple interfaces. + /// LWIP does not like having a pbuf sent over serparate interfaces, hence we create a copy + /// TODO: this wastes one copy of the data and that could be optimized away + chip::System::PacketBufferHandle copy = data.Clone(); + if (info->addressType == chip::Inet::kIPAddressType_IPv6) { - err = info->udp->SendTo(kBroadcastIp.ipv6, port, info->udp->GetBoundInterface(), extraCopy.Release_ForNow()); + err = info->udp->SendTo(kBroadcastIp.ipv6, port, info->udp->GetBoundInterface(), copy.Release_ForNow()); } +#if INET_CONFIG_ENABLE_IPV4 else if (info->addressType == chip::Inet::kIPAddressType_IPv4) { - err = info->udp->SendTo(kBroadcastIp.ipv4, port, info->udp->GetBoundInterface(), extraCopy.Release_ForNow()); + err = info->udp->SendTo(kBroadcastIp.ipv4, port, info->udp->GetBoundInterface(), copy.Release_ForNow()); } +#endif else { return CHIP_ERROR_INCORRECT_STATE; @@ -199,7 +203,7 @@ CHIP_ERROR ServerBase::BroadcastSend(chip::System::PacketBufferHandle && data, u return CHIP_NO_ERROR; } -CHIP_ERROR ServerBase::BroadcastSend(chip::System::PacketBuffer * data, uint16_t port) +CHIP_ERROR ServerBase::BroadcastSend(chip::System::PacketBufferHandle data, uint16_t port) { for (size_t i = 0; i < mEndpointCount; i++) { @@ -210,24 +214,25 @@ CHIP_ERROR ServerBase::BroadcastSend(chip::System::PacketBuffer * data, uint16_t continue; } - // data may be sent over multiple packets. Keep the one ref active all the time - data->AddRef(); - CHIP_ERROR err; + /// The same packet needs to be sent over potentially multiple interfaces. + /// LWIP does not like having a pbuf sent over serparate interfaces, hence we create a copy + /// TODO: this wastes one copy of the data and that could be optimized away + chip::System::PacketBufferHandle copy = data.Clone(); + if (info->addressType == chip::Inet::kIPAddressType_IPv6) { - err = info->udp->SendTo(kBroadcastIp.ipv6, port, info->udp->GetBoundInterface(), data); + err = info->udp->SendTo(kBroadcastIp.ipv6, port, info->udp->GetBoundInterface(), copy.Release_ForNow()); } +#if INET_CONFIG_ENABLE_IPV4 else if (info->addressType == chip::Inet::kIPAddressType_IPv4) { - err = info->udp->SendTo(kBroadcastIp.ipv4, port, info->udp->GetBoundInterface(), data); + err = info->udp->SendTo(kBroadcastIp.ipv4, port, info->udp->GetBoundInterface(), copy.Release_ForNow()); } +#endif else { - // remove extra ref and then also clear it - chip::System::PacketBuffer::Free(data); - chip::System::PacketBuffer::Free(data); return CHIP_ERROR_INCORRECT_STATE; } @@ -240,12 +245,10 @@ CHIP_ERROR ServerBase::BroadcastSend(chip::System::PacketBuffer * data, uint16_t } else if (err != CHIP_NO_ERROR) { - chip::System::PacketBuffer::Free(data); return err; } } - chip::System::PacketBuffer::Free(data); return CHIP_NO_ERROR; } diff --git a/src/lib/mdns/minimal/Server.h b/src/lib/mdns/minimal/Server.h index e5e1dd73e3f07e..e1e66ecce0ddb5 100644 --- a/src/lib/mdns/minimal/Server.h +++ b/src/lib/mdns/minimal/Server.h @@ -95,10 +95,10 @@ class ServerBase chip::Inet::InterfaceId interface); /// Send a specific packet broadcast to all interfaces - CHIP_ERROR BroadcastSend(chip::System::PacketBuffer * data, uint16_t port); + CHIP_ERROR BroadcastSend(chip::System::PacketBufferHandle data, uint16_t port); /// Send a specific packet broadcast to a specific interface - CHIP_ERROR BroadcastSend(chip::System::PacketBufferHandle && data, uint16_t port, chip::Inet::InterfaceId interface); + CHIP_ERROR BroadcastSend(chip::System::PacketBufferHandle data, uint16_t port, chip::Inet::InterfaceId interface); ServerBase & SetDelegate(ServerDelegate * d) { diff --git a/src/lib/mdns/minimal/records/ResourceRecord.h b/src/lib/mdns/minimal/records/ResourceRecord.h index 85d98bba1e449f..74ff2a980d5b28 100644 --- a/src/lib/mdns/minimal/records/ResourceRecord.h +++ b/src/lib/mdns/minimal/records/ResourceRecord.h @@ -35,6 +35,8 @@ class ResourceRecord virtual ~ResourceRecord() {} + ResourceRecord & operator=(const ResourceRecord & other) = default; + const FullQName & GetName() const { return mQName; } QClass GetClass() const { return QClass::IN; } QType GetType() const { return mType; } @@ -57,9 +59,9 @@ class ResourceRecord ResourceRecord(QType type, FullQName name) : mType(type), mQName(name) {} private: - const QType mType; + QType mType; uint64_t mTtl = kDefaultTtl; - const FullQName mQName; + FullQName mQName; }; } // namespace Minimal diff --git a/src/lib/mdns/minimal/records/Srv.h b/src/lib/mdns/minimal/records/Srv.h index 9b1dd51ca6833f..3dc7211cbb5ef3 100644 --- a/src/lib/mdns/minimal/records/Srv.h +++ b/src/lib/mdns/minimal/records/Srv.h @@ -28,6 +28,7 @@ class SrvResourceRecord : public ResourceRecord SrvResourceRecord(const FullQName & qName, const FullQName & serverName, uint16_t port) : ResourceRecord(QType::SRV, qName), mServerName(serverName), mPort(port) {} + SrvResourceRecord & operator=(const SrvResourceRecord & other) = default; FullQName GetServerName() const { return mServerName; } uint16_t GetPort() const { return mPort; } @@ -49,8 +50,8 @@ class SrvResourceRecord : public ResourceRecord } private: - const FullQName mServerName; - const uint16_t mPort; + FullQName mServerName; + uint16_t mPort; uint16_t mPriority = 0; uint16_t mWeight = 0; }; diff --git a/src/lib/mdns/minimal/responders/Srv.h b/src/lib/mdns/minimal/responders/Srv.h index 876c1c62bbae35..54c5afdb0811ed 100644 --- a/src/lib/mdns/minimal/responders/Srv.h +++ b/src/lib/mdns/minimal/responders/Srv.h @@ -28,13 +28,15 @@ class SrvResponder : public Responder public: SrvResponder(const FullQName & qname, const SrvResourceRecord & record) : Responder(QType::SRV, qname), mRecord(record) {} + void SetRecord(const SrvResourceRecord & record) { mRecord = record; } + void AddAllResponses(const chip::Inet::IPPacketInfo * source, ResponderDelegate * delegate) override { delegate->AddResponse(mRecord); } private: - const SrvResourceRecord mRecord; + SrvResourceRecord mRecord; }; } // namespace Minimal diff --git a/src/lib/support/StringBuilder.h b/src/lib/support/StringBuilder.h new file mode 100644 index 00000000000000..bab9f2de5a300d --- /dev/null +++ b/src/lib/support/StringBuilder.h @@ -0,0 +1,86 @@ +/* + * + * 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 "BufferWriter.h" + +namespace chip { + +/// Build a c-style string out of distinct parts +class StringBuilderBase +{ +public: + StringBuilderBase(char * buffer, size_t size) : mWriter(reinterpret_cast(buffer), size - 1) + { + nlASSERT(size > 0); + buffer[0] = 0; // make c-str work by default + } + + /// Append a null terminated string + StringBuilderBase & Add(const char * s) + { + mWriter.Put(s); + NullTerminate(); + return *this; + } + + /// Append an integer value + StringBuilderBase & Add(int value) + { + char buff[32]; + snprintf(buff, sizeof(buff), "%d", value); + buff[sizeof(buff) - 1] = 0; + return Add(buff); + } + + /// did all the values fit? + bool Fit() const { return mWriter.Fit(); } + + /// access the underlying value + const char * c_str() const { return reinterpret_cast(mWriter.Buffer()); } + +private: + Encoding::LittleEndian::BufferWriter mWriter; + + void NullTerminate() + { + if (mWriter.Fit()) + { + mWriter.Buffer()[mWriter.Needed()] = 0; + } + else + { + mWriter.Buffer()[mWriter.Size()] = 0; + } + } +}; + +/// a preallocated sized string builder +template +class StringBuilder : public StringBuilderBase +{ +public: + StringBuilder() : StringBuilderBase(mBuffer, kSize) {} + +private: + char mBuffer[kSize]; +}; + +} // namespace chip diff --git a/src/lib/support/tests/BUILD.gn b/src/lib/support/tests/BUILD.gn index b644042e0e707b..3f31e469663220 100644 --- a/src/lib/support/tests/BUILD.gn +++ b/src/lib/support/tests/BUILD.gn @@ -32,6 +32,7 @@ chip_test_suite("tests") { "TestSafeInt.cpp", "TestScopedBuffer.cpp", "TestSerializableIntegerSet.cpp", + "TestStringBuilder.cpp", "TestTimeUtils.cpp", ] sources = [] diff --git a/src/lib/support/tests/TestStringBuilder.cpp b/src/lib/support/tests/TestStringBuilder.cpp new file mode 100644 index 00000000000000..5fa0a95492f535 --- /dev/null +++ b/src/lib/support/tests/TestStringBuilder.cpp @@ -0,0 +1,97 @@ +/* + * + * 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 + +namespace { + +using namespace chip; + +void TestStringBuilder(nlTestSuite * inSuite, void * inContext) +{ + + StringBuilder<64> builder; + + NL_TEST_ASSERT(inSuite, builder.Fit()); + NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "") == 0); + + builder.Add("foo"); + NL_TEST_ASSERT(inSuite, builder.Fit()); + NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "foo") == 0); + + builder.Add("bar"); + NL_TEST_ASSERT(inSuite, builder.Fit()); + NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "foobar") == 0); +} + +void TestIntegerAppend(nlTestSuite * inSuite, void * inContext) +{ + + StringBuilder<64> builder; + + builder.Add("nr: ").Add(1234); + NL_TEST_ASSERT(inSuite, builder.Fit()); + NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "nr: 1234") == 0); + + builder.Add(", ").Add(-22); + NL_TEST_ASSERT(inSuite, builder.Fit()); + NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "nr: 1234, -22") == 0); +} + +void TestOverflow(nlTestSuite * inSuite, void * inContext) +{ + + { + StringBuilder<4> builder; + + builder.Add("foo"); + NL_TEST_ASSERT(inSuite, builder.Fit()); + NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "foo") == 0); + + builder.Add("bar"); + NL_TEST_ASSERT(inSuite, !builder.Fit()); + NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "foo") == 0); + } + + { + StringBuilder<7> builder; + + builder.Add("x: ").Add(12345); + NL_TEST_ASSERT(inSuite, !builder.Fit()); + NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "x: 123") == 0); + } +} + +const nlTest sTests[] = { + NL_TEST_DEF("TestStringBuilder", TestStringBuilder), // + NL_TEST_DEF("TestIntegerAppend", TestIntegerAppend), // + NL_TEST_DEF("TestOverflow", TestOverflow), // + NL_TEST_SENTINEL() // +}; + +} // namespace + +int TestStringBuilder(void) +{ + nlTestSuite theSuite = { "StringBuilder", sTests, nullptr, nullptr }; + nlTestRunner(&theSuite, nullptr); + return nlTestRunnerStats(&theSuite); +} + +CHIP_REGISTER_TEST_SUITE(TestStringBuilder) diff --git a/src/platform/ESP32/Logging.cpp b/src/platform/ESP32/Logging.cpp index e05be9eacca4e2..b3e02f51e80f1e 100644 --- a/src/platform/ESP32/Logging.cpp +++ b/src/platform/ESP32/Logging.cpp @@ -27,6 +27,11 @@ #include +#ifdef LOG_LOCAL_LEVEL +#undef LOG_LOCAL_LEVEL +#endif +#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE + #include "esp_log.h" using namespace ::chip; diff --git a/src/system/SystemPacketBuffer.cpp b/src/system/SystemPacketBuffer.cpp index dcc50ae701ee1a..f407419cba6c33 100644 --- a/src/system/SystemPacketBuffer.cpp +++ b/src/system/SystemPacketBuffer.cpp @@ -726,5 +726,31 @@ PacketBufferHandle PacketBufferHandle::PopHead() return PacketBufferHandle(head); } +PacketBufferHandle PacketBufferHandle::Clone() +{ + if (mBuffer->Next() != nullptr) + { + // We do not clone an entire chain. + return PacketBufferHandle(); + } + + PacketBufferHandle other = PacketBuffer::New(); + if (other.IsNull()) + { + return other; + } + + if (other->AvailableDataLength() < mBuffer->DataLength()) + { + other.Adopt(nullptr); + return other; + } + + memcpy(other->Start(), mBuffer->Start(), mBuffer->DataLength()); + other->SetDataLength(mBuffer->DataLength()); + + return other; +} + } // namespace System } // namespace chip diff --git a/src/system/SystemPacketBuffer.h b/src/system/SystemPacketBuffer.h index 32fc9d945c0501..84a34445035ac7 100644 --- a/src/system/SystemPacketBuffer.h +++ b/src/system/SystemPacketBuffer.h @@ -341,6 +341,15 @@ class DLL_EXPORT PacketBufferHandle mBuffer = PacketBuffer::FreeHead(mBuffer); } + /** + * Creates a copy of the data in this packet. + * + * Does NOT support chained buffers. + * + * @returns empty handle on allocation failure. + */ + PacketBufferHandle Clone(); + private: PacketBufferHandle(const PacketBufferHandle &) = delete; PacketBufferHandle & operator=(const PacketBufferHandle &) = delete;