mirror of
https://github.com/espressif/openthread.git
synced 2026-09-27 11:27:36 +00:00
[tcp] implement global linked lists and TCP timer management (#6790)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user