/*
* Jitsi, 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.protocol.jabber;
import java.util.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.media.*;
import net.java.sip.communicator.util.*;
import org.jitsi.service.neomedia.*;
import ch.imvs.sdes4j.srtp.*;
/**
* An implementation of the CallPeerMediaHandler abstract class for the
* common part of Jabber and Gtalk protocols.
*
* @author Vincent Lucas
* @author Lyubomir Marinov
*/
public abstract class AbstractCallPeerMediaHandlerJabberGTalkImpl
>
extends CallPeerMediaHandler
{
/**
* The Logger used by the CallPeerMediaHandlerJabberImpl
* class and its instances for logging output.
*/
private static final Logger logger
= Logger.getLogger(AbstractCallPeerMediaHandlerJabberGTalkImpl.class);
/**
* Indicates if the CallPeer will support inputevt
* extension (i.e. will be able to be remote-controlled).
*/
private boolean localInputEvtAware = false;
/**
* Creates a new handler that will be managing media streams for
* peer.
*
* @param peer that AbstractCallPeerJabberGTalkImpl instance that
* we will be managing media for.
*/
public AbstractCallPeerMediaHandlerJabberGTalkImpl(T peer)
{
super(peer, peer);
}
/**
* Gets the inputevt support: true for enable, false for disable.
*
* @return The state of inputevt support: true for enable, false for
* disable.
*/
public boolean getLocalInputEvtAware()
{
return this.localInputEvtAware;
}
/**
* Enable or disable inputevt support (remote-control).
*
* @param enable new state of inputevt support
*/
public void setLocalInputEvtAware(boolean enable)
{
localInputEvtAware = enable;
}
/**
* Detects and adds ZRTP available encryption method present in the
* description given in parameter.
*
* @param isInitiator True if the local call instance is the initiator of
* the call. False otherwise.
* @param description The DESCRIPTION element of the JINGLE element which
* contains the PAYLOAD-TYPE and (more important here) the ENCRYPTION.
* @param mediaType The type of media (AUDIO or VIDEO).
*/
protected void addZrtpAdvertisedEncryptions(
boolean isInitiator,
RtpDescriptionPacketExtension description,
MediaType mediaType)
{
CallPeer peer = getPeer();
Call call = peer.getCall();
/*
* ZRTP is not supported in telephony conferences utilizing the
* server-side technology Jitsi Videobridge yet.
*/
if (call.getConference().isJitsiVideobridge())
return;
// Conforming to XEP-0167 schema there is 0 or 1 ENCRYPTION element for
// a given DESCRIPTION.
EncryptionPacketExtension encryptionPacketExtension
= description.getFirstChildOfType(
EncryptionPacketExtension.class);
if(encryptionPacketExtension != null)
{
AccountID accountID = peer.getProtocolProvider().getAccountID();
if (accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_ENCRYPTION,
true)
&& accountID.isEncryptionProtocolEnabled(
ZrtpControl.PROTO_NAME)
&& call.isSipZrtpAttribute())
{
// ZRTP
ZrtpHashPacketExtension zrtpHashPacketExtension
= encryptionPacketExtension.getFirstChildOfType(
ZrtpHashPacketExtension.class);
if ((zrtpHashPacketExtension != null)
&& (zrtpHashPacketExtension.getValue() != null))
{
addAdvertisedEncryptionMethod(SrtpControlType.ZRTP);
}
}
}
}
/**
* Detects and adds SDES available encryption method present in the
* description given in parameter.
*
* @param isInitiator True if the local call instance is the initiator of
* the call. False otherwise.
* @param description The DESCRIPTION element of the JINGLE element which
* contains the PAYLOAD-TYPE and (more important here) the ENCRYPTION.
* @param mediaType The type of media (AUDIO or VIDEO).
*/
protected void addSDesAdvertisedEncryptions(
boolean isInitiator,
RtpDescriptionPacketExtension description,
MediaType mediaType)
{
CallPeer peer = getPeer();
/*
* SDES is not supported in telephony conferences utilizing the
* server-side technology Jitsi Videobridge yet.
*/
if (peer.getCall().getConference().isJitsiVideobridge())
return;
// Conforming to XEP-0167 schema there is 0 or 1 ENCRYPTION element for
// a given DESCRIPTION.
EncryptionPacketExtension encryptionPacketExtension
= description.getFirstChildOfType(
EncryptionPacketExtension.class);
if(encryptionPacketExtension != null)
{
AccountID accountID = peer.getProtocolProvider().getAccountID();
// SDES
if(accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_ENCRYPTION,
true)
&& accountID.isEncryptionProtocolEnabled(
SDesControl.PROTO_NAME))
{
SrtpControls srtpControls = getSrtpControls();
SDesControl sdesControl
= (SDesControl)
srtpControls.getOrCreate(
mediaType,
SrtpControlType.SDES);
SrtpCryptoAttribute selectedSdes
= selectSdesCryptoSuite(
isInitiator,
sdesControl,
encryptionPacketExtension);
if(selectedSdes != null)
{
//found an SDES answer, remove all other controls
removeAndCleanupOtherSrtpControls(
mediaType,
SrtpControlType.SDES);
addAdvertisedEncryptionMethod(SrtpControlType.SDES);
}
else
{
sdesControl.cleanup();
srtpControls.remove(mediaType, SrtpControlType.SDES);
}
}
}
// If we were initiating the encryption, and the remote peer does not
// manage it, then we must remove the unusable SDES srtpControl.
else if(isInitiator)
{
// SDES
SrtpControl sdesControl
= getSrtpControls().remove(mediaType, SrtpControlType.SDES);
if (sdesControl != null)
sdesControl.cleanup();
}
}
/**
* Returns the selected SDES crypto suite selected.
*
* @param isInitiator True if the local call instance is the initiator of
* the call. False otherwise.
* @param sDesControl The SDES based SRTP MediaStream encryption
* control.
* @param encryptionPacketExtension The ENCRYPTION element received from the
* remote peer. This may contain the SDES crypto suites available for the
* remote peer.
*
* @return The selected SDES crypto suite supported by both the local and
* the remote peer. Or null, if there is no crypto suite supported by both
* of the peers.
*/
protected SrtpCryptoAttribute selectSdesCryptoSuite(
boolean isInitiator,
SDesControl sDesControl,
EncryptionPacketExtension encryptionPacketExtension)
{
List cryptoPacketExtensions
= encryptionPacketExtension.getCryptoList();
List peerAttributes
= new ArrayList(cryptoPacketExtensions.size());
for (CryptoPacketExtension cpe : cryptoPacketExtensions)
peerAttributes.add(cpe.toSrtpCryptoAttribute());
return
isInitiator
? sDesControl.initiatorSelectAttribute(peerAttributes)
: sDesControl.responderSelectAttribute(peerAttributes);
}
/**
* Returns if the remote peer supports ZRTP.
*
* @param encryptionPacketExtension The ENCRYPTION element received from
* the remote peer. This may contain the ZRTP packet element for the remote
* peer.
*
* @return True if the remote peer supports ZRTP. False, otherwise.
*/
protected boolean isRemoteZrtpCapable(
EncryptionPacketExtension encryptionPacketExtension)
{
return
(encryptionPacketExtension.getFirstChildOfType(
ZrtpHashPacketExtension.class)
!= null);
}
/**
* Sets ZRTP element to the ENCRYPTION element of the DESCRIPTION for a
* given media.
*
* @param mediaType The type of media we are modifying the DESCRIPTION to
* integrate the ENCRYPTION element.
* @param description The element containing the media DESCRIPTION and its
* encryption.
* @param remoteDescription The element containing the media DESCRIPTION and
* its encryption for the remote peer. Null, if the local peer is the
* initiator of the call.
*
* @return True if the ZRTP element has been added to encryption. False,
* otherwise.
*/
protected boolean setZrtpEncryptionOnDescription(
MediaType mediaType,
RtpDescriptionPacketExtension description,
RtpDescriptionPacketExtension remoteDescription)
{
CallPeer peer = getPeer();
Call call = peer.getCall();
/*
* ZRTP is not supported in telephony conferences utilizing the
* server-side technology Jitsi Videobridge yet.
*/
if (call.getConference().isJitsiVideobridge())
return false;
boolean isRemoteZrtpCapable;
if (remoteDescription == null)
isRemoteZrtpCapable = true;
else
{
// Conforming to XEP-0167 schema there is 0 or 1 ENCRYPTION element
// for a given DESCRIPTION.
EncryptionPacketExtension remoteEncryption
= remoteDescription.getFirstChildOfType(
EncryptionPacketExtension.class);
isRemoteZrtpCapable
= (remoteEncryption != null)
&& isRemoteZrtpCapable(remoteEncryption);
}
boolean zrtpHashSet = false; // Will become true if at least one is set.
if (isRemoteZrtpCapable)
{
AccountID accountID = peer.getProtocolProvider().getAccountID();
if(accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_ENCRYPTION,
true)
&& accountID.isEncryptionProtocolEnabled(
ZrtpControl.PROTO_NAME)
&& call.isSipZrtpAttribute())
{
ZrtpControl zrtpControl
= (ZrtpControl)
getSrtpControls().getOrCreate(
mediaType,
SrtpControlType.ZRTP);
int numberSupportedVersions
= zrtpControl.getNumberSupportedVersions();
for (int i = 0; i < numberSupportedVersions; i++)
{
String helloHash[] = zrtpControl.getHelloHashSep(i);
if ((helloHash != null) && (helloHash[1].length() > 0))
{
ZrtpHashPacketExtension hash
= new ZrtpHashPacketExtension();
hash.setVersion(helloHash[0]);
hash.setValue(helloHash[1]);
EncryptionPacketExtension encryption
= description.getFirstChildOfType(
EncryptionPacketExtension.class);
if (encryption == null)
{
encryption = new EncryptionPacketExtension();
description.addChildExtension(encryption);
}
encryption.addChildExtension(hash);
zrtpHashSet = true;
}
}
}
}
return zrtpHashSet;
}
/**
* Sets SDES element(s) to the ENCRYPTION element of the DESCRIPTION for a
* given media.
*
* @param mediaType The type of media we are modifying the DESCRIPTION to
* integrate the ENCRYPTION element.
* @param localDescription The element containing the media DESCRIPTION and
* its encryption.
* @param remoteDescription The element containing the media DESCRIPTION and
* its encryption for the remote peer. Null, if the local peer is the
* initiator of the call.
*
* @return True if the crypto element has been added to encryption. False,
* otherwise.
*/
protected boolean setSDesEncryptionOnDescription(
MediaType mediaType,
RtpDescriptionPacketExtension localDescription,
RtpDescriptionPacketExtension remoteDescription)
{
CallPeer peer = getPeer();
/*
* SDES is not supported in telephony conferences utilizing the
* server-side technology Jitsi Videobridge yet.
*/
if (peer.getCall().getConference().isJitsiVideobridge())
return false;
AccountID accountID = peer.getProtocolProvider().getAccountID();
// check if SDES and encryption is enabled at all
if (accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_ENCRYPTION,
true)
&& accountID.isEncryptionProtocolEnabled(
SDesControl.PROTO_NAME))
{
// get or create the control
SrtpControls srtpControls = getSrtpControls();
SDesControl sdesControl
= (SDesControl)
srtpControls.getOrCreate(mediaType, SrtpControlType.SDES);
// set the enabled ciphers suites
String ciphers
= accountID.getAccountPropertyString(
ProtocolProviderFactory.SDES_CIPHER_SUITES);
if (ciphers == null)
{
ciphers =
JabberActivator.getResources().getSettingsString(
SDesControl.SDES_CIPHER_SUITES);
}
sdesControl.setEnabledCiphers(Arrays.asList(ciphers.split(",")));
// act as initiator
if (remoteDescription == null)
{
EncryptionPacketExtension localEncryption
= localDescription.getFirstChildOfType(
EncryptionPacketExtension.class);
if(localEncryption == null)
{
localEncryption = new EncryptionPacketExtension();
localDescription.addChildExtension(localEncryption);
}
for(SrtpCryptoAttribute ca:
sdesControl.getInitiatorCryptoAttributes())
{
CryptoPacketExtension crypto
= new CryptoPacketExtension(ca);
localEncryption.addChildExtension(crypto);
}
return true;
}
// act as responder
else
{
// Conforming to XEP-0167 schema there is 0 or 1 ENCRYPTION
// element for a given DESCRIPTION.
EncryptionPacketExtension remoteEncryption
= remoteDescription.getFirstChildOfType(
EncryptionPacketExtension.class);
if(remoteEncryption != null)
{
SrtpCryptoAttribute selectedSdes = selectSdesCryptoSuite(
false,
sdesControl,
remoteEncryption);
if(selectedSdes != null)
{
EncryptionPacketExtension localEncryption
= localDescription.getFirstChildOfType(
EncryptionPacketExtension.class);
if(localEncryption == null)
{
localEncryption = new EncryptionPacketExtension();
localDescription.addChildExtension(localEncryption);
}
CryptoPacketExtension crypto
= new CryptoPacketExtension(selectedSdes);
localEncryption.addChildExtension(crypto);
return true;
}
else
{
// none of the offered suites match, destroy the sdes
// control
sdesControl.cleanup();
srtpControls.remove(mediaType, SrtpControlType.SDES);
logger.warn(
"Received unsupported sdes crypto attribute");
}
}
else
{
// peer doesn't offer any SDES attribute, destroy the sdes
// control
sdesControl.cleanup();
srtpControls.remove(mediaType, SrtpControlType.SDES);
}
}
}
return false;
}
/**
* Selects the preferred encryption protocol (only used by the callee).
*
* @param mediaType The type of media (AUDIO or VIDEO).
* @param localDescription The element containing the media DESCRIPTION and
* its encryption.
* @param remoteDescription The element containing the media DESCRIPTION and
* its encryption for the remote peer; null if the local peer is
* the initiator of the call.
*/
protected void setAndAddPreferredEncryptionProtocol(
MediaType mediaType,
RtpDescriptionPacketExtension localDescription,
RtpDescriptionPacketExtension remoteDescription)
{
// Sets ZRTP or SDES, depending on the preferences for this account.
List preferredEncryptionProtocols
= getPeer()
.getProtocolProvider()
.getAccountID()
.getSortedEnabledEncryptionProtocolList();
for(String preferredEncryptionProtocol : preferredEncryptionProtocols)
{
String protoName
= preferredEncryptionProtocol.substring(
ProtocolProviderFactory.ENCRYPTION_PROTOCOL.length()
+ 1);
if (setAndAddPreferredEncryptionProtocol(
protoName,
mediaType,
localDescription,
remoteDescription))
{
// Stop once an encryption advertisement has been chosen.
return;
}
}
}
/**
* Selects a specific encryption protocol if it is the preferred (only used
* by the callee).
*
* @param protoName the name of the encryption protocol which is to be
* selected
* @param mediaType The type of media (AUDIO or VIDEO).
* @param localDescription The element containing the media DESCRIPTION and
* its encryption.
* @param remoteDescription The element containing the media DESCRIPTION and
* its encryption for the remote peer; null if the local peer is
* the initiator of the call.
* @return true if the specified encryption protocol has been
* selected; false, otherwise
*/
protected boolean setAndAddPreferredEncryptionProtocol(
String protoName,
MediaType mediaType,
RtpDescriptionPacketExtension localDescription,
RtpDescriptionPacketExtension remoteDescription)
{
/*
* Neither SDES nor ZRTP is supported in telephony conferences utilizing
* the server-side technology Jitsi Videobridge yet.
*/
if (getPeer().isJitsiVideobridge())
return false;
// SDES
if(SDesControl.PROTO_NAME.equals(protoName))
{
addSDesAdvertisedEncryptions(
false,
remoteDescription,
mediaType);
if(setSDesEncryptionOnDescription(
mediaType,
localDescription,
remoteDescription))
{
// Stop once an encryption advertisement has been chosen.
return true;
}
}
// ZRTP
else if(ZrtpControl.PROTO_NAME.equals(protoName))
{
if(setZrtpEncryptionOnDescription(
mediaType,
localDescription,
remoteDescription))
{
addZrtpAdvertisedEncryptions(
false,
remoteDescription,
mediaType);
// Stop once an encryption advertisement has been chosen.
return true;
}
}
return false;
}
}