/* * TeleStax, Open Source Cloud Communications * Copyright 2011-2014, Telestax Inc and individual contributors * by the @authors tag. * * This program is free software: you can redistribute it and/or modify * under the terms of the GNU Affero General Public License as * published by the Free Software Foundation; either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> * */ package org.restcomm.media.rtp; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import org.apache.log4j.Logger; import org.restcomm.media.network.deprecated.channel.PacketHandler; import org.restcomm.media.network.deprecated.channel.PacketHandlerException; import org.restcomm.media.rtcp.RtcpHeader; import org.restcomm.media.rtp.jitter.FixedJitterBuffer; import org.restcomm.media.rtp.jitter.JitterBuffer; import org.restcomm.media.rtp.rfc2833.DtmfInput; import org.restcomm.media.rtp.secure.DtlsHandler; import org.restcomm.media.rtp.statistics.RtpStatistics; import org.restcomm.media.scheduler.PriorityQueueScheduler; import org.restcomm.media.sdp.format.RTPFormat; import org.restcomm.media.sdp.format.RTPFormats; /** * Handles incoming RTP packets. * * @author Oifa Yulian * @author Henrique Rosa (henrique.rosa@telestax.com) * */ public class RtpHandler implements PacketHandler { private static final Logger logger = Logger.getLogger(RtpHandler.class); private int pipelinePriority; private RTPFormats rtpFormats; private final RtpClock rtpClock; private final RtpClock oobClock; private JitterBuffer jitterBuffer; private int jitterBufferSize; private final RTPInput rtpInput; private final DtmfInput dtmfInput; private boolean loopable; private boolean receivable; private final RtpStatistics statistics; private final RtpPacket rtpPacket; // SRTP private boolean secure; private DtlsHandler dtlsHandler; public RtpHandler(PriorityQueueScheduler scheduler, RtpClock clock, RtpClock oobClock, int jitterBufferSize, RtpStatistics statistics) { this.pipelinePriority = 0; this.rtpClock = clock; this.oobClock = oobClock; this.jitterBufferSize = jitterBufferSize; this.jitterBuffer = new FixedJitterBuffer(this.rtpClock, this.jitterBufferSize); this.rtpInput = new RTPInput(scheduler, jitterBuffer); this.jitterBuffer.setListener(this.rtpInput); this.dtmfInput = new DtmfInput(scheduler, oobClock); this.rtpFormats = new RTPFormats(); this.statistics = statistics; this.rtpPacket = new RtpPacket(RtpPacket.RTP_PACKET_MAX_SIZE, true); this.receivable = false; this.loopable = false; this.secure = false; } public int getPipelinePriority() { return pipelinePriority; } public void setPipelinePriority(int pipelinePriority) { this.pipelinePriority = pipelinePriority; } public RTPInput getRtpInput() { return rtpInput; } public DtmfInput getDtmfInput() { return dtmfInput; } public boolean isLoopable() { return loopable; } public void setLoopable(boolean loopable) { this.loopable = loopable; } public boolean isReceivable() { return receivable; } public void setReceivable(boolean receivable) { this.receivable = receivable; } public void useJitterBuffer(boolean useBuffer) { this.jitterBuffer.setInUse(useBuffer); } /** * Modifies the map between format and RTP payload number * * @param rtpFormats * the format map */ public void setFormatMap(final RTPFormats rtpFormats) { this.rtpFormats = rtpFormats; } public RTPFormats getFormatMap() { return this.rtpFormats; } public void enableSrtp(final DtlsHandler handler) { this.secure = true; this.dtlsHandler = handler; } public void disableSrtp() { this.secure = false; this.dtlsHandler = null; } public void activate() { this.rtpInput.activate(); this.dtmfInput.activate(); } public void deactivate() { this.rtpInput.deactivate(); this.dtmfInput.deactivate(); } public void reset() { this.deactivate(); this.dtmfInput.reset(); this.jitterBuffer.restart(); if(this.secure) { disableSrtp(); } } public boolean canHandle(byte[] packet) { return canHandle(packet, packet.length, 0); } public boolean canHandle(byte[] packet, int dataLength, int offset) { /* * The RTP header has the following format: * * 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 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |V=2|P|X| CC |M| PT | sequence number | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | timestamp | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | synchronization source (SSRC) identifier | * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * | contributing source (CSRC) identifiers | * | .... | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * The first twelve octets are present in every RTP packet, while the * list of CSRC identifiers is present only when inserted by a mixer. * * The version defined by RFC3550 specification is two. */ // Packet must be equal or greater than an RTP Packet Header if (dataLength >= RtpPacket.FIXED_HEADER_SIZE) { // The most significant 2 bits of every RTP message correspond to the version. // Currently supported version is 2 according to RFC3550 byte b0 = packet[offset]; int b0Int = b0 & 0xff; // Differentiate between RTP, STUN and DTLS packets in the pipeline // https://tools.ietf.org/html/rfc5764#section-5.1.2 if (b0Int > 127 && b0Int < 192) { int version = (b0 & 0xC0) >> 6; if (RtpPacket.VERSION == version) { /* * When RTP and RTCP packets are multiplexed onto a single port, the RTCP packet type field occupies the * same position in the packet as the combination of the RTP marker (M) bit and the RTP payload type (PT). * This field can be used to distinguish RTP and RTCP packets when two restrictions are observed: * * 1) the RTP payload type values used are distinct from the RTCP packet types used. * * 2) for each RTP payload type (PT), PT+128 is distinct from the RTCP packet types used. The first * constraint precludes a direct conflict between RTP payload type and RTCP packet type; the second * constraint precludes a conflict between an RTP data packet with the marker bit set and an RTCP packet. */ int type = packet[offset + 1] & 0xff & 0x7f; int rtcpType = type + 128; // RTP payload types 72-76 conflict with the RTCP SR, RR, SDES, BYE, // and APP packets defined in the RTP specification switch (rtcpType) { case RtcpHeader.RTCP_SR: case RtcpHeader.RTCP_RR: case RtcpHeader.RTCP_SDES: case RtcpHeader.RTCP_BYE: case RtcpHeader.RTCP_APP: return false; default: return true; } } } } return false; } public byte[] handle(byte[] packet, InetSocketAddress localPeer, InetSocketAddress remotePeer) throws PacketHandlerException { return this.handle(packet, packet.length, 0, localPeer, remotePeer); } public byte[] handle(byte[] packet, int dataLength, int offset, InetSocketAddress localPeer, InetSocketAddress remotePeer) throws PacketHandlerException { // Do not handle data while DTLS handshake is ongoing. WebRTC calls only. if(this.secure && !this.dtlsHandler.isHandshakeComplete()) { return null; } if(this.secure) { // Decode SRTP packet into RTP. WebRTC calls only. byte[] decoded = this.dtlsHandler.decodeRTP(packet, offset, dataLength); if(decoded == null || decoded.length == 0) { logger.warn("SRTP packet is not valid! Dropping packet."); return null; } else { // Transform incoming data directly into an RTP Packet ByteBuffer buffer = this.rtpPacket.getBuffer(); buffer.clear(); buffer.put(decoded); buffer.flip(); } } else { // Transform incoming data directly into an RTP Packet ByteBuffer buffer = this.rtpPacket.getBuffer(); buffer.clear(); buffer.put(packet, offset, dataLength); buffer.flip(); } // For RTP keep-alive purposes this.statistics.setLastHeartbeat(this.rtpClock.getWallClock().getTime()); // RTP v0 packets are used in some applications. Discarded since we do not handle them. if (rtpPacket.getVersion() != 0 && (receivable || loopable)) { // Queue packet into the jitter buffer if (rtpPacket.getBuffer().limit() > 0) { if (loopable) { // Update statistics for RTCP this.statistics.onRtpReceive(rtpPacket); this.statistics.onRtpSent(rtpPacket); // Return same packet (looping) so it can be transmitted return packet; } else { // Update statistics for RTCP this.statistics.onRtpReceive(rtpPacket); // Write packet int payloadType = rtpPacket.getPayloadType(); RTPFormat format = rtpFormats.find(payloadType); if(format != null) { if(RtpChannel.DTMF_FORMAT.matches(format.getFormat())) { dtmfInput.write(rtpPacket); } else { jitterBuffer.write(rtpPacket, format); } } else { logger.warn("Dropping packet because payload type (" + payloadType + ") is unknown."); } } } else { logger.warn("Skipping packet because limit of the packets buffer is zero"); } } return null; } public int compareTo(PacketHandler o) { if(o == null) { return 1; } return this.getPipelinePriority() - o.getPipelinePriority(); } }