#include "protoPktIGMP.h"
const UINT8 ProtoPktIGMP::DEFAULT_QRV = 2; const double ProtoPktIGMP::DEFAULT_QQIC = 125.0; const double ProtoPktIGMP::DEFAULT_MAX_RESP = 10.0;
ProtoPktIGMP::ProtoPktIGMP(void* bufferPtr,
unsigned int numBytes,
bool initFromBuffer,
bool freeOnDestruct)
: ProtoPkt(bufferPtr, numBytes, freeOnDestruct)
{
if (initFromBuffer) InitFromBuffer(numBytes);
}
ProtoPktIGMP::~ProtoPktIGMP()
{
}
bool ProtoPktIGMP::InitFromBuffer(UINT16 pktLength,
void* bufferPtr,
unsigned int bufferBytes,
bool freeOnDestruct)
{
unsigned int minLength = OFFSET_RESERVED;
if (NULL != bufferPtr)
{
if (bufferBytes < minLength) return false;
else
AttachBuffer(bufferPtr, bufferBytes, freeOnDestruct);
}
if (GetBufferLength() < pktLength)
{
PLOG(PL_ERROR, "ProtoPktIGMP::InitFromBuffer() error: insufficient buffer size\n");
return false;
}
SetLength(pktLength);
if (GetVersion() > 2)
{
minLength = OFFSET_SRC_LIST;
if (pktLength < minLength)
{
PLOG(PL_ERROR, "ProtoPktIGMP::InitFromBuffer() error: invalid IGMPv3 packet\n");
return false;
}
minLength += 4*GetNumSources();
if (pktLength < minLength)
{
PLOG(PL_ERROR, "ProtoPktIGMP::InitFromBuffer() error: truncated IGMPv3 packet\n");
return false;
}
}
return true;
}
UINT8 ProtoPktIGMP::GetVersion() const
{
switch (GetType())
{
case REPORT_V1:
return 1;
case REPORT_V2:
return 2;
case REPORT_V3:
return 3;
case QUERY:
case LEAVE:
if (GetLength() > OFFSET_RESERVED)
return 3;
else
return 2;
default:
return 0;
}
}
double ProtoPktIGMP::GetMaxResponseTime() const
{
UINT8 value = GetUINT8(OFFSET_MAX_RESP);
if ((value < 128) || (GetVersion() < 3))
{
return (0.1*(double)value);
}
else
{
unsigned int exp = (value & 0x70) >> 4;
unsigned int mant = value & 0x0f;
unsigned int maxRespTime = (mant | 0x10) << (exp + 3);
return (0.1*(double)maxRespTime);
}
}
double ProtoPktIGMP::GetQQIC() const
{
UINT8 qqic = GetUINT8(OFFSET_QQIC);
if (qqic < 128)
{
return (double)qqic;
}
else
{
unsigned int exp = (qqic & 0x70) >> 4;
unsigned int mant = qqic & 0x0f;
return (double)((mant | 0x10) << (exp + 3));
}
}
bool ProtoPktIGMP::GetSourceAddress(UINT16 index, ProtoAddress& srcAddr) const
{
if (index < GetNumSources())
{
srcAddr.SetRawHostAddress(ProtoAddress::IPv4, (char*)GetBuffer(OFFSET_SRC_LIST + index*4), 4);
return true;
}
else
{
srcAddr.Invalidate();
return false;
}
}
bool ProtoPktIGMP::GetNextGroupRecord(ProtoPktIGMP::GroupRecord& groupRecord, bool first)
{
if (0 == GetNumRecords()) return false;
void* recordPtr;
unsigned int bufferSpace;
if (first || (NULL == groupRecord.GetBuffer()))
{
recordPtr = AccessBuffer(OFFSET_REC_LIST);
bufferSpace = GetLength() - OFFSET_REC_LIST;
}
else
{
recordPtr = groupRecord.AccessBuffer(groupRecord.GetLength());
size_t offset = (char*)recordPtr - (char*)GetBuffer();
if (offset > GetLength())
return false; bufferSpace = GetLength() - (unsigned int)offset;
}
if (0 == bufferSpace)
return false;
else
return groupRecord.InitFromBuffer(recordPtr, bufferSpace);
}
bool ProtoPktIGMP::InitIntoBuffer(Type type,
unsigned int version,
void* bufferPtr,
unsigned int bufferBytes,
bool freeOnDestruct)
{
UINT16 minLength ;
switch (type)
{
case QUERY:
if (version < 3)
minLength = OFFSET_RESERVED;
else
minLength = OFFSET_SRC_LIST;
case REPORT_V1:
version = 1;
minLength = OFFSET_RESERVED;
break;
case REPORT_V2:
case LEAVE:
version = 2;
minLength = OFFSET_RESERVED;
break;
case REPORT_V3:
version = 3;
minLength = OFFSET_REC_LIST;
break;
default:
break;
}
if (NULL != bufferPtr)
{
if (bufferBytes < minLength)
return false;
else
AttachBuffer(bufferPtr, bufferBytes, freeOnDestruct);
}
if (GetBufferLength() < minLength) return false;
memset(AccessBuffer(), 0, minLength);
SetUINT8(OFFSET_TYPE, type);
SetLength(minLength);
if (QUERY == type)
{
SetMaxResponseTime(DEFAULT_MAX_RESP);
if (3 == version)
{
SetQRV(DEFAULT_QRV);
SetQQIC(DEFAULT_QQIC);
}
}
return true;
}
void ProtoPktIGMP::SetMaxResponseTime(double seconds, bool updateChecksum)
{
seconds *= 10.0; UINT8 code;
if (seconds < 128)
{
code = (unsigned int)seconds;
}
else if (seconds > 31743.0)
{
code = 255;
}
else
{
unsigned int value = (unsigned int)seconds;
unsigned exp = 0;
value >>= 3;
while (value > 31)
{
exp++;
value >>= 1;
}
exp <<= 4;
code = (0x80 | exp | (value & 0x0f));
}
SetUINT8(OFFSET_MAX_RESP, code);
if (updateChecksum) ComputeChecksum();
}
void ProtoPktIGMP::SetGroupAddress(ProtoAddress* groupAddr, bool updateChecksum)
{
void* ptr = AccessBuffer(OFFSET_GROUP);
if ((NULL == groupAddr) || (ProtoAddress::IPv4 != groupAddr->GetType()) || (!groupAddr->IsMulticast()))
memset(ptr, 0, 4);
else
memcpy(ptr, groupAddr->GetRawHostAddress(), 4);
if (updateChecksum) ComputeChecksum();
}
bool ProtoPktIGMP::SetSuppress(bool state, bool updateChecksum)
{
if (GetBufferLength() <= OFFSET_RESERVED)
{
PLOG(PL_ERROR, "ProtoPktIGMP::SetSuppress() error: insufficient buffer space\n");
return false;
}
UINT8 code = GetUINT8(OFFSET_S);
if (state)
code |= 0x08;
else
code = (code & ~0x08) & 0x0f;
SetUINT8(OFFSET_S, code);
if (updateChecksum) ComputeChecksum();
return true;
}
bool ProtoPktIGMP::SetQRV(UINT8 qrv, bool updateChecksum)
{
if (GetBufferLength() <= OFFSET_RESERVED)
{
PLOG(PL_ERROR, "ProtoPktIGMP::SetQRV() error: insufficient buffer space\n");
return false;
}
UINT8 code = GetUINT8(OFFSET_QRV);
code &= 0x08; qrv &= 0x07; code |= qrv; SetUINT8(OFFSET_QRV, code);
if (updateChecksum) ComputeChecksum();
return true;
}
bool ProtoPktIGMP::SetQQIC(double seconds, bool updateChecksum)
{
if (GetBufferLength() <= OFFSET_RESERVED)
{
PLOG(PL_ERROR, "ProtoPktIGMP::SetQQIC() error: insufficient buffer space\n");
return false;
}
UINT8 code;
if (seconds < 128)
{
code = (unsigned int)seconds;
}
else if (seconds > 31743.0)
{
code = 255;
}
else
{
unsigned int value = (unsigned int)seconds;
unsigned exp = 0;
value >>= 3;
while (value > 31)
{
exp++;
value >>= 1;
}
exp <<= 4;
code = (0x80 | exp | (value & 0x0f));
}
SetUINT8(OFFSET_QQIC, code);
if (updateChecksum) ComputeChecksum();
return true;
}
bool ProtoPktIGMP::AppendSourceAddress(ProtoAddress& srcAddr, bool updateChecksum)
{
if ((ProtoAddress::IPv4 != srcAddr.GetType()) || !srcAddr.IsUnicast())
{
PLOG(PL_ERROR, "ProtoPktIGMP::AppendSourceAddress() error: invalid source address\n");
return false;
}
UINT16 numSrc = GetNumSources();
unsigned int offset = OFFSET_SRC_LIST + 4*numSrc;
ASSERT(offset == GetLength());
unsigned int newLength = offset + 4;
if (newLength > GetBufferLength())
{
PLOG(PL_ERROR, "ProtoPktIGMP::AppendSourceAddress() error: insufficient buffer space\n");
return false;
}
memcpy(AccessBuffer(offset), srcAddr.GetRawHostAddress(), 4);
SetUINT16(OFFSET_NUM_SRC, numSrc + 1);
SetLength(newLength);
if (updateChecksum) ComputeChecksum();
return true;
}
bool ProtoPktIGMP::AttachGroupRecord(GroupRecord& groupRecord)
{
UINT32 currentLength = GetLength();
ASSERT(0 == (currentLength & 0x00000003)); void* bufferPtr = AccessBuffer(currentLength);
unsigned int bufferSpace = GetBufferLength() - currentLength;
if (!groupRecord.InitIntoBuffer(bufferPtr, bufferSpace))
{
PLOG(PL_ERROR, "ProtoPktIGMP::AttachGroupRecord() error: insufficient buffer space\n");
return false;
}
return true;
}
bool ProtoPktIGMP::AppendGroupRecord(const GroupRecord& groupRecord, bool updateChecksum)
{
UINT32 currentLength = GetLength();
ASSERT(0 == (currentLength & 0x00000003)); unsigned int bufferSpace = GetBufferLength() - currentLength;
if (bufferSpace < groupRecord.GetLength())
{
PLOG(PL_ERROR, "ProtoPktIGMP::AppendGroupRecord() error: insufficient buffer space\n");
return false;
}
void* ptr = AccessBuffer(currentLength);
if (ptr != groupRecord.GetBuffer())
memcpy(ptr, groupRecord.GetBuffer(), groupRecord.GetLength());
SetUINT16(OFFSET_NUM_REC, GetNumRecords() + 1);
SetLength(currentLength + groupRecord.GetLength());
if (updateChecksum) ComputeChecksum();
return true;
}
UINT16 ProtoPktIGMP::ComputeChecksum(bool set)
{
UINT32 sum = 0;
if (set) SetUINT16(OFFSET_CHECKSUM, (UINT16)0);
unsigned int end = OFFSET_CHECKSUM/2;
end = GetLength() / 2;
for (unsigned int i = 0; i < end; i++)
sum += GetWord16(i);
while (0 != (sum >> 16))
sum = (sum & 0x0000ffff) + (sum >> 16);
sum = ~sum;
if (0 == sum) sum = 0x0000ffff;
if (set) SetUINT16(OFFSET_CHECKSUM, (UINT16)sum);
return (UINT16)sum;
}
ProtoPktIGMP::GroupRecord::GroupRecord(void* bufferPtr,
unsigned int numBytes,
bool initFromBuffer,
bool freeOnDestruct)
: ProtoPkt(bufferPtr, numBytes, freeOnDestruct)
{
if (initFromBuffer)
InitFromBuffer();
else
InitIntoBuffer();
}
ProtoPktIGMP::GroupRecord::~GroupRecord()
{
}
bool ProtoPktIGMP::GroupRecord::InitFromBuffer(void* bufferPtr,
unsigned int bufferBytes,
bool freeOnDestruct)
{
unsigned int minLength = OFFSET_SRC_LIST;
if (NULL != bufferPtr)
{
if (bufferBytes < minLength) return false;
else
AttachBuffer(bufferPtr, bufferBytes, freeOnDestruct);
}
minLength += GetAuxDataLen();
minLength += 4*GetNumSources();
if (GetBufferLength() < minLength)
{
PLOG(PL_ERROR, "ProtoPktIGMP::GroupRecord::InitFromBuffer() error: truncated IGMPv3 group record?!\n");
return false;
}
SetLength(minLength);
return true;
}
bool ProtoPktIGMP::GroupRecord::GetSourceAddress(UINT16 index, ProtoAddress& srcAddr) const
{
if (index >= GetNumSources())
{
PLOG(PL_ERROR, "ProtoPktIGMP::GroupRecord::GetSourceAddress() error: index out of range\n");
srcAddr.Invalidate();
return false;
}
const void* ptr = GetBuffer(OFFSET_SRC_LIST + (index << 2));
srcAddr.SetRawHostAddress(ProtoAddress::IPv4, (char*)ptr, 4);
return true;
}
bool ProtoPktIGMP::GroupRecord::InitIntoBuffer(void* bufferPtr,
unsigned int bufferBytes,
bool freeOnDestruct)
{
UINT16 minLength = OFFSET_SRC_LIST;
if (NULL != bufferPtr)
{
if (bufferBytes < minLength)
return false;
else
AttachBuffer(bufferPtr, bufferBytes, freeOnDestruct);
}
if (GetBufferLength() < minLength) return false;
memset(AccessBuffer(), 0, OFFSET_SRC_LIST);
SetLength(minLength);
return true;
}
void ProtoPktIGMP::GroupRecord::SetGroupAddress(const ProtoAddress* groupAddr)
{
void* ptr = AccessBuffer(OFFSET_GROUP);
if ((NULL == groupAddr) || (ProtoAddress::IPv4 != groupAddr->GetType()) || (!groupAddr->IsMulticast()))
memset(ptr, 0, 4);
else
memcpy(ptr, groupAddr->GetRawHostAddress(), 4);
}
bool ProtoPktIGMP::GroupRecord::AppendSourceAddress(const ProtoAddress& srcAddr)
{
if ((ProtoAddress::IPv4 != srcAddr.GetType()) || !srcAddr.IsUnicast())
{
PLOG(PL_ERROR, "ProtoPktIGMP::GroupRecord::AppendSourceAddress() error: invalid source address\n");
return false;
}
UINT16 numSrc = GetNumSources();
unsigned int offset = OFFSET_SRC_LIST + 4*numSrc;
ASSERT(offset == GetLength());
unsigned int newLength = offset + 4;
if (newLength > GetBufferLength())
{
PLOG(PL_ERROR, "ProtoPktIGMP::GroupRecord::AppendSourceAddress() error: insufficient buffer space\n");
return false;
}
memcpy(AccessBuffer(offset), srcAddr.GetRawHostAddress(), 4);
SetUINT16(OFFSET_NUM_SRC, numSrc + 1);
SetLength(newLength);
return true;
}
bool ProtoPktIGMP::GroupRecord::AppendAuxiliaryData(const char* data, UINT16 len)
{
unsigned int currentLength = GetLength();
unsigned int bufferSpace = GetBufferLength() - currentLength;
if (0 != (len % 0xfffc))
{
PLOG(PL_ERROR, "ProtoPktIGMP::GroupRecord::AppendAuxiliaryData() error: invalid data length\n");
return false;
}
if (bufferSpace < len)
{
PLOG(PL_ERROR, "ProtoPktIGMP::GroupRecord::AppendAuxiliaryData() error: insufficient buffer space\n");
return false;
}
void* ptr = AccessBuffer(currentLength);
memcpy(ptr, data, len);
SetUINT8(OFFSET_AUX_LEN,len >> 2);
SetLength(currentLength + len);
return true;
}