NORM-mirror/src/common/normNode.cpp

3833 lines
145 KiB
C++
Executable File

#include "normNode.h"
#include "normSession.h"
#include "normEncoderMDP.h"
#include "normEncoderRS8.h" // 8-bit Reed-Solomon encoder of RFC 5510
#include "normEncoderRS16.h" // 16-bit Reed-Solomon encoder of RFC 5510
NormNode::NormNode(Type nodeType, class NormSession& theSession, NormNodeId nodeId)
: session(theSession), node_type(nodeType), id(nodeId), reference_count(1), user_data(NULL),
parent(NULL), right(NULL), left(NULL)
{
}
NormNode::~NormNode()
{
}
void NormNode::Retain()
{
reference_count++;
} // end NormNode::Retain()
void NormNode::Release()
{
if (reference_count)
reference_count--;
else
PLOG(PL_ERROR, "NormNode::Release() releasing non-retained node?!\n");
if (0 == reference_count) delete this;
} // end NormNode::Release()
const NormNodeId& NormNode::LocalNodeId() const
{return session.LocalNodeId();}
NormNode::Accumulator::Accumulator()
: msb(0), lsb(0)
{
}
NormCCNode::NormCCNode(class NormSession& theSession, NormNodeId nodeId)
: NormNode(CC_NODE, theSession, nodeId)
{
}
NormCCNode::~NormCCNode()
{
}
NormSenderNode::CmdBuffer::CmdBuffer()
: length(0), next(NULL)
{
}
NormSenderNode::CmdBuffer::~CmdBuffer()
{
}
const double NormSenderNode::DEFAULT_NOMINAL_INTERVAL = 2*NormSession::DEFAULT_GRTT_ESTIMATE;
const double NormSenderNode::ACTIVITY_INTERVAL_MIN = 1.0; // 1 second min activity timeout
NormSenderNode::NormSenderNode(class NormSession& theSession, NormNodeId nodeId)
: NormNode(SENDER, theSession, nodeId), instance_id(0), robust_factor(session.GetRxRobustFactor()),
synchronized(false), sync_id(0),
is_open(false), preset_fti(false), preset_stream(NULL),
repair_boundary(BLOCK_BOUNDARY), decoder(NULL), erasure_loc(NULL),
retrieval_loc(NULL), retrieval_pool(NULL), ack_pending(false),
ack_ex_pending(false), ack_ex_buffer(NULL), ack_ex_length(0),
notify_on_grtt_update(true),
cc_sequence(0), cc_enable(false), cc_feedback_needed(false), cc_rate(0.0),
rtt_confirmed(false), is_clr(false), is_plr(false),
slow_start(true), send_rate(0.0), recv_rate(0.0), recv_rate_prev(0.0),
nominal_packet_size(0), cmd_buffer_head(NULL), cmd_buffer_tail(NULL),
cmd_buffer_pool(NULL), resync_count(0),
nack_count(0), suppress_count(0), completion_count(0), failure_count(0)
{
repair_boundary = session.ReceiverGetDefaultRepairBoundary();
sync_policy = session.ReceiverGetDefaultSyncPolicy();
default_nacking_mode = session.ReceiverGetDefaultNackingMode();
unicast_nacks = session.ReceiverGetUnicastNacks();
max_pending_range = session.GetRxCacheMax();
repair_timer.SetListener(this, &NormSenderNode::OnRepairTimeout);
repair_timer.SetInterval(0.0);
repair_timer.SetRepeat(1);
activity_timer.SetListener(this, &NormSenderNode::OnActivityTimeout);
double activityInterval = 2*NormSession::DEFAULT_GRTT_ESTIMATE*session.GetTxRobustFactor();
if (activityInterval < ACTIVITY_INTERVAL_MIN) activityInterval = ACTIVITY_INTERVAL_MIN;
activity_timer.SetInterval(activityInterval);
activity_timer.SetRepeat(robust_factor);
cc_timer.SetListener(this, &NormSenderNode::OnCCTimeout);
cc_timer.SetInterval(0.0);
cc_timer.SetRepeat(1);
ack_timer.SetListener(this, &NormSenderNode::OnAckTimeout);
ack_timer.SetInterval(0.0);
ack_timer.SetRepeat(0);
grtt_send_time.tv_sec = 0;
grtt_send_time.tv_usec = 0;
grtt_quantized = NormQuantizeRtt(NormSession::DEFAULT_GRTT_ESTIMATE);
grtt_estimate = NormUnquantizeRtt(grtt_quantized);
gsize_quantized = NormQuantizeGroupSize(NormSession::DEFAULT_GSIZE_ESTIMATE);
gsize_estimate = NormUnquantizeGroupSize(gsize_quantized);
backoff_factor = NormSession::DEFAULT_BACKOFF_FACTOR;
rtt_quantized = NormQuantizeRtt(NormSession::DEFAULT_GRTT_ESTIMATE);
rtt_estimate = NormUnquantizeRtt(rtt_quantized);
loss_estimator.SetLossEventWindow(NormSession::DEFAULT_GRTT_ESTIMATE);
loss_estimator.SetIgnoreLoss(session.GetEcnIgnoreLoss());
loss_estimator.SetTolerateLoss(session.GetCCTolerateLoss());
prev_update_time.tv_sec = 0;
prev_update_time.tv_usec = 0;
}
NormSenderNode::~NormSenderNode()
{
Close();
}
bool NormSenderNode::Open(UINT16 instanceId)
{
instance_id = instanceId;
if (!rx_table.Init(max_pending_range))
{
PLOG(PL_FATAL, "NormSenderNode::Open() rx_table init error\n");
Close();
return false;
}
if (!rx_pending_mask.Init(max_pending_range, 0x0000ffff))
{
PLOG(PL_FATAL, "NormSenderNode::Open() rx_pending_mask init error\n");
Close();
return false;
}
if (!rx_repair_mask.Init(max_pending_range, 0x0000ffff))
{
PLOG(PL_FATAL, "NormSenderNode::Open() rx_repair_mask init error\n");
Close();
return false;
}
is_open = true;
synchronized = false;
//resync_count = 0; // reset resync_count
return true;
} // end NormSenderNode::Open()
void NormSenderNode::Close()
{
if (activity_timer.IsActive()) activity_timer.Deactivate();
if (repair_timer.IsActive()) repair_timer.Deactivate();
if (cc_timer.IsActive()) cc_timer.Deactivate();
if (ack_timer.IsActive()) ack_timer.Deactivate();
FreeBuffers();
if (NULL != ack_ex_buffer)
{
delete[] ack_ex_buffer;
ack_ex_buffer = NULL;
ack_ex_length = 0;
}
// Delete any command buffers from cmd_buffer queue
while (NULL != cmd_buffer_head)
{
CmdBuffer* buf = cmd_buffer_head;
cmd_buffer_head = buf->GetNext();
delete buf;
}
// Delete any command buffers from cmd_buffer pool
while (NULL != cmd_buffer_pool)
{
CmdBuffer* buf = cmd_buffer_pool;
cmd_buffer_pool = buf->GetNext();
delete buf;
}
rx_repair_mask.Destroy();
rx_pending_mask.Destroy();
rx_table.Destroy();
synchronized = false;
is_open = false;
} // end NormSenderNode::Close()
bool NormSenderNode::AllocateBuffers(unsigned int bufferSpace,
UINT8 fecId,
UINT16 fecInstanceId,
UINT8 fecM,
UINT16 segmentSize,
UINT16 numData,
UINT16 numParity)
{
ASSERT(IsOpen());
// Calculate how much memory each buffered block will require
UINT16 blockSize = numData + numParity;
unsigned long maskSize = blockSize >> 3;
if (0 != (blockSize & 0x07)) maskSize++;
unsigned long blockStateSpace = sizeof(NormBlock) + blockSize * sizeof(char*) + 2*maskSize;
// The "bufferFactor" weight determines the ratio of segment buffers (blockSegmentSpace) to
// allocated NormBlock (blockStateSpace).
// If "bufferFactor = 1.0", this is equivalent to the old scheme, where every allocated
// block can be fully buffered (numData segs) for decoding (no seeking required). If
// "bufferFactor = 0.0", only a guarantee of at least "numParity" segments per block is
// enforced. Note that "bufferFactor" values > 0.0 help reduce "seeking" for decoding,
// but reduce the number of blocks for which NORM can keep state. Note this only comes
// into play when NORM would be "buffer constrained"
// (TBD) perhaps we should keep state for more blocks than we can even buffer parity for ???
// (this would reduce requests for full block retransmissions when resource constrained)
// (this would correspond to "bufferFactor < 0.0"
double bufferFactor = 0.0; // (TBD) let app control "bufferFactor"???
unsigned long segPerBlock =
(unsigned long) ((bufferFactor * (double)numData) +
((1.0 - bufferFactor) * (double)numParity) + 0.5);
if (segPerBlock > numData) segPerBlock = numData;
// If there's no parity, no segment buffering for decoding is required at all!
// (Thus, the full rxbuffer space can be used for block state)
if (0 == numParity) segPerBlock = 0;
unsigned long blockSegmentSpace = segPerBlock * (segmentSize + NormDataMsg::GetStreamPayloadHeaderLength());
unsigned long blockSpace = blockStateSpace + blockSegmentSpace;
unsigned long numBlocks = bufferSpace / blockSpace;
// Round numBlocks upward
if (bufferSpace > (numBlocks*blockSpace)) numBlocks++;
// Always have at least 2 blocks in the pool
if (numBlocks < 2) numBlocks = 2;
unsigned long numSegments = numBlocks * segPerBlock;
if (!block_pool.Init((UINT32)numBlocks, blockSize))
{
PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() block_pool init error\n");
Close();
return false;
}
// Segment buffers include space for NORM_OBJECT_STREAM stream payload header
if (!segment_pool.Init((unsigned int)numSegments, segmentSize+NormDataMsg::GetStreamPayloadHeaderLength()))
{
PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() segment_pool init error\n");
Close();
return false;
}
// The "retrieval_pool" is used for FEC block decoding
// These segments are temporarily used for "retrieved" source symbol segments
// that aren't still cached and needed for block decoding
if (!(retrieval_pool = new char*[numData]))
{
PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() new retrieval_pool error: %s\n", GetErrorString());
Close();
return false;
}
memset(retrieval_pool, 0, numData*sizeof(char*));
for (UINT16 i = 0; i < numData; i++)
{
// allocate segment with extra byte for stream flags ...
char* s = new char[segmentSize+NormDataMsg::GetStreamPayloadHeaderLength()];
if (NULL == s)
{
PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() new retrieval segment error: %s\n", GetErrorString());
Close();
return false;
}
retrieval_pool[i] = s;
}
retrieval_index = 0;
if (!(retrieval_loc = new unsigned int[numData]))
{
PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() retrieval_loc allocation error: %s\n", GetErrorString());
Close();
return false;
}
if (NULL != decoder) delete decoder;
if (0 != numParity)
{
switch (fecId)
{
case 2:
if (8 == fecM)
{
if (NULL == (decoder = new NormDecoderRS8))
{
PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() new NormDecoderRS8 error: %s\n", GetErrorString());
Close();
return false;
}
}
else if (16 == fecM)
{
if (NULL == (decoder = new NormDecoderRS16))
{
PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() new NormDecoderRS16 error: %s\n", GetErrorString());
Close();
return false;
}
}
else
{
PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() error: unsupported fecId=2 'm' value %d!\n", fecM);
Close();
return false;
}
break;
case 5:
if (NULL == (decoder = new NormDecoderRS8))
{
PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() new NormDecoderRS8 error: %s\n", GetErrorString());
Close();
return false;
}
break;
case 129:
#ifdef ASSUME_MDP_FEC
if (NULL == (decoder = new NormDecoderMDP))
{
PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() new NormDecoderMDP error: %s\n", GetErrorString());
Close();
return false;
}
#else
if (0 == fecInstanceId)
{
if (NULL == (decoder = new NormDecoderRS8))
{
PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() new NormDecoderRS8 error: %s\n", GetErrorString());
Close();
return false;
}
}
else
{
PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() error: unknown fecId=129 instanceId!\n");
Close();
return false;
}
#endif // if/else ASSUME_MDP_FEC
break;
default:
PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() error: unknown fecId>%d!\n", fecId);
Close();
return false;
}
if (!decoder->Init(numData, numParity, segmentSize+NormDataMsg::GetStreamPayloadHeaderLength()))
{
PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() decoder init error\n");
Close();
return false;
}
if (!(erasure_loc = new unsigned int[numParity]))
{
PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() erasure_loc allocation error: %s\n", GetErrorString());
Close();
return false;
}
}
else
{
decoder = NULL;
} // end if/else (0 != numParity)
fti_data.SetSegmentSize(segmentSize);
nominal_packet_size = (double)segmentSize;
fec_id = fecId;
fti_data.SetFecFieldSize(fecM);
fti_data.SetFecMaxBlockLen(numData);
fti_data.SetFecNumParity(numParity);
IncrementResyncCount();
return true;
} // end NormSenderNode::AllocateBuffers()
void NormSenderNode::FreeBuffers()
{
if (erasure_loc)
{
delete[] erasure_loc;
erasure_loc = NULL;
}
if (NULL != decoder)
{
decoder->Destroy();
delete decoder;
decoder = NULL;
}
if (retrieval_loc)
{
delete[] retrieval_loc;
retrieval_loc = NULL;
}
if (retrieval_pool)
{
UINT16 numData = BlockSize();
for (unsigned int i = 0; i < numData; i++)
{
if (retrieval_pool[i])
{
delete[] retrieval_pool[i];
retrieval_pool[i] = NULL;
}
}
delete[] retrieval_pool;
retrieval_pool = NULL;
}
NormObject* obj;
while ((obj = rx_table.Find(rx_table.RangeLo())))
{
UINT16 objectId = obj->GetId();
AbortObject(obj);
// We do the following to remember which _objects_ were pending
rx_pending_mask.Set(objectId);
}
segment_pool.Destroy();
block_pool.Destroy();
fti_data.Invalidate();
} // end NormSenderNode::FreeBuffers()
unsigned long NormSenderNode::CurrentStreamBufferUsage()
{
unsigned long usage = 0;
NormObjectTable::Iterator it(rx_table);
NormObject* obj;
while (NULL != (obj = it.GetNextObject()))
{
if (obj->IsStream())
usage += static_cast<NormStreamObject*>(obj)->CurrentBufferUsage();
}
return usage;
} // end NormSenderNode::CurrentStreamBufferUsage()
unsigned long NormSenderNode::PeakStreamBufferUsage()
{
unsigned long usage = 0;
NormObjectTable::Iterator it(rx_table);
NormObject* obj;
while (NULL != (obj = it.GetNextObject()))
{
if (obj->IsStream())
usage += static_cast<NormStreamObject*>(obj)->PeakBufferUsage();
}
return usage;
} // end NormSenderNode::PeakStreamBufferUsage()
unsigned long NormSenderNode::StreamBufferOverunCount()
{
unsigned long count = 0;
NormObjectTable::Iterator it(rx_table);
NormObject* obj;
while (NULL != (obj = it.GetNextObject()))
{
if (obj->IsStream())
count += static_cast<NormStreamObject*>(obj)->BufferOverunCount();
}
return count;
} // end NormSenderNode::StreamBufferOverunCount()
bool NormSenderNode::ReadNextCmd(char* buffer, unsigned int* buflen)
{
if (NULL == buflen) return false; // (TBD) indicate error type
if (NULL != cmd_buffer_head)
{
if (NULL == buffer)
{
// User is just querying for content length size.
*buflen = cmd_buffer_head->GetContentLength();
return true;
}
else if (*buflen < cmd_buffer_head->GetContentLength())
{
*buflen = cmd_buffer_head->GetContentLength();
return false;
}
else
{
// a) remove cmd from cmd_buffer queue
CmdBuffer* buf = cmd_buffer_head;
cmd_buffer_head = buf->GetNext();
if (NULL == cmd_buffer_head)
cmd_buffer_tail = NULL;
// b) copy content
*buflen = buf->GetContentLength();
memcpy(buffer, buf->GetContent(), *buflen);
// c) put cmd into cmd_buffer pool
buf->Append(cmd_buffer_pool);
cmd_buffer_pool = buf;
return true;
}
}
else
{
// Tell user there is no cmd content to read
*buflen = 0;
return false;
}
} // end NormSenderNode::ReadNextCmd()
bool NormSenderNode::SendAckEx(const char* appAck, unsigned int appAckLen)
{
// First copy in the new appAck content for transmission
if (NULL != appAck)
{
if (appAckLen != ack_ex_length)
{
if (NULL != ack_ex_buffer)
{
delete[] ack_ex_buffer;
ack_ex_buffer = NULL;
ack_ex_length = 0;
}
// Make sure there is room for the header extension
if (appAckLen > SegmentSize())
{
PLOG(PL_ERROR, "NormSenderNode::SendAckEx() error: application-defined ACK_REQ content too large!\n");
ack_ex_pending = false;
return false;
}
else if (NULL == (ack_ex_buffer = new char[appAckLen]))
{
PLOG(PL_ERROR, "NormSenderNode::SendAckEx() new app_req_buffer error: %s\n", GetErrorString());
ack_ex_pending = false;
return false;
}
}
memcpy(ack_ex_buffer, appAck, appAckLen);
ack_ex_length = appAckLen;
}
else if (NULL != ack_ex_buffer)
{
delete[] ack_ex_buffer;
ack_ex_buffer = NULL;
ack_ex_length = 0;
}
ack_ex_pending = false;
if (!ack_timer.IsActive())
OnAckTimeout(ack_timer);
return true;
} // end NormSenderNode::SendAckEx()
bool NormSenderNode::GetWatermarkEx(char* buffer, unsigned int* buflen)
{
if (0 != ack_ex_length)
{
if (NULL != buflen)
{
if (*buflen < ack_ex_length)
{
*buflen = ack_ex_length;
return false;
}
*buflen = ack_ex_length;
if (NULL != buffer)
memcpy(buffer, ack_ex_buffer, ack_ex_length);
else
return false;
}
return true;
}
else
{
if (NULL != buflen) *buflen = 0;
return false; // no application-defined ACK request data
}
} // end NormSenderNode::GetWatermarkEx()
void NormSenderNode::SetRobustFactor(int value)
{
robust_factor = value;
// activity timer depends upon robust_factor
// (TBD) do a proper rescaling here instead?
double activityInterval = 2*session.GetTxRobustFactor()*grtt_estimate;
if (activityInterval < ACTIVITY_INTERVAL_MIN) activityInterval = ACTIVITY_INTERVAL_MIN;
activity_timer.SetInterval(activityInterval);
activity_timer.SetRepeat(robust_factor);
if (activity_timer.IsActive()) activity_timer.Reschedule();
} // end NormSenderNode::SetRobustFactor()
void NormSenderNode::UpdateGrttEstimate(UINT8 grttQuantized)
{
grtt_quantized = grttQuantized;
grtt_estimate = NormUnquantizeRtt(grttQuantized);
PLOG(PL_DEBUG, "NormSenderNode::UpdateGrttEstimate() node>%lu sender>%lu new grtt: %lf sec\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId(), grtt_estimate);
// activity timer depends upon sender's grtt estimate
// (TBD) do a proper rescaling here instead?
double activityInterval = 2*session.GetTxRobustFactor()*grtt_estimate;
if (activityInterval < ACTIVITY_INTERVAL_MIN) activityInterval = ACTIVITY_INTERVAL_MIN;
activity_timer.SetInterval(activityInterval);
if (activity_timer.IsActive()) activity_timer.Reschedule();
// (TBD) Scale/reschedule repair_timer and/or cc_timer???
if (notify_on_grtt_update)
{
notify_on_grtt_update = false;
session.Notify(NormController::GRTT_UPDATED, this, (NormObject*)NULL);
}
} // end NormSenderNode::UpdateGrttEstimate()
void NormSenderNode::HandleCommand(const struct timeval& currentTime,
const NormCmdMsg& cmd)
{
UINT8 grttQuantized = cmd.GetGrtt();
if (grttQuantized != grtt_quantized) UpdateGrttEstimate(grttQuantized);
UINT8 gsizeQuantized = cmd.GetGroupSize();
if (gsizeQuantized != gsize_quantized)
{
gsize_quantized = gsizeQuantized;
gsize_estimate = NormUnquantizeGroupSize(gsizeQuantized);
PLOG(PL_DEBUG, "NormSenderNode::HandleCommand() node>%lu sender>%lu new group size:%lf\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId(), gsize_estimate);
}
backoff_factor = (double)cmd.GetBackoffFactor();
NormCmdMsg::Flavor flavor = cmd.GetFlavor();
switch (flavor)
{
case NormCmdMsg::SQUELCH:
{
const NormCmdSquelchMsg& squelch = (const NormCmdSquelchMsg&)cmd;
if (!synchronized)
{
// Cache the remote sender's "fec_id" so we will
// build proper NACKs since we have no prior state
fec_id = squelch.GetFecId();
if (2 == fec_id) // see comment in HandleObjectMessage() method on this
fti_data.SetFecFieldSize(16);
else
fti_data.SetFecFieldSize(8);
}
else
{
// TBD - should we confirm the sender's FEC config here???
}
// 1) Sync to squelch (discards all objects prior to squelch objectId)
NormObjectId objectId = squelch.GetObjectId();
Sync(objectId);
// 2) Prune stream object if applicable
NormObject* obj = rx_table.Find(objectId);
if ((NULL != obj) && (NormObject::STREAM == obj->GetType()))
{
NormBlockId blockId = squelch.GetFecBlockId(fti_data.GetFecFieldSize());
static_cast<NormStreamObject*>(obj)->Prune(blockId, true);
}
// 3) Discard any invalidated objects (those listed in the squelch)
UINT16 objCount = squelch.GetInvalidObjectCount();
for (UINT16 i = 0; i < objCount; i++)
{
NormObjectId objId = squelch.GetInvalidObjectId(i);
obj = rx_table.Find(objId);
if (NULL != obj) AbortObject(obj);
rx_pending_mask.Unset(objId);
}
break;
}
case NormCmdMsg::ACK_REQ:
// (TBD) handle ack requests (i.e. incl. app-defined ack requests)
break;
case NormCmdMsg::CC:
{
// TBD - do some duplicate detection here ?
const NormCmdCCMsg& cc = (const NormCmdCCMsg&)cmd;
grtt_recv_time = currentTime;
cc.GetSendTime(grtt_send_time);
cc_sequence = cc.GetCCSequence();
NormCCRateExtension ext;
bool hasCCRateExtension = false;
while (cc.GetNextExtension(ext))
{
if (NormHeaderExtension::CC_RATE == ext.GetType())
{
hasCCRateExtension = true;
cc_enable = true;
send_rate = NormUnquantizeRate(ext.GetSendRate());
// Are we in the cc_node_list?
UINT8 flags, rtt;
UINT16 loss;
if (cc.GetCCNode(LocalNodeId(), flags, rtt, loss))
{
if (rtt != rtt_quantized)
{
rtt_quantized = rtt;
rtt_estimate = NormUnquantizeRtt(rtt);
loss_estimator.SetLossEventWindow(rtt_estimate);
}
rtt_confirmed = true;
if (0 != (flags & NormCC::CLR))
{
is_clr = true;
is_plr = false;
}
else if (0 != (flags & NormCC::PLR))
{
is_clr = false;
is_plr = true;
}
else
{
is_clr = is_plr = false;
}
}
else
{
is_clr = is_plr = false;
}
double maxBackoff;
if (is_clr || is_plr || !session.Address().IsMulticast())
{
// Respond immediately (i.e., no backoff, holdoff etc)
maxBackoff = 0.0;
if (cc_timer.IsActive()) cc_timer.Deactivate();
cc_timer.ResetRepeat(); // makes sure timer phase is correct
OnCCTimeout(cc_timer);
break;
}
else
{
if (cc_timer.IsActive()) break;
double backoffFactor = backoff_factor;
backoffFactor = MAX(backoffFactor, 4.0);
maxBackoff = grtt_estimate*backoffFactor;
}
double backoffTime =
(maxBackoff > 0.0) ?
ExponentialRand(maxBackoff, gsize_estimate) : 0.0;
// Bias backoff timeout based on our rate
double r;
double ccLoss = slow_start ? 0.0 : LossEstimate();
if (0.0 == ccLoss)
{
r = recv_rate / send_rate;
cc_rate = 2.0 * recv_rate;
}
else
{
double nominalSize = nominal_packet_size ? nominal_packet_size : SegmentSize();
cc_rate = NormSession::CalculateRate(nominalSize, rtt_estimate, ccLoss);
r = cc_rate / send_rate;
r = MIN(r, 0.9);
r = MAX(r, 0.5);
r = (r - 0.5) / 0.4;
}
//DMSG(0, "NormSenderNode::HandleCommand(CC) node>%lu bias:%lf "
// "recv_rate:%lf send_rate:%lf grtt:%lf gsize:%lf\n",
// (unsigned long)LocalNodeId(), r, 8.0e-03*recv_rate, 8.0e-03*send_rate,
//
backoffTime = 0.25 * r * maxBackoff + 0.75 * backoffTime;
cc_timer.SetInterval(backoffTime);
PLOG(PL_DEBUG, "NormSenderNode::HandleCommand() node>%lu begin CC back-off: %lf sec)...\n",
(unsigned long)LocalNodeId(), backoffTime);
session.ActivateTimer(cc_timer);
break;
} // end if (CC_RATE == ext.GetType())
} // end while (GetNextExtension())
// Disable CC feedback if sender doesn't want it
if (!hasCCRateExtension && cc_enable) cc_enable = false;
break;
}
case NormCmdMsg::FLUSH:
{
// (TBD) should we force synchronize if we're expected
// to positively acknowledge the FLUSH
const NormCmdFlushMsg& flush = (const NormCmdFlushMsg&)cmd;
bool doAck = false;
UINT16 nodeCount = flush.GetAckingNodeCount();
NormNodeId localId = LocalNodeId();
for (UINT16 i = 0; i < nodeCount; i++)
{
// (TBD) also ACK if NORM_NODE_ANY is listed???
if (flush.GetAckingNodeId(i) == localId)
{
doAck = true;
break;
}
}
NormObjectId objectId = flush.GetObjectId();
NormBlockId blockId = 0;
NormSegmentId symbolId = 0;
if (!synchronized)
{
// Cache the remote sender's "fec_id" so we will
// build proper NACKs since we have no prior state
fec_id = flush.GetFecId();
if (2 == fec_id) // see comment in HandleObjectMessage() method on this
fti_data.SetFecFieldSize(16);
else
fti_data.SetFecFieldSize(8);
}
else if (flush.GetFecId() == fec_id)
{
blockId = flush.GetFecBlockId(fti_data.GetFecFieldSize());
symbolId = flush.GetFecSymbolId(fti_data.GetFecFieldSize());
}
else
{
// TBD - should we confirm the sender's FEC config here???
}
if (!synchronized)
{
if ((doAck) || (SYNC_ALL == sync_policy))
{
// Force sync since we're expected to ACK
// and request repair for object indicated
Sync(objectId);
}
else
{
// (TBD) optionally sync on any flush ?
}
}
if (synchronized)
{
if (doAck) // this was a watermark flush
{
if (!PassiveRepairCheck(objectId, blockId, symbolId))
{
watermark_object_id = objectId;
watermark_block_id = blockId;
watermark_segment_id = symbolId;
// Check for application-extended watermark request (see NormSetWatermarkEx())
const char* appAckReq = NULL;
unsigned int appAckReqLen = 0;
NormAppAckExtension ext;
while (flush.GetNextExtension(ext))
{
if (NormHeaderExtension::APP_ACK == ext.GetType())
{
appAckReq = ext.GetContent();
appAckReqLen = ext.GetContentLength();
}
}
if (NULL != appAckReq)
{
// We need to bubble this up to the application before we acknowledge watermark
// so app can set any extended ACK content in response
if (appAckReqLen != ack_ex_length)
{
if (NULL != ack_ex_buffer) delete[] ack_ex_buffer;
if (NULL == (ack_ex_buffer = new char[appAckReqLen]))
{
// TBD - notify app of allocation error
PLOG(PL_ERROR, "NormSenderNode::HandleCommand() new ack_ex_buffer error: %s\n", GetErrorString());
ack_ex_length = 0;
}
else
{
ack_ex_length = appAckReqLen;
}
}
if (NULL != ack_ex_buffer)
{
memcpy(ack_ex_buffer, appAckReq, appAckReqLen);
ack_ex_pending = true;
session.Notify(NormController::RX_ACK_REQUEST, this, NULL);
}
}
else if (!ack_timer.IsActive())
{
double ackBackoff = (session.Address().IsMulticast() && (backoff_factor > 0.0)) ?
UniformRand(grtt_estimate) : 0.0;
ack_timer.SetInterval(ackBackoff);
ack_pending = true;
session.ActivateTimer(ack_timer);
}
break; // no pending repairs, skip regular "RepairCheck"
}
}
UpdateSyncStatus(objectId);
RepairCheck(NormObject::THRU_SEGMENT, objectId, blockId, symbolId);
}
break;
}
case NormCmdMsg::REPAIR_ADV:
{
const NormCmdRepairAdvMsg& repairAdv = (const NormCmdRepairAdvMsg&)cmd;
// Does the CC feedback of this ACK suppress our CC feedback?
if (!is_clr && !is_plr && cc_timer.IsActive() &&
cc_timer.GetRepeatCount())
{
NormCCFeedbackExtension ext;
while (repairAdv.GetNextExtension(ext))
{
if (NormHeaderExtension::CC_FEEDBACK == ext.GetType())
{
HandleCCFeedback(ext.GetCCFlags(), NormUnquantizeRate(ext.GetCCRate()));
break;
}
}
}
if (repair_timer.IsActive() && repair_timer.GetRepeatCount())
{
// (TBD) pay attention to the NORM_REPAIR_ADV_LIMIT flag
HandleRepairContent(repairAdv.GetRepairContent(),
repairAdv.GetRepairContentLength());
}
break;
}
case NormCmdMsg::APPLICATION:
{
PLOG(PL_TRACE, "NormSenderNode::HandleCommand(APPLICATION) node>%lu recvd app-defined cmd...\n",
(unsigned long)LocalNodeId());
const NormCmdAppMsg& appCmd = static_cast<const NormCmdAppMsg&>(cmd);
// 1) Buffer the received command either using a buffer structure
// cmd_buffer pool or allocating a new one as needed.
CmdBuffer* buf = cmd_buffer_pool;
if (NULL != buf)
cmd_buffer_pool = buf->GetNext();
else
buf = new CmdBuffer();
if (NULL == buf)
{
PLOG(PL_ERROR, "NormSenderNode::HandleCommand(APPLICATION) node>%lu NewCmdCBuffer() error: %s\n",
(unsigned long)LocalNodeId(), GetErrorString());
}
else
{
unsigned int cmdLength = appCmd.GetContentLength();
if ((cmdLength <= SegmentSize()) ||
((0 == SegmentSize()) && (cmdLength < 8192)))
{
// 2) Copy the app-defined command content into our buffer
buf->SetContent(appCmd.GetContent(), appCmd.GetContentLength());
// 3) Append the buffer into our cmd_buffer FIFO queue
if (NULL != cmd_buffer_tail)
{
cmd_buffer_tail->Append(buf);
cmd_buffer_tail = buf;
}
else
{
cmd_buffer_head = cmd_buffer_tail = buf;
}
session.Notify(NormController::RX_CMD_NEW, this, NULL);
}
else
{
PLOG(PL_ERROR, "NormSenderNode::HandleCommand(APPLICATION) node>%lu error: "
"cmd content greater than sender's segment_size?!\n",
(unsigned long)LocalNodeId());
buf->Append(cmd_buffer_pool);
cmd_buffer_pool = buf;
}
}
break;
}
default:
PLOG(PL_ERROR, "NormSenderNode::HandleCommand() recv'd unimplemented command!\n");
break;
} // end switch(flavor)
} // end NormSenderNode::HandleCommand()
void NormSenderNode::HandleCCFeedback(UINT8 ccFlags, double ccRate)
{
//ASSERT(cc_timer.IsActive() && cc_timer.GetRepeatCount());
if (0 == (ccFlags & NormCC::CLR))
{
// We're suppressed by non-CLR receivers with no RTT confirmed
// and/or lower rate
double nominalSize = nominal_packet_size ? nominal_packet_size : SegmentSize();
double ccLoss = slow_start ? 0.0 : LossEstimate();
double localRate = (0.0 == ccLoss) ?
(2.0*recv_rate) :
NormSession::CalculateRate(nominalSize,
rtt_estimate,
ccLoss);
// This increases our chance of being suppressed
// (but is it a good idea?)
localRate = MAX(localRate, cc_rate);
bool hasRtt = (0 != (ccFlags & NormCC::RTT));
bool suppressed;
if (rtt_confirmed)
{
// If we have confirmed our own RTT we
// are suppressed by _any_ receivers with
// lower rate than our own
if (localRate > (0.9 * ccRate))
suppressed = true;
else
suppressed = false;
}
else
{
// If we haven't confirmed our own RTT we
// are suppressed by only by other
// non-confirmed receivers
if (hasRtt)
suppressed = false;
else if (localRate > (0.9 * ccRate))
suppressed = true;
else
suppressed = false;
}
if (suppressed)
{
// This sets a holdoff timeout for cc feedback when suppressed
double backoffFactor = backoff_factor;
backoffFactor = MAX(backoffFactor, 4.0); // always use at least 4 for cc purposes
cc_timer.SetInterval(grtt_estimate*backoffFactor);
if (cc_timer.IsActive())
cc_timer.Reschedule();
else
session.ActivateTimer(cc_timer);
cc_timer.DecrementRepeatCount();
}
}
} // end NormSenderNode::HandleCCFeedback()
void NormSenderNode::HandleAckMessage(const NormAckMsg& ack)
{
// Does the CC feedback of this ACK suppress our CC feedback
if (!is_clr && !is_plr && cc_timer.IsActive() && cc_timer.GetRepeatCount())
{
NormCCFeedbackExtension ext;
while (ack.GetNextExtension(ext))
{
if (NormHeaderExtension::CC_FEEDBACK == ext.GetType())
{
HandleCCFeedback(ext.GetCCFlags(), NormUnquantizeRate(ext.GetCCRate()));
break;
}
}
}
} // end NormSenderNode::HandleAckMessage()
void NormSenderNode::HandleNackMessage(const NormNackMsg& nack)
{
// Does the CC feedback of this NACK suppress our CC feedback
if (!is_clr && !is_plr && cc_timer.IsActive() && cc_timer.GetRepeatCount())
{
NormCCFeedbackExtension ext;
while (nack.GetNextExtension(ext))
{
if (NormHeaderExtension::CC_FEEDBACK == ext.GetType())
{
HandleCCFeedback(ext.GetCCFlags(), NormUnquantizeRate(ext.GetCCRate()));
break;
}
}
}
// Receivers also care about recvd NACKS for NACK suppression
if (repair_timer.IsActive() && repair_timer.GetRepeatCount())
HandleRepairContent(nack.GetRepairContent(), nack.GetRepairContentLength());
} // end NormSenderNode::HandleNackMessage()
// Receivers use this method to process NACK content overheard from other
// receivers or via NORM_CMD(REPAIR_ADV) messages received from the sender.
// Such content can "suppress" pending NACKs
// (TBD) add provision to handle case when NORM_REPAIR_ADV_FLAG_LIMIT was set
void NormSenderNode::HandleRepairContent(const UINT32* buffer, UINT16 bufferLen)
{
// Parse NACK and incorporate into repair state masks
NormRepairRequest req;
UINT16 requestLength = 0;
bool freshObject = true;
NormObjectId prevObjectId(0);
NormObject* object = NULL;
bool freshBlock = true;
NormBlockId prevBlockId = 0;
NormBlock* block = NULL;
while (0 != (requestLength = req.Unpack(buffer, bufferLen)))
{
// Point "buffer" to next request and adjust "bufferLen"
buffer += (requestLength/4);
bufferLen -= requestLength;
// Process request
enum NormRequestLevel {SEGMENT, BLOCK, INFO, OBJECT};
NormRepairRequest::Form requestForm = req.GetForm();
NormRequestLevel requestLevel;
if (req.FlagIsSet(NormRepairRequest::SEGMENT))
requestLevel = SEGMENT;
else if (req.FlagIsSet(NormRepairRequest::BLOCK))
requestLevel = BLOCK;
else if (req.FlagIsSet(NormRepairRequest::OBJECT))
requestLevel = OBJECT;
else
{
requestLevel = INFO;
ASSERT(req.FlagIsSet(NormRepairRequest::INFO));
}
bool repairInfo = req.FlagIsSet(NormRepairRequest::INFO);
NormRepairRequest::Iterator iterator(req, fec_id, fti_data.GetFecFieldSize()); // assumes constant "m"
NormObjectId nextObjectId, lastObjectId;
NormBlockId nextBlockId, lastBlockId;
UINT16 nextBlockLen, lastBlockLen;
NormSegmentId nextSegmentId, lastSegmentId;
while (iterator.NextRepairItem(&nextObjectId, &nextBlockId,
&nextBlockLen, &nextSegmentId))
{
if (NormRepairRequest::RANGES == requestForm)
{
if (!iterator.NextRepairItem(&lastObjectId, &lastBlockId,
&lastBlockLen, &lastSegmentId))
{
PLOG(PL_ERROR, "NormSenderNode::HandleRepairContent() node>%lu recvd incomplete RANGE request!\n",
(unsigned long)LocalNodeId());
continue; // (TBD) break/return instead???
}
// (TBD) test for valid range form/level
}
else
{
lastObjectId = nextObjectId;
lastBlockId = nextBlockId;
lastBlockLen = nextBlockLen;
lastSegmentId = nextSegmentId;
}
switch(requestLevel)
{
case INFO:
{
while (nextObjectId <= lastObjectId)
{
NormObject* obj = rx_table.Find(nextObjectId);
if (obj) obj->SetRepairInfo();
nextObjectId++;
}
break;
}
case OBJECT:
{
UINT16 numBits = (UINT16)(lastObjectId - nextObjectId) + 1;
rx_repair_mask.SetBits(nextObjectId, numBits);
break;
}
case BLOCK:
{
if (nextObjectId != prevObjectId) freshObject = true;
if (freshObject)
{
object = rx_table.Find(nextObjectId);
prevObjectId = nextObjectId;
}
if (object)
{
if (repairInfo) object->SetRepairInfo();
object->SetRepairs(nextBlockId, lastBlockId);
}
break;
}
case SEGMENT:
{
if (nextObjectId != prevObjectId) freshObject = true;
if (freshObject)
{
object = rx_table.Find(nextObjectId);
prevObjectId = nextObjectId;
}
if (object)
{
if (repairInfo) object->SetRepairInfo();
if (nextBlockId != prevBlockId) freshBlock = true;
if (freshBlock)
{
block = object->FindBlock(nextBlockId);
prevBlockId = nextBlockId;
}
if (block)
block->SetRepairs(nextSegmentId,lastSegmentId);
}
break;
}
} // end switch(requestLevel)
} // end while (iterator.NextRepairItem())
} // end while (nack.UnpackRepairRequest())
} // end NormSenderNode::HandleRepairContent()
void NormSenderNode::CalculateGrttResponse(const struct timeval& currentTime,
struct timeval& grttResponse) const
{
// The returned "grttResponse" is the remote sender's cached "grtt_send_time" with
// any processing delay (currentTime - grtt_recv_time) removed.
grttResponse.tv_sec = grttResponse.tv_usec = 0;
if (grtt_send_time.tv_sec || grtt_send_time.tv_usec)
{
// 1st - Get current time
grttResponse = currentTime;
// 2nd - Calculate hold_time (current_time - recv_time)
if (grttResponse.tv_usec < grtt_recv_time.tv_usec)
{
grttResponse.tv_sec = grttResponse.tv_sec - grtt_recv_time.tv_sec - 1;
grttResponse.tv_usec = 1000000 - (grtt_recv_time.tv_usec - grttResponse.tv_usec);
}
else
{
grttResponse.tv_sec = grttResponse.tv_sec - grtt_recv_time.tv_sec;
grttResponse.tv_usec = grttResponse.tv_usec - grtt_recv_time.tv_usec;
}
// 3rd - Calculate adjusted grtt_send_time (hold_time + send_time)
grttResponse.tv_sec += grtt_send_time.tv_sec;
grttResponse.tv_usec += grtt_send_time.tv_usec;
if (grttResponse.tv_usec > 1000000)
{
grttResponse.tv_usec -= 1000000;
grttResponse.tv_sec += 1;
}
}
} // end NormSenderNode::CalculateGrttResponse()
void NormSenderNode::DeleteObject(NormObject* obj)
{
if (rx_table.Remove(obj))
{
rx_pending_mask.Unset(obj->GetId());
obj->Close();
obj->Release();
}
} // end NormSenderNode::DeleteObject()
NormBlock* NormSenderNode::GetFreeBlock(NormObjectId objectId, NormBlockId blockId)
{
NormBlock* b = block_pool.Get();
if (NULL == b)
{
if (session.ReceiverIsSilent() || session.RcvrIsRealtime())
{
// forward iteration to find oldest older object with resources
NormObjectTable::Iterator iterator(rx_table);
NormObject* obj;
while ((obj = iterator.GetNextObject()))
{
if (obj->GetId() > objectId)
{
break;
}
else
{
if (obj->GetId() < objectId)
b = obj->StealOldestBlock(false);
else
b = obj->StealOldestBlock(true, blockId);
if (b)
{
b->EmptyToPool(segment_pool);
break;
}
}
}
}
else
{
// reverse iteration to find newest newer object with resources
NormObjectTable::Iterator iterator(rx_table);
NormObject* obj;
while ((obj = iterator.GetPrevObject()))
{
if (obj->GetId() < objectId)
{
break;
}
else
{
if (obj->GetId() > objectId)
b = obj->StealNewestBlock(false);
else
b = obj->StealNewestBlock(true, blockId);
if (b)
{
b->EmptyToPool(segment_pool);
break;
}
}
}
}
}
return b;
} // end NormSenderNode::GetFreeBlock()
char* NormSenderNode::GetFreeSegment(NormObjectId objectId, NormBlockId blockId)
{
if (segment_pool.IsEmpty())
{
// First, try to steal (retrievable) buffered source symbol segments
NormObjectTable::Iterator iterator(rx_table);
NormObject* obj;
while ((obj = iterator.GetNextObject()))
{
// This takes source segments only from the "oldest" obj/blk
// (TBD) Should these be from the "newest" obj/blk instead?
if (obj->ReclaimSourceSegments(segment_pool))
break;
}
// Second, if necessary, steal an ordinally "newer" block
// (TBD) we might try to keep the block state, and only
// steal the segment needed?
while (segment_pool.IsEmpty())
{
NormBlock* b = GetFreeBlock(objectId, blockId);
if (b)
block_pool.Put(b);
else
break;
}
}
char* result = segment_pool.Get();
return result;
} // end NormSenderNode::GetFreeSegment()
bool NormSenderNode::PreallocateRxStream(unsigned int bufferSize,
UINT16 segmentSize,
UINT16 numData,
UINT16 numParity)
{
if (NULL!= preset_stream) delete preset_stream;
if (NULL == (preset_stream = new NormStreamObject(session, this, 0)))
{
PLOG(PL_ERROR, "NormSenderNode::PreallocateRxStream() new NormStreamObject error: %s\n",
GetErrorString());
return false;
}
UINT8 fecId;
UINT8 fecM = 8;
if ((numData + numParity) > 255)
{
fecId = 2;
fecM = 16;
}
else
{
fecId = 5;
}
UINT32 blockSize = segmentSize * numData;
UINT32 numBlocks = bufferSize / blockSize;
// Buffering requires at least 2 blocks
numBlocks = MAX(2, numBlocks);
// Recompute "bufferSize" to match any adjustments
bufferSize = numBlocks * blockSize;
if (!preset_stream->RxOpen(NormObjectSize(bufferSize),
true,
segmentSize,
fecId,
fecM,
numData,
numParity))
{
PLOG(PL_ERROR, "NormSenderNode::PreallocateRxStream() error: RxOpen() failure\n");
delete preset_stream;
preset_stream = NULL;
return false;
}
if (!preset_stream->Accept(bufferSize, true))
{
PLOG(PL_ERROR, "NormSenderNode::PreallocateRxStream() error: Accept() failure\n");
delete preset_stream;
preset_stream = NULL;
return false;
}
return true;
} // end NormSenderNode::PreallocateRxStream()
// TBD - Move this method to NormObjectMsg class
bool NormSenderNode::GetFtiData(const NormObjectMsg& msg, NormFtiData& ftiData)
{
UINT8 fecId = msg.GetFecId();
switch (fecId)
{
case 2:
{
NormFtiExtension2 fti;
while (msg.GetNextExtension(fti))
{
if (NormHeaderExtension::FTI == fti.GetType())
{
ASSERT(1 == fti.GetFecGroupSize()); // TBD - allow for different groupings
ftiData.SetFecInstanceId(0);
ftiData.SetFecFieldSize(fti.GetFecFieldSize());
ftiData.SetSegmentSize(fti.GetSegmentSize());
ftiData.SetFecMaxBlockLen(fti.GetFecMaxBlockLen());
ftiData.SetFecNumParity(fti.GetFecNumParity());
ftiData.SetObjectSize(fti.GetObjectSize());
return true;
}
}
break;
}
case 5:
{
NormFtiExtension5 fti;
while (msg.GetNextExtension(fti))
{
if (NormHeaderExtension::FTI == fti.GetType())
{
ftiData.SetFecInstanceId(0);
ftiData.SetFecFieldSize(8);
ftiData.SetSegmentSize(fti.GetSegmentSize());
ftiData.SetFecMaxBlockLen(fti.GetFecMaxBlockLen());
ftiData.SetFecNumParity(fti.GetFecNumParity());
ftiData.SetObjectSize(fti.GetObjectSize());
return true;
}
}
break;
}
case 129:
{
NormFtiExtension129 fti;
while (msg.GetNextExtension(fti))
{
if (NormHeaderExtension::FTI == fti.GetType())
{
ftiData.SetFecInstanceId(fti.GetFecInstanceId());
ftiData.SetFecFieldSize(8);
ftiData.SetSegmentSize(fti.GetSegmentSize());
ftiData.SetFecMaxBlockLen(fti.GetFecMaxBlockLen());
ftiData.SetFecNumParity(fti.GetFecNumParity());
ftiData.SetObjectSize(fti.GetObjectSize());
return true;
}
}
break;
}
default:
PLOG(PL_ERROR, "NormSenderNode::GetFtiData() node>%lu sender>%lu unknown fec_id type:%d\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId(), (int)fecId);
break;
} // end switch (fecId)
PLOG(PL_ERROR, "NormSenderNode::GetFtiData() node>%lu sender>%lu unknown fec_id type:%d\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId(), (int)fecId);
return false;
} // end NormSenderNode::GetFtiData()
void NormSenderNode::HandleObjectMessage(const NormObjectMsg& msg)
{
UINT8 grttQuantized = msg.GetGrtt();
if (grttQuantized != grtt_quantized) UpdateGrttEstimate(grttQuantized);
UINT8 gsizeQuantized = msg.GetGroupSize();
if (gsizeQuantized != gsize_quantized)
{
gsize_quantized = gsizeQuantized;
gsize_estimate = NormUnquantizeGroupSize(gsizeQuantized);
PLOG(PL_DEBUG, "NormSenderNode::HandleObjectMessage() node>%lu sender>%lu new group size: %lf\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId(), gsize_estimate);
}
backoff_factor = (double)msg.GetBackoffFactor();
NormMsg::Type msgType = msg.GetType();
NormObjectId objectId = msg.GetObjectId();
UINT8 fecId = msg.GetFecId();
// The current NORM implementation assumes senders maintain a fixed, common
// set of FEC coding parameters for its transmissions. The buffers (on a
// "per-remote-sender basis") for receiver FEC processing are allocated here
// when:
// 1) A NORM_DATA message is received and the buffers have not
// been previously allocated, or
// 2) When the FEC parameters have changed (TBD)
//
bool allocateBuffers = true;
bool gotFTI = false;
NormFtiData ftiData;
if (BuffersAllocated())
{
// Validate that allocated buffers match object FEC params
if (fecId == fec_id)
{
if (GetFtiData(msg, ftiData) || session.GetPresetFtiData(ftiData))
{
gotFTI = true;
if ((ftiData.GetSegmentSize() != SegmentSize()) ||
(ftiData.GetFecFieldSize() != fti_data.GetFecFieldSize()) ||
(ftiData.GetFecMaxBlockLen() != fti_data.GetFecMaxBlockLen()) ||
(ftiData.GetFecNumParity() != fti_data.GetFecNumParity()))
{
FreeBuffers(); // force reallocation because fec params changed
fti_data = ftiData;
}
else
{
allocateBuffers = false; // FEC params match
}
}
else if ((NormMsg::INFO != msgType) && msg.FlagIsSet(NormObjectMsg::FLAG_INFO))
{
// This handles case where only NORM_INFO carries FTI Info to reduce overhead
// We have to assume sender FTI hasn't changed ...
allocateBuffers = false;
}
else
{
PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() node>%lu sender>%lu - no FTI provided!\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId());
return; // (TBD) notify app of error ??
}
}
else
{
FreeBuffers(); // force reallocation because fec id changed
}
} // end if (BuffersAllocated())
NormBlockId blockId;
NormSegmentId segmentId;
if (NormMsg::INFO == msgType)
{
if (!BuffersAllocated())
{
fec_id = fecId;
// Go ahead and capture FTI from INFO
if (GetFtiData(msg, ftiData) || session.GetPresetFtiData(ftiData))
{
gotFTI = true;
fti_data = ftiData;
}
else
{
PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() node>%lu sender>%lu - no FTI provided!\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId());
return; // (TBD) notify app of error ??
}
}
else
{
ASSERT(gotFTI);
}
blockId = 0;
segmentId = 0;
}
else // NormMsg::DATA
{
if (allocateBuffers)
{
PLOG(PL_DEBUG, "NormSenderNode::HandleObjectMessage() node>%lu allocating sender>%lu buffers ...\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId());
// Currently,, our implementation requires the FEC Object Transmission Information
// to properly allocate resources for FEC buffering and decoding
// So, get the FEC Transport Information (FTI) from header extension
// TBD - allow for application preset FTI
if (!gotFTI)
{
if (GetFtiData(msg, ftiData))
{
gotFTI = true;
}
else if (fti_data.IsValid())
{
ftiData = fti_data;
gotFTI = true;
}
else if (session.GetPresetFtiData(ftiData))
{
gotFTI = true;
}
else if ((NormMsg::INFO != msgType) && !msg.FlagIsSet(NormObjectMsg::FLAG_INFO))
{
PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() node>%lu sender>%lu - no FTI provided!\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId());
// (TBD) notify app of error ??
return;
}
// else wait for NORM_INFO message with sender FTI
}
if (gotFTI && !AllocateBuffers((unsigned int)session.RemoteSenderBufferSize(),
fecId, ftiData.GetFecInstanceId(),
ftiData.GetFecFieldSize(),
ftiData.GetSegmentSize(),
ftiData.GetFecMaxBlockLen(),
ftiData.GetFecNumParity()))
{
PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() node>%lu sender>%lu buffer allocation error\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId());
// (TBD) notify app of error ??
return;
}
} // end if (allocateBuffers)
if (fti_data.IsValid())
{
ASSERT(0 != fti_data.GetFecFieldSize());
const NormDataMsg& data = static_cast<const NormDataMsg&>(msg);
blockId = data.GetFecBlockId(fti_data.GetFecFieldSize());
segmentId = data.GetFecSymbolId(fti_data.GetFecFieldSize());
}
else
{
// These won't come into play anyway
if (2 == fecId)
fti_data.SetFecFieldSize(16);
else
fti_data.SetFecFieldSize(8);
blockId = 0;
segmentId = 0;
}
} // end if/else (NormMsg::INFO == msgType)
ObjectStatus status;
if (synchronized)
{
status = UpdateSyncStatus(objectId);
}
else
{
// Does this object message meet our sync policy?
if (SyncTest(msg))
{
Sync(objectId);
status = OBJ_NEW;
}
else
{
// The hacky use of "sync_id" here keeps the debug message from
// printing too often while "waiting to sync" ...
if (0 == sync_id)
{
PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() waiting to sync ...\n");
sync_id = 100;
}
else
{
sync_id--;
}
return;
}
}
bool presetStream = false;
NormObject* obj = NULL;
bool doInsert = true;
bool seen = false;
switch (status)
{
case OBJ_PENDING:
{
if (NULL != (obj = rx_table.Find(objectId)))
{
// This checks for an object that's been "seen" but did not
// include Object FTI information previously.
if (0 == obj->GetSize().GetOffset())
{
// It's a seen object for which are awaiting FTI
if (GetFtiData(msg, ftiData))
{
gotFTI = true;
obj->SetNackingMode(default_nacking_mode);
doInsert = false;
seen = true;
// Intentionally pass through to case OBJ_NEW
}
else
{
obj = NULL; // keep waiting for FTI
break;
}
}
else
{
break; // handle as normal pending object
}
}
// else intentionally pass through to case OBJ_NEW
}
case OBJ_NEW:
{
if (msg.FlagIsSet(NormObjectMsg::FLAG_STREAM))
{
if ((NULL != preset_stream) && ((NULL == obj) || (obj == static_cast<NormObject*>(preset_stream))))
{
obj = static_cast<NormObject*>(preset_stream);
// Validate FTI params
if (!gotFTI)
{
// need to get FTI data
if (GetFtiData(msg, ftiData))
{
gotFTI = true;
}
else if (session.GetPresetFtiData(ftiData))
{
gotFTI = true;
}
else if ((NormMsg::INFO != msgType) && !msg.FlagIsSet(NormObjectMsg::FLAG_INFO))
{
PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() node>%lu sender>%lu - no FTI provided!\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId());
// (TBD) notify app of error ??
return;
}
}
if (gotFTI &&
((obj->GetSize() != ftiData.GetObjectSize()) ||
(obj->GetFecId() != fecId) ||
(obj->GetSegmentSize() != ftiData.GetSegmentSize()) ||
(obj->GetFecMaxBlockLen() != ftiData.GetFecMaxBlockLen()) ||
(obj->GetFecNumParity() != ftiData.GetFecNumParity()) ||
(obj->GetFecFieldSize() != ftiData.GetFecFieldSize())))
{
PLOG(PL_WARN, "NormSenderNode::HandleObjectMessage() node>%lu sender>%lu warning: "
"FTI does not match preset_stream!\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId());
obj = NULL;
}
else
{
// Init preset_stream objectId and INFO status
obj->SetId(objectId);
if (!msg.FlagIsSet(NormObjectMsg::FLAG_INFO))
obj->ClearInfo();
presetStream = true;
}
}
if (NULL == obj)
{
if (NULL == (obj = new NormStreamObject(session, this, objectId)))
{
PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() new NORM_OBJECT_STREAM error: %s\n",
GetErrorString());
}
}
}
else if (msg.FlagIsSet(NormObjectMsg::FLAG_FILE) && (NULL == obj))
{
#ifdef SIMULATE
if (!(obj = new NormSimObject(session, this, objectId)))
#else
if (!(obj = new NormFileObject(session, this, objectId)))
#endif
{
PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() new NORM_OBJECT_FILE error: %s\n",
GetErrorString());
}
}
else if (NULL == obj)
{
if (!(obj = new NormDataObject(session, this, objectId, session.GetSessionMgr().GetDataFreeFunction())))
{
PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() new NORM_OBJECT_DATA error: %s\n",
GetErrorString());
}
}
// TBD - if buffers were _just_ allocated above, we could avoid this second
// parsing of FTI header extension by promoting the FEC parameters learned
// above into stack variable that are still accessible here and adding a
// state variable to indicate they are valid
if (NULL != obj)
{
ASSERT(rx_table.CanInsert(objectId));
ASSERT(rx_pending_mask.Test(objectId));
if (doInsert) rx_table.Insert(obj);
// Pull out FTI parameters from header extension if we didn't get it above
if (!gotFTI)
{
if (GetFtiData(msg, ftiData) || session.GetPresetFtiData(ftiData))
gotFTI = true;
}
if (gotFTI || presetStream) // this assumes presetStream matches if !gotFTI (if we get conflicting info, an abort/resync will be forced)
{
if (presetStream ||
obj->RxOpen(ftiData.GetObjectSize(),
msg.FlagIsSet(NormObjectMsg::FLAG_INFO),
ftiData.GetSegmentSize(),
fecId,
ftiData.GetFecFieldSize(),
ftiData.GetFecMaxBlockLen(),
ftiData.GetFecNumParity()))
{
session.Notify(NormController::RX_OBJECT_NEW, this, obj);
if (obj->Accepted())
{
if (obj->IsStream())
{
if (presetStream) preset_stream = NULL; // we're using it up
// This initial "StreamUpdateStatus()" syncs the stream according to our sync policy
NormStreamObject* stream = static_cast<NormStreamObject*>(obj);
if (SYNC_CURRENT == sync_policy)
{
// Just "sync" to first received blockId
stream->StreamUpdateStatus(blockId);
}
else
{
// This forces the sender to do a maximum "rewind"
// If the resultant "syncId" is close to zero, assume
// we are "in-range" of sender initial (block zero) stream start
NormBlockId syncId = blockId;
stream->Decrement(syncId, stream->GetPendingMaskSize() - 1);
if ((stream->Compare(blockId, NormBlockId(0)) >= 0) &&
(stream->Compare(syncId, NormBlockId(0)) <= 0) && !seen)
{
// Assume we are "in-range" of sender initial stream startup
syncId = NormBlockId(0);
}
stream->StreamUpdateStatus(syncId);
}
}
PLOG(PL_DETAIL, "NormSenderNode::HandleObjectMessage() node>%lu sender>%lu new obj>%hu\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId(), (UINT16)objectId);
}
else
{
PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() object not accepted\n");
if (presetStream)
rx_table.Remove(obj);
else
DeleteObject(obj);
obj = NULL;
}
}
else
{
PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() error opening object\n");
DeleteObject(obj);
obj = NULL;
}
}
else if ((NormMsg::INFO != msgType) && msg.FlagIsSet(NormObjectMsg::FLAG_INFO))
{
// Open a zero-sized object in NACK_INFO_ONLY nacking mode until NORM_INFO w/ FTI arrives
obj->SetPendingInfo(true, fecId);
obj->SetNackingMode(NormObject::NACK_INFO_ONLY);
if (presetStream) preset_stream = NULL; // we're using it up
obj = NULL; // can't process NORM_DATA until we have FTI
// TBD - buffer received messages instead of discarding them???
}
else
{
PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() node>%lu sender>%lu "
"new obj>%hu - no FTI provided!\n", (unsigned long)LocalNodeId(),
(unsigned long)GetId(), (UINT16)objectId);
if (!presetStream) DeleteObject(obj);
obj = NULL;
} // end if/else (gotFTI : ((NormMsg::INFO != msgType) && msg.FlagIsSet(NormObjectMsg::FLAG_INFO)))
} // end if (NULL != obj)
break;
}
case OBJ_COMPLETE:
obj = NULL;
break;
default:
ASSERT(0);
break;
} // end switch(status)
if (NULL != obj)
{
obj->HandleObjectMessage(msg, msgType, blockId, segmentId);
bool objIsPending = obj->IsPending();
// Silent receivers may be configured to allow obj completion w/out INFO
if (objIsPending && session.RcvrIgnoreInfo())
objIsPending = obj->PendingMaskIsSet();
if (!objIsPending)
{
// Reliable reception of this object has completed
if (NormObject::FILE == obj->GetType())
#ifdef SIMULATE
static_cast<NormSimObject*>(obj)->Close();
#else
static_cast<NormFileObject*>(obj)->Close();
#endif // !SIMULATE
if (NormObject::STREAM != obj->GetType())
{
// Streams never complete unless they are "closed" by sender
// and this is handled within stream control code in "normObject.cpp"
session.Notify(NormController::RX_OBJECT_COMPLETED, this, obj);
DeleteObject(obj);
obj = NULL;
completion_count++;
}
}
} // end (if (NULL != obj)
switch (repair_boundary)
{
case BLOCK_BOUNDARY:
// Normal FEC "block boundary" repair check
// (checks for repair needs for objects/blocks _prior_ to current objectId::blockId)
RepairCheck(NormObject::TO_BLOCK, objectId, blockId, segmentId);
break;
case OBJECT_BOUNDARY:
// Optional "object boundary repair check (non-streams only!)
// (checks for repair needs for objects _prior_ to current objectId)
// (also requests "info" for current objectId)
if (NULL != obj && obj->IsStream())
RepairCheck(NormObject::TO_BLOCK, objectId, blockId, segmentId);
else
RepairCheck(NormObject::THRU_INFO, objectId, blockId, segmentId);
break;
}
} // end NormSenderNode::HandleObjectMessage()
bool NormSenderNode::SyncTest(const NormObjectMsg& msg) const
{
switch (sync_policy)
{
case SYNC_CURRENT: // default, more conservative "sync policy"
case SYNC_STREAM:
{
// Allow sync on stream at any time
bool result = msg.FlagIsSet(NormObjectMsg::FLAG_STREAM);
// Allow sync on INFO or block zero DATA message
result = result || (NormMsg::INFO == msg.GetType()) ?
true : (NormBlockId(0) == ((const NormDataMsg&)msg).GetFecBlockId(fti_data.GetFecFieldSize()));
// Never sync on repair messages
result = result && !msg.FlagIsSet(NormObjectMsg::FLAG_REPAIR);
return result;
}
case SYNC_ALL: // sync on anything
return true;
default:
ASSERT(0); // should never occur
return false;
}
} // end NormSenderNode::SyncTest()
// a little helper method
void NormSenderNode::AbortObject(NormObject* obj)
{
// it it's a file, close it first, so app can do something
if (NormObject::FILE == obj->GetType())
#ifdef SIMULATE
static_cast<NormSimObject*>(obj)->Close();
#else
static_cast<NormFileObject*>(obj)->Close();
#endif // !SIMULATE
session.Notify(NormController::RX_OBJECT_ABORTED, this, obj);
DeleteObject(obj);
failure_count++;
} // end NormSenderNode::AbortObject()
// This method establishes the sync point "sync_id"
// objectId. The sync point is the first ordinal
// object id for which the receiver is maintaining
// reliability. Objects prior to the "sync point"
// are ignored.
// The related member variables and their purpose:
// "sync_id" - sync point object id, gets rolled upward
// in NormSenderNode::SetPending() to deal with wrap
//
// "next_id" - id of next expected pending object
// (set in NormSenderNode::SetPending())
//
// "max_pending_object" - max object id heard from sender
// (inited in NormSenderNode::Sync() on
// initial sync, update in NormSenderNode::RepairCheck()
//
void NormSenderNode::Sync(NormObjectId objectId)
{
if (synchronized)
{
NormObjectId firstPending;
if (GetFirstPending(firstPending))
{
NormObjectId lastPending;
GetLastPending(lastPending);
if ((objectId > lastPending) || ((next_id - objectId) > max_pending_range))
{
bool incrementResyncCount = objectId <= lastPending; // may just be a squelch trim
NormObject* obj;
while ((obj = rx_table.Find(rx_table.RangeLo())))
{
incrementResyncCount = true;
AbortObject(obj);
}
rx_pending_mask.Clear();
if (incrementResyncCount) IncrementResyncCount();
}
else if (objectId > firstPending)
{
bool incrementResyncCount = false; // may just be a squelch trim
NormObject* obj;
while ((obj = rx_table.Find(rx_table.RangeLo())) &&
(obj->GetId() < objectId))
{
AbortObject(obj);
incrementResyncCount = true; // more than just a trim
}
unsigned long numBits = (UINT16)(objectId - firstPending);
rx_pending_mask.UnsetBits(firstPending, (UINT32)numBits);
if (incrementResyncCount) IncrementResyncCount();
}
}
if ((next_id < objectId) || ((next_id - objectId) > max_pending_range))
{
max_pending_object = next_id = objectId;
}
sync_id = objectId;
ASSERT(OBJ_INVALID != GetObjectStatus(objectId));
if (OBJ_NEW == GetObjectStatus(objectId))
SetPending(objectId);
}
else
{
ASSERT(!rx_pending_mask.IsSet());
synchronized = true;
switch (sync_policy)
{
case SYNC_CURRENT: // this is the usual default
case SYNC_STREAM:
sync_id = next_id = max_pending_object = objectId;
break;
case SYNC_ALL: // gratuitously sync for anything in our "range"
sync_id = next_id = objectId - max_pending_range + 1;
max_pending_object = objectId;
break;
}
SetPending(objectId); // inclusively sets pending mask for next_id..objectId
}
} // end NormSenderNode::Sync()
NormSenderNode::ObjectStatus NormSenderNode::UpdateSyncStatus(const NormObjectId& objectId)
{
ASSERT(synchronized);
ObjectStatus status = GetObjectStatus(objectId);
switch (status)
{
case OBJ_INVALID:
{
// (TBD) We may want to control resync policy options
// or revert to fresh sync if sync is totally lost,
// otherwise SQUELCH process will get things in order
PLOG(PL_DEBUG, "NormSenderNode::UpdateSyncStatus() node>%lu resync to sender>%lu obj>%hu...\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId(), (UINT16)objectId);
NormObjectId syncId = objectId;
// This code avoids grosser resyncs (if uncommented) ...
// However, attempts at finer-grained resync
// (i.e. preserving some partially-received objects)
// has tended to exhibit an inability to ever "catch up"
// But, note newer flow control feature might help here
// so this might be worthwhile to some day uncomment
// and experiment!
if (rx_pending_mask.IsSet())
{
NormObjectId lastPending;//(65535);
GetLastPending(lastPending);
if (syncId > lastPending)
{
UINT16 delta = syncId - lastPending;
if (delta < max_pending_range)
{
syncId -= (max_pending_range - 1);
}
}
}
Sync(syncId);
return UpdateSyncStatus(objectId);
}
case OBJ_NEW:
SetPending(objectId);
break;
default:
break;
}
return status;
} // end NormSenderNode::UpdateSyncStatus()
void NormSenderNode::SetPending(NormObjectId objectId)
{
ASSERT(synchronized);
ASSERT(OBJ_NEW == GetObjectStatus(objectId));
if (objectId < next_id)
{
rx_pending_mask.Set(objectId);
}
else
{
UINT16 numBits = (UINT16)(objectId - next_id) + 1;
rx_pending_mask.SetBits(next_id, numBits);
next_id = objectId + 1;
// This prevents the "sync_id" from getting stale
GetFirstPending(sync_id);
}
} // end NormSenderNode::SetPending()
NormSenderNode::ObjectStatus NormSenderNode::GetObjectStatus(const NormObjectId& objectId) const
{
if (synchronized)
{
if (objectId < sync_id)
{
// TBD - is there a better way this should be done?
// If the object is a "little bit" old, it is probably an
// object we recently completed. If it is _very_ old,
// we are probably "out of sync" with the sender? Perhaps
// this is too aggressive a resync rule?
if ((sync_id - objectId) > 2*max_pending_range)
{
// This can happen with NORM_SYNC_ALL sync policy
PLOG(PL_DEBUG, "NormSenderNode::GetObjectStatus() INVALID object>%hu sync_id>%hu\n",
(UINT16)objectId, (UINT16)sync_id);
return OBJ_INVALID;
}
else
{
return OBJ_COMPLETE;
}
}
else
{
if (objectId < next_id)
{
if (rx_pending_mask.Test(objectId))
{
return OBJ_PENDING;
}
else
{
return OBJ_COMPLETE;
}
}
else
{
if (rx_pending_mask.IsSet())
{
if (rx_pending_mask.CanSet(objectId))
{
ASSERT(rx_table.CanInsert(objectId));
return OBJ_NEW;
}
else
{
NormObjectId fp;
GetFirstPending(fp);
PLOG(PL_DEBUG, "NormSenderNode::GetObjectStatus() INVALID object>%hu firstPending>%hu\n",
(UINT16)objectId, (UINT16)fp);
return OBJ_INVALID;
}
}
else
{
NormObjectId delta = objectId - next_id + 1;
if (delta > NormObjectId((UINT16)rx_pending_mask.GetSize()))
{
PLOG(PL_DEBUG, "NormSenderNode::GetObjectStatus() INVALID object>%hu next_id>%hu\n",
(UINT16)objectId, (UINT16)next_id);
return OBJ_INVALID;
}
else
{
ASSERT(rx_table.CanInsert(objectId));
return OBJ_NEW;
}
}
}
}
}
else
{
return OBJ_NEW;
}
} // end NormSenderNode::GetObjectStatus()
// This is a "passive" THRU_SEGMENT repair check
// (used to for watermark ack check)
// Returns true if repairs are pending before and thru the given object:block:segment
bool NormSenderNode::PassiveRepairCheck(NormObjectId objectId,
NormBlockId blockId,
NormSegmentId segmentId)
{
if (!synchronized) return true;
NormObjectId nextId;
if (GetFirstPending(nextId))
{
if (nextId < objectId)
{
return true;
}
else if (nextId == objectId)
{
NormObject* obj = rx_table.Find(nextId);
if (NULL != obj)
return obj->PassiveRepairCheck(blockId, segmentId);
else
return true; // entire object pending
}
else
{
return false; // it's an object already received (watermark past)
}
}
else
{
return (OBJ_NEW == GetObjectStatus(objectId));
}
} // end NormSenderNode::PassiveRepairCheck()
// This is the "active" repair check, which may activate NACKing
void NormSenderNode::RepairCheck(NormObject::CheckLevel checkLevel,
NormObjectId objectId,
NormBlockId blockId,
NormSegmentId segmentId)
{
ASSERT(synchronized);
if (NormObject::BLIND_CHECK == checkLevel)
{
// A "blind" check is used upon sender activity timeout or reactivation
// to NACK for repairs based on prior state.
ASSERT(objectId == max_pending_object);
NormObject* objMax = rx_table.Find(objectId);
if (NULL != objMax)
{
NormSegmentId segMax = objMax->GetMaxPendingSegmentId();
if (0 != segMax)
return RepairCheck(NormObject::THRU_SEGMENT,
objectId,
objMax->GetMaxPendingBlockId(),
objMax->GetMaxPendingSegmentId() - 1);
else
return RepairCheck(NormObject::TO_BLOCK,
objectId,
objMax->GetMaxPendingBlockId(),
0);
// The above has been reinstated because the alternative "THRU_OBJECT" here
// causes gratuitous NACKing when the sender goes IDLE ..
// Or we could do this instead (possibly some unnecessary NACKing for NORM_OBJECT_STREAM will occur here)
//RepairCheck(NormObject::THRU_OBJECT, // (TBD) thru object???
// max_pending_object, 0, 0);
// (TBD) What should we really do here? Our current NormNode::RepairCheck() and
// NormObject::ReceiverRepairCheck() methods update the "max_pending" indices
// so we _could_ make ourselves NACK for more repair than we should
// when the inactivity timeout kicks in ??? But if we don't NACK "thru object"
// we may miss something at end-of-transmission by not not NACKing? I guess
// the reliability really is in the flush process and our activity timeout NACK
// is "iffy, at best" ... Perhaps we need to have some sort of "wildcard" NACK,
// but then _everyone_ would NACK at EOT all the time, often for nothing ... so
// I guess the activity timeout NACK isn't perfect ... but could help some
// so we leave it as it is for the moment ("THRU_OBJECT") ... perhaps we could
// add a parameter so NormObject::ReceiverRepairCheck() doesn't update its
// "max_pending" indices - or would this break NACK building?
// Maybe we should do THRU_OBJECT when the remote sender is fully inactive as
// opposed to this inactivity timeout that only pays attention to NORM_DATA. I.e.,
// do the above refined RepairCheck() when we still have NORM_CMD activity but
// no NORM_DATA activity??? We'd still have potentially a lot of EOT NACKing
}
else
{
return RepairCheck(NormObject::THRU_OBJECT, objectId, 0, 0);
//RepairCheck(NormObject::TO_BLOCK, // (TBD) thru object???
// max_pending_object, 0, 0);
}
} // end if (NormObject::BLIND_CHECK == checkLevel)
if (objectId > max_pending_object)
max_pending_object = objectId;
if (!repair_timer.IsActive())
{
// repair timer inactive
bool startTimer = false;
NormObjectId firstId;
if (GetFirstPending(firstId))
{
NormObjectId nextId = firstId;
do
{
if (nextId > objectId) break;
NormObject* obj = rx_table.Find(nextId);
if (NULL != obj)
{
NormObject::CheckLevel level;
if (nextId < objectId)
level = NormObject::THRU_OBJECT;
else
level = checkLevel;
if (obj->ReceiverRepairCheck(level, blockId, segmentId, false))
startTimer = true;
}
else
{
startTimer = true;
}
nextId++;
} while (GetNextPending(nextId));
current_object_id = objectId;
if (startTimer)
{
// BACKOFF related code
double backoffInterval =
(session.Address().IsMulticast() && (backoff_factor > 0.0)) ?
ExponentialRand(grtt_estimate*backoff_factor, gsize_estimate) :
0.0;
PLOG(PL_DEBUG, "NormSenderNode::RepairCheck() node>%lu begin NACK backoff: %lf sec)...\n",
(unsigned long)LocalNodeId(), backoffInterval);
// Here, we clear NormSenderNode repair_mask
// that is used for NACK suppression.
// (object/block repair_masks are cleared as needed in NormObject::ReceiverRepairCheck
if (rx_repair_mask.IsSet()) rx_repair_mask.Clear();
repair_timer.SetInterval(backoffInterval);
session.ActivateTimer(repair_timer);
}
}
}
else if (repair_timer.GetRepeatCount())
{
// Repair timer in backoff phase
// Trim sender current transmit position reference
NormObject* obj = rx_table.Find(objectId);
if (obj) obj->ReceiverRepairCheck(checkLevel, blockId, segmentId, true);
if (objectId < current_object_id) current_object_id = objectId;
}
else
{
// Repair timer in holdoff phase
bool rewindDetected = false;
if (objectId < current_object_id)
{
rewindDetected = true;
}
else if (objectId == current_object_id)
{
NormObject* obj = rx_table.Find(objectId);
if (obj)
rewindDetected = obj->ReceiverRewindCheck(blockId, segmentId);
}
if (rewindDetected)
{
repair_timer.Deactivate();
PLOG(PL_DEBUG, "NormSenderNode::RepairCheck() node>%lu sender rewind detected, ending NACK holdoff ...\n",
(unsigned long)LocalNodeId());
// Immediately do a repair check to see if rewind was sufficient
// TBD - will we get too much unnecessary NACKing with out-of-order packet delivery ???
RepairCheck(checkLevel, objectId, blockId, segmentId);
}
}
} // end NormSenderNode::RepairCheck()
// When repair timer fires, possibly build a NACK
// and queue for transmission to this sender node
bool NormSenderNode::OnRepairTimeout(ProtoTimer& /*theTimer*/)
{
switch(repair_timer.GetRepeatCount())
{
case 0: // hold-off time complete
PLOG(PL_DEBUG, "NormSenderNode::OnRepairTimeout() node>%lu sender>%lu end NACK hold-off ...\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId());
break;
case 1: // back-off timeout complete
{
PLOG(PL_DEBUG, "NormSenderNode::OnRepairTimeout() node>%lu sender>%lu end NACK back-off ...\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId());
// 1) Were we suppressed?
NormObjectId nextId;
if (GetFirstPending(nextId))
{
// This loop checks to see if we have any repair pending objects
// (If we don't have any, that means we were suppressed)
bool repairPending = false;
do
{
if (nextId > current_object_id) break;
if (!rx_repair_mask.Test(nextId))
{
NormObject* obj = rx_table.Find(nextId);
if (!obj || obj->IsRepairPending(nextId != current_object_id))
{
repairPending = true;
break;
}
}
nextId++;
} while (GetNextPending(nextId));
if (repairPending)
{
// We weren't completely suppressed, so build NACK
NormNackMsg* nack = static_cast<NormNackMsg*>(session.GetMessageFromPool());
if (NULL == nack)
{
PLOG(PL_WARN, "NormSenderNode::OnRepairTimeout() node>%lu Warning! "
"message pool empty ...\n", (unsigned long)LocalNodeId());
repair_timer.Deactivate();
return false;
}
nack->Init();
UINT16 payloadMax = 4*SegmentSize();
// If we sync'd to non-DATA, we don't yet know the sender segment_size
if (0 == payloadMax)
payloadMax = 4*NormNackMsg::DEFAULT_LENGTH_MAX;
bool nackAppended = false;
if (cc_enable)
{
NormCCFeedbackExtension ext;
nack->AttachExtension(ext);
if (is_clr)
ext.SetCCFlag(NormCC::CLR);
else if (is_plr)
ext.SetCCFlag(NormCC::PLR);
if (rtt_confirmed)
ext.SetCCFlag(NormCC::RTT);
ext.SetCCRtt(rtt_quantized);
double ccLoss = slow_start ? 0.0 : LossEstimate();
//UINT16 lossQuantized = NormQuantizeLoss(ccLoss);
//ext.SetCCLoss(lossQuantized);
UINT16 lossQuantized = NormQuantizeLoss32(ccLoss);
ext.SetCCLoss32(lossQuantized);
//if (0.0 == ccLoss)
if (0 == lossQuantized)
{
//if (slow_start) // (TBD) should we only set flag on actual slow_start?
ext.SetCCFlag(NormCC::START);
if (recv_rate > 0.0)
ext.SetCCRate(NormQuantizeRate(2.0 * recv_rate));
else
ext.SetCCRate(NormQuantizeRate(2.0 * nominal_packet_size)); // (TBD revisit this)
}
else
{
double nominalSize = (nominal_packet_size > SegmentSize()) ? nominal_packet_size : SegmentSize();
if (0 == nominalSize) nominalSize = 512; // TBD - what should this really be
double ccRate = NormSession::CalculateRate(nominalSize,
rtt_estimate,
ccLoss);
#ifdef LIMIT_CC_RATE
// Experimental modification to NORM-CC where congestion control rate is limited
// to MIN(2.0*measured recv rate, calculated rate). This might prevent large rate
// overshoot in conditions where the loss measurement (perhaps initial loss) is
// very low due to big network packet buffers, etc
double rxRate = 2.0*recv_rate;
if (rxRate < ccRate)
{
ext.SetCCFlag(NormCC::LIMIT);
ccRate = rxRate;
}
#endif // LIMIT_CC_RATE
ext.SetCCRate(NormQuantizeRate(ccRate));
}
PLOG(PL_DEBUG, "NormSenderNode::OnRepairTimeout() node>%lu sending NACK rate:%lf kbps (rtt:%lf loss:%lf s:%hu) slow_start:%d\n",
(unsigned long)LocalNodeId(), 8.0e-03*NormUnquantizeRate(ext.GetCCRate()),
rtt_estimate, ccLoss, (UINT16)nominal_packet_size, slow_start);
ext.SetCCSequence(cc_sequence);
if (0 == session.GetProbeTOS()) // always send NormAck(CC) for special TOS case
{
// Cancel potential pending NORM_ACK(CC) since we are NACKing
if (cc_timer.IsActive())
{
// Set holdoff timeout to refrain from sending too much cc feedback
cc_timer.SetInterval(grtt_estimate*backoff_factor);
cc_timer.Reschedule();
cc_timer.DecrementRepeatCount();
}
}
} // end if (cc_enable)
// Iterate through rx pending object list,
// appending repair requests as needed
NormRepairRequest req;
NormRepairRequest::Form prevForm = NormRepairRequest::INVALID;
bool iterating = GetFirstPending(nextId);
iterating = iterating && (nextId <= max_pending_object);
NormObjectId prevId = nextId;
UINT16 consecutiveCount = 0;
while (iterating || (0 != consecutiveCount))
{
bool appendRequest = false;
NormObject* obj = iterating ? rx_table.Find(nextId) : NULL;
UINT16 diff = nextId - prevId;
if (obj)
appendRequest = true;
else if (iterating && (diff == consecutiveCount))
consecutiveCount++; // consecutive series of missing objs starts/continues
else
appendRequest = true; // consecutive series broken or finished
if (appendRequest)
{
NormRepairRequest::Form nextForm;
switch (consecutiveCount)
{
case 0:
nextForm = NormRepairRequest::INVALID;
break;
case 1:
case 2:
nextForm = NormRepairRequest::ITEMS;
break;
default:
nextForm = NormRepairRequest::RANGES;
break;
}
if (prevForm != nextForm)
{
if ((NormRepairRequest::INVALID != prevForm) &&
(NormObject::NACK_NONE != default_nacking_mode))
{
if (0 == nack->PackRepairRequest(req))
{
PLOG(PL_WARN, "NormSenderNode::OnRepairTimeout() warning: full NACK msg\n");
break;
}
nackAppended = true;
}
if (NormRepairRequest::INVALID != nextForm)
{
nack->AttachRepairRequest(req, payloadMax); // (TBD) error check
req.SetForm(nextForm);
req.ResetFlags();
// Set flags for missing objects according to
// default "nacking mode"
if (NormObject::NACK_INFO_ONLY == default_nacking_mode)
req.SetFlag(NormRepairRequest::INFO);
else
req.SetFlag(NormRepairRequest::OBJECT);
}
prevForm = nextForm;
}
switch (nextForm)
{
case NormRepairRequest::ITEMS:
req.AppendRepairItem(fec_id, fti_data.GetFecFieldSize(), prevId, 0, BlockSize(), 0);
if (2 == consecutiveCount)
req.AppendRepairItem(fec_id, fti_data.GetFecFieldSize(), prevId+1, 0, BlockSize(), 0);
break;
case NormRepairRequest::RANGES:
req.AppendRepairRange(fec_id, fti_data.GetFecFieldSize(), prevId, 0, BlockSize(), 0,
prevId+consecutiveCount-1, 0, BlockSize(), 0);
break;
default:
break;
}
if (NULL != obj)
{
if (obj->IsPending(nextId != max_pending_object))
{
if ((NormRepairRequest::INVALID != prevForm) &&
(NormObject::NACK_NONE != default_nacking_mode))
{
if (0 == nack->PackRepairRequest(req))
{
PLOG(PL_WARN, "NormSenderNode::OnRepairTimeout() warning: full NACK msg\n");
break;
}
nackAppended = true;
}
prevForm = NormRepairRequest::INVALID;
bool flush = (nextId != max_pending_object);
nackAppended |= obj->AppendRepairRequest(*nack, flush, payloadMax);
}
consecutiveCount = 0;
}
else if (iterating)
{
consecutiveCount = 1;
}
else
{
consecutiveCount = 0; // we're all done
}
prevId = nextId;
} // end if (appendRequest)
nextId++;
iterating = GetNextPending(nextId);
} // end while (iterating || (0 != consecutiveCount))
// Pack in repair request "req" if it's outstanding
if ((NormRepairRequest::INVALID != prevForm) &&
(NormObject::NACK_NONE != default_nacking_mode))
{
if (0 != nack->PackRepairRequest(req))
nackAppended = true;
else
PLOG(PL_WARN, "NormSenderNode::OnRepairTimeout() warning: full NACK msg\n");
}
// Queue NACK for transmission
nack->SetSenderId(GetId());
nack->SetInstanceId(instance_id);
// GRTT response is deferred until transmit time
if (unicast_nacks)
nack->SetDestination(GetAddress());
else
nack->SetDestination(session.Address());
if (nackAppended)
{
// Debug check to make sure NACK has content
ASSERT(nack->GetRepairContentLength() > 0);
if (!session.ReceiverIsSilent())
{
UINT16 singleNackSize = SegmentSize() ? SegmentSize() : NormNackMsg::DEFAULT_LENGTH_MAX;
if (nack->GetRepairContentLength() <= singleNackSize)
{
session.SendMessage(*nack);
nack_count++;
}
else
{
FragmentNack(*nack);
}
}
session.ReturnMessageToPool(nack);
}
else
{
// The nack had no repair request content,
// perhaps because of our "nacking mode"
// even though there were pending objects
// TBD - should we avoid NACK hold-off when this happens?
PLOG(PL_DEBUG, "NormSenderNode::OnRepairTimeout() node>%lu sender>%lu zero content nack ...\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId());
session.ReturnMessageToPool(nack);
}
}
else
{
if (!session.ReceiverIsSilent())
{
suppress_count++;
PLOG(PL_DEBUG, "NormSenderNode::OnRepairTimeout() node>%lu sender>%lu NACK SUPPRESSED ...\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId());
}
} // end if/else(repairPending)
// BACKOFF related code
double holdoffInterval = grtt_estimate;
if (session.Address().IsMulticast())
{
holdoffInterval *= (backoff_factor + 2.0);
}
else
{
// Allow at least a packet interval of "slop" time for holdoff
if (0.0 != recv_rate)
{
double nominalPktInterval = nominal_packet_size / recv_rate;
holdoffInterval += MIN(nominalPktInterval, grtt_estimate);
}
else
{
holdoffInterval += grtt_estimate;
}
}
// Uncommenting the line below treats ((0 == nparity) && 0.0 == backoff_factor)
// as a special case (assumes zero sender aggregateInterval)
holdoffInterval = ((0 != NumParity()) || (backoff_factor > 0.0)) ? holdoffInterval : grtt_estimate;
repair_timer.SetInterval(holdoffInterval);
PLOG(PL_DEBUG, "NormSenderNode::OnRepairTimeout() node>%lu sender>%lu begin NACK hold-off: %lf sec ...\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId(), holdoffInterval);
}
else
{
PLOG(PL_DEBUG, "NormSenderNode::OnRepairTimeout() node>%lu sender>%lu nothing pending ...\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId());
// (TBD) cancel hold-off timeout ???
} // end if/else (repair_mask.IsSet())
}
break;
default: // should never occur
ASSERT(0);
break;
}
return true;
} // end NormSenderNode::OnRepairTimeout()
void NormSenderNode::FragmentNack(NormNackMsg& superNack)
{
// Parse a "super" NACK and refactor it into a series of smaller
// NACK messages as needed (per "segment_size" constraint)
// and send them.
NormNackMsg* nack = (NormNackMsg*)session.GetMessageFromPool();
if (!nack)
{
PLOG(PL_WARN, "NormSenderNode::FragmentNack() node>%lu Warning! "
"message pool empty ...\n", (unsigned long)LocalNodeId());
return;
}
nack->InitFrom(superNack);
// GRTT response is deferred until transmit time
if (unicast_nacks)
nack->SetDestination(GetAddress());
else
nack->SetDestination(session.Address());
UINT16 payloadLength = 0;
NormRepairRequest superReq;
UINT16 requestOffset = 0;
UINT16 requestLength = 0;
while (0 != (requestLength = superNack.UnpackRepairRequest(superReq, requestOffset)))
{
const UINT16 REQ_HDR_LEN = 4; // TBD - get from normMessage.h instead
requestOffset += requestLength;
if ((payloadLength + requestLength) <= SegmentSize())
{
// Copy whole request over
nack->AppendRepairRequest(superReq);
payloadLength += requestLength;
}
else if ((payloadLength + REQ_HDR_LEN) < SegmentSize())
{
// Duplicate request and add individual repair items
NormRepairRequest::Form requestForm = superReq.GetForm();
NormRepairRequest req;
nack->AttachRepairRequest(req, SegmentSize());
req.SetForm(requestForm);
req.SetFlags(superReq.GetFlags());
payloadLength += REQ_HDR_LEN;
NormRepairRequest::Iterator iterator(superReq, fec_id, fti_data.GetFecFieldSize());
NormObjectId objectId, lastObjectId;
NormBlockId blockId, lastBlockId;
UINT16 blockLen, lastBlockLen;
NormSegmentId segmentId, lastSegmentId;
UINT16 itemLength;
while (0 != (itemLength = iterator.NextRepairItem(&objectId, &blockId, &blockLen, &segmentId)))
{
if (NormRepairRequest::RANGES == requestForm)
{
itemLength += iterator.NextRepairItem(&lastObjectId, &lastBlockId,
&lastBlockLen, &lastSegmentId);
}
if ((payloadLength + itemLength) > SegmentSize())
{
// We have filled the NACK, so pack, send, and reset request
nack->PackRepairRequest(req);
session.SendMessage(*nack);
nack_count++;
nack->ResetPayload();
nack->AttachRepairRequest(req, SegmentSize());
payloadLength = REQ_HDR_LEN;
}
if (NormRepairRequest::RANGES == requestForm)
{
req.AppendRepairRange(fec_id, fti_data.GetFecFieldSize(), objectId, blockId, blockLen, segmentId,
lastObjectId, lastBlockId, lastBlockLen, lastSegmentId);
}
else
{
req.AppendRepairItem(fec_id, fti_data.GetFecFieldSize(), objectId, blockId, blockLen, segmentId);
}
payloadLength += itemLength;
}
nack->PackRepairRequest(req);
ASSERT(nack->GetRepairContentLength() == payloadLength);
}
else
{
session.SendMessage(*nack);
nack_count++;
nack->ResetPayload();
payloadLength = 0;
}
}
if (0 != payloadLength)
{
ASSERT(nack->GetRepairContentLength() == payloadLength);
session.SendMessage(*nack);
nack_count++;
}
session.ReturnMessageToPool(nack);
} // end NormSenderNode::FragmentNack()
void NormSenderNode::UpdateRecvRate(const struct timeval& currentTime, unsigned short msgSize)
{
if (prev_update_time.tv_sec || prev_update_time.tv_usec)
{
double interval = (double)(currentTime.tv_sec - prev_update_time.tv_sec);
if (currentTime.tv_sec > prev_update_time.tv_sec)
interval += 1.0e-06*(double)(currentTime.tv_usec - prev_update_time.tv_usec);
else
interval -= 1.0e-06*(double)(prev_update_time.tv_usec - currentTime.tv_usec);
double measurementInterval = rtt_confirmed ? rtt_estimate : grtt_estimate;
// Here, we put a NORM_TICK_MIN sec lower bound on our measurementInterval for the
// recv_rate because of the typical limited granularity of our system clock
// (Note this can limit our ramp up of data rate during slow start)
if (measurementInterval < NORM_TICK_MIN) measurementInterval = NORM_TICK_MIN;
recv_accumulator.Increment(msgSize);
if (interval > 0.0)
{
double currentRecvRate = recv_accumulator.GetScaledValue(1.0 / interval);
if ((interval >= measurementInterval) && (currentRecvRate < recv_rate))
{
// Make sure we've allowed sufficient time for a measurement at low rates
double nominalSize = (nominal_packet_size > SegmentSize()) ? nominal_packet_size : SegmentSize();
double minInterval = 4.0 * nominalSize / recv_rate;
if (measurementInterval < minInterval) measurementInterval = minInterval;
}
if (interval >= measurementInterval)
{
recv_rate = recv_rate_prev = currentRecvRate;
prev_update_time = currentTime;
recv_accumulator.Reset();
}
else if (0.0 == recv_rate)
{
recv_rate = currentRecvRate;
recv_rate_prev = 0.0;
}
else if (slow_start)
{
// Go ahead and allow estimate to slew upwards on new packet arrivals
// (helps "slow start" ramp up a little more cleanly)
double rateDelta = currentRecvRate - recv_rate_prev;
if (rateDelta > 0.0)
{
double scale = interval / measurementInterval;
double partialRate = recv_rate_prev + scale*rateDelta;
if (partialRate > recv_rate) recv_rate = partialRate;
}
}
}
else if (0.0 == recv_rate)
{
// Approximate initial recv_rate when initial packets arrive in a burst
recv_rate = recv_accumulator.GetValue() / NORM_TICK_MIN;
recv_rate_prev = 0.0;
}
nominal_packet_size += 0.05 * (((double)msgSize) - nominal_packet_size);
}
else
{
recv_rate = recv_rate_prev = 0.0;
prev_update_time = currentTime;
recv_accumulator.Reset();
nominal_packet_size = msgSize;
}
} // end NormSenderNode::UpdateRecvRate()
void NormSenderNode::Activate(bool isObjectMsg)
{
if (!activity_timer.IsActive())
{
double activityInterval = 2*session.GetTxRobustFactor()*grtt_estimate;
if (activityInterval < ACTIVITY_INTERVAL_MIN) activityInterval = ACTIVITY_INTERVAL_MIN;
activity_timer.SetInterval(activityInterval);
activity_timer.SetRepeat(robust_factor);
session.ActivateTimer(activity_timer);
sender_active = false;
// If it is _not_ an object msg, do a comprehensive repair check
// to re-initiate NACKing for any missing data from prior sender
// activity (iff rx_pending_mask.IsSet())
// (If it is an object message, RepairCheck() will be called accordingly
if (!isObjectMsg && rx_pending_mask.IsSet())
RepairCheck(NormObject::BLIND_CHECK, max_pending_object, 0, 0);
session.Notify(NormController::REMOTE_SENDER_ACTIVE, this, NULL);
}
else if (isObjectMsg)
{
sender_active = true;
}
} // end NormSenderNode::Activate()
bool NormSenderNode::OnActivityTimeout(ProtoTimer& /*theTimer*/)
{
if (sender_active)
{
activity_timer.ResetRepeat();
}
else if (0 == activity_timer.GetRepeatCount())
{
// Remote sender completely inactive?
PLOG(PL_INFO, "NormSenderNode::OnActivityTimeout() node>%lu sender>%lu gone inactive?\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId());
//FreeBuffers(); This now needs to be done by the app as of norm version 1.4b3
session.Notify(NormController::REMOTE_SENDER_INACTIVE, this, NULL);
}
else
{
PLOG(PL_INFO, "NormSenderNode::OnActivityTimeout() node>%lu for sender>%lu\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId());
struct timeval currentTime;
::ProtoSystemTime(currentTime);
UpdateRecvRate(currentTime, 0);
if (synchronized)
{
// A "blind check" is used to request repair for any known missing data while
// for which we have state, or alternatively request repair through the whole of
// the "max_pending_object". The sender will either provide the requested repair
// information or respond with a NORM_CMD(SQUELCH) to bring the receiver back into
// sync if is out of sync (i.e., an outage occurred). THis blind check is done upon
// activity timeout (here) or upon reactivation of a sender when seeing a NORM_CMD message
// instead of a NORM_DATA message. Since NORM_CMD doesn't provide objectId, etc information,
// thus a "blind" check is needed.
if (rx_pending_mask.IsSet())
RepairCheck(NormObject::BLIND_CHECK, max_pending_object, 0, 0);
}
// We manually manage the "repeat_count" here to avoid the
// case where "bursty" receiver scheduling may lead to false
// inactivity indication
int repeatCount = activity_timer.GetRepeatCount();
if (repeatCount > 0) repeatCount--;
activity_timer.Deactivate();
session.ActivateTimer(activity_timer);
activity_timer.SetRepeatCount(repeatCount);
sender_active = false;
return false; // since we manually deactivated/reactivated the timer
}
sender_active = false;
return true;
} // end NormSenderNode::OnActivityTimeout()
bool NormSenderNode::UpdateLossEstimate(const struct timeval& currentTime,
unsigned short seq,
bool ecnStatus)
{
if (loss_estimator.Update(currentTime, seq, ecnStatus))
{
if (slow_start)
{
// Calculate loss initialization based on current receive rate
// and rtt estimation
double nominalSize = (nominal_packet_size > SegmentSize()) ? nominal_packet_size : SegmentSize();
double lossInit = nominalSize / (recv_rate * rtt_estimate);
lossInit *= lossInit;
lossInit *= 3.0/2.0;
double altLoss = (double)loss_estimator.LastLossInterval();
if (altLoss < 2.0) altLoss = 2.0; // makes sure it's no worse than 50% pkt loss
double altInit = 1.0 / altLoss;
if (altInit < lossInit) lossInit = altInit;
loss_estimator.SetInitialLoss(lossInit);
slow_start = false;
}
// TBD - schedule immediate CC feedback if CLR? - note duplicate feedback issue so
// need to do this with preemptively incremented cc_sequence value ...
// TBD - This can cause extra ACK for non-cc unicast sessions.
// We could reset "cc_feedback_needed" to false if NACK feedback is scheduled
// for non-clr unicast case instead???
if (cc_enable && (is_clr || is_plr))// || !session.Address().IsMulticast()))
cc_feedback_needed = true;
return true;
}
else
{
return false;
}
} // end NormSenderNode::UpdateLossEstimate()
void NormSenderNode::CheckCCFeedback()
{
// "cc_feedback_needed" is set to "true" if a loss event occurs
// and remains "true" no cc feedback was sent otherwise
// (gets reset to "false" when OnCCTimeout() is called here)
if (cc_feedback_needed)
{
cc_sequence++; // so sender won't ignore as duplicate feedback
if (cc_timer.IsActive()) cc_timer.Deactivate();
cc_timer.ResetRepeat(); // makes sure timer phase is correct
OnCCTimeout(cc_timer);
}
} // end NormSenderNode::CheckCCFeedback()
void NormSenderNode::AttachCCFeedback(NormAckMsg& ack)
{
// GRTT response is deferred until transmit time
NormCCFeedbackExtension ext;
ack.AttachExtension(ext);
if (is_clr)
ext.SetCCFlag(NormCC::CLR);
else if (is_plr)
ext.SetCCFlag(NormCC::PLR);
if (rtt_confirmed)
ext.SetCCFlag(NormCC::RTT);
ext.SetCCRtt(rtt_quantized);
double ccLoss = slow_start ? 0.0 : LossEstimate();
//UINT16 lossQuantized = NormQuantizeLoss(ccLoss);
//ext.SetCCLoss(lossQuantized);
UINT32 lossQuantized = NormQuantizeLoss32(ccLoss);
ext.SetCCLoss32(lossQuantized);
//if (0.0 == ccLoss)
if (0 == lossQuantized)
{
ext.SetCCFlag(NormCC::START);
ext.SetCCRate(NormQuantizeRate(2.0 * recv_rate));
}
else
{
//double nominalSize = (nominal_packet_size > segment_size) ? nominal_packet_size : segment_size;
double nominalSize = (0 != nominal_packet_size) ? nominal_packet_size : SegmentSize();
double ccRate = NormSession::CalculateRate(nominalSize, rtt_estimate, ccLoss);
#ifdef LIMIT_CC_RATE
// Experimental modification to NORM-CC where congestion control rate is limited
// to MIN(2.0*measured recv rate, calculated rate). This might prevent large rate
// overshoot in conditions where the loss measurement (perhaps initial loss) is
// very low due to big network packet buffers, etc
double rxRate = 2.0*recv_rate;
if (rxRate < ccRate)
{
ext.SetCCFlag(NormCC::LIMIT);
ccRate = rxRate;
}
#endif // LIMIT_CC_RATE
ext.SetCCRate(NormQuantizeRate(ccRate));
}
PLOG(PL_DEBUG, "NormSenderNode::AttachCCFeedback() node>%lu sender>%lu sending ACK rate:%lf kbps "
"(rtt:%lf loss:%lf s:%lf recvRate:%lf) slow_start:%d\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId(),
8.0e-03*NormUnquantizeRate(ext.GetCCRate()) ,
rtt_estimate, ccLoss, nominal_packet_size,
8.0e-03*recv_rate, slow_start);
ext.SetCCSequence(cc_sequence);
} // end NormSenderNode::AttachCCFeedback()
bool NormSenderNode::OnCCTimeout(ProtoTimer& /*theTimer*/)
{
// Build and send NORM_ACK(CC)
if (ack_pending && !ack_ex_pending && (1 == cc_timer.GetRepeatCount()))
{
if (0 == session.GetProbeTOS()) // always send NormAck(CC) in special TOS case
{
// Send ACK flush right away (CC feedback is included)
if (ack_timer.IsActive()) ack_timer.Deactivate();
if (cc_timer.IsActive()) cc_timer.Deactivate(); // will be reactivated if needed
OnAckTimeout(ack_timer);
return false;
}
}
switch (cc_timer.GetRepeatCount())
{
case 0:
// "hold-off" time has ended
break;
case 1:
{
// We weren't suppressed, so build an ACK(CC) and send
NormAckMsg* ack = (NormAckMsg*)session.GetMessageFromPool();
if (!ack)
{
PLOG(PL_WARN, "NormSenderNode::OnCCTimeout() node>%lu sender>%lu warning: message pool empty ...\n",
(unsigned long)LocalNodeId(), (unsigned long)GetId());
if (cc_timer.IsActive()) cc_timer.Deactivate();
return false;
}
ack->Init();
ack->SetSenderId(GetId());
ack->SetInstanceId(instance_id);
ack->SetAckType(NormAck::CC);
ack->SetAckId(0);
AttachCCFeedback(*ack); // cc feedback extension
// TBD - we need to provide a multicast_acks option
if (unicast_nacks)
ack->SetDestination(GetAddress());
else
ack->SetDestination(session.Address());
bool success = session.SendMessage(*ack);
session.ReturnMessageToPool(ack);
if (success)
{
cc_feedback_needed = false;
// Begin cc_timer "holdoff" phase
if (!is_clr && !is_plr && session.Address().IsMulticast())
{
cc_timer.SetInterval(grtt_estimate*backoff_factor);
}
else if (cc_timer.IsActive())
{
cc_timer.Deactivate();
return false;
}
}
else
{
// TBD - queue ack so it gets send retry?
PLOG(PL_ERROR, "NormSenderNode::OnCCTimeout() error: SendMessage(ack) failure\n");
if (cc_timer.IsActive()) cc_timer.Deactivate();
return false;
}
break;
}
default:
// Should never occur
ASSERT(0);
break;
}
return true;
} // end NormSenderNode::OnCCTimeout()
bool NormSenderNode::OnAckTimeout(ProtoTimer& /*theTimer*/)
{
// Build and send NORM_ACK(FLUSH)
if (ack_ex_pending)
return true; // Will acknowledge when application services RX_ACK_REQUEST notification
NormAckFlushMsg* ack = (NormAckFlushMsg*)session.GetMessageFromPool();
if (NULL != ack)
{
ack->Init();
ack->SetSenderId(GetId());
ack->SetInstanceId(instance_id);
ack->SetAckType(NormAck::FLUSH);
ack->SetAckId(0);
AttachCCFeedback(*ack);
if (0 != ack_ex_length)
{
NormAppAckExtension ext;
ack->AttachExtension(ext);
ext.SetContent(ack_ex_buffer, ack_ex_length);
ack->PackExtension(ext);
}
ack->SetObjectId(watermark_object_id);
// _Attempt_ to set the fec_payload_id source block length field appropriately
UINT16 blockLen;
NormObject* obj = rx_table.Find(watermark_object_id);
if (NULL != obj)
blockLen = obj->GetBlockSize(watermark_block_id);
else if (watermark_segment_id < BlockSize())
blockLen = BlockSize();
else
blockLen = watermark_segment_id;
ack->SetFecPayloadId(fec_id, watermark_block_id.GetValue(), watermark_segment_id, blockLen, fti_data.GetFecFieldSize());
if (unicast_nacks)
ack->SetDestination(GetAddress());
else
ack->SetDestination(session.Address());
// Don't rate limit feedback messages
if (session.SendMessage(*ack))
{
ack_pending = false;
if (0 == session.GetProbeTOS()) // Always send NormAck(CC) for special TOS case
{
cc_feedback_needed = false;
if (cc_enable && !is_clr && !is_plr && session.Address().IsMulticast())
{
// Install cc feedback holdoff
cc_timer.SetInterval(grtt_estimate*backoff_factor);
if (cc_timer.IsActive())
cc_timer.Reschedule();
else
session.ActivateTimer(cc_timer);
cc_timer.DecrementRepeatCount(); // put timer into "holdoff" phase
}
else if (cc_timer.IsActive())
{
cc_timer.Deactivate();
}
}
}
else
{
// TBD - should we queue the message so it can get a send retry?
PLOG(PL_ERROR, "NormSenderNode::OnAckTimeout() error: SendMessage(ack) failure\n");
}
session.ReturnMessageToPool(ack);
}
else
{
PLOG(PL_WARN, "NormSenderNode::OnAckTimeout() warning: message pool exhausted!\n");
}
return true;
} // end NormSenderNode::OnAckTimeout()
NormAckingNode::NormAckingNode(class NormSession& theSession, NormNodeId nodeId)
: NormNode(ACKER, theSession, nodeId),
ack_received(false), req_count(theSession.GetTxRobustFactor()),
ack_ex_buffer(NULL), ack_ex_length(0)
{
}
NormAckingNode::~NormAckingNode()
{
if (NULL != ack_ex_buffer)
{
delete[] ack_ex_buffer;
ack_ex_buffer = NULL;
ack_ex_length = 0;
}
}
bool NormAckingNode::SetAckEx(const char* buffer, UINT16 numBytes)
{
if (numBytes != ack_ex_length)
{
if (NULL != ack_ex_buffer) delete[] ack_ex_buffer;
if (NULL == (ack_ex_buffer = new char[numBytes]))
{
// TBD - notify app of errror
PLOG(PL_ERROR, "NormAckingNode::SetAppAckContent() new ack_ex_buffer error: %s\n", GetErrorString());
ack_ex_length = 0;
return false;
}
ack_ex_length = numBytes;
}
memcpy(ack_ex_buffer, buffer, numBytes);
return true;
} // end NormAckingNode::SetAckEx()
bool NormAckingNode::GetAckEx(char* buffer, unsigned int* buflen)
{
if (0 != ack_ex_length)
{
if (NULL != buflen)
{
if (*buflen < ack_ex_length)
{
*buflen = ack_ex_length;
return false;
}
*buflen = ack_ex_length;
if (NULL != buffer)
memcpy(buffer, ack_ex_buffer, ack_ex_length);
else
return false;
}
return true;
}
else
{
if (NULL != buflen) *buflen = 0;
return false; // no application-defined ACK request data
}
} // end NormAckingNode::GetAckEx()
NormNodeTree::NormNodeTree()
: root(NULL)
{
}
NormNodeTree::~NormNodeTree()
{
Destroy();
}
NormNode *NormNodeTree::FindNodeById(NormNodeId nodeId) const
{
NormNode* x = root;
while(x && (x->id != nodeId))
{
if (nodeId < x->id)
x = x->left;
else
x = x->right;
}
return x;
} // end NormNodeTree::FindNodeById()
void NormNodeTree::AttachNode(NormNode *node)
{
ASSERT(NULL != node);
node->Retain();
node->left = NULL;
node->right = NULL;
NormNode *x = root;
while (x)
{
if (node->id < x->id)
{
if (!x->left)
{
x->left = node;
node->parent = x;
return;
}
else
{
x = x->left;
}
}
else
{
if (!x->right)
{
x->right = node;
node->parent = x;
return;
}
else
{
x = x->right;
}
}
}
root = node; // root _was_ NULL
} // end NormNodeTree::AttachNode()
void NormNodeTree::DetachNode(NormNode* node)
{
ASSERT(NULL != node);
NormNode* x;
NormNode* y;
if (!node->left || !node->right)
{
y = node;
}
else
{
if (node->right)
{
y = node->right;
while (y->left) y = y->left;
}
else
{
x = node;
y = node->parent;
while(y && (y->right == x))
{
x = y;
y = y->parent;
}
}
}
if (y->left)
x = y->left;
else
x = y->right;
if (x) x->parent = y->parent;
if (!y->parent)
root = x;
else if (y == y->parent->left)
y->parent->left = x;
else
y->parent->right = x;
if (node != y)
{
if ((y->parent = node->parent))
{
if (y->id < y->parent->id)
y->parent->left = y;
else
y->parent->right = y;
}
else
{
root = y;
}
if ((y->left = node->left)) y->left->parent = y;
if ((y->right = node->right)) y->right->parent = y;
}
node->Release();
} // end NormNodeTree::DetachNode()
void NormNodeTree::Destroy()
{
NormNode* n;
while ((n = root))
{
DetachNode(n);
n->Release(); // note will delete the node if no other reference
}
} // end NormNodeTree::Destroy()
NormNodeTreeIterator::NormNodeTreeIterator(const NormNodeTree& t, NormNode* prevNode)
: tree(t)
{
Reset(prevNode);
}
void NormNodeTreeIterator::Reset(NormNode* prevNode)
{
NormNode* x = tree.root;
if (NULL != x)
{
if (NULL == prevNode)
{
while (x->left) x = x->left;
next = x;
}
else
{
next = prevNode;
GetNextNode(); // sets "next" to return subsequent node
}
}
else
{
next = NULL;
}
} // end NormNodeTreeIterator::Reset()
NormNode* NormNodeTreeIterator::GetNextNode()
{
NormNode* n = next;
if (n)
{
if (next->right)
{
NormNode* y = n->right;
while (y->left) y = y->left;
next = y;
}
else
{
NormNode* x = n;
NormNode* y = n->parent;
while(y && (y->right == x))
{
x = y;
y = y->parent;
}
next = y;
}
}
return n;
} // end NormNodeTreeIterator::GetNextNode()
NormNodeList::NormNodeList()
: head(NULL), tail(NULL), count(0)
{
}
NormNodeList::~NormNodeList()
{
Destroy();
}
NormNode* NormNodeList::FindNodeById(NormNodeId nodeId) const
{
NormNode *next = head;
while (next)
{
if (nodeId == next->id)
return next;
else
next = next->right;
}
return NULL;
} // NormNodeList::Find()
void NormNodeList::Append(NormNode *theNode)
{
ASSERT(NULL != theNode);
theNode->Retain();
theNode->left = tail;
if (tail)
tail->right = theNode;
else
head = theNode;
tail = theNode;
theNode->right = NULL;
count++;
} // end NormNodeList::Append()
void NormNodeList::Remove(NormNode *theNode)
{
ASSERT(NULL != theNode);
theNode->Release();
if (theNode->right)
theNode->right->left = theNode->left;
else
tail = theNode->left;
if (theNode->left)
theNode->left->right = theNode->right;
else
head = theNode->right;
count--;
} // end NormNodeList::Remove()
void NormNodeList::Destroy()
{
NormNode* n;
while ((n = head))
{
Remove(n);
n->Release();
}
} // end NormNodeList::Destroy()
//////////////////////////////////////////////////////////
//
// NormLossEstimator implementation
//
NormLossEstimator::NormLossEstimator()
: synchronized(false), seeking_loss_event(true), event_window(0.0)
{
event_time.tv_sec = event_time.tv_usec = 0;
memset(history, 0, (DEPTH+1)*sizeof(unsigned int));
}
const double NormLossEstimator::weight[DEPTH] =
{
1.0, 1.0, 1.0, 1.0,
0.8, 0.6, 0.4, 0.2
};
int NormLossEstimator::SequenceDelta(unsigned short a, unsigned short b)
{
int delta = a - b;
if (delta < -0x8000)
return (delta + 0x10000);
else if (delta < 0x8000)
return delta;
else
return delta - 0x10000;
} // end NormLossEstimator::SequenceDelta()
// Returns true when a loss event has occurred
bool NormLossEstimator::Update(const struct timeval& currentTime,
unsigned short seq,
bool ecn)
{
if (!synchronized)
{
Sync(seq);
return false;
}
bool outage = false;
int delta = SequenceDelta(seq, index_seq);
if (abs(delta) > MAX_OUTAGE) // out-of-range packet
{
index_seq = seq;
return false;
}
else if (delta > 0) // new packet arrival
{
if (ecn || (delta > 1)) outage = true;
index_seq = seq;
}
else // (delta <= 0) // old misordered or duplicate packet
{
return false;
}
if (outage)
{
if (!seeking_loss_event)
{
double deltaTime = (double)(currentTime.tv_sec - event_time.tv_sec);
if (currentTime.tv_usec > event_time.tv_usec)
deltaTime += (double)(currentTime.tv_usec - event_time.tv_usec) * 1.0e-06;
else
deltaTime -= (double)(event_time.tv_usec - currentTime.tv_usec) * 1.0e-06;
if (deltaTime > event_window) seeking_loss_event = true;
}
if (seeking_loss_event)
{
memmove(history+1, history, DEPTH*sizeof(unsigned int));
history[0] = 1;
seeking_loss_event = false;
event_time = currentTime;
return true;
}
else
{
// Only count one loss per loss event
history[0] = 1;
return false;
}
}
else
{
history[0]++;
return false;
}
} // end NormLossEstimator::Update()
double NormLossEstimator::LossFraction()
{
if (0 == history[1]) return 0.0;
double weightSum = 0.0;
double s0 = 0.0;
const double* wptr = weight;
const unsigned int* h = history;
unsigned int i;
for (i = 0; i < DEPTH; i++)
{
if (0 == *h) break;
s0 += *wptr * *h++;
weightSum += *wptr++;
}
s0 /= weightSum;
weightSum = 0.0;
double s1 = 0.0;
wptr = weight;
h = history + 1;
for (i = 0; i < DEPTH; i++)
{
if (0 == *h) break;
s1 += *wptr * *h++; // ave loss interval w/out current interval
weightSum += *wptr++; // (TBD) this could be pre-computed
}
s1 /= weightSum;
return (1.0 / (MAX(s0,s1)));
} // end NormLossEstimator::LossFraction()
NormLossEstimator2::NormLossEstimator2()
: init(false), ignore_loss(false), tolerate_loss(false),
lag_mask(0xffffffff), lag_depth(0), lag_test_bit(0x01),
event_window(0.0), seeking_loss_event(SEEKING), current_discount(1.0)
{
event_time.tv_sec = event_time.tv_usec = 0;
memset(history, 0, 9*sizeof(unsigned long));
discount[0] = 1.0;
}
const double NormLossEstimator2::weight[8] =
{
1.0, 1.0, 1.0, 1.0,
0.8, 0.6, 0.4, 0.2
};
int NormLossEstimator2::SequenceDelta(unsigned short a, unsigned short b)
{
int delta = a - b;
if (delta < -0x8000)
return (delta + 0x10000);
else if (delta < 0x8000)
return delta;
else
return delta - 0x10000;
} // end NormLossEstimator2::SequenceDelta()
bool NormLossEstimator2::Update(const struct timeval& currentTime,
unsigned short theSequence,
bool ecnStatus)
{
// (TBD) What if the first packet that arrives has ECN set???
if (!init)
{
Init(theSequence);
return false;
}
unsigned int outageDepth = 0;
// Process packet through lag filter and check for loss
int delta = SequenceDelta(theSequence, lag_index);
if (delta > 100) // Very new packet arrived
{
Sync(theSequence); // resync
return false;
}
else if (delta > 0) // New packet arrived
{
if (lag_depth)
{
unsigned int outage = 0;
for (int i = 0; i < delta; i++)
{
if (i <= (int)lag_depth)
{
outage++;
if (lag_mask & lag_test_bit)
{
if (outage > 1)
outageDepth = MAX(outage, outageDepth);
outage = 0;
}
else
{
lag_mask |= lag_test_bit;
}
lag_mask <<= 1;
}
else
{
outage += delta - lag_depth - 1;
break;
}
}
outageDepth = MAX(outage, outageDepth);
lag_mask |= 0x01;
}
else
{
if (delta > 1) outageDepth = delta - 1;
}
lag_index = theSequence;
}
else if (delta < -100) // Very old packet arrived
{
Sync(theSequence); // resync
return false;
}
else if (delta < -((int)lag_depth)) // Old packet arrived
{
ChangeLagDepth(-delta);
}
else if (delta < 0) // Lagging packet arrived
{ // (duplicates have no effect)
lag_mask |= (0x01 << (-delta));
return false;
}
else // (delta == 0)
{
return false; // Duplicate packet arrived, ignore
}
if (ignore_loss) outageDepth = 0;
if (ecnStatus) outageDepth += 1;
bool newLossEvent = false;
//if (!seeking_loss_event)
if (SEEKING != seeking_loss_event)
{
double deltaTime = (double)(currentTime.tv_sec - event_time.tv_sec);
if (currentTime.tv_usec > event_time.tv_usec)
deltaTime += (double)(currentTime.tv_usec - event_time.tv_usec) * 1.0e-06;
else
deltaTime -= (double)(event_time.tv_usec - currentTime.tv_usec) * 1.0e-06;
// Use a longer "loss event window" for NORM-CCE ("ignore_loss" = "true")
// since RED/ECN tends to start marking "early" and stop marking "late"
double windowScale = ignore_loss ? 2.0 : 1.0;
if (deltaTime > windowScale*event_window)
{
seeking_loss_event = SEEKING;
}
}
//if (seeking_loss_event)
if (CONFIRMED != seeking_loss_event)
{
if ((1 == outageDepth) && !ecnStatus && tolerate_loss) // single, non-ECN loss event
{
if (SEEKING == seeking_loss_event)
{
seeking_loss_event = CONFIRMING; // wait for more loss to confirm congestion event
event_time = event_time_orig = currentTime;
outageDepth = 0;
}
}
if (outageDepth) // non-zero outageDepth means pkt loss(es)
{
// call to LossFraction() here is just to make sure "current_discount"
// is updated accordlingly
LossFraction();
// New method
// New loss event, shift loss interval history & discounts
memmove(&history[1], &history[0], 8*sizeof(unsigned long));
history[0] = 0;
for (int i = 8; i > 0; i--)
discount[i] = discount[i-1]*current_discount;
discount[0] = 1.0;
current_discount = 1.0;
seeking_loss_event = CONFIRMED;
event_time = event_time_orig = currentTime;
newLossEvent = true;
}
}
else
{
// we commented this out not to reset history to get better loss event period measurement
// if (outageDepth > 0)
// history[0] = 0;
} // end if/else (seeking_loss_event)
// TBD - instead of counting packets, should we calculate based on the sequence number
// of the last loss event and the current sequence number? (so dups won't fool us into increasing rate)
//if (history[0] < 65536*2)
history[0]++;
return newLossEvent;
} // end NormLossEstimator2::Update()
// TFRC Loss interval averaging with discounted, weighted averaging
double NormLossEstimator2::LossFraction()
{
if (0 == history[1]) return 0.0;
// Compute older weighted average s1->s8 for discount determination
double average = 0.0;
double scaling = 0.0;
unsigned int i;
for (i = 1; i < 9; i++)
{
if (history[i])
{
average += history[i] * weight[i-1] * discount[i];
scaling += discount[i] * weight[i-1];
}
else
{
break;
}
}
double s1 = average / scaling;
// Compute discount if applicable
if (history[0] > (2.0*s1))
{
current_discount = (2.0*s1) / (double) history[0];
current_discount = MAX(current_discount, 0.5);
}
if (history[0] > s1) return (1.0 / (double)history[0]);
// Compute newer weighted average s0->s7 with discounting
average = 0.0;
scaling = 0.0;
for (i = 0; i < 8; i++)
{
if (history[i])
{
double d = (i > 0) ? current_discount : 1.0;
average += d * history[i] * weight[i] * discount[i];
scaling += d * discount[i] * weight[i];
}
else
{
break;
}
}
double s0 = (average > 0.0) ? average / scaling : 0.0;
// Use max of old/new averages (i.e. only use discounting if it helps increase rate)
double result = (1.0 / MAX(s0, s1));
return result;
} // end NormLossEstimator2::LossFraction()