/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jitsi.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
     * <tt>marker</tt> parameter.
     * @param marker <tt>true</tt> if we are to raise the marker bit and
     * <tt>false</tt> 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 <tt>RawPacket</tt>.
     *
     * @return the timestamp for this RTP <tt>RawPacket</tt>.
     */
    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 <tt>RawPacket</tt>.
     */
    public int getCsrcCount()
    {
        return (buffer[offset] & 0x0f);
    }

    /**
     * Replaces the existing CSRC list (even if empty) with <tt>newCsrcList</tt>
     * and updates the CC (CSRC count) field of this <tt>RawPacket</tt>
     * accordingly.
     *
     * @param newCsrcList the list of CSRC identifiers that we'd like to set for
     * this <tt>RawPacket</tt>.
     */
    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
     *
     * @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 authTagLen authentication tag length
     * @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 <tt>byte</tt>s 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 <tt>true</tt> if the extension bit of this packet has been set
     * and false otherwise.
     *
     * @return  <tt>true</tt> 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 <tt>extBit</tt> is
     * <tt>true</tt> or set it to <tt>0</tt> if <tt>extBit</tt> is
     * <tt>false</tt>.
     *
     * @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] << 8) | buffer[extLenIndex + 1]) * 4;
    }

    /**
     * Adds the <tt>extBuff</tt> 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 >> 8);
        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 <tt>RawPacket</tt> 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 <tt>0</tt> 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 <tt>RawPacket</tt> belongs to.
     *
     * @return the CSRC audio level at the specified index of the csrc audio
     * level option or <tt>0</tt> 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 <tt>extensionID</tt> 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 <tt>extensionID</tt> 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 <tt>contentStart</tt>. 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 <tt>contentStart</tt> 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
     * <tt>contentStart</tt>.
     */
    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
     * <tt>-1</tt> 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
     * <tt>-1</tt> 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);
    }
}