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