Skip to content
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; // monotonic time of the first transmission
unsigned int LastTimeMilliseconds = 0; // monotonic time of 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
99 changes: 74 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,27 @@ char const * ConnectionClass::Commands[PACKET_COUNT] = {
"ACK"
};

namespace {

/// <summary>Converts engine ticks to milliseconds.</summary>
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 and bounds a legacy connection timeout.</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 +100,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 +113,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 +140,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 +217,8 @@ void ConnectionClass::Init (void)

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

Queue->Init();

Expand Down Expand Up @@ -719,11 +747,8 @@ 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);
}
IsBad = !(Service_Send_Queue() && Service_Receive_Queue());
return(IsBad ? 0 : 1);

} /* end of Service */

Expand All @@ -748,7 +773,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 +794,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 +818,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())
: (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 +838,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 +862,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 +896,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