| /* |
| * Copyright (c) 2016, 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. |
| */ |
| |
| /** |
| * @file |
| * This file implements MPL. |
| */ |
| |
| #include <openthread/config.h> |
| |
| #include "ip6_mpl.hpp" |
| |
| #include <openthread/platform/random.h> |
| |
| #include "openthread-instance.h" |
| #include "common/code_utils.hpp" |
| #include "common/message.hpp" |
| #include "net/ip6.hpp" |
| |
| namespace ot { |
| namespace Ip6 { |
| |
| void MplBufferedMessageMetadata::GenerateNextTransmissionTime(uint32_t aCurrentTime, uint8_t aInterval) |
| { |
| // Emulate Trickle timer behavior and set up the next retransmission within [0,I) range. |
| uint8_t t = otPlatRandomGet() % aInterval; |
| |
| // Set transmission time at the beginning of the next interval. |
| SetTransmissionTime(aCurrentTime + GetIntervalOffset() + t); |
| SetIntervalOffset(aInterval - t); |
| } |
| |
| Mpl::Mpl(Ip6 &aIp6): |
| Ip6Locator(aIp6), |
| mSeedSetTimer(aIp6.mTimerScheduler, &Mpl::HandleSeedSetTimer, this), |
| mRetransmissionTimer(aIp6.mTimerScheduler, &Mpl::HandleRetransmissionTimer, this), |
| mTimerExpirations(0), |
| mSequence(0), |
| mSeedId(0), |
| mMatchingAddress(NULL) |
| { |
| memset(mSeedSet, 0, sizeof(mSeedSet)); |
| } |
| |
| void Mpl::InitOption(OptionMpl &aOption, const Address &aAddress) |
| { |
| aOption.Init(); |
| aOption.SetSequence(mSequence++); |
| |
| // Check if Seed Id can be elided. |
| if (mMatchingAddress && aAddress == *mMatchingAddress) |
| { |
| aOption.SetSeedIdLength(OptionMpl::kSeedIdLength0); |
| |
| // Decrease default option length. |
| aOption.SetLength(aOption.GetLength() - sizeof(mSeedId)); |
| } |
| else |
| { |
| aOption.SetSeedIdLength(OptionMpl::kSeedIdLength2); |
| aOption.SetSeedId(mSeedId); |
| } |
| } |
| |
| otError Mpl::UpdateSeedSet(uint16_t aSeedId, uint8_t aSequence) |
| { |
| otError error = OT_ERROR_NONE; |
| MplSeedEntry *entry = NULL; |
| int8_t diff; |
| |
| for (uint32_t i = 0; i < kNumSeedEntries; i++) |
| { |
| if (mSeedSet[i].GetLifetime() == 0) |
| { |
| // Start allocating from the first possible entry to speed up process of searching. |
| if (entry == NULL) |
| { |
| entry = &mSeedSet[i]; |
| } |
| } |
| else if (mSeedSet[i].GetSeedId() == aSeedId) |
| { |
| entry = &mSeedSet[i]; |
| diff = static_cast<int8_t>(aSequence - entry->GetSequence()); |
| |
| VerifyOrExit(diff > 0, error = OT_ERROR_DROP); |
| |
| break; |
| } |
| } |
| |
| VerifyOrExit(entry != NULL, error = OT_ERROR_DROP); |
| |
| entry->SetSeedId(aSeedId); |
| entry->SetSequence(aSequence); |
| entry->SetLifetime(kSeedEntryLifetime); |
| mSeedSetTimer.Start(kSeedEntryLifetimeDt); |
| |
| exit: |
| return error; |
| } |
| |
| void Mpl::UpdateBufferedSet(uint16_t aSeedId, uint8_t aSequence) |
| { |
| int8_t diff; |
| MplBufferedMessageMetadata messageMetadata; |
| |
| Message *message = mBufferedMessageSet.GetHead(); |
| Message *nextMessage = NULL; |
| |
| // Check if multicast forwarding is enabled. |
| VerifyOrExit(GetTimerExpirations() > 0); |
| |
| while (message != NULL) |
| { |
| nextMessage = message->GetNext(); |
| messageMetadata.ReadFrom(*message); |
| |
| if (messageMetadata.GetSeedId() == aSeedId) |
| { |
| diff = static_cast<int8_t>(aSequence - messageMetadata.GetSequence()); |
| |
| if (diff > 0) |
| { |
| // Stop retransmitting MPL Data Message that is consider to be old. |
| mBufferedMessageSet.Dequeue(*message); |
| message->Free(); |
| } |
| |
| break; |
| } |
| |
| message = nextMessage; |
| } |
| |
| exit: |
| return; |
| } |
| |
| void Mpl::AddBufferedMessage(Message &aMessage, uint16_t aSeedId, uint8_t aSequence, bool aIsOutbound) |
| { |
| uint32_t now = Timer::GetNow(); |
| otError error = OT_ERROR_NONE; |
| Message *messageCopy = NULL; |
| MplBufferedMessageMetadata messageMetadata; |
| uint32_t nextTransmissionTime; |
| uint8_t hopLimit = 0; |
| |
| VerifyOrExit(GetTimerExpirations() > 0); |
| VerifyOrExit((messageCopy = aMessage.Clone()) != NULL, error = OT_ERROR_NO_BUFS); |
| |
| if (!aIsOutbound) |
| { |
| aMessage.Read(Header::GetHopLimitOffset(), Header::GetHopLimitSize(), &hopLimit); |
| VerifyOrExit(hopLimit-- > 1, error = OT_ERROR_DROP); |
| messageCopy->Write(Header::GetHopLimitOffset(), Header::GetHopLimitSize(), &hopLimit); |
| } |
| |
| messageMetadata.SetSeedId(aSeedId); |
| messageMetadata.SetSequence(aSequence); |
| messageMetadata.SetTransmissionCount(aIsOutbound ? 1 : 0); |
| messageMetadata.GenerateNextTransmissionTime(now, kDataMessageInterval); |
| |
| // Append the message with MplBufferedMessageMetadata and add it to the queue. |
| SuccessOrExit(error = messageMetadata.AppendTo(*messageCopy)); |
| mBufferedMessageSet.Enqueue(*messageCopy); |
| |
| if (mRetransmissionTimer.IsRunning()) |
| { |
| // If timer is already running, check if it should be restarted with earlier fire time. |
| nextTransmissionTime = mRetransmissionTimer.GetFireTime(); |
| |
| if (messageMetadata.IsEarlier(nextTransmissionTime)) |
| { |
| mRetransmissionTimer.Start(messageMetadata.GetTransmissionTime() - now); |
| } |
| } |
| else |
| { |
| // Otherwise just set the timer. |
| mRetransmissionTimer.Start(messageMetadata.GetTransmissionTime() - now); |
| } |
| |
| exit: |
| |
| if (error != OT_ERROR_NONE && messageCopy != NULL) |
| { |
| messageCopy->Free(); |
| } |
| } |
| |
| otError Mpl::ProcessOption(Message &aMessage, const Address &aAddress, bool aIsOutbound) |
| { |
| otError error; |
| OptionMpl option; |
| |
| VerifyOrExit(aMessage.Read(aMessage.GetOffset(), sizeof(option), &option) >= OptionMpl::kMinLength && |
| (option.GetSeedIdLength() == OptionMpl::kSeedIdLength0 || |
| option.GetSeedIdLength() == OptionMpl::kSeedIdLength2), |
| error = OT_ERROR_DROP); |
| |
| if (option.GetSeedIdLength() == OptionMpl::kSeedIdLength0) |
| { |
| // Retrieve MPL Seed Id from the IPv6 Source Address. |
| option.SetSeedId(HostSwap16(aAddress.mFields.m16[7])); |
| } |
| |
| // Check MPL Data Messages in the MPL Buffered Set against sequence number. |
| UpdateBufferedSet(option.GetSeedId(), option.GetSequence()); |
| |
| // Check if the MPL Data Message is new. |
| error = UpdateSeedSet(option.GetSeedId(), option.GetSequence()); |
| |
| if (error == OT_ERROR_NONE) |
| { |
| AddBufferedMessage(aMessage, option.GetSeedId(), option.GetSequence(), aIsOutbound); |
| } |
| else if (aIsOutbound) |
| { |
| // In case MPL Data Message is generated locally, ignore potential error of the MPL Seed Set |
| // to allow subsequent retransmissions with the same sequence number. |
| ExitNow(error = OT_ERROR_NONE); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| void Mpl::HandleRetransmissionTimer(Timer &aTimer) |
| { |
| GetOwner(aTimer).HandleRetransmissionTimer(); |
| } |
| |
| void Mpl::HandleRetransmissionTimer(void) |
| { |
| uint32_t now = Timer::GetNow(); |
| uint32_t nextDelta = 0xffffffff; |
| MplBufferedMessageMetadata messageMetadata; |
| |
| Message *message = mBufferedMessageSet.GetHead(); |
| Message *nextMessage = NULL; |
| |
| while (message != NULL) |
| { |
| nextMessage = message->GetNext(); |
| messageMetadata.ReadFrom(*message); |
| |
| if (messageMetadata.IsLater(now)) |
| { |
| // Calculate the next retransmission time and choose the lowest. |
| if (messageMetadata.GetTransmissionTime() - now < nextDelta) |
| { |
| nextDelta = messageMetadata.GetTransmissionTime() - now; |
| } |
| } |
| else |
| { |
| // Update the number of transmission timer expirations. |
| messageMetadata.SetTransmissionCount(messageMetadata.GetTransmissionCount() + 1); |
| |
| if (messageMetadata.GetTransmissionCount() < GetTimerExpirations()) |
| { |
| Message *messageCopy = message->Clone(message->GetLength() - sizeof(MplBufferedMessageMetadata)); |
| |
| if (messageCopy != NULL) |
| { |
| if (messageMetadata.GetTransmissionCount() > 1) |
| { |
| messageCopy->SetSubType(Message::kSubTypeMplRetransmission); |
| } |
| |
| GetIp6().EnqueueDatagram(*messageCopy); |
| } |
| |
| messageMetadata.GenerateNextTransmissionTime(now, kDataMessageInterval); |
| messageMetadata.UpdateIn(*message); |
| |
| // Check if retransmission time is lower than the current lowest one. |
| if (messageMetadata.GetTransmissionTime() - now < nextDelta) |
| { |
| nextDelta = messageMetadata.GetTransmissionTime() - now; |
| } |
| } |
| else |
| { |
| mBufferedMessageSet.Dequeue(*message); |
| |
| if (messageMetadata.GetTransmissionCount() == GetTimerExpirations()) |
| { |
| if (messageMetadata.GetTransmissionCount() > 1) |
| { |
| message->SetSubType(Message::kSubTypeMplRetransmission); |
| } |
| |
| // Remove the extra metadata from the MPL Data Message. |
| messageMetadata.RemoveFrom(*message); |
| GetIp6().EnqueueDatagram(*message); |
| } |
| else |
| { |
| // Stop retransmitting if the number of timer expirations is already exceeded. |
| message->Free(); |
| } |
| } |
| } |
| |
| message = nextMessage; |
| } |
| |
| if (nextDelta != 0xffffffff) |
| { |
| mRetransmissionTimer.Start(nextDelta); |
| } |
| } |
| |
| void Mpl::HandleSeedSetTimer(Timer &aTimer) |
| { |
| GetOwner(aTimer).HandleSeedSetTimer(); |
| } |
| |
| void Mpl::HandleSeedSetTimer(void) |
| { |
| bool startTimer = false; |
| |
| for (int i = 0; i < kNumSeedEntries; i++) |
| { |
| if (mSeedSet[i].GetLifetime() > 0) |
| { |
| mSeedSet[i].SetLifetime(mSeedSet[i].GetLifetime() - 1); |
| startTimer = true; |
| } |
| } |
| |
| if (startTimer) |
| { |
| mSeedSetTimer.Start(kSeedEntryLifetimeDt); |
| } |
| } |
| |
| Mpl &Mpl::GetOwner(const Context &aContext) |
| { |
| #if OPENTHREAD_ENABLE_MULTIPLE_INSTANCES |
| Mpl &mpl = *static_cast<Mpl *>(aContext.GetContext()); |
| #else |
| Mpl &mpl = otGetIp6().mMpl; |
| OT_UNUSED_VARIABLE(aContext); |
| #endif |
| return mpl; |
| } |
| |
| } // namespace Ip6 |
| } // namespace ot |