/*
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.neomedia;
/**
* When using TransformConnector, a RTP/RTCP packet is represented using
* RawPacket. RawPacket stores the buffer holding the RTP/RTCP packet, as well
* as the inner offset and length of RTP/RTCP packet data.
*
* After transformation, data is also store in RawPacket objects, either the
* original RawPacket (in place transformation), or a newly created RawPacket.
*
* Besides packet info storage, RawPacket also provides some other operations
* such as readInt() to ease the development process.
*
* @author Werner Dittmann (Werner.Dittmann@t-online.de)
* @author Bing SU (nova.su@gmail.com)
* @author Emil Ivov
* @author Damian Minkov
*/
public class RawPacket
{
/**
* The size of the fixed part of the RTP header as defined by RFC 3550.
*/
public static final int FIXED_HEADER_SIZE = 12;
/**
* The size of the extension header as defined by RFC 3550.
*/
public static final int EXT_HEADER_SIZE = 4;
/**
* Byte array storing the content of this Packet
*/
private byte[] buffer;
/**
* Start offset of the packet data inside buffer.
* Usually this value would be 0. But in order to be compatible with
* RTPManager we store this info. (Not assuming the offset is always zero)
*/
private int offset;
/**
* Length of this packet's data
*/
private int length;
/**
* Construct an empty RawPacket
*
*/
public RawPacket()
{
}
/**
* Construct a RawPacket using specified value.
*
* @param buffer Byte array holding the content of this Packet
* @param offset Start offset of packet content inside buffer
* @param length Length of the packet's data
*/
public RawPacket(byte[] buffer, int offset, int length)
{
this.buffer = buffer;
this.offset = offset;
this.length = length;
}
/**
* Get buffer containing the content of this packet
*
* @return buffer containing the content of this packet
*/
public byte[] getBuffer()
{
return this.buffer;
}
/**
* Get the length of this packet's data
*
* @return length of this packet's data
*/
public int getLength()
{
return this.length;
}
/**
* Get the start offset of this packet's data inside storing buffer
*
* @return start offset of this packet's data inside storing buffer
*/
public int getOffset()
{
return this.offset;
}
/**
* @param buffer the buffer to set
*/
protected void setBuffer(byte[] buffer) {
this.buffer = buffer;
}
/**
* @param offset the offset to set
*/
protected void setOffset(int offset) {
this.offset = offset;
}
/**
* @param length the length to set
*/
protected void setLength(int length) {
this.length = length;
}
/**
* Sets or resets the marker bit of this packet according to the
* marker parameter.
* @param marker true if we are to raise the marker bit and
* false otherwise.
*/
public void setMarker(boolean marker)
{
if(marker)
{
buffer[offset + 1] |= (byte) 0x80;
}
else
{
buffer[offset + 1] &= (byte) 0x7F;
}
}
/**
* Sets the payload of this packet.
*
* @param payload the RTP payload type describing the content of this
* packet.
*/
public void setPayload(byte payload)
{
//this is supposed to be a 7bit payload so make sure that the leftmost
//bit is 0 so that we don't accidentally overwrite the marker.
payload &= (byte)0x7F;
buffer[offset + 1] = (byte)((buffer[offset + 1] & 0x80) | payload);
}
/**
* Returns the timestamp for this RTP RawPacket.
*
* @return the timestamp for this RTP RawPacket.
*/
public long getTimestamp()
{
return readInt(offset + 4);
}
/**
* Set the timestamp value of the RTP Packet
*
* @param timestamp : the RTP Timestamp
*/
public void setTimestamp(long timestamp)
{
writeInt(offset + 4, (int)timestamp);
}
/**
* Read a integer from this packet at specified offset
*
* @param off start offset of the integer to be read
* @return the integer to be read
*/
public int readInt(int off)
{
return (this.buffer[this.offset + off + 0] << 24) |
((this.buffer[this.offset + off + 1] & 0xff) << 16) |
((this.buffer[this.offset + off + 2] & 0xff) << 8) |
(this.buffer[this.offset + off + 3] & 0xff);
}
/**
* Set an integer at specified offset in network order.
*
* @param off Offset into the buffer
* @param data The integer to store in the packet
*/
public void writeInt(int off, int data)
{
buffer[offset + off++] = (byte)(data>>24);
buffer[offset + off++] = (byte)(data>>16);
buffer[offset + off++] = (byte)(data>>8);
buffer[offset + off] = (byte)data;
}
/**
* Read a short from this packet at specified offset
*
* @param off start offset of this short
* @return short value at offset
*/
public short readShort(int off)
{
return (short) ((this.buffer[this.offset + off + 0] << 8) |
(this.buffer[this.offset + off + 1] & 0xff));
}
/**
* Read an unsigned short at specified offset as a int
*
* @param off start offset of the unsigned short
* @return the int value of the unsigned short at offset
*/
public int readUnsignedShortAsInt(int off)
{
int b1 = (0x000000FF & (this.buffer[this.offset + off + 0]));
int b2 = (0x000000FF & (this.buffer[this.offset + off + 1]));
int val = b1 << 8 | b2;
return val;
}
/**
* Read a byte from this packet at specified offset
*
* @param off start offset of the byte
* @return byte at offset
*/
public byte readByte(int off)
{
return buffer[offset + off];
}
/**
* Write a byte to this packet at specified offset
*
* @param off start offset of the byte
* @param b byte to write
*/
public void writeByte(int off, byte b)
{
buffer[offset + off] = b;
}
/**
* Read an unsigned integer as long at specified offset
*
* @param off start offset of this unsigned integer
* @return unsigned integer as long at offset
*/
public long readUnsignedIntAsLong(int off)
{
int b0 = (0x000000FF & (this.buffer[this.offset + off + 0]));
int b1 = (0x000000FF & (this.buffer[this.offset + off + 1]));
int b2 = (0x000000FF & (this.buffer[this.offset + off + 2]));
int b3 = (0x000000FF & (this.buffer[this.offset + off + 3]));
return ((b0 << 24 | b1 << 16 | b2 << 8 | b3)) & 0xFFFFFFFFL;
}
/**
* Read a byte region from specified offset with specified length
*
* @param off start offset of the region to be read
* @param len length of the region to be read
* @return byte array of [offset, offset + length)
*/
public byte[] readRegion(int off, int len)
{
int startOffset = this.offset + off;
if (off < 0 || len <= 0 || startOffset + len > this.buffer.length)
return null;
byte[] region = new byte[len];
System.arraycopy(this.buffer, startOffset, region, 0, len);
return region;
}
/**
* Read a byte region from specified offset with specified length in given
* buffer
*
* @param off start offset of the region to be read
* @param len length of the region to be read
* @param outBuff output buffer
*/
public void readRegionToBuff(int off, int len, byte[] outBuff)
{
int startOffset = this.offset + off;
if (off < 0 || len <= 0 || startOffset + len > this.buffer.length)
return;
if (outBuff.length < len)
return;
System.arraycopy(this.buffer, startOffset, outBuff, 0, len);
}
/**
* Grow the internal packet buffer.
*
* This will change the data buffer of this packet but not the
* length of the valid data. Use this to grow the internal buffer
* to avoid buffer re-allocations when appending data.
*
* @param howMuch number of bytes to grow
*/
public void grow(int howMuch) {
if (howMuch == 0) {
return;
}
byte[] newBuffer = new byte[this.length + howMuch];
System.arraycopy(this.buffer, this.offset, newBuffer, 0, this.length);
offset = 0;
buffer = newBuffer;
}
/**
* Append a byte array to the end of the packet. This may change the data
* buffer of this packet.
*
* @param data byte array to append
* @param len the number of bytes to append
*/
public void append(byte[] data, int len) {
if (data == null || len == 0) {
return;
}
// re-allocate internal buffer if it is too small
if ((this.length + len) > (buffer.length - this.offset)) {
byte[] newBuffer = new byte[this.length + len];
System.arraycopy(this.buffer, this.offset, newBuffer, 0, this.length);
this.offset = 0;
this.buffer = newBuffer;
}
// append data
System.arraycopy(data, 0, this.buffer, this.length, len);
this.length = this.length + len;
}
/**
* Shrink the buffer of this packet by specified length
*
* @param len length to shrink
*/
public void shrink(int len)
{
if (len <= 0)
return;
this.length -= len;
if (this.length < 0)
this.length = 0;
}
/**
* Returns the number of CSRC identifiers currently included in this packet.
*
* @return the CSRC count for this RawPacket.
*/
public int getCsrcCount()
{
return (buffer[offset] & 0x0f);
}
/**
* Replaces the existing CSRC list (even if empty) with newCsrcList
* and updates the CC (CSRC count) field of this RawPacket
* accordingly.
*
* @param newCsrcList the list of CSRC identifiers that we'd like to set for
* this RawPacket.
*/
public void setCsrcList(long[] newCsrcList)
{
int newCsrcCount = newCsrcList.length;
byte[] csrcBuff = new byte[newCsrcCount * 4];
int csrcOffset = 0;
for(long csrc : newCsrcList)
{
csrcBuff[csrcOffset] = (byte)(csrc >> 24);
csrcBuff[csrcOffset+1] = (byte)(csrc >> 16);
csrcBuff[csrcOffset+2] = (byte)(csrc >> 8);
csrcBuff[csrcOffset+3] = (byte)csrc;
csrcOffset += 4;
}
int oldCsrcCount = getCsrcCount();
byte[] oldBuffer = this.getBuffer();
//the new buffer needs to be bigger than the new one in order to
//accommodate the list of CSRC IDs (unless there were more of them
//previously than after setting the new list).
byte[] newBuffer
= new byte[length + offset + csrcBuff.length - oldCsrcCount*4];
//copy the part up to the CSRC list
System.arraycopy(
oldBuffer, 0, newBuffer, 0, offset + FIXED_HEADER_SIZE);
//copy the new CSRC list
System.arraycopy( csrcBuff, 0, newBuffer,
offset + FIXED_HEADER_SIZE, csrcBuff.length);
//now copy the payload from the old buff and make sure we don't copy
//the CSRC list if there was one in the old packet
int payloadOffsetForOldBuff
= offset + FIXED_HEADER_SIZE + oldCsrcCount*4;
int payloadOffsetForNewBuff
= offset + FIXED_HEADER_SIZE + newCsrcCount*4;
System.arraycopy( oldBuffer, payloadOffsetForOldBuff,
newBuffer, payloadOffsetForNewBuff,
length - payloadOffsetForOldBuff);
//set the new CSRC count
newBuffer[offset] = (byte)((newBuffer[offset] & 0xF0)
| newCsrcCount);
this.buffer = newBuffer;
this.length = payloadOffsetForNewBuff + length
- payloadOffsetForOldBuff - offset;
}
/**
* Returns the list of CSRC IDs, currently encapsulated in this packet.
*
* @return an array containing the list of CSRC IDs, currently encapsulated
* in this packet.
*/
public long[] extractCsrcList()
{
int csrcCount = getCsrcCount();
long[] csrcList = new long[csrcCount];
int csrcStartIndex = offset + FIXED_HEADER_SIZE;
for (int i = 0; i < csrcCount; i++)
{
csrcList[i] = readInt(csrcStartIndex);
csrcStartIndex += 4;
}
return csrcList;
}
/**
* Get RTP padding size from a RTP packet
*
* @return RTP padding size from source RTP packet
*/
public int getPaddingSize()
{
if ((buffer[offset] & 0x4) == 0)
return 0;
else
return buffer[offset + length - 1];
}
/**
* Get RTP header length from a RTP packet
*
* @return RTP header length from source RTP packet
*/
public int getHeaderLength()
{
if(getExtensionBit())
return FIXED_HEADER_SIZE + 4 * getCsrcCount()
+ EXT_HEADER_SIZE + getExtensionLength();
else
return FIXED_HEADER_SIZE + 4 * getCsrcCount();
}
/**
* Get RTP payload length from a RTP packet
*
* @return RTP payload length from source RTP packet
*/
public int getPayloadLength()
{
return length - getHeaderLength();
}
/**
* Get RTP SSRC from a RTP packet
*
* @return RTP SSRC from source RTP packet
*/
public int getSSRC()
{
return (int)(readUnsignedIntAsLong(8) & 0xffffffff);
}
/**
* Get RTCP SSRC from a RTCP packet
*
* @param pkt the source RTP packet
* @return RTP SSRC from source RTP packet
*/
public long GetRTCPSSRC()
{
return (int)(readUnsignedIntAsLong(4) & 0xffffffff);
}
/**
* Get RTP sequence number from a RTP packet
*
* @return RTP sequence num from source packet
*/
public int getSequenceNumber()
{
return readUnsignedShortAsInt(2);
}
/**
* Get SRTCP sequence number from a SRTCP packet
*
* @param pkt the source SRTCP packet
* @return SRTCP sequence num from source packet
*/
public int getSRTCPIndex(int authTagLen)
{
int offset = getLength() - (4 + authTagLen);
return readInt(offset);
}
/**
* Test whether if a RTP packet is padded
*
* @return whether if source RTP packet is padded
*/
public boolean isPacketMarked()
{
return (buffer[offset + 1] & 0x80) != 0;
}
/**
* Get RTP payload type from a RTP packet
*
* @return RTP payload type of source RTP packet
*/
public byte getPayloadType()
{
return (byte) (buffer[offset + 1] & (byte)0x7F);
}
/**
* Get the RTP payload (bytes) of this RTP packet.
*
* @return an array of bytes which represents the RTP payload of
* this RTP packet
*/
public byte[] getPayload()
{
return readRegion(getHeaderLength(), getPayloadLength());
}
/**
* Get RTP timestamp from a RTP packet
*
* @return RTP timestamp of source RTP packet
*/
public byte[] readTimeStampIntoByteArray()
{
return readRegion(4, 4);
}
/**
* Returns true if the extension bit of this packet has been set
* and false otherwise.
*
* @return true if the extension bit of this packet has been set
* and false otherwise.
*/
public boolean getExtensionBit()
{
return (buffer[offset] & 0x10) == 0x10;
}
/**
* Raises the extension bit of this packet is extBit is
* true or set it to 0 if extBit is
* false.
*
* @param extBit the flag that indicates whether we are to set or clear
* the extension bit of this packet.
*/
private void setExtensionBit(boolean extBit)
{
if(extBit)
buffer[offset] |= 0x10;
else
buffer[offset] &= 0xEF;
}
/**
* Returns the length of the extensions currently added to this packet.
*
* @return the length of the extensions currently added to this packet.
*/
public int getExtensionLength()
{
if (!getExtensionBit())
return 0;
//the extension length comes after the RTP header, the CSRC list, and
//after two bytes in the extension header called "defined by profile"
int extLenIndex = offset
+ FIXED_HEADER_SIZE
+ getCsrcCount()*4 + 2;
return ((buffer[extLenIndex] << 4) | buffer[extLenIndex + 1]) * 4;
}
/**
* Adds the extBuff buffer to as an extension of this packet
* according the rules specified in RFC 5285. Note that this method does
* not replace extensions so if you add the same buffer twice it would be
* added as to separate extensions.
*
* @param extBuff the buffer that we'd like to add as an extension in this
* packet.
* @param newExtensionLen the length of the data in extBuff.
*/
public void addExtension(byte[] extBuff, int newExtensionLen)
{
int newBuffLen = length + offset + newExtensionLen;
int bufferOffset = offset;
int newBufferOffset = offset;
int lengthToCopy = FIXED_HEADER_SIZE + getCsrcCount()*4;
boolean extensionBit = getExtensionBit();
//if there was no extension previously, we also need to consider adding
//the extension header.
if (extensionBit)
{
// without copying the extension length value, will set it later
lengthToCopy += EXT_HEADER_SIZE - 2;
}
else
newBuffLen += EXT_HEADER_SIZE;
byte[] newBuffer = new byte[ newBuffLen ];
/*
* Copy header, CSRC list and the leading two bytes of the extension
* header if any.
*/
System.arraycopy(buffer, bufferOffset,
newBuffer, newBufferOffset, lengthToCopy);
//raise the extension bit.
newBuffer[newBufferOffset] |= 0x10;
bufferOffset += lengthToCopy;
newBufferOffset += lengthToCopy;
// Set the extension header or modify the existing one.
int totalExtensionLen = newExtensionLen + getExtensionLength();
//if there were no extensions previously, we need to add the hdr now
if(extensionBit)
bufferOffset += 4;
else
{
// we will now be adding the RFC 5285 ext header which looks like
// this:
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | 0xBE | 0xDE | length=3 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
newBuffer[newBufferOffset++] = (byte)0xBE;
newBuffer[newBufferOffset++] = (byte)0xDE;
}
// length field counts the number of 32-bit words in the extension
int lengthInWords = (totalExtensionLen + 3)/4;
newBuffer[newBufferOffset++] = (byte)(lengthInWords >>4);
newBuffer[newBufferOffset++] = (byte)lengthInWords;
// Copy the existing extension content if any.
if (extensionBit)
{
lengthToCopy = getExtensionLength();
System.arraycopy(buffer, bufferOffset,
newBuffer, newBufferOffset, lengthToCopy);
bufferOffset += lengthToCopy;
newBufferOffset += lengthToCopy;
}
//copy the extension content from the new extension.
System.arraycopy(extBuff, 0,
newBuffer, newBufferOffset, newExtensionLen);
newBufferOffset += newExtensionLen;
//now copy the payload
System.arraycopy(buffer, bufferOffset,
newBuffer, newBufferOffset, getPayloadLength());
newBufferOffset += getPayloadLength();
buffer = newBuffer;
this.length = newBufferOffset - offset;
}
/**
* Removes the extension from the packet and its header.
*/
public void removeExtension()
{
if(!getExtensionBit())
return;
int payloadOffset = offset + getHeaderLength();
int extHeaderLen = getExtensionLength() + EXT_HEADER_SIZE;
System.arraycopy(buffer, payloadOffset,
buffer, payloadOffset - extHeaderLen, getPayloadLength());
this.length -= extHeaderLen;
setExtensionBit(false);
}
/**
* Returns a map binding CSRC IDs to audio levels as reported by the remote
* party that sent this packet.
*
* @param csrcExtID the ID of the extension that's transporting csrc audio
* levels in the session that this RawPacket belongs to.
*
* @return an array representing a map binding CSRC IDs to audio levels as
* reported by the remote party that sent this packet. The entries of the
* map are contained in consecutive elements of the returned array where
* elements at even indices stand for CSRC IDs and elements at odd indices
* stand for the associated audio levels
*/
public long[] extractCsrcLevels(byte csrcExtID)
{
if (!getExtensionBit()
|| (getExtensionLength() == 0)
|| (getCsrcCount() == 0))
return null;
int csrcCount = getCsrcCount();
/*
* XXX The guideline which is also supported by Google and recommended
* for Android is that single-dimensional arrays should be preferred to
* multi-dimensional arrays in Java because the former take less space
* than the latter and are thus more efficient in terms of memory and
* garbage collection.
*/
long[] csrcLevels = new long[csrcCount * 2];
//first extract the csrc IDs
int csrcStartIndex = offset + FIXED_HEADER_SIZE;
for (int i = 0; i < csrcCount; i++)
{
int csrcLevelsIndex = 2 * i;
csrcLevels[csrcLevelsIndex] = readInt(csrcStartIndex);
csrcLevels[csrcLevelsIndex + 1] = getCsrcLevel(i, csrcExtID);
csrcStartIndex += 4;
}
return csrcLevels;
}
/**
* Returns the CSRC level at the specified index or 0 if there was
* no level at that index.
*
* @param index the sequence number of the CSRC audio level extension to
* return.
* @param csrcExtID the ID of the extension that's transporting csrc audio
* levels in the session that this RawPacket belongs to.
*
* @return the CSRC audio level at the specified index of the csrc audio
* level option or 0 if there was no level at that index.
*/
private int getCsrcLevel(int index, byte csrcExtID)
{
if( !getExtensionBit() || getExtensionLength() == 0)
return 0;
int levelsStart = findExtension(csrcExtID);
if(levelsStart == -1)
return 0;
int levelsCount = getLengthForExtension(levelsStart);
if(levelsCount < index)
{
//apparently the remote side sent more CSRCs than levels.
// ... yeah remote sides do that now and then ...
return 0;
}
return buffer[levelsStart + index];
}
/**
* Returns the index of the element in this packet's buffer where the
* content of the header with the specified extensionID starts.
*
* @param extensionID the ID of the extension whose content we are looking
* for.
*
* @return the index of the first byte of the content of the extension
* with the specified extensionID or -1 if no such extension was
* found.
*/
private int findExtension(int extensionID)
{
if( !getExtensionBit() || getExtensionLength() == 0)
return 0;
int extOffset = offset + FIXED_HEADER_SIZE
+ getCsrcCount()*4 + EXT_HEADER_SIZE;
int extensionEnd = extOffset + getExtensionLength();
int extHdrLen = getExtensionHeaderLength();
if (extHdrLen != 1 && extHdrLen != 2)
{
return -1;
}
while (extOffset < extensionEnd)
{
int currType = -1;
int currLen = -1;
if(extHdrLen == 1)
{
//short header. type is in the lefter 4 bits and length is on
//the right; like this:
// 0
// 0 1 2 3 4 5 6 7
// +-+-+-+-+-+-+-+-+
// | ID | len |
// +-+-+-+-+-+-+-+-+
currType = buffer[extOffset] >> 4;
currLen = (buffer[extOffset] & 0x0F) + 1; //add one as per 5285
//now skip the header
extOffset ++;
}
else
{
//long header. type is in the first byte and length is in the
//second
// 0 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
currType = buffer[extOffset];
currLen = buffer[extOffset + 1];
//now skip the header
extOffset += 2;
}
if(currType == extensionID)
{
return extOffset;
}
extOffset += currLen;
}
return -1;
}
/**
* Returns the length of the header extension that is carrying the content
* starting at contentStart. In other words this method checks the
* size of extension headers in this packet and then either returns the
* value of the byte right before contentStart or its lower 4 bits.
* This is a very basic method so if you are using it - make sure u know
* what you are doing.
*
* @param contentStart the index of the first element of the content of
* the extension whose size we are trying to obtain.
*
* @return the length of the extension carrying the content starting at
* contentStart.
*/
private int getLengthForExtension(int contentStart)
{
int hdrLen = getExtensionHeaderLength();
if( hdrLen == 1 )
return ( buffer[contentStart - 1] & 0x0F ) + 1;
else
return buffer[contentStart - 1];
}
/**
* Returns the length of the extension header being used in this packet or
* -1 in case there were no extension headers here or we didn't
* understand the kind of extension being used.
*
* @return the length of the extension header being used in this packet or
* -1 in case there were no extension headers here or we didn't
* understand the kind of extension being used.
*/
private int getExtensionHeaderLength()
{
if (!getExtensionBit())
return -1;
//the type of the extension header comes right after the RTP header and
//the CSRC list.
int extLenIndex = offset + FIXED_HEADER_SIZE + getCsrcCount()*4;
//0xBEDE means short extension header.
if (buffer[extLenIndex] == (byte)0xBE
&& buffer[extLenIndex + 1] == (byte)0xDE)
return 1;
//0x100 means a two-byte extension header.
if (buffer[extLenIndex]== (byte)0x10
&& (buffer[extLenIndex + 1] >> 4)== 0)
return 2;
return -1;
}
/**
* Return the define by profile part of the extension header.
* @return the starting two bytes of extension header.
*/
public int getHeaderExtensionType()
{
if (!getExtensionBit())
return 0;
return readUnsignedShortAsInt(
offset + FIXED_HEADER_SIZE + getCsrcCount()*4);
}
}