1 /*
2  *    Copyright (c) 2019, The OpenThread Authors.
3  *    All rights reserved.
4  *
5  *    Redistribution and use in source and binary forms, with or without
6  *    modification, are permitted provided that the following conditions are met:
7  *    1. Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *    2. Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *    3. Neither the name of the copyright holder nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17  *    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  *    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  *    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
20  *    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  *    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  *    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  *    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  *    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  *    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 /**
29  * @file
30  *   This file implements Thread Radio Encapsulation Link (TREL) interface.
31  */
32 
33 #include "trel_interface.hpp"
34 
35 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
36 
37 #include <string.h>
38 
39 #include "common/array.hpp"
40 #include "common/as_core_type.hpp"
41 #include "common/code_utils.hpp"
42 #include "common/debug.hpp"
43 #include "common/locator_getters.hpp"
44 #include "common/log.hpp"
45 #include "common/string.hpp"
46 #include "instance/instance.hpp"
47 #include "net/dns_types.hpp"
48 
49 namespace ot {
50 namespace Trel {
51 
52 RegisterLogModule("TrelInterface");
53 
54 const char Interface::kTxtRecordExtAddressKey[] = "xa";
55 const char Interface::kTxtRecordExtPanIdKey[]   = "xp";
56 
Interface(Instance & aInstance)57 Interface::Interface(Instance &aInstance)
58     : InstanceLocator(aInstance)
59     , mInitialized(false)
60     , mEnabled(false)
61     , mFiltered(false)
62     , mRegisterServiceTask(aInstance)
63 {
64 }
65 
Init(void)66 void Interface::Init(void)
67 {
68     OT_ASSERT(!mInitialized);
69 
70     mInitialized = true;
71 
72     if (mEnabled)
73     {
74         mEnabled = false;
75         Enable();
76     }
77 }
78 
SetEnabled(bool aEnable)79 void Interface::SetEnabled(bool aEnable)
80 {
81     if (aEnable)
82     {
83         Enable();
84     }
85     else
86     {
87         Disable();
88     }
89 }
90 
Enable(void)91 void Interface::Enable(void)
92 {
93     VerifyOrExit(!mEnabled);
94 
95     mEnabled = true;
96     VerifyOrExit(mInitialized);
97 
98     otPlatTrelEnable(&GetInstance(), &mUdpPort);
99 
100     LogInfo("Enabled interface, local port:%u", mUdpPort);
101     mRegisterServiceTask.Post();
102 
103 exit:
104     return;
105 }
106 
Disable(void)107 void Interface::Disable(void)
108 {
109     VerifyOrExit(mEnabled);
110 
111     mEnabled = false;
112     VerifyOrExit(mInitialized);
113 
114     otPlatTrelDisable(&GetInstance());
115     mPeerTable.Clear();
116     LogDebg("Disabled interface");
117 
118 exit:
119     return;
120 }
121 
HandleExtAddressChange(void)122 void Interface::HandleExtAddressChange(void)
123 {
124     VerifyOrExit(mInitialized && mEnabled);
125     LogDebg("Extended Address changed, re-registering DNS-SD service");
126     mRegisterServiceTask.Post();
127 
128 exit:
129     return;
130 }
131 
HandleExtPanIdChange(void)132 void Interface::HandleExtPanIdChange(void)
133 {
134     VerifyOrExit(mInitialized && mEnabled);
135     LogDebg("Extended PAN ID changed, re-registering DNS-SD service");
136     mRegisterServiceTask.Post();
137 
138 exit:
139     return;
140 }
141 
RegisterService(void)142 void Interface::RegisterService(void)
143 {
144     // TXT data consists of two entries: the length fields, the
145     // "key" string, "=" char, and binary representation of the MAC
146     // or Extended PAN ID values.
147     static constexpr uint8_t kTxtDataSize =
148         /* ExtAddr  */ sizeof(uint8_t) + sizeof(kTxtRecordExtAddressKey) - 1 + sizeof(char) + sizeof(Mac::ExtAddress) +
149         /* ExtPanId */ sizeof(uint8_t) + sizeof(kTxtRecordExtPanIdKey) - 1 + sizeof(char) +
150         sizeof(MeshCoP::ExtendedPanId);
151 
152     uint8_t                        txtDataBuffer[kTxtDataSize];
153     MutableData<kWithUint16Length> txtData;
154     Dns::TxtEntry                  txtEntries[2];
155 
156     VerifyOrExit(mInitialized && mEnabled);
157 
158     txtEntries[0].Init(kTxtRecordExtAddressKey, Get<Mac::Mac>().GetExtAddress().m8, sizeof(Mac::ExtAddress));
159     txtEntries[1].Init(kTxtRecordExtPanIdKey, Get<MeshCoP::ExtendedPanIdManager>().GetExtPanId().m8,
160                        sizeof(MeshCoP::ExtendedPanId));
161 
162     txtData.Init(txtDataBuffer, sizeof(txtDataBuffer));
163     SuccessOrAssert(Dns::TxtEntry::AppendEntries(txtEntries, GetArrayLength(txtEntries), txtData));
164 
165     LogInfo("Registering DNS-SD service: port:%u, txt:\"%s=%s, %s=%s\"", mUdpPort, kTxtRecordExtAddressKey,
166             Get<Mac::Mac>().GetExtAddress().ToString().AsCString(), kTxtRecordExtPanIdKey,
167             Get<MeshCoP::ExtendedPanIdManager>().GetExtPanId().ToString().AsCString());
168 
169     otPlatTrelRegisterService(&GetInstance(), mUdpPort, txtData.GetBytes(), static_cast<uint8_t>(txtData.GetLength()));
170 
171 exit:
172     return;
173 }
174 
otPlatTrelHandleDiscoveredPeerInfo(otInstance * aInstance,const otPlatTrelPeerInfo * aInfo)175 extern "C" void otPlatTrelHandleDiscoveredPeerInfo(otInstance *aInstance, const otPlatTrelPeerInfo *aInfo)
176 {
177     Instance &instance = AsCoreType(aInstance);
178 
179     VerifyOrExit(instance.IsInitialized());
180     instance.Get<Interface>().HandleDiscoveredPeerInfo(*static_cast<const Interface::Peer::Info *>(aInfo));
181 
182 exit:
183     return;
184 }
185 
HandleDiscoveredPeerInfo(const Peer::Info & aInfo)186 void Interface::HandleDiscoveredPeerInfo(const Peer::Info &aInfo)
187 {
188     Peer                  *entry;
189     Mac::ExtAddress        extAddress;
190     MeshCoP::ExtendedPanId extPanId;
191     bool                   isNew = false;
192 
193     VerifyOrExit(mInitialized && mEnabled);
194 
195     SuccessOrExit(ParsePeerInfoTxtData(aInfo, extAddress, extPanId));
196 
197     VerifyOrExit(extAddress != Get<Mac::Mac>().GetExtAddress());
198 
199     if (aInfo.IsRemoved())
200     {
201         entry = mPeerTable.FindMatching(extAddress);
202         VerifyOrExit(entry != nullptr);
203         RemovePeerEntry(*entry);
204         ExitNow();
205     }
206 
207     // It is a new entry or an update to an existing entry. First
208     // check whether we have an existing entry that matches the same
209     // socket address, and remove it if it is associated with a
210     // different Extended MAC address. This ensures that we do not
211     // keep stale entries in the peer table.
212 
213     entry = mPeerTable.FindMatching(aInfo.GetSockAddr());
214 
215     if ((entry != nullptr) && !entry->Matches(extAddress))
216     {
217         RemovePeerEntry(*entry);
218         entry = nullptr;
219     }
220 
221     if (entry == nullptr)
222     {
223         entry = mPeerTable.FindMatching(extAddress);
224     }
225 
226     if (entry == nullptr)
227     {
228         entry = GetNewPeerEntry();
229         VerifyOrExit(entry != nullptr);
230 
231         entry->SetExtAddress(extAddress);
232         isNew = true;
233     }
234 
235     if (!isNew)
236     {
237         VerifyOrExit((entry->GetExtPanId() != extPanId) || (entry->GetSockAddr() != aInfo.GetSockAddr()));
238     }
239 
240     entry->SetExtPanId(extPanId);
241     entry->SetSockAddr(aInfo.GetSockAddr());
242 
243     entry->Log(isNew ? "Added" : "Updated");
244 
245 exit:
246     return;
247 }
248 
ParsePeerInfoTxtData(const Peer::Info & aInfo,Mac::ExtAddress & aExtAddress,MeshCoP::ExtendedPanId & aExtPanId) const249 Error Interface::ParsePeerInfoTxtData(const Peer::Info       &aInfo,
250                                       Mac::ExtAddress        &aExtAddress,
251                                       MeshCoP::ExtendedPanId &aExtPanId) const
252 {
253     Error                   error;
254     Dns::TxtEntry           entry;
255     Dns::TxtEntry::Iterator iterator;
256     bool                    parsedExtAddress = false;
257     bool                    parsedExtPanId   = false;
258 
259     aExtPanId.Clear();
260 
261     iterator.Init(aInfo.GetTxtData(), aInfo.GetTxtLength());
262 
263     while ((error = iterator.GetNextEntry(entry)) == kErrorNone)
264     {
265         // If the TXT data happens to have entries with key longer
266         // than `kMaxIterKeyLength`, `mKey` would be `nullptr` and full
267         // entry would be placed in `mValue`. We skip over such
268         // entries.
269         if (entry.mKey == nullptr)
270         {
271             continue;
272         }
273 
274         if (strcmp(entry.mKey, kTxtRecordExtAddressKey) == 0)
275         {
276             VerifyOrExit(!parsedExtAddress, error = kErrorParse);
277             VerifyOrExit(entry.mValueLength == sizeof(Mac::ExtAddress), error = kErrorParse);
278             aExtAddress.Set(entry.mValue);
279             parsedExtAddress = true;
280         }
281         else if (strcmp(entry.mKey, kTxtRecordExtPanIdKey) == 0)
282         {
283             VerifyOrExit(!parsedExtPanId, error = kErrorParse);
284             VerifyOrExit(entry.mValueLength == sizeof(MeshCoP::ExtendedPanId), error = kErrorParse);
285             memcpy(aExtPanId.m8, entry.mValue, sizeof(MeshCoP::ExtendedPanId));
286             parsedExtPanId = true;
287         }
288 
289         // Skip over and ignore any unknown keys.
290     }
291 
292     VerifyOrExit(error == kErrorNotFound);
293     error = kErrorNone;
294 
295     VerifyOrExit(parsedExtAddress && parsedExtPanId, error = kErrorParse);
296 
297 exit:
298     return error;
299 }
300 
GetNewPeerEntry(void)301 Interface::Peer *Interface::GetNewPeerEntry(void)
302 {
303     Peer *peerEntry;
304 
305     peerEntry = mPeerTable.PushBack();
306     VerifyOrExit(peerEntry == nullptr);
307 
308     for (Peer &entry : mPeerTable)
309     {
310         if (entry.GetExtPanId() != Get<MeshCoP::ExtendedPanIdManager>().GetExtPanId())
311         {
312             ExitNow(peerEntry = &entry);
313         }
314     }
315 
316     for (Peer &entry : mPeerTable)
317     {
318         // We skip over any existing entry in neighbor table (even if the
319         // entry is in invalid state).
320 
321         if (Get<NeighborTable>().FindNeighbor(entry.GetExtAddress(), Neighbor::kInStateAny) != nullptr)
322         {
323             continue;
324         }
325 
326 #if OPENTHREAD_FTD
327         if (Get<NeighborTable>().FindRxOnlyNeighborRouter(entry.GetExtAddress()) != nullptr)
328         {
329             continue;
330         }
331 #endif
332 
333         ExitNow(peerEntry = &entry);
334     }
335 
336 exit:
337     return peerEntry;
338 }
339 
RemovePeerEntry(Peer & aEntry)340 void Interface::RemovePeerEntry(Peer &aEntry)
341 {
342     aEntry.Log("Removing");
343 
344     // Replace the entry being removed with the last entry (if not the
345     // last one already) and then pop the last entry from array.
346 
347     if (&aEntry != mPeerTable.Back())
348     {
349         aEntry = *mPeerTable.Back();
350     }
351 
352     mPeerTable.PopBack();
353 }
354 
GetCounters(void) const355 const Counters *Interface::GetCounters(void) const { return otPlatTrelGetCounters(&GetInstance()); }
356 
ResetCounters(void)357 void Interface::ResetCounters(void) { otPlatTrelResetCounters(&GetInstance()); }
358 
Send(const Packet & aPacket,bool aIsDiscovery)359 Error Interface::Send(const Packet &aPacket, bool aIsDiscovery)
360 {
361     Error error = kErrorNone;
362     Peer *peerEntry;
363 
364     VerifyOrExit(mInitialized && mEnabled, error = kErrorAbort);
365     VerifyOrExit(!mFiltered);
366 
367     switch (aPacket.GetHeader().GetType())
368     {
369     case Header::kTypeBroadcast:
370         for (Peer &entry : mPeerTable)
371         {
372             if (!aIsDiscovery && (entry.GetExtPanId() != Get<MeshCoP::ExtendedPanIdManager>().GetExtPanId()))
373             {
374                 continue;
375             }
376 
377             otPlatTrelSend(&GetInstance(), aPacket.GetBuffer(), aPacket.GetLength(), &entry.mSockAddr);
378         }
379         break;
380 
381     case Header::kTypeUnicast:
382     case Header::kTypeAck:
383         peerEntry = mPeerTable.FindMatching(aPacket.GetHeader().GetDestination());
384         VerifyOrExit(peerEntry != nullptr, error = kErrorAbort);
385         otPlatTrelSend(&GetInstance(), aPacket.GetBuffer(), aPacket.GetLength(), &peerEntry->mSockAddr);
386         break;
387     }
388 
389 exit:
390     return error;
391 }
392 
otPlatTrelHandleReceived(otInstance * aInstance,uint8_t * aBuffer,uint16_t aLength)393 extern "C" void otPlatTrelHandleReceived(otInstance *aInstance, uint8_t *aBuffer, uint16_t aLength)
394 {
395     Instance &instance = AsCoreType(aInstance);
396 
397     VerifyOrExit(instance.IsInitialized());
398     instance.Get<Interface>().HandleReceived(aBuffer, aLength);
399 
400 exit:
401     return;
402 }
403 
HandleReceived(uint8_t * aBuffer,uint16_t aLength)404 void Interface::HandleReceived(uint8_t *aBuffer, uint16_t aLength)
405 {
406     LogDebg("HandleReceived(aLength:%u)", aLength);
407 
408     VerifyOrExit(mInitialized && mEnabled && !mFiltered);
409 
410     mRxPacket.Init(aBuffer, aLength);
411     Get<Link>().ProcessReceivedPacket(mRxPacket);
412 
413 exit:
414     return;
415 }
416 
GetNextPeer(PeerIterator & aIterator) const417 const Interface::Peer *Interface::GetNextPeer(PeerIterator &aIterator) const
418 {
419     const Peer *entry = mPeerTable.At(aIterator);
420 
421     if (entry != nullptr)
422     {
423         aIterator++;
424     }
425 
426     return entry;
427 }
428 
Log(const char * aAction) const429 void Interface::Peer::Log(const char *aAction) const
430 {
431     OT_UNUSED_VARIABLE(aAction);
432 
433     LogInfo("%s peer mac:%s, xpan:%s, %s", aAction, GetExtAddress().ToString().AsCString(),
434             GetExtPanId().ToString().AsCString(), GetSockAddr().ToString().AsCString());
435 }
436 
437 } // namespace Trel
438 } // namespace ot
439 
440 #endif // #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
441