/** * Code derived and adapted from the Jitsi client side SRTP framework. * * Distributed under LGPL license. * See terms of license at gnu.org. */ package org.restcomm.media.rtp.crypto; import java.nio.ByteBuffer; import org.restcomm.media.rtp.RtpPacket; /** * 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 * @author Boris Grozev * @author Lyubomir Marinov * @author Henrique Rosa (henrique.rosa@telestax.com) */ public class RawPacket { /** * The size of the extension header as defined by RFC 3550. */ public static final int EXT_HEADER_SIZE = 4; /** * The size of the fixed part of the RTP header as defined by RFC 3550. */ public static final int FIXED_HEADER_SIZE = 12; /** * Byte array storing the content of this Packet */ private ByteBuffer buffer; /** * Initializes a new empty <tt>RawPacket</tt> instance. */ public RawPacket() { this.buffer = ByteBuffer.allocateDirect(RtpPacket.RTP_PACKET_MAX_SIZE); } /** * Initializes a new <tt>RawPacket</tt> instance with a specific * <tt>byte</tt> array buffer. * * @param buffer the <tt>byte</tt> array to be the buffer of the new * instance * @param offset the offset in <tt>buffer</tt> at which the actual data to * be represented by the new instance starts * @param length the number of <tt>byte</tt>s in <tt>buffer</tt> which * constitute the actual data to be represented by the new instance */ public RawPacket(byte[] data, int offset, int length) { this.buffer = ByteBuffer.allocateDirect(RtpPacket.RTP_PACKET_MAX_SIZE); wrap(data, offset, length); } public void wrap(byte[] data, int offset, int length) { this.buffer.clear(); this.buffer.rewind(); this.buffer.put(data, offset, length); this.buffer.flip(); this.buffer.rewind(); } public byte[] getData() { this.buffer.rewind(); byte[] data = new byte[this.buffer.limit()]; this.buffer.get(data, 0, data.length); return data; } /** * 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 || len > data.length) { throw new IllegalArgumentException("Invalid combination of parameters data and length to append()"); } int oldLimit = buffer.limit(); // grow buffer if necessary grow(len); // set positing to begin writing immediately after the last byte of the current buffer buffer.position(oldLimit); // set the buffer limit to exactly the old size plus the new appendix length buffer.limit(oldLimit + len); // append data buffer.put(data, 0, len); } /** * Get buffer containing the content of this packet * * @return buffer containing the content of this packet */ public ByteBuffer getBuffer() { return this.buffer; } /** * Returns <tt>true</tt> if the extension bit of this packet has been set * and <tt>false</tt> otherwise. * * @return <tt>true</tt> if the extension bit of this packet has been set * and <tt>false</tt> otherwise. */ public boolean getExtensionBit() { buffer.rewind(); return (buffer.get() & 0x10) == 0x10; } /** * 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() { int length = 0; if (getExtensionBit()) { // 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 = FIXED_HEADER_SIZE + getCsrcCount() * 4 + 2; length = ((buffer.get(extLenIndex) << 8) | buffer.get(extLenIndex + 1) * 4); } return length; } /** * Returns the number of CSRC identifiers currently included in this packet. * * @return the CSRC count for this <tt>RawPacket</tt>. */ public int getCsrcCount() { this.buffer.rewind(); return (this.buffer.get() & 0x0f); } /** * Get RTP header length from a RTP packet * * @return RTP header length from source RTP packet */ public int getHeaderLength() { int length = FIXED_HEADER_SIZE + 4 * getCsrcCount(); if(getExtensionBit()) { length += EXT_HEADER_SIZE + getExtensionLength(); } return length; } /** * Get the length of this packet's data * * @return length of this packet's data */ public int getLength() { return this.buffer.limit(); } /** * Get RTP padding size from a RTP packet * * @return RTP padding size from source RTP packet */ public int getPaddingSize() { buffer.rewind(); if ((this.buffer.get() & 0x20) == 0) { return 0; } return this.buffer.get(this.buffer.limit()); } /** * 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 payload length from a RTP packet * * @return RTP payload length from source RTP packet */ public int getPayloadLength() { return getLength() - getHeaderLength(); } /** * Get RTP payload type from a RTP packet * * @return RTP payload type of source RTP packet */ public byte getPayloadType() { buffer.rewind(); return (byte) (this.buffer.get(1) & (byte)0x7F); } /** * Get RTCP SSRC from a RTCP packet * * @return RTP SSRC from source RTP packet */ public int getRTCPSSRC() { return readInt(4); } /** * 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); } /** * Get RTP SSRC from a RTP packet * * @return RTP SSRC from source RTP packet */ public int getSSRC() { return readInt(8); } /** * Returns the timestamp for this RTP <tt>RawPacket</tt>. * * @return the timestamp for this RTP <tt>RawPacket</tt>. */ public long getTimestamp() { return readInt(4); } /** * 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 delta number of bytes to grow */ public void grow(int delta) { if (delta == 0) { return; } int newLen = buffer.limit() + delta; if (newLen <= buffer.capacity()) { // there is more room in the underlying reserved buffer memory buffer.limit(newLen); return; } else { // create a new bigger buffer ByteBuffer newBuffer = buffer.isDirect() ? ByteBuffer.allocateDirect(newLen) : ByteBuffer.allocate(newLen); buffer.rewind(); newBuffer.put(buffer); newBuffer.limit(newLen); // switch to new buffer buffer = newBuffer; } } /** * 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) { this.buffer.rewind(); return ((buffer.get(off++) & 0xFF) << 24) | ((buffer.get(off++) & 0xFF) << 16) | ((buffer.get(off++) & 0xFF) << 8) | (buffer.get(off++) & 0xFF); } /** * 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) { this.buffer.rewind(); if (off < 0 || len <= 0 || off + len > this.buffer.limit()) { return null; } byte[] region = new byte[len]; this.buffer.get(region, off, len); return region; } /** * Read a byte region from specified offset in the RTP packet and with * specified length into a given buffer * * @param off * start offset in the RTP packet 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) { assert off >= 0; assert len > 0; assert outBuff != null; assert outBuff.length >= len; assert buffer.limit() >= off + len; buffer.position(off); buffer.get(outBuff, 0, len); } /** * 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) { this.buffer.position(off); int b1 = (0x000000FF & (this.buffer.get())); int b2 = (0x000000FF & (this.buffer.get())); int val = b1 << 8 | b2; return val; } /** * 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) { buffer.position(off); return (((long)(buffer.get() & 0xff) << 24) | ((long)(buffer.get() & 0xff) << 16) | ((long)(buffer.get() & 0xff) << 8) | ((long)(buffer.get() & 0xff))) & 0xFFFFFFFFL; } /** * Shrink the buffer of this packet by specified length * * @param len length to shrink */ public void shrink(int delta) { if (delta <= 0) { return; } int newLimit = buffer.limit() - delta; if (newLimit <= 0) { newLimit = 0; } this.buffer.limit(newLimit); } }