diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 8664112df..0ea66677b 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (152) +#define OPENTHREAD_API_VERSION (153) /** * @addtogroup api-instance diff --git a/include/openthread/tcp.h b/include/openthread/tcp.h index 138e7af23..a0d4b3a09 100644 --- a/include/openthread/tcp.h +++ b/include/openthread/tcp.h @@ -215,6 +215,8 @@ struct otTcpEndpoint otTcpReceiveAvailable mReceiveAvailableCallback; ///< "Receive available" callback function otTcpDisconnected mDisconnectedCallback; ///< "Disconnected" callback function + uint32_t mTimers[4]; + /* Other implementation-defined fields go here. */ }; diff --git a/src/core/net/tcp6.cpp b/src/core/net/tcp6.cpp index 4bf7b2ae0..1d078fae6 100644 --- a/src/core/net/tcp6.cpp +++ b/src/core/net/tcp6.cpp @@ -39,26 +39,45 @@ #include "common/code_utils.hpp" #include "common/error.hpp" +#include "common/instance.hpp" +#include "common/logging.hpp" +#include "net/ip6.hpp" namespace ot { namespace Ip6 { Tcp::Tcp(Instance &aInstance) : InstanceLocator(aInstance) + , mTimer(aInstance, Tcp::HandleTimer) + , mEphemeralPort(kDynamicPortMin) { + OT_UNUSED_VARIABLE(mEphemeralPort); } Error Tcp::Endpoint::Initialize(Instance &aInstance, otTcpEndpointInitializeArgs &aArgs) { - OT_UNUSED_VARIABLE(aInstance); - OT_UNUSED_VARIABLE(aArgs); + Error error; - return kErrorNotImplemented; + SuccessOrExit(error = aInstance.Get().mEndpoints.Add(*this)); + + mContext = aArgs.mContext; + mEstablishedCallback = aArgs.mEstablishedCallback; + mSendDoneCallback = aArgs.mSendDoneCallback; + mSendReadyCallback = aArgs.mSendReadyCallback; + mReceiveAvailableCallback = aArgs.mReceiveAvailableCallback; + mDisconnectedCallback = aArgs.mDisconnectedCallback; + + mInstance = &aInstance; + + memset(mTimers, 0x00, sizeof(mTimers)); + +exit: + return error; } Instance &Tcp::Endpoint::GetInstance(void) { - return *reinterpret_cast(mInstance); + return *static_cast(mInstance); } const SockAddr &Tcp::Endpoint::GetLocalAddress(void) const @@ -136,20 +155,125 @@ Error Tcp::Endpoint::Abort(void) Error Tcp::Endpoint::Deinitialize(void) { - return kErrorNotImplemented; + Error error; + + Tcp &tcp = GetInstance().Get(); + + SuccessOrExit(error = tcp.mEndpoints.Remove(*this)); + SetNext(nullptr); + +exit: + return error; +} + +uint8_t Tcp::Endpoint::TimerFlagToIndex(uint8_t aTimerFlag) +{ + OT_UNUSED_VARIABLE(aTimerFlag); + /* + * TODO: Convert from the timer flag provided by TCPlp to the index in + * our timers array. + */ + return 0; +} + +bool Tcp::Endpoint::IsTimerActive(uint8_t aTimerIndex) +{ + OT_UNUSED_VARIABLE(aTimerIndex); + /* TODO: Check whether TCPlp has marked this timer as active. */ + return false; +} + +void Tcp::Endpoint::SetTimer(uint8_t aTimerFlag, uint32_t aDelay) +{ + /* + * TCPlp has already set the flag for this timer to record that it's + * running. So, all that's left to do is record the expiry time and + * (re)set the main timer as appropriate. + */ + + TimeMilli now = TimerMilli::GetNow(); + TimeMilli newFireTime = now + aDelay; + uint8_t timerIndex = TimerFlagToIndex(aTimerFlag); + + mTimers[timerIndex] = newFireTime.GetValue(); + otLogDebgTcp("Endpoint %p set timer %u to %u ms", static_cast(this), static_cast(timerIndex), + static_cast(aDelay)); + + GetInstance().Get().mTimer.FireAtIfEarlier(newFireTime); +} + +void Tcp::Endpoint::CancelTimer(uint8_t aTimerFlag) +{ + /* + * TCPlp has already cleared the timer flag before calling this. Since the + * main timer's callback properly handles the case where no timers are + * actually due, there's actually no work to be done here. + */ + + OT_UNUSED_VARIABLE(aTimerFlag); + + otLogDebgTcp("Endpoint %p cancelled timer %u", static_cast(this), + static_cast(TimerFlagToIndex(aTimerFlag))); +} + +bool Tcp::Endpoint::FirePendingTimers(TimeMilli aNow, bool &aHasFutureTimer, TimeMilli &aEarliestFutureExpiry) +{ + /* + * NOTE: Firing a timer might potentially activate/deactivate other timers. + * If timers x and y expire at the same time, but the callback for timer x + * (for x < y) cancels or postpones timer y, should timer y's callback be + * called? Our answer is no, since timer x's callback has updated the + * TCP stack's state in such a way that it no longer expects timer y's + * callback to to be called. Because the TCP stack thinks that timer y + * has been cancelled, calling timer y's callback could potentially cause + * problems. + * + * If the timer callbacks set other timers, then they may not be taken + * into account when setting aEarliestFutureExpiry. But mTimer's expiry + * time will be updated by those, so we can just compare against mTimer's + * expiry time when resetting mTimer. + */ + for (uint8_t timerIndex = 0; timerIndex != kNumTimers; timerIndex++) + { + if (IsTimerActive(timerIndex)) + { + TimeMilli expiry(mTimers[timerIndex]); + + if (expiry <= aNow) + { + /* TODO: Call TCPlp's callback for this timer. */ + /* If a user callback is called, then return true. */ + } + else + { + aHasFutureTimer = true; + aEarliestFutureExpiry = OT_MIN(aEarliestFutureExpiry, expiry); + } + } + } + + return false; } Error Tcp::Listener::Initialize(Instance &aInstance, otTcpListenerInitializeArgs &aArgs) { - OT_UNUSED_VARIABLE(aInstance); - OT_UNUSED_VARIABLE(aArgs); + Error error; - return kErrorNotImplemented; + SuccessOrExit(error = aInstance.Get().mListeners.Add(*this)); + + mContext = aArgs.mContext; + mAcceptReadyCallback = aArgs.mAcceptReadyCallback; + mAcceptDoneCallback = aArgs.mAcceptDoneCallback; + + mInstance = &aInstance; + +exit: + return error; } Instance &Tcp::Listener::GetInstance(void) { - return *reinterpret_cast(mInstance); + return *static_cast(mInstance); } Error Tcp::Listener::Listen(const SockAddr &aSockName) @@ -166,7 +290,15 @@ Error Tcp::Listener::StopListening(void) Error Tcp::Listener::Deinitialize(void) { - return kErrorNotImplemented; + Error error; + + Tcp &tcp = GetInstance().Get(); + + SuccessOrExit(error = tcp.mListeners.Remove(*this)); + SetNext(nullptr); + +exit: + return error; } Error Tcp::HandleMessage(ot::Ip6::Header &aIp6Header, Message &aMessage, MessageInfo &aMessageInfo) @@ -175,7 +307,86 @@ Error Tcp::HandleMessage(ot::Ip6::Header &aIp6Header, Message &aMessage, Message OT_UNUSED_VARIABLE(aMessage); OT_UNUSED_VARIABLE(aMessageInfo); - return kErrorNotImplemented; + Error error = kErrorNotImplemented; + + for (Endpoint *active = mEndpoints.GetHead(); active != nullptr; active = active->GetNext()) + { + } + + for (Listener *passive = mListeners.GetHead(); passive != nullptr; passive = passive->GetNext()) + { + } + + return error; +} + +void Tcp::HandleTimer(Timer &aTimer) +{ + OT_ASSERT(&aTimer == &aTimer.GetInstance().Get().mTimer); + otLogDebgTcp("Main TCP timer expired"); + aTimer.GetInstance().Get().ProcessTimers(); +} + +void Tcp::ProcessTimers() +{ + TimeMilli now = TimerMilli::GetNow(); + bool pendingTimer; + TimeMilli earliestPendingTimerExpiry; + Endpoint *endpoint; + + OT_ASSERT(!mTimer.IsRunning()); + + /* + * The timer callbacks could potentially set/reset/cancel timers. + * Importantly, Endpoint::SetTimer and Endpoint::CancelTimer do not call + * this function to recompute the timer. If they did, we'd have a + * re-entrancy problem, where the callbacks called in this function could + * wind up re-entering this function in a nested call frame. + * + * In general, calling this function from Endpoint::SetTimer and + * Endpoint::CancelTimer could be inefficient, since those functions are + * called multiple times on each received TCP segment. If we want to + * prevent the main timer from firing except when an actual TCP timer + * expires, a better alternative is to reset the main timer in + * HandleMessage, right before processing signals. That would achieve that + * objective while avoiding re-entrancy issues altogether. + */ +restart: + pendingTimer = false; + earliestPendingTimerExpiry = now.GetDistantFuture(); + for (endpoint = mEndpoints.GetHead(); endpoint != nullptr; endpoint = endpoint->GetNext()) + { + if (endpoint->FirePendingTimers(now, pendingTimer, earliestPendingTimerExpiry)) + { + /* + * If a non-OpenThread callback is called --- which, in practice, + * happens if the connection times out and the user-defined + * connection lost callback is called --- then we might have to + * start over. The reason is that the user might deinitialize + * endpoints, changing the structure of the linked list. For + * example, if the user deinitializes both this endpoint and the + * next one in the linked list, then we can't continue traversing + * the linked list. + */ + goto restart; + } + } + + if (pendingTimer) + { + /* + * We need to use Timer::FireAtIfEarlier instead of timer::FireAt + * because one of the earlier callbacks might have set TCP timers, + * in which case `mTimer` would have been set to the earliest of those + * timers. + */ + mTimer.FireAtIfEarlier(earliestPendingTimerExpiry); + otLogDebgTcp("Reset main TCP timer to %u ms", static_cast(earliestPendingTimerExpiry - now)); + } + else + { + otLogDebgTcp("Did not reset main TCP timer"); + } } } // namespace Ip6 diff --git a/src/core/net/tcp6.hpp b/src/core/net/tcp6.hpp index f0323d731..d216bd013 100644 --- a/src/core/net/tcp6.hpp +++ b/src/core/net/tcp6.hpp @@ -28,7 +28,7 @@ /** * @file - * This file includes definitions for UDP/IPv6 sockets. + * This file includes definitions for TCP/IPv6 sockets. */ #ifndef TCP6_HPP_ @@ -44,6 +44,7 @@ #include "common/linked_list.hpp" #include "common/locator.hpp" #include "common/non_copyable.hpp" +#include "common/timer.hpp" namespace ot { namespace Ip6 { @@ -71,6 +72,8 @@ public: */ class Endpoint : public otTcpEndpoint, public LinkedListEntry { + friend class Tcp; + public: /** * Initializes a TCP endpoint. @@ -325,6 +328,23 @@ public: * */ Error Deinitialize(void); + + private: + enum : uint8_t + { + kTimerDelack = 0, + kTimerRexmtPersist = 1, + kTimerKeep = 2, + kTimer2Msl = 3, + kNumTimers = 4, + }; + + static uint8_t TimerFlagToIndex(uint8_t aTimerFlag); + + bool IsTimerActive(uint8_t aTimerIndex); + void SetTimer(uint8_t aTimerFlag, uint32_t aDelay); + void CancelTimer(uint8_t aTimerFlag); + bool FirePendingTimers(TimeMilli aNow, bool &aHasFutureTimer, TimeMilli &aEarliestFutureExpiry); }; /** @@ -525,6 +545,22 @@ public: * */ Error HandleMessage(ot::Ip6::Header &aIp6Header, Message &aMessage, MessageInfo &aMessageInfo); + +private: + enum + { + kDynamicPortMin = 49152, ///< Service Name and Transport Protocol Port Number Registry + kDynamicPortMax = 65535, ///< Service Name and Transport Protocol Port Number Registry + }; + + static void HandleTimer(Timer &aTimer); + void ProcessTimers(void); + + TimerMilli mTimer; + + LinkedList mEndpoints; + LinkedList mListeners; + uint16_t mEphemeralPort; }; } // namespace Ip6