#ifndef _NORM_MESSAGE #define _NORM_MESSAGE // PROTOLIB includes #include "protokit.h" // standard includes #include // for memcpy(), etc #include #include // for rand(), etc #ifdef _WIN32_WCE #include #else #include // for off_t #endif // if/else _WIN32_WCE #ifdef SIMULATE // IMPORTANT! This _assumes_ that the message header of interest _will_ be // aligned with a NormSegment (i.e. flush mode of active or passive // is used with flushing after _each_ message written to a // NORM_OBJECT_STREAM! #define SIM_PAYLOAD_MAX (36+8) // MGEN message size + StreamPayloadHeaderLen() #endif // SIMULATE const UINT8 NORM_PROTOCOL_VERSION = 1; // This value is used in a couple places in the code as // a safety check where some critical timeouts may be // less than expected operating system clock resolution const double NORM_TICK_MIN = 0.100; // in seconds // Pick a random number from 0..max inline double UniformRand(double max) {return (max * ((double)rand() / (double)RAND_MAX));} // Pick a random number from 0..max // (truncated exponential dist. lambda = log(groupSize) + 1) inline double ExponentialRand(double max, double groupSize) { double lambda = log(groupSize) + 1; double x = UniformRand(lambda/max)+lambda/(max*(exp(lambda)-1)); return ((max/lambda)*log(x*(exp(lambda)-1)*(max/lambda))); } // These are the GRTT estimation bounds set for the current // NORM protocol. (Note that our Grtt quantization routines // are good for the range of 1.0e-06 <= 1000.0 seconds) const double NORM_GRTT_MIN = 0.001; // 1 msec const double NORM_GRTT_MAX = 15.0; // 15 sec const double NORM_RTT_MIN = 1.0e-06; const double NORM_RTT_MAX = 1000.0; extern const double NORM_RTT[]; inline double NormUnquantizeRtt(UINT8 qrtt) {return NORM_RTT[qrtt];} UINT8 NormQuantizeRtt(double rtt); extern const double NORM_GSIZE[]; inline double NormUnquantizeGroupSize(UINT8 gsize) {return NORM_GSIZE[gsize];} UINT8 NormQuantizeGroupSize(double gsize); inline UINT16 NormQuantizeLoss(double lossFraction) { lossFraction = MAX(lossFraction, 0.0); lossFraction = lossFraction*65535.0 + 0.5; lossFraction = MIN(lossFraction, 65535.0); return (UINT16)lossFraction; } // end NormQuantizeLossFraction() inline double NormUnquantizeLoss(UINT16 lossQuantized) { return (((double)lossQuantized) / 65535.0); } // end NormUnquantizeLossFraction() // Extended precision Norm loss quantize/unquantize with // 32-bit precision (needed for low BER, high bandwidth*delay) inline UINT32 NormQuantizeLoss32(double lossFraction) { const double MAX_SCALE = (double)((unsigned int)0xffffffff); lossFraction = MAX(lossFraction, 0.0); lossFraction = lossFraction*MAX_SCALE + 0.5; lossFraction = MIN(lossFraction, MAX_SCALE); return (UINT32)lossFraction; } // end NormQuantizeLossFraction32() inline double NormUnquantizeLoss32(UINT32 lossQuantized) { const double MAX_SCALE = (double)((unsigned int)0xffffffff); return (((double)lossQuantized) / MAX_SCALE); } // end NormUnquantizeLossFraction32() inline UINT16 NormQuantizeRate(double rate) { if (rate <= 0.0) return 0x01; // rate = 0.0 UINT16 exponent = (UINT16)log10(rate); UINT16 mantissa = (UINT16)((4096.0/10.0) * (rate / pow(10.0, (double)exponent)) + 0.5); return ((mantissa << 4) | exponent); } inline double NormUnquantizeRate(UINT16 rate) { double mantissa = ((double)(rate >> 4)) * (10.0/4096.0); double exponent = (double)(rate & 0x000f); return mantissa * pow(10.0, exponent); } class NormObjectSize { public: #ifdef WIN32 #define _FILE_OFFSET_BITS 64 typedef __int64 Offset; #else typedef off_t Offset; #endif // if/else WIN32 NormObjectSize() : size(0) {} NormObjectSize(Offset theSize) : size(theSize) {} NormObjectSize(UINT16 msb, UINT32 lsb) { size = (Offset)lsb; #if (_FILE_OFFSET_BITS > 32) && !defined(ANDROID) size |= ((Offset)msb) << 32; #endif } Offset GetOffset() const {return size;} #if (_FILE_OFFSET_BITS > 32) && !defined(ANDROID) UINT16 MSB() const {return ((UINT16)((size >> 32) & 0x0000ffff));} #else UINT16 MSB() const {return 0;} #endif UINT32 LSB() const {return ((UINT32)(size & 0xffffffff));} // Operators bool operator==(const NormObjectSize& b) const {return (b.size == size);} bool operator!=(const NormObjectSize& b) const {return (b.size != size);} NormObjectSize operator+(const NormObjectSize& b) const { NormObjectSize result(size); result.size += b.size; return result; } void operator+=(const NormObjectSize& b) {size += b.size;} NormObjectSize operator-(const NormObjectSize& b) const { NormObjectSize result(size); result.size -= b.size; return result; } void operator-=(const NormObjectSize& b) {size -= b.size;} void operator+=(Offset increment) {size += increment;} bool operator>(const NormObjectSize& b) const {return (size > b.size);} NormObjectSize operator*(const NormObjectSize& b) const { NormObjectSize result(size); result.size *= b.size; return result; } // Note: this is a "round-upwards" division operator NormObjectSize operator/(const NormObjectSize& b) const { NormObjectSize result(size); result.size /= b.size; result.size = ((result.size * b.size) < size) ? result.size + 1 : result.size; return result; } private: Offset size; }; // end class NormObjectSize #ifndef _NORM_API typedef UINT32 NormNodeId; const NormNodeId NORM_NODE_NONE = 0x00000000; const NormNodeId NORM_NODE_ANY = 0xffffffff; #endif // !_NORM_API class NormObjectId { public: NormObjectId() : value(0) {}; NormObjectId(UINT16 id) {value = id;} NormObjectId(const NormObjectId& id) {value = id.value;} operator UINT16() const {return value;} //INT16 operator-(const NormObjectId& id) const // {return ((INT16)(value - id.value));} bool operator<(const NormObjectId& id) const { UINT16 diff = value - id.value; return ((diff > 0x8000) || ((0x8000 == diff) && (value > id.value))); } bool operator>(const NormObjectId& id) const { UINT16 diff = id.value - value; return ((diff > 0x8000) || ((0x8000 == diff) && (id.value > value))); } bool operator<=(const NormObjectId& id) const {return ((value == id.value) || (*this < id));} bool operator>=(const NormObjectId& id) const {return ((value == id.value) || (*this > id));} bool operator==(const NormObjectId& id) const {return (value == id.value);} bool operator!=(const NormObjectId& id) const {return (value != id.value);} void operator-=(UINT16 delta) {value -= delta;} const char* GetValuePtr() const {return (const char*)(&value);} NormObjectId& operator++(int) {value++; return *this;} NormObjectId& operator--(int) {value--; return *this;} private: UINT16 value; }; // end class NormObjectId class NormBlockId { public: NormBlockId() {}; NormBlockId(UINT32 id) : value(id) {} UINT32 GetValue() const {return value;} const char* GetValuePtr() const {return ((const char*)&value);} bool operator==(const NormBlockId& id) const {return (value == id.value);} bool operator!=(const NormBlockId& id) const {return (value != id.value);} // These static helper methods provide a "mask" parameter to allow // for different bit-length NormBlockId values (depends upon FEC encoding scheme). // The "mask" is in the eye-of-the-beholder, i.e., the given NormObject // or NormBlockBuffer that is manipulating block id values for protocol purposes // provide the FEC block "mask" to these methods. // We may reconsider adding the "mask" as a NormBlockId member variable, but since // NormBlockId usage is so ubiquitous, it could be added overhead consider within // the context of sender, object, etc a common fec_block_mask is used. // Compute difference of (a - b). If a non-zero bit "mask" is given, the difference // is for the masked word size (e.g., mask = 0x00ffffff is a 24-bit integer). static INT32 Difference(const NormBlockId& a, const NormBlockId& b, UINT32 mask) { if (mask) { UINT32 sign = (mask ^ (mask >> 1)); UINT32 result = a.value - b.value; if (0 == (result & sign)) return (INT32)(result & mask); else if ((result != sign) || (a.value < b.value)) return (INT32)(result | ~mask); else return (INT32)(result & mask); } else { return ((INT32)(a.value - b.value)); } } // Compare two block ids. If a non-zero bit "mask" is given, the comparison // is a "sliding window" (signed) over the bit space. Otherwise, it is // simply an unsigned value comparison. // Returns -1, 0, +1 for (a < b), (a == b), and (a > b), respectively static int Compare(const NormBlockId& a, const NormBlockId& b, UINT32 mask) { if (mask) { // "Sliding window" comparison INT32 delta = Difference(a, b, mask); if (delta < 0) return -1; else if (0 == delta) return 0; else // if delta > 0 return 1; } else if (a.value < b.value) { return -1; } else if (a.value == b.value) { return 0; } else // if (a > b) { return 1; } } void Increment(UINT32 i, UINT32 mask) { value = value + i; if (mask) value &= mask; } void Decrement(UINT32 i, UINT32 mask) { if (mask && (value < i)) value = (mask - (i - value) + 1); else value -= i; } private: UINT32 value; }; // end class NormBlockId typedef UINT16 NormSymbolId; typedef NormSymbolId NormSegmentId; // Base class for NORM header extensions class NormHeaderExtension { public: enum Type { INVALID = 0, FTI = 64, // FEC Object Transmission Information (FTI) extension CC_FEEDBACK = 3, // NORM-CC Feedback extension CC_RATE = 128, // NORM-CC Rate extension APP_ACK = 65 // app-defined ACK extension (see NormSetWatermarkEx()) }; NormHeaderExtension(); virtual ~NormHeaderExtension() {} virtual void Init(UINT32* theBuffer, UINT16 numBytes) { // TBD - should we confirm that 'numBytes' is sufficient AttachBuffer(theBuffer, numBytes); SetType(INVALID); SetWords(0); } void SetType(Type type) {((UINT8*)buffer)[TYPE_OFFSET] = (UINT8)type;} void SetWords(UINT8 words) {((UINT8*)buffer)[LENGTH_OFFSET] = words;} void AttachBuffer(const UINT32* theBuffer, UINT16 bufferLength) { buffer = (UINT32*)theBuffer; buffer_length = bufferLength; } const UINT32* GetBuffer() {return buffer;} Type GetType() const {return buffer ? (Type)(((UINT8*)buffer)[TYPE_OFFSET]) : INVALID;} UINT16 GetLength() const { return (buffer ? ((GetType() < 128) ? ((((UINT8*)buffer)[LENGTH_OFFSET]) << 2) : 4) : 0); } // These currently only used for APP_ACK extension const char* GetContent() {return (((const char*)buffer) + CONTENT_OFFSET);} UINT16 GetContentLength() const { UINT16 totalLen = GetLength(); return ((totalLen > CONTENT_OFFSET) ? (totalLen - CONTENT_OFFSET) : 0); } static UINT16 GetContentOffset() {return (UINT16)CONTENT_OFFSET;} protected: enum { TYPE_OFFSET = 0, // UINT8 offset LENGTH_OFFSET = TYPE_OFFSET + 1, // UINT8 offset CONTENT_OFFSET = LENGTH_OFFSET + 1 // UINT8 offset }; UINT32* buffer; UINT16 buffer_length; }; // end class NormHeaderExtension // This class is what we use to set/get // FEC Payload Id content. The FEC Payload // Id format is dependent upon the "fec_id" (FEC Type) // and, in some cases, its field size ("m") parameter class NormPayloadId { public: enum FecType { RS = 2, // fully-specified, general purpose Reed-Solomon RS8 = 5, // fully-specified 8-bit Reed-Solmon per RFC 5510 SB = 129 // partially-specified "small block" codes }; static bool IsValid(UINT8 fecId) { switch (fecId) { case 2: case 5: case 129: return true; default: return false; } } NormPayloadId(UINT8 fecId, UINT8 m, UINT32* theBuffer) : fec_id(fecId), fec_m(m), buffer(theBuffer) {} NormPayloadId(UINT8 fecId, UINT8 m, const UINT32* theBuffer) : fec_id(fecId), fec_m(m), cbuffer(theBuffer) {} static UINT16 GetLength(UINT8 fecId) { switch (fecId) { case 2: case 5: return 4; case 129: return 8; default: return 0; } } static UINT32 GetFecBlockMask(UINT8 fecId, UINT8 fecM) { switch (fecId) { case 2: if (8 == fecM) return 0x00ffffff; // 24-bit blockId, 8-bit symbolId else // (16 == fec_m) return 0x0000ffff; // 16-bit blockId,, 16-bit symbolId case 5: return 0x00ffffff; // 24-bit blockId case 129: return 0xffffffff; // 32-bit blockId default: return 0x00000000; // invalid fecId } } void SetFecPayloadId(UINT32 blockId, UINT16 symbolId, UINT16 blockLen) { switch (fec_id) { case 2: if (8 == fec_m) { blockId = (blockId << 8) | (symbolId & 0x00ff); *buffer = htonl(blockId); // 3 + 1 bytes } else // (16 == fec_m) { UINT16* payloadId = (UINT16*)buffer; payloadId[0] = htons(blockId); // 2 bytes payloadId[1] = htons(symbolId); // 2 bytes } break; case 5: blockId = (blockId << 8) | (symbolId & 0x00ff); *buffer = htonl(blockId); // 3 + 1 bytes break; case 129: *buffer = htonl(blockId); // 4 bytes UINT16* ptr = (UINT16*)(buffer + 1); ptr[0] = htons(blockLen); // 2 bytes ptr[1] = htons(symbolId); // 2 bytes break; } } // Message processing methods NormBlockId GetFecBlockId() const { switch (fec_id) { case 2: if (8 == fec_m) { UINT32 blockId = ntohl(*cbuffer); return (0x00ffffff & (blockId >> 8)); } else // (16 == fec_m) { UINT16* blockId = (UINT16*)cbuffer; return ntohs(*blockId); } case 5: { UINT32 blockId = ntohl(*cbuffer); return (0x00ffffff & (blockId >> 8)); } case 129: return ntohl(*cbuffer); default: ASSERT(0); return 0; } } UINT16 GetFecSymbolId() const { switch (fec_id) { case 2: if (8 == fec_m) { UINT32 payloadId = ntohl(*cbuffer); return (0x000000ff & payloadId); // lsb is symbolId } else // ( 16 == fec_m) { UINT16* payloadId = (UINT16*)cbuffer; return ntohs(payloadId[1]); } case 5: { UINT32 payloadId = ntohl(*cbuffer); return (0x000000ff & payloadId); // lsb is symbolId } case 129: { UINT16* ptr = (UINT16*)(cbuffer + 1); return ntohs(ptr[1]); } default: ASSERT(0); return 0; } } UINT16 GetFecBlockLength() const { if (129 == fec_id) { UINT16* blockLen = (UINT16*)(cbuffer + 1); return ntohs(*blockLen); } else { return 0; } } private: UINT8 fec_id; UINT8 fec_m; union { UINT32* buffer; const UINT32* cbuffer; }; }; // end class NormPayloadId class NormMsg { friend class NormMessageQueue; public: enum Type { INVALID = 0, INFO = 1, DATA = 2, CMD = 3, NACK = 4, ACK = 5, REPORT = 6 }; enum {MAX_SIZE = 65536}; NormMsg(); // Message building routines void SetVersion(UINT8 version) { ((UINT8*)buffer)[VERSION_OFFSET] = (((UINT8*)buffer)[VERSION_OFFSET] & 0x0f) | (version << 4); } void SetType(NormMsg::Type type) { ((UINT8*)buffer)[TYPE_OFFSET] = (((UINT8*)buffer)[VERSION_OFFSET] & 0xf0) | (type & 0x0f); } void SetSequence(UINT16 sequence) { ((UINT16*)buffer)[SEQUENCE_OFFSET] = htons(sequence); } void SetSourceId(NormNodeId sourceId) { buffer[SOURCE_ID_OFFSET] = htonl(sourceId); } // For messages to be sent, "addr" is destination void SetDestination(const ProtoAddress& dst) {addr = dst;} // For message received, "addr" is source void SetSource(const ProtoAddress& src) {addr = src;} void AttachExtension(NormHeaderExtension& extension) { extension.Init(buffer+(header_length/4), MAX_SIZE - header_length); ExtendHeaderLength(extension.GetLength()); } // Only use this for extensions that have content appended after attachment // (Fixed-length extensions should set their length upon Init()) void PackExtension(NormHeaderExtension& extension) { ExtendHeaderLength(2 + extension.GetContentLength()); } // Message processing routines bool InitFromBuffer(UINT16 msgLength); bool CopyFromBuffer(const char* theBuffer, unsigned int theLength) { if (theLength > MAX_SIZE) return false; memcpy(buffer, theBuffer, theLength); return InitFromBuffer(theLength); } UINT8 GetVersion() const {return (((UINT8*)buffer)[VERSION_OFFSET] >> 4);} NormMsg::Type GetType() const {return (Type)(((UINT8*)buffer)[TYPE_OFFSET] & 0x0f);} UINT16 GetHeaderLength() const {return ((UINT8*)buffer)[HDR_LEN_OFFSET] << 2;} UINT16 GetBaseHeaderLength() {return header_length_base;} UINT16 GetSequence() const { return (ntohs((((UINT16*)buffer)[SEQUENCE_OFFSET]))); } NormNodeId GetSourceId() const { return (ntohl(buffer[SOURCE_ID_OFFSET])); } const ProtoAddress& GetDestination() const {return addr;} const ProtoAddress& GetSource() const {return addr;} const char* GetBuffer() const {return ((char*)buffer);} UINT16 GetLength() const {return length;} void Display() const; // hex output to log // To retrieve any attached header extensions bool HasExtensions() const {return (header_length > header_length_base);} bool HasExtension(NormHeaderExtension::Type extType); bool GetNextExtension(NormHeaderExtension& ext) const { const UINT32* currentBuffer = ext.GetBuffer(); // 'nextOffset' here is a UINT32 offset UINT16 nextOffset = (UINT16)(currentBuffer ? (currentBuffer - buffer + (ext.GetLength()/4)) : (header_length_base/4)); bool result = HasExtensions() ? (nextOffset < (header_length/4)) : false; if (result) { UINT16 nextLength = ((UINT8*)(buffer + nextOffset))[1] << 2; ext.AttachBuffer(buffer+nextOffset, nextLength); } else { ext.AttachBuffer((UINT32*)NULL, 0); } return result; } // For message reception and misc. char* AccessBuffer() {return ((char*)buffer);} ProtoAddress& AccessAddress() {return addr;} NormMsg* GetNext() {return next;} protected: // Common message header offsets // All of our offsets reflect their offset based on the field size! // (So we can efficiently dereference msg fields with proper alignment) enum { VERSION_OFFSET = 0, TYPE_OFFSET = VERSION_OFFSET, HDR_LEN_OFFSET = VERSION_OFFSET+1, SEQUENCE_OFFSET = (HDR_LEN_OFFSET+1)/2, SOURCE_ID_OFFSET = ((SEQUENCE_OFFSET*2)+2)/4, MSG_OFFSET = (SOURCE_ID_OFFSET*4)+4 }; void SetBaseHeaderLength(UINT16 len) { ((UINT8*)buffer)[HDR_LEN_OFFSET] = len >> 2; length = header_length_base = header_length = len; } void ExtendHeaderLength(UINT16 len) { header_length += len; length += len; ((UINT8*)buffer)[HDR_LEN_OFFSET] = header_length >> 2; } UINT32 buffer[MAX_SIZE / sizeof(UINT32)]; UINT16 length; // in bytes UINT16 header_length; UINT16 header_length_base; ProtoAddress addr; // src or dst address NormMsg* prev; NormMsg* next; }; // end class NormMsg // "NormObjectMsg" is a base class for the similar "NormInfoMsg" // and "NormDataMsg" types class NormObjectMsg : public NormMsg { friend class NormMsg; public: enum Flag { FLAG_REPAIR = 0x01, FLAG_EXPLICIT = 0x02, FLAG_INFO = 0x04, FLAG_UNRELIABLE = 0x08, FLAG_FILE = 0x10, FLAG_STREAM = 0x20, FLAG_SYN = 0x40 //FLAG_MSG_START = 0x40 deprecated }; UINT16 GetInstanceId() const {return (ntohs(((UINT16*)buffer)[INSTANCE_ID_OFFSET]));} UINT8 GetGrtt() const {return ((UINT8*)buffer)[GRTT_OFFSET];} UINT8 GetBackoffFactor() const {return ((((UINT8*)buffer)[GSIZE_OFFSET] >> 4) & 0x0f);} UINT8 GetGroupSize() const {return (((UINT8*)buffer)[GSIZE_OFFSET] & 0x0f);} bool FlagIsSet(NormObjectMsg::Flag flag) const {return (0 != (flag & ((UINT8*)buffer)[FLAGS_OFFSET]));} bool IsStream() const {return FlagIsSet(FLAG_STREAM);} UINT8 GetFecId() const {return ((UINT8*)buffer)[FEC_ID_OFFSET];} NormObjectId GetObjectId() const {return (ntohs(((UINT16*)buffer)[OBJ_ID_OFFSET]));} // Message building routines void SetInstanceId(UINT16 instanceId) {((UINT16*)buffer)[INSTANCE_ID_OFFSET] = htons(instanceId);} void SetGrtt(UINT8 grtt) {((UINT8*)buffer)[GRTT_OFFSET] = grtt;} void SetBackoffFactor(UINT8 backoff) {((UINT8*)buffer)[BACKOFF_OFFSET] = (((UINT8*)buffer)[GSIZE_OFFSET] & 0x0f) | (backoff << 4);} void SetGroupSize(UINT8 gsize) {((UINT8*)buffer)[GSIZE_OFFSET] = (((UINT8*)buffer)[GSIZE_OFFSET] & 0xf0) | gsize;} void ResetFlags() {((UINT8*)buffer)[FLAGS_OFFSET] = 0;} void SetFlag(NormObjectMsg::Flag flag) {((UINT8*)buffer)[FLAGS_OFFSET] |= flag;} void SetObjectId(const NormObjectId& objectId) {((UINT16*)buffer)[OBJ_ID_OFFSET] = htons((UINT16)objectId);} protected: enum { INSTANCE_ID_OFFSET = MSG_OFFSET/2, GRTT_OFFSET = (INSTANCE_ID_OFFSET*2)+2, BACKOFF_OFFSET = GRTT_OFFSET+1, GSIZE_OFFSET = BACKOFF_OFFSET, FLAGS_OFFSET = GSIZE_OFFSET+1, FEC_ID_OFFSET = FLAGS_OFFSET+1, OBJ_ID_OFFSET = (FEC_ID_OFFSET+1)/2, OBJ_MSG_OFFSET = (OBJ_ID_OFFSET*2)+2 }; }; // end class NormObjectMsg // This FEC Object Transmission Information assumes "fec_id" == 2 (RFC 5510) // (This is an m-bit Reed-Solomon codec (we use it in NORM for m == 16) class NormFtiExtension2 : public NormHeaderExtension { public: // To build the FTI Header Extension virtual void Init(UINT32* theBuffer, UINT16 numBytes) { AttachBuffer(theBuffer, numBytes); SetType(FTI); // HET = 64 SetWords(4); } void SetObjectSize(const NormObjectSize& objectSize) { ((UINT16*)buffer)[OBJ_SIZE_MSB_OFFSET] = htons(objectSize.MSB()); buffer[OBJ_SIZE_LSB_OFFSET] = htonl(objectSize.LSB()); } void SetFecFieldSize(UINT8 numBits) {((UINT8*)buffer)[FEC_M_OFFSET] = numBits;} // usually 16 for this FTI void SetFecGroupSize(UINT8 symbolsPerPkt) {((UINT8*)buffer)[FEC_G_OFFSET] = symbolsPerPkt;} // usually one void SetSegmentSize(UINT16 segmentSize) {((UINT16*)buffer)[SEG_SIZE_OFFSET] = htons(segmentSize);} void SetFecMaxBlockLen(UINT16 ndata) {((UINT16*)buffer)[FEC_NDATA_OFFSET] = htons(ndata);} void SetFecNumParity(UINT16 nparity) {((UINT16*)buffer)[FEC_NPARITY_OFFSET] = htons(nparity);} // FTI Extension parsing methods NormObjectSize GetObjectSize() const { return NormObjectSize(ntohs(((UINT16*)buffer)[OBJ_SIZE_MSB_OFFSET]), ntohl(buffer[OBJ_SIZE_LSB_OFFSET])); } UINT8 GetFecFieldSize() const {return ((UINT8*)buffer)[FEC_M_OFFSET];} // usually 16 for this FTI UINT8 GetFecGroupSize() const {return ((UINT8*)buffer)[FEC_G_OFFSET];} // usually 1 UINT16 GetSegmentSize() const {return (ntohs(((UINT16*)buffer)[SEG_SIZE_OFFSET]));} UINT16 GetFecMaxBlockLen() const {return (ntohs(((UINT16*)buffer)[FEC_NDATA_OFFSET]));} UINT16 GetFecNumParity() const {return (ntohs(((UINT16*)buffer)[FEC_NPARITY_OFFSET]));} private: enum { OBJ_SIZE_MSB_OFFSET = (LENGTH_OFFSET + 1)/2, OBJ_SIZE_LSB_OFFSET = ((OBJ_SIZE_MSB_OFFSET*2)+2)/4, FEC_M_OFFSET = ((OBJ_SIZE_LSB_OFFSET*4)+4), FEC_G_OFFSET = FEC_M_OFFSET + 1, SEG_SIZE_OFFSET = (FEC_G_OFFSET+1)/2, FEC_NDATA_OFFSET = ((SEG_SIZE_OFFSET*2)+2)/2, FEC_NPARITY_OFFSET = ((FEC_NDATA_OFFSET*2)+2)/2 }; }; // end class NormFtiExtension2 // Helper class for containing key FTI params class NormFtiData { public: NormFtiData() : object_size(NormObjectSize(0)), segment_size(0), num_data(0), num_parity(0), fec_m(0), instance_id(0) {} ~NormFtiData() {} bool IsValid() const {return (0 != segment_size);} void Invalidate() { object_size = 0; segment_size = num_data = num_parity = instance_id = 0; fec_m = 0; } void SetObjectSize(const NormObjectSize& objectSize) {object_size = objectSize;} void SetSegmentSize(UINT16 segmentSize) {segment_size = segmentSize;} void SetFecMaxBlockLen(UINT16 numData) {num_data = numData;} void SetFecNumParity(UINT16 numParity) {num_parity = numParity;} void SetFecFieldSize(UINT8 fecM) {fec_m = fecM;} void SetFecInstanceId(UINT16 instanceId) {instance_id = instanceId;} const NormObjectSize& GetObjectSize() const {return object_size;} UINT16 GetSegmentSize() const {return segment_size;} UINT16 GetFecMaxBlockLen() const {return num_data;} UINT16 GetFecNumParity() const {return num_parity;} UINT8 GetFecFieldSize() const {return fec_m;} UINT16 GetFecInstanceId() const {return instance_id;} private: NormObjectSize object_size; UINT16 segment_size; UINT16 num_data; UINT16 num_parity; UINT8 fec_m; UINT16 instance_id; }; // end class NormFtiData // This FEC Object Transmission Information assumes "fec_id" == 5 (RFC 5510) // (this is the fully-defined 8-bit Reed-Solomon codec) class NormFtiExtension5 : public NormHeaderExtension { public: // To build the fec_id=5 FTI Header Extension virtual void Init(UINT32* theBuffer, UINT16 numBytes) { AttachBuffer(theBuffer, numBytes); SetType(FTI); // HET = 64 SetWords(3); } void SetObjectSize(const NormObjectSize& objectSize) { ((UINT16*)buffer)[OBJ_SIZE_MSB_OFFSET] = htons(objectSize.MSB()); buffer[OBJ_SIZE_LSB_OFFSET] = htonl(objectSize.LSB()); } void SetSegmentSize(UINT16 segmentSize) {((UINT16*)buffer)[SEG_SIZE_OFFSET] = htons(segmentSize);} void SetFecMaxBlockLen(UINT8 ndata) {((UINT8*)buffer)[FEC_NDATA_OFFSET] = ndata;} void SetFecNumParity(UINT8 nparity) {((UINT8*)buffer)[FEC_NPARITY_OFFSET] = nparity;} // FTI Extension parsing methods NormObjectSize GetObjectSize() const { return NormObjectSize(ntohs(((UINT16*)buffer)[OBJ_SIZE_MSB_OFFSET]), ntohl(buffer[OBJ_SIZE_LSB_OFFSET])); } UINT16 GetSegmentSize() const {return (ntohs(((UINT16*)buffer)[SEG_SIZE_OFFSET]));} UINT8 GetFecMaxBlockLen() const {return (((UINT8*)buffer)[FEC_NDATA_OFFSET]);} UINT8 GetFecNumParity() const {return (((UINT8*)buffer)[FEC_NPARITY_OFFSET]);} private: enum { OBJ_SIZE_MSB_OFFSET = (CONTENT_OFFSET)/2, // UINT16 offset OBJ_SIZE_LSB_OFFSET = ((OBJ_SIZE_MSB_OFFSET*2)+2)/4,// UINT32 offset SEG_SIZE_OFFSET = ((OBJ_SIZE_LSB_OFFSET*4)+4)/2, // UINT16 offset FEC_NDATA_OFFSET = ((SEG_SIZE_OFFSET+1)*2), // UINT8 offset FEC_NPARITY_OFFSET = (FEC_NDATA_OFFSET+1) // UINT8 offset }; }; // end class NormFtiExtension5 class NormAppAckExtension : public NormHeaderExtension { public: virtual void Init(UINT32* theBuffer, UINT16 numBytes) { AttachBuffer(theBuffer, numBytes); SetType(APP_ACK); SetWords(0); } bool SetContent(const char* data, UINT16 dataLen) { if (dataLen > (buffer_length - CONTENT_OFFSET)) return false; memcpy(((char*)buffer) + CONTENT_OFFSET, data, dataLen); if (dataLen > 2) { // Pad out to get 32-bit alignment of extension dataLen += CONTENT_OFFSET; UINT16 padLen = dataLen % 4; if (padLen) padLen = 4 - padLen; dataLen += padLen; SetWords(dataLen/4); } else { SetWords(1); } return true; } }; // end class NormAppAckExtension // This FEC Object Transmission Information assumes "fec_id" == 129 class NormFtiExtension129 : public NormHeaderExtension { public: // To build the FTI Header Extension // (TBD) allow for different "fec_id" types in the future virtual void Init(UINT32* theBuffer, UINT16 numBytes) { AttachBuffer(theBuffer, numBytes); SetType(FTI); SetWords(4); } void SetFecInstanceId(UINT16 instanceId) { ((UINT16*)buffer)[FEC_INSTANCE_OFFSET] = htons(instanceId);} void SetFecMaxBlockLen(UINT16 ndata) {((UINT16*)buffer)[FEC_NDATA_OFFSET] = htons(ndata);} void SetFecNumParity(UINT16 nparity) {((UINT16*)buffer)[FEC_NPARITY_OFFSET] = htons(nparity);} void SetSegmentSize(UINT16 segmentSize) {((UINT16*)buffer)[SEG_SIZE_OFFSET] = htons(segmentSize);} void SetObjectSize(const NormObjectSize& objectSize) { ((UINT16*)buffer)[OBJ_SIZE_MSB_OFFSET] = htons(objectSize.MSB()); buffer[OBJ_SIZE_LSB_OFFSET] = htonl(objectSize.LSB()); } // FTI Extension parsing methods UINT16 GetFecInstanceId() const { return (ntohs(((UINT16*)buffer)[FEC_INSTANCE_OFFSET])); } UINT16 GetFecMaxBlockLen() const {return (ntohs(((UINT16*)buffer)[FEC_NDATA_OFFSET]));} UINT16 GetFecNumParity() const {return (ntohs(((UINT16*)buffer)[FEC_NPARITY_OFFSET]));} UINT16 GetSegmentSize() const {return (ntohs(((UINT16*)buffer)[SEG_SIZE_OFFSET]));} NormObjectSize GetObjectSize() const { return NormObjectSize(ntohs(((UINT16*)buffer)[OBJ_SIZE_MSB_OFFSET]), ntohl(buffer[OBJ_SIZE_LSB_OFFSET])); } private: enum { OBJ_SIZE_MSB_OFFSET = (LENGTH_OFFSET + 1)/2, OBJ_SIZE_LSB_OFFSET = ((OBJ_SIZE_MSB_OFFSET*2)+2)/4, FEC_INSTANCE_OFFSET = ((OBJ_SIZE_LSB_OFFSET*4)+4)/2, SEG_SIZE_OFFSET = ((FEC_INSTANCE_OFFSET*2)+2)/2, FEC_NDATA_OFFSET = ((SEG_SIZE_OFFSET*2)+2)/2, FEC_NPARITY_OFFSET = ((FEC_NDATA_OFFSET*2)+2)/2 }; }; // end class NormFtiExtension129 class NormInfoMsg : public NormObjectMsg { public: void Init() { SetType(INFO); SetBaseHeaderLength(INFO_HEADER_LEN); ResetFlags(); } UINT16 GetInfoLen() const {return (length - header_length);} const char* GetInfo() const {return (((char*)buffer) + header_length);} // Message building methods (in addition to NormObjectMsg fields) void SetFecId(UINT8 fecId) {((UINT8*)buffer)[FEC_ID_OFFSET] = fecId;} // Note: apply any header extensions first void SetInfo(const char* data, UINT16 size) { memcpy(((char*)buffer)+header_length, data, size); length = size + header_length; } private: enum {INFO_HEADER_LEN = OBJ_MSG_OFFSET}; }; // end class NormInfoMsg class NormDataMsg : public NormObjectMsg { public: void Init() { SetType(DATA); ResetFlags(); // Note: for NORM_DATA base header length depends on fec_id } // Message building methods (in addition to NormObjectMsg fields) void SetFecId(UINT8 fecId) { ((UINT8*)buffer)[FEC_ID_OFFSET] = fecId; SetBaseHeaderLength(OBJ_MSG_OFFSET + NormPayloadId::GetLength(fecId)); } void SetFecPayloadId(UINT8 fecId, UINT32 blockId, UINT16 symbolId, UINT16 blockLen, UINT8 m) { NormPayloadId payloadId(fecId, m, buffer + FEC_PAYLOAD_ID_OFFSET); payloadId.SetFecPayloadId(blockId, symbolId, blockLen); } // Two ways to set payload content: // 1) Directly access payload to copy segment, then set data message length // (Note NORM_STREAM_OBJECT segments must already include "payload_len" // and "payload_offset" with the "payload_data" char* AccessPayload() {return (((char*)buffer)+header_length);} // For NORM_STREAM_OBJECT segments, "dataLength" must include the PAYLOAD_HEADER_LENGTH void SetPayloadLength(UINT16 payloadLength) {length = header_length + payloadLength;} // Set "payload" directly (useful for FEC parity segments) void SetPayload(char* payload, UINT16 payloadLength) { memcpy(((char*)buffer)+header_length, payload, payloadLength); length = header_length + payloadLength; } // AccessPayloadData() (useful for setting ZERO padding) char* AccessPayloadData() { UINT16 payloadIndex = IsStream() ? header_length+PAYLOAD_DATA_OFFSET : header_length; return (((char*)buffer)+payloadIndex); } // Message processing methods NormBlockId GetFecBlockId(UINT8 m) const { NormPayloadId payloadId(GetFecId(), m, buffer + FEC_PAYLOAD_ID_OFFSET); return payloadId.GetFecBlockId(); } UINT16 GetFecSymbolId(UINT8 m) const { NormPayloadId payloadId(GetFecId(), m, buffer + FEC_PAYLOAD_ID_OFFSET); return payloadId.GetFecSymbolId(); } UINT16 GetFecBlockLength() const { NormPayloadId payloadId(GetFecId(), 8, buffer + FEC_PAYLOAD_ID_OFFSET); return payloadId.GetFecBlockLength(); } // Note: For NORM_OBJECT_STREAM, "payload" includes "payload_reserved", // "payload_len", "payload_offset", and "payload_data" fields // For NORM_OBJECT_FILE and NORM_OBJECT_DATA, "payload" includes // "payload_data" only const char* GetPayload() const {return (((char*)buffer)+header_length);} UINT16 GetPayloadLength() const {return (length - header_length);} const char* GetPayloadData() const { UINT16 dataIndex = IsStream() ? header_length+PAYLOAD_DATA_OFFSET : header_length; return (((char*)buffer)+dataIndex); } UINT16 GetPayloadDataLength() const { UINT16 dataIndex = IsStream() ? header_length+PAYLOAD_DATA_OFFSET : header_length; return (length - dataIndex); } // These routines are only applicable to messages containing NORM_OBJECT_STREAM content // Some static helper routines for reading/writing embedded payload length/offsets static UINT16 GetStreamPayloadHeaderLength() {return (PAYLOAD_DATA_OFFSET);} static void WriteStreamPayloadLength(char* payload, UINT16 len) { UINT16 temp16 = htons(len); memcpy(payload+PAYLOAD_LENGTH_OFFSET, &temp16, 2); } static void WriteStreamPayloadMsgStart(char* payload, UINT16 msgStartOffset) { UINT16 temp16 = htons(msgStartOffset); memcpy(payload+PAYLOAD_MSG_START_OFFSET, &temp16, 2); } static void WriteStreamPayloadOffset(char* payload, UINT32 offset) { UINT32 temp32 = htonl(offset); memcpy(payload+PAYLOAD_OFFSET_OFFSET, &temp32, 4); } static UINT16 ReadStreamPayloadLength(const char* payload) { UINT16 temp16; memcpy(&temp16, payload+PAYLOAD_LENGTH_OFFSET, 2); return (ntohs(temp16)); } static UINT16 ReadStreamPayloadMsgStart(const char* payload) { UINT16 temp16; memcpy(&temp16, payload+PAYLOAD_MSG_START_OFFSET, 2); return (ntohs(temp16)); } static UINT32 ReadStreamPayloadOffset(const char* payload) { UINT32 temp32; memcpy(&temp32, payload+PAYLOAD_OFFSET_OFFSET, 4); return (ntohl(temp32)); } private: enum { FEC_PAYLOAD_ID_OFFSET = OBJ_MSG_OFFSET/4 }; // IMPORTANT: These offsets are _relative_ to the NORM_DATA header // (incl. any extensions) enum { PAYLOAD_LENGTH_OFFSET = 0, PAYLOAD_MSG_START_OFFSET = PAYLOAD_LENGTH_OFFSET+2, PAYLOAD_OFFSET_OFFSET = PAYLOAD_MSG_START_OFFSET+2, PAYLOAD_DATA_OFFSET = PAYLOAD_OFFSET_OFFSET+4 }; }; // end class NormDataMsg class NormCmdMsg : public NormMsg { public: enum Flavor { INVALID = 0, FLUSH = 1, EOT = 2, SQUELCH = 3, CC = 4, REPAIR_ADV = 5, ACK_REQ = 6, APPLICATION = 7 }; // Message build void SetInstanceId(UINT16 instanceId) {((UINT16*)buffer)[INSTANCE_ID_OFFSET] = htons(instanceId);} void SetGrtt(UINT8 quantizedGrtt) {((UINT8*)buffer)[GRTT_OFFSET] = quantizedGrtt;} void SetBackoffFactor(UINT8 backoff) {((UINT8*)buffer)[BACKOFF_OFFSET] = (((UINT8*)buffer)[GSIZE_OFFSET] & 0x0f) | (backoff << 4);} void SetGroupSize(UINT8 gsize) {((UINT8*)buffer)[GSIZE_OFFSET] = (((UINT8*)buffer)[GSIZE_OFFSET] & 0xf0) | gsize;} void SetFlavor(NormCmdMsg::Flavor flavor) {((UINT8*)buffer)[FLAVOR_OFFSET] = (UINT8)flavor;} // Message parse UINT16 GetInstanceId() const {return (ntohs(((UINT16*)buffer)[INSTANCE_ID_OFFSET]));} UINT8 GetGrtt() const {return ((UINT8*)buffer)[GRTT_OFFSET];} UINT8 GetBackoffFactor() const {return ((((UINT8*)buffer)[GSIZE_OFFSET] >> 4) & 0x0f);} UINT8 GetGroupSize() const {return (((UINT8*)buffer)[GSIZE_OFFSET] & 0x0f);} NormCmdMsg::Flavor GetFlavor() const {return (Flavor)((UINT8*)buffer)[FLAVOR_OFFSET];} protected: friend class NormMsg; enum { INSTANCE_ID_OFFSET = MSG_OFFSET/2, GRTT_OFFSET = (INSTANCE_ID_OFFSET+1)*2, BACKOFF_OFFSET = GRTT_OFFSET + 1, GSIZE_OFFSET = BACKOFF_OFFSET, FLAVOR_OFFSET = GSIZE_OFFSET + 1 }; }; // end class NormCmdMsg class NormCmdFlushMsg : public NormCmdMsg { friend class NormMsg; public: void Init() { SetType(CMD); SetFlavor(FLUSH); // base header length depends on fec payload id } void SetFecId(UINT8 fecId) {((UINT8*)buffer)[FEC_ID_OFFSET] = fecId;} void SetObjectId(const NormObjectId& objectId) {((UINT16*)buffer)[OBJ_ID_OFFSET] = htons((UINT16)objectId);} void SetFecPayloadId(UINT8 fecId, UINT32 blockId, UINT16 symbolId, UINT16 blockLen, UINT8 m) { SetFecId(fecId); SetBaseHeaderLength(4*FEC_PAYLOAD_ID_OFFSET + NormPayloadId::GetLength(fecId)); NormPayloadId payloadId(fecId, m, buffer + FEC_PAYLOAD_ID_OFFSET); payloadId.SetFecPayloadId(blockId, symbolId, blockLen); ResetAckingNodeList(); } void ResetAckingNodeList() {length = header_length;} bool AppendAckingNode(NormNodeId nodeId, UINT16 segmentSize) { if ((length-header_length + 4) > segmentSize) return false; buffer[length/4] = htonl((UINT32)nodeId); length += 4; return true; } // Message processing UINT8 GetFecId() const {return ((UINT8*)buffer)[FEC_ID_OFFSET];} NormObjectId GetObjectId() const { return ntohs(((UINT16*)buffer)[OBJ_ID_OFFSET]);} NormBlockId GetFecBlockId(UINT8 m) const { NormPayloadId payloadId(GetFecId(), m, buffer + FEC_PAYLOAD_ID_OFFSET); return payloadId.GetFecBlockId(); } UINT16 GetFecSymbolId(UINT8 m) const { NormPayloadId payloadId(GetFecId(), m, buffer + FEC_PAYLOAD_ID_OFFSET); return payloadId.GetFecSymbolId(); } UINT16 GetFecBlockLength() const { NormPayloadId payloadId(GetFecId(), 8, buffer + FEC_PAYLOAD_ID_OFFSET); return payloadId.GetFecBlockLength(); } UINT16 GetAckingNodeCount() const {return ((length - header_length) >> 2);} const UINT32* GetAckingNodeList() const {return (buffer+(header_length/4));} NormNodeId GetAckingNodeId(UINT16 index) const {return (ntohl(buffer[(header_length/4)+index]));} private: enum { FEC_ID_OFFSET = FLAVOR_OFFSET + 1, OBJ_ID_OFFSET = (FEC_ID_OFFSET + 1)/2, FEC_PAYLOAD_ID_OFFSET = ((OBJ_ID_OFFSET+1)*2)/4 }; }; // end class NormCmdFlushMsg class NormCmdEotMsg : public NormCmdMsg { public: void Init() { SetType(CMD); SetFlavor(EOT); SetBaseHeaderLength(EOT_HEADER_LEN); memset(((char*)buffer)+RESERVED_OFFSET, 0, 3); } private: enum { RESERVED_OFFSET = FLAVOR_OFFSET + 1, EOT_HEADER_LEN = RESERVED_OFFSET + 3 }; }; // end class NormCmdEotMsg class NormCmdSquelchMsg : public NormCmdMsg { public: // Message building void Init(UINT8 fecId) { SetType(CMD); SetFlavor(SQUELCH); SetFecId(fecId); // default "fec_id" SetBaseHeaderLength(4*FEC_PAYLOAD_ID_OFFSET + NormPayloadId::GetLength(fecId)); } void SetFecId(UINT8 fecId) {((UINT8*)buffer)[FEC_ID_OFFSET] = fecId;} void SetObjectId(const NormObjectId& objectId) {((UINT16*)buffer)[OBJ_ID_OFFSET] = htons((UINT16)objectId);} void SetFecPayloadId(UINT8 fecId, UINT32 blockId, UINT16 symbolId, UINT16 blockLen, UINT8 m) { SetFecId(fecId); SetBaseHeaderLength(4*FEC_PAYLOAD_ID_OFFSET + NormPayloadId::GetLength(fecId)); NormPayloadId payloadId(fecId, m, buffer + FEC_PAYLOAD_ID_OFFSET); payloadId.SetFecPayloadId(blockId, symbolId, blockLen); ResetInvalidObjectList(); } void ResetInvalidObjectList() {length = header_length;} // Note must apply any header extensions _before_ appending payload. bool AppendInvalidObject(NormObjectId objectId, UINT16 segmentSize) { if ((length-header_length+2) > segmentSize) return false; ((UINT16*)buffer)[length/2] = htons((UINT16)objectId); length += 2; return true; } // Message processing UINT8 GetFecId() const {return ((UINT8*)buffer)[FEC_ID_OFFSET];} NormObjectId GetObjectId() const {return (ntohs(((UINT16*)buffer)[OBJ_ID_OFFSET]));} NormBlockId GetFecBlockId(UINT8 m) const { NormPayloadId payloadId(GetFecId(), m, buffer + FEC_PAYLOAD_ID_OFFSET); return payloadId.GetFecBlockId(); } UINT16 GetFecSymbolId(UINT8 m) const { NormPayloadId payloadId(GetFecId(), m, buffer + FEC_PAYLOAD_ID_OFFSET); return payloadId.GetFecSymbolId(); } UINT16 GetFecBlockLength() const { NormPayloadId payloadId(GetFecId(), 8, buffer + FEC_PAYLOAD_ID_OFFSET); return payloadId.GetFecBlockLength(); } // Use these to parse invalid object list UINT16 GetInvalidObjectCount() const {return ((length - header_length) >> 1);} UINT16* GetInvalidObjectList() const {return (UINT16*)(buffer+header_length);} NormObjectId GetInvalidObjectId(UINT16 index) const {return (ntohs(((UINT16*)buffer)[(header_length/2)+index]));} private: enum { FEC_ID_OFFSET = FLAVOR_OFFSET + 1, OBJ_ID_OFFSET = (FEC_ID_OFFSET + 1)/2, FEC_PAYLOAD_ID_OFFSET = ((OBJ_ID_OFFSET+1)*2)/4 }; }; // end class NormCmdSquelchMsg // These flag values are used "cc_flags" field of NORM_CMD(CC) CC_NODE_LIST // items and NORM_NACK and NORM_ACK messages class NormCC { public: enum Flag { CLR = 0x01, PLR = 0x02, RTT = 0x04, START = 0x08, LEAVE = 0x10, LIMIT = 0x20 // experimental, non-RFC5740 }; // (set when included rate is measured, not calculated) }; // end class NormCC class NormCmdCCMsg : public NormCmdMsg { public: void Init() { SetType(CMD); SetFlavor(CC); SetBaseHeaderLength(CC_HEADER_LEN); ((UINT8*)buffer)[RESERVED_OFFSET] = 0; } void SetCCSequence(UINT16 ccSequence) { ((UINT16*)buffer)[CC_SEQUENCE_OFFSET] = htons(ccSequence); } void SetSendTime(const struct timeval& sendTime) { buffer[SEND_TIME_SEC_OFFSET] = htonl(sendTime.tv_sec); buffer[SEND_TIME_USEC_OFFSET] = htonl(sendTime.tv_usec); } UINT16 GetCCSequence() const {return (ntohs(((UINT16*)buffer)[CC_SEQUENCE_OFFSET]));} void GetSendTime(struct timeval& sendTime) const { sendTime.tv_sec = ntohl(buffer[SEND_TIME_SEC_OFFSET]); sendTime.tv_usec = ntohl(buffer[SEND_TIME_USEC_OFFSET]); } bool AppendCCNode(UINT16 segMax, NormNodeId nodeId, UINT8 flags, UINT8 rtt, UINT16 rate) { if ((length-header_length+CC_ITEM_SIZE)> segMax) return false; UINT32* ptr = buffer + length/4; ptr[CC_NODE_ID_OFFSET] = htonl(nodeId); ((UINT8*)ptr)[CC_FLAGS_OFFSET] = flags; ((UINT8*)ptr)[CC_RTT_OFFSET] = rtt; ((UINT16*)ptr)[CC_RATE_OFFSET] = htons(rate); length += CC_ITEM_SIZE; return true; } bool GetCCNode(NormNodeId nodeId, UINT8& flags, UINT8& rtt, UINT16& rate) const; // This function uses the "reserved" field of the NORM_CMD(CC) message // and is not strictly compliant with RFC 5740 when invoked. enum {FLAG_SYN = 0x01}; bool SynIsSet() const {return (0 != (FLAG_SYN & ((UINT8*)buffer)[RESERVED_OFFSET]));} void SetSyn() {((UINT8*)buffer)[RESERVED_OFFSET] = FLAG_SYN;} class Iterator; friend class Iterator; class Iterator { public: Iterator(const NormCmdCCMsg& msg); void Reset() {offset = 0;} bool GetNextNode(NormNodeId& nodeId, UINT8& flags, UINT8& rtt, UINT16& rate); private: const NormCmdCCMsg& cc_cmd; UINT16 offset; }; private: enum { RESERVED_OFFSET = FLAVOR_OFFSET + 1, CC_SEQUENCE_OFFSET = (RESERVED_OFFSET + 1)/2, SEND_TIME_SEC_OFFSET = ((CC_SEQUENCE_OFFSET*2)+2)/4, SEND_TIME_USEC_OFFSET = ((SEND_TIME_SEC_OFFSET*4)+4)/4, CC_HEADER_LEN = (SEND_TIME_USEC_OFFSET*4)+4 }; enum { CC_NODE_ID_OFFSET = 0, CC_FLAGS_OFFSET = CC_NODE_ID_OFFSET + 4, CC_RTT_OFFSET = CC_FLAGS_OFFSET + 1, CC_RATE_OFFSET = (CC_RTT_OFFSET + 1)/2, CC_ITEM_SIZE = (CC_RATE_OFFSET*2)+2 }; }; // end class NormCmdCCMsg class NormCCRateExtension : public NormHeaderExtension { public: virtual void Init(UINT32* theBuffer, UINT16 numBytes) { AttachBuffer(theBuffer, numBytes); SetType(CC_RATE); ((UINT8*)buffer)[RESERVED_OFFSET] = 0; } void SetSendRate(UINT16 sendRate) {((UINT16*)buffer)[SEND_RATE_OFFSET] = htons(sendRate);} UINT16 GetSendRate() {return (ntohs(((UINT16*)buffer)[SEND_RATE_OFFSET]));} private: enum { RESERVED_OFFSET = TYPE_OFFSET + 1, SEND_RATE_OFFSET = (RESERVED_OFFSET + 1)/2 }; }; // end class NormCCRateExtension // This implementation currently assumes "fec_id"= 129 class NormRepairRequest { public: class Iterator; friend class NormRepairRequest::Iterator; enum Form { INVALID = 0, ITEMS = 1, RANGES = 2, ERASURES = 3 }; enum Flag { SEGMENT = 0x01, BLOCK = 0x02, INFO = 0x04, OBJECT = 0x08 }; // Construction NormRepairRequest(); void Init(UINT32* bufferPtr, UINT16 bufferLen) { buffer = bufferPtr; buffer_len = bufferLen; length = 0; } // (TBD) these could be an enumeration for optimization static UINT16 RepairItemLength(UINT8 fecId) {return (4 + NormPayloadId::GetLength(fecId));} static UINT16 RepairRangeLength(UINT8 fecId) {return (2 * RepairItemLength(fecId));} static UINT16 ErasureItemLength(UINT8 fecId) {return RepairItemLength(fecId);} // Repair request building void SetForm(NormRepairRequest::Form theForm) {form = theForm;} void ResetFlags() {flags = 0;} void SetFlag(NormRepairRequest::Flag theFlag) {flags |= theFlag;} void ClearFlag(NormRepairRequest::Flag theFlag) {flags &= ~theFlag;} void SetFlags(int theFlags) {flags = theFlags;} // Returns length (each repair item requires 8 bytes of space) bool AppendRepairItem(UINT8 fecId, UINT8 fecM, const NormObjectId& objectId, const NormBlockId& blockId, UINT16 blockLen, UINT16 symbolId); bool AppendRepairRange(UINT8 fecId, UINT8 fecM, const NormObjectId& startObjectId, const NormBlockId& startBlockId, UINT16 startBlockLen, UINT16 startSymbolId, const NormObjectId& endObjectId, const NormBlockId& endBlockId, UINT16 endBlockLen, UINT16 endSymbolId); bool AppendErasureCount(UINT8 fecId, UINT8 fecM, const NormObjectId& objectId, const NormBlockId& blockId, UINT16 blockLen, UINT16 erasureCount); UINT16 Pack(); // Repair request processing UINT16 Unpack(const UINT32* bufferPtr, UINT16 bufferLen); NormRepairRequest::Form GetForm() const {return form;} bool FlagIsSet(NormRepairRequest::Flag theFlag) const {return (0 != (theFlag & flags));} int GetFlags() const {return flags;} UINT16 GetLength() const {return (ITEM_LIST_OFFSET + length);} UINT32* GetBuffer() const {return buffer;} // Outputs textual representation of RepairRequest content void Log(UINT8 fecId, UINT8 fecM) const; class Iterator { public: // Checks for matching fecId and assumes constant 'm' ?!?! Iterator(const NormRepairRequest& theRequest, UINT8 fecId, UINT8 fecM); void Reset() {offset = 0;} UINT16 NextRepairItem(NormObjectId* objectId, NormBlockId* blockId, UINT16* blockLen, UINT16* symbolId); private: const NormRepairRequest& request; UINT8 fec_id; UINT8 fec_m; UINT16 offset; }; // end class NormRepairRequest::Iterator private: UINT16 RetrieveRepairItem(UINT8 fecM, UINT16 offset, UINT8* fecId, NormObjectId* objectId, NormBlockId* blockId, UINT16* blockLen, UINT16* symbolId) const; enum { FORM_OFFSET = 0, FLAGS_OFFSET = FORM_OFFSET + 1, LENGTH_OFFSET = (FLAGS_OFFSET + 1)/2, ITEM_LIST_OFFSET = (LENGTH_OFFSET*2)+2 }; // These are the offsets for "fec_id" = 129 NormRepairRequest items enum { FEC_ID_OFFSET = 0, RESERVED_OFFSET = FEC_ID_OFFSET + 1, OBJ_ID_OFFSET = (RESERVED_OFFSET + 1)/2, FEC_PAYLOAD_ID_OFFSET = ((OBJ_ID_OFFSET+1)*2)/4 }; Form form; int flags; UINT16 length; // length of repair items UINT32* buffer; UINT16 buffer_len; // in bytes }; // end class NormRepairRequest class NormCmdRepairAdvMsg : public NormCmdMsg { public: enum Flag {NORM_REPAIR_ADV_FLAG_LIMIT = 0x01}; void Init() { SetType(CMD); SetFlavor(REPAIR_ADV); SetBaseHeaderLength(REPAIR_ADV_HEADER_LEN); ResetFlags(); ((UINT16*)buffer)[RESERVED_OFFSET] = 0; } // Message building void ResetFlags() {((UINT8*)buffer)[FLAGS_OFFSET] = 0;} void SetFlag(NormCmdRepairAdvMsg::Flag flag) {((UINT8*)buffer)[FLAGS_OFFSET] |= (UINT8)flag;} void AttachRepairRequest(NormRepairRequest& request, UINT16 segmentMax) { int buflen = segmentMax - (length - header_length); buflen = (buflen>0) ? buflen : 0; request.Init(buffer+length/4, buflen); } UINT16 PackRepairRequest(NormRepairRequest& request) { UINT16 requestLength = request.Pack(); length += requestLength; return requestLength; } // Message processing bool FlagIsSet(NormCmdRepairAdvMsg::Flag flag) const {return (0 != ((UINT8)flag | ((UINT8*)buffer)[FLAGS_OFFSET]));} //char* AccessRepairContent() {return (buffer + header_length);} const UINT32* GetRepairContent() const {return (buffer + header_length/4);} UINT16 GetRepairContentLength() const {return (length - header_length);} private: enum { FLAGS_OFFSET = FLAVOR_OFFSET + 1, RESERVED_OFFSET = FLAGS_OFFSET + 1, REPAIR_ADV_HEADER_LEN = RESERVED_OFFSET + 2 }; }; // end class NormCmdRepairAdvMsg // TBD - define NormCCFeedbackExtension2 for larger loss encoding range // the current 16-bit range is too limited for large RTT*rate combos class NormCCFeedbackExtension : public NormHeaderExtension { public: virtual void Init(UINT32* theBuffer, UINT16 numBytes) { AttachBuffer(theBuffer, numBytes); SetType(CC_FEEDBACK); SetWords(3); ((UINT8*)buffer)[CC_FLAGS_OFFSET] = 0; //((UINT16*)buffer)[CC_RESERVED_OFFSET] = 0; } void SetCCSequence(UINT16 ccSequence) {((UINT16*)buffer)[CC_SEQUENCE_OFFSET] = htons(ccSequence);} void ResetCCFlags() {((UINT8*)buffer)[CC_FLAGS_OFFSET] = 0;} void SetCCFlag(NormCC::Flag flag) {((UINT8*)buffer)[CC_FLAGS_OFFSET] |= (UINT8)flag;} void SetCCRtt(UINT8 ccRtt) {((UINT8*)buffer)[CC_RTT_OFFSET] = ccRtt;} //void SetCCLoss(UINT16 ccLoss) // {((UINT16*)buffer)[CC_LOSS_OFFSET] = htons(ccLoss);} void SetCCRate(UINT16 ccRate) {((UINT16*)buffer)[CC_RATE_OFFSET] = htons(ccRate);} void SetCCLoss32(UINT32 ccLoss) { ccLoss = htonl(ccLoss); UINT16* ptr = (UINT16*)&ccLoss; ((UINT16*)buffer)[CC_LOSS_OFFSET] = ptr[0]; // msb ((UINT16*)buffer)[CC_LOSS_EX_OFFSET] = ptr[1]; // lsb } UINT16 GetCCSequence() const {return (ntohs(((UINT16*)buffer)[CC_SEQUENCE_OFFSET]));} UINT8 GetCCFlags() {return ((UINT8*)buffer)[CC_FLAGS_OFFSET];} bool CCFlagIsSet(NormCC::Flag flag) const {return (0 != ((UINT8)flag & ((UINT8*)buffer)[CC_FLAGS_OFFSET]));} UINT8 GetCCRtt() const {return ((UINT8*)buffer)[CC_RTT_OFFSET];} //UINT16 GetCCLoss() const // {return (ntohs(((UINT16*)buffer)[CC_LOSS_OFFSET]));} UINT16 GetCCRate()const {return (ntohs(((UINT16*)buffer)[CC_RATE_OFFSET]));} UINT32 GetCCLoss32() const { UINT32 lossQuantized; UINT16* ptr = (UINT16*)&lossQuantized; ptr[0] = ((UINT16*)buffer)[CC_LOSS_OFFSET]; // msb ptr[1] = ((UINT16*)buffer)[CC_LOSS_EX_OFFSET]; // lsb return ntohl(lossQuantized); // return in host byte order } private: enum { CC_SEQUENCE_OFFSET = (LENGTH_OFFSET+1)/2, CC_FLAGS_OFFSET = (CC_SEQUENCE_OFFSET*2)+2, CC_RTT_OFFSET = CC_FLAGS_OFFSET + 1, CC_LOSS_OFFSET = (CC_RTT_OFFSET + 1)/2, CC_RATE_OFFSET = ((CC_LOSS_OFFSET*2)+2)/2, //CC_RESERVED_OFFSET = ((CC_RATE_OFFSET*2)+2)/2 CC_LOSS_EX_OFFSET = ((CC_RATE_OFFSET*2)+2)/2 // extended precision loss estimate (non-RFC5740 compliant, but compatible) }; }; // end class NormCCFeedbackExtension // Note: Support for application-defined AckFlavors (32-255) // may be provided, but 0-31 are reserved values class NormAck { public: enum Type { INVALID = 0, CC = 1, FLUSH = 2, APP_BASE = 16 }; }; class NormCmdAckReqMsg : public NormCmdMsg { public: void Init() { SetType(CMD); SetFlavor(ACK_REQ); SetBaseHeaderLength(ACK_REQ_HEADER_LEN); ((UINT8*)buffer)[RESERVED_OFFSET] = 0; } // Message building void SetAckType(NormAck::Type ackType) {((UINT8*)buffer)[ACK_TYPE_OFFSET] = (UINT8)ackType;} void SetAckId(UINT8 ackId) {((UINT8*)buffer)[ACK_ID_OFFSET] = ackId;} void ResetAckingNodeList() {length = header_length;} bool AppendAckingNode(NormNodeId nodeId, UINT16 segmentSize) { if ((length - header_length + 4) > segmentSize) return false; buffer[length/4] = htonl(nodeId); length += 4; return true; } // Message processing NormAck::Type GetAckType() const {return (NormAck::Type)(((UINT8*)buffer)[ACK_TYPE_OFFSET]);} UINT8 GetAckId() const {return ((UINT8*)buffer)[ACK_ID_OFFSET];} UINT16 GetAckingNodeCount() const {return ((length - header_length) >> 2);} NormNodeId GetAckingNodeId(UINT16 index) const {return (ntohl(buffer[(header_length/4)+index]));} private: enum { RESERVED_OFFSET = FLAVOR_OFFSET + 1, ACK_TYPE_OFFSET = RESERVED_OFFSET + 1, ACK_ID_OFFSET = ACK_TYPE_OFFSET + 1, ACK_REQ_HEADER_LEN = ACK_ID_OFFSET + 1 }; }; // end class NormCmdAckReqMsg class NormCmdAppMsg : public NormCmdMsg { public: void Init() { SetType(CMD); SetFlavor(APPLICATION); SetBaseHeaderLength(APPLICATION_HEADER_LEN); memset(((UINT8*)buffer)+RESERVED_OFFSET, 0, 3); } bool SetContent(const char* content, UINT16 contentLen, UINT16 segmentSize) { UINT16 len = MIN(contentLen, segmentSize); memcpy(((char*)buffer)+header_length, content, len); length = header_length + len; return (contentLen <= segmentSize); } UINT16 GetContentLength() const {return (length - header_length);} const char* GetContent() const {return (((char*)buffer)+header_length);} private: enum { RESERVED_OFFSET = FLAVOR_OFFSET + 1, APPLICATION_HEADER_LEN = RESERVED_OFFSET + 3 }; }; // end class NormCmdAppMsg // Receiver Messages class NormNackMsg : public NormMsg { public: enum {DEFAULT_LENGTH_MAX = 40}; void Init() { SetType(NACK); ((UINT16*)buffer)[RESERVED_OFFSET] = 0; SetBaseHeaderLength(NACK_HEADER_LEN); } // Message building void SetSenderId(NormNodeId senderId) {buffer[SENDER_ID_OFFSET] = htonl(senderId);} void SetInstanceId(UINT16 instanceId) {((UINT16*)buffer)[INSTANCE_ID_OFFSET] = htons(instanceId);} void SetGrttResponse(const struct timeval& grttResponse) { buffer[GRTT_RESPONSE_SEC_OFFSET] = htonl(grttResponse.tv_sec); buffer[GRTT_RESPONSE_USEC_OFFSET] = htonl(grttResponse.tv_usec); } void AttachRepairRequest(NormRepairRequest& request, UINT16 segmentMax) { int buflen = segmentMax - (length - header_length); buflen = (buflen>0) ? buflen : 0; request.Init(buffer+length/4, buflen); } UINT16 PackRepairRequest(NormRepairRequest& request) { UINT16 requestLength = request.Pack(); length += requestLength; return requestLength; } // TBD - add some safety checks to these methods void InitFrom(NormNackMsg nack) { // Copy header from "nack" memcpy(buffer, nack.buffer, nack.GetHeaderLength()); header_length_base = nack.header_length_base; length = header_length = nack.GetHeaderLength(); } void AppendRepairRequest(const NormRepairRequest request) { memcpy(buffer+length/4, request.GetBuffer(), request.GetLength()); length += request.GetLength(); } void ResetPayload() {length = GetHeaderLength();} // Message processing NormNodeId GetSenderId() const {return (ntohl(buffer[SENDER_ID_OFFSET]));} UINT16 GetInstanceId() const {return (ntohs(((UINT16*)buffer)[INSTANCE_ID_OFFSET]));} void GetGrttResponse(struct timeval& grttResponse) const { grttResponse.tv_sec = ntohl(buffer[GRTT_RESPONSE_SEC_OFFSET]); grttResponse.tv_usec = ntohl(buffer[GRTT_RESPONSE_USEC_OFFSET]); } //char* AccessRepairContent() {return (buffer + header_length);} const UINT32* GetRepairContent() const {return (buffer + header_length/4);} UINT16 GetRepairContentLength() const {return ((length > header_length) ? length - header_length : 0);} UINT16 UnpackRepairRequest(NormRepairRequest& request, UINT16 requestOffset) { int buflen = length - header_length - requestOffset; buflen = (buflen > 0) ? buflen : 0; return request.Unpack(buffer+(header_length+requestOffset)/4, buflen); } private: enum { SENDER_ID_OFFSET = MSG_OFFSET/4, INSTANCE_ID_OFFSET = ((SENDER_ID_OFFSET*4)+4)/2, RESERVED_OFFSET = ((INSTANCE_ID_OFFSET*2)+2)/2, GRTT_RESPONSE_SEC_OFFSET = ((RESERVED_OFFSET*2)+2)/4, GRTT_RESPONSE_USEC_OFFSET = ((GRTT_RESPONSE_SEC_OFFSET*4)+4)/4, NACK_HEADER_LEN = (GRTT_RESPONSE_USEC_OFFSET*4)+4 }; }; // end class NormNackMsg class NormAckMsg : public NormAck, public NormMsg { public: // Message building void Init() { SetType(ACK); SetBaseHeaderLength(ACK_HEADER_LEN); SetAckType(NormAck::INVALID); } void SetSenderId(NormNodeId senderId) {buffer[SENDER_ID_OFFSET] = htonl(senderId);} void SetInstanceId(UINT16 instanceId) {((UINT16*)buffer)[INSTANCE_ID_OFFSET] = htons(instanceId);} void SetAckType(NormAck::Type ackType) {((UINT8*)buffer)[ACK_TYPE_OFFSET] = (UINT8)ackType;} void SetAckId(UINT8 ackId) {((UINT8*)buffer)[ACK_ID_OFFSET] = ackId;} void SetGrttResponse(const struct timeval& grttResponse) { buffer[GRTT_RESPONSE_SEC_OFFSET] = htonl(grttResponse.tv_sec); buffer[GRTT_RESPONSE_USEC_OFFSET] = htonl(grttResponse.tv_usec); } bool SetAckPayload(const char* payload, UINT16 payloadLen, UINT16 segmentSize) { UINT16 len = MIN(payloadLen, segmentSize); memcpy(((char*)buffer)+header_length, payload, len); length += len; return (payloadLen <= segmentSize); } // Message processing NormNodeId GetSenderId() const {return (ntohl(buffer[SENDER_ID_OFFSET]));} UINT16 GetInstanceId() const {return (ntohs(((UINT16*)buffer)[INSTANCE_ID_OFFSET]));} void GetGrttResponse(struct timeval& grttResponse) const { grttResponse.tv_sec = ntohl(buffer[GRTT_RESPONSE_SEC_OFFSET]); grttResponse.tv_usec = ntohl(buffer[GRTT_RESPONSE_USEC_OFFSET]); } NormAck::Type GetAckType() const {return (NormAck::Type)((UINT8*)buffer)[ACK_TYPE_OFFSET];} UINT8 GetAckId() const {return ((UINT8*)buffer)[ACK_ID_OFFSET];} UINT16 GetPayloadLength() const {return (length - header_length);} const char* GetPayload() const {return (((char*)buffer) + header_length);} protected: enum { SENDER_ID_OFFSET = MSG_OFFSET/4, INSTANCE_ID_OFFSET = ((SENDER_ID_OFFSET*4)+4)/2, ACK_TYPE_OFFSET = (INSTANCE_ID_OFFSET*2)+2, ACK_ID_OFFSET = ACK_TYPE_OFFSET + 1, GRTT_RESPONSE_SEC_OFFSET = (ACK_ID_OFFSET + 1)/4, GRTT_RESPONSE_USEC_OFFSET = ((GRTT_RESPONSE_SEC_OFFSET+1)*4)/4, ACK_HEADER_LEN = (GRTT_RESPONSE_USEC_OFFSET+1)*4 }; }; // end class NormAckMsg class NormAckFlushMsg : public NormAckMsg { public: void Init() { SetType(ACK); SetBaseHeaderLength(ACK_HEADER_LEN); SetAckType(NormAck::FLUSH); ((UINT8*)buffer)[RESERVED_OFFSET] = 0; } // Note: must apply any header exts _before_ the payload is set void SetFecId(UINT8 fecId) {((UINT8*)buffer)[header_length+FEC_ID_OFFSET] = fecId;} void SetObjectId(NormObjectId objectId) {((UINT16*)buffer)[(header_length/2)+OBJ_ID_OFFSET] = htons((UINT16)objectId);} void SetFecPayloadId(UINT8 fecId, UINT32 blockId, UINT16 symbolId, UINT16 blockLen, UINT8 m) { SetFecId(fecId); ((UINT8*)buffer)[header_length+RESERVED_OFFSET] = 0; NormPayloadId payloadId(fecId, m, buffer + header_length/4 + FEC_PAYLOAD_ID_OFFSET); payloadId.SetFecPayloadId(blockId, symbolId, blockLen); length = header_length + 4*FEC_PAYLOAD_ID_OFFSET + NormPayloadId::GetLength(fecId); } UINT8 GetFecId() const {return ((UINT8*)buffer)[header_length+FEC_ID_OFFSET];} NormObjectId GetObjectId() const {return ntohs(((UINT16*)buffer)[(header_length/2)+OBJ_ID_OFFSET]);} NormBlockId GetFecBlockId(UINT8 m) const { NormPayloadId payloadId(GetFecId(), m, buffer + header_length/4 + FEC_PAYLOAD_ID_OFFSET); return payloadId.GetFecBlockId(); } UINT16 GetFecSymbolId(UINT8 m) const { NormPayloadId payloadId(GetFecId(), m, buffer + header_length/4 + FEC_PAYLOAD_ID_OFFSET); return payloadId.GetFecSymbolId(); } UINT16 GetFecBlockLength() const { NormPayloadId payloadId(GetFecId(), 8, buffer + header_length/4 + FEC_PAYLOAD_ID_OFFSET); return payloadId.GetFecBlockLength(); } private: // Note - These are the payload offsets for "fec_id" = 129 // "fec_payload_id" field // IMPORTANT - These are _relative_ to the NORM_ACK header (incl. extensions) enum { FEC_ID_OFFSET = 0, RESERVED_OFFSET = FEC_ID_OFFSET + 1, OBJ_ID_OFFSET = (RESERVED_OFFSET+1)/2, FEC_PAYLOAD_ID_OFFSET = ((OBJ_ID_OFFSET*2)+2)/4 }; }; // end class NormAckFlushMsg class NormReportMsg : public NormMsg { // (TBD) }; // end class NormReportMsg // One we've defined our basic message types, we // do some unions so we can easily use these // via casting or dereferencing the union members class NormMessageQueue { public: NormMessageQueue(); ~NormMessageQueue(); void Destroy(); void Prepend(NormMsg* msg); void Append(NormMsg* msg); void Remove(NormMsg* msg); NormMsg* RemoveHead(); NormMsg* RemoveTail(); NormMsg* GetHead() {return head;} bool IsEmpty() {return ((NormMsg*)NULL == head);} private: NormMsg* head; NormMsg* tail; }; // end class NormMessageQueue // Helper function to output report on repair content (e.g. NormNack content) to debug log void LogRepairContent(const UINT32* buffer, UINT16 bufferLen, UINT8 fecId, UINT8 fecM); #endif // _NORM_MESSAGE