/*
* 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.rtcp;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.channels.DatagramChannel;
import org.apache.log4j.Logger;
import org.restcomm.media.ice.IceAuthenticator;
import org.restcomm.media.ice.IceComponent;
import org.restcomm.media.ice.IceHandler;
import org.restcomm.media.ice.events.IceEventListener;
import org.restcomm.media.ice.events.SelectedCandidatesEvent;
import org.restcomm.media.network.deprecated.UdpManager;
import org.restcomm.media.network.deprecated.channel.MultiplexedChannel;
import org.restcomm.media.rtp.RtpListener;
import org.restcomm.media.rtp.crypto.DtlsSrtpServerProvider;
import org.restcomm.media.rtp.secure.DtlsHandler;
import org.restcomm.media.rtp.secure.DtlsListener;
import org.restcomm.media.rtp.statistics.RtpStatistics;
import org.restcomm.media.spi.utils.Text;
/**
* Channel for exchanging RTCP traffic
*
* @author Henrique Rosa (henrique.rosa@telestax.com)
*
*/
public class RtcpChannel extends MultiplexedChannel implements DtlsListener, IceEventListener {
private static final Logger logger = Logger.getLogger(RtcpChannel.class);
// Core elements
private final UdpManager udpManager;
// Channel attribute
private int channelId;
private boolean bound;
// Protocol handler pipeline
private static final int STUN_PRIORITY = 3; // a packet each 400ms
private static final int RTCP_PRIORITY = 2; // a packet each 5s
private static final int DTLS_PRIORITY = 1; // only for a handshake
private RtcpHandler rtcpHandler;
private DtlsHandler dtlsHandler;
private IceHandler stunHandler;
// WebRTC
private boolean ice;
private boolean secure;
// Listeners
private RtpListener rtpListener;
public RtcpChannel(int channelId, RtpStatistics statistics, UdpManager udpManager, DtlsSrtpServerProvider dtlsServerProvider) {
// Initialize MultiplexedChannel elements
super();
// Core elements
this.udpManager = udpManager;
// Channel attributes
this.channelId = channelId;
this.bound = false;
// Protocol Handler pipeline
this.rtcpHandler = new RtcpHandler(udpManager.getScheduler(), statistics);
this.dtlsHandler = new DtlsHandler(dtlsServerProvider);
this.stunHandler = new IceHandler(IceComponent.RTCP_ID, this);
// WebRTC
this.secure = false;
}
public void setRemotePeer(SocketAddress remotePeer) {
if (this.dataChannel != null) {
if (this.dataChannel.isConnected()) {
try {
this.dataChannel.disconnect();
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
boolean connectNow = this.udpManager.connectImmediately((InetSocketAddress) remotePeer);
if (connectNow) {
try {
this.dataChannel.connect(remotePeer);
} catch (IOException e) {
logger.error("Can not connect to remote address. Check that you are not using local address (127.0.0.X)", e);
}
}
}
}
public void setRtpListener(RtpListener rtpListener) {
this.rtpListener = rtpListener;
}
public boolean isAvailable() {
// The channel is available is is connected
boolean available = this.dataChannel != null && this.dataChannel.isConnected();
// In case of WebRTC calls the DTLS handshake must be completed
if(this.secure) {
available = available && this.dtlsHandler.isHandshakeComplete();
}
return available;
}
public boolean isBound() {
return bound;
}
private void onBinding() {
// Set protocol handler priorities
this.rtcpHandler.setPipelinePriority(RTCP_PRIORITY);
if(this.secure) {
this.stunHandler.setPipelinePriority(STUN_PRIORITY);
}
// Protocol Handler pipeline
this.rtcpHandler.setChannel(this.dataChannel);
this.handlers.addHandler(this.rtcpHandler);
if(this.secure) {
this.dtlsHandler.setPipelinePriority(DTLS_PRIORITY);
this.handlers.addHandler(this.dtlsHandler);
this.dtlsHandler.setChannel(this.dataChannel);
this.dtlsHandler.addListener(this);
this.handlers.addHandler(this.stunHandler);
// Start DTLS handshake
this.dtlsHandler.handshake();
} else {
this.rtcpHandler.joinRtpSession();
}
}
/**
* Binds the channel to an address and port
*
* @param isLocal
* whether the connection is local or not
* @param port
* The RTCP port. Usually the RTP channel gets the even port and
* RTCP channel get the next port.
* @throws IOException
* When the channel cannot be openend or bound
*/
public void bind(boolean isLocal, int port) throws IOException {
try {
// Open this channel with UDP Manager on first available address
this.selectionKey = udpManager.open(this);
this.dataChannel = (DatagramChannel) this.selectionKey.channel();
} catch (IOException e) {
throw new SocketException(e.getMessage());
}
// activate media elements
onBinding();
// bind data channel
this.udpManager.bind(this.dataChannel, port, isLocal);
this.bound = true;
}
@Deprecated
public void bind(DatagramChannel channel) throws SocketException {
// External channel must be bound already
if (!channel.socket().isBound()) {
throw new SocketException("Datagram channel is not bound!");
}
try {
// Register the channel on UDP Manager
this.selectionKey = udpManager.open(channel, this);
this.dataChannel = channel;
} catch (IOException e) {
throw new SocketException(e.getMessage());
}
// activate media elements
onBinding();
this.bound = true;
}
/**
* Checks whether the channel is secure or not.
*
* @return Whether the channel handles regular RTCP traffic or SRTCP (secure).
*/
public boolean isSecure() {
return secure;
}
public void enableIce(IceAuthenticator authenticator) {
if(!this.ice) {
this.ice = true;
this.stunHandler.setAuthenticator(authenticator);
this.handlers.addHandler(this.stunHandler);
}
}
public void disableIce() {
if(this.ice) {
this.ice = false;
this.handlers.removeHandler(this.stunHandler);
}
}
public void enableSRTCP(String hashFunction, String remotePeerFingerprint) {
if (!this.secure) {
this.secure = true;
this.dtlsHandler.setRemoteFingerprint(hashFunction, remotePeerFingerprint);
// setup the SRTCP handler
this.rtcpHandler.enableSRTCP(this.dtlsHandler);
// Add handler to pipeline to handle incoming DTLS packets
this.dtlsHandler.setChannel(this.dataChannel);
this.handlers.addHandler(this.dtlsHandler);
}
}
public void enableSRTCP() {
if (!this.secure) {
this.secure = true;
// setup the SRTCP handler
this.rtcpHandler.enableSRTCP(this.dtlsHandler);
// Add handler to pipeline to handle incoming DTLS packets
this.dtlsHandler.setChannel(this.dataChannel);
this.handlers.addHandler(this.dtlsHandler);
}
}
public void setRemoteFingerprint(String hashFunction, String fingerprint) {
this.dtlsHandler.setRemoteFingerprint(hashFunction, fingerprint);
}
public void disableSRTCP() {
if (this.secure) {
this.secure = false;
// setup the DTLS handler
if (this.dtlsHandler != null) {
this.dtlsHandler.setRemoteFingerprint("", "");
}
this.dtlsHandler.resetLocalFingerprint();
// setup the SRTCP handler
this.rtcpHandler.disableSRTCP();
}
}
public Text getDtlsLocalFingerprint() {
if(this.secure) {
return new Text(this.dtlsHandler.getLocalFingerprint());
}
return new Text("");
}
@Override
public void close() {
/*
* Instruct the RTCP handler to leave the RTP session.
*
* This will result in scheduling an RTCP BYE to be sent. Since the BYE
* is not sent right away, the datagram channel can only be closed once
* the BYE has been sent. So, the handler is responsible for closing the
* channel.
*/
this.rtcpHandler.leaveRtpSession();
this.bound = false;
super.close();
reset();
}
public void reset() {
this.rtcpHandler.reset();
if(this.ice) {
disableIce();
this.stunHandler.reset();
}
if(this.secure) {
disableSRTCP();
this.dtlsHandler.reset();
}
}
public void onDtlsHandshakeComplete() {
logger.info("DTLS handshake completed for RTCP candidate.\nJoining RTP session.");
this.rtcpHandler.joinRtpSession();
}
public void onDtlsHandshakeFailed(Throwable e) {
if(this.rtpListener != null) {
this.rtpListener.onRtcpFailure(e);
}
}
@Override
public void onSelectedCandidates(SelectedCandidatesEvent event) {
try {
// Connect channel to start receiving traffic from remote peer
this.connect(event.getRemotePeer());
if (this.secure) {
// Start DTLS handshake
this.dtlsHandler.handshake();
}
} catch (IOException e) {
this.rtpListener.onRtcpFailure(e);
}
}
}