OpenThreadのIPv6プロトコルスタックを読む

OpenThreadのIPv6実装のを読んでみました。

OpenThreadは Googleが開発しているIOT向けのネットワークプロトコル Threadのオープンソース実装です。
OpenThreadには、IPv6の実装が含まれているので調べてみました。

#まとめ
* OpenThreadのIPv6実装の送信処理を読んでみた。
* UDPの送信からCC2538のUART送信までのデータの流れを辿った。
* UdpSocketクラス→Ip6クラス→ThreadNetifクラス→NcpBaseクラス→ NcpUartクラス→cc2538 UARTドライバ というデータの流れ

# 送信の流れ
* UdpSocket::SendTo()

```cpp
ThreadError UdpSocket::SendTo(Message &aMessage, const MessageInfo &aMessageInfo)
{
    ThreadError error = kThreadError_None;
    MessageInfo messageInfoLocal;
    UdpHeader udpHeader;

    messageInfoLocal = aMessageInfo;

    if (messageInfoLocal.GetSockAddr().IsUnspecified())
    {
        messageInfoLocal.GetSockAddr() = GetSockName().GetAddress();
    }

    if (GetSockName().mPort == 0)
    {
        GetSockName().mPort = Udp::sEphemeralPort;

        if (Udp::sEphemeralPort < Udp::kDynamicPortMax)
        {
            Udp::sEphemeralPort++;
        }
        else
        {
            Udp::sEphemeralPort = Udp::kDynamicPortMin;
        }
    }

    udpHeader.SetSourcePort(GetSockName().mPort);
    udpHeader.SetDestinationPort(messageInfoLocal.mPeerPort);
    udpHeader.SetLength(sizeof(udpHeader) + aMessage.GetLength());
    udpHeader.SetChecksum(0);

    SuccessOrExit(error = aMessage.Prepend(&udpHeader, sizeof(udpHeader)));
    aMessage.SetOffset(0);
    SuccessOrExit(error = Ip6::SendDatagram(aMessage, messageInfoLocal, kProtoUdp));

exit:
    return error;
}
```
* Ip6::SendDatagram()

```cpp
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 : kDefaultHopLimit);

    if (messageInfo.GetSockAddr().IsUnspecified())
    {
        VerifyOrExit((source = Netif::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.mInterfaceId != 0, error = kThreadError_Drop);
    }

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

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

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

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

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

    default:
        break;
    }

exit:

    if (error == kThreadError_None)
    {
        error = HandleDatagram(message, NULL, messageInfo.mInterfaceId, NULL, false);
    }

    return error;
}
```
* Ip6::HandleDatagram()

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

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

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

    // check Version
    VerifyOrExit(header.IsVersion6(), ;);

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

    memset(&messageInfo, 0, sizeof(messageInfo));
    messageInfo.GetPeerAddr() = header.GetSource();
    messageInfo.GetSockAddr() = header.GetDestination();
    messageInfo.mInterfaceId = interfaceId;
    messageInfo.mHopLimit = header.GetHopLimit();
    messageInfo.mLinkInfo = linkMessageInfo;

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

        if (header.GetDestination().GetScope() > Address::kLinkLocalScope)
        {
            forward = true;
        }
        else if (netif == NULL)
        {
            forward = true;
        }
    }
    else
    {
        if (Netif::IsUnicastAddress(header.GetDestination()))
        {
            receive = true;
        }
        else if (!header.GetDestination().IsLinkLocal())
        {
            forward = true;
        }
        else if (netif == NULL)
        {
            forward = true;
        }
    }

    message.SetOffset(sizeof(header));

    // process IPv6 Extension Headers
    nextHeader = header.GetNextHeader();
    SuccessOrExit(HandleExtensionHeaders(message, nextHeader, receive));

    // process IPv6 Payload
    if (receive)
    {
        if (fromLocalHost == false)
        {
            ProcessReceiveCallback(message);
        }

        SuccessOrExit(HandlePayload(message, messageInfo, nextHeader));
    }

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

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

exit:

    if (error == kThreadError_Drop)
    {
        Message::Free(message);
    }

    return kThreadError_None;
}
```
* ForwardMessage()

```cpp
ThreadError ForwardMessage(Message &message, MessageInfo &messageInfo)
{
    ThreadError error = kThreadError_None;
    int interfaceId;
    Netif *netif;

    if (messageInfo.GetSockAddr().IsMulticast())
    {
        // multicast
        interfaceId = messageInfo.mInterfaceId;
    }
    else if (messageInfo.GetSockAddr().IsLinkLocal())
    {
        // on-link link-local address
        interfaceId = messageInfo.mInterfaceId;
    }
    else if ((interfaceId = Netif::GetOnLinkNetif(messageInfo.GetSockAddr())) > 0)
    {
        // on-link global address
        ;
    }
    else if ((interfaceId = Routes::Lookup(messageInfo.GetPeerAddr(), messageInfo.GetSockAddr())) > 0)
    {
        // route
        ;
    }
    else
    {
        otDumpDebgIp6("no route", &messageInfo.GetSockAddr(), 16);
        ExitNow(error = kThreadError_NoRoute);
    }

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

exit:
    return error;
}
```
* netif->SendMessage(), ThreadNetif::SendMessage

```cpp
ThreadError ThreadNetif::SendMessage(Message &message)
{
    return mMeshForwarder.SendMessage(message);
}
```

* MeshForwarder::SendMessage

```cpp
ThreadError MeshForwarder::SendMessage(Message &aMessage)
{
    ThreadError error = kThreadError_None;
    Neighbor *neighbor;
    Ip6::Header ip6Header;
    uint8_t numChildren;
    Child *children;
    Lowpan::MeshHeader meshHeader;

    switch (aMessage.GetType())
    {
    case Message::kTypeIp6:
        aMessage.Read(0, sizeof(ip6Header), &ip6Header);

        if (!memcmp(&ip6Header.GetDestination(), mMle.GetLinkLocalAllThreadNodesAddress(),
                    sizeof(ip6Header.GetDestination())) ||
            !memcmp(&ip6Header.GetDestination(), mMle.GetRealmLocalAllThreadNodesAddress(), sizeof(ip6Header.GetDestination())))
        {
            // schedule direct transmission
            aMessage.SetDirectTransmission();

            // destined for all sleepy children
            children = mMle.GetChildren(&numChildren);

            for (int i = 0; i < numChildren; i++)
            {
                if (children[i].mState == Neighbor::kStateValid && (children[i].mMode & Mle::ModeTlv::kModeRxOnWhenIdle) == 0)
                {
                    aMessage.SetChildMask(i);
                }
            }
        }
        else if ((neighbor = mMle.GetNeighbor(ip6Header.GetDestination())) != NULL &&
                 (neighbor->mMode & Mle::ModeTlv::kModeRxOnWhenIdle) == 0)
        {
            // destined for a sleepy child
            aMessage.SetChildMask(mMle.GetChildIndex(*reinterpret_cast<Child *>(neighbor)));
        }
        else
        {
            // schedule direct transmission
            aMessage.SetDirectTransmission();
        }

        break;

    case Message::kType6lowpan:
        aMessage.Read(0, meshHeader.GetHeaderLength(), &meshHeader);

        if ((neighbor = mMle.GetNeighbor(meshHeader.GetDestination())) != NULL &&
            (neighbor->mMode & Mle::ModeTlv::kModeRxOnWhenIdle) == 0)
        {
            // destined for a sleepy child
            aMessage.SetChildMask(mMle.GetChildIndex(*reinterpret_cast<Child *>(neighbor)));
        }
        else
        {
            // not destined for a sleepy child
            aMessage.SetDirectTransmission();
        }

        break;

    case Message::kTypeMacDataPoll:
        aMessage.SetDirectTransmission();
        break;
    }

    aMessage.SetOffset(0);
    SuccessOrExit(error = mSendQueue.Enqueue(aMessage));
    mScheduleTransmissionTask.Post();

exit:
    return error;
}
```

* NcpBase::HandleDatagramFromStack()

```cpp
void NcpBase::HandleDatagramFromStack(Message &message)
{
    ThreadError errorCode;

    if (mSending == false)
    {
        errorCode = OutboundFrameBegin();

        if (errorCode == kThreadError_None)
        {
            errorCode = OutboundFrameFeedPacked(
                            "CiiS",
                            SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0,
                            SPINEL_CMD_PROP_VALUE_IS,
                            message.IsLinkSecurityEnabled()
                            ? SPINEL_PROP_STREAM_NET
                            : SPINEL_PROP_STREAM_NET_INSECURE,
                            message.GetLength()
                        );
        }

        if (errorCode == kThreadError_None)
        {
            errorCode = OutboundFrameFeedMessage(message);
        }

        // TODO: Append any metadata (rssi, lqi, channel, etc) here!

        if (errorCode == kThreadError_None)
        {
            errorCode = OutboundFrameSend();
        }

        if (errorCode != kThreadError_None)
        {
            SendLastStatus(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_STATUS_DROPPED);
        }

        Message::Free(message);
    }
    else
    {
        if (mSendQueue.Enqueue(message) != kThreadError_None)
        {
            Message::Free(message);
        }
    }
}
```

* NcpUart::OutboundFrameSend (NcpSpiクラスにも同様のメソッドがある)

```cpp
ThreadError
NcpUart::OutboundFrameSend(void)
{
    ThreadError errorCode;

    errorCode = mFrameEncoder.Finalize(mSendFrame);

    if (errorCode == kThreadError_None)
    {
        errorCode = otPlatUartSend(mSendFrame.GetBuffer(), mSendFrame.GetLength());
    }

    if (errorCode == kThreadError_None)
    {
        mSending = true;
    }

    return errorCode;
}
```

* otPlatUartSend

```c
ThreadError otPlatUartSend(const uint8_t *aBuf, uint16_t aBufLength)
{
    ThreadError error = kThreadError_None;

    VerifyOrExit(sTransmitBuffer == NULL, error = kThreadError_Busy);

    sTransmitBuffer = aBuf;
    sTransmitLength = aBufLength;

exit:
    return error;
}
```

* ここから別のプロセスでの処理

* cc2538UartProcess

```c
void cc2538UartProcess(void)
{
    processReceive();
    processTransmit();
}
```
* processTransmit

```c
void processTransmit(void)
{
    VerifyOrExit(sTransmitBuffer != NULL, ;);

    for (; sTransmitLength > 0; sTransmitLength--)
    {
        while (HWREG(UART0_BASE + UART_O_FR) & UART_FR_TXFF);

        HWREG(UART0_BASE + UART_O_DR) = *sTransmitBuffer++;
    }

    sTransmitBuffer = NULL;
    otPlatUartSendDone();

exit:
    return;
}
```

* 最後はUARTに送信してる。　想定では無線で送信するのかと思ったのだけど、どうしてなんだろう？
* CC2538で動かしてみた人はいないんでしょうか？

# 受信 (途中)
 
* SetNcpReceivedHandler
* HandleDatagramFromStack
* Ncp::OutboundFrameFeedMessage(Message &message)

* Mac::ReceiveDoneTask(void *aContext)
* Mac::ReceiveDoneTask(void)
* MeshForwarder::HandleReceivedFrame(void *aContext, Mac::Frame &aFrame, ThreadError aError)
* MeshForwarder::HandleReceivedFrame(Mac::Frame &aFrame, ThreadError aError)
* MeshForwarder::HandleLowpanHC() or MeshForwarder::HandleFragment()
* Ip6::HandleDatagram()
* HandlePayload


# 参考
* https://github.com/openthread/openthread