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 #include "instance/instance.hpp"
37
38 namespace ot {
39
40 #if OPENTHREAD_FTD || OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
41
GetMacAddress(Mac::Address & aMacAddress) const42 const Mac::Address &IndirectSender::NeighborInfo::GetMacAddress(Mac::Address &aMacAddress) const
43 {
44 if (mUseShortAddress)
45 {
46 aMacAddress.SetShort(static_cast<const CslNeighbor *>(this)->GetRloc16());
47 }
48 else
49 {
50 aMacAddress.SetExtended(static_cast<const CslNeighbor *>(this)->GetExtAddress());
51 }
52
53 return aMacAddress;
54 }
55
IndirectSender(Instance & aInstance)56 IndirectSender::IndirectSender(Instance &aInstance)
57 : InstanceLocator(aInstance)
58 , mEnabled(false)
59 #if OPENTHREAD_FTD
60 , mSourceMatchController(aInstance)
61 , mDataPollHandler(aInstance)
62 #endif
63 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
64 , mCslTxScheduler(aInstance)
65 #endif
66 {
67 }
68
Stop(void)69 void IndirectSender::Stop(void)
70 {
71 VerifyOrExit(mEnabled);
72
73 #if OPENTHREAD_FTD
74 for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptInvalid))
75 {
76 child.SetIndirectMessage(nullptr);
77 mSourceMatchController.ResetMessageCount(child);
78 }
79
80 mDataPollHandler.Clear();
81 #endif
82
83 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
84 mCslTxScheduler.Clear();
85 #endif
86
87 exit:
88 mEnabled = false;
89 }
90
91 #if OPENTHREAD_FTD
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.GetIndirectTxChildMask().Has(childIndex));
101
102 aMessage.GetIndirectTxChildMask().Add(childIndex);
103 mSourceMatchController.IncrementMessageCount(aChild);
104
105 if ((aMessage.GetType() != Message::kTypeSupervision) && (aChild.GetIndirectMessageCount() > 1))
106 {
107 Message *supervisionMessage = FindQueuedMessageForSleepyChild(aChild, AcceptSupervisionMessage);
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.GetIndirectTxChildMask().Has(childIndex), error = kErrorNotFound);
128
129 aMessage.GetIndirectTxChildMask().Remove(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 VerifyOrExit(aChild.GetIndirectMessageCount() > 0);
141
142 for (Message &message : Get<MeshForwarder>().mSendQueue)
143 {
144 message.GetIndirectTxChildMask().Remove(Get<ChildTable>().GetChildIndex(aChild));
145
146 Get<MeshForwarder>().RemoveMessageIfNoPendingTx(message);
147 }
148
149 aChild.SetIndirectMessage(nullptr);
150 mSourceMatchController.ResetMessageCount(aChild);
151
152 mDataPollHandler.RequestFrameChange(DataPollHandler::kPurgeFrame, aChild);
153 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
154 mCslTxScheduler.Update();
155 #endif
156
157 exit:
158 return;
159 }
160
FindQueuedMessageForSleepyChild(const Child & aChild,MessageChecker aChecker) const161 const Message *IndirectSender::FindQueuedMessageForSleepyChild(const Child &aChild, MessageChecker aChecker) const
162 {
163 const Message *match = nullptr;
164 uint16_t childIndex = Get<ChildTable>().GetChildIndex(aChild);
165
166 for (const Message &message : Get<MeshForwarder>().mSendQueue)
167 {
168 if (message.GetIndirectTxChildMask().Has(childIndex) && aChecker(message))
169 {
170 match = &message;
171 break;
172 }
173 }
174
175 return match;
176 }
177
SetChildUseShortAddress(Child & aChild,bool aUseShortAddress)178 void IndirectSender::SetChildUseShortAddress(Child &aChild, bool aUseShortAddress)
179 {
180 VerifyOrExit(aChild.IsIndirectSourceMatchShort() != aUseShortAddress);
181
182 mSourceMatchController.SetSrcMatchAsShort(aChild, aUseShortAddress);
183
184 exit:
185 return;
186 }
187
HandleChildModeChange(Child & aChild,Mle::DeviceMode aOldMode)188 void IndirectSender::HandleChildModeChange(Child &aChild, Mle::DeviceMode aOldMode)
189 {
190 if (!aChild.IsRxOnWhenIdle() && (aChild.IsStateValid()))
191 {
192 SetChildUseShortAddress(aChild, true);
193 }
194
195 // On sleepy to non-sleepy mode change, convert indirect messages in
196 // the send queue destined to the child to direct.
197
198 if (!aOldMode.IsRxOnWhenIdle() && aChild.IsRxOnWhenIdle() && (aChild.GetIndirectMessageCount() > 0))
199 {
200 uint16_t childIndex = Get<ChildTable>().GetChildIndex(aChild);
201
202 for (Message &message : Get<MeshForwarder>().mSendQueue)
203 {
204 if (message.GetIndirectTxChildMask().Has(childIndex))
205 {
206 message.GetIndirectTxChildMask().Remove(childIndex);
207 message.SetDirectTransmission();
208 message.SetTimestampToNow();
209 }
210 }
211
212 aChild.SetIndirectMessage(nullptr);
213 mSourceMatchController.ResetMessageCount(aChild);
214
215 mDataPollHandler.RequestFrameChange(DataPollHandler::kPurgeFrame, aChild);
216 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
217 mCslTxScheduler.Update();
218 #endif
219 }
220
221 // Since the queuing delays for direct transmissions are expected to
222 // be relatively small especially when compared to indirect, for a
223 // non-sleepy to sleepy mode change, we allow any direct message
224 // (for the child) already in the send queue to remain as is. This
225 // is equivalent to dropping the already queued messages in this
226 // case.
227 }
228
RequestMessageUpdate(Child & aChild)229 void IndirectSender::RequestMessageUpdate(Child &aChild)
230 {
231 Message *curMessage = aChild.GetIndirectMessage();
232 Message *newMessage;
233
234 // Purge the frame if the current message is no longer destined
235 // for the child. This check needs to be done first to cover the
236 // case where we have a pending "replace frame" request and while
237 // waiting for the callback, the current message is removed.
238
239 if ((curMessage != nullptr) && !curMessage->GetIndirectTxChildMask().Has(Get<ChildTable>().GetChildIndex(aChild)))
240 {
241 // Set the indirect message for this child to `nullptr` to ensure
242 // it is not processed on `HandleSentFrameToChild()` callback.
243
244 aChild.SetIndirectMessage(nullptr);
245
246 // Request a "frame purge" using `RequestFrameChange()` and
247 // wait for `HandleFrameChangeDone()` callback for completion
248 // of the request. Note that the callback may be directly
249 // called from the `RequestFrameChange()` itself when the
250 // request can be handled immediately.
251
252 aChild.SetWaitingForMessageUpdate(true);
253 mDataPollHandler.RequestFrameChange(DataPollHandler::kPurgeFrame, aChild);
254 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
255 mCslTxScheduler.Update();
256 #endif
257
258 ExitNow();
259 }
260
261 VerifyOrExit(!aChild.IsWaitingForMessageUpdate());
262
263 newMessage = FindQueuedMessageForSleepyChild(aChild, AcceptAnyMessage);
264
265 VerifyOrExit(curMessage != newMessage);
266
267 if (curMessage == nullptr)
268 {
269 // Current message is `nullptr`, but new message is not.
270 // We have a new indirect message.
271
272 UpdateIndirectMessage(aChild);
273 ExitNow();
274 }
275
276 // Current message and new message differ and are both
277 // non-`nullptr`. We need to request the frame to be replaced.
278 // The current indirect message can be replaced only if it is
279 // the first fragment. If a next fragment frame for message is
280 // already prepared, we wait for the entire message to be
281 // delivered.
282
283 VerifyOrExit(aChild.GetIndirectFragmentOffset() == 0);
284
285 aChild.SetWaitingForMessageUpdate(true);
286 mDataPollHandler.RequestFrameChange(DataPollHandler::kReplaceFrame, aChild);
287 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
288 mCslTxScheduler.Update();
289 #endif
290
291 exit:
292 return;
293 }
294
HandleFrameChangeDone(Child & aChild)295 void IndirectSender::HandleFrameChangeDone(Child &aChild)
296 {
297 VerifyOrExit(aChild.IsWaitingForMessageUpdate());
298 UpdateIndirectMessage(aChild);
299
300 exit:
301 return;
302 }
303
UpdateIndirectMessage(Child & aChild)304 void IndirectSender::UpdateIndirectMessage(Child &aChild)
305 {
306 Message *message = FindQueuedMessageForSleepyChild(aChild, AcceptAnyMessage);
307
308 aChild.SetWaitingForMessageUpdate(false);
309 aChild.SetIndirectMessage(message);
310 aChild.SetIndirectFragmentOffset(0);
311 aChild.SetIndirectTxSuccess(true);
312
313 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
314 mCslTxScheduler.Update();
315 #endif
316
317 if (message != nullptr)
318 {
319 Mac::Address childAddress;
320
321 aChild.GetMacAddress(childAddress);
322 Get<MeshForwarder>().LogMessage(MeshForwarder::kMessagePrepareIndirect, *message, kErrorNone, &childAddress);
323 }
324 }
325
PrepareFrameForChild(Mac::TxFrame & aFrame,FrameContext & aContext,Child & aChild)326 Error IndirectSender::PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &aContext, Child &aChild)
327 {
328 Error error = kErrorNone;
329 Message *message = aChild.GetIndirectMessage();
330
331 VerifyOrExit(mEnabled, error = kErrorAbort);
332
333 if (message == nullptr)
334 {
335 PrepareEmptyFrame(aFrame, aChild, /* aAckRequest */ true);
336 aContext.mMessageNextOffset = 0;
337 ExitNow();
338 }
339
340 switch (message->GetType())
341 {
342 case Message::kTypeIp6:
343 aContext.mMessageNextOffset = PrepareDataFrame(aFrame, aChild, *message);
344 break;
345
346 case Message::kTypeSupervision:
347 PrepareEmptyFrame(aFrame, aChild, /* aAckRequest */ true);
348 aContext.mMessageNextOffset = message->GetLength();
349 break;
350
351 default:
352 OT_ASSERT(false);
353 }
354
355 exit:
356 return error;
357 }
358
PrepareDataFrame(Mac::TxFrame & aFrame,Child & aChild,Message & aMessage)359 uint16_t IndirectSender::PrepareDataFrame(Mac::TxFrame &aFrame, Child &aChild, Message &aMessage)
360 {
361 Ip6::Header ip6Header;
362 Mac::Addresses macAddrs;
363 uint16_t directTxOffset;
364 uint16_t nextOffset;
365
366 // Determine the MAC source and destination addresses.
367
368 IgnoreError(aMessage.Read(0, ip6Header));
369
370 Get<MeshForwarder>().GetMacSourceAddress(ip6Header.GetSource(), macAddrs.mSource);
371
372 if (ip6Header.GetDestination().IsLinkLocalUnicast())
373 {
374 Get<MeshForwarder>().GetMacDestinationAddress(ip6Header.GetDestination(), macAddrs.mDestination);
375 }
376 else
377 {
378 aChild.GetMacAddress(macAddrs.mDestination);
379 }
380
381 // Prepare the data frame from previous child's indirect offset.
382
383 directTxOffset = aMessage.GetOffset();
384 aMessage.SetOffset(aChild.GetIndirectFragmentOffset());
385
386 nextOffset = Get<MeshForwarder>().PrepareDataFrameWithNoMeshHeader(aFrame, aMessage, macAddrs);
387
388 aMessage.SetOffset(directTxOffset);
389
390 // Set `FramePending` if there are more queued messages (excluding
391 // the current one being sent out) for the child (note `> 1` check).
392 // The case where the current message itself requires fragmentation
393 // is already checked and handled in `PrepareDataFrame()` method.
394
395 if (aChild.GetIndirectMessageCount() > 1)
396 {
397 aFrame.SetFramePending(true);
398 }
399
400 return nextOffset;
401 }
402
PrepareEmptyFrame(Mac::TxFrame & aFrame,Child & aChild,bool aAckRequest)403 void IndirectSender::PrepareEmptyFrame(Mac::TxFrame &aFrame, Child &aChild, bool aAckRequest)
404 {
405 Mac::Address macDest;
406 aChild.GetMacAddress(macDest);
407 Get<MeshForwarder>().PrepareEmptyFrame(aFrame, macDest, aAckRequest);
408 }
409
HandleSentFrameToChild(const Mac::TxFrame & aFrame,const FrameContext & aContext,Error aError,Child & aChild)410 void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame,
411 const FrameContext &aContext,
412 Error aError,
413 Child &aChild)
414 {
415 Message *message = aChild.GetIndirectMessage();
416 uint16_t nextOffset = aContext.mMessageNextOffset;
417
418 VerifyOrExit(mEnabled);
419
420 if (aError == kErrorNone)
421 {
422 Get<ChildSupervisor>().UpdateOnSend(aChild);
423 }
424
425 // A zero `nextOffset` indicates that the sent frame is an empty
426 // frame generated by `PrepareFrameForChild()` when there was no
427 // indirect message in the send queue for the child. This can happen
428 // in the (not common) case where the radio platform does not
429 // support the "source address match" feature and always includes
430 // "frame pending" flag in acks to data poll frames. In such a case,
431 // `IndirectSender` prepares and sends an empty frame to the child
432 // after it sends a data poll. Here in `HandleSentFrameToChild()` we
433 // exit quickly if we detect the "send done" is for the empty frame
434 // to ensure we do not update any newly added indirect message after
435 // preparing the empty frame.
436
437 VerifyOrExit(nextOffset != 0);
438
439 switch (aError)
440 {
441 case kErrorNone:
442 break;
443
444 case kErrorNoAck:
445 case kErrorChannelAccessFailure:
446 case kErrorAbort:
447
448 aChild.SetIndirectTxSuccess(false);
449
450 #if OPENTHREAD_CONFIG_DROP_MESSAGE_ON_FRAGMENT_TX_FAILURE
451 // We set the nextOffset to end of message, since there is no need to
452 // send any remaining fragments in the message to the child, if all tx
453 // attempts of current frame already failed.
454
455 if (message != nullptr)
456 {
457 nextOffset = message->GetLength();
458 }
459 #endif
460 break;
461
462 default:
463 OT_ASSERT(false);
464 }
465
466 if ((message != nullptr) && (nextOffset < message->GetLength()))
467 {
468 aChild.SetIndirectFragmentOffset(nextOffset);
469 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
470 mCslTxScheduler.Update();
471 #endif
472 ExitNow();
473 }
474
475 if (message != nullptr)
476 {
477 // The indirect tx of this message to the child is done.
478
479 Error txError = aError;
480 uint16_t childIndex = Get<ChildTable>().GetChildIndex(aChild);
481 Mac::Address macDest;
482
483 aChild.SetIndirectMessage(nullptr);
484 aChild.GetLinkInfo().AddMessageTxStatus(aChild.GetIndirectTxSuccess());
485
486 // Enable short source address matching after the first indirect
487 // message transmission attempt to the child. We intentionally do
488 // not check for successful tx here to address the scenario where
489 // the child does receive "Child ID Response" but parent misses the
490 // 15.4 ack from child. If the "Child ID Response" does not make it
491 // to the child, then the child will need to send a new "Child ID
492 // Request" which will cause the parent to switch to using long
493 // address mode for source address matching.
494
495 mSourceMatchController.SetSrcMatchAsShort(aChild, true);
496
497 #if !OPENTHREAD_CONFIG_DROP_MESSAGE_ON_FRAGMENT_TX_FAILURE
498
499 // When `CONFIG_DROP_MESSAGE_ON_FRAGMENT_TX_FAILURE` is
500 // disabled, all fragment frames of a larger message are
501 // sent even if the transmission of an earlier fragment fail.
502 // Note that `GetIndirectTxSuccess() tracks the tx success of
503 // the entire message to the child, while `txError = aError`
504 // represents the error status of the last fragment frame
505 // transmission.
506
507 if (!aChild.GetIndirectTxSuccess() && (txError == kErrorNone))
508 {
509 txError = kErrorFailed;
510 }
511 #endif
512
513 if (!aFrame.IsEmpty())
514 {
515 IgnoreError(aFrame.GetDstAddr(macDest));
516 Get<MeshForwarder>().LogMessage(MeshForwarder::kMessageTransmit, *message, txError, &macDest);
517 }
518
519 if (message->GetType() == Message::kTypeIp6)
520 {
521 if (aChild.GetIndirectTxSuccess())
522 {
523 Get<MeshForwarder>().mIpCounters.mTxSuccess++;
524 }
525 else
526 {
527 Get<MeshForwarder>().mIpCounters.mTxFailure++;
528 }
529 }
530
531 if (message->GetIndirectTxChildMask().Has(childIndex))
532 {
533 message->GetIndirectTxChildMask().Remove(childIndex);
534 mSourceMatchController.DecrementMessageCount(aChild);
535 }
536
537 Get<MeshForwarder>().RemoveMessageIfNoPendingTx(*message);
538 }
539
540 UpdateIndirectMessage(aChild);
541
542 exit:
543 if (mEnabled)
544 {
545 ClearMessagesForRemovedChildren();
546 }
547 }
548
ClearMessagesForRemovedChildren(void)549 void IndirectSender::ClearMessagesForRemovedChildren(void)
550 {
551 for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptValidOrRestoring))
552 {
553 if (child.GetIndirectMessageCount() == 0)
554 {
555 continue;
556 }
557
558 ClearAllMessagesForSleepyChild(child);
559 }
560 }
561
AcceptAnyMessage(const Message & aMessage)562 bool IndirectSender::AcceptAnyMessage(const Message &aMessage)
563 {
564 OT_UNUSED_VARIABLE(aMessage);
565
566 return true;
567 }
568
AcceptSupervisionMessage(const Message & aMessage)569 bool IndirectSender::AcceptSupervisionMessage(const Message &aMessage)
570 {
571 return aMessage.GetType() == Message::kTypeSupervision;
572 }
573
574 #endif // OPENTHREAD_FTD
575
576 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
577
PrepareFrameForCslNeighbor(Mac::TxFrame & aFrame,FrameContext & aContext,CslNeighbor & aCslNeighbor)578 Error IndirectSender::PrepareFrameForCslNeighbor(Mac::TxFrame &aFrame,
579 FrameContext &aContext,
580 CslNeighbor &aCslNeighbor)
581 {
582 Error error = kErrorNotFound;
583
584 #if OPENTHREAD_FTD
585 // `CslNeighbor` can only be a `Child` for now, but can be changed later.
586 error = PrepareFrameForChild(aFrame, aContext, static_cast<Child &>(aCslNeighbor));
587 #else
588 OT_UNUSED_VARIABLE(aFrame);
589 OT_UNUSED_VARIABLE(aContext);
590 OT_UNUSED_VARIABLE(aCslNeighbor);
591 #endif
592
593 return error;
594 }
595
HandleSentFrameToCslNeighbor(const Mac::TxFrame & aFrame,const FrameContext & aContext,Error aError,CslNeighbor & aCslNeighbor)596 void IndirectSender::HandleSentFrameToCslNeighbor(const Mac::TxFrame &aFrame,
597 const FrameContext &aContext,
598 Error aError,
599 CslNeighbor &aCslNeighbor)
600 {
601 #if OPENTHREAD_FTD
602 HandleSentFrameToChild(aFrame, aContext, aError, static_cast<Child &>(aCslNeighbor));
603 #else
604 OT_UNUSED_VARIABLE(aFrame);
605 OT_UNUSED_VARIABLE(aContext);
606 OT_UNUSED_VARIABLE(aError);
607 OT_UNUSED_VARIABLE(aCslNeighbor);
608 #endif
609 }
610
611 #endif // OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
612
613 #endif // OPENTHREAD_FTD || OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
614
615 } // namespace ot
616