[tcp] implement global linked lists and TCP timer management (#6790)

This commit is contained in:
Sam Kumar
2021-08-16 18:26:51 -07:00
committed by Jonathan Hui
parent 06f36c9c3d
commit f426ff35c0
4 changed files with 262 additions and 13 deletions
+1 -1
View File
@@ -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
+2
View File
@@ -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. */
};
+222 -11
View File
@@ -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<Tcp>().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<Instance *>(mInstance);
return *static_cast<Instance *>(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<Tcp>();
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<void *>(this), static_cast<unsigned int>(timerIndex),
static_cast<unsigned int>(aDelay));
GetInstance().Get<Tcp>().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<void *>(this),
static_cast<unsigned int>(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<Tcp>().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<Instance *>(mInstance);
return *static_cast<Instance *>(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<Tcp>();
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<Tcp>().mTimer);
otLogDebgTcp("Main TCP timer expired");
aTimer.GetInstance().Get<Tcp>().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<unsigned int>(earliestPendingTimerExpiry - now));
}
else
{
otLogDebgTcp("Did not reset main TCP timer");
}
}
} // namespace Ip6
+37 -1
View File
@@ -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<Endpoint>
{
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<Endpoint> mEndpoints;
LinkedList<Listener> mListeners;
uint16_t mEphemeralPort;
};
} // namespace Ip6