/* * Copyright (c) 2024, The OpenThread Authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holder nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "nexus_node.hpp" #include "nexus_utils.hpp" namespace ot { namespace Nexus { //---------------------------------------------------------------------------------------------------------------------- Node::Node(void) : Platform(static_cast(*this)) , mCliInterpreter(static_cast(this), HandleCliOutput, this) , mX(0.0f) , mY(0.0f) , mLastParentId(0xffff) { mCliInterpreter.SetPromptConfig(false); } void Node::Reset(void) { Instance *instance = &GetInstance(); uint32_t id = GetId(); mRadio.Reset(); mAlarmMilli.Reset(); mAlarmMicro.Reset(); mMdns.Reset(); mUpstreamDns.Reset(); mInfraIf.mPendingTxQueue.DequeueAndFreeAll(); mPendingTasklet = false; #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE mTrel.Reset(); #endif instance->~Instance(); instance = new (instance) Instance(); instance->SetId(id); instance->AfterInit(); instance->Get().SetReceiveCallback(Node::HandleIp6Receive, this); } void Node::Form(void) { MeshCoP::Dataset::Info datasetInfo; SuccessOrQuit(datasetInfo.GenerateRandom(*this)); Get().SaveLocal(datasetInfo); Get().Up(); SuccessOrQuit(Get().Start()); } void Node::Join(Node &aNode, JoinMode aJoinMode) { MeshCoP::Dataset dataset; uint8_t mode = 0; switch (aJoinMode) { case kAsFed: SuccessOrQuit(Get().SetRouterEligible(false)); OT_FALL_THROUGH; case kAsFtd: mode = Mle::DeviceMode::kModeRxOnWhenIdle | Mle::DeviceMode::kModeFullThreadDevice | Mle::DeviceMode::kModeFullNetworkData; break; case kAsMed: mode = Mle::DeviceMode::kModeRxOnWhenIdle | Mle::DeviceMode::kModeFullNetworkData; break; case kAsSedWithFullNetData: mode = Mle::DeviceMode::kModeFullNetworkData; break; case kAsSed: break; } SuccessOrQuit(Get().SetDeviceMode(Mle::DeviceMode(mode))); SuccessOrQuit(aNode.Get().Read(dataset)); Get().SaveLocal(dataset); Get().Up(); SuccessOrQuit(Get().Start()); } void Node::AllowList(Node &aNode) { SuccessOrQuit(Get().AddAddress(aNode.Get().GetExtAddress())); Get().SetMode(Mac::Filter::kModeAllowlist); } void Node::UnallowList(Node &aNode) { Get().RemoveAddress(aNode.Get().GetExtAddress()); } void Node::SendEchoRequest(const Ip6::Address &aDestination, uint16_t aIdentifier, uint16_t aPayloadSize, uint8_t aHopLimit, const Ip6::Address *aSrcAddress) { Message *message; Ip6::MessageInfo messageInfo; message = Get().NewMessage(); VerifyOrQuit(message != nullptr); SuccessOrQuit(message->SetLength(aPayloadSize)); messageInfo.SetPeerAddr(aDestination); messageInfo.SetHopLimit(aHopLimit); messageInfo.mAllowZeroHopLimit = true; if (aSrcAddress != nullptr) { messageInfo.SetSockAddr(*aSrcAddress); } SuccessOrQuit(Get().SendEchoRequest(*message, messageInfo, aIdentifier)); } void Node::SetName(const char *aPrefix, uint16_t aIndex) { mName.Clear().Append("%s_%u", aPrefix, aIndex); } void Node::HandleIp6Receive(otMessage *aMessage, void *aContext) { OwnedPtr messagePtr(AsCoreTypePtr(aMessage)); static_cast(aContext)->HandleIp6Receive(messagePtr.PassOwnership()); } void Node::HandleIp6Receive(OwnedPtr aMessagePtr) { Ip6::Header header; VerifyOrExit(aMessagePtr != nullptr); SuccessOrExit(header.ParseFrom(*aMessagePtr)); // Forward packets to InfraIf if they are intended for the backbone. // We avoid forwarding link-local and realm-local scope packets. VerifyOrExit(mInfraIf.IsInitialized()); VerifyOrExit(header.GetHopLimit() > 1); header.SetHopLimit(header.GetHopLimit() - 1); aMessagePtr->Write(0, header); VerifyOrExit(header.GetDestination().GetScope() > Ip6::Address::kRealmLocalScope); if (header.GetDestination().IsMulticast()) { VerifyOrExit(Get().IsPrimary()); } VerifyOrExit(!header.GetSource().IsLinkLocalUnicastOrMulticast()); VerifyOrExit(!Get().IsMeshLocalAddress(header.GetSource())); VerifyOrExit(Get().IsOnMesh(header.GetSource())); mInfraIf.SendIp6(header, aMessagePtr.PassOwnership()); exit: return; } #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE void Node::GetTrelSockAddr(Ip6::SockAddr &aSockAddr) const { aSockAddr.SetAddress(mInfraIf.GetLinkLocalAddress()); aSockAddr.SetPort(mTrel.mUdpPort); } #endif const Ip6::Address &Node::FindMatchingAddress(const char *aPrefix) { Ip6::Prefix prefix; const Ip6::Address *matchedAddress = nullptr; SuccessOrQuit(prefix.FromString(aPrefix)); for (const Ip6::Netif::UnicastAddress &unicastAddress : Get().GetUnicastAddresses()) { if (unicastAddress.GetAddress().MatchesPrefix(prefix)) { matchedAddress = &unicastAddress.GetAddress(); break; } } VerifyOrQuit(matchedAddress != nullptr, "no matching address found"); return *matchedAddress; } const Ip6::Address &Node::FindGlobalAddress(void) { const Ip6::Address *matchedAddress = nullptr; for (const Ip6::Netif::UnicastAddress &unicastAddress : Get().GetUnicastAddresses()) { const Ip6::Address &address = unicastAddress.GetAddress(); if (address.GetScope() == Ip6::Address::kGlobalScope && !Get().IsMeshLocalAddress(address)) { matchedAddress = &address; break; } } VerifyOrQuit(matchedAddress != nullptr, "no global address found"); return *matchedAddress; } bool Node::Matches(const Ip6::Address &aAddress, AddressNetif aNetif) const { bool matches = false; switch (aNetif) { case kThreadNetifAddress: matches = Get().HasUnicastAddress(aAddress); break; case kInfraNetifAddress: matches = mInfraIf.HasAddress(aAddress); break; case kAnyNetifAddress: matches = Get().HasUnicastAddress(aAddress) || mInfraIf.HasAddress(aAddress); break; } return matches; } const char *Node::GetExtendedRoleString(void) const { const char *roleStr; Mle::DeviceRole role = Get().GetRole(); switch (role) { case Mle::kRoleDisabled: roleStr = "Disabled"; break; case Mle::kRoleDetached: roleStr = "Detached"; break; case Mle::kRoleLeader: roleStr = "Leader"; break; case Mle::kRoleRouter: roleStr = "Router"; break; case Mle::kRoleChild: if (Get().IsFullThreadDevice()) { roleStr = Get().IsRouterRoleAllowed() ? "REED" : "FED"; } else { roleStr = Get().IsRxOnWhenIdle() ? "MED" : "SED"; } break; default: roleStr = "Unknown"; break; } return roleStr; } //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Cli Node::CliOutputLine &Node::CliOutputLine::operator=(const CliOutputLine &aOther) { SuccessOrQuit(mLine.Set(aOther.mLine)); return *this; } Node::CliOutputLine &Node::CliOutputLine::operator=(CliOutputLine &&aOther) { mLine.TakeFrom(aOther.mLine.Move()); return *this; } void Node::InputCli(const char *aFormat, ...) { static constexpr uint16_t kMaxCommandSize = 256; va_list args; char command[kMaxCommandSize]; StringWriter writer(command, kMaxCommandSize); va_start(args, aFormat); writer.AppendVarArgs(aFormat, args); VerifyOrExit(!writer.IsTruncated()); ClearCliOutput(); mCliInterpreter.ProcessLine(command); exit: va_end(args); } int Node::HandleCliOutput(void *aContext, const char *aFormat, va_list aArguments) { return static_cast(aContext)->HandleCliOutput(aFormat, aArguments); } int Node::HandleCliOutput(const char *aFormat, va_list aArguments) { uint16_t length; // Generate output - determine number of chars written length = mCliCurOutputLine.GetLength(); mCliCurOutputLine.AppendVarArgs(aFormat, aArguments); VerifyOrQuit(!mCliCurOutputLine.IsTruncated()); length = mCliCurOutputLine.GetLength() - length; // Search for `\n` and parse lines one by one while (true) { char lineString[CliOutputLine::kMaxLineSize]; char *end; CliOutputLine *lineEnrry; SuccessOrQuit(StringCopy(lineString, mCliCurOutputLine.AsCString())); end = AsNonConst(StringFind(lineString, '\n')); VerifyOrExit(end != nullptr); mCliCurOutputLine.Clear(); mCliCurOutputLine.Append("%s", end + 1); *end = kNullChar; if (end > &lineString[0]) { end--; if (*end == '\r') { *end = kNullChar; } } // Push the full line into the `mCliOutputLines` array lineEnrry = mCliOutputLines.PushBack(); VerifyOrQuit(lineEnrry != nullptr); SuccessOrQuit(lineEnrry->mLine.Set(lineString)); } exit: return static_cast(length); } bool Node::IsCliOutputSuccess(void) { bool isSuccess = false; VerifyOrExit(mCliOutputLines.GetLength() > 0); VerifyOrExit(mCliOutputLines.Back()->IsDone()); isSuccess = true; exit: return isSuccess; } //---------------------------------------------------------------------------------------------------------------------- void AllowLinkBetween(Node &aFirstNode, Node &aSecondNode) { aFirstNode.AllowList(aSecondNode); aSecondNode.AllowList(aFirstNode); } void UnallowLinkBetween(Node &aFirstNode, Node &aSecondNode) { aFirstNode.UnallowList(aSecondNode); aSecondNode.UnallowList(aFirstNode); } } // namespace Nexus } // namespace ot