4851 lines
175 KiB
C++
Executable File
4851 lines
175 KiB
C++
Executable File
#include "normObject.h"
|
|
#include "normSession.h"
|
|
|
|
#ifndef _WIN32_WCE
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#endif // !_WIN32_WCE
|
|
|
|
NormObject::NormObject(NormObject::Type theType,
|
|
class NormSession& theSession,
|
|
class NormSenderNode* theSender,
|
|
const NormObjectId& transportId)
|
|
: type(theType), session(theSession), sender(theSender), reference_count(1),
|
|
transport_id(transportId), segment_size(0), pending_info(false), repair_info(false),
|
|
current_block_id(0), next_segment_id(0),
|
|
max_pending_block(0), max_pending_segment(0),
|
|
info_ptr(NULL), info_len(0), first_pass(true), accepted(false), notify_on_update(true),
|
|
user_data(NULL)
|
|
#ifndef USE_PROTO_TREE
|
|
, next(NULL)
|
|
#endif
|
|
{
|
|
if (theSender)
|
|
{
|
|
nacking_mode = theSender->GetDefaultNackingMode();
|
|
theSender->Retain();
|
|
}
|
|
else
|
|
{
|
|
nacking_mode = NACK_NORMAL; // it doesn't really matter if !theSender
|
|
}
|
|
}
|
|
|
|
NormObject::~NormObject()
|
|
{
|
|
Close();
|
|
if (NULL != info_ptr)
|
|
{
|
|
delete[] info_ptr;
|
|
info_ptr = NULL;
|
|
}
|
|
}
|
|
|
|
void NormObject::Retain()
|
|
{
|
|
reference_count++;
|
|
if (sender) sender->Retain();
|
|
} // end NormObject::Retain()
|
|
|
|
void NormObject::Release()
|
|
{
|
|
if (sender) sender->Release();
|
|
if (reference_count)
|
|
{
|
|
reference_count--;
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_ERROR, "NormObject::Release() releasing non-retained object?!\n");
|
|
}
|
|
if (0 == reference_count) delete this;
|
|
} // end NormObject::Release()
|
|
|
|
// This is mainly used for debug messages
|
|
NormNodeId NormObject::LocalNodeId() const
|
|
{
|
|
return session.LocalNodeId();
|
|
} // end NormObject::LocalNodeId()
|
|
|
|
NormNodeId NormObject::GetSenderNodeId() const
|
|
{
|
|
return sender ? sender->GetId() : NORM_NODE_NONE;
|
|
} // end NormObject::GetSenderNodeId()
|
|
|
|
bool NormObject::Open(const NormObjectSize& objectSize,
|
|
const char* infoPtr,
|
|
UINT16 infoLen,
|
|
UINT16 segmentSize,
|
|
UINT8 fecId,
|
|
UINT8 fecM,
|
|
UINT16 numData,
|
|
UINT16 numParity)
|
|
{
|
|
// Note "objectSize" represents actual total object size for
|
|
// DATA or FILE objects, buffer size for STREAM objects
|
|
// In either case, we need our sliding bit masks to be of
|
|
// appropriate size.
|
|
if (sender)
|
|
{
|
|
if (infoLen > 0)
|
|
{
|
|
pending_info = true;
|
|
if (!(info_ptr = new char[segmentSize]))
|
|
{
|
|
PLOG(PL_FATAL, "NormObject::Open() info allocation error\n");
|
|
return false;
|
|
}
|
|
}
|
|
info_len = 0; // will be set properly upon NORM_INFO arrival
|
|
last_nack_time.GetCurrentTime(); // init to now
|
|
}
|
|
else
|
|
{
|
|
if (NULL != infoPtr)
|
|
{
|
|
if (NULL != info_ptr) delete[] info_ptr;
|
|
if (infoLen > segmentSize)
|
|
{
|
|
PLOG(PL_FATAL, "NormObject::Open() info too big error\n");
|
|
info_len = 0;
|
|
return false;
|
|
}
|
|
if (!(info_ptr = new char[infoLen]))
|
|
{
|
|
PLOG(PL_FATAL, "NormObject::Open() info allocation error\n");
|
|
info_len = 0;
|
|
return false;
|
|
}
|
|
memcpy(info_ptr, infoPtr, infoLen);
|
|
info_len = infoLen;
|
|
pending_info = true;
|
|
}
|
|
else
|
|
{
|
|
if (NormSession::FTI_INFO == session.SenderFtiMode())
|
|
{
|
|
|
|
pending_info = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute number of segments and coding blocks for the object
|
|
// (Note NormObjectSize divide operator always rounds _upwards_)
|
|
NormObjectSize numSegments = objectSize / NormObjectSize(segmentSize);
|
|
NormObjectSize numBlocks = numSegments / NormObjectSize(numData);
|
|
|
|
// TBD - make sure numBlocks is in range of fec_block_mask
|
|
fec_block_mask = NormPayloadId::GetFecBlockMask(fecId, fecM);
|
|
|
|
UINT32 blockIdMax = fec_block_mask;
|
|
|
|
if (IsStream()) blockIdMax = (blockIdMax / 2) - 1;
|
|
if ((numBlocks.MSB() > 0) || (numBlocks.LSB() > blockIdMax))
|
|
{
|
|
PLOG(PL_FATAL, "NormObject::Open() error: object size exceeds FEC blocking and segmentation parameter capability\n");
|
|
Close();
|
|
return false;
|
|
}
|
|
|
|
// No NormBlockId wrapping for NORM_DATA or NORM_FILE objects
|
|
// (so we zero fec_block_mask for "unsigned" value behaviors
|
|
// in NormBlockId and ProtoBitmask classes
|
|
|
|
if (!IsStream()) fec_block_mask = 0;
|
|
|
|
if (!block_buffer.Init(numBlocks.LSB(), 256, fec_block_mask))
|
|
{
|
|
PLOG(PL_FATAL, "NormObject::Open() init block_buffer error\n");
|
|
Close();
|
|
return false;
|
|
}
|
|
|
|
// Init pending_mask (everything pending)
|
|
// Note the pending_mask and repair_mask both need to be inited with a
|
|
// "rangeMask" according to their fecId (e.g. 24-bit mask for some)!!!
|
|
if (!pending_mask.Init(numBlocks.LSB(), fec_block_mask))
|
|
{
|
|
PLOG(PL_FATAL, "NormObject::Open() init pending_mask (%lu) error: %s\n",
|
|
(unsigned long)numBlocks.LSB(), GetErrorString());
|
|
Close();
|
|
return false;
|
|
}
|
|
|
|
// Init repair_mask
|
|
if (!repair_mask.Init(numBlocks.LSB(), fec_block_mask))
|
|
{
|
|
PLOG(PL_FATAL, "NormObject::Open() init pending_mask error\n");
|
|
Close();
|
|
return false;
|
|
}
|
|
repair_mask.Clear();
|
|
|
|
if (STREAM == type)
|
|
{
|
|
small_block_size = large_block_size = numData;
|
|
small_block_count = large_block_count = numBlocks.LSB();
|
|
final_segment_size = segmentSize;
|
|
if (NULL == sender)
|
|
{
|
|
// This inits sender stream state to set things tx pending
|
|
// (the receiver stream is sync'd according to sync policy)
|
|
NormStreamObject* stream = static_cast<NormStreamObject*>(this);
|
|
stream->StreamResync(NormBlockId(0));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Set everything pending (i.e, nothing sent or received yet)
|
|
pending_mask.Clear();
|
|
pending_mask.SetBits(0, numBlocks.LSB());
|
|
// Compute FEC block structure per NORM Protocol Spec Section 5.1.1
|
|
// (Note NormObjectSize divide operator always rounds _up_, i.e., ceil(numSegments/numBlocks))
|
|
NormObjectSize largeBlockSize;
|
|
if ((0 != numBlocks.MSB()) || (0 != numBlocks.LSB()))
|
|
largeBlockSize = numSegments / numBlocks;
|
|
ASSERT(0 == largeBlockSize.MSB());
|
|
large_block_size = largeBlockSize.LSB();
|
|
if (numSegments == (numBlocks*largeBlockSize))
|
|
{
|
|
small_block_size = large_block_size;
|
|
small_block_count = numBlocks.LSB();
|
|
large_block_count = 0;
|
|
}
|
|
else
|
|
{
|
|
small_block_size = large_block_size - 1;
|
|
NormObjectSize largeBlockCount = numSegments - numBlocks*small_block_size;
|
|
ASSERT(0 == largeBlockCount.MSB());
|
|
large_block_count = largeBlockCount.LSB();
|
|
NormObjectSize smallBlockCount = numBlocks - largeBlockCount;
|
|
ASSERT(0 == smallBlockCount.MSB());
|
|
small_block_count = smallBlockCount.LSB();
|
|
}
|
|
// not used for STREAM objects
|
|
final_block_id = large_block_count + small_block_count - 1;
|
|
NormObjectSize finalSegmentSize =
|
|
objectSize - (numSegments - NormObjectSize((UINT32)1))*segmentSize;
|
|
ASSERT(0 == finalSegmentSize.MSB());
|
|
final_segment_size = finalSegmentSize.LSB();
|
|
}
|
|
|
|
object_size = objectSize;
|
|
segment_size = segmentSize;
|
|
fec_id = fecId;
|
|
fec_m = fecM;
|
|
ndata = numData;
|
|
nparity = numParity;
|
|
current_block_id = 0;
|
|
next_segment_id = 0;
|
|
max_pending_block = 0;
|
|
max_pending_segment = 0;
|
|
return true;
|
|
} // end NormObject::Open()
|
|
|
|
void NormObject::Close()
|
|
{
|
|
NormBlock* block;
|
|
while ((block = block_buffer.Find(block_buffer.RangeLo())))
|
|
{
|
|
block_buffer.Remove(block);
|
|
if (sender)
|
|
sender->PutFreeBlock(block);
|
|
else
|
|
session.SenderPutFreeBlock(block);
|
|
}
|
|
repair_mask.Destroy();
|
|
pending_mask.Destroy();
|
|
block_buffer.Destroy();
|
|
segment_size = 0;
|
|
} // end NormObject::Close()
|
|
|
|
NormObjectSize NormObject::GetBytesPending() const
|
|
{
|
|
NormBlockId nextId;
|
|
if (!IsStream() && GetFirstPending(nextId))
|
|
{
|
|
NormObjectSize largeBlockBytes = NormObjectSize(large_block_size) *
|
|
NormObjectSize(segment_size);
|
|
NormObjectSize smallBlockBytes = NormObjectSize(small_block_size) *
|
|
NormObjectSize(segment_size);
|
|
NormObjectSize lastBlockBytes = smallBlockBytes - NormObjectSize(segment_size) +
|
|
NormObjectSize(final_segment_size);
|
|
NormObjectSize pendingBytes(0);
|
|
do
|
|
{
|
|
NormBlock* block = block_buffer.Find(nextId);
|
|
if (block)
|
|
pendingBytes += block->GetBytesPending(GetBlockSize(nextId), segment_size,
|
|
final_block_id, final_segment_size);
|
|
else if (nextId.GetValue() < large_block_count)
|
|
pendingBytes += largeBlockBytes;
|
|
else if (nextId == final_block_id)
|
|
pendingBytes += lastBlockBytes;
|
|
else
|
|
pendingBytes += smallBlockBytes;
|
|
Increment(nextId);
|
|
} while (GetNextPending(nextId));
|
|
return pendingBytes;
|
|
}
|
|
else
|
|
{
|
|
return NormObjectSize(0);
|
|
}
|
|
} // end NormObject::GetBytesPending()
|
|
|
|
// Used by sender
|
|
bool NormObject::HandleInfoRequest(bool holdoff)
|
|
{
|
|
bool increasedRepair = false;
|
|
if (info_ptr || (NormSession::FTI_INFO == session.SenderFtiMode()))
|
|
{
|
|
if (!repair_info)
|
|
{
|
|
increasedRepair = true;
|
|
if (holdoff)
|
|
{
|
|
if (pending_info)
|
|
increasedRepair = false;
|
|
else
|
|
pending_info = true;
|
|
}
|
|
else
|
|
{
|
|
pending_info = true; // does this really need to be done?
|
|
repair_info = true;
|
|
}
|
|
}
|
|
}
|
|
return increasedRepair;
|
|
} // end NormObject::HandleInfoRequest()
|
|
|
|
/*
|
|
bool NormObject::HandleBlockRequest(const NormBlockId& firstId, const NormBlockId& lastId)
|
|
{
|
|
PLOG(PL_TRACE, "NormObject::HandleBlockRequest() node>%lu blk>%lu -> blk>%lu\n",
|
|
(unsigned long)LocalNodeId(),
|
|
(unsigned long)firstId.GetValue(),
|
|
(unsigned long)lastId.GetValue());
|
|
bool increasedRepair = false;
|
|
NormBlockId nextId = firstId;
|
|
while (Compare(nextId, lastId) <= 0)
|
|
{
|
|
if (!repair_mask.Test(nextId.GetValue()))
|
|
{
|
|
// (TBD) these tests can probably go away if everything else is done right
|
|
if (!pending_mask.CanSet(nextId.GetValue()))
|
|
PLOG(PL_ERROR, "NormObject::HandleBlockRequest() pending_mask.CanSet(%lu) error\n",
|
|
(unsigned long)nextId.GetValue());
|
|
if (!repair_mask.Set(nextId.GetValue()))
|
|
PLOG(PL_ERROR, "NormObject::HandleBlockRequest() repair_mask.Set(%lu) error\n",
|
|
(unsigned long)nextId.GetValue());
|
|
increasedRepair = true;
|
|
}
|
|
Increment(nextId);
|
|
}
|
|
return increasedRepair;
|
|
} // end NormObject::HandleBlockRequest();
|
|
*/
|
|
|
|
bool NormObject::HandleBlockRequest(const NormBlockId& firstId, const NormBlockId& lastId)
|
|
{
|
|
PLOG(PL_TRACE, "NormObject::HandleBlockRequest() node>%lu blk>%lu -> blk>%lu\n",
|
|
(unsigned long)LocalNodeId(),
|
|
(unsigned long)firstId.GetValue(),
|
|
(unsigned long)lastId.GetValue());
|
|
bool blockInRange = true;
|
|
NormBlockId nextId = firstId;
|
|
while (Compare(nextId, lastId) <= 0)
|
|
{
|
|
if (!repair_mask.Test(nextId.GetValue()))
|
|
{
|
|
// (TBD) these tests can probably go away if everything else is done right
|
|
if (!pending_mask.CanSet(nextId.GetValue()))
|
|
{
|
|
PLOG(PL_ERROR, "NormObject::HandleBlockRequest() pending_mask.CanSet(%lu) error\n",
|
|
(unsigned long)nextId.GetValue());
|
|
blockInRange = false;
|
|
if (IsStream())
|
|
static_cast<NormStreamObject*>(this)->UnlockBlock(nextId);
|
|
}
|
|
else if (!repair_mask.Set(nextId.GetValue()))
|
|
{
|
|
PLOG(PL_ERROR, "NormObject::HandleBlockRequest() repair_mask.Set(%lu) error\n",
|
|
(unsigned long)nextId.GetValue());
|
|
blockInRange = false;
|
|
if (IsStream())
|
|
static_cast<NormStreamObject*>(this)->UnlockBlock(nextId);
|
|
}
|
|
}
|
|
Increment(nextId);
|
|
}
|
|
return blockInRange;
|
|
} // end NormObject::HandleBlockRequest();
|
|
|
|
|
|
bool NormObject::TxReset(NormBlockId firstBlock, bool requeue)
|
|
{
|
|
bool increasedRepair = false;
|
|
if (!pending_info && (HaveInfo() || (NormSession::FTI_INFO == session.SenderFtiMode())))
|
|
{
|
|
increasedRepair = true;
|
|
pending_info = true;
|
|
}
|
|
repair_info = false;
|
|
repair_mask.Reset(firstBlock.GetValue());
|
|
repair_mask.Xor(pending_mask);
|
|
if (repair_mask.IsSet())
|
|
{
|
|
increasedRepair = true;
|
|
pending_mask.Reset(firstBlock.GetValue());
|
|
}
|
|
repair_mask.Clear();
|
|
// TBD - Use "firstBlock" as starting point for the iterator
|
|
NormBlockBuffer::Iterator iterator(block_buffer);
|
|
NormBlock* block;
|
|
while ((block = iterator.GetNextBlock()))
|
|
{
|
|
NormBlockId blockId = block->GetId();
|
|
//if (blockId >= firstBlock)
|
|
if (Compare(blockId, firstBlock) >= 0)
|
|
{
|
|
increasedRepair |= block->TxReset(GetBlockSize(blockId),
|
|
nparity,
|
|
session.SenderAutoParity(),
|
|
segment_size);
|
|
if (requeue) block->ClearFlag(NormBlock::IN_REPAIR); // since we're requeuing
|
|
}
|
|
}
|
|
if (requeue)
|
|
{
|
|
first_pass = true;
|
|
max_pending_block = 0;
|
|
}
|
|
return increasedRepair;
|
|
} // end NormObject::TxReset()
|
|
|
|
bool NormObject::TxResetBlocks(const NormBlockId& firstId, const NormBlockId& lastId)
|
|
{
|
|
bool increasedRepair = false;
|
|
UINT16 autoParity = session.SenderAutoParity();
|
|
NormBlockId nextId = firstId;
|
|
while (Compare(nextId, lastId) <= 0)
|
|
{
|
|
if (!pending_mask.Test(nextId.GetValue()))
|
|
{
|
|
pending_mask.Set(nextId.GetValue());
|
|
increasedRepair = true;
|
|
}
|
|
NormBlock* block = block_buffer.Find(nextId);
|
|
if (NULL != block)
|
|
increasedRepair |= block->TxReset(GetBlockSize(nextId), nparity, autoParity, segment_size);
|
|
Increment(nextId);
|
|
}
|
|
return increasedRepair;
|
|
} // end NormObject::TxResetBlocks()
|
|
|
|
bool NormObject::TxUpdateBlock(NormBlock* theBlock,
|
|
NormSegmentId firstSegmentId,
|
|
NormSegmentId lastSegmentId,
|
|
UINT16 numErasures)
|
|
{
|
|
NormBlockId blockId = theBlock->GetId();
|
|
if (pending_mask.CanSet(blockId.GetValue()))
|
|
{
|
|
if (theBlock->TxUpdate(firstSegmentId, lastSegmentId,
|
|
GetBlockSize(blockId), nparity,
|
|
numErasures))
|
|
{
|
|
pending_mask.Set(blockId.GetValue());
|
|
return true;
|
|
}
|
|
}
|
|
else if (IsStream())
|
|
{
|
|
static_cast<NormStreamObject*>(this)->UnlockBlock(blockId);
|
|
}
|
|
return false;
|
|
} // end NormObject::TxUpdateBlock()
|
|
|
|
bool NormObject::ActivateRepairs()
|
|
{
|
|
bool repairsActivated = false;
|
|
// Activate repair of info if applicable (TBD - how to flag info message as repair???)
|
|
if (repair_info)
|
|
{
|
|
pending_info = true;
|
|
repair_info = false;
|
|
repairsActivated = true;
|
|
}
|
|
// Activate any complete block repairs
|
|
NormBlockId nextId;
|
|
if (GetFirstRepair(nextId))
|
|
{
|
|
NormBlockId lastId;
|
|
GetLastRepair(lastId); // for debug output only
|
|
PLOG(PL_DEBUG, "NormObject::ActivateRepairs() node>%lu obj>%hu activating blk>%lu->%lu block repairs ...\n",
|
|
(unsigned long)LocalNodeId(),
|
|
(UINT16)transport_id,
|
|
(unsigned long)nextId.GetValue(),
|
|
(unsigned long)lastId.GetValue());
|
|
//repairsActivated = true;
|
|
UINT16 autoParity = session.SenderAutoParity();
|
|
do
|
|
{
|
|
if (pending_mask.CanSet(nextId.GetValue()))
|
|
{
|
|
NormBlock* block = block_buffer.Find(nextId);
|
|
if (NULL != block) block->TxReset(GetBlockSize(nextId), nparity, autoParity, segment_size);
|
|
pending_mask.Set(nextId.GetValue());
|
|
repairsActivated = true;
|
|
}
|
|
else if (IsStream())
|
|
{
|
|
static_cast<NormStreamObject*>(this)->UnlockBlock(nextId);
|
|
}
|
|
repair_mask.Unset(nextId.GetValue());
|
|
Increment(nextId);
|
|
} while (GetNextRepair(nextId));
|
|
ASSERT(!repair_mask.IsSet());
|
|
}
|
|
// Activate partial block (segment) repairs
|
|
// TBD - Can we make this more efficient for larger block_buffer sizes
|
|
// (e.g., have a separate list (or bitmask) of repair pending blocks?)
|
|
NormBlockBuffer::Iterator iterator(block_buffer);
|
|
NormBlock* block;
|
|
while ((block = iterator.GetNextBlock()))
|
|
{
|
|
if (block->ActivateRepairs(nparity))
|
|
{
|
|
PLOG(PL_TRACE, "NormObject::ActivateRepairs() node>%lu obj>%hu activated blk>%lu segment repairs ...\n",
|
|
(unsigned long)LocalNodeId(), (UINT16)transport_id, (unsigned long)block->GetId().GetValue());
|
|
if (!pending_mask.Set(block->GetId().GetValue()))
|
|
{
|
|
block->ClearPending();
|
|
if (IsStream())
|
|
static_cast<NormStreamObject*>(this)->UnlockBlock(block->GetId());
|
|
}
|
|
else
|
|
{
|
|
repairsActivated = true;
|
|
}
|
|
}
|
|
}
|
|
return repairsActivated;
|
|
} // end NormObject::ActivateRepairs()
|
|
|
|
// called by senders only
|
|
bool NormObject::AppendRepairAdv(NormCmdRepairAdvMsg& cmd)
|
|
{
|
|
// Determine range of blocks possibly pending repair
|
|
NormBlockId nextId = 0;
|
|
GetFirstRepair(nextId);
|
|
NormBlockId endId = 0;
|
|
GetLastRepair(endId);
|
|
if (block_buffer.IsEmpty())
|
|
{
|
|
if (repair_mask.IsSet()) Increment(endId);
|
|
}
|
|
else
|
|
{
|
|
NormBlockId lo = block_buffer.RangeLo();
|
|
NormBlockId hi = block_buffer.RangeHi();
|
|
if (repair_mask.IsSet())
|
|
{
|
|
nextId = (Compare(lo, nextId) < 0) ? lo : nextId;
|
|
endId = (Compare(hi, endId) > 0) ? hi : endId;
|
|
}
|
|
else
|
|
{
|
|
nextId = lo;
|
|
endId = hi;
|
|
}
|
|
Increment(endId);
|
|
}
|
|
// Instantiate a repair request for BLOCK level repairs
|
|
NormRepairRequest req;
|
|
bool requestAppended = false;
|
|
req.SetFlag(NormRepairRequest::BLOCK);
|
|
if (repair_info) req.SetFlag(NormRepairRequest::INFO);
|
|
NormRepairRequest::Form prevForm = NormRepairRequest::INVALID;
|
|
// Iterate through the range of blocks possibly pending repair
|
|
NormBlockId firstId;
|
|
UINT32 blockCount = 0;
|
|
//while (nextId < endId)
|
|
while (Compare(nextId, endId) < 0)
|
|
{
|
|
NormBlockId currentId = nextId;
|
|
Increment(nextId);
|
|
bool repairEntireBlock = repair_mask.Test(currentId.GetValue());
|
|
if (repairEntireBlock)
|
|
{
|
|
if (!blockCount) firstId = currentId;
|
|
blockCount++;
|
|
}
|
|
// Check for break in continuity or end
|
|
//if (blockCount && (!repairEntireBlock || (nextId >= endId)))
|
|
if (blockCount && (!repairEntireBlock || (Compare(nextId, endId) >= 0)))
|
|
{
|
|
NormRepairRequest::Form form;
|
|
switch (blockCount)
|
|
{
|
|
case 0:
|
|
form = NormRepairRequest::INVALID;
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
form = NormRepairRequest::ITEMS;
|
|
break;
|
|
default:
|
|
form = NormRepairRequest::RANGES;
|
|
break;
|
|
}
|
|
if (form != prevForm)
|
|
{
|
|
if (NormRepairRequest::INVALID != prevForm)
|
|
{
|
|
if (0 == cmd.PackRepairRequest(req))
|
|
{
|
|
PLOG(PL_ERROR, "NormObject::AppendRepairAdv() warning: full msg\n");
|
|
return requestAppended;
|
|
}
|
|
requestAppended = true;
|
|
}
|
|
cmd.AttachRepairRequest(req, segment_size); // (TBD) error check
|
|
req.SetForm(form);
|
|
prevForm = form;
|
|
}
|
|
switch (form)
|
|
{
|
|
case NormRepairRequest::INVALID:
|
|
ASSERT(0); // can't happen
|
|
break;
|
|
case NormRepairRequest::ITEMS:
|
|
req.AppendRepairItem(fec_id, fec_m, transport_id, firstId, GetBlockSize(firstId), 0);
|
|
if (2 == blockCount)
|
|
req.AppendRepairItem(fec_id, fec_m, transport_id, currentId, GetBlockSize(currentId), 0);
|
|
break;
|
|
case NormRepairRequest::RANGES:
|
|
req.AppendRepairRange(fec_id, fec_m, transport_id, firstId, GetBlockSize(firstId), 0,
|
|
transport_id, currentId, GetBlockSize(currentId), 0);
|
|
break;
|
|
case NormRepairRequest::ERASURES:
|
|
// erasure counts not used
|
|
break;
|
|
}
|
|
blockCount = 0;
|
|
}
|
|
if (!repairEntireBlock)
|
|
{
|
|
NormBlock* block = block_buffer.Find(currentId);
|
|
if (block && block->IsRepairPending())
|
|
{
|
|
if (NormRepairRequest::INVALID != prevForm)
|
|
{
|
|
if (0 == cmd.PackRepairRequest(req))
|
|
{
|
|
PLOG(PL_ERROR, "NormObject::AppendRepairAdv() warning: full msg\n");
|
|
return requestAppended;
|
|
}
|
|
prevForm = NormRepairRequest::INVALID;
|
|
}
|
|
if (block->AppendRepairAdv(cmd, transport_id, repair_info, fec_id, fec_m, GetBlockSize(currentId), segment_size))
|
|
requestAppended = true;
|
|
else
|
|
return requestAppended;
|
|
}
|
|
}
|
|
} // end while(nextId < endId)
|
|
if (NormRepairRequest::INVALID != prevForm)
|
|
{
|
|
if (0 == cmd.PackRepairRequest(req))
|
|
{
|
|
PLOG(PL_ERROR, "NormObject::AppendRepairAdv() warning: full msg\n");
|
|
return requestAppended;
|
|
}
|
|
requestAppended = true;
|
|
}
|
|
else if (repair_info && !requestAppended)
|
|
{
|
|
// make the "req" an INFO-only request
|
|
req.ClearFlag(NormRepairRequest::BLOCK);
|
|
req.SetForm(NormRepairRequest::ITEMS);
|
|
req.AppendRepairItem(fec_id, fec_m, transport_id, 0, 0, 0);
|
|
if (0 == cmd.PackRepairRequest(req))
|
|
{
|
|
PLOG(PL_ERROR, "NormObject::AppendRepairAdv() warning: full msg\n");
|
|
return requestAppended;
|
|
}
|
|
}
|
|
return requestAppended;
|
|
} // end NormObject::AppendRepairAdv()
|
|
|
|
// This is used by sender (only) for watermark check
|
|
bool NormObject::FindRepairIndex(NormBlockId& blockId, NormSegmentId& segmentId)
|
|
{
|
|
//ASSERT(NULL == sender);
|
|
if (repair_info)
|
|
{
|
|
blockId = 0;
|
|
segmentId = 0;
|
|
return true;
|
|
}
|
|
// TBD - Can we make this more efficient for large block_buffer sizes
|
|
// (e.g., by maintaining a separate bitmask for blocks needing partial repair)
|
|
NormBlockBuffer::Iterator iterator(block_buffer);
|
|
NormBlock* block;
|
|
while ((block = iterator.GetNextBlock()))
|
|
if (block->IsRepairPending()) break;
|
|
if (GetFirstRepair(blockId))
|
|
{
|
|
//if (!block || (blockId <= block->GetId()))
|
|
if ((NULL == block) || (Compare(blockId, block->GetId()) <= 0))
|
|
{
|
|
segmentId = 0;
|
|
return true;
|
|
}
|
|
}
|
|
if (block)
|
|
{
|
|
block->GetFirstRepair(segmentId);
|
|
// The segmentId must < block length for watermarks
|
|
if (segmentId >= GetBlockSize(block->GetId()))
|
|
segmentId = GetBlockSize(block->GetId()) - 1;
|
|
return true;
|
|
}
|
|
return false;
|
|
} // end NormObject::FindRepairIndex()
|
|
|
|
|
|
// Called by sender only
|
|
bool NormObject::IsRepairPending()
|
|
{
|
|
//ASSERT(NULL == sender);
|
|
if (repair_info) return true;
|
|
if (repair_mask.IsSet()) return true;
|
|
// TBD - Can we make this more efficient for large block_buffer sizes
|
|
// (e.g., by maintaining a separate bitmask for blocks needing partial repair)
|
|
NormBlockBuffer::Iterator iterator(block_buffer);
|
|
NormBlock* block;
|
|
while ((block = iterator.GetNextBlock()))
|
|
{
|
|
if (block->IsRepairPending()) return true;
|
|
}
|
|
return false;
|
|
} // end NormObject::IsRepairPending()
|
|
|
|
|
|
bool NormObject::IsPending(bool flush) const
|
|
{
|
|
if (pending_info) return true;
|
|
if (flush)
|
|
{
|
|
return pending_mask.IsSet();
|
|
}
|
|
else
|
|
{
|
|
// This should only be invoked by NORM receiver code
|
|
//ASSERT(NULL != sender);
|
|
NormBlockId firstId;
|
|
if (GetFirstPending(firstId))
|
|
{
|
|
//if (firstId < max_pending_block)
|
|
if (Compare(firstId, max_pending_block) < 0)
|
|
{
|
|
return true;
|
|
}
|
|
//else if (firstId > max_pending_block)
|
|
else if (Compare(firstId, max_pending_block) > 0)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (max_pending_segment > 0)
|
|
{
|
|
NormBlock* block = block_buffer.Find(max_pending_block);
|
|
if (block)
|
|
{
|
|
NormSegmentId firstPending = 0;
|
|
block->GetFirstPending(firstPending);
|
|
if (firstPending < max_pending_segment)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
} // end NormObject::IsPending()
|
|
|
|
// This is a "passive" THRU_SEGMENT repair check
|
|
// (used to for watermark ack check)
|
|
// return "true" if repair is pending up through blockId::segmentId
|
|
bool NormObject::PassiveRepairCheck(NormBlockId blockId,
|
|
NormSegmentId segmentId)
|
|
{
|
|
if (pending_info) return true;
|
|
NormBlockId firstPendingBlock;
|
|
if (GetFirstPending(firstPendingBlock))
|
|
{
|
|
//if (firstPendingBlock < blockId)
|
|
if (Compare(firstPendingBlock, blockId) < 0)
|
|
{
|
|
return true;
|
|
}
|
|
else if (firstPendingBlock == blockId)
|
|
{
|
|
NormBlock* block = block_buffer.Find(firstPendingBlock);
|
|
if (NULL != block)
|
|
{
|
|
NormSegmentId firstPendingSegment;
|
|
if (block->GetFirstPending(firstPendingSegment))
|
|
{
|
|
if (firstPendingSegment <= segmentId)
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(0); // this will never happen
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return true; // entire block was pending
|
|
}
|
|
}
|
|
}
|
|
// If it's a stream, make sure the app has read the data
|
|
if (IsStream())
|
|
return static_cast<NormStreamObject*>(this)->PassiveReadCheck(blockId, segmentId);
|
|
return false;
|
|
} // end NormObject::PassiveRepairCheck()
|
|
|
|
bool NormObject::ReceiverRepairCheck(CheckLevel level,
|
|
NormBlockId blockId,
|
|
NormSegmentId segmentId,
|
|
bool timerActive,
|
|
bool holdoffPhase)
|
|
{
|
|
// (TBD) make sure it's OK for "max_pending_segment"
|
|
// and "next_segment_id" to be > blockSize
|
|
// These block ids are the range (depending on CheckLevel)
|
|
// for which repair_masks need to reset for NACK suppression use
|
|
NormBlockId nextBlockId = 0;
|
|
NormBlockId endBlockId = 0;
|
|
bool thruObject = false;
|
|
bool startRepairTimer = false;
|
|
switch (level)
|
|
{
|
|
case BLIND_CHECK:
|
|
ASSERT(0); // should never occur
|
|
return false;
|
|
case TO_OBJECT:
|
|
return false;
|
|
case THRU_INFO:
|
|
if (timerActive)
|
|
{
|
|
if (!holdoffPhase)
|
|
{
|
|
current_block_id = 0;
|
|
next_segment_id = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pending_info)
|
|
startRepairTimer = true;
|
|
current_block_id = 0;
|
|
next_segment_id = 0;
|
|
}
|
|
// (TBD) set "thruObject" if info-only obj
|
|
break;
|
|
case TO_BLOCK:
|
|
//if (blockId >= max_pending_block)
|
|
if (Compare(blockId, max_pending_block) >= 0)
|
|
{
|
|
max_pending_block = blockId;
|
|
max_pending_segment = 0;
|
|
}
|
|
if (timerActive)
|
|
{
|
|
if (!holdoffPhase)
|
|
{
|
|
//if (blockId < current_block_id)
|
|
if (Compare(blockId, current_block_id) < 0)
|
|
{
|
|
current_block_id = blockId;
|
|
next_segment_id = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pending_info)
|
|
{
|
|
startRepairTimer = true;
|
|
// Cache pending blockId range for TO_BLOCK
|
|
GetFirstPending(nextBlockId);
|
|
endBlockId = blockId;
|
|
}
|
|
else
|
|
{
|
|
NormBlockId firstPending;
|
|
if (GetFirstPending(firstPending))
|
|
{
|
|
//if (firstPending < blockId)
|
|
if (Compare(firstPending, blockId) < 0)
|
|
{
|
|
startRepairTimer = true;
|
|
// Cache pending blockId range for TO_BLOCK
|
|
nextBlockId = firstPending;
|
|
endBlockId = blockId;
|
|
}
|
|
}
|
|
}
|
|
current_block_id = blockId;
|
|
next_segment_id = 0;
|
|
}
|
|
// (TBD) set "thruObject" if info-only obj
|
|
break;
|
|
case THRU_SEGMENT:
|
|
//if (blockId > max_pending_block)
|
|
if (Compare(blockId, max_pending_block) > 0)
|
|
{
|
|
max_pending_block = blockId;
|
|
max_pending_segment = segmentId + 1;
|
|
}
|
|
else if (blockId == max_pending_block)
|
|
{
|
|
if (segmentId >= max_pending_segment)
|
|
max_pending_segment = segmentId + 1;
|
|
}
|
|
if (!IsStream() && (blockId == final_block_id))
|
|
{
|
|
unsigned int finalSegment = GetBlockSize(blockId) - 1;
|
|
if (finalSegment <= segmentId)
|
|
thruObject = true;
|
|
}
|
|
if (timerActive)
|
|
{
|
|
if (!holdoffPhase)
|
|
{
|
|
//if (blockId < current_block_id)
|
|
if (Compare(blockId, current_block_id) < 0)
|
|
{
|
|
current_block_id = blockId;
|
|
next_segment_id = segmentId + 1;
|
|
}
|
|
else if (blockId == current_block_id)
|
|
{
|
|
if (segmentId < next_segment_id)
|
|
next_segment_id = segmentId + 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pending_info)
|
|
{
|
|
startRepairTimer = true;
|
|
// Cache pending blockId range for THRU_SEGMENT
|
|
GetFirstPending(nextBlockId);
|
|
endBlockId = blockId;
|
|
Increment(endBlockId);
|
|
}
|
|
else
|
|
{
|
|
NormBlockId firstPending;
|
|
if (GetFirstPending(firstPending))
|
|
{
|
|
//if (firstPending < blockId)
|
|
if (Compare(firstPending, blockId) < 0)
|
|
{
|
|
startRepairTimer = true;
|
|
}
|
|
else if (firstPending == blockId)
|
|
{
|
|
NormBlock* block = block_buffer.Find(blockId);
|
|
if (NULL != block)
|
|
{
|
|
ASSERT(block->IsPending());
|
|
NormSymbolId firstPendingSegment;
|
|
block->GetFirstPending(firstPendingSegment);
|
|
if (firstPendingSegment <= segmentId)
|
|
startRepairTimer = true;
|
|
}
|
|
else
|
|
{
|
|
startRepairTimer = true;
|
|
}
|
|
}
|
|
if (startRepairTimer)
|
|
{
|
|
// Cache pending blockId range for THRU_SEGMENT
|
|
nextBlockId = firstPending;
|
|
endBlockId = blockId;
|
|
Increment(endBlockId);
|
|
}
|
|
}
|
|
}
|
|
current_block_id = blockId;
|
|
next_segment_id = segmentId + 1;
|
|
}
|
|
break;
|
|
case THRU_BLOCK:
|
|
//if (blockId > max_pending_block)
|
|
if (Compare(blockId, max_pending_block) > 0)
|
|
{
|
|
max_pending_block = blockId;
|
|
max_pending_segment = GetBlockSize(blockId);
|
|
}
|
|
if (!IsStream() && (blockId == final_block_id))
|
|
thruObject = true;
|
|
if (timerActive)
|
|
{
|
|
if (!holdoffPhase)
|
|
{
|
|
//if (blockId < current_block_id)
|
|
if (Compare(blockId, current_block_id) < 0)
|
|
{
|
|
current_block_id = blockId;
|
|
next_segment_id = GetBlockSize(blockId);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pending_info)
|
|
{
|
|
startRepairTimer = true;
|
|
// Cache pending blockId range for THRU_BLOCK
|
|
GetFirstPending(nextBlockId);
|
|
endBlockId = blockId;
|
|
Increment(endBlockId);
|
|
}
|
|
else
|
|
{
|
|
NormBlockId firstPending;
|
|
if (GetFirstPending(firstPending))
|
|
{
|
|
//if (firstPending <= blockId)
|
|
if (Compare(firstPending, blockId) <= 0)
|
|
{
|
|
startRepairTimer = true;
|
|
// Cache pending blockId range for THRU_BLOCK
|
|
nextBlockId = firstPending;
|
|
endBlockId = blockId;
|
|
Increment(endBlockId);
|
|
}
|
|
}
|
|
}
|
|
current_block_id = blockId;
|
|
next_segment_id = GetBlockSize(blockId);
|
|
}
|
|
break;
|
|
case THRU_OBJECT:
|
|
if (!IsStream())
|
|
max_pending_block = final_block_id;
|
|
//else if (blockId > max_pending_block)
|
|
else if (Compare(blockId, max_pending_block) > 0)
|
|
max_pending_block = blockId;
|
|
max_pending_segment = GetBlockSize(max_pending_block);
|
|
thruObject = true;
|
|
if (!timerActive)
|
|
{
|
|
if (pending_info || pending_mask.IsSet())
|
|
{
|
|
startRepairTimer = true;
|
|
// Cache pending blockId range for THRU_OBJECT
|
|
GetFirstPending(nextBlockId);
|
|
GetLastPending(endBlockId);
|
|
Increment(endBlockId);
|
|
}
|
|
if (IsStream())
|
|
current_block_id = max_pending_block;
|
|
else
|
|
current_block_id = final_block_id;
|
|
next_segment_id = GetBlockSize(current_block_id);
|
|
}
|
|
break;
|
|
} // end switch (level)
|
|
|
|
// This special object abort is for silent receivers configured as "low delay"
|
|
// so they immediately deliver even partially completed objects (aborted) to
|
|
// the app as soon as a new object is begun or old object transmission completed
|
|
if (thruObject && session.RcvrIsLowDelay() && !IsStream())
|
|
{
|
|
sender->AbortObject(this);
|
|
return false;
|
|
}
|
|
if (startRepairTimer)
|
|
{
|
|
// Reset repair_info, NormObject::repair_mask and NormBlock::repair_masks
|
|
// (These are used for NACK suppression state accumulation and are reset
|
|
// here at the start of a fresh NACK cycle)
|
|
repair_info = false; // info repair mask
|
|
if (repair_mask.IsSet()) repair_mask.Clear();
|
|
//while (nextBlockId < endBlockId)
|
|
while (Compare(nextBlockId, endBlockId) < 0)
|
|
{
|
|
NormBlock* block = block_buffer.Find(nextBlockId);
|
|
if (NULL != block) block->ClearRepairs();
|
|
Increment(nextBlockId);
|
|
if (!GetNextPending(nextBlockId)) break;
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
} // end NormObject::ReceiverRepairCheck()
|
|
|
|
bool NormObject::ReceiverRewindCheck(NormBlockId blockId,
|
|
NormSegmentId segmentId)
|
|
{
|
|
//if (blockId > current_block_id)
|
|
if (Compare(blockId, current_block_id) > 0)
|
|
return false;
|
|
else if (blockId == current_block_id)
|
|
return ((segmentId+1) < next_segment_id);
|
|
else // if (blockId < current_block_id)
|
|
return true;
|
|
} // end NormObject::ReceiverRewindCheck()
|
|
|
|
// Note this partially clears "repair_mask" state
|
|
// Thus should only be called once at end of repair cycle
|
|
// (called on receiver repair_timer timeout)
|
|
// This uses the repair_mask that has accumulated state from
|
|
// oveheard NACKs or NORM_CMD(REPAIR_ADV) to possibly suppress NACKing
|
|
// up through the "current_block_id"
|
|
bool NormObject::IsRepairPending(bool flush)
|
|
{
|
|
// Only NORM receiver code should call this
|
|
if (pending_info && !repair_info) return true;
|
|
// Calculate repair_mask = pending_mask - repair_mask
|
|
repair_mask.XCopy(pending_mask);
|
|
NormBlockId nextId;
|
|
if (GetFirstRepair(nextId))
|
|
{
|
|
do
|
|
{
|
|
//if (!flush && (nextId > current_block_id)) break;
|
|
if (!flush && (Compare(nextId, current_block_id) > 0)) break;
|
|
NormBlock* block = block_buffer.Find(nextId);
|
|
if (block)
|
|
{
|
|
bool isPending;
|
|
UINT16 numData = GetBlockSize(nextId);
|
|
//if (flush || (nextId < current_block_id))
|
|
if (flush || (Compare(nextId, current_block_id) < 0))
|
|
{
|
|
isPending = block->IsRepairPending(numData, nparity);
|
|
}
|
|
else
|
|
{
|
|
if (next_segment_id < numData)
|
|
isPending = block->IsRepairPending(next_segment_id, 0);
|
|
else
|
|
isPending = block->IsRepairPending(numData, nparity);
|
|
}
|
|
if (isPending) return true;
|
|
}
|
|
else
|
|
{
|
|
return true; // We need the whole thing
|
|
}
|
|
Increment(nextId);
|
|
} while (GetNextRepair(nextId));
|
|
}
|
|
return false;
|
|
} // end NormObject::IsRepairPending()
|
|
|
|
bool NormObject::AppendRepairRequest(NormNackMsg& nack,
|
|
bool flush,
|
|
UINT16 payloadMax)
|
|
{
|
|
// If !flush, we request only up _to_ max_pending_block::max_pending_segment.
|
|
NormRepairRequest req;
|
|
bool requestAppended = false; // is set to true when content added to "nack"
|
|
NormRepairRequest::Form prevForm = NormRepairRequest::INVALID;
|
|
// First iterate over any pending blocks, appending any requests
|
|
NormBlockId nextId;
|
|
bool iterating = GetFirstPending(nextId);
|
|
NormBlockId prevId = nextId;
|
|
//iterating = iterating && (flush || (nextId <= max_pending_block));
|
|
iterating = iterating && (flush || (Compare(nextId, max_pending_block) <= 0));
|
|
UINT32 consecutiveCount = 0;
|
|
while (iterating || (0 != consecutiveCount))
|
|
{
|
|
NormBlockId lastId;
|
|
GetLastPending(lastId); // for debug output only
|
|
// Two PLOG() statements because one doesn't work right for some reason?!
|
|
if (PL_TRACE <= GetDebugLevel())
|
|
{
|
|
PLOG(PL_TRACE, "NormObject::AppendRepairRequest() node>%lu obj>%hu, blk>%lu->%lu ",
|
|
(unsigned long)LocalNodeId(), (UINT16)GetId(),
|
|
(unsigned long)nextId.GetValue(), (unsigned long)lastId.GetValue());
|
|
PLOG(PL_ALWAYS, "(maxPending = %lu)\n", (unsigned long)max_pending_block.GetValue());
|
|
}
|
|
bool appendRequest = false;
|
|
NormBlock* block = iterating ? block_buffer.Find(nextId) : NULL;
|
|
if (NULL != block)
|
|
appendRequest = true;
|
|
else if (iterating && ((UINT32)Difference(nextId, prevId) == consecutiveCount))
|
|
consecutiveCount++;
|
|
else
|
|
appendRequest = true;
|
|
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;
|
|
} // end switch(reqCount)
|
|
if (prevForm != nextForm)
|
|
{
|
|
if ((NormRepairRequest::INVALID != prevForm) &&
|
|
(NACK_NONE != nacking_mode))
|
|
{
|
|
if (0 == nack.PackRepairRequest(req))
|
|
{
|
|
PLOG(PL_WARN, "NormObject::AppendRepairRequest() warning: full NACK msg\n");
|
|
return requestAppended;
|
|
}
|
|
requestAppended = true;
|
|
}
|
|
if (NormRepairRequest::INVALID != nextForm)
|
|
{
|
|
nack.AttachRepairRequest(req, payloadMax);
|
|
req.SetForm(nextForm);
|
|
req.ResetFlags();
|
|
if (NACK_NORMAL == nacking_mode)
|
|
req.SetFlag(NormRepairRequest::BLOCK);
|
|
if (pending_info)
|
|
req.SetFlag(NormRepairRequest::INFO);
|
|
}
|
|
prevForm = nextForm;
|
|
}
|
|
if (NormRepairRequest::INVALID != nextForm)
|
|
PLOG(PL_TRACE, "NormObject::AppendRepairRequest() BLOCK request\n");
|
|
switch (nextForm)
|
|
{
|
|
case NormRepairRequest::ITEMS:
|
|
req.AppendRepairItem(fec_id, fec_m, transport_id, prevId, GetBlockSize(prevId), 0); // (TBD) error check
|
|
if (2 == consecutiveCount)
|
|
{
|
|
Increment(prevId);
|
|
req.AppendRepairItem(fec_id, fec_m, transport_id, prevId, GetBlockSize(prevId), 0); // (TBD) error check
|
|
}
|
|
break;
|
|
case NormRepairRequest::RANGES:
|
|
{
|
|
// lastId = prevId + consecutiveCount - 1
|
|
NormBlockId endId = prevId;
|
|
Increment(endId, consecutiveCount - 1);
|
|
req.AppendRepairRange(fec_id, fec_m,
|
|
transport_id, prevId, GetBlockSize(prevId), 0,
|
|
transport_id, endId, GetBlockSize(endId), 0); // (TBD) error check
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
} // end switch(nextForm)
|
|
if (NULL != block)
|
|
{
|
|
// Note our NACK construction is limited by "max_pending_block:max_pending_segment"
|
|
// based on most recent transmissions from sender
|
|
bool blockIsPending = false;
|
|
if (nextId == max_pending_block)
|
|
{
|
|
ASSERT(block->IsPending());
|
|
NormSymbolId firstPending;
|
|
block->GetFirstPending(firstPending);
|
|
if (firstPending < max_pending_segment) blockIsPending = true;
|
|
}
|
|
else
|
|
{
|
|
blockIsPending = true;
|
|
}
|
|
if (blockIsPending && (NACK_NONE != nacking_mode))
|
|
{
|
|
UINT16 numData = GetBlockSize(nextId);
|
|
if (NormRepairRequest::INVALID != prevForm)
|
|
{
|
|
if (0 == nack.PackRepairRequest(req))
|
|
{
|
|
PLOG(PL_WARN, "NormObject::AppendRepairRequest() warning: full NACK msg\n");
|
|
return requestAppended;
|
|
}
|
|
requestAppended = true;
|
|
}
|
|
bool blockRequestAppended;
|
|
if (flush || (nextId != max_pending_block))
|
|
{
|
|
blockRequestAppended =
|
|
block->AppendRepairRequest(nack, fec_id, fec_m, numData, nparity,
|
|
transport_id, pending_info, payloadMax);
|
|
}
|
|
else
|
|
{
|
|
if (max_pending_segment < numData)
|
|
{
|
|
blockRequestAppended =
|
|
block->AppendRepairRequest(nack, fec_id, fec_m, max_pending_segment, 0,
|
|
transport_id, pending_info, payloadMax);
|
|
}
|
|
else
|
|
{
|
|
blockRequestAppended =
|
|
block->AppendRepairRequest(nack, fec_id, fec_m, numData, nparity,
|
|
transport_id, pending_info, payloadMax);
|
|
}
|
|
}
|
|
if (blockRequestAppended)
|
|
requestAppended = true;
|
|
else // Must have filled NACK message
|
|
return requestAppended;
|
|
prevForm = NormRepairRequest::INVALID;
|
|
}
|
|
consecutiveCount = 0;
|
|
}
|
|
else if (iterating)
|
|
{
|
|
consecutiveCount = 1;
|
|
}
|
|
else
|
|
{
|
|
consecutiveCount = 0; // we're all done
|
|
}
|
|
prevId = nextId;
|
|
} // end if (appendRequest)
|
|
Increment(nextId);
|
|
iterating = GetNextPending(nextId);
|
|
//iterating = iterating && (flush || (nextId <= max_pending_block));
|
|
iterating = iterating && (flush || (Compare(nextId, max_pending_block) <= 0));
|
|
} // end while (iterating || (0 != consecutiveCount))
|
|
|
|
// This conditional makes sure any outstanding requests constructed
|
|
// are packed into the nack message.
|
|
if ((NormRepairRequest::INVALID != prevForm) && (NACK_NONE != nacking_mode))
|
|
{
|
|
if (0 == nack.PackRepairRequest(req))
|
|
{
|
|
PLOG(PL_WARN, "NormObject::AppendRepairRequest() warning: full NACK msg\n");
|
|
return requestAppended;
|
|
}
|
|
requestAppended = true;
|
|
prevForm = NormRepairRequest::INVALID;
|
|
}
|
|
if (!requestAppended && pending_info && (NACK_NONE != nacking_mode))
|
|
{
|
|
// INFO_ONLY repair request
|
|
nack.AttachRepairRequest(req, payloadMax);
|
|
req.SetForm(NormRepairRequest::ITEMS);
|
|
req.ResetFlags();
|
|
req.SetFlag(NormRepairRequest::INFO);
|
|
req.AppendRepairItem(fec_id, fec_m, transport_id, 0, 0, 0); // (TBD) error check
|
|
if (0 == nack.PackRepairRequest(req))
|
|
{
|
|
PLOG(PL_WARN, "NormObject::AppendRepairRequest() warning: full NACK msg\n");
|
|
return requestAppended;
|
|
}
|
|
requestAppended = true;
|
|
}
|
|
return requestAppended;
|
|
} // end NormObject::AppendRepairRequest()
|
|
|
|
|
|
void NormObject::HandleObjectMessage(const NormObjectMsg& msg,
|
|
NormMsg::Type msgType,
|
|
NormBlockId blockId,
|
|
NormSegmentId segmentId)
|
|
{
|
|
if (NormMsg::INFO == msgType)
|
|
{
|
|
if (pending_info)
|
|
{
|
|
const NormInfoMsg& infoMsg = (const NormInfoMsg&)msg;
|
|
info_len = infoMsg.GetInfoLen();
|
|
if (info_len > segment_size)
|
|
{
|
|
info_len = segment_size;
|
|
PLOG(PL_WARN, "NormObject::HandleObjectMessage() node>%lu sender>%lu obj>%hu "
|
|
"Warning! info too long.\n", (unsigned long)LocalNodeId(),
|
|
(unsigned long)sender->GetId(), (UINT16)transport_id);
|
|
}
|
|
else if (0 == info_len)
|
|
{
|
|
// NORM_INFO used to convey FTI only
|
|
if (NULL != info_ptr)
|
|
{
|
|
delete[] info_ptr;
|
|
info_ptr = NULL;
|
|
}
|
|
pending_info = false;
|
|
}
|
|
else
|
|
{
|
|
memcpy(info_ptr, infoMsg.GetInfo(), info_len);
|
|
pending_info = false;
|
|
session.Notify(NormController::RX_OBJECT_INFO, sender, this);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// (TBD) Verify info hasn't changed?
|
|
PLOG(PL_DEBUG, "NormObject::HandleObjectMessage() node>%lu sender>%lu obj>%hu "
|
|
"received duplicate info ...\n",
|
|
(unsigned long)LocalNodeId(), (unsigned long)sender->GetId(),
|
|
(UINT16)transport_id);
|
|
}
|
|
}
|
|
else // NORM_MSG_DATA
|
|
{
|
|
const NormDataMsg& data = (const NormDataMsg&)msg;
|
|
UINT16 numData = GetBlockSize(blockId);
|
|
|
|
// For stream objects, a little extra mgmt is required
|
|
NormStreamObject* stream = NULL;
|
|
if (STREAM == type)
|
|
{
|
|
stream = static_cast<NormStreamObject*>(this);
|
|
if (!stream->StreamUpdateStatus(blockId))
|
|
{
|
|
PLOG(PL_WARN, "NormObject::HandleObjectMessage() node:%lu sender:%lu obj>%hu blk>%lu "
|
|
"broken stream ...\n", (unsigned long)LocalNodeId(), (unsigned long)sender->GetId(),
|
|
(UINT16)transport_id, (unsigned long)blockId.GetValue());
|
|
sender->IncrementResyncCount();
|
|
while (!stream->StreamUpdateStatus(blockId))
|
|
{
|
|
// Sender is too far ahead of me ...
|
|
NormBlockId firstId;
|
|
if (GetFirstPending(firstId))
|
|
{
|
|
NormBlock* block = block_buffer.Find(firstId);
|
|
if (block)
|
|
{
|
|
block_buffer.Remove(block);
|
|
sender->PutFreeBlock(block);
|
|
}
|
|
pending_mask.Unset(firstId.GetValue());
|
|
}
|
|
else
|
|
{
|
|
// If we try to resync to too far back, we end up chasing our tail here
|
|
// so we just sync to the current block under these circumstances
|
|
stream->StreamResync(blockId); // - pending_mask.GetSize()/2);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (pending_mask.Test(blockId.GetValue()))
|
|
{
|
|
NormBlock* block = block_buffer.Find(blockId);
|
|
if (!block)
|
|
{
|
|
if (!(block = sender->GetFreeBlock(transport_id, blockId)))
|
|
{
|
|
//DMSG(2, "NormObject::HandleObjectMessage() node>%lu sender>%lu obj>%hu "
|
|
// "Warning! no free blocks ...\n", (unsigned long)LocalNodeId(), (unsigned long)
|
|
// sender->GetId(), (UINT16)transport_id);
|
|
return;
|
|
}
|
|
block->RxInit(blockId, numData, nparity);
|
|
block_buffer.Insert(block);
|
|
}
|
|
if (block->IsPending(segmentId))
|
|
{
|
|
UINT16 segmentLength = data.GetPayloadDataLength();
|
|
if (segmentLength > segment_size)
|
|
{
|
|
PLOG(PL_ERROR, "NormObject::HandleObjectMessage() node>%lu sender>%lu obj>%hu "
|
|
"Error! segment too large ...\n", (unsigned long)LocalNodeId(),
|
|
(unsigned long)sender->GetId(),(UINT16)transport_id);
|
|
return;
|
|
}
|
|
UINT16 payloadLength = data.GetPayloadLength();
|
|
UINT16 payloadMax = segment_size + NormDataMsg::GetStreamPayloadHeaderLength();
|
|
#ifdef SIMULATE
|
|
// For simulations, we may need to cap the payloadLength
|
|
payloadMax = MIN(payloadMax, SIM_PAYLOAD_MAX);
|
|
payloadLength = MIN(payloadLength, SIM_PAYLOAD_MAX);
|
|
#endif // SIMULATE
|
|
|
|
// Is this a source symbol or a parity symbol?
|
|
bool isSourceSymbol = (segmentId < numData);
|
|
|
|
// Try to cache segment in block buffer in case it's needed for decoding
|
|
char* segment = (!isSourceSymbol || !sender->SegmentPoolIsEmpty()) ?
|
|
sender->GetFreeSegment(transport_id, blockId) : NULL;
|
|
|
|
if (segment)
|
|
{
|
|
memcpy(segment, data.GetPayload(), payloadLength);
|
|
if (payloadLength < payloadMax)
|
|
memset(segment+payloadLength, 0, payloadMax-payloadLength);
|
|
block->AttachSegment(segmentId, segment);
|
|
}
|
|
else
|
|
{
|
|
//DMSG(2, "NormObject::HandleObjectMessage() node>%lu sender>%lu obj>%hu "
|
|
// "Warning! no free segments ...\n", (unsigned long)LocalNodeId(),
|
|
// (unsigned long)sender->GetId(), (UINT16)transport_id);
|
|
if (!isSourceSymbol) return;
|
|
}
|
|
block->UnsetPending(segmentId);
|
|
|
|
bool objectUpdated = false;
|
|
// 2) Write segment to object (if it's source symbol (data))
|
|
if (isSourceSymbol)
|
|
{
|
|
block->DecrementErasureCount();
|
|
if (WriteSegment(blockId, segmentId, data.GetPayload()))
|
|
{
|
|
objectUpdated = true;
|
|
// For statistics only (TBD) #ifdef NORM_DEBUG
|
|
sender->IncrementRecvGoodput(segmentLength);
|
|
}
|
|
else
|
|
{
|
|
if (IsStream())
|
|
PLOG(PL_DEBUG, "NormObject::HandleObjectMessage() WriteSegment() error\n");
|
|
else
|
|
PLOG(PL_ERROR, "NormObject::HandleObjectMessage() WriteSegment() error\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
block->IncrementParityCount();
|
|
}
|
|
|
|
// 3) Decode block if ready and return to pool
|
|
if (block->ErasureCount() <= block->ParityCount())
|
|
{
|
|
// Decode (if pending_mask.FirstSet() < numData)
|
|
// and write any decoded data segments to object
|
|
PLOG(PL_DETAIL, "NormObject::HandleObjectMessage() node>%lu sender>%lu obj>%hu blk>%lu "
|
|
"completed block ...\n", (unsigned long)LocalNodeId(),
|
|
(unsigned long)sender->GetId(), (UINT16)transport_id,
|
|
(unsigned long)block->GetId().GetValue());
|
|
UINT16 erasureCount = 0;
|
|
UINT16 nextErasure = 0;
|
|
UINT16 retrievalCount = 0;
|
|
if (block->GetFirstPending(nextErasure))
|
|
{
|
|
// Is the block missing _any_ source symbols?
|
|
if (nextErasure < numData)
|
|
{
|
|
// Use "NormObject::RetrieveSegment() method to "retrieve"
|
|
// source symbol segments already received which aren't still cached.
|
|
for (UINT16 nextSegment = 0; nextSegment < numData; nextSegment++)
|
|
{
|
|
if (block->IsPending(nextSegment))
|
|
{
|
|
sender->SetErasureLoc(erasureCount++, nextSegment);
|
|
segment = sender->GetRetrievalSegment();
|
|
ASSERT(NULL != segment);
|
|
UINT16 payloadMax = segment_size + NormDataMsg::GetStreamPayloadHeaderLength();
|
|
#ifdef SIMULATE
|
|
payloadMax = MIN(payloadMax, SIM_PAYLOAD_MAX);
|
|
#endif // SIMULATE
|
|
// Zeroize the missing segment payload in prep for decoding
|
|
memset(segment, 0, payloadMax);
|
|
sender->SetRetrievalLoc(retrievalCount++, nextSegment);
|
|
block->SetSegment(nextSegment, segment);
|
|
}
|
|
else if (!block->GetSegment(nextSegment))
|
|
{
|
|
if (!(segment = RetrieveSegment(blockId, nextSegment)))
|
|
{
|
|
// Stream objects should be the only ones that fail
|
|
// to retrieve segments (due to stream buffer size limit)
|
|
ASSERT(IsStream());
|
|
block->SetPending(nextSegment);
|
|
block->IncrementErasureCount();
|
|
// Clear block of any retrieval (temp) segments
|
|
for (UINT16 i = 0; i < retrievalCount; i++)
|
|
block->DetachSegment(sender->GetRetrievalLoc(i));
|
|
return;
|
|
}
|
|
sender->SetRetrievalLoc(retrievalCount++, nextSegment);
|
|
block->SetSegment(nextSegment, segment);
|
|
}
|
|
}
|
|
nextErasure = numData;
|
|
// Set erasure locs for any missing parity symbol segments, too!
|
|
while (block->GetNextPending(nextErasure))
|
|
sender->SetErasureLoc(erasureCount++, nextErasure++);
|
|
} // end if (nextErasure < numData)
|
|
} // end if (block->GetFirstPending(nextErasure))
|
|
|
|
if (erasureCount)
|
|
{
|
|
sender->Decode(block->SegmentList(), numData, erasureCount);
|
|
for (UINT16 i = 0; i < erasureCount; i++)
|
|
{
|
|
NormSegmentId sid = sender->GetErasureLoc(i);
|
|
if (sid < numData)
|
|
{
|
|
if (WriteSegment(blockId, sid, block->GetSegment(sid)))
|
|
{
|
|
objectUpdated = true;
|
|
// For statistics only (TBD) #ifdef NORM_DEBUG
|
|
// "segmentLength" is not necessarily correct here (TBD - fix this)
|
|
sender->IncrementRecvGoodput(segmentLength);
|
|
}
|
|
else
|
|
{
|
|
if (IsStream())
|
|
PLOG(PL_DEBUG, "NormObject::HandleObjectMessage() WriteSegment() error\n");
|
|
else
|
|
PLOG(PL_ERROR, "NormObject::HandleObjectMessage() WriteSegment() error\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Clear any temporarily retrieved segments for the block
|
|
for (UINT16 i = 0; i < retrievalCount; i++)
|
|
block->DetachSegment(sender->GetRetrievalLoc(i));
|
|
// OK, we're done with this block
|
|
pending_mask.Unset(blockId.GetValue());
|
|
block_buffer.Remove(block);
|
|
sender->PutFreeBlock(block);
|
|
} // if erasureCount <= parityCount (i.e., block complete)
|
|
// Notify application of new data available
|
|
// (TBD) this could be improved for stream objects
|
|
// so it's not called unnecessarily
|
|
if (objectUpdated && notify_on_update)
|
|
{
|
|
if ((NULL == stream) || stream->DetermineReadReadiness() || session.RcvrIsLowDelay())
|
|
{
|
|
notify_on_update = false;
|
|
session.Notify(NormController::RX_OBJECT_UPDATED, sender, this);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_DEBUG, "NormObject::HandleObjectMessage() node>%lu sender>%lu obj>%hu "
|
|
"received duplicate segment blk>%lu segment>%hu...\n", (unsigned long)LocalNodeId(),
|
|
(unsigned long)sender->GetId(), (UINT16)transport_id,(unsigned long)blockId.GetValue(),
|
|
segmentId);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_DEBUG, "NormObject::HandleObjectMessage() node>%lu sender>%lu obj>%hu "
|
|
"received duplicate block message blk>%lu ...\n", (unsigned long)LocalNodeId(),
|
|
(unsigned long)sender->GetId(), (UINT16)transport_id, (unsigned long)blockId.GetValue());
|
|
} // end if/else pending_mask.Test(blockId)
|
|
} // end if/else (NORM_MSG_INFO)
|
|
|
|
} // end NormObject::HandleObjectMessage()
|
|
|
|
// Returns source symbol segments to pool for ordinally _first_ block with such resources
|
|
bool NormObject::ReclaimSourceSegments(NormSegmentPool& segmentPool)
|
|
{
|
|
NormBlockBuffer::Iterator iterator(block_buffer);
|
|
NormBlock* block;
|
|
while ((block = iterator.GetNextBlock()))
|
|
{
|
|
bool reclaimed = false;
|
|
UINT16 numData = GetBlockSize(block->GetId());
|
|
for (UINT16 i = 0; i < numData; i++)
|
|
{
|
|
char* s = block->DetachSegment(i);
|
|
if (s)
|
|
{
|
|
segmentPool.Put(s);
|
|
reclaimed = true;
|
|
}
|
|
}
|
|
if (reclaimed) return true;
|
|
}
|
|
return false;
|
|
} // end NormObject::ReclaimSourceSegments()
|
|
|
|
|
|
// Steals non-pending block (ordinally _first_) for _sender_ resource management
|
|
// (optionally excludes block indicated by blockId)
|
|
NormBlock* NormObject::StealNonPendingBlock(bool excludeBlock, NormBlockId excludeId)
|
|
{
|
|
if (block_buffer.IsEmpty())
|
|
{
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
NormBlockBuffer::Iterator iterator(block_buffer);
|
|
NormBlock* block;
|
|
while ((block = iterator.GetNextBlock()))
|
|
{
|
|
NormBlockId bid = block->GetId();
|
|
if (block->IsTransmitPending() ||
|
|
pending_mask.Test(bid.GetValue()) ||
|
|
repair_mask.Test(bid.GetValue()) ||
|
|
(excludeBlock && (excludeId == bid)))
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
block_buffer.Remove(block);
|
|
return block;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
} // end NormObject::StealNonPendingBlock()
|
|
|
|
|
|
// For receiver & sender resource management, steals newer block resources when
|
|
// needing resources for ordinally older blocks.
|
|
NormBlock* NormObject::StealNewestBlock(bool excludeBlock, NormBlockId excludeId)
|
|
{
|
|
if (block_buffer.IsEmpty())
|
|
{
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
NormBlock* block = block_buffer.Find(block_buffer.RangeHi());
|
|
if (excludeBlock && (excludeId == block->GetId()))
|
|
{
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
block_buffer.Remove(block);
|
|
return block;
|
|
}
|
|
}
|
|
} // end NormObject::StealNewestBlock()
|
|
|
|
|
|
// For silent receiver resource management, steals older block resources when
|
|
// needing resources for ordinally newer blocks.
|
|
NormBlock* NormObject::StealOldestBlock(bool excludeBlock, NormBlockId excludeId)
|
|
{
|
|
if (block_buffer.IsEmpty())
|
|
{
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
NormBlock* block = block_buffer.Find(block_buffer.RangeLo());
|
|
if (excludeBlock && (excludeId == block->GetId()))
|
|
{
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
block_buffer.Remove(block);
|
|
return block;
|
|
}
|
|
}
|
|
} // end NormObject::StealOldestBlock()
|
|
|
|
bool NormObject::NextSenderMsg(NormObjectMsg* msg)
|
|
{
|
|
// Init() the message
|
|
if (pending_info)
|
|
{
|
|
NormInfoMsg* infoMsg = static_cast<NormInfoMsg*>(msg);
|
|
infoMsg->Init();
|
|
infoMsg->SetFecId(fec_id);
|
|
}
|
|
else
|
|
{
|
|
NormDataMsg* dataMsg = static_cast<NormDataMsg*>(msg);
|
|
dataMsg->Init();
|
|
dataMsg->SetFecId(fec_id); // we do this here so the base header length is properly set
|
|
}
|
|
|
|
// General object message flags
|
|
msg->ResetFlags();
|
|
switch(type)
|
|
{
|
|
case STREAM:
|
|
msg->SetFlag(NormObjectMsg::FLAG_STREAM);
|
|
break;
|
|
case FILE:
|
|
msg->SetFlag(NormObjectMsg::FLAG_FILE);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
NormSession::FtiMode ftiMode = session.SenderFtiMode();
|
|
if ((NULL != info_ptr) || (NormSession::FTI_INFO == ftiMode))
|
|
msg->SetFlag(NormObjectMsg::FLAG_INFO);
|
|
|
|
msg->SetObjectId(transport_id);
|
|
|
|
// Apply FTI extension as needed
|
|
if ((NormSession::FTI_ALWAYS == ftiMode) ||
|
|
(pending_info && (NormSession::FTI_INFO == ftiMode)))
|
|
{
|
|
switch (fec_id)
|
|
{
|
|
case 2:
|
|
{
|
|
NormFtiExtension2 fti;
|
|
msg->AttachExtension(fti);
|
|
fti.SetObjectSize(object_size);
|
|
fti.SetFecFieldSize(fec_m);
|
|
fti.SetFecGroupSize(1);
|
|
fti.SetSegmentSize(segment_size);
|
|
fti.SetFecMaxBlockLen(ndata);
|
|
fti.SetFecNumParity(nparity);
|
|
break;
|
|
}
|
|
case 5:
|
|
{
|
|
NormFtiExtension5 fti;
|
|
msg->AttachExtension(fti);
|
|
fti.SetObjectSize(object_size);
|
|
fti.SetSegmentSize(segment_size);
|
|
fti.SetFecMaxBlockLen((UINT8)ndata);
|
|
fti.SetFecNumParity((UINT8)nparity);
|
|
break;
|
|
}
|
|
case 129:
|
|
{
|
|
NormFtiExtension129 fti;
|
|
msg->AttachExtension(fti);
|
|
fti.SetObjectSize(object_size);
|
|
fti.SetFecInstanceId(0); // ZERO is for legacy MDP/NORM FEC encoder (TBD - use appropriate instanceId)
|
|
fti.SetSegmentSize(segment_size);
|
|
fti.SetFecMaxBlockLen(ndata);
|
|
fti.SetFecNumParity(nparity);
|
|
break;
|
|
}
|
|
default:
|
|
ASSERT(0);
|
|
return false;
|
|
}
|
|
}
|
|
if (pending_info)
|
|
{
|
|
// (TBD) set REPAIR_FLAG for retransmitted info
|
|
NormInfoMsg* infoMsg = static_cast<NormInfoMsg*>(msg);
|
|
infoMsg->SetInfo(info_ptr, info_len);
|
|
pending_info = false;
|
|
return true;
|
|
}
|
|
// This block gets the next pending block/segment
|
|
// (The loop handles NORM_OBJECT_STREAM advancement
|
|
// without the prior approach that used recursion)
|
|
NormDataMsg* data = static_cast<NormDataMsg*>(msg);
|
|
NormBlock* block = NULL;
|
|
NormBlockId blockId;
|
|
UINT16 numData = 0;
|
|
NormSegmentId segmentId;
|
|
bool squelchQueued = false;
|
|
while (NULL == block)
|
|
{
|
|
if (!GetFirstPending(blockId))
|
|
{
|
|
// Attempt to advance stream (probably had been repair-delayed)
|
|
if (IsStream())
|
|
{
|
|
if (static_cast<NormStreamObject*>(this)->StreamAdvance())
|
|
{
|
|
continue; // return NextSenderMsg(msg);
|
|
}
|
|
else
|
|
{
|
|
//ASSERT(IsRepairPending());
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_FATAL, "NormObject::NextSenderMsg() pending object w/ no pending blocks?!\n");
|
|
return false;
|
|
}
|
|
}
|
|
numData = GetBlockSize(blockId);
|
|
block = block_buffer.Find(blockId);
|
|
if (!block)
|
|
{
|
|
if (NULL == (block = session.SenderGetFreeBlock(transport_id, blockId)))
|
|
{
|
|
PLOG(PL_INFO, "NormObject::NextSenderMsg() node>%lu warning: sender resource "
|
|
"constrained (no free blocks).\n", (unsigned long)LocalNodeId());
|
|
return false;
|
|
}
|
|
// Load block with zero initialized parity segments
|
|
UINT16 totalBlockLen = numData + nparity;
|
|
for (UINT16 i = numData; i < totalBlockLen; i++)
|
|
{
|
|
char* s = session.SenderGetFreeSegment(transport_id, blockId);
|
|
if (s)
|
|
{
|
|
UINT16 payloadMax = segment_size + NormDataMsg::GetStreamPayloadHeaderLength();
|
|
#ifdef SIMULATE
|
|
payloadMax = MIN(payloadMax, SIM_PAYLOAD_MAX);
|
|
#endif // SIMULATE
|
|
memset(s, 0, payloadMax); // extra byte for msg flags
|
|
block->AttachSegment(i, s);
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_INFO, "NormObject::NextSenderMsg() node>%lu warning: sender resource "
|
|
"constrained (no free segments).\n", (unsigned long)LocalNodeId());
|
|
session.SenderPutFreeBlock(block);
|
|
return false;
|
|
}
|
|
}
|
|
block->TxInit(blockId, numData, session.SenderAutoParity());
|
|
//if (blockId < max_pending_block)
|
|
if (Compare(blockId, max_pending_block) < 0)
|
|
block->SetFlag(NormBlock::IN_REPAIR);
|
|
while (!block_buffer.Insert(block))
|
|
{
|
|
//ASSERT(STREAM == type);
|
|
//if (blockId > block_buffer.RangeLo())
|
|
if (Compare(blockId, block_buffer.RangeLo()) > 0)
|
|
{
|
|
NormBlock* lowBlock = block_buffer.Find(block_buffer.RangeLo());
|
|
NormBlockId lowBlockId = lowBlock->GetId();
|
|
bool push = static_cast<NormStreamObject*>(this)->GetPushMode();
|
|
if (!push && (lowBlock->IsRepairPending() || IsRepairSet(lowBlockId)))
|
|
{
|
|
// Pending repairs delaying stream advance
|
|
PLOG(PL_DEBUG, "NormObject::NextSenderMsg() node>%lu pending repairs delaying stream progress\n",
|
|
(unsigned long)LocalNodeId());
|
|
session.SenderPutFreeBlock(block);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Prune old non-pending block (or even pending if "push" enabled stream)
|
|
block_buffer.Remove(lowBlock);
|
|
repair_mask.Unset(lowBlockId.GetValue()); // just in case
|
|
pending_mask.Unset(lowBlockId.GetValue());
|
|
if (IsStream()) // always true
|
|
static_cast<NormStreamObject*>(this)->UnlockBlock(lowBlockId);
|
|
session.SenderPutFreeBlock(lowBlock);
|
|
continue;
|
|
}
|
|
}
|
|
else if (IsStream())
|
|
{
|
|
PLOG(PL_WARN, "NormObject::NextSenderMsg() node>%lu Warning! can't repair old stream block\n",
|
|
(unsigned long)LocalNodeId());
|
|
if (!squelchQueued)
|
|
{
|
|
session.SenderQueueSquelch(transport_id);
|
|
squelchQueued = true;
|
|
}
|
|
session.SenderPutFreeBlock(block);
|
|
repair_mask.Unset(blockId.GetValue()); // just in case
|
|
pending_mask.Unset(blockId.GetValue());
|
|
// Unlock (set to non-pending status) the corresponding stream_buffer block
|
|
static_cast<NormStreamObject*>(this)->UnlockBlock(blockId);
|
|
block = NULL;
|
|
break; //return NextSenderMsg(msg);
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_FATAL, "NormObject::NextSenderMsg() invalid non-stream state!\n");
|
|
ASSERT(0);
|
|
return false;
|
|
}
|
|
} // end while (!block_buffer.Insert())
|
|
if (NULL == block) continue;
|
|
} // end if (!block)
|
|
if (!block->GetFirstPending(segmentId))
|
|
{
|
|
PLOG(PL_ERROR, "NormObject::NextSenderMsg() warning: found pending block %lu with nothing pending!?\n",
|
|
(unsigned long)blockId.GetValue());
|
|
pending_mask.Unset(blockId.GetValue());
|
|
block = NULL;
|
|
continue; //return NextSenderMsg(msg);
|
|
}
|
|
// Try to read segment
|
|
if (segmentId < numData)
|
|
{
|
|
// Try to read data segment (Note "ReadSegment" copies in offset/length info also)
|
|
char* buffer = data->AccessPayload();
|
|
UINT16 payloadLength = ReadSegment(blockId, segmentId, buffer);
|
|
if (0 == payloadLength)
|
|
{
|
|
// (TBD) deal with read error
|
|
//(for streams, it currently means the stream is pending, but app hasn't yet written data)
|
|
if (!IsStream())
|
|
{
|
|
PLOG(PL_FATAL, "NormObject::NextSenderMsg() ReadSegment() error\n");
|
|
return false;
|
|
}
|
|
else if (static_cast<NormStreamObject*>(this)->IsOldBlock(blockId))
|
|
{
|
|
PLOG(PL_ERROR, "NormObject::NextSenderMsg() node>%lu Warning! can't repair old stream segment\n",
|
|
(unsigned long)LocalNodeId());
|
|
block->UnsetPending(segmentId);
|
|
if (!block->IsPending())
|
|
{
|
|
// End of old block reached
|
|
block->ResetParityCount(nparity);
|
|
pending_mask.Unset(blockId.GetValue());
|
|
// for EMCON sending, mark NORM_INFO for re-transmission, if applicable
|
|
if (session.SndrEmcon() && (HaveInfo() || (NormSession::FTI_INFO == session.SenderFtiMode())))
|
|
pending_info = true;
|
|
}
|
|
block = NULL;
|
|
continue; //return NextSenderMsg(msg);
|
|
}
|
|
else
|
|
{
|
|
// App hasn't written data for this block yet
|
|
return false;
|
|
}
|
|
}
|
|
data->SetPayloadLength(payloadLength);
|
|
|
|
// Perform incremental FEC encoding as needed
|
|
if ((block->ParityReadiness() == segmentId) && (0 != nparity))
|
|
// (TBD) && ((incrementalParity == true) || (auto_parity != 0))
|
|
{
|
|
// (TBD) for non-stream objects, catch alternate "last block/segment len"
|
|
// ZERO pad any "runt" segments before encoding
|
|
UINT16 payloadMax = segment_size + NormDataMsg::GetStreamPayloadHeaderLength();
|
|
#ifdef SIMULATE
|
|
payloadMax = MIN(payloadMax, SIM_PAYLOAD_MAX);
|
|
#endif // SIMULATE
|
|
if (payloadLength < payloadMax)
|
|
memset(buffer+payloadLength, 0, payloadMax-payloadLength);
|
|
// (TBD) the encode routine could update the block's parity readiness
|
|
block->UpdateSegSizeMax(payloadLength);
|
|
session.SenderEncode(segmentId, data->AccessPayload(), block->SegmentList(numData));
|
|
block->IncreaseParityReadiness();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!block->ParityReady(numData))
|
|
{
|
|
ASSERT(0 == block->ParityReadiness());
|
|
CalculateBlockParity(block);
|
|
}
|
|
char* segment = block->GetSegment(segmentId);
|
|
ASSERT(NULL != segment);
|
|
// We only need to send FEC content to cover the biggest segment
|
|
// sent for the block.
|
|
#ifdef SIMULATE
|
|
UINT16 payloadMax = MIN(block->GetSegSizeMax(), SIM_PAYLOAD_MAX);
|
|
data->SetPayload(segment, payloadMax);
|
|
data->SetPayloadLength(block->GetSegSizeMax()); // correct the msg length
|
|
#else
|
|
data->SetPayload(segment, block->GetSegSizeMax());
|
|
#endif // if/else SIMULATE
|
|
}
|
|
} // end while (NULL == block)
|
|
block->UnsetPending(segmentId);
|
|
//if (block->InRepair())
|
|
// data->SetFlag(NormObjectMsg::FLAG_REPAIR);
|
|
data->SetFecPayloadId(fec_id, blockId.GetValue(), segmentId, numData, fec_m);
|
|
if (!block->IsPending())
|
|
{
|
|
// End of block reached
|
|
block->ResetParityCount(nparity);
|
|
pending_mask.Unset(blockId.GetValue());
|
|
// for EMCON sending, mark NORM_INFO for re-transmission, if applicable
|
|
if (session.SndrEmcon() && (HaveInfo() || (NormSession::FTI_INFO == session.SenderFtiMode())))
|
|
pending_info = true;
|
|
// Advance sender use of "max_pending_block" so we always
|
|
// know when a block should be flagged as IN_REPAIR
|
|
if (blockId == max_pending_block)
|
|
Increment(max_pending_block);
|
|
}
|
|
|
|
// We update the object/block flow control timestamp to
|
|
// mark the "recent" activity for this object or block
|
|
if (session.GetFlowControl() > 0.0)
|
|
{
|
|
ProtoTime currentTime;
|
|
currentTime.GetCurrentTime();
|
|
SetLastNackTime(currentTime);
|
|
if (IsStream())
|
|
static_cast<NormStreamObject*>(this)->SetLastNackTime(blockId, currentTime);
|
|
}
|
|
|
|
if (!pending_mask.IsSet())
|
|
{
|
|
if (IsStream())
|
|
{
|
|
// This lets NORM_STREAM objects continue indefinitely
|
|
static_cast<NormStreamObject*>(this)->StreamAdvance();
|
|
// (TBD) Is there a case where posting TX_OBJECT_SENT for a stream
|
|
// makes sense? E.g., so we could use NormRequeueObject() for streams?
|
|
}
|
|
else if (first_pass)
|
|
{
|
|
// "First pass" transmission of object (and any auto parity) has completed
|
|
first_pass = false;
|
|
session.Notify(NormController::TX_OBJECT_SENT, NULL, this);
|
|
}
|
|
}
|
|
return true;
|
|
} // end NormObject::NextSenderMsg()
|
|
|
|
|
|
bool NormStreamObject::StreamAdvance()
|
|
{
|
|
// (TBD) should we make sure !pending_mask.IsSet()???
|
|
NormBlockId nextBlockId = stream_next_id;
|
|
// Make sure we won't prevent any pending repairs
|
|
if (repair_mask.CanSet(nextBlockId.GetValue()))
|
|
{
|
|
if (block_buffer.CanInsert(nextBlockId.GetValue()))
|
|
{
|
|
if (pending_mask.Set(nextBlockId.GetValue()))
|
|
{
|
|
Increment(stream_next_id);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_ERROR, "NormStreamObject::StreamAdvance() error: node>%lu couldn't set set stream pending mask (1)\n",
|
|
(unsigned long)LocalNodeId());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NormBlock* block = block_buffer.Find(block_buffer.RangeLo());
|
|
ASSERT(NULL != block);
|
|
if (!block->IsTransmitPending())
|
|
{
|
|
// ??? (TBD) Should this block be returned to the pool right now???
|
|
// especially for "push" enabled streams
|
|
if (pending_mask.Set(nextBlockId.GetValue()))
|
|
{
|
|
Increment(stream_next_id);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_ERROR, "NormStreamObject::StreamAdvance() error: node>%lu couldn't set stream pending mask (2)\n",
|
|
(unsigned long)LocalNodeId());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_DEBUG, "NormStreamObject::StreamAdvance() warning: node>%lu pending segment repairs (blk>%lu) "
|
|
"delaying stream advance ...\n", (unsigned long)LocalNodeId(),
|
|
(unsigned long)block->GetId().GetValue());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_WARN, "NormStreamObject::StreamAdvance() warning: node>%lu pending block repair delaying stream advance ...\n",
|
|
(unsigned long)LocalNodeId());
|
|
}
|
|
return false;
|
|
} // end NormStreamObject::StreamAdvance()
|
|
|
|
NormBlockId NormStreamObject::RepairWindowLo() const
|
|
{
|
|
NormBlockId blockId(0);
|
|
if (!stream_buffer.IsEmpty())
|
|
blockId = StreamBufferLo();
|
|
if (!block_buffer.IsEmpty())
|
|
{
|
|
NormBlockId rangeMin = block_buffer.RangeMin();
|
|
if (Compare(rangeMin, blockId) > 0)
|
|
blockId = rangeMin;
|
|
}
|
|
return blockId;
|
|
|
|
/*
|
|
// TBD - use block_buffer.range_hi information to compute
|
|
// (i.e. range_hi - range_max
|
|
while (!block_buffer.CanInsert(blockId))
|
|
{
|
|
Increment(blockId);
|
|
}
|
|
return blockId;
|
|
*/
|
|
} // end NormStreamObject::RepairWindowLo()
|
|
|
|
bool NormObject::CalculateBlockParity(NormBlock* block)
|
|
{
|
|
if (0 == nparity) return true;
|
|
char buffer[NormMsg::MAX_SIZE];
|
|
UINT16 numData = GetBlockSize(block->GetId());
|
|
for (UINT16 i = 0; i < numData; i++)
|
|
{
|
|
UINT16 payloadLength = ReadSegment(block->GetId(), i, buffer);
|
|
if (0 != payloadLength)
|
|
{
|
|
UINT16 payloadMax = segment_size+NormDataMsg::GetStreamPayloadHeaderLength();
|
|
#ifdef SIMULATE
|
|
payloadMax = MIN(payloadMax, SIM_PAYLOAD_MAX);
|
|
#endif // SIMULATE
|
|
if (payloadLength < payloadMax)
|
|
memset(buffer+payloadLength, 0, payloadMax-payloadLength+1);
|
|
block->UpdateSegSizeMax(payloadLength);
|
|
session.SenderEncode(i, buffer, block->SegmentList(numData));
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
block->SetParityReadiness(numData);
|
|
return true;
|
|
} // end NormObject::CalculateBlockParity()
|
|
|
|
NormBlock* NormObject::SenderRecoverBlock(NormBlockId blockId)
|
|
{
|
|
NormBlock* block = session.SenderGetFreeBlock(transport_id, blockId);
|
|
if (block)
|
|
{
|
|
UINT16 numData = GetBlockSize(blockId);
|
|
// Init block parameters
|
|
block->TxRecover(blockId, numData, nparity);
|
|
// Fill block with zero initialized parity segments
|
|
UINT16 totalBlockLen = numData + nparity;
|
|
for (UINT16 i = numData; i < totalBlockLen; i++)
|
|
{
|
|
char* s = session.SenderGetFreeSegment(transport_id, blockId);
|
|
if (s)
|
|
{
|
|
UINT16 payloadMax = segment_size + NormDataMsg::GetStreamPayloadHeaderLength();
|
|
#ifdef SIMULATE
|
|
payloadMax = MIN(payloadMax, SIM_PAYLOAD_MAX);
|
|
#endif // SIMULATE
|
|
memset(s, 0, payloadMax); // extra byte for msg flags
|
|
block->AttachSegment(i, s);
|
|
}
|
|
else
|
|
{
|
|
//DMSG(2, "NormObject::SenderRecoverBlock() node>%lu Warning! sender resource "
|
|
// "constrained (no free segments).\n", LocalNodeId());
|
|
session.SenderPutFreeBlock(block);
|
|
return (NormBlock*)NULL;
|
|
}
|
|
}
|
|
// Attempt to re-generate parity for the block
|
|
if (CalculateBlockParity(block))
|
|
{
|
|
if (!block_buffer.Insert(block))
|
|
{
|
|
session.SenderPutFreeBlock(block);
|
|
PLOG(PL_DEBUG, "NormObject::SenderRecoverBlock() node>%lu couldn't buffer recovered block\n",
|
|
(unsigned long)LocalNodeId());
|
|
return NULL;
|
|
}
|
|
return block;
|
|
}
|
|
else
|
|
{
|
|
session.SenderPutFreeBlock(block);
|
|
return (NormBlock*)NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//DMSG(2, "NormObject::SenderRecoverBlock() node>%lu Warning! sender resource "
|
|
// "constrained (no free blocks).\n", (unsigned long)LocalNodeId());
|
|
return (NormBlock*)NULL;
|
|
}
|
|
} // end NormObject::SenderRecoverBlock()
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// NormFileObject Implementation
|
|
//
|
|
NormFileObject::NormFileObject(class NormSession& theSession,
|
|
class NormSenderNode* theSender,
|
|
const NormObjectId& objectId)
|
|
: NormObject(FILE, theSession, theSender, objectId),
|
|
large_block_length(0), small_block_length(0)
|
|
{
|
|
path[0] = '\0';
|
|
}
|
|
|
|
NormFileObject::~NormFileObject()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
// Open file
|
|
bool NormFileObject::Open(const char* thePath,
|
|
const char* infoPtr,
|
|
UINT16 infoLen)
|
|
{
|
|
if (sender)
|
|
{
|
|
// We're receiving this file
|
|
if (NormFile::IsLocked(thePath))
|
|
{
|
|
PLOG(PL_FATAL, "NormFileObject::Open() Error trying to open locked file for recv!\n");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (file.Open(thePath, O_RDWR | O_CREAT | O_TRUNC))
|
|
{
|
|
if (!file.Lock())
|
|
PLOG(PL_WARN, "NormFileObject::Open() warning: NormFile::Lock() failure\n");
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_FATAL, "NormFileObject::Open() recv file.Open() error!\n");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Verify that it _is_ a file
|
|
if (NormFile::NORMAL != NormFile::GetType(thePath))
|
|
{
|
|
PLOG(PL_FATAL, "NormFileObject::Open() send file \"%s\" is not a file "
|
|
"(a directory perhaps?)\n", thePath);
|
|
return false;
|
|
}
|
|
|
|
// We're sending this file
|
|
if (file.Open(thePath, O_RDONLY))
|
|
{
|
|
NormObjectSize::Offset size = file.GetSize();
|
|
//if (size)
|
|
{
|
|
if (!NormObject::Open(NormObjectSize(size),
|
|
infoPtr,
|
|
infoLen,
|
|
session.SenderSegmentSize(),
|
|
session.GetSenderFecId(),
|
|
session.GetSenderFecFieldSize(),
|
|
session.SenderBlockSize(),
|
|
session.SenderNumParity()))
|
|
{
|
|
PLOG(PL_FATAL, "NormFileObject::Open() send object open error\n");
|
|
Close();
|
|
return false;
|
|
}
|
|
}
|
|
/*
|
|
else
|
|
{
|
|
PLOG(PL_FATAL, "NormFileObject::Open() send file.GetSize() error!\n");
|
|
file.Close();
|
|
return false;
|
|
}*/
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_FATAL, "NormFileObject::Open() send file.Open() error!\n");
|
|
return false;
|
|
}
|
|
}
|
|
large_block_length = NormObjectSize(large_block_size) * segment_size;
|
|
small_block_length = NormObjectSize(small_block_size) * segment_size;
|
|
strncpy(path, thePath, PATH_MAX);
|
|
size_t len = strlen(thePath);
|
|
len = MIN(len, PATH_MAX);
|
|
if (len < PATH_MAX) path[len] = '\0';
|
|
return true;
|
|
} // end NormFileObject::Open()
|
|
|
|
bool NormFileObject::Accept(const char* thePath)
|
|
{
|
|
if (Open(thePath))
|
|
{
|
|
NormObject::Accept();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
} // end NormFileObject::Accept()
|
|
|
|
void NormFileObject::CloseFile()
|
|
{
|
|
if (file.IsOpen())
|
|
{
|
|
if (NULL != sender) // we've been receiving this file
|
|
file.Unlock();
|
|
file.Close();
|
|
}
|
|
} // end NormFileObject::CloseFile()
|
|
|
|
void NormFileObject::Close()
|
|
{
|
|
CloseFile();
|
|
NormObject::Close();
|
|
} // end NormFileObject::Close()
|
|
|
|
bool NormFileObject::WriteSegment(NormBlockId blockId,
|
|
NormSegmentId segmentId,
|
|
const char* buffer)
|
|
{
|
|
size_t len;
|
|
if (blockId == final_block_id)
|
|
{
|
|
if (segmentId == (GetBlockSize(blockId)-1))
|
|
len = final_segment_size;
|
|
else
|
|
len = segment_size;
|
|
}
|
|
else
|
|
{
|
|
len = segment_size;
|
|
}
|
|
// Determine segment offset from blockId::segmentId
|
|
NormObjectSize segmentOffset;
|
|
NormObjectSize segmentSize = NormObjectSize(segment_size);
|
|
if (blockId.GetValue() < large_block_count)
|
|
{
|
|
segmentOffset = (large_block_length*blockId.GetValue()) + (segmentSize*segmentId);
|
|
}
|
|
else
|
|
{
|
|
segmentOffset = large_block_length*large_block_count; // (TBD) pre-calc this
|
|
UINT32 smallBlockIndex = blockId.GetValue() - large_block_count;
|
|
segmentOffset = segmentOffset + small_block_length*smallBlockIndex +
|
|
segmentSize*segmentId;
|
|
}
|
|
NormFile::Offset offset = segmentOffset.GetOffset();
|
|
if (offset != file.GetOffset())
|
|
{
|
|
if (!file.Seek(offset)) return false;
|
|
}
|
|
size_t nbytes = file.Write(buffer, len);
|
|
return (nbytes == len);
|
|
} // end NormFileObject::WriteSegment()
|
|
|
|
|
|
UINT16 NormFileObject::ReadSegment(NormBlockId blockId,
|
|
NormSegmentId segmentId,
|
|
char* buffer)
|
|
{
|
|
// Determine segment length from blockId::segmentId
|
|
size_t len;
|
|
if (blockId == final_block_id)
|
|
{
|
|
if (segmentId == (GetBlockSize(blockId) - 1))
|
|
len = final_segment_size;
|
|
else
|
|
len = segment_size;
|
|
}
|
|
else
|
|
{
|
|
len = segment_size;
|
|
}
|
|
|
|
// Determine segment offset from blockId::segmentId
|
|
NormObjectSize segmentOffset;
|
|
NormObjectSize segmentSize = NormObjectSize(segment_size);
|
|
if (blockId.GetValue() < large_block_count)
|
|
{
|
|
segmentOffset = large_block_length*blockId.GetValue() + segmentSize*segmentId;
|
|
}
|
|
else
|
|
{
|
|
segmentOffset = large_block_length*large_block_count; // (TBD) pre-calc this
|
|
UINT32 smallBlockIndex = blockId.GetValue() - large_block_count;
|
|
segmentOffset = segmentOffset + small_block_length*smallBlockIndex +
|
|
segmentSize*segmentId;
|
|
}
|
|
NormFile::Offset offset = segmentOffset.GetOffset();
|
|
if (offset != file.GetOffset())
|
|
{
|
|
if (!file.Seek(offset))
|
|
{
|
|
PLOG(PL_FATAL, "NormFileObject::ReadSegment() error seeking to file offset\n");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
size_t nbytes = file.Read(buffer, len);
|
|
|
|
if (len == nbytes)
|
|
return (UINT16)len;
|
|
else
|
|
return 0;
|
|
} // end NormFileObject::ReadSegment()
|
|
|
|
char* NormFileObject::RetrieveSegment(NormBlockId blockId,
|
|
NormSegmentId segmentId)
|
|
{
|
|
if (sender)
|
|
{
|
|
char* segment = sender->GetRetrievalSegment();
|
|
UINT16 len = ReadSegment(blockId, segmentId, segment);
|
|
if (len)
|
|
{
|
|
// zeroize remainder for proper decodes
|
|
if (len < segment_size)
|
|
memset(segment+len, 0, segment_size-len);
|
|
return segment;
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_FATAL, "NormFileObject::RetrieveSegment() error reading segment\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_FATAL, "NormFileObject::RetrieveSegment() error: NULL sender!\n");
|
|
return NULL;
|
|
}
|
|
} // end NormFileObject::RetrieveSegment()
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// NormDataObject Implementation
|
|
//
|
|
NormDataObject::NormDataObject(class NormSession& theSession,
|
|
class NormSenderNode* theSender,
|
|
const NormObjectId& objectId,
|
|
DataFreeFunctionHandle dataFreeFunc)
|
|
: NormObject(DATA, theSession, theSender, objectId),
|
|
large_block_length(0), small_block_length(0),
|
|
data_ptr(NULL), data_max(0), data_released(false),
|
|
data_free_func(dataFreeFunc)
|
|
{
|
|
|
|
}
|
|
|
|
NormDataObject::~NormDataObject()
|
|
{
|
|
Close();
|
|
if (data_released)
|
|
{
|
|
if (NULL != data_ptr)
|
|
{
|
|
if (NULL != data_free_func)
|
|
data_free_func(data_ptr);
|
|
else
|
|
delete[] data_ptr;
|
|
data_ptr = NULL;
|
|
}
|
|
data_released = false;
|
|
}
|
|
}
|
|
|
|
// Assign data object to data ptr
|
|
bool NormDataObject::Open(char* dataPtr,
|
|
UINT32 dataLen,
|
|
bool dataRelease,
|
|
const char* infoPtr,
|
|
UINT16 infoLen)
|
|
{
|
|
if (data_released && (NULL != data_ptr))
|
|
{
|
|
if (NULL != data_free_func)
|
|
data_free_func(data_ptr);
|
|
else
|
|
delete[] data_ptr;
|
|
data_ptr = NULL;
|
|
data_released = false;
|
|
}
|
|
if (NULL == sender)
|
|
{
|
|
// We're sending this data object
|
|
if (!NormObject::Open(dataLen,
|
|
infoPtr,
|
|
infoLen,
|
|
session.SenderSegmentSize(),
|
|
session.GetSenderFecId(),
|
|
session.GetSenderFecFieldSize(),
|
|
session.SenderBlockSize(),
|
|
session.SenderNumParity()))
|
|
{
|
|
PLOG(PL_FATAL, "NormDataObject::Open() send object open error\n");
|
|
Close();
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We're receiving this data object
|
|
//ASSERT(NULL == infoPtr);
|
|
}
|
|
data_ptr = dataPtr;
|
|
data_max = dataLen;
|
|
data_released = dataRelease;
|
|
large_block_length = NormObjectSize(large_block_size) * segment_size;
|
|
small_block_length = NormObjectSize(small_block_size) * segment_size;
|
|
return true;
|
|
} // end NormDataObject::Open()
|
|
|
|
bool NormDataObject::Accept(char* dataPtr, UINT32 dataMax, bool dataRelease)
|
|
{
|
|
if (Open(dataPtr, dataMax, dataRelease))
|
|
{
|
|
NormObject::Accept();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
} // end NormDataObject::Accept()
|
|
|
|
void NormDataObject::Close()
|
|
{
|
|
NormObject::Close();
|
|
} // end NormDataObject::Close()
|
|
|
|
bool NormDataObject::WriteSegment(NormBlockId blockId,
|
|
NormSegmentId segmentId,
|
|
const char* buffer)
|
|
{
|
|
if (NULL == data_ptr)
|
|
{
|
|
PLOG(PL_FATAL, "NormDataObject::WriteSegment() error: NULL data_ptr\n");
|
|
return false;
|
|
}
|
|
UINT16 len;
|
|
if (blockId == final_block_id)
|
|
{
|
|
if (segmentId == (GetBlockSize(blockId)-1))
|
|
len = final_segment_size;
|
|
else
|
|
len = segment_size;
|
|
}
|
|
else
|
|
{
|
|
len = segment_size;
|
|
}
|
|
// Determine segment offset from blockId::segmentId
|
|
NormObjectSize segmentOffset;
|
|
NormObjectSize segmentSize = NormObjectSize(segment_size);
|
|
if (blockId.GetValue() < large_block_count)
|
|
{
|
|
segmentOffset = large_block_length*blockId.GetValue() + segmentSize*segmentId;
|
|
}
|
|
else
|
|
{
|
|
segmentOffset = large_block_length*large_block_count; // (TBD) pre-calc this
|
|
UINT32 smallBlockIndex = blockId.GetValue() - large_block_count;
|
|
segmentOffset = segmentOffset + small_block_length*smallBlockIndex +
|
|
segmentSize*segmentId;
|
|
}
|
|
ASSERT(0 == segmentOffset.MSB()); // we don't yet support super-sized "data" objects
|
|
if (data_max <= segmentOffset.LSB())
|
|
return true;
|
|
else if (data_max <= (segmentOffset.LSB() + len))
|
|
len -= (segmentOffset.LSB() + len - data_max);
|
|
memcpy(data_ptr + segmentOffset.LSB(), buffer, len);
|
|
return true;
|
|
} // end NormDataObject::WriteSegment()
|
|
|
|
|
|
UINT16 NormDataObject::ReadSegment(NormBlockId blockId,
|
|
NormSegmentId segmentId,
|
|
char* buffer)
|
|
{
|
|
if (NULL == data_ptr)
|
|
{
|
|
PLOG(PL_FATAL, "NormDataObject::ReadSegment() error: NULL data_ptr\n");
|
|
return 0;
|
|
}
|
|
// Determine segment length from blockId::segmentId
|
|
UINT16 len;
|
|
if (blockId == final_block_id)
|
|
{
|
|
if (segmentId == (GetBlockSize(blockId)-1))
|
|
len = final_segment_size;
|
|
else
|
|
len = segment_size;
|
|
}
|
|
else
|
|
{
|
|
len = segment_size;
|
|
}
|
|
|
|
// Determine segment offset from blockId::segmentId
|
|
NormObjectSize segmentOffset;
|
|
NormObjectSize segmentSize = NormObjectSize(segment_size);
|
|
if (blockId.GetValue() < large_block_count)
|
|
{
|
|
segmentOffset = large_block_length*blockId.GetValue() + segmentSize*segmentId;
|
|
}
|
|
else
|
|
{
|
|
segmentOffset = large_block_length*large_block_count; // (TBD) pre-calc this
|
|
UINT32 smallBlockIndex = blockId.GetValue() - large_block_count;
|
|
segmentOffset = segmentOffset + small_block_length*smallBlockIndex +
|
|
segmentSize*segmentId;
|
|
}
|
|
ASSERT(0 == segmentOffset.MSB()); // we don't yet support super-sized "data" objects
|
|
if (data_max <= segmentOffset.LSB())
|
|
return 0;
|
|
else if (data_max <= (segmentOffset.LSB() + len))
|
|
len -= (segmentOffset.LSB() + len - data_max);
|
|
|
|
memcpy(buffer, data_ptr + segmentOffset.LSB(), len);
|
|
return len;
|
|
} // end NormDataObject::ReadSegment()
|
|
|
|
char* NormDataObject::RetrieveSegment(NormBlockId blockId,
|
|
NormSegmentId segmentId)
|
|
{
|
|
if (NULL == data_ptr)
|
|
{
|
|
PLOG(PL_FATAL, "NormDataObject::RetrieveSegment() error: NULL data_ptr\n");
|
|
return NULL;
|
|
}
|
|
// Determine segment length from blockId::segmentId
|
|
UINT16 len;
|
|
if (blockId == final_block_id)
|
|
{
|
|
if (segmentId == (GetBlockSize(blockId)-1))
|
|
len = final_segment_size;
|
|
else
|
|
len = segment_size;
|
|
}
|
|
else
|
|
{
|
|
len = segment_size;
|
|
}
|
|
// Determine segment offset from blockId::segmentId
|
|
NormObjectSize segmentOffset;
|
|
NormObjectSize segmentSize = NormObjectSize(segment_size);
|
|
if (blockId.GetValue() < large_block_count)
|
|
{
|
|
segmentOffset = large_block_length*blockId.GetValue() + segmentSize*segmentId;
|
|
}
|
|
else
|
|
{
|
|
segmentOffset = large_block_length*large_block_count; // (TBD) pre-calc this
|
|
UINT32 smallBlockIndex = blockId.GetValue() - large_block_count;
|
|
segmentOffset = segmentOffset + small_block_length*smallBlockIndex +
|
|
segmentSize*segmentId;
|
|
}
|
|
ASSERT(0 == segmentOffset.MSB()); // we don't yet support super-sized "data" objects
|
|
if ((len < segment_size) || (data_max < (segmentOffset.LSB() + len)))
|
|
{
|
|
if (sender)
|
|
{
|
|
char* segment = sender->GetRetrievalSegment();
|
|
len = ReadSegment(blockId, segmentId, segment);
|
|
memset(segment+len, 0, segment_size-len);
|
|
return segment;
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_FATAL, "NormDataObject::RetrieveSegment() error: NULL sender!\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return (data_ptr + segmentOffset.LSB());
|
|
}
|
|
} // end NormDataObject::RetrieveSegment()
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// NormStreamObject Implementation
|
|
//
|
|
|
|
NormStreamObject::NormStreamObject(class NormSession& theSession,
|
|
class NormSenderNode* theSender,
|
|
const NormObjectId& objectId)
|
|
: NormObject(STREAM, theSession, theSender, objectId),
|
|
stream_sync(false), write_vacancy(false),
|
|
read_init(true), read_ready(false),
|
|
flush_pending(false), msg_start(true),
|
|
flush_mode(FLUSH_NONE), push_mode(false),
|
|
stream_broken(false), stream_closing(false),
|
|
block_pool_threshold(0)
|
|
{
|
|
}
|
|
|
|
NormStreamObject::~NormStreamObject()
|
|
{
|
|
Close();
|
|
tx_offset = write_offset = read_offset = 0;
|
|
NormBlock* b;
|
|
while ((b = stream_buffer.Find(stream_buffer.RangeLo())))
|
|
{
|
|
stream_buffer.Remove(b);
|
|
b->EmptyToPool(segment_pool);
|
|
block_pool.Put(b);
|
|
}
|
|
stream_buffer.Destroy();
|
|
segment_pool.Destroy();
|
|
block_pool.Destroy();
|
|
}
|
|
|
|
NormBlockId NormStreamObject::FlushBlockId() const
|
|
{
|
|
if (0 != write_index.segment)
|
|
{
|
|
return write_index.block;
|
|
}
|
|
else
|
|
{
|
|
// blockId = write_index.block - 1
|
|
NormBlockId blockId = write_index.block;
|
|
Decrement(blockId);
|
|
return blockId;
|
|
}
|
|
} // end NormStreamObject::FlushBlockId()
|
|
|
|
bool NormStreamObject::Open(UINT32 bufferSize,
|
|
bool doubleBuffer,
|
|
const char* infoPtr,
|
|
UINT16 infoLen)
|
|
{
|
|
if (0 == bufferSize)
|
|
{
|
|
PLOG(PL_FATAL, "NormStreamObject::Open() zero bufferSize error\n");
|
|
return false;
|
|
}
|
|
UINT16 segmentSize, numData;
|
|
if (NULL != sender)
|
|
{
|
|
// receive streams have already beeen pre-opened via RxOpen()
|
|
segmentSize = segment_size;
|
|
numData = ndata;
|
|
}
|
|
else
|
|
{
|
|
segmentSize = session.SenderSegmentSize();
|
|
numData = session.SenderBlockSize();
|
|
stream_next_id = pending_mask.GetSize();
|
|
}
|
|
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 (NULL == sender)
|
|
{
|
|
// This is a tx stream object, open with adjusted "bufferSize"
|
|
if (!NormObject::Open(NormObjectSize((UINT32)bufferSize),
|
|
infoPtr,
|
|
infoLen,
|
|
session.SenderSegmentSize(),
|
|
session.GetSenderFecId(),
|
|
session.GetSenderFecFieldSize(),
|
|
session.SenderBlockSize(),
|
|
session.SenderNumParity()))
|
|
{
|
|
PLOG(PL_FATAL, "NormStreamObject::Open() object open error\n");
|
|
Close();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (doubleBuffer) numBlocks *= 2;
|
|
UINT32 numSegments = numBlocks * numData;
|
|
|
|
if (!block_pool.Init(numBlocks, numData))
|
|
{
|
|
PLOG(PL_FATAL, "NormStreamObject::Open() block_pool init error\n");
|
|
Close();
|
|
return false;
|
|
}
|
|
|
|
if (!segment_pool.Init(numSegments, segmentSize+NormDataMsg::GetStreamPayloadHeaderLength()))
|
|
{
|
|
PLOG(PL_FATAL, "NormStreamObject::Open() segment_pool init error\n");
|
|
Close();
|
|
return false;
|
|
}
|
|
|
|
if (!stream_buffer.Init(numBlocks, 256, fec_block_mask))
|
|
{
|
|
PLOG(PL_FATAL, "NormStreamObject::Open() stream_buffer init error\n");
|
|
Close();
|
|
return false;
|
|
}
|
|
// (TBD) we really only need one set of indexes & offset
|
|
// since our objects are exclusively read _or_ write
|
|
read_init = true;
|
|
|
|
read_index.block = read_index.segment = read_index.offset = 0;
|
|
write_index.block = write_index.segment = 0;
|
|
tx_index.block = tx_index.segment = 0;
|
|
tx_offset = write_offset = read_offset = 0;
|
|
write_vacancy = true;
|
|
stream_sync = false;
|
|
flush_pending = false;
|
|
msg_start = true;
|
|
stream_closing = false;
|
|
return true;
|
|
} // end NormStreamObject::Open()
|
|
|
|
bool NormStreamObject::Accept(UINT32 bufferSize, bool doubleBuffer)
|
|
{
|
|
if (Accepted()) return true; // was preset_stream (already pre-accepted)
|
|
if (Open(bufferSize, doubleBuffer))
|
|
{
|
|
NormObject::Accept();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
} // end NormStreamObject::Accept()
|
|
|
|
void NormStreamObject::Close(bool graceful)
|
|
{
|
|
if (graceful && (NULL == sender))
|
|
{
|
|
Terminate();
|
|
//SetFlushMode(FLUSH_ACTIVE);
|
|
//Flush();
|
|
}
|
|
else
|
|
{
|
|
NormObject::Close();
|
|
write_vacancy = false;
|
|
}
|
|
} // end NormStreamObject::Close()
|
|
|
|
bool NormStreamObject::LockBlocks(NormBlockId firstId, NormBlockId lastId, const ProtoTime& currentTime)
|
|
{
|
|
NormBlockId nextId = firstId;
|
|
// First check that we _can_ lock/reset them all
|
|
//while (nextId <= lastId)
|
|
while (Compare(nextId, lastId) <= 0)
|
|
{
|
|
NormBlock* block = stream_buffer.Find(nextId);
|
|
if (NULL == block) return false;
|
|
Increment(nextId);
|
|
}
|
|
nextId = firstId;
|
|
//while (nextId <= lastId)
|
|
while (Compare(nextId, lastId) <= 0)
|
|
{
|
|
NormBlock* block = stream_buffer.Find(nextId);
|
|
if (NULL != block)
|
|
{
|
|
UINT16 numData = GetBlockSize(nextId);
|
|
block->SetPending(0, numData); // TBD - should this reset auto-parity, too?
|
|
block->SetLastNackTime(currentTime);
|
|
}
|
|
Increment(nextId);
|
|
}
|
|
return true;
|
|
} // end NormStreamObject::LockBlocks()
|
|
|
|
void NormStreamObject::UnlockBlock(NormBlockId blockId)
|
|
{
|
|
NormBlock* block = stream_buffer.Find(blockId);
|
|
if (NULL != block) block->ClearPending();
|
|
} // end NormStreamObject::UnlockBlock()
|
|
|
|
|
|
bool NormStreamObject::LockSegments(NormBlockId blockId, NormSegmentId firstId, NormSegmentId lastId)
|
|
{
|
|
NormBlock* block = stream_buffer.Find(blockId);
|
|
if (block)
|
|
{
|
|
ASSERT(firstId <= lastId);
|
|
block->SetPending(firstId, (lastId - firstId + 1));
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
} // end NormStreamObject::LockSegments()
|
|
|
|
bool NormStreamObject::StreamUpdateStatus(NormBlockId blockId)
|
|
{
|
|
if (stream_sync)
|
|
{
|
|
//if (blockId < stream_sync_id)
|
|
if (Compare(blockId, stream_sync_id) < 0)
|
|
{
|
|
// it's an old block or stream is _very_ broken
|
|
// (nothing to update)
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
//if (blockId < stream_next_id)
|
|
if (Compare(blockId, stream_next_id) < 0)
|
|
{
|
|
// it's pending or complete (nothing to update)
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (pending_mask.IsSet())
|
|
{
|
|
if (pending_mask.CanSet(blockId.GetValue()))
|
|
{
|
|
UINT32 numBits = (UINT32)Difference(blockId, stream_next_id) + 1;
|
|
pending_mask.SetBits(stream_next_id.GetValue(), numBits);
|
|
//stream_next_id = blockId + 1;
|
|
stream_next_id = blockId;
|
|
Increment(stream_next_id);
|
|
// Handle potential stream_sync_id wrap
|
|
// Note: stream_next_id is always greater than stream_sync_id
|
|
UINT32 delta = (UINT32)Difference(stream_next_id, stream_sync_id);
|
|
if (delta > 2*pending_mask.GetSize())
|
|
GetFirstPending(stream_sync_id);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Stream broken
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UINT32 delta = (UINT32)Difference(blockId, stream_next_id) + 1;
|
|
if (delta > pending_mask.GetSize())
|
|
{
|
|
// Stream broken
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
pending_mask.SetBits(blockId.GetValue(), pending_mask.GetSize());
|
|
// stream_next_id = block_id + pending_mask.GetSize();
|
|
stream_next_id = blockId;
|
|
Increment(stream_next_id, pending_mask.GetSize());
|
|
// Handle potential stream_sync_id wrap
|
|
UINT32 delta = (UINT32)Difference(stream_next_id, stream_sync_id);
|
|
if (delta > (2*pending_mask.GetSize()))
|
|
GetFirstPending(stream_sync_id);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// For now, let stream begin anytime
|
|
// First, clean out block_buffer
|
|
NormBlock* block;
|
|
while (NULL != (block = block_buffer.Find(block_buffer.RangeLo())))
|
|
{
|
|
block_buffer.Remove(block);
|
|
sender->PutFreeBlock(block);
|
|
}
|
|
pending_mask.Clear();
|
|
|
|
pending_mask.SetBits(blockId.GetValue(), pending_mask.GetSize());
|
|
stream_sync = true;
|
|
stream_sync_id = blockId;
|
|
//stream_next_id = blockId + pending_mask.GetSize();
|
|
stream_next_id = blockId;
|
|
Increment(stream_next_id, pending_mask.GetSize());
|
|
if ((NULL != sender) && read_init)
|
|
{
|
|
// This is a fresh rx stream, so init the read indices
|
|
PLOG(PL_DEBUG, "NormStreamObject::StreamUpdateStatus() syncing stream to blockId: %lu\n",
|
|
(unsigned long)blockId.GetValue());
|
|
read_init = false;
|
|
read_index.block = blockId;
|
|
read_index.segment = 0;
|
|
read_index.offset = 0;
|
|
read_offset = 0;
|
|
sender->DecrementResyncCount(); // correction since stream sync here will falsely increment
|
|
if ((NormSenderNode::SYNC_CURRENT != sender->GetSyncPolicy()) &&
|
|
(0 != blockId.GetValue()))
|
|
{
|
|
stream_broken = true;
|
|
}
|
|
} // end if ((NULL != sender) && read_init)
|
|
|
|
// Since we're doing a resync including "read_init", dump any buffered data
|
|
// (TBD) this may not be necessary??? and is thus currently commented-out code
|
|
/*read_init = true;
|
|
NormBlock* block;
|
|
while (NULL != (block = stream_buffer.Find(stream_buffer.RangeLo())))
|
|
{
|
|
stream_buffer.Remove(block);
|
|
block->EmptyToPool(segment_pool);
|
|
block_pool.Put(block);
|
|
}*/
|
|
return true;
|
|
}
|
|
} // end NormStreamObject::StreamUpdateStatus()
|
|
|
|
|
|
char* NormStreamObject::RetrieveSegment(NormBlockId blockId,
|
|
NormSegmentId segmentId)
|
|
{
|
|
NormBlock* block = stream_buffer.Find(blockId);
|
|
if (!block)
|
|
{
|
|
PLOG(PL_FATAL, "NormStreamObject::RetrieveSegment() segment block unavailable\n");
|
|
return NULL;
|
|
}
|
|
char* segment = block->GetSegment(segmentId);
|
|
if (NULL == segment)
|
|
PLOG(PL_FATAL, "NormStreamObject::RetrieveSegment() segment unavailable\n");
|
|
return segment;
|
|
} // end NormStreamObject::RetrieveSegment()
|
|
|
|
UINT16 NormStreamObject::ReadSegment(NormBlockId blockId,
|
|
NormSegmentId segmentId,
|
|
char* buffer)
|
|
{
|
|
// (TBD) compare blockId with stream_buffer.RangeLo() and stream_buffer.RangeHi()
|
|
NormBlock* block = stream_buffer.Find(blockId);
|
|
if (NULL == block)
|
|
{
|
|
//DMSG(0, "NormStreamObject::ReadSegment() stream starved (1)\n");
|
|
//if (!stream_buffer.IsEmpty() && (blockId < stream_buffer.RangeLo()))
|
|
if (!stream_buffer.IsEmpty() && (Compare(blockId, stream_buffer.RangeLo()) < 0))
|
|
{
|
|
PLOG(PL_ERROR, "NormStreamObject::ReadSegment() error: attempted to read old block> %lu\n",
|
|
(unsigned long)blockId.GetValue());
|
|
}
|
|
return 0;
|
|
}
|
|
// (TBD) should we check to see if "blockId > write_index.block" ?
|
|
if ((blockId == write_index.block) && (segmentId >= write_index.segment))
|
|
{
|
|
//DMSG(0, "NormStreamObject::ReadSegment(blk>%lu seg>%hu) stream starved (2) (write_index>%lu:%hu)\n",
|
|
// (unsigned long)blockId.GetValue(), (UINT16)segmentId,
|
|
// (unsigned long)write_index.block.GetValue(), (UINT16)write_index.segment);
|
|
return 0;
|
|
}
|
|
block->UnsetPending(segmentId);
|
|
|
|
char* segment = block->GetSegment(segmentId);
|
|
ASSERT(segment != NULL);
|
|
|
|
// Update tx_offset if ((segmentOffset - tx_offset) > 0) (TBD) deprecate "tx_offset"
|
|
//UINT32 segmentOffset = NormDataMsg::ReadStreamPayloadOffset(segment);
|
|
//INT32 offsetDelta = segmentOffset - tx_offset;
|
|
//if (offsetDelta > 0) tx_offset = segmentOffset;
|
|
// Update tx_index if (blockId::segmentId > tx_index)
|
|
//if (blockId > tx_index.block)
|
|
if (Compare(blockId, tx_index.block) > 0)
|
|
{
|
|
tx_index.block = blockId;
|
|
tx_index.segment = segmentId;
|
|
}
|
|
else if ((blockId == tx_index.block) && (segmentId > tx_index.segment))
|
|
{
|
|
tx_index.segment = segmentId;
|
|
}
|
|
|
|
// Only advertise vacancy if stream_buffer.RangeLo() is non-pending _and_
|
|
// (write_index.block - tx_index.block) < block_pool.GetTotal() / 2
|
|
if (!write_vacancy)
|
|
{
|
|
//offsetDelta = write_offset - tx_offset;
|
|
//ASSERT(offsetDelta >= 0);
|
|
//if ((UINT32)offsetDelta < object_size.LSB())
|
|
//ASSERT(write_index.block >= tx_index.block);
|
|
ASSERT(Compare(write_index.block, tx_index.block) >= 0);
|
|
UINT32 blockDelta = (UINT32)Difference(write_index.block, tx_index.block);
|
|
if (blockDelta <= (block_pool.GetTotal() >> 1))
|
|
{
|
|
NormBlock* b = stream_buffer.Find(stream_buffer.RangeLo());
|
|
if (NULL != b)
|
|
{
|
|
if (!b->IsPending())
|
|
{
|
|
// make sure no recent nacking
|
|
double delay = session.GetFlowControlDelay() - b->GetNackAge();
|
|
if (delay < 1.0e-06)
|
|
|
|
{
|
|
if (session.FlowControlIsActive() && (session.GetFlowControlObject() == GetId()))
|
|
session.DeactivateFlowControl();
|
|
write_vacancy = true;
|
|
}
|
|
else
|
|
{
|
|
if (!session.FlowControlIsActive())
|
|
{
|
|
session.ActivateFlowControl(delay, GetId(), NormController::TX_QUEUE_VACANCY);
|
|
PLOG(PL_DEBUG, "NormStreamObject::ReadSegment() asserting flow control for stream (postedEmpty:%d)\n",
|
|
session.GetPostedTxQueueEmpty());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
write_vacancy = true;
|
|
}
|
|
if (write_vacancy)
|
|
session.Notify(NormController::TX_QUEUE_VACANCY, NULL, this);
|
|
}
|
|
}
|
|
|
|
UINT16 segmentLength = NormDataMsg::ReadStreamPayloadLength(segment);
|
|
ASSERT(segmentLength <= segment_size);
|
|
UINT16 payloadLength = segmentLength+NormDataMsg::GetStreamPayloadHeaderLength();
|
|
#ifdef SIMULATE
|
|
UINT16 payloadMax = segment_size + NormDataMsg::GetStreamPayloadHeaderLength();
|
|
payloadMax = MIN(payloadMax, SIM_PAYLOAD_MAX);
|
|
UINT16 copyMax = MIN(payloadMax, payloadLength);
|
|
memcpy(buffer, segment, copyMax);
|
|
#else
|
|
memcpy(buffer, segment, payloadLength);
|
|
#endif // SIMULATE
|
|
return payloadLength;
|
|
} // end NormStreamObject::ReadSegment()
|
|
|
|
bool NormStreamObject::WriteSegment(NormBlockId blockId,
|
|
NormSegmentId segmentId,
|
|
const char* segment)
|
|
{
|
|
//UINT32 segmentOffset = NormDataMsg::ReadStreamPayloadOffset(segment);
|
|
ASSERT(!read_init);
|
|
|
|
//if ((blockId < read_index.block) ||
|
|
if ((Compare(blockId, read_index.block) < 0) ||
|
|
((blockId == read_index.block) && (segmentId < read_index.segment)))
|
|
{
|
|
PLOG(PL_DEBUG, "NormStreamObject::WriteSegment() block/segment < read_index!?\n");
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
// if (segmentOffset < read_offset)
|
|
UINT32 diff = segmentOffset - read_offset;
|
|
if ((diff > 0x80000000) || ((0x80000000 == diff) && (segmentOffset > read_offset)))
|
|
{
|
|
PLOG(PL_DEBUG, "NormStreamObject::WriteSegment() diff:%lu segmentOffset:%lu < read_offset:%lu \n",
|
|
(unsigned long)diff, (unsigned long)segmentOffset, (unsigned long)read_offset);
|
|
return false;
|
|
}
|
|
*/
|
|
|
|
NormBlock* block = stream_buffer.Find(blockId);
|
|
if (NULL == block)
|
|
{
|
|
bool broken = false;
|
|
bool dataLost = false;
|
|
while (block_pool.IsEmpty() || !stream_buffer.CanInsert(blockId))
|
|
{
|
|
block = stream_buffer.Find(stream_buffer.RangeLo());
|
|
ASSERT(NULL != block);
|
|
//if (blockId < block->GetId())
|
|
if (Compare(blockId, block->GetId()) < 0)
|
|
{
|
|
PLOG(PL_DEBUG, "NormStreamObject::WriteSegment() blockId too old!?\n");
|
|
return false;
|
|
}
|
|
while (block->IsPending())
|
|
{
|
|
// Force read_index forward, giving app a chance to read data
|
|
NormSegmentId firstPending;
|
|
block->GetFirstPending(firstPending);
|
|
if (read_index.block != block->GetId())
|
|
{
|
|
// The block's id > read_index, so force read_index forward
|
|
read_index.block = block->GetId();
|
|
read_index.segment = firstPending;
|
|
read_index.offset = 0;
|
|
broken = true;
|
|
stream_broken = true;
|
|
}
|
|
if (read_index.segment < firstPending)
|
|
{
|
|
read_index.segment = firstPending;
|
|
read_index.offset = 0;
|
|
broken = true;
|
|
stream_broken = true;
|
|
}
|
|
NormBlock* tempBlock = block;
|
|
//UINT32 tempOffset = read_offset;
|
|
NormStreamObject::Index tempIndex = read_index;
|
|
// (TBD) uncomment the code so that only a single
|
|
// UPDATED notification is posted???
|
|
if (notify_on_update)
|
|
{
|
|
notify_on_update = false;
|
|
session.Notify(NormController::RX_OBJECT_UPDATED, sender, this);
|
|
}
|
|
block = stream_buffer.Find(stream_buffer.RangeLo());
|
|
if (tempBlock == block)
|
|
{
|
|
if (//(tempOffset == read_offset) &&
|
|
(tempIndex.block == read_index.block) &&
|
|
(tempIndex.segment == read_index.segment) &&
|
|
(tempIndex.offset == read_index.offset))
|
|
{
|
|
// App didn't grab data here, purge segment
|
|
dataLost = true;
|
|
broken = true;
|
|
stream_broken = true;
|
|
block->UnsetPending(read_index.segment++);
|
|
read_index.offset = 0;
|
|
if (read_index.segment >= ndata)
|
|
{
|
|
Increment(read_index.block);
|
|
read_index.segment = 0;
|
|
stream_buffer.Remove(block);
|
|
block->EmptyToPool(segment_pool);
|
|
block_pool.Put(block);
|
|
block = NULL;
|
|
Prune(read_index.block, false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// App consumed block via Read()
|
|
block = NULL;
|
|
break;
|
|
}
|
|
} // end while (block->IsPending())
|
|
if (NULL != block)
|
|
{
|
|
NormBlockId bid = block->GetId();
|
|
stream_buffer.Remove(block);
|
|
block->EmptyToPool(segment_pool);
|
|
block_pool.Put(block);
|
|
if (bid == read_index.block)
|
|
{
|
|
stream_broken = true;
|
|
broken = true;
|
|
Increment(read_index.block);
|
|
read_index.segment = 0;
|
|
read_index.offset = 0;
|
|
Prune(read_index.block, false);
|
|
}
|
|
}
|
|
} // end while (block_pool.IsEmpty() || !stream_buffer.CanInsert(blockId))
|
|
if (broken)
|
|
{
|
|
PLOG(PL_WARN, "NormStreamObject::WriteSegment() node>%lu obj>%hu blk>%lu seg>%hu broken stream ...\n",
|
|
(unsigned long)LocalNodeId(), (UINT16)transport_id,
|
|
(unsigned long)blockId.GetValue(), (UINT16)segmentId);
|
|
if (dataLost)
|
|
PLOG(PL_ERROR, "NormStreamObject::WriteSegment() broken stream data not read by app!\n");
|
|
}
|
|
block = block_pool.Get();
|
|
block->SetId(blockId);
|
|
block->ClearPending();
|
|
//ASSERT(blockId >= read_index.block);
|
|
ASSERT(Compare(blockId, read_index.block) >= 0);
|
|
bool success = stream_buffer.Insert(block);
|
|
ASSERT(success);
|
|
} // end if (!block)
|
|
|
|
// Make sure this segment hasn't already been written
|
|
if(!block->GetSegment(segmentId))
|
|
{
|
|
char* s = segment_pool.Get();
|
|
ASSERT(s != NULL); // for now, this should always succeed
|
|
UINT16 payloadLength = NormDataMsg::ReadStreamPayloadLength(segment) + NormDataMsg::GetStreamPayloadHeaderLength();
|
|
#ifdef SIMULATE
|
|
UINT16 payloadMax = segment_size + NormDataMsg::GetStreamPayloadHeaderLength();
|
|
payloadMax = MIN(SIM_PAYLOAD_MAX, payloadMax);
|
|
payloadLength = MIN(payloadMax, payloadLength);
|
|
#endif // SIMULATE
|
|
memcpy(s, segment, payloadLength);
|
|
block->AttachSegment(segmentId, s);
|
|
block->SetPending(segmentId);
|
|
|
|
if (!read_ready)
|
|
{
|
|
// Did this segment make our stream ready for reading
|
|
if ((blockId == read_index.block) && (segmentId == read_index.segment))
|
|
{
|
|
read_ready = true;
|
|
}
|
|
else if (block_pool.GetCount() < block_pool_threshold)
|
|
{
|
|
// We're starting to fill up receive stream buffer
|
|
// with alot of partially received coding blocks
|
|
read_ready = true;
|
|
}
|
|
else if (session.RcvrIsLowDelay())
|
|
{
|
|
INT32 delta = (UINT32)Difference(blockId, read_index.block);
|
|
if (delta > session.RcvrGetMaxDelay())
|
|
read_ready = true;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
} // end NormStreamObject::WriteSegment()
|
|
|
|
void NormStreamObject::Prune(NormBlockId blockId, bool updateStatus)
|
|
{
|
|
if (updateStatus || StreamUpdateStatus(blockId))
|
|
{
|
|
bool resync = false;
|
|
NormBlock* block;
|
|
while (NULL != (block = block_buffer.Find(block_buffer.RangeLo())))
|
|
{
|
|
//if (block->GetId() < blockId)
|
|
if (Compare(block->GetId(), blockId) < 0)
|
|
{
|
|
resync = true;
|
|
pending_mask.Unset(block->GetId().GetValue());
|
|
block_buffer.Remove(block);
|
|
sender->PutFreeBlock(block);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
NormBlockId firstId;
|
|
if (GetFirstPending(firstId))
|
|
{
|
|
//if (firstId < blockId)
|
|
if (Compare(firstId, blockId) < 0)
|
|
{
|
|
resync = true;
|
|
UINT32 numBits = (UINT32)Difference(blockId, firstId);
|
|
pending_mask.UnsetBits(firstId.GetValue(), numBits);
|
|
}
|
|
}
|
|
while (!StreamUpdateStatus(blockId))
|
|
{
|
|
// Sender is too far ahead of me ...
|
|
resync = true;
|
|
NormBlockId firstId;
|
|
if (GetFirstPending(firstId))
|
|
{
|
|
NormBlock* block = block_buffer.Find(firstId);
|
|
if (NULL != block)
|
|
{
|
|
block_buffer.Remove(block);
|
|
sender->PutFreeBlock(block);
|
|
}
|
|
pending_mask.Unset(firstId.GetValue());
|
|
}
|
|
else
|
|
{
|
|
StreamResync(blockId);
|
|
break;
|
|
}
|
|
}
|
|
if (resync)
|
|
{
|
|
//if (updateStatus && (read_index.block < blockId) && notify_on_update)
|
|
//if (updateStatus && notify_on_update && (Compare(read_index.block, blockId) < 0))
|
|
if (notify_on_update)
|
|
{
|
|
// If "updateStatus" is true, then Prune() was invoked due to SQUELCH
|
|
// This will prompt the app to read from the stream which, in turn
|
|
// will force the stream read_index forward
|
|
notify_on_update = false;
|
|
session.Notify(NormController::RX_OBJECT_UPDATED, sender, this);
|
|
}
|
|
sender->IncrementResyncCount();
|
|
}
|
|
}
|
|
} // end NormStreamObject::Prune()
|
|
|
|
// returns "true" if given blockId::segmentId has _not_ been read yet
|
|
// i.e., "true" if still pending reading by application
|
|
bool NormStreamObject::PassiveReadCheck(NormBlockId blockId, NormSegmentId segmentId)
|
|
{
|
|
bool result;
|
|
//if (read_index.block < blockId)
|
|
if (Compare(read_index.block, blockId) < 0)
|
|
result = true;
|
|
else if (read_index.block == blockId)
|
|
result = (read_index.segment <= segmentId);
|
|
else //if (read_index.block > blockId)
|
|
result = false;
|
|
/*if (result)
|
|
{
|
|
TRACE("NormStreamObject::PassiveReadCheck() %s\n", result ? "data not yet read by app" : "OK");
|
|
TRACE(" (read_index>%lu:%hu check>%lu:%hu usage:%u)\n",
|
|
(unsigned long)read_index.block.GetValue(), (UINT16)read_index.segment,
|
|
(unsigned long)blockId.GetValue(), (UINT16)segmentId, GetCurrentBufferUsage());
|
|
}
|
|
*/
|
|
return result;
|
|
} // end NormStreamObject::PassiveReadCheck()
|
|
|
|
bool NormStreamObject::Read(char* buffer, unsigned int* buflen, bool seekMsgStart)
|
|
{
|
|
// NOTES: ReadPrivate() always returns "true" if it reads any bytes
|
|
// It returns "false" upon a stream break.
|
|
// To properly reset "update" notification, it needs to
|
|
// be called until it passes back a read byte count of zero.
|
|
// So, upon read byte count < bytesWanted, we add an extra
|
|
// call to ReadPrivate() to do that. I.e., so the "update"
|
|
// notification is reset upon a short read count. The
|
|
// "stream_broken" state variable is used for when this
|
|
// extra ReadPrivate() call reveals the broken stream condition.
|
|
if (stream_broken && !seekMsgStart)
|
|
{
|
|
if (NULL != buflen) *buflen = 0;
|
|
stream_broken = false;
|
|
return false;
|
|
}
|
|
unsigned int bytesWanted;
|
|
if (NULL == buflen)
|
|
{
|
|
bytesWanted = 0;
|
|
buflen = &bytesWanted;
|
|
}
|
|
else
|
|
{
|
|
bytesWanted = *buflen;
|
|
}
|
|
bool result = ReadPrivate(buffer, buflen, seekMsgStart);
|
|
if (!read_ready)
|
|
{
|
|
notify_on_update = true;
|
|
}
|
|
if (!seekMsgStart && result && (0 != *buflen) && (*buflen < bytesWanted))
|
|
{
|
|
char dummyBuffer[8];
|
|
unsigned int dummyCount = 8;
|
|
stream_broken = ReadPrivate(dummyBuffer, &dummyCount, false) ? false : true;
|
|
ASSERT(0 == dummyCount);
|
|
if (!read_ready)
|
|
{
|
|
notify_on_update = true;
|
|
}
|
|
}
|
|
return result;
|
|
} // end NormStreamObject::Read()
|
|
|
|
|
|
// Sequential (in order) read/write routines (TBD) Add a "Seek()" method
|
|
bool NormStreamObject::ReadPrivate(char* buffer, unsigned int* buflen, bool seekMsgStart)
|
|
{
|
|
if (stream_closing || read_init)
|
|
{
|
|
if (stream_closing)
|
|
PLOG(PL_DEBUG, "NormStreamObject::Read() attempted to read from closed stream\n");
|
|
*buflen = 0;
|
|
return seekMsgStart ? false : true;
|
|
}
|
|
Retain();
|
|
unsigned int bytesRead = 0;
|
|
unsigned int bytesToRead = *buflen;
|
|
bool brokenStream = false;
|
|
do
|
|
{
|
|
NormBlock* block = stream_buffer.Find(read_index.block);
|
|
if (NULL == block)
|
|
{
|
|
PLOG(PL_DETAIL, "NormStreamObject::ReadPrivate() stream buffer empty (1) (sbEmpty:%d)\n", stream_buffer.IsEmpty());
|
|
read_ready = false;
|
|
*buflen = bytesRead;
|
|
if (bytesRead > 0)
|
|
{
|
|
Release();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
bool forceForward = false;
|
|
if (block_pool.GetCount() < block_pool_threshold)
|
|
{
|
|
// We're starting to fill up receive stream buffer
|
|
// with alot of partially received coding blocks
|
|
forceForward = true;
|
|
}
|
|
else if (session.RcvrIsLowDelay())
|
|
{
|
|
// Has the sender moved forward to the next FEC blocks
|
|
if (Compare(max_pending_block, read_index.block) >= 0)
|
|
{
|
|
INT32 delta = (INT32)Difference(max_pending_block, read_index.block);
|
|
if (delta > session.RcvrGetMaxDelay())
|
|
{
|
|
forceForward = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NormBlockId firstPending;
|
|
if (GetFirstPending(firstPending))
|
|
{
|
|
//if (read_index.block < firstPending)
|
|
if (Compare(read_index.block, firstPending) < 0)
|
|
forceForward = true;
|
|
}
|
|
}
|
|
if (forceForward)
|
|
{
|
|
Increment(read_index.block);
|
|
read_index.segment = 0;
|
|
read_index.offset = 0;
|
|
if (!seekMsgStart) brokenStream = true;
|
|
Prune(read_index.block, false);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
Release();
|
|
return (seekMsgStart || brokenStream) ? false : true;
|
|
}
|
|
}
|
|
}
|
|
char* segment = block->GetSegment(read_index.segment);
|
|
|
|
ASSERT((NULL == segment) || block->IsPending(read_index.segment));
|
|
|
|
if (NULL == segment)
|
|
{
|
|
PLOG(PL_DETAIL, "NormStreamObject::ReadPrivate(%lu:%hu) stream buffer empty (read_offset>%lu) (2)\n",
|
|
(unsigned long)read_index.block.GetValue(), read_index.segment, (unsigned long)read_offset);
|
|
read_ready = false;
|
|
*buflen = bytesRead;
|
|
if (bytesRead > 0)
|
|
{
|
|
Release();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
bool forceForward = false;
|
|
if (block_pool.GetCount() < block_pool_threshold)
|
|
{
|
|
// We're starting to fill up receive stream buffer
|
|
// with alot of partially received coding blocks
|
|
forceForward = true;
|
|
}
|
|
else if (session.RcvrIsLowDelay())
|
|
{
|
|
// Has the sender moved forward to the next FEC block
|
|
if (Compare(max_pending_block, read_index.block) >= 0)
|
|
{
|
|
INT32 delta = (UINT32)Difference(max_pending_block, read_index.block);
|
|
if (delta > session.RcvrGetMaxDelay())
|
|
forceForward = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NormBlockId firstPending;
|
|
if (GetFirstPending(firstPending))
|
|
{
|
|
//if (read_index.block < firstPending)
|
|
if (Compare(read_index.block, firstPending) < 0)
|
|
forceForward = true;
|
|
}
|
|
}
|
|
if (forceForward)
|
|
{
|
|
// Force read_index forward and try again if seeking msg start
|
|
if (++read_index.segment >= ndata)
|
|
{
|
|
stream_buffer.Remove(block);
|
|
block->EmptyToPool(segment_pool);
|
|
block_pool.Put(block);
|
|
Increment(read_index.block);
|
|
read_index.segment = 0;
|
|
read_index.offset = 0;
|
|
Prune(read_index.block, false); // prevents repair requests for data we
|
|
} // no longer care about (i.e, prior to this block)
|
|
if (!seekMsgStart) brokenStream = true;
|
|
continue; // attempt to find next valid read_index beyond break
|
|
}
|
|
else
|
|
{
|
|
Release();
|
|
return (seekMsgStart || brokenStream) ? false : true;
|
|
}
|
|
}
|
|
} // end if (NULL = segment)
|
|
ASSERT(NULL != segment);
|
|
read_ready = true;
|
|
if (brokenStream)
|
|
{
|
|
// We have found the next valid read_index and block/segment but stream
|
|
// continuity was broken so report immediately by returning false
|
|
Release();
|
|
return false;
|
|
}
|
|
|
|
UINT16 length = NormDataMsg::ReadStreamPayloadLength(segment);
|
|
if (0 == length)
|
|
{
|
|
// It's a "stream control" message, so interpret "payload_msg_start" as control code
|
|
switch (NormDataMsg::ReadStreamPayloadMsgStart(segment))
|
|
{
|
|
case 0:
|
|
// This is the "stream end" control code
|
|
break;
|
|
default:
|
|
// Invalid stream control code, skip this invalid segment
|
|
PLOG(PL_ERROR, "NormStreamObject::ReadPrivate() invalid stream control message\n");
|
|
if (++read_index.segment >= ndata)
|
|
{
|
|
stream_buffer.Remove(block);
|
|
block->EmptyToPool(segment_pool);
|
|
block_pool.Put(block);
|
|
Increment(read_index.block);
|
|
read_index.segment = 0;
|
|
read_index.offset = 0;
|
|
//Prune(read_index.block, false);
|
|
}
|
|
continue;
|
|
break;
|
|
} // end switch(NormDataMsg::ReadStreamPayloadMsgStart(segment))
|
|
}
|
|
else if (length > segment_size)
|
|
{
|
|
// This segment is an invalid segment because its length is too long
|
|
read_ready = false;
|
|
if (bytesRead > 0)
|
|
{
|
|
// Go ahead and return data read thus so far
|
|
*buflen = bytesRead;
|
|
Release();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Skip this invalid segment
|
|
PLOG(PL_ERROR, "NormStreamObject::ReadPrivate() node>%lu obj>%hu blk>%lu seg>%hu invalid stream segment!\n",
|
|
(unsigned long)LocalNodeId(), (UINT16)transport_id,
|
|
(unsigned long)read_index.block.GetValue(), (UINT16)read_index.segment);
|
|
if (++read_index.segment >= ndata)
|
|
{
|
|
stream_buffer.Remove(block);
|
|
block->EmptyToPool(segment_pool);
|
|
block_pool.Put(block);
|
|
Increment(read_index.block);
|
|
read_index.segment = 0;
|
|
read_index.offset = 0;
|
|
Prune(read_index.block, false);
|
|
}
|
|
*buflen = 0;
|
|
Release();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
UINT32 segmentOffset = NormDataMsg::ReadStreamPayloadOffset(segment);
|
|
|
|
/*// if (read_offset < segmentOffset)
|
|
UINT32 diff = read_offset - segmentOffset;
|
|
if ((diff > 0x80000000) || ((0x80000000 == diff) && (read_offset > segmentOffset)))
|
|
{
|
|
read_ready = false; //DetermineReadReadiness();
|
|
if (bytesRead > 0)
|
|
{
|
|
// Go ahead and return data read thus so far
|
|
*buflen = bytesRead;
|
|
Release();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_WARN, "NormStreamObject::ReadPrivate() node>%lu obj>%hu blk>%lu seg>%hu broken stream (read_offset:%lu segmentOffset:%lu)!\n",
|
|
(unsigned long)LocalNodeId(), (UINT16)transport_id,
|
|
(unsigned long)read_index.block.GetValue(), (UINT16)read_index.segment,
|
|
(unsigned long)read_offset, (unsigned long)segmentOffset);
|
|
read_offset = segmentOffset;
|
|
*buflen = 0;
|
|
Release();
|
|
return false;
|
|
}
|
|
}*/
|
|
//UINT32 index = read_offset - segmentOffset;
|
|
|
|
if ((length > 0) && (read_index.offset >= length))
|
|
{
|
|
read_ready = false; //DetermineReadReadiness();
|
|
if (bytesRead > 0)
|
|
{
|
|
// Go ahead and return data read thus so far
|
|
*buflen = bytesRead;
|
|
Release();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_ERROR, "NormStreamObject::ReadPrivate() node>%lu obj>%hu blk>%lu seg>%hu mangled stream! "
|
|
"offset:%hu length:%hu read_offset:%lu segmentOffset:%lu\n",
|
|
(unsigned long)read_index.block.GetValue(), (UINT16)read_index.segment,
|
|
read_index.offset, length, (unsigned long)read_offset, (unsigned long)segmentOffset);
|
|
// Reset our read_offset ...
|
|
read_offset = segmentOffset;
|
|
read_index.offset = 0;
|
|
*buflen = 0;
|
|
Release();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (seekMsgStart)
|
|
{
|
|
UINT16 msgStart = NormDataMsg::ReadStreamPayloadMsgStart(segment);
|
|
if (0 == msgStart)
|
|
{
|
|
// make sure msg start searches don't miss the stream end ...
|
|
if (0 == NormDataMsg::ReadStreamPayloadLength(segment))
|
|
{
|
|
PLOG(PL_DEBUG, "NormStreamObject::ReadPrivate() stream ended by sender 1\n");
|
|
session.Notify(NormController::RX_OBJECT_COMPLETED, sender, this);
|
|
stream_closing = true;
|
|
sender->DeleteObject(this);
|
|
}
|
|
// Don't bother managing individual segments since
|
|
// stream buffers are exact multiples of block size!
|
|
//block->DetachSegment(read_index.segment);
|
|
//segment_pool.Put(segment);
|
|
block->UnsetPending(read_index.segment++);
|
|
read_index.offset = 0;
|
|
if (read_index.segment >= ndata)
|
|
{
|
|
stream_buffer.Remove(block);
|
|
block->EmptyToPool(segment_pool);
|
|
block_pool.Put(block);
|
|
Increment(read_index.block);
|
|
read_index.segment = 0;
|
|
Prune(read_index.block, false);
|
|
}
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
read_offset += (msgStart - 1);
|
|
read_index.offset = (msgStart - 1);
|
|
seekMsgStart = false;
|
|
}
|
|
}
|
|
UINT16 count = length - read_index.offset;
|
|
count = MIN(count, bytesToRead);
|
|
#ifdef SIMULATE
|
|
UINT16 simCount = read_index.offset + count + NormDataMsg::GetStreamPayloadHeaderLength();
|
|
simCount = (simCount < SIM_PAYLOAD_MAX) ? (SIM_PAYLOAD_MAX - simCount) : 0;
|
|
memcpy(buffer+bytesRead, segment+read_index.offset+NormDataMsg::GetStreamPayloadHeaderLength(), simCount);
|
|
#else
|
|
memcpy(buffer+bytesRead, segment+read_index.offset+NormDataMsg::GetStreamPayloadHeaderLength(), count);
|
|
#endif // if/else SIMULATE
|
|
|
|
read_index.offset += count;
|
|
bytesRead += count;
|
|
read_offset += count;
|
|
bytesToRead -= count;
|
|
if (read_index.offset >= length)
|
|
{
|
|
bool streamEnded = (0 == NormDataMsg::ReadStreamPayloadLength(segment));
|
|
// NormDataMsg::StreamPayloadFlagIsSet(segment, NormDataMsg::FLAG_STREAM_END);
|
|
// Don't bother managing individual segments since
|
|
// stream buffers are multiples of block size!
|
|
// block->DetachSegment(read_index.segment);
|
|
// segment_pool.Put(segment);
|
|
block->UnsetPending(read_index.segment++);
|
|
read_index.offset = 0;
|
|
if (read_index.segment >= ndata)
|
|
{
|
|
stream_buffer.Remove(block);
|
|
block->EmptyToPool(segment_pool);
|
|
block_pool.Put(block);
|
|
Increment(read_index.block);
|
|
read_index.segment = 0;
|
|
Prune(read_index.block, false);
|
|
if (0 == bytesToRead)
|
|
read_ready = DetermineReadReadiness();
|
|
}
|
|
else
|
|
{
|
|
if (0 == bytesToRead)
|
|
read_ready = (NULL != block->GetSegment(read_index.segment));
|
|
}
|
|
if (streamEnded)
|
|
{
|
|
PLOG(PL_DEBUG, "NormStreamObject::ReadPrivate() stream ended by sender 2\n");
|
|
session.Notify(NormController::RX_OBJECT_COMPLETED, sender, this);
|
|
stream_closing = true;
|
|
sender->DeleteObject(this);
|
|
}
|
|
}
|
|
} while ((bytesToRead > 0) || seekMsgStart);
|
|
*buflen = bytesRead;
|
|
Release();
|
|
return true;
|
|
} // end NormStreamObject::ReadPrivate()
|
|
|
|
|
|
void NormStreamObject::Terminate()
|
|
{
|
|
// Flush stream and create a ZERO length segment to send
|
|
Flush(); // should eom be set for this call?
|
|
stream_closing = true;
|
|
NormBlock* block = stream_buffer.Find(write_index.block);
|
|
if (NULL == block)
|
|
{
|
|
if (NULL == (block = block_pool.Get()))
|
|
{
|
|
block = stream_buffer.Find(stream_buffer.RangeLo());
|
|
ASSERT(NULL != block);
|
|
if (block->IsPending())
|
|
{
|
|
NormBlockId blockId = block->GetId();
|
|
pending_mask.Unset(blockId.GetValue());
|
|
repair_mask.Unset(blockId.GetValue());
|
|
NormBlock* b = FindBlock(blockId);
|
|
if (b)
|
|
{
|
|
block_buffer.Remove(b);
|
|
session.SenderPutFreeBlock(b);
|
|
}
|
|
if (!pending_mask.IsSet())
|
|
{
|
|
pending_mask.Set(write_index.block.GetValue());
|
|
//stream_next_id = write_index.block + 1;
|
|
stream_next_id = write_index.block;
|
|
Increment(stream_next_id);
|
|
}
|
|
}
|
|
stream_buffer.Remove(block);
|
|
block->EmptyToPool(segment_pool);
|
|
}
|
|
block->SetId(write_index.block);
|
|
block->ClearPending();
|
|
bool success = stream_buffer.Insert(block);
|
|
ASSERT(success);
|
|
} // end if (!block)
|
|
char* segment = block->GetSegment(write_index.segment);
|
|
if (NULL == segment)
|
|
{
|
|
if (NULL == (segment = segment_pool.Get()))
|
|
{
|
|
NormBlock* b = stream_buffer.Find(stream_buffer.RangeLo());
|
|
ASSERT(b != block);
|
|
if (b->IsPending())
|
|
{
|
|
NormBlockId blockId = b->GetId();
|
|
pending_mask.Unset(blockId.GetValue());
|
|
repair_mask.Unset(blockId.GetValue());
|
|
NormBlock* c = FindBlock(blockId);
|
|
if (c)
|
|
{
|
|
block_buffer.Remove(c);
|
|
session.SenderPutFreeBlock(c);
|
|
}
|
|
if (!pending_mask.IsSet())
|
|
{
|
|
pending_mask.Set(write_index.block.GetValue());
|
|
//stream_next_id = write_index.block + 1;
|
|
stream_next_id = write_index.block;
|
|
Increment(stream_next_id);
|
|
}
|
|
}
|
|
stream_buffer.Remove(b);
|
|
b->EmptyToPool(segment_pool);
|
|
block_pool.Put(b);
|
|
segment = segment_pool.Get();
|
|
ASSERT(NULL != segment);
|
|
}
|
|
block->AttachSegment(write_index.segment, segment);
|
|
NormDataMsg::WriteStreamPayloadMsgStart(segment, 0);
|
|
NormDataMsg::WriteStreamPayloadLength(segment, 0);
|
|
}
|
|
else
|
|
{
|
|
// Make sure the segment is not still referenced (TBD - why was I checking this???)
|
|
//ASSERT(0 == NormDataMsg::ReadStreamPayloadMsgStart(segment));
|
|
//ASSERT(0 == NormDataMsg::ReadStreamPayloadLength(segment));
|
|
}
|
|
NormDataMsg::WriteStreamPayloadOffset(segment, write_offset);
|
|
|
|
block->SetPending(write_index.segment);
|
|
if (++write_index.segment >= ndata)
|
|
{
|
|
Increment(write_index.block);
|
|
write_index.segment = 0;
|
|
}
|
|
flush_pending = true;
|
|
session.TouchSender();
|
|
} // end NormStreamObject::Terminate()
|
|
|
|
unsigned int NormStreamObject::GetVacancy(unsigned int wanted)
|
|
{
|
|
// Computes how many bytes are available for _immediate_ writing
|
|
ASSERT(Compare(write_index.block, tx_index.block) >= 0);
|
|
unsigned int maxDelta = block_pool.GetTotal() >> 1;
|
|
UINT32 blockDelta = (UINT32)Difference(write_index.block, tx_index.block);
|
|
if (blockDelta > maxDelta) return 0;
|
|
UINT32 nBytes = 0;
|
|
NormBlock* block = stream_buffer.Find(write_index.block);
|
|
if (NULL != block)
|
|
{
|
|
char* segment = block->GetSegment(write_index.segment);
|
|
if (NULL != segment)
|
|
{
|
|
UINT16 index = NormDataMsg::ReadStreamPayloadLength(segment);
|
|
nBytes += segment_size - index;
|
|
}
|
|
else
|
|
{
|
|
nBytes += segment_size;
|
|
}
|
|
nBytes += (ndata - write_index.segment - 1) * segment_size;
|
|
}
|
|
unsigned int blocksAllowed = maxDelta - blockDelta;
|
|
unsigned int poolCount = block_pool.GetCount();
|
|
if (poolCount >= blocksAllowed)
|
|
poolCount = blocksAllowed;
|
|
nBytes += poolCount * ndata * segment_size;
|
|
|
|
NormBlockBuffer::Iterator iterator(block_buffer);
|
|
while ((NULL != (block = iterator.GetNextBlock())) &&
|
|
(blocksAllowed > 0) &&
|
|
((0 == wanted) || (nBytes < wanted)))
|
|
{
|
|
double delay = session.GetFlowControlDelay() - block->GetNackAge();
|
|
if (block->IsPending() || (delay >= 1.0e-06)) break;
|
|
nBytes += (segment_size * ndata);
|
|
blocksAllowed--;
|
|
}
|
|
return nBytes;
|
|
} // end NormStreamObject::GetVacancy()
|
|
|
|
UINT32 NormStreamObject::Write(const char* buffer, UINT32 len, bool eom)
|
|
{
|
|
UINT32 nBytes = 0;
|
|
do
|
|
{
|
|
if (stream_closing)
|
|
{
|
|
if (0 != len)
|
|
{
|
|
PLOG(PL_ERROR, "NormStreamObject::Write() error: stream is closing (len:%lu eom:%d)\n",
|
|
(unsigned long)len, eom);
|
|
len = 0;
|
|
}
|
|
break;
|
|
}
|
|
// This old code detected buffer "fullness" by offset instead of segment index
|
|
// but, the problem there was when apps wrote & flushed messages smaller than
|
|
// the segment_size, the buffer was used up before this detected it.
|
|
//INT32 deltaOffset = write_offset - tx_offset; // (TBD) deprecate tx_offset
|
|
//ASSERT(deltaOffset >= 0);
|
|
//if (deltaOffset >= (INT32)object_size.LSB())
|
|
//ASSERT(write_index.block >= tx_index.block);
|
|
ASSERT(Compare(write_index.block, tx_index.block) >= 0);
|
|
UINT32 deltaBlock = (UINT32)Difference(write_index.block, tx_index.block);
|
|
if (deltaBlock > (block_pool.GetTotal() >> 1))
|
|
{
|
|
write_vacancy = false;
|
|
PLOG(PL_DEBUG, "NormStreamObject::Write() stream buffer full (1)\n");
|
|
if (!push_mode) break;
|
|
}
|
|
NormBlock* block = stream_buffer.Find(write_index.block);
|
|
if (NULL == block)
|
|
{
|
|
block = block_pool.Get();
|
|
if (NULL == block)
|
|
{
|
|
block = stream_buffer.Find(stream_buffer.RangeLo());
|
|
ASSERT(NULL != block);
|
|
double delay = session.GetFlowControlDelay() - block->GetNackAge();
|
|
if (block->IsPending() || (delay >= 1.0e-06))
|
|
{
|
|
write_vacancy = false;
|
|
if (push_mode)
|
|
{
|
|
NormBlockId blockId = block->GetId();
|
|
pending_mask.Unset(blockId.GetValue());
|
|
repair_mask.Unset(blockId.GetValue());
|
|
NormBlock* b = FindBlock(blockId);
|
|
if (b)
|
|
{
|
|
block_buffer.Remove(b);
|
|
session.SenderPutFreeBlock(b);
|
|
}
|
|
if (!pending_mask.IsSet())
|
|
{
|
|
pending_mask.Set(write_index.block.GetValue());
|
|
//stream_next_id = write_index.block + 1;
|
|
stream_next_id = write_index.block;
|
|
Increment(stream_next_id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The timer activated here makes sure a deferred TX_QUEUE_VACANCY is posted
|
|
// when flow control has been asserted.
|
|
if (!block->IsPending())
|
|
{
|
|
PLOG(PL_DEBUG, "NormStreamObject::Write() asserting flow control for stream (postedEmpty:%d)\n",
|
|
session.GetPostedTxQueueEmpty());
|
|
if (session.GetPostedTxQueueEmpty())
|
|
session.ActivateFlowControl(delay, GetId(), NormController::TX_QUEUE_EMPTY);
|
|
else
|
|
session.ActivateFlowControl(delay, GetId(), NormController::TX_QUEUE_VACANCY);
|
|
}
|
|
PLOG(PL_DEBUG, "NormStreamObject::Write() stream buffer full (2) len:%d eom:%d\n", len, eom);
|
|
break;
|
|
}
|
|
}
|
|
stream_buffer.Remove(block);
|
|
block->EmptyToPool(segment_pool);
|
|
}
|
|
block->SetId(write_index.block);
|
|
block->ClearPending();
|
|
bool success = stream_buffer.Insert(block);
|
|
ASSERT(success);
|
|
} // end if (NULL == block)
|
|
char* segment = block->GetSegment(write_index.segment);
|
|
if (NULL == segment)
|
|
{
|
|
if (NULL == (segment = segment_pool.Get()))
|
|
{
|
|
NormBlock* b = stream_buffer.Find(stream_buffer.RangeLo());
|
|
ASSERT(b != block);
|
|
if (b->IsPending())
|
|
{
|
|
write_vacancy = false;
|
|
if (push_mode)
|
|
{
|
|
NormBlockId blockId = b->GetId();
|
|
pending_mask.Unset(blockId.GetValue());
|
|
repair_mask.Unset(blockId.GetValue());
|
|
NormBlock* c = FindBlock(blockId);
|
|
if (c)
|
|
{
|
|
block_buffer.Remove(c);
|
|
session.SenderPutFreeBlock(c);
|
|
}
|
|
if (!pending_mask.IsSet())
|
|
{
|
|
pending_mask.Set(write_index.block.GetValue());
|
|
//stream_next_id = write_index.block + 1;
|
|
stream_next_id = write_index.block;
|
|
Increment(stream_next_id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PLOG(PL_DEBUG, "NormStreamObject::Write() stream buffer full (3)\n");
|
|
break;
|
|
}
|
|
}
|
|
stream_buffer.Remove(b);
|
|
b->EmptyToPool(segment_pool);
|
|
block_pool.Put(b);
|
|
segment = segment_pool.Get();
|
|
ASSERT(NULL != segment);
|
|
}
|
|
NormDataMsg::WriteStreamPayloadMsgStart(segment, 0);
|
|
NormDataMsg::WriteStreamPayloadLength(segment, 0);
|
|
NormDataMsg::WriteStreamPayloadOffset(segment, write_offset);
|
|
block->AttachSegment(write_index.segment, segment);
|
|
} // end if (!segment)
|
|
|
|
UINT16 index = NormDataMsg::ReadStreamPayloadLength(segment);
|
|
// If it is an application start-of-message, mark the stream header accordingly
|
|
// (but only if it is the _first_ message start for this segment!)
|
|
if (msg_start && (0 != len))
|
|
{
|
|
if (0 == NormDataMsg::ReadStreamPayloadMsgStart(segment))
|
|
NormDataMsg::WriteStreamPayloadMsgStart(segment, index+1);
|
|
msg_start = false;
|
|
}
|
|
|
|
UINT32 count = len - nBytes;
|
|
UINT32 space = (UINT32)(segment_size - index);
|
|
count = MIN(count, space);
|
|
#ifdef SIMULATE
|
|
UINT32 simCount = index + NormDataMsg::GetStreamPayloadHeaderLength();
|
|
simCount = (simCount < SIM_PAYLOAD_MAX) ? (SIM_PAYLOAD_MAX - simCount) : 0;
|
|
simCount = MIN(count, simCount);
|
|
memcpy(segment+index+NormDataMsg::GetStreamPayloadHeaderLength(), buffer+nBytes, simCount);
|
|
#else
|
|
memcpy(segment+index+NormDataMsg::GetStreamPayloadHeaderLength(), buffer+nBytes, count);
|
|
#endif // if/else SIMULATE
|
|
NormDataMsg::WriteStreamPayloadLength(segment, index+count);
|
|
nBytes += count;
|
|
write_offset += count;
|
|
// Is the segment full? or flushing
|
|
//if ((count == space) || ((FLUSH_NONE != flush_mode) && (0 != index) && (nBytes == len)))
|
|
if ((count == space) ||
|
|
((FLUSH_NONE != flush_mode) && (nBytes == len) && ((0 != index) || (0 != len))))
|
|
{
|
|
block->SetPending(write_index.segment);
|
|
if (++write_index.segment >= ndata)
|
|
{
|
|
ProtoTime currentTime;
|
|
currentTime.GetCurrentTime();
|
|
block->SetLastNackTime(currentTime);
|
|
Increment(write_index.block);
|
|
write_index.segment = 0;
|
|
}
|
|
}
|
|
} while (nBytes < len);
|
|
|
|
// if this was end-of-message next Write() will be considered a new message
|
|
if (nBytes == len)
|
|
{
|
|
if (eom)
|
|
msg_start = true;
|
|
if (FLUSH_ACTIVE == flush_mode)
|
|
flush_pending = true;
|
|
else if (!stream_closing)
|
|
flush_pending = false;
|
|
if ((0 != nBytes) || (FLUSH_NONE != flush_mode))
|
|
{
|
|
session.TouchSender();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
session.TouchSender();
|
|
}
|
|
return nBytes;
|
|
} // end NormStreamObject::Write()
|
|
|
|
#ifdef SIMULATE
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// NormSimObject Implementation (dummy NORM_OBJECT_FILE or NORM_OBJECT_DATA)
|
|
//
|
|
|
|
NormSimObject::NormSimObject(class NormSession& theSession,
|
|
class NormSenderNode* theSender,
|
|
const NormObjectId& objectId)
|
|
: NormObject(FILE, theSession, theSender, objectId)
|
|
{
|
|
|
|
}
|
|
|
|
NormSimObject::~NormSimObject()
|
|
{
|
|
|
|
}
|
|
|
|
bool NormSimObject::Open(UINT32 objectSize,
|
|
const char* infoPtr ,
|
|
UINT16 infoLen)
|
|
{
|
|
return (sender ?
|
|
true :
|
|
NormObject::Open(NormObjectSize(objectSize),
|
|
infoPtr, infoLen,
|
|
session.SenderSegmentSize(),
|
|
session.GetSenderFecId(),
|
|
session.GetSenderFecFieldSize(),
|
|
session.SenderBlockSize(),
|
|
session.SenderNumParity()));
|
|
} // end NormSimObject::Open()
|
|
|
|
UINT16 NormSimObject::ReadSegment(NormBlockId blockId,
|
|
NormSegmentId segmentId,
|
|
char* buffer)
|
|
{
|
|
// Determine segment length
|
|
UINT16 len;
|
|
if (blockId == final_block_id)
|
|
{
|
|
if (segmentId == (GetBlockSize(blockId)-1))
|
|
len = final_segment_size;
|
|
else
|
|
len = segment_size;
|
|
}
|
|
else
|
|
{
|
|
len = segment_size;
|
|
}
|
|
return len;
|
|
} // end NormSimObject::ReadSegment()
|
|
|
|
char* NormSimObject::RetrieveSegment(NormBlockId blockId,
|
|
NormSegmentId segmentId)
|
|
{
|
|
return sender ? sender->GetRetrievalSegment() : NULL;
|
|
} // end NormSimObject::RetrieveSegment()
|
|
|
|
#endif // SIMULATE
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// NormObjectTable Implementation
|
|
//
|
|
|
|
NormObjectTable::NormObjectTable()
|
|
#ifndef USE_PROTO_TREE
|
|
: table((NormObject**)NULL),
|
|
#else
|
|
:
|
|
#endif // if/else USE_PROTO_TREE
|
|
range_max(0), range(0),
|
|
count(0), size(0)
|
|
{
|
|
}
|
|
|
|
NormObjectTable::~NormObjectTable()
|
|
{
|
|
Destroy();
|
|
}
|
|
|
|
bool NormObjectTable::Init(UINT16 rangeMax, UINT16 tableSize)
|
|
{
|
|
Destroy();
|
|
if (!rangeMax || !tableSize) return false;
|
|
#ifndef USE_PROTO_TREE
|
|
// Make sure (rangeMax > 0) and tableSize is greater than 0 and 2^n
|
|
if (!rangeMax || !tableSize) return false;
|
|
if (0 != (tableSize & 0x07)) tableSize = (tableSize >> 3) + 1;
|
|
if (!(table = new NormObject*[tableSize]))
|
|
{
|
|
PLOG(PL_FATAL, "NormObjectTable::Init() table allocation error: %s\n", GetErrorString());
|
|
return false;
|
|
}
|
|
memset(table, 0, tableSize*sizeof(char*));
|
|
hash_mask = tableSize - 1;
|
|
#endif // !USE_PROTO_TREE
|
|
range_max = rangeMax;
|
|
count = range = 0;
|
|
size = NormObjectSize(0);
|
|
return true;
|
|
} // end NormObjectTable::Init()
|
|
|
|
void NormObjectTable::SetRangeMax(UINT16 rangeMax)
|
|
{
|
|
if (rangeMax < range_max)
|
|
{
|
|
// Prune if necessary
|
|
while (range > rangeMax)
|
|
{
|
|
NormObject* obj = Find(range_lo);
|
|
ASSERT(NULL != obj);
|
|
NormSenderNode* sender = obj->GetSender();
|
|
NormSession& session = obj->GetSession();
|
|
if (NULL == sender)
|
|
{
|
|
session.DeleteTxObject(obj, true);
|
|
}
|
|
else
|
|
{
|
|
if (!session.ReceiverIsSilent()) obj = Find(range_hi);
|
|
session.Notify(NormController::RX_OBJECT_ABORTED, sender, obj);
|
|
sender->DeleteObject(obj);
|
|
}
|
|
}
|
|
}
|
|
range_max = rangeMax;
|
|
} // end NormObjectTable::SetRangeMax()
|
|
|
|
#ifdef USE_PROTO_TREE
|
|
|
|
NormObject* NormObjectTable::Find(const NormObjectId& objectId) const
|
|
{
|
|
if ((0 == range) || (objectId < range_lo) || (objectId > range_hi))
|
|
return NULL;
|
|
else
|
|
return tree.Find(objectId.GetValuePtr(), 8*sizeof(UINT16));
|
|
} // end NormObjectTable::Find()
|
|
|
|
void NormObjectTable::Destroy()
|
|
{
|
|
NormObject* obj;
|
|
while(NULL != (obj = Find(range_lo)))
|
|
{
|
|
// TBD - should we issue PURGED/ABORTED notifications here???
|
|
// (We haven't since this is destroyed only when session is terminated)
|
|
// or when a NormSenderNode is deleted
|
|
Remove(obj);
|
|
obj->Release();
|
|
}
|
|
count = range = range_max = 0;
|
|
} // end NormObjectTable::Destroy()
|
|
|
|
#else // !USE_PROTO_TREE
|
|
|
|
NormObject* NormObjectTable::Find(const NormObjectId& objectId) const
|
|
{
|
|
if (0 != range)
|
|
{
|
|
if ((objectId < range_lo) || (objectId > range_hi)) return (NormObject*)NULL;
|
|
NormObject* theObject = table[((UINT16)objectId) & hash_mask];
|
|
while (theObject && (objectId != theObject->GetId()))
|
|
theObject = theObject->next;
|
|
return theObject;
|
|
}
|
|
else
|
|
{
|
|
return (NormObject*)NULL;
|
|
}
|
|
} // end NormObjectTable::Find()
|
|
|
|
void NormObjectTable::Destroy()
|
|
{
|
|
if (NULL != table)
|
|
{
|
|
NormObject* obj;
|
|
while((obj = Find(range_lo)))
|
|
{
|
|
// TBD - should we issue PURGED/ABORTED notifications here???
|
|
// (We haven't since this is destroyed only when session is terminated)
|
|
// or when a NormSenderNode is deleted
|
|
Remove(obj);
|
|
obj->Release();
|
|
}
|
|
delete[] table;
|
|
table = (NormObject**)NULL;
|
|
count = range = range_max = 0;
|
|
}
|
|
} // end NormObjectTable::Destroy()
|
|
|
|
#endif // if/else USE_PROTO_TREE
|
|
|
|
bool NormObjectTable::CanInsert(NormObjectId objectId) const
|
|
{
|
|
if (0 != range)
|
|
{
|
|
if (objectId < range_lo)
|
|
{
|
|
if ((range_lo - objectId + range) > range_max)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
else if (objectId > range_hi)
|
|
{
|
|
if ((objectId - range_hi + range) > range_max)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
} // end NormObjectTable::CanInsert()
|
|
|
|
|
|
bool NormObjectTable::Insert(NormObject* theObject)
|
|
{
|
|
const NormObjectId& objectId = theObject->GetId();
|
|
if (0 == range)
|
|
{
|
|
range_lo = range_hi = objectId;
|
|
range = 1;
|
|
}
|
|
else if (objectId < range_lo)
|
|
{
|
|
UINT16 newRange = range_lo - objectId + range;
|
|
if (newRange > range_max) return false;
|
|
range_lo = objectId;
|
|
ASSERT(range_lo <= range_hi);
|
|
range = newRange;
|
|
}
|
|
else if (objectId > range_hi)
|
|
{
|
|
UINT16 newRange = objectId - range_hi + range;
|
|
if (newRange > range_max) return false;
|
|
range_hi = objectId;
|
|
ASSERT(range_lo <= range_hi);
|
|
range = newRange;
|
|
}
|
|
|
|
#ifdef USE_PROTO_TREE
|
|
ASSERT(NULL == Find(theObject->GetId()));
|
|
tree.Insert(*theObject);
|
|
#else
|
|
UINT16 index = ((UINT16)objectId) & hash_mask;
|
|
NormObject* prev = NULL;
|
|
NormObject* entry = table[index];
|
|
while (entry && (entry->GetId() < objectId))
|
|
{
|
|
prev = entry;
|
|
entry = entry->next;
|
|
}
|
|
if (prev)
|
|
prev->next = theObject;
|
|
else
|
|
table[index] = theObject;
|
|
ASSERT(((NULL != entry) ? (objectId != entry->GetId()) : true));
|
|
theObject->next = entry;
|
|
#endif // if/else USE_PROTO_TREE
|
|
count++;
|
|
size = size + theObject->GetSize();
|
|
theObject->Retain();
|
|
return true;
|
|
} // end NormObjectTable::Insert()
|
|
|
|
#ifdef USE_PROTO_TREE
|
|
bool NormObjectTable::Remove(NormObject* theObject)
|
|
{
|
|
ASSERT(NULL != theObject);
|
|
const NormObjectId& objectId = theObject->GetId();
|
|
if (range)
|
|
{
|
|
if ((objectId < range_lo) || (objectId > range_hi)) return false;
|
|
if (range > 1)
|
|
{
|
|
if (objectId == range_lo)
|
|
{
|
|
const NormObject* next = static_cast<const NormObject*>(theObject->GetNext());
|
|
if (NULL == next) next = static_cast<const NormObject*>(tree.GetHead());
|
|
ASSERT(NULL != next);
|
|
range_lo = next->GetId();
|
|
ASSERT(range_lo <= range_hi);
|
|
range = range_hi - range_lo + 1;
|
|
}
|
|
else if (objectId == range_hi)
|
|
{
|
|
const NormObject* prev = static_cast<const NormObject*>(theObject->GetPrev());
|
|
if (NULL == prev) prev = static_cast<const NormObject*>(tree.GetTail());
|
|
ASSERT(NULL != prev);
|
|
range_hi = prev->GetId();
|
|
ASSERT(range_lo <= range_hi);
|
|
range = range_hi - range_lo + 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
range = 0;
|
|
}
|
|
ASSERT(NULL != tree.Find(theObject->GetId().GetValuePtr(), 8*sizeof(UINT16)));
|
|
tree.Remove(*theObject);
|
|
count--;
|
|
size = size - theObject->GetSize();
|
|
theObject->Release();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
} // end NormObjectTable::Remove()
|
|
#else
|
|
bool NormObjectTable::Remove(NormObject* theObject)
|
|
{
|
|
ASSERT(NULL != theObject);
|
|
const NormObjectId& objectId = theObject->GetId();
|
|
if (range)
|
|
{
|
|
if ((objectId < range_lo) || (objectId > range_hi)) return false;
|
|
UINT16 index = ((UINT16)objectId) & hash_mask;
|
|
NormObject* prev = NULL;
|
|
NormObject* entry = table[index];
|
|
while (entry && (entry->GetId() != objectId))
|
|
{
|
|
prev = entry;
|
|
entry = entry->next;
|
|
}
|
|
if (entry != theObject) return false;
|
|
if (prev)
|
|
prev->next = entry->next;
|
|
else
|
|
table[index] = entry->next;
|
|
if (range > 1)
|
|
{
|
|
if (objectId == range_lo)
|
|
{
|
|
// Find next entry for range_lo
|
|
UINT16 i = index;
|
|
UINT16 endex;
|
|
if (range <= hash_mask)
|
|
endex = (index + range - 1) & hash_mask;
|
|
else
|
|
endex = index;
|
|
entry = NULL;
|
|
UINT16 offset = 0;
|
|
NormObjectId nextId = range_hi;
|
|
do
|
|
{
|
|
++i &= hash_mask;
|
|
offset++;
|
|
if ((entry = table[i]))
|
|
{
|
|
NormObjectId id = (UINT16)objectId + offset;
|
|
while(entry && (entry->GetId() != id))
|
|
{
|
|
if ((entry->GetId() > objectId) &&
|
|
(entry->GetId() < nextId)) nextId = entry->GetId();
|
|
entry = entry->next;
|
|
|
|
}
|
|
if (entry) break;
|
|
}
|
|
} while (i != endex);
|
|
if (entry)
|
|
range_lo = entry->GetId();
|
|
else
|
|
range_lo = nextId;
|
|
range = range_hi - range_lo + 1;
|
|
}
|
|
else if (objectId == range_hi)
|
|
{
|
|
// Find prev entry for range_hi
|
|
UINT16 i = index;
|
|
UINT16 endex;
|
|
if (range <= hash_mask)
|
|
endex = (index - range + 1) & hash_mask;
|
|
else
|
|
endex = index;
|
|
entry = NULL;
|
|
UINT16 offset = 0;
|
|
NormObjectId prevId = range_lo;
|
|
do
|
|
{
|
|
--i &= hash_mask;
|
|
offset++;
|
|
if ((entry = table[i]))
|
|
{
|
|
NormObjectId id = (UINT16)objectId - offset;
|
|
while(entry && (entry->GetId() != id))
|
|
{
|
|
if ((entry->GetId() < objectId) &&
|
|
(entry->GetId() > prevId)) prevId = entry->GetId();
|
|
entry = entry->next;
|
|
}
|
|
if (entry) break;
|
|
}
|
|
} while (i != endex);
|
|
if (entry)
|
|
range_hi = entry->GetId();
|
|
else
|
|
range_hi = prevId;
|
|
range = range_hi - range_lo + 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
range = 0;
|
|
}
|
|
count--;
|
|
size = size - theObject->GetSize();
|
|
theObject->Release();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
} // end NormObjectTable::Remove()
|
|
#endif // if/else USE_PROTO_TREE
|
|
|
|
#ifdef USE_PROTO_TREE
|
|
NormObjectTable::Iterator::Iterator(NormObjectTable& objectTable)
|
|
: table(objectTable), iterator(objectTable.tree, false, objectTable.range_lo.GetValuePtr(), 8*sizeof(UINT16))
|
|
{
|
|
next_object = iterator.GetNextItem();
|
|
ASSERT((NULL == next_object) || (objectTable.range_lo == next_object->GetId()))
|
|
}
|
|
|
|
void NormObjectTable::Iterator::Reset()
|
|
{
|
|
iterator.Reset(false, table.range_lo.GetValuePtr(), 8*sizeof(UINT16));
|
|
next_object = iterator.GetNextItem();
|
|
ASSERT(table.IsEmpty() || (NULL != next_object));
|
|
ASSERT((NULL == next_object) || (table.range_lo == next_object->GetId()))
|
|
}
|
|
|
|
NormObject* NormObjectTable::Iterator::GetNextObject()
|
|
{
|
|
NormObject* nextObj = next_object;
|
|
if (NULL != nextObj)
|
|
{
|
|
next_object = iterator.GetNextItem();
|
|
if (NULL == next_object)
|
|
{
|
|
iterator.Reset();
|
|
next_object = iterator.GetNextItem();
|
|
if (next_object->GetId() <= nextObj->GetId())
|
|
next_object = NULL;
|
|
}
|
|
else if (next_object->GetId() <= nextObj->GetId())
|
|
{
|
|
next_object = NULL;
|
|
}
|
|
}
|
|
return nextObj;
|
|
} // end NormObjectTable::Iterator::GetNextObject()
|
|
|
|
NormObject* NormObjectTable::Iterator::GetPrevObject()
|
|
{
|
|
if (!iterator.IsReversed())
|
|
{
|
|
iterator.Reset(true, table.range_hi.GetValuePtr(), 8*sizeof(UINT16));
|
|
next_object = iterator.GetPrevItem();
|
|
}
|
|
NormObject* prevObj = next_object;
|
|
if (NULL != prevObj)
|
|
{
|
|
next_object = iterator.GetPrevItem();
|
|
if (NULL == next_object)
|
|
{
|
|
iterator.Reset(true);
|
|
next_object = iterator.GetPrevItem();
|
|
if (next_object->GetId() >= prevObj->GetId())
|
|
next_object = NULL;
|
|
}
|
|
else if (next_object->GetId() >= prevObj->GetId())
|
|
{
|
|
next_object = NULL;
|
|
}
|
|
}
|
|
return prevObj;
|
|
} // end NormObjectTable::Iterator::GetPrevObject()
|
|
|
|
|
|
#else
|
|
NormObjectTable::Iterator::Iterator(const NormObjectTable& objectTable)
|
|
: table(objectTable), reset(true)
|
|
{
|
|
}
|
|
|
|
NormObject* NormObjectTable::Iterator::GetNextObject()
|
|
{
|
|
if (reset)
|
|
{
|
|
if (table.range)
|
|
{
|
|
reset = false;
|
|
index = table.range_lo;
|
|
return table.Find(index);
|
|
}
|
|
else
|
|
{
|
|
return (NormObject*)NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (table.range &&
|
|
(index < table.range_hi) &&
|
|
(index >= table.range_lo))
|
|
{
|
|
// Find next entry _after_ current "index"
|
|
UINT16 i = index;
|
|
UINT16 endex;
|
|
if ((UINT16)(table.range_hi - index) <= table.hash_mask)
|
|
endex = table.range_hi & table.hash_mask;
|
|
else
|
|
endex = index;
|
|
UINT16 offset = 0;
|
|
NormObjectId nextId = table.range_hi;
|
|
do
|
|
{
|
|
++i &= table.hash_mask;
|
|
offset++;
|
|
NormObjectId id = (UINT16)index + offset;
|
|
NormObject* entry = table.table[i];
|
|
while ((NULL != entry) && (entry->GetId() != id))
|
|
{
|
|
if ((entry->GetId() > index) && (entry->GetId() < nextId))
|
|
nextId = entry->GetId();
|
|
entry = table.Next(entry);
|
|
}
|
|
if (entry)
|
|
{
|
|
index = entry->GetId();
|
|
return entry;
|
|
}
|
|
} while (i != endex);
|
|
// If we get here, use nextId value
|
|
index = nextId;
|
|
return table.Find(nextId);
|
|
}
|
|
else
|
|
{
|
|
return (NormObject*)NULL;
|
|
}
|
|
}
|
|
} // end NormObjectTable::Iterator::GetNextObject()
|
|
|
|
NormObject* NormObjectTable::Iterator::GetPrevObject()
|
|
{
|
|
if (reset)
|
|
{
|
|
if (table.range)
|
|
{
|
|
reset = false;
|
|
index = table.range_hi;
|
|
return table.Find(index);
|
|
}
|
|
else
|
|
{
|
|
return (NormObject*)NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (table.range &&
|
|
(index <= table.range_hi) &&
|
|
(index > table.range_lo))
|
|
{
|
|
// Find prev entry _before_ current "index"
|
|
UINT16 i = index;
|
|
UINT16 endex;
|
|
if ((UINT16)(index - table.range_lo) <= table.hash_mask)
|
|
endex = table.range_lo & table.hash_mask;
|
|
else
|
|
endex = index;
|
|
UINT16 offset = 0;
|
|
NormObjectId nextId = table.range_hi;
|
|
do
|
|
{
|
|
--i &= table.hash_mask;
|
|
offset--;
|
|
NormObjectId id = (UINT16)index + offset;
|
|
NormObject* entry = table.table[i];
|
|
while ((NULL != entry ) && (entry->GetId() != id))
|
|
{
|
|
if ((entry->GetId() > index) && (entry->GetId() < nextId))
|
|
nextId = entry->GetId();
|
|
entry = table.Next(entry);
|
|
}
|
|
if (entry)
|
|
{
|
|
index = entry->GetId();
|
|
return entry;
|
|
}
|
|
} while (i != endex);
|
|
// If we get here, use nextId value
|
|
index = nextId;
|
|
return table.Find(nextId);
|
|
}
|
|
else
|
|
{
|
|
return (NormObject*)NULL;
|
|
}
|
|
}
|
|
} // end NormObjectTable::Iterator::GetPrevObject()
|
|
|
|
#endif // if/else USE_PROTO_TREE
|