Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions code/combuf.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ struct SendQueueType {
unsigned int IsUndeliverable : 1; /// 1 = gave up on it (retries or timeout)
unsigned int FirstTime; // time this packet was first sent
unsigned int LastTime; // time this packet was last sent
unsigned int FirstTimeMilliseconds = 0; // millisecond clock at the first transmission
unsigned int LastTimeMilliseconds = 0; // millisecond clock at the latest transmission
unsigned int RetransmitTimeoutMilliseconds = 0; // base RTO captured for this packet
unsigned int SendCount; // # of times this packet has been sent
int BufLen; // size of the packet stored in this entry
char *Buffer; // the data packet
Expand Down
100 changes: 75 additions & 25 deletions code/connect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,11 @@
#include "_timer.h"
#include "dbgprint.h"

#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <limits>
#include <sys\timeb.h>


Expand All @@ -61,6 +64,26 @@ char const * ConnectionClass::Commands[PACKET_COUNT] = {
"ACK"
};

namespace {

NetTiming::Milliseconds Ticks_To_Milliseconds(unsigned int ticks)
{
std::uint64_t const milliseconds = (static_cast<std::uint64_t>(ticks) * 1000 + TIMER_SECOND - 1) / TIMER_SECOND;
if (milliseconds > std::numeric_limits<NetTiming::Milliseconds>::max()) {
return(std::numeric_limits<NetTiming::Milliseconds>::max());
}
return(static_cast<NetTiming::Milliseconds>(milliseconds));
}


/// <summary>Converts a legacy tick timeout and clamps it to the supported range.</summary>
NetTiming::Milliseconds Legacy_Connection_Timeout(unsigned int ticks)
{
return(std::clamp(Ticks_To_Milliseconds(ticks), NetTiming::MINIMUM_CONNECTION_TIMEOUT, NetTiming::MAXIMUM_CONNECTION_TIMEOUT));
}

}


/***************************************************************************
* ConnectionClass::ConnectionClass -- class constructor *
Expand All @@ -76,6 +99,7 @@ char const * ConnectionClass::Commands[PACKET_COUNT] = {
* timeout the max amount of time before we give up on a packet*
* (-1 means retry forever, based on this parameter) *
* extralen max size of app-specific extra bytes (optional) *
* clock monotonic millisecond clock (default if NULL) *
* *
* OUTPUT: *
* none. *
Expand All @@ -88,7 +112,7 @@ char const * ConnectionClass::Commands[PACKET_COUNT] = {
*=========================================================================*/
ConnectionClass::ConnectionClass (int numsend, int numreceive,
int maxlen, unsigned short magicnum, unsigned int retry_delta,
unsigned int max_retries, unsigned int timeout, int extralen)
unsigned int max_retries, unsigned int timeout, int extralen, NetTiming::MillisecondClock const * clock)
{
/*------------------------------------------------------------------------
Compute our maximum packet length
Expand All @@ -115,6 +139,7 @@ ConnectionClass::ConnectionClass (int numsend, int numreceive,
Set the timeout for this connection.
------------------------------------------------------------------------*/
Timeout = timeout;
MillisecondTime = clock != nullptr ? clock : &NetTiming::Default_Clock();

/*------------------------------------------------------------------------
Allocate the packet staging buffer. This will be used to
Expand Down Expand Up @@ -191,6 +216,8 @@ void ConnectionClass::Init (void)

LastSeqID = 0xffffffff;
LastReadID = 0xffffffff;
RoundTripEstimator.Reset();
IsBad = false;

Queue->Init();

Expand Down Expand Up @@ -719,11 +746,10 @@ int ConnectionClass::Service (void)
been ACK'd yet. Entries that the app has read, and have been ACK'd,
should be removed.
------------------------------------------------------------------------*/
if ( Service_Send_Queue() && Service_Receive_Queue() ) {
return(1);
} else {
return(0);
}
int const send_status = Service_Send_Queue();
int const receive_status = Service_Receive_Queue();
IsBad = !(send_status && receive_status);
return(IsBad ? 0 : 1);

} /* end of Service */

Expand All @@ -748,7 +774,7 @@ int ConnectionClass::Service_Send_Queue (void)
int i;
int num_entries;
SendQueueType *send_entry; // ptr to send queue entry
CommHeaderType *packet_hdr; // packet header
CommHeaderType packet_header; // packet header
unsigned int curtime; // current time
int bad_conn = 0;

Expand All @@ -769,9 +795,15 @@ int ConnectionClass::Service_Send_Queue (void)
/*..................................................................
Update this queue's response time
..................................................................*/
packet_hdr = (CommHeaderType *)send_entry->Buffer;
if (packet_hdr->Code == PACKET_DATA_ACK) {
Queue->Add_Delay(Time() - send_entry->FirstTime);
if (send_entry->BufLen >= (int)sizeof(CommHeaderType)) {
CommHeaderType header;
memcpy(&header, send_entry->Buffer, sizeof(header));
if (header.Code == PACKET_DATA_ACK) {
Queue->Add_Delay(Time() - send_entry->FirstTime);
if (Adaptive_Timing_Enabled()) {
RoundTripEstimator.Acknowledge(send_entry->FirstTimeMilliseconds, send_entry->SendCount, *MillisecondTime);
}
}
}

/*..................................................................
Expand All @@ -787,6 +819,18 @@ int ConnectionClass::Service_Send_Queue (void)
need it.
------------------------------------------------------------------------*/
num_entries = Queue->Num_Send();
curtime = Time();
NetTiming::Milliseconds const current_milliseconds = MillisecondTime->Now();
bool const adaptive_channel = Adaptive_Timing_Enabled();
bool const adaptive_timing = adaptive_channel && RoundTripEstimator.Has_Sample();
bool const timeout_enabled = Timeout != (unsigned int)-1;
NetTiming::Milliseconds const connection_timeout = !timeout_enabled
? NetTiming::MAXIMUM_CONNECTION_TIMEOUT
: (adaptive_timing ? NetTiming::Connection_Timeout(RoundTripEstimator.Smoothed_Rtt(), RoundTripEstimator.Retransmit_Timeout())
: (adaptive_channel ? Legacy_Connection_Timeout(Timeout) : Ticks_To_Milliseconds(Timeout)));
NetTiming::Milliseconds const base_retry_timeout = adaptive_timing
? NetTiming::Initial_Retry_Timeout(RoundTripEstimator.Retransmit_Timeout(), connection_timeout)
: Ticks_To_Milliseconds(RetryDelta);

for (i = 0; i < num_entries; i++) {
send_entry = Queue->Get_Send(i);
Expand All @@ -795,13 +839,19 @@ int ConnectionClass::Service_Send_Queue (void)
continue;
}

/*.....................................................................
Only send the message if time has elapsed. (The message's Time
fields are init'd to 0 when a message is queue'd or unqueue'd, so the
first time through, the delta time will appear large.)
.....................................................................*/
curtime = Time();
if (curtime - send_entry->LastTime > RetryDelta) {
NetTiming::RetransmitState const retransmit_state{
send_entry->FirstTimeMilliseconds,
send_entry->LastTimeMilliseconds,
send_entry->RetransmitTimeoutMilliseconds,
send_entry->SendCount
};
NetTiming::RetryDecision const retry_decision = NetTiming::Evaluate_Retry(
retransmit_state, current_milliseconds, base_retry_timeout, connection_timeout, timeout_enabled, adaptive_channel);
if (retry_decision.TimedOut) {
bad_conn = 1;
send_entry->IsUndeliverable = true;
}
if (retry_decision.Send) {

/*..................................................................
Send the message
Expand All @@ -813,20 +863,26 @@ int ConnectionClass::Service_Send_Queue (void)
Fill in Time fields
..................................................................*/
send_entry->LastTime = curtime;
send_entry->LastTimeMilliseconds = current_milliseconds;
if (send_entry->SendCount==0) {
send_entry->FirstTime = curtime;
send_entry->FirstTimeMilliseconds = current_milliseconds;
send_entry->RetransmitTimeoutMilliseconds = base_retry_timeout;

/*...............................................................
If this is the 1st time we're sending this packet, and it doesn't
require an ACK, mark it as ACK'd; then, the next time through,
it will just be removed from the queue.
...............................................................*/
packet_hdr = (CommHeaderType *)send_entry->Buffer;
if (packet_hdr->Code == PACKET_DATA_NOACK) {
memcpy(&packet_header, send_entry->Buffer, sizeof(packet_header));
if (packet_header.Code == PACKET_DATA_NOACK) {
send_entry->IsACK = 1;
}
} else {
NumResends++;
if (adaptive_channel) {
RoundTripEstimator.Note_Retransmit(send_entry->RetransmitTimeoutMilliseconds);
}
}

/*..................................................................
Expand All @@ -841,12 +897,6 @@ int ConnectionClass::Service_Send_Queue (void)
bad_conn = 1;
send_entry->IsUndeliverable = true;
}

if (Timeout != -1 &&
(send_entry->LastTime - send_entry->FirstTime) > Timeout) {
bad_conn = 1;
send_entry->IsUndeliverable = true;
}
}
}

Expand Down
17 changes: 12 additions & 5 deletions code/connect.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
*/
#include "combuf.h"
#include "netadmit.h"
#include "nettiming.h"

/*
********************************** Defines **********************************
Expand Down Expand Up @@ -143,9 +144,8 @@ class ConnectionClass
/*.....................................................................
Constructor/destructor.
.....................................................................*/
ConnectionClass (int numsend, int numrecieve, int maxlen,
unsigned short magicnum, unsigned int retry_delta,
unsigned int max_retries, unsigned int timeout, int extralen = 0);
ConnectionClass (int numsend, int numrecieve, int maxlen, unsigned short magicnum, unsigned int retry_delta,
unsigned int max_retries, unsigned int timeout, int extralen = 0, NetTiming::MillisecondClock const *clock = nullptr);
virtual ~ConnectionClass (void);

/*.....................................................................
Expand Down Expand Up @@ -185,13 +185,15 @@ class ConnectionClass
unsigned int Time_Out (void) { return(Timeout); }
void Set_TimeOut (unsigned int t) { Timeout = t;}
unsigned int Max_Packet_Len (void) { return(MaxPacketLen); }
void Reset_Round_Trip_Time(void) {RoundTripEstimator.Reset();}
static const char * Command_Name(int command);

int Num_Resends(void) const { return(NumResends); }
int Num_Lost(void) const { return(NumLost); }
int Percent_Lost(void) const { return(PercentLost); }
int Missed_Overall(void) const { return(MissedOverall); }
int Missed_Magic(void) const { return(MissedMagic); }
bool Is_Bad(void) const { return(IsBad); }

enum PacketDropReasonType {
CONNECTION_DROP_SHORT_HEADER,
Expand Down Expand Up @@ -227,8 +229,8 @@ class ConnectionClass
is protected; it's only called by the ACK/Retry logic, not the
application.
.....................................................................*/
virtual int Send(char *buf, int buflen, void *extrabuf,
int extralen) = 0;
virtual int Send(char *buf, int buflen, void *extrabuf, int extralen) = 0;
virtual bool Adaptive_Timing_Enabled(void) const {return(true);}
void Record_Packet_Drop(PacketDropReasonType reason);
void Record_Admission_Drop(NetAdmission::Error error, unsigned char code);

Expand Down Expand Up @@ -293,6 +295,11 @@ class ConnectionClass
.....................................................................*/
unsigned int Timeout;

// An injected clock must outlive the connection.
NetTiming::MillisecondClock const *MillisecondTime;
NetTiming::RttEstimator RoundTripEstimator;
bool IsBad = false;

/*.....................................................................
Running totals of # of packets we send & receive which require an ACK,
and those that don't.
Expand Down
2 changes: 2 additions & 0 deletions code/ipxgconn.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ class IPXGlobalConnClass : public IPXConnClass
// stored in the extra buffer within the Queue.
//.....................................................................
virtual int Send (char *buf, int buflen, void *extrabuf, int extralen) override;
virtual bool Adaptive_Timing_Enabled(void) const override {return(false);}

//.....................................................................
// This routine is overloaded from SequencedConnClass, because the
// Global Connection needs to ACK its packets differently from the
Expand Down
8 changes: 7 additions & 1 deletion code/ipxmgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1066,10 +1066,13 @@ int IPXManagerClass::Service(void)
}
}
for (i = 0; i < NumConnections; i++) {
bool const was_bad = Connection[i]->Is_Bad();
if (!Connection[i]->Service()) {
rc = 0;
BadConnection = Connection[i]->ID;
DebugString("Error - Connection %d has gone bad\n", BadConnection);
if (!was_bad) {
DebugString("Error - Connection %d has gone bad\n", BadConnection);
}
}
}

Expand Down Expand Up @@ -1478,6 +1481,9 @@ void IPXManagerClass::Reset_Response_Time(bool zero)

for (i = 0; i < NumConnections; i++) {
Connection[i]->Queue->Reset_Response_Time(zero);
if (zero) {
Connection[i]->Reset_Round_Trip_Time();
}
}

if (GlobalChannel)
Expand Down
42 changes: 42 additions & 0 deletions code/nettime.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*******************************************************************************
* O P E N T S
*******************************************************************************
* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright 2026 OpenTS contributors
*
* See LICENSE.md for applicable additional terms and warranty disclaimers.
******************************************************************************/


#include "nettime.h"

#include <windows.h>
#include <mmsystem.h>


namespace NetTiming
{
namespace
{
class SystemMillisecondClock final : public MillisecondClock
{
public:
Milliseconds Now(void) const override;
};
}


/// <summary>Reads the system's wrapping millisecond clock.</summary>
Milliseconds SystemMillisecondClock::Now(void) const
{
return(static_cast<Milliseconds>(::timeGetTime()));
}


/// <summary>Returns the process-wide network clock.</summary>
MillisecondClock const & Default_Clock(void)
{
static SystemMillisecondClock clock;
return(clock);
}
}
38 changes: 38 additions & 0 deletions code/nettime.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*******************************************************************************
* O P E N T S
*******************************************************************************
* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright 2026 OpenTS contributors
*
* See LICENSE.md for applicable additional terms and warranty disclaimers.
******************************************************************************/


#pragma once

#include <cstdint>


namespace NetTiming
{
using Milliseconds = std::uint32_t;

class MillisecondClock
{
public:
virtual ~MillisecondClock() = default;
virtual Milliseconds Now(void) const = 0;
};

MillisecondClock const & Default_Clock(void);

constexpr Milliseconds Elapsed_Milliseconds(Milliseconds start, Milliseconds finish)
{
return(finish - start);
}

constexpr bool Milliseconds_Have_Elapsed(Milliseconds start, Milliseconds now, Milliseconds duration)
{
return(Elapsed_Milliseconds(start, now) >= duration);
}
}
Loading
Loading