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"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 /**
30 * @file
31 * This file includes definitions for handling indirect transmission.
32 */
33
34 #include "indirect_sender.hpp"
35
36 #if OPENTHREAD_FTD
37
38 #include "common/code_utils.hpp"
39 #include "common/instance.hpp"
40 #include "common/locator_getters.hpp"
41 #include "common/logging.hpp"
42 #include "common/message.hpp"
43 #include "thread/mesh_forwarder.hpp"
44 #include "thread/mle_tlvs.hpp"
45 #include "thread/topology.hpp"
46
47 namespace ot {
48
GetMacAddress(Mac::Address & aMacAddress) const49 const Mac::Address &IndirectSender::ChildInfo::GetMacAddress(Mac::Address &aMacAddress) const
50 {
51 if (mUseShortAddress)
52 {
53 aMacAddress.SetShort(static_cast<const Child *>(this)->GetRloc16());
54 }
55 else
56 {
57 aMacAddress.SetExtended(static_cast<const Child *>(this)->GetExtAddress());
58 }
59
60 return aMacAddress;
61 }
62
IndirectSender(Instance & aInstance)63 IndirectSender::IndirectSender(Instance &aInstance)
64 : InstanceLocator(aInstance)
65 , mEnabled(false)
66 , mSourceMatchController(aInstance)
67 , mDataPollHandler(aInstance)
68 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
69 , mCslTxScheduler(aInstance)
70 #endif
71 {
72 }
73
Stop(void)74 void IndirectSender::Stop(void)
75 {
76 VerifyOrExit(mEnabled);
77
78 for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptInvalid))
79 {
80 child.SetIndirectMessage(nullptr);
81 mSourceMatchController.ResetMessageCount(child);
82 }
83
84 mDataPollHandler.Clear();
85 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
86 mCslTxScheduler.Clear();
87 #endif
88
89 exit:
90 mEnabled = false;
91 }
92
AddMessageForSleepyChild(Message & aMessage,Child & aChild)93 void IndirectSender::AddMessageForSleepyChild(Message &aMessage, Child &aChild)
94 {
95 uint16_t childIndex;
96
97 OT_ASSERT(!aChild.IsRxOnWhenIdle());
98
99 childIndex = Get<ChildTable>().GetChildIndex(aChild);
100 VerifyOrExit(!aMessage.GetChildMask(childIndex));
101
102 aMessage.SetChildMask(childIndex);
103 mSourceMatchController.IncrementMessageCount(aChild);
104
105 if ((aMessage.GetType() != Message::kTypeSupervision) && (aChild.GetIndirectMessageCount() > 1))
106 {
107 Message *supervisionMessage = FindIndirectMessage(aChild, /* aSupervisionTypeOnly */ true);
108
109 if (supervisionMessage != nullptr)
110 {
111 IgnoreError(RemoveMessageFromSleepyChild(*supervisionMessage, aChild));
112 Get<MeshForwarder>().RemoveMessageIfNoPendingTx(*supervisionMessage);
113 }
114 }
115
116 RequestMessageUpdate(aChild);
117
118 exit:
119 return;
120 }
121
RemoveMessageFromSleepyChild(Message & aMessage,Child & aChild)122 Error IndirectSender::RemoveMessageFromSleepyChild(Message &aMessage, Child &aChild)
123 {
124 Error error = kErrorNone;
125 uint16_t childIndex = Get<ChildTable>().GetChildIndex(aChild);
126
127 VerifyOrExit(aMessage.GetChildMask(childIndex), error = kErrorNotFound);
128
129 aMessage.ClearChildMask(childIndex);
130 mSourceMatchController.DecrementMessageCount(aChild);
131
132 RequestMessageUpdate(aChild);
133
134 exit:
135 return error;
136 }
137
ClearAllMessagesForSleepyChild(Child & aChild)138 void IndirectSender::ClearAllMessagesForSleepyChild(Child &aChild)
139 {
140 Message *message;
141 Message *nextMessage;
142
143 VerifyOrExit(aChild.GetIndirectMessageCount() > 0);
144
145 for (message = Get<MeshForwarder>().mSendQueue.GetHead(); message; message = nextMessage)
146 {
147 nextMessage = message->GetNext();
148
149 message->ClearChildMask(Get<ChildTable>().GetChildIndex(aChild));
150
151 Get<MeshForwarder>().RemoveMessageIfNoPendingTx(*message);
152 }
153
154 aChild.SetIndirectMessage(nullptr);
155 mSourceMatchController.ResetMessageCount(aChild);
156
157 mDataPollHandler.RequestFrameChange(DataPollHandler::kPurgeFrame, aChild);
158 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
159 mCslTxScheduler.Update();
160 #endif
161
162 exit:
163 return;
164 }
165
SetChildUseShortAddress(Child & aChild,bool aUseShortAddress)166 void IndirectSender::SetChildUseShortAddress(Child &aChild, bool aUseShortAddress)
167 {
168 VerifyOrExit(aChild.IsIndirectSourceMatchShort() != aUseShortAddress);
169
170 mSourceMatchController.SetSrcMatchAsShort(aChild, aUseShortAddress);
171
172 exit:
173 return;
174 }
175
HandleChildModeChange(Child & aChild,Mle::DeviceMode aOldMode)176 void IndirectSender::HandleChildModeChange(Child &aChild, Mle::DeviceMode aOldMode)
177 {
178 if (!aChild.IsRxOnWhenIdle() && (aChild.IsStateValid()))
179 {
180 SetChildUseShortAddress(aChild, true);
181 }
182
183 // On sleepy to non-sleepy mode change, convert indirect messages in
184 // the send queue destined to the child to direct.
185
186 if (!aOldMode.IsRxOnWhenIdle() && aChild.IsRxOnWhenIdle() && (aChild.GetIndirectMessageCount() > 0))
187 {
188 uint16_t childIndex = Get<ChildTable>().GetChildIndex(aChild);
189
190 for (Message *message = Get<MeshForwarder>().mSendQueue.GetHead(); message; message = message->GetNext())
191 {
192 if (message->GetChildMask(childIndex))
193 {
194 message->ClearChildMask(childIndex);
195 message->SetDirectTransmission();
196 }
197 }
198
199 aChild.SetIndirectMessage(nullptr);
200 mSourceMatchController.ResetMessageCount(aChild);
201
202 mDataPollHandler.RequestFrameChange(DataPollHandler::kPurgeFrame, aChild);
203 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
204 mCslTxScheduler.Update();
205 #endif
206 }
207
208 // Since the queuing delays for direct transmissions are expected to
209 // be relatively small especially when compared to indirect, for a
210 // non-sleepy to sleepy mode change, we allow any direct message
211 // (for the child) already in the send queue to remain as is. This
212 // is equivalent to dropping the already queued messages in this
213 // case.
214 }
215
FindIndirectMessage(Child & aChild,bool aSupervisionTypeOnly)216 Message *IndirectSender::FindIndirectMessage(Child &aChild, bool aSupervisionTypeOnly)
217 {
218 Message *message;
219 uint16_t childIndex = Get<ChildTable>().GetChildIndex(aChild);
220
221 for (message = Get<MeshForwarder>().mSendQueue.GetHead(); message; message = message->GetNext())
222 {
223 if (message->GetChildMask(childIndex) &&
224 (!aSupervisionTypeOnly || (message->GetType() == Message::kTypeSupervision)))
225 {
226 break;
227 }
228 }
229
230 return message;
231 }
232
RequestMessageUpdate(Child & aChild)233 void IndirectSender::RequestMessageUpdate(Child &aChild)
234 {
235 Message *curMessage = aChild.GetIndirectMessage();
236 Message *newMessage;
237
238 // Purge the frame if the current message is no longer destined
239 // for the child. This check needs to be done first to cover the
240 // case where we have a pending "replace frame" request and while
241 // waiting for the callback, the current message is removed.
242
243 if ((curMessage != nullptr) && !curMessage->GetChildMask(Get<ChildTable>().GetChildIndex(aChild)))
244 {
245 // Set the indirect message for this child to nullptr to ensure
246 // it is not processed on `HandleSentFrameToChild()` callback.
247
248 aChild.SetIndirectMessage(nullptr);
249
250 // Request a "frame purge" using `RequestFrameChange()` and
251 // wait for `HandleFrameChangeDone()` callback for completion
252 // of the request. Note that the callback may be directly
253 // called from the `RequestFrameChange()` itself when the
254 // request can be handled immediately.
255
256 aChild.SetWaitingForMessageUpdate(true);
257 mDataPollHandler.RequestFrameChange(DataPollHandler::kPurgeFrame, aChild);
258 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
259 mCslTxScheduler.Update();
260 #endif
261
262 ExitNow();
263 }
264
265 VerifyOrExit(!aChild.IsWaitingForMessageUpdate());
266
267 newMessage = FindIndirectMessage(aChild);
268
269 VerifyOrExit(curMessage != newMessage);
270
271 if (curMessage == nullptr)
272 {
273 // Current message is nullptr, but new message is not.
274 // We have a new indirect message.
275
276 UpdateIndirectMessage(aChild);
277 ExitNow();
278 }
279
280 // Current message and new message differ and are both non-nullptr.
281 // We need to request the frame to be replaced. The current
282 // indirect message can be replaced only if it is the first
283 // fragment. If a next fragment frame for message is already
284 // prepared, we wait for the entire message to be delivered.
285
286 VerifyOrExit(aChild.GetIndirectFragmentOffset() == 0);
287
288 aChild.SetWaitingForMessageUpdate(true);
289 mDataPollHandler.RequestFrameChange(DataPollHandler::kReplaceFrame, aChild);
290 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
291 mCslTxScheduler.Update();
292 #endif
293
294 exit:
295 return;
296 }
297
HandleFrameChangeDone(Child & aChild)298 void IndirectSender::HandleFrameChangeDone(Child &aChild)
299 {
300 VerifyOrExit(aChild.IsWaitingForMessageUpdate());
301 UpdateIndirectMessage(aChild);
302
303 exit:
304 return;
305 }
306
UpdateIndirectMessage(Child & aChild)307 void IndirectSender::UpdateIndirectMessage(Child &aChild)
308 {
309 Message *message = FindIndirectMessage(aChild);
310
311 aChild.SetWaitingForMessageUpdate(false);
312 aChild.SetIndirectMessage(message);
313 aChild.SetIndirectFragmentOffset(0);
314 aChild.SetIndirectTxSuccess(true);
315
316 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
317 mCslTxScheduler.Update();
318 #endif
319
320 if (message != nullptr)
321 {
322 Mac::Address childAddress;
323
324 mDataPollHandler.HandleNewFrame(aChild);
325
326 aChild.GetMacAddress(childAddress);
327 Get<MeshForwarder>().LogMessage(MeshForwarder::kMessagePrepareIndirect, *message, &childAddress, kErrorNone);
328 }
329 }
330
PrepareFrameForChild(Mac::TxFrame & aFrame,FrameContext & aContext,Child & aChild)331 Error IndirectSender::PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &aContext, Child &aChild)
332 {
333 Error error = kErrorNone;
334 Message *message = aChild.GetIndirectMessage();
335
336 VerifyOrExit(mEnabled, error = kErrorAbort);
337
338 if (message == nullptr)
339 {
340 PrepareEmptyFrame(aFrame, aChild, /* aAckRequest */ true);
341 aContext.mMessageNextOffset = 0;
342 ExitNow();
343 }
344
345 switch (message->GetType())
346 {
347 case Message::kTypeIp6:
348 aContext.mMessageNextOffset = PrepareDataFrame(aFrame, aChild, *message);
349 break;
350
351 case Message::kTypeSupervision:
352 PrepareEmptyFrame(aFrame, aChild, kSupervisionMsgAckRequest);
353 aContext.mMessageNextOffset = message->GetLength();
354 break;
355
356 default:
357 OT_ASSERT(false);
358 OT_UNREACHABLE_CODE(break);
359 }
360
361 exit:
362 return error;
363 }
364
PrepareDataFrame(Mac::TxFrame & aFrame,Child & aChild,Message & aMessage)365 uint16_t IndirectSender::PrepareDataFrame(Mac::TxFrame &aFrame, Child &aChild, Message &aMessage)
366 {
367 Ip6::Header ip6Header;
368 Mac::Address macSource, macDest;
369 uint16_t directTxOffset;
370 uint16_t nextOffset;
371
372 // Determine the MAC source and destination addresses.
373
374 IgnoreError(aMessage.Read(0, ip6Header));
375
376 Get<MeshForwarder>().GetMacSourceAddress(ip6Header.GetSource(), macSource);
377
378 if (ip6Header.GetDestination().IsLinkLocal())
379 {
380 Get<MeshForwarder>().GetMacDestinationAddress(ip6Header.GetDestination(), macDest);
381 }
382 else
383 {
384 aChild.GetMacAddress(macDest);
385 }
386
387 // Prepare the data frame from previous child's indirect offset.
388
389 directTxOffset = aMessage.GetOffset();
390 aMessage.SetOffset(aChild.GetIndirectFragmentOffset());
391
392 nextOffset = Get<MeshForwarder>().PrepareDataFrame(aFrame, aMessage, macSource, macDest);
393
394 aMessage.SetOffset(directTxOffset);
395
396 // Set `FramePending` if there are more queued messages (excluding
397 // the current one being sent out) for the child (note `> 1` check).
398 // The case where the current message itself requires fragmentation
399 // is already checked and handled in `PrepareDataFrame()` method.
400
401 if (aChild.GetIndirectMessageCount() > 1)
402 {
403 aFrame.SetFramePending(true);
404 }
405
406 return nextOffset;
407 }
408
PrepareEmptyFrame(Mac::TxFrame & aFrame,Child & aChild,bool aAckRequest)409 void IndirectSender::PrepareEmptyFrame(Mac::TxFrame &aFrame, Child &aChild, bool aAckRequest)
410 {
411 Mac::Address macDest;
412 aChild.GetMacAddress(macDest);
413 Get<MeshForwarder>().PrepareEmptyFrame(aFrame, macDest, aAckRequest);
414 }
415
HandleSentFrameToChild(const Mac::TxFrame & aFrame,const FrameContext & aContext,Error aError,Child & aChild)416 void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame,
417 const FrameContext &aContext,
418 Error aError,
419 Child & aChild)
420 {
421 Message *message = aChild.GetIndirectMessage();
422 uint16_t nextOffset = aContext.mMessageNextOffset;
423
424 VerifyOrExit(mEnabled);
425
426 #if OPENTHREAD_CONFIG_CHILD_SUPERVISION_ENABLE
427 if (aError == kErrorNone)
428 {
429 Get<Utils::ChildSupervisor>().UpdateOnSend(aChild);
430 }
431 #endif
432
433 // A zero `nextOffset` indicates that the sent frame is an empty
434 // frame generated by `PrepareFrameForChild()` when there was no
435 // indirect message in the send queue for the child. This can happen
436 // in the (not common) case where the radio platform does not
437 // support the "source address match" feature and always includes
438 // "frame pending" flag in acks to data poll frames. In such a case,
439 // `IndirectSender` prepares and sends an empty frame to the child
440 // after it sends a data poll. Here in `HandleSentFrameToChild()` we
441 // exit quickly if we detect the "send done" is for the empty frame
442 // to ensure we do not update any newly added indirect message after
443 // preparing the empty frame.
444
445 VerifyOrExit(nextOffset != 0);
446
447 switch (aError)
448 {
449 case kErrorNone:
450 break;
451
452 case kErrorNoAck:
453 case kErrorChannelAccessFailure:
454 case kErrorAbort:
455
456 aChild.SetIndirectTxSuccess(false);
457
458 #if OPENTHREAD_CONFIG_DROP_MESSAGE_ON_FRAGMENT_TX_FAILURE
459 // We set the nextOffset to end of message, since there is no need to
460 // send any remaining fragments in the message to the child, if all tx
461 // attempts of current frame already failed.
462
463 if (message != nullptr)
464 {
465 nextOffset = message->GetLength();
466 }
467 #endif
468 break;
469
470 default:
471 OT_ASSERT(false);
472 OT_UNREACHABLE_CODE(break);
473 }
474
475 if ((message != nullptr) && (nextOffset < message->GetLength()))
476 {
477 aChild.SetIndirectFragmentOffset(nextOffset);
478 mDataPollHandler.HandleNewFrame(aChild);
479 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
480 mCslTxScheduler.Update();
481 #endif
482 ExitNow();
483 }
484
485 if (message != nullptr)
486 {
487 // The indirect tx of this message to the child is done.
488
489 Error txError = aError;
490 uint16_t childIndex = Get<ChildTable>().GetChildIndex(aChild);
491 Mac::Address macDest;
492
493 aChild.SetIndirectMessage(nullptr);
494 aChild.GetLinkInfo().AddMessageTxStatus(aChild.GetIndirectTxSuccess());
495
496 // Enable short source address matching after the first indirect
497 // message transmission attempt to the child. We intentionally do
498 // not check for successful tx here to address the scenario where
499 // the child does receive "Child ID Response" but parent misses the
500 // 15.4 ack from child. If the "Child ID Response" does not make it
501 // to the child, then the child will need to send a new "Child ID
502 // Request" which will cause the parent to switch to using long
503 // address mode for source address matching.
504
505 mSourceMatchController.SetSrcMatchAsShort(aChild, true);
506
507 #if !OPENTHREAD_CONFIG_DROP_MESSAGE_ON_FRAGMENT_TX_FAILURE
508
509 // When `CONFIG_DROP_MESSAGE_ON_FRAGMENT_TX_FAILURE` is
510 // disabled, all fragment frames of a larger message are
511 // sent even if the transmission of an earlier fragment fail.
512 // Note that `GetIndirectTxSuccess() tracks the tx success of
513 // the entire message to the child, while `txError = aError`
514 // represents the error status of the last fragment frame
515 // transmission.
516
517 if (!aChild.GetIndirectTxSuccess() && (txError == kErrorNone))
518 {
519 txError = kErrorFailed;
520 }
521 #endif
522
523 if (!aFrame.IsEmpty())
524 {
525 IgnoreError(aFrame.GetDstAddr(macDest));
526 Get<MeshForwarder>().LogMessage(MeshForwarder::kMessageTransmit, *message, &macDest, txError);
527 }
528
529 if (message->GetType() == Message::kTypeIp6)
530 {
531 if (aChild.GetIndirectTxSuccess())
532 {
533 Get<MeshForwarder>().mIpCounters.mTxSuccess++;
534 }
535 else
536 {
537 Get<MeshForwarder>().mIpCounters.mTxFailure++;
538 }
539 }
540
541 if (message->GetChildMask(childIndex))
542 {
543 message->ClearChildMask(childIndex);
544 mSourceMatchController.DecrementMessageCount(aChild);
545 }
546
547 Get<MeshForwarder>().RemoveMessageIfNoPendingTx(*message);
548 }
549
550 UpdateIndirectMessage(aChild);
551
552 exit:
553 if (mEnabled)
554 {
555 ClearMessagesForRemovedChildren();
556 }
557 }
558
ClearMessagesForRemovedChildren(void)559 void IndirectSender::ClearMessagesForRemovedChildren(void)
560 {
561 for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptValidOrRestoring))
562 {
563 if (child.GetIndirectMessageCount() == 0)
564 {
565 continue;
566 }
567
568 ClearAllMessagesForSleepyChild(child);
569 }
570 }
571
572 } // namespace ot
573
574 #endif // #if OPENTHREAD_FTD
575