/*
 *  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 IPv6 networking.
 */

#define WPP_NAME "ip6.tmh"

#include <common/code_utils.hpp>
#include <common/debug.hpp>
#include <common/logging.hpp>
#include <common/message.hpp>
#include <net/icmp6.hpp>
#include <net/ip6.hpp>
#include <net/ip6_address.hpp>
#include <net/ip6_routes.hpp>
#include <net/netif.hpp>
#include <net/udp6.hpp>
#include <thread/mle.hpp>
#include <openthread-instance.h>

namespace Thread {
namespace Ip6 {

Ip6::Ip6(void):
    mRoutes(*this),
    mIcmp(*this),
    mUdp(*this),
    mMpl(*this),
    mForwardingEnabled(false),
    mSendQueueTask(mTaskletScheduler, HandleSendQueue, this),
    mReceiveIp6DatagramCallback(NULL),
    mReceiveIp6DatagramCallbackContext(NULL),
    mIsReceiveIp6FilterEnabled(false),
    mNetifListHead(NULL)
{
}

Message *Ip6::NewMessage(uint16_t reserved)
{
    return mMessagePool.New(Message::kTypeIp6, sizeof(Header) + sizeof(HopByHopHeader) + sizeof(OptionMpl) + reserved);
}

bool Ip6::IsForwardingEnabled(void)
{
    return mForwardingEnabled;
}

void Ip6::SetForwardingEnabled(bool aEnable)
{
    mForwardingEnabled = aEnable;
}

uint16_t Ip6::UpdateChecksum(uint16_t checksum, uint16_t val)
{
    uint16_t result = checksum + val;
    return result + (result < checksum);
}

uint16_t Ip6::UpdateChecksum(uint16_t checksum, const void *buf, uint16_t len)
{
    const uint8_t *bytes = reinterpret_cast<const uint8_t *>(buf);

    for (int i = 0; i < len; i++)
    {
        checksum = Ip6::UpdateChecksum(checksum, (i & 1) ? bytes[i] : static_cast<uint16_t>(bytes[i] << 8));
    }

    return checksum;
}

uint16_t Ip6::UpdateChecksum(uint16_t checksum, const Address &address)
{
    return Ip6::UpdateChecksum(checksum, address.mFields.m8, sizeof(address));
}

uint16_t Ip6::ComputePseudoheaderChecksum(const Address &src, const Address &dst, uint16_t length, IpProto proto)
{
    uint16_t checksum;

    checksum = Ip6::UpdateChecksum(0, length);
    checksum = Ip6::UpdateChecksum(checksum, static_cast<uint16_t>(proto));
    checksum = UpdateChecksum(checksum, src);
    checksum = UpdateChecksum(checksum, dst);

    return checksum;
}

void Ip6::SetReceiveDatagramCallback(otReceiveIp6DatagramCallback aCallback, void *aCallbackContext)
{
    mReceiveIp6DatagramCallback = aCallback;
    mReceiveIp6DatagramCallbackContext = aCallbackContext;
}

bool Ip6::IsReceiveIp6FilterEnabled(void)
{
    return mIsReceiveIp6FilterEnabled;
}

void Ip6::SetReceiveIp6FilterEnabled(bool aEnabled)
{
    mIsReceiveIp6FilterEnabled = aEnabled;
}

ThreadError Ip6::AddMplOption(Message &message, Header &header)
{
    ThreadError error = kThreadError_None;
    HopByHopHeader hbhHeader;
    OptionMpl mplOption;
    OptionPadN padOption;

    hbhHeader.SetNextHeader(header.GetNextHeader());
    hbhHeader.SetLength(0);
    mMpl.InitOption(mplOption, header.GetSource());

    // Mpl option may require two bytes padding.
    if ((mplOption.GetTotalLength() + sizeof(hbhHeader)) % 8)
    {
        padOption.Init(2);
        SuccessOrExit(error = message.Prepend(&padOption, padOption.GetTotalLength()));
    }

    SuccessOrExit(error = message.Prepend(&mplOption, mplOption.GetTotalLength()));
    SuccessOrExit(error = message.Prepend(&hbhHeader, sizeof(hbhHeader)));
    header.SetPayloadLength(header.GetPayloadLength() + sizeof(hbhHeader) + sizeof(mplOption));
    header.SetNextHeader(kProtoHopOpts);
exit:
    return error;
}

ThreadError Ip6::AddTunneledMplOption(Message &aMessage, Header &aHeader, MessageInfo &aMessageInfo)
{
    ThreadError error = kThreadError_None;
    Header tunnelHeader;
    const NetifUnicastAddress *source;
    MessageInfo messageInfo(aMessageInfo);

    // Use IP-in-IP encapsulation (RFC2473) and ALL_MPL_FORWARDERS address.
    memset(&messageInfo.GetPeerAddr(), 0, sizeof(Address));
    messageInfo.GetPeerAddr().mFields.m16[0] = HostSwap16(0xff03);
    messageInfo.GetPeerAddr().mFields.m16[7] = HostSwap16(0x00fc);

    tunnelHeader.Init();
    tunnelHeader.SetHopLimit(static_cast<uint8_t>(kDefaultHopLimit));
    tunnelHeader.SetPayloadLength(aHeader.GetPayloadLength() + sizeof(tunnelHeader));
    tunnelHeader.SetDestination(messageInfo.GetPeerAddr());
    tunnelHeader.SetNextHeader(kProtoIp6);

    VerifyOrExit((source = SelectSourceAddress(messageInfo)) != NULL,
                 error = kThreadError_Error);

    tunnelHeader.SetSource(source->GetAddress());

    SuccessOrExit(error = AddMplOption(aMessage, tunnelHeader));
    SuccessOrExit(error = aMessage.Prepend(&tunnelHeader, sizeof(tunnelHeader)));

exit:
    return error;
}

ThreadError Ip6::InsertMplOption(Message &aMessage, Header &aIp6Header, MessageInfo &aMessageInfo)
{
    ThreadError error = kThreadError_None;

    VerifyOrExit(aIp6Header.GetDestination().IsMulticast() &&
                 aIp6Header.GetDestination().GetScope() >= Address::kRealmLocalScope, ;);

    if (aIp6Header.GetDestination().IsRealmLocalMulticast())
    {
        aMessage.RemoveHeader(sizeof(aIp6Header));

        if (aIp6Header.GetNextHeader() == kProtoHopOpts)
        {
            HopByHopHeader hbh;
            uint8_t hbhLength = 0;
            OptionMpl mplOption;

            // read existing hop-by-hop option header
            aMessage.Read(0, sizeof(hbh), &hbh);
            hbhLength = (hbh.GetLength() + 1) * 8;

            // increase existing hop-by-hop option header length by 8 bytes
            hbh.SetLength(hbh.GetLength() + 1);
            aMessage.Write(0, sizeof(hbh), &hbh);

            // make space for MPL Option + padding by shifting hop-by-hop option header
            SuccessOrExit(error = aMessage.Prepend(NULL, 8));
            aMessage.CopyTo(8, 0, hbhLength, aMessage);

            // insert MPL Option
            mMpl.InitOption(mplOption, aIp6Header.GetSource());
            aMessage.Write(hbhLength, mplOption.GetTotalLength(), &mplOption);

            // insert Pad Option (if needed)
            if (mplOption.GetTotalLength() % 8)
            {
                OptionPadN padOption;
                padOption.Init(8 - (mplOption.GetTotalLength() % 8));
                aMessage.Write(hbhLength + mplOption.GetTotalLength(), padOption.GetTotalLength(), &padOption);
            }

            // increase IPv6 Payload Length
            aIp6Header.SetPayloadLength(aIp6Header.GetPayloadLength() + 8);
        }
        else
        {
            SuccessOrExit(error = AddMplOption(aMessage, aIp6Header));
        }

        SuccessOrExit(error = aMessage.Prepend(&aIp6Header, sizeof(aIp6Header)));
    }
    else
    {
        SuccessOrExit(error = AddTunneledMplOption(aMessage, aIp6Header, aMessageInfo));
    }

exit:
    return error;
}

ThreadError Ip6::RemoveMplOption(Message &aMessage)
{
    ThreadError error = kThreadError_None;
    Header ip6Header;
    HopByHopHeader hbh;
    uint16_t offset;
    uint16_t endOffset;
    uint16_t mplOffset = 0;
    uint8_t mplLength = 0;
    bool remove = false;

    offset = 0;
    aMessage.Read(offset, sizeof(ip6Header), &ip6Header);
    offset += sizeof(ip6Header);
    VerifyOrExit(ip6Header.GetNextHeader() == kProtoHopOpts,);

    aMessage.Read(offset, sizeof(hbh), &hbh);
    endOffset = offset + (hbh.GetLength() + 1) * 8;
    VerifyOrExit(aMessage.GetLength() >= endOffset,);

    offset += sizeof(hbh);

    while (offset < endOffset)
    {
        OptionHeader option;

        aMessage.Read(offset, sizeof(option), &option);

        switch (option.GetType())
        {
        case OptionMpl::kType:
            mplOffset = offset;
            mplLength = option.GetLength();

            if (mplOffset == sizeof(ip6Header) + sizeof(hbh) && hbh.GetLength() == 0)
            {
                // first and only IPv6 Option, remove IPv6 HBH Option header
                remove = true;
            }
            else if (mplOffset + 8 == endOffset)
            {
                // last IPv6 Option, remove last 8 bytes
                remove = true;
            }

            offset += sizeof(option) + option.GetLength();
            break;

        case OptionPad1::kType:
            offset += sizeof(OptionPad1);
            break;

        case OptionPadN::kType:
            offset += sizeof(option) + option.GetLength();
            break;

        default:
            // encountered another option, now just replace MPL Option with PadN
            remove = false;
            offset += sizeof(option) + option.GetLength();
            break;
        }
    }

    // verify that IPv6 Options header is properly formed
    VerifyOrExit(offset == endOffset,);

    if (remove)
    {
        // last IPv6 Option, shrink HBH Option header
        uint8_t buf[8];

        offset = endOffset - sizeof(buf);

        while (offset >= sizeof(buf))
        {
            aMessage.Read(offset - sizeof(buf), sizeof(buf), buf);
            aMessage.Write(offset, sizeof(buf), buf);
            offset -= sizeof(buf);
        }

        aMessage.RemoveHeader(sizeof(buf));

        if (mplOffset == sizeof(ip6Header) + sizeof(hbh))
        {
            // remove entire HBH header
            ip6Header.SetNextHeader(hbh.GetNextHeader());
        }
        else
        {
            // update HBH header length
            hbh.SetLength(hbh.GetLength() - 1);
            aMessage.Write(sizeof(ip6Header), sizeof(hbh), &hbh);
        }

        ip6Header.SetPayloadLength(ip6Header.GetPayloadLength() - sizeof(buf));
        aMessage.Write(0, sizeof(ip6Header), &ip6Header);
    }
    else if (mplOffset != 0)
    {
        // replace MPL Option with PadN Option
        OptionPadN padOption;

        padOption.Init(sizeof(OptionHeader) + mplLength);
        aMessage.Write(mplOffset, padOption.GetTotalLength(), &padOption);
    }

exit:
    return error;
}

void Ip6::EnqueueDatagram(Message &aMessage)
{
    mSendQueue.Enqueue(aMessage);
    mSendQueueTask.Post();
}

ThreadError Ip6::SendDatagram(Message &message, MessageInfo &messageInfo, IpProto ipproto)
{
    ThreadError error = kThreadError_None;
    Header header;
    uint16_t payloadLength = message.GetLength();
    uint16_t checksum;
    const NetifUnicastAddress *source;

    header.Init();
    header.SetPayloadLength(payloadLength);
    header.SetNextHeader(ipproto);
    header.SetHopLimit(messageInfo.mHopLimit ? messageInfo.mHopLimit : static_cast<uint8_t>(kDefaultHopLimit));

    if (messageInfo.GetSockAddr().IsUnspecified())
    {
        VerifyOrExit((source = SelectSourceAddress(messageInfo)) != NULL,
                     error = kThreadError_Error);
        header.SetSource(source->GetAddress());
    }
    else
    {
        header.SetSource(messageInfo.GetSockAddr());
    }

    header.SetDestination(messageInfo.GetPeerAddr());

    if (header.GetDestination().IsLinkLocal() || header.GetDestination().IsLinkLocalMulticast())
    {
        VerifyOrExit(messageInfo.GetInterfaceId() != 0, error = kThreadError_Drop);
    }

    if (messageInfo.GetPeerAddr().IsRealmLocalMulticast())
    {
        SuccessOrExit(error = AddMplOption(message, header));
    }

    SuccessOrExit(error = message.Prepend(&header, sizeof(header)));

    if (messageInfo.GetPeerAddr().IsMulticast() &&
        messageInfo.GetPeerAddr().GetScope() > Address::kRealmLocalScope)
    {
        SuccessOrExit(error = AddTunneledMplOption(message, header, messageInfo));
    }

    // compute checksum
    checksum = ComputePseudoheaderChecksum(header.GetSource(), header.GetDestination(),
                                           payloadLength, ipproto);

    switch (ipproto)
    {
    case kProtoUdp:
        SuccessOrExit(error = mUdp.UpdateChecksum(message, checksum));
        break;

    case kProtoIcmp6:
        SuccessOrExit(error = mIcmp.UpdateChecksum(message, checksum));
        break;

    default:
        break;
    }

exit:

    if (error == kThreadError_None)
    {
        message.SetInterfaceId(messageInfo.GetInterfaceId());
        EnqueueDatagram(message);
    }

    return error;
}

void Ip6::HandleSendQueue(void *aContext)
{
    static_cast<Ip6 *>(aContext)->HandleSendQueue();
}

void Ip6::HandleSendQueue(void)
{
    while (mSendQueue.GetHead())
    {
        Message *message = mSendQueue.GetHead();
        mSendQueue.Dequeue(*message);
        HandleDatagram(*message, NULL, message->GetInterfaceId(), NULL, false);
    }
}

ThreadError Ip6::HandleOptions(Message &message, Header &header, bool &forward)
{
    ThreadError error = kThreadError_None;
    HopByHopHeader hbhHeader;
    OptionHeader optionHeader;
    uint16_t endOffset;

    VerifyOrExit(message.Read(message.GetOffset(), sizeof(hbhHeader), &hbhHeader) == sizeof(hbhHeader),
                 error = kThreadError_Drop);
    endOffset = message.GetOffset() + (hbhHeader.GetLength() + 1) * 8;

    VerifyOrExit(endOffset <= message.GetLength(), error = kThreadError_Drop);

    message.MoveOffset(sizeof(optionHeader));

    while (message.GetOffset() < endOffset)
    {
        VerifyOrExit(message.Read(message.GetOffset(), sizeof(optionHeader), &optionHeader) == sizeof(optionHeader),
                     error = kThreadError_Drop);
        VerifyOrExit(message.GetOffset() + sizeof(optionHeader) + optionHeader.GetLength() <= endOffset,
                     error = kThreadError_Drop);

        switch (optionHeader.GetType())
        {
        case OptionMpl::kType:
            SuccessOrExit(error = mMpl.ProcessOption(message, header.GetSource(), forward));
            break;

        default:
            switch (optionHeader.GetAction())
            {
            case OptionHeader::kActionSkip:
                break;

            case OptionHeader::kActionDiscard:
                ExitNow(error = kThreadError_Drop);

            case OptionHeader::kActionForceIcmp:
                // TODO: send icmp error
                ExitNow(error = kThreadError_Drop);

            case OptionHeader::kActionIcmp:
                // TODO: send icmp error
                ExitNow(error = kThreadError_Drop);

            }

            break;
        }

        if (optionHeader.GetType() == OptionPad1::kType)
        {
            message.MoveOffset(sizeof(OptionPad1));
        }
        else
        {
            message.MoveOffset(sizeof(optionHeader) + optionHeader.GetLength());
        }

    }

exit:
    return error;
}

ThreadError Ip6::HandleFragment(Message &message)
{
    ThreadError error = kThreadError_None;
    FragmentHeader fragmentHeader;

    VerifyOrExit(message.Read(message.GetOffset(), sizeof(fragmentHeader), &fragmentHeader) == sizeof(fragmentHeader),
                 error = kThreadError_Drop);

    VerifyOrExit(fragmentHeader.GetOffset() == 0 && fragmentHeader.IsMoreFlagSet() == false,
                 error = kThreadError_Drop);

    message.MoveOffset(sizeof(fragmentHeader));

exit:
    return error;
}

ThreadError Ip6::HandleExtensionHeaders(Message &message, Header &header, uint8_t &nextHeader, bool forward,
                                        bool receive)
{
    ThreadError error = kThreadError_None;
    ExtensionHeader extHeader;

    while (receive == true || nextHeader == kProtoHopOpts)
    {
        VerifyOrExit(message.Read(message.GetOffset(), sizeof(extHeader), &extHeader) == sizeof(extHeader),
                     error = kThreadError_Drop);

        switch (nextHeader)
        {
        case kProtoHopOpts:
            SuccessOrExit(error = HandleOptions(message, header, forward));
            break;

        case kProtoFragment:
            SuccessOrExit(error = HandleFragment(message));
            break;

        case kProtoDstOpts:
            SuccessOrExit(error = HandleOptions(message, header, forward));
            break;

        case kProtoIp6:
            ExitNow();

        case kProtoRouting:
        case kProtoNone:
            ExitNow(error = kThreadError_Drop);

        default:
            ExitNow();
        }

        nextHeader = static_cast<uint8_t>(extHeader.GetNextHeader());
    }

exit:
    return error;
}

ThreadError Ip6::HandlePayload(Message &message, MessageInfo &messageInfo, uint8_t ipproto)
{
    ThreadError error = kThreadError_None;

    switch (ipproto)
    {
    case kProtoUdp:
        ExitNow(error = mUdp.HandleMessage(message, messageInfo));

    case kProtoIcmp6:
        ExitNow(error = mIcmp.HandleMessage(message, messageInfo));
    }

exit:
    return error;
}

ThreadError Ip6::ProcessReceiveCallback(const Message &aMessage, const MessageInfo &messageInfo, uint8_t aIpProto)
{
    ThreadError error = kThreadError_None;
    Message *messageCopy = NULL;

    VerifyOrExit(mReceiveIp6DatagramCallback != NULL, error = kThreadError_NoRoute);

    if (mIsReceiveIp6FilterEnabled)
    {
        // do not pass messages sent to/from an RLOC
        VerifyOrExit(!messageInfo.GetSockAddr().IsRoutingLocator() &&
                     !messageInfo.GetPeerAddr().IsRoutingLocator(),
                     error = kThreadError_NoRoute);

        switch (aIpProto)
        {
        case kProtoIcmp6:
            if (mIcmp.IsEchoEnabled())
            {
                IcmpHeader icmp;
                aMessage.Read(aMessage.GetOffset(), sizeof(icmp), &icmp);

                // do not pass ICMP Echo Request messages
                VerifyOrExit(icmp.GetType() != IcmpHeader::kTypeEchoRequest, error = kThreadError_NoRoute);
            }

            break;

        case kProtoUdp:
            if (messageInfo.GetSockAddr().IsLinkLocal())
            {
                UdpHeader udp;
                aMessage.Read(aMessage.GetOffset(), sizeof(udp), &udp);

                // do not pass MLE messages
                VerifyOrExit(udp.GetDestinationPort() != Mle::kUdpPort, error = kThreadError_NoRoute);
            }

            break;

        default:
            break;
        }
    }

    // make a copy of the datagram to pass to host
    VerifyOrExit((messageCopy = aMessage.Clone()) != NULL, error = kThreadError_NoBufs);
    RemoveMplOption(*messageCopy);
    mReceiveIp6DatagramCallback(messageCopy, mReceiveIp6DatagramCallbackContext);

exit:
    return error;
}

ThreadError Ip6::HandleDatagram(Message &message, Netif *netif, int8_t interfaceId, const void *linkMessageInfo,
                                bool fromLocalHost)
{
    ThreadError error = kThreadError_None;
    MessageInfo messageInfo;
    Header header;
    uint16_t payloadLength;
    bool receive = false;
    bool forward = false;
    bool tunnel = false;
    bool multicastPromiscuous = false;
    uint8_t nextHeader;
    uint8_t hopLimit;

    otLogFuncEntry();

#if 0
    uint8_t buf[1024];
    message.Read(0, sizeof(buf), buf);
    dump("handle datagram", buf, message.GetLength());
#endif

    // check message length
    VerifyOrExit(message.Read(0, sizeof(header), &header) == sizeof(header), error = kThreadError_Drop);
    payloadLength = header.GetPayloadLength();

    // check Version
    VerifyOrExit(header.IsVersion6(), error = kThreadError_Drop);

    // check Payload Length
    VerifyOrExit(sizeof(header) + payloadLength == message.GetLength() &&
                 sizeof(header) + payloadLength <= Ip6::kMaxDatagramLength, error = kThreadError_Drop);

    messageInfo.SetPeerAddr(header.GetSource());
    messageInfo.SetSockAddr(header.GetDestination());
    messageInfo.SetInterfaceId(interfaceId);
    messageInfo.SetHopLimit(header.GetHopLimit());
    messageInfo.SetLinkInfo(linkMessageInfo);

    // determine destination of packet
    if (header.GetDestination().IsMulticast())
    {
        if (netif != NULL)
        {
            if (netif->IsMulticastSubscribed(header.GetDestination()))
            {
                receive = true;
            }
            else if (netif->IsMulticastPromiscuousModeEnabled())
            {
                multicastPromiscuous = true;
            }
        }

        if (netif == NULL)
        {
            forward = true;

            if (fromLocalHost)
            {
                SuccessOrExit(error = InsertMplOption(message, header, messageInfo));
            }
        }
    }
    else
    {
        if (IsUnicastAddress(header.GetDestination()))
        {
            receive = true;
        }
        else if (!header.GetDestination().IsLinkLocal())
        {
            forward = true;
        }
        else if (netif == NULL)
        {
            forward = true;
        }
    }

    message.SetInterfaceId(interfaceId);
    message.SetOffset(sizeof(header));

    // process IPv6 Extension Headers
    nextHeader = static_cast<uint8_t>(header.GetNextHeader());
    SuccessOrExit(error = HandleExtensionHeaders(message, header, nextHeader, forward, receive));

    if (!mForwardingEnabled && netif != NULL)
    {
        forward = false;
    }

    // process IPv6 Payload
    if (receive)
    {
        if (nextHeader == kProtoIp6)
        {
            // Remove encapsulating header.
            message.RemoveHeader(message.GetOffset());

            HandleDatagram(message, netif, interfaceId, linkMessageInfo, fromLocalHost);
            ExitNow(tunnel = true);
        }

        if (fromLocalHost == false)
        {
            ProcessReceiveCallback(message, messageInfo, nextHeader);
        }

        SuccessOrExit(error = HandlePayload(message, messageInfo, nextHeader));
    }
    else if (multicastPromiscuous)
    {
        ProcessReceiveCallback(message, messageInfo, nextHeader);
    }

    if (forward)
    {
        if (netif != NULL)
        {
            header.SetHopLimit(header.GetHopLimit() - 1);
        }

        if (header.GetHopLimit() == 0)
        {
            // send time exceeded
            ExitNow(error = kThreadError_Drop);
        }
        else
        {
            hopLimit = header.GetHopLimit();
            message.Write(Header::GetHopLimitOffset(), Header::GetHopLimitSize(), &hopLimit);
            SuccessOrExit(error = ForwardMessage(message, messageInfo, nextHeader));
        }
    }

exit:

    if (!tunnel && (error != kThreadError_None || !forward))
    {
        message.Free();
    }

    otLogFuncExitErr(error);
    return error;
}

ThreadError Ip6::ForwardMessage(Message &message, MessageInfo &messageInfo, uint8_t ipproto)
{
    ThreadError error = kThreadError_None;
    int8_t interfaceId;
    Netif *netif;

    otLogFuncEntry();

    if (messageInfo.GetSockAddr().IsMulticast())
    {
        // multicast
        interfaceId = messageInfo.mInterfaceId;
    }
    else if (messageInfo.GetSockAddr().IsLinkLocal())
    {
        // on-link link-local address
        interfaceId = messageInfo.mInterfaceId;
    }
    else if ((interfaceId = GetOnLinkNetif(messageInfo.GetSockAddr())) > 0)
    {
        // on-link global address
        ;
    }
    else if ((interfaceId = mRoutes.Lookup(messageInfo.GetPeerAddr(), messageInfo.GetSockAddr())) > 0)
    {
        // route
        ;
    }
    else
    {
        // try passing to host
        error = ProcessReceiveCallback(message, messageInfo, ipproto);

        switch (error)
        {
        case kThreadError_None:
            // the caller transfers custody in the success case, so free the message here
            message.Free();
            break;

        case kThreadError_NoRoute:
            otDumpDebgIp6("no route", &messageInfo.GetSockAddr(), 16);
            break;

        default:
            break;
        }

        ExitNow();
    }

    // submit message to interface
    VerifyOrExit((netif = GetNetifById(interfaceId)) != NULL, error = kThreadError_NoRoute);
    SuccessOrExit(error = netif->SendMessage(message));

exit:

    otLogFuncExitErr(error);
    return error;
}

ThreadError Ip6::AddNetif(Netif &aNetif)
{
    ThreadError error = kThreadError_None;
    Netif *netif;

    if (mNetifListHead == NULL)
    {
        mNetifListHead = &aNetif;
    }
    else
    {
        netif = mNetifListHead;

        do
        {
            if (netif == &aNetif || netif->mInterfaceId == aNetif.mInterfaceId)
            {
                ExitNow(error = kThreadError_Already);
            }
        }
        while (netif->mNext);

        netif->mNext = &aNetif;
    }

    aNetif.mNext = NULL;

exit:
    return error;
}

ThreadError Ip6::RemoveNetif(Netif &aNetif)
{
    ThreadError error = kThreadError_NotFound;

    VerifyOrExit(mNetifListHead != NULL, error = kThreadError_NotFound);

    if (mNetifListHead == &aNetif)
    {
        mNetifListHead = aNetif.mNext;
    }
    else
    {
        for (Netif *netif = mNetifListHead; netif->mNext; netif = netif->mNext)
        {
            if (netif->mNext != &aNetif)
            {
                continue;
            }

            netif->mNext = aNetif.mNext;
            error = kThreadError_None;
            break;
        }
    }

    aNetif.mNext = NULL;

exit:
    return error;
}

Netif *Ip6::GetNetifList()
{
    return mNetifListHead;
}

Netif *Ip6::GetNetifById(int8_t aInterfaceId)
{
    Netif *netif;

    for (netif = mNetifListHead; netif; netif = netif->mNext)
    {
        if (netif->GetInterfaceId() == aInterfaceId)
        {
            ExitNow();
        }
    }

exit:
    return netif;
}

bool Ip6::IsUnicastAddress(const Address &aAddress)
{
    bool rval = false;

    for (Netif *netif = mNetifListHead; netif; netif = netif->mNext)
    {
        rval = netif->IsUnicastAddress(aAddress);

        if (rval)
        {
            ExitNow();
        }
    }

exit:
    return rval;
}

const NetifUnicastAddress *Ip6::SelectSourceAddress(MessageInfo &aMessageInfo)
{
    Address *destination = &aMessageInfo.GetPeerAddr();
    int interfaceId = aMessageInfo.mInterfaceId;
    const NetifUnicastAddress *rvalAddr = NULL;
    const Address *candidateAddr;
    int8_t candidateId;
    int8_t rvalIface = 0;

    for (Netif *netif = GetNetifList(); netif; netif = netif->mNext)
    {
        candidateId = netif->GetInterfaceId();

        for (const NetifUnicastAddress *addr = netif->mUnicastAddresses; addr; addr = addr->GetNext())
        {
            candidateAddr = &addr->GetAddress();

            if (destination->IsLinkLocal() || destination->IsMulticast())
            {
                if (interfaceId != candidateId)
                {
                    continue;
                }
            }

            if (rvalAddr == NULL)
            {
                // Rule 0: Prefer any address
                rvalAddr = addr;
                rvalIface = candidateId;
            }
            else if (*candidateAddr == *destination)
            {
                // Rule 1: Prefer same address
                rvalAddr = addr;
                rvalIface = candidateId;
                goto exit;
            }
            else if (addr->GetScope() < rvalAddr->GetScope())
            {
                // Rule 2: Prefer appropriate scope
                if (addr->GetScope() >= destination->GetScope())
                {
                    rvalAddr = addr;
                    rvalIface = candidateId;
                }
            }
            else if (addr->GetScope() > rvalAddr->GetScope())
            {
                if (rvalAddr->GetScope() < destination->GetScope())
                {
                    rvalAddr = addr;
                    rvalIface = candidateId;
                }
            }
            else if (addr->mPreferred && !rvalAddr->mPreferred)
            {
                // Rule 3: Avoid deprecated addresses
                rvalAddr = addr;
                rvalIface = candidateId;
            }
            else if (aMessageInfo.mInterfaceId != 0 && aMessageInfo.mInterfaceId == candidateId &&
                     rvalIface != candidateId)
            {
                // Rule 4: Prefer home address
                // Rule 5: Prefer outgoing interface
                rvalAddr = addr;
                rvalIface = candidateId;
            }
            else if (destination->PrefixMatch(*candidateAddr) > destination->PrefixMatch(rvalAddr->GetAddress()))
            {
                // Rule 6: Prefer matching label
                // Rule 7: Prefer public address
                // Rule 8: Use longest prefix matching
                rvalAddr = addr;
                rvalIface = candidateId;
            }
        }
    }

exit:
    aMessageInfo.mInterfaceId = rvalIface;
    return rvalAddr;
}

int8_t Ip6::GetOnLinkNetif(const Address &aAddress)
{
    int8_t rval = -1;

    for (Netif *netif = mNetifListHead; netif; netif = netif->mNext)
    {
        for (const NetifUnicastAddress *cur = netif->mUnicastAddresses; cur; cur = cur->GetNext())
        {
            if (cur->GetAddress().PrefixMatch(aAddress) >= cur->mPrefixLength)
            {
                ExitNow(rval = netif->GetInterfaceId());
            }
        }
    }

exit:
    return rval;
}

otInstance *Ip6::GetInstance(void)
{
    return otInstanceFromIp6(this);
}

}  // namespace Ip6
}  // namespace Thread
