From 6cd84a6a1a3a57f267fdd8d75d532e41ed1102f1 Mon Sep 17 00:00:00 2001 From: Ivan Valchev Date: Mon, 18 May 2015 10:12:46 +0300 Subject: [PATCH] Addint SRTP and rtcp-mux + few bugfixes --- src/com/xonami/javaBells/IceAgent.java | 109 +++++---- .../xonami/javaBells/JinglePacketHandler.java | 60 +++-- src/com/xonami/javaBells/JingleStream.java | 1 + .../xonami/javaBells/JingleStreamManager.java | 219 ++++++++++-------- src/com/xonami/javaBells/StunTurnAddress.java | 129 ++++++----- .../javaBellsSample/JavaBellsSample2.java | 4 +- .../SampleJinglePacketHandler.java | 4 +- .../javaBellsSample/SampleJingleSession.java | 103 ++++++-- .../jingle/CandidatePacketExtension.java | 15 +- .../extensions/jingle/JingleIQProvider.java | 52 ++++- .../jingle/PayloadTypePacketExtension.java | 12 +- .../extensions/jingle/RtcpMuxExtension.java | 25 ++ .../jingle/RtpDescriptionPacketExtension.java | 8 +- .../jingle/SsrcPacketExtension.java | 16 ++ .../jingle/StreamPacketExtension.java | 26 +++ .../jingle/StreamsPacketExtension.java | 41 ++++ 16 files changed, 554 insertions(+), 270 deletions(-) create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/RtcpMuxExtension.java create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/SsrcPacketExtension.java create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/StreamPacketExtension.java create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/StreamsPacketExtension.java diff --git a/src/com/xonami/javaBells/IceAgent.java b/src/com/xonami/javaBells/IceAgent.java index 01a922e..5e92962 100644 --- a/src/com/xonami/javaBells/IceAgent.java +++ b/src/com/xonami/javaBells/IceAgent.java @@ -27,6 +27,7 @@ import org.ice4j.ice.RemoteCandidate; import org.ice4j.ice.harvest.StunCandidateHarvester; import org.ice4j.ice.harvest.TurnCandidateHarvester; +import org.ice4j.security.LongTermCredential; //import org.ice4j.security.LongTermCredential; @@ -54,7 +55,7 @@ public class IceAgent { static final int MIN_STREAM_PORT = 5000; static final int MAX_STREAM_PORT = 9000; - static int streamPort = (int) ( random.nextFloat() * ( MAX_STREAM_PORT - MIN_STREAM_PORT ) + MIN_STREAM_PORT ); + static volatile int streamPort = (int) ( random.nextFloat() * ( MAX_STREAM_PORT - MIN_STREAM_PORT ) + MIN_STREAM_PORT ); private final Agent agent; @@ -75,6 +76,25 @@ public IceAgent( final boolean controling, StunTurnAddress sta ) { for( TransportAddress ta : sta.turnAddresses ) agent.addCandidateHarvester(new TurnCandidateHarvester(ta) ); } + + public IceAgent(final boolean controlling, + final TransportAddress[] stunAddresses, final TransportAddress[] turnAddresses, + final LongTermCredential ltCreds) { + this.agent = new Agent(); + agent.setControlling(controlling); + if( controlling ) + agent.setNominationStrategy(NominationStrategy.NOMINATE_HIGHEST_PRIO); + + //stun and turn + if (stunAddresses != null) + for( TransportAddress ta : stunAddresses ) + agent.addCandidateHarvester(new StunCandidateHarvester(ta) ); + + if (turnAddresses != null) + for( TransportAddress ta : turnAddresses ) + agent.addCandidateHarvester(new TurnCandidateHarvester(ta, ltCreds) ); + + } public void createStreams( Collection streamnames ) throws IOException { for( String s : streamnames ) { createStream( s ); @@ -98,6 +118,10 @@ public void removeAgentStateChangeListener( PropertyChangeListener pcl ) { public List getStreamNames() { return agent.getStreamNames(); } + public List getStreams() { + return agent.getStreams(); + } + public void removeStream(IceMediaStream stream) {agent.removeStream(stream);} public void freeAgent() { agent.free(); } @@ -109,7 +133,6 @@ public void addRemoteCandidates(JingleIQ jiq) { IceMediaStream ims = agent.getStream(name); if (ims != null) { for (IceUdpTransportPacketExtension tpe : contentpe.getChildExtensionsOfType(IceUdpTransportPacketExtension.class)) { - System.out.println( "\t" + tpe ); if (tpe.getPassword() != null) ims.setRemotePassword(tpe.getPassword()); if (tpe.getUfrag() != null) @@ -149,7 +172,7 @@ public void addRemoteCandidates(JingleIQ jiq) { RemoteCandidate rc = new RemoteCandidate( ta, component, convertType(cpe.getType()), - Integer.toString(cpe.getFoundation()), + cpe.getFoundation(), cpe.getPriority(), relatedCandidate); component.addRemoteCandidate(rc); @@ -184,50 +207,50 @@ public void addLocalCandidateToContents(List contentList cpe.addChildExtension(ext); } } - public IceUdpTransportPacketExtension getLocalCandidatePacketExtensionForStream( String streamName ) { - IceUdpTransportPacketExtension transport = new IceUdpTransportPacketExtension(); - transport.setPassword( agent.getLocalPassword() ); - transport.setUfrag( agent.getLocalUfrag() ); - - try { - IceMediaStream ims = agent.getStream(streamName); - if( ims == null ) - return null; - - for( Component c : ims.getComponents() ) { - for( Candidate can : c.getLocalCandidates() ) { - CandidatePacketExtension candidate = new CandidatePacketExtension(); - candidate.setComponent(c.getComponentID()); - candidate.setFoundation(Integer.parseInt(can.getFoundation())); - candidate.setGeneration(agent.getGeneration()); - candidate.setID(nextCandidateId()); - candidate.setNetwork(0); //FIXME: we need to identify the network card properly. - TransportAddress ta = can.getTransportAddress(); - candidate.setIP( ta.getHostAddress() ); - candidate.setPort( ta.getPort() ); - candidate.setPriority(can.getPriority()); - candidate.setProtocol(can.getTransport().toString()); - if( can.getRelatedAddress() != null ) { - candidate.setRelAddr(can.getRelatedAddress().getHostAddress()); - candidate.setRelPort(can.getRelatedAddress().getPort()); - } - candidate.setType(convertType(can.getType())); - - transport.addCandidate(candidate); - } - } - } catch( Exception e ) { - e.printStackTrace(); - System.exit(0); - } - return transport; - } - private CandidateType convertType(org.ice4j.ice.CandidateType type) { String ts = type.toString(); return CandidateType.valueOf(ts); } - private org.ice4j.ice.CandidateType convertType(CandidateType type) { + + public IceUdpTransportPacketExtension getLocalCandidatePacketExtensionForStream( String streamName ) { + IceUdpTransportPacketExtension transport = new IceUdpTransportPacketExtension(); + transport.setPassword( agent.getLocalPassword() ); + transport.setUfrag( agent.getLocalUfrag() ); + + try { + IceMediaStream ims = agent.getStream(streamName); + if( ims == null ) + return null; + + for( Component c : ims.getComponents() ) { + for( Candidate can : c.getLocalCandidates() ) { + CandidatePacketExtension candidate = new CandidatePacketExtension(); + candidate.setComponent(c.getComponentID()); + candidate.setFoundation(can.getFoundation()); + candidate.setGeneration(agent.getGeneration()); + candidate.setID(nextCandidateId()); + candidate.setNetwork(0); //FIXME: we need to identify the network card properly. + TransportAddress ta = can.getTransportAddress(); + candidate.setIP( ta.getHostAddress() ); + candidate.setPort( ta.getPort() ); + candidate.setPriority(can.getPriority()); + candidate.setProtocol(can.getTransport().toString()); + if( can.getRelatedAddress() != null ) { + candidate.setRelAddr(can.getRelatedAddress().getHostAddress()); + candidate.setRelPort(can.getRelatedAddress().getPort()); + } + candidate.setType(convertType(can.getType())); + + transport.addCandidate(candidate); + } + } + } catch( Exception e ) { + e.printStackTrace(); + System.exit(0); + } + return transport; + } + private org.ice4j.ice.CandidateType convertType(CandidateType type) { String ts = type.toString(); return org.ice4j.ice.CandidateType.parse(ts); } diff --git a/src/com/xonami/javaBells/JinglePacketHandler.java b/src/com/xonami/javaBells/JinglePacketHandler.java index 52a5d0d..48d3d8f 100644 --- a/src/com/xonami/javaBells/JinglePacketHandler.java +++ b/src/com/xonami/javaBells/JinglePacketHandler.java @@ -1,9 +1,12 @@ package com.xonami.javaBells; -import java.util.HashMap; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.JingleIQ; +import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.JinglePacketFactory; +import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.Reason; import org.jivesoftware.smack.PacketListener; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.filter.PacketFilter; @@ -18,8 +21,10 @@ * */ public class JinglePacketHandler implements PacketListener, PacketFilter { - private final HashMap jingleSessions = new HashMap(); - private final HashMap deadSessions = new HashMap(); +// private final Map jingleSessions = new ConcurrentHashMap(); + private volatile JingleSession jingleSession; + private volatile String sid; + private volatile String jid; protected final XMPPConnection connection; public JinglePacketHandler( XMPPConnection connection ) { @@ -32,13 +37,19 @@ public void processPacket(Packet packet) { JingleIQ jiq = (JingleIQ) packet; String sid = jiq.getSID(); - JingleSession js = jingleSessions.get(sid); - if( js == null ) - js = deadSessions.get(sid); - if( js == null ) { - js = createJingleSession( sid, jiq ); - jingleSessions.put( sid, js ); - } +// JingleSession js = jingleSessions.get(sid); + if( jingleSession == null ) { + createSession(jiq, sid); +// jingleSessions.put( sid, js ); + } else if (!jingleSession.getSessionId().equals(sid)) { + JingleIQ sessionTerminate = JinglePacketFactory + .createSessionTerminate(this.jid, jiq.getFrom(), this.sid, Reason.GONE, + "New session request from " + jiq.getFrom()); + connection.sendPacket(sessionTerminate); + destroySession(); + createSession(jiq, sid); + } + JingleSession js = jingleSession; switch( jiq.getAction() ) { case CONTENT_ACCEPT: js.handleContentAcept( jiq ); @@ -87,11 +98,26 @@ public void processPacket(Packet packet) { break; } } - - public JingleSession removeJingleSession( JingleSession session ) { - JingleSession ret = jingleSessions.remove( session.getSessionId() ); - deadSessions.put( session.getSessionId(), session ); - return ret; + + private void destroySession() { + jingleSession.handleSessionTerminate(null); + } + + private void createSession(final JingleIQ jiq, final String sid) { + jingleSession = createJingleSession( sid, jiq ); + this.sid = sid; + this.jid = jiq.getTo(); + } + + public JingleSession removeJingleSession( JingleSession session ) { + JingleSession ret = null; + if (jingleSession == session) { + ret = jingleSession; + jingleSession = null; + this.sid = null; + this.jid = null; + } + return ret; } /** @@ -112,4 +138,8 @@ public JingleSession createJingleSession( String sid, JingleIQ jiq ) { public boolean accept(Packet packet) { return packet.getClass() == JingleIQ.class; } + + public Collection getSessions(){ + return new HashSet(Arrays.asList(jingleSession)); + } } diff --git a/src/com/xonami/javaBells/JingleStream.java b/src/com/xonami/javaBells/JingleStream.java index 1e7839f..dced79b 100644 --- a/src/com/xonami/javaBells/JingleStream.java +++ b/src/com/xonami/javaBells/JingleStream.java @@ -103,6 +103,7 @@ private void updateVisualComponent() { } public void shutdown() { +// mediaStream.stop(); mediaStream.close(); } } diff --git a/src/com/xonami/javaBells/JingleStreamManager.java b/src/com/xonami/javaBells/JingleStreamManager.java index ad5d9f2..8d2bcd4 100644 --- a/src/com/xonami/javaBells/JingleStreamManager.java +++ b/src/com/xonami/javaBells/JingleStreamManager.java @@ -7,12 +7,10 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TreeMap; -import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.ContentPacketExtension; -import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.JingleIQ; -import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.ParameterPacketExtension; -import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.PayloadTypePacketExtension; -import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.RtpDescriptionPacketExtension; +import java.util.concurrent.ConcurrentHashMap; + +import ch.imvs.sdes4j.srtp.SrtpCryptoAttribute; +import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.ContentPacketExtension.CreatorEnum; import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.ContentPacketExtension.SendersEnum; @@ -21,29 +19,23 @@ import org.ice4j.ice.Component; import org.ice4j.ice.IceMediaStream; import org.jitsi.service.libjitsi.LibJitsi; -import org.jitsi.service.neomedia.DefaultStreamConnector; -import org.jitsi.service.neomedia.MediaDirection; -import org.jitsi.service.neomedia.MediaService; -import org.jitsi.service.neomedia.MediaStream; -import org.jitsi.service.neomedia.MediaStreamTarget; -import org.jitsi.service.neomedia.MediaType; -import org.jitsi.service.neomedia.MediaUseCase; -import org.jitsi.service.neomedia.StreamConnector; +import org.jitsi.service.neomedia.*; import org.jitsi.service.neomedia.device.MediaDevice; import org.jitsi.service.neomedia.format.AudioMediaFormat; import org.jitsi.service.neomedia.format.MediaFormat; public class JingleStreamManager { private static final DynamicPayloadTypeRegistry dynamicPayloadTypes = new DynamicPayloadTypeRegistry(); - + private final CreatorEnum creator; private SendersEnum senders; - - private final TreeMap devices = new TreeMap(); - private final TreeMap jingleStreams = new TreeMap(); - private final TreeMap streamNameToMediaFormats = new TreeMap(); - private final TreeMap streamNameToPayloadTypeId = new TreeMap(); - + + private final Map devices = new ConcurrentHashMap(); + private final Map jingleStreams = new ConcurrentHashMap(); + private final Map streamNameToMediaFormats = new ConcurrentHashMap(); + private final Map streamNameToPayloadTypeId = new ConcurrentHashMap(); + private final Map streamNameToSDesControl = new ConcurrentHashMap(); + public JingleStreamManager(CreatorEnum creator) { this.creator = creator; } @@ -51,24 +43,24 @@ public JingleStreamManager(CreatorEnum creator) { public Set getMediaNames() { return Collections.unmodifiableSet( devices.keySet() ); } - + public boolean addDefaultMedia( MediaType mediaType, String name ) { MediaService mediaService = LibJitsi.getMediaService(); MediaDevice dev = mediaService.getDefaultDevice(mediaType, MediaUseCase.CALL); - + if( dev == null ) return false; - + devices.put(name, dev); return true; } - + public MediaDevice getDefaultAudioDevice() { MediaService mediaService = LibJitsi.getMediaService(); MediaDevice dev = mediaService.getDefaultDevice(MediaType.AUDIO, MediaUseCase.CALL); return dev; } - + public List createContentList(SendersEnum senders) { this.senders = senders; List contentList = new ArrayList(); @@ -107,14 +99,14 @@ private ContentPacketExtension createContentPacketExtention(SendersEnum senders, return content; } - + public JingleStream startStream(String name, IceAgent iceAgent ) throws IOException { if( streamNameToMediaFormats.size() == 0 ) { // media has not been negotiated. This seems to happen with jitsi. // we will assume our requested formats are acceptable. parseIncomingAndBuildMedia( createContentList( senders ), senders ); } - + IceMediaStream stream = iceAgent.getAgent().getStream(name); MediaFormat format = streamNameToMediaFormats.get(name); Byte payloadTypeId = streamNameToPayloadTypeId.get(name); @@ -122,7 +114,7 @@ public JingleStream startStream(String name, IceAgent iceAgent ) throws IOExcept throw new IOException("Stream \"" + name + "\" not found."); Component rtpComponent = stream.getComponent(org.ice4j.ice.Component.RTP); Component rtcpComponent = stream.getComponent(org.ice4j.ice.Component.RTCP); - + if( rtpComponent == null ) throw new IOException("RTP component not found."); if( rtcpComponent == null ) @@ -133,7 +125,7 @@ public JingleStream startStream(String name, IceAgent iceAgent ) throws IOExcept // System.out.println( "RTP : L " + rtpPair.getLocalCandidate().getDatagramSocket().getLocalPort() + " <-> " + rtpPair.getRemoteCandidate().getTransportAddress() + " R " ); // System.out.println( "RTCP: L " + rtcpPair.getLocalCandidate().getDatagramSocket().getLocalPort() + " <-> " + rtcpPair.getRemoteCandidate().getTransportAddress() + " R " ); - + return startStream( name, payloadTypeId, format, @@ -142,15 +134,17 @@ public JingleStream startStream(String name, IceAgent iceAgent ) throws IOExcept rtpPair.getLocalCandidate().getDatagramSocket(), rtcpPair.getLocalCandidate().getDatagramSocket()); } - + public JingleStream startStream( String name, byte payloadTypeId, MediaFormat format, TransportAddress remoteRtpAddress, TransportAddress remoteRtcpAddress, DatagramSocket rtpDatagramSocket, DatagramSocket rtcpDatagramSocket ) throws IOException { MediaDevice dev = devices.get(name); - + MediaService mediaService = LibJitsi.getMediaService(); - - MediaStream mediaStream = mediaService.createMediaStream(dev); - mediaStream.setDirection(MediaDirection.SENDRECV); + MediaStream mediaStream = mediaService.createMediaStream( + new DefaultStreamConnector(rtpDatagramSocket, rtcpDatagramSocket), dev, + streamNameToSDesControl.get(name)); + + mediaStream.setDirection(MediaDirection.SENDONLY); /* * The MediaFormat instances which do not have a static RTP @@ -162,7 +156,7 @@ public JingleStream startStream( String name, byte payloadTypeId, MediaFormat fo payloadTypeId, format); } - + // System.out.println( "++++++++++++++++++++++" ); // System.out.println( "++++++++++++++++++++++" ); // System.out.println( "++++++++++++++++++++++" ); @@ -175,21 +169,17 @@ public JingleStream startStream( String name, byte payloadTypeId, MediaFormat fo mediaStream.setFormat(format); - StreamConnector connector = new DefaultStreamConnector( rtpDatagramSocket, rtcpDatagramSocket ); - - mediaStream.setConnector(connector); - mediaStream.setTarget( new MediaStreamTarget( remoteRtpAddress, remoteRtcpAddress ) ); mediaStream.setName(name); - + mediaStream.start(); - + JingleStream js = new JingleStream( name, mediaStream, this ); jingleStreams.put( name, js ); return js; } - + public static PayloadTypePacketExtension formatToPayloadType( MediaFormat format, DynamicPayloadTypeRegistry ptRegistry, @@ -212,7 +202,7 @@ public static PayloadTypePacketExtension formatToPayloadType( if(format instanceof AudioMediaFormat) ptExt.setChannels(((AudioMediaFormat)format).getChannels()); - ptExt.setClockrate((int)format.getClockRate()); + ptExt.setClockrate(String.valueOf(Double.valueOf(format.getClockRate()).intValue())); /* * Add the format parameters and the advanced attributes (as parameter @@ -237,7 +227,7 @@ public static PayloadTypePacketExtension formatToPayloadType( return ptExt; } - + /** Parses the incoming session-initiate or session-accept jingle iqs and * and sets up a local stream as required by building the required media. * Returns a new list of ContentPacketExtention representing the content @@ -246,60 +236,68 @@ public static PayloadTypePacketExtension formatToPayloadType( * @param senders who is sending and receiving media? * @throws IOException if the packets are not correctly parsed. */ public List parseIncomingAndBuildMedia(JingleIQ jiq, SendersEnum senders) throws IOException { - return parseIncomingAndBuildMedia(jiq.getContentList(),senders); + return parseIncomingAndBuildMedia(jiq,senders, true, true); } - public List parseIncomingAndBuildMedia(List cpes, SendersEnum senders) throws IOException { + public List parseIncomingAndBuildMedia(JingleIQ jiq, SendersEnum senders, boolean audio, boolean video) throws IOException { + return parseIncomingAndBuildMedia(jiq.getContentList(),senders, audio, video); + } + public List parseIncomingAndBuildMedia(List cpes, SendersEnum senders) throws IOException { + return parseIncomingAndBuildMedia(cpes, senders, true, true); + } + public List parseIncomingAndBuildMedia(List cpes, SendersEnum senders, boolean audio, boolean video) throws IOException { this.senders = senders; String name = null; String toclean = null; List ret = new ArrayList(); - try { - for( ContentPacketExtension cpe : cpes ) { - toclean = name = cpe.getName(); - if( name == null ) - throw new IOException(); - -// SendersEnum senders = cpe.getSenders(); -// CreatorEnum creator = cpe.getCreator(); - String media = null; - - List descriptions = cpe.getChildExtensionsOfType(RtpDescriptionPacketExtension.class); - - for( RtpDescriptionPacketExtension description : descriptions ) { - System.out.println( description ); - media = description.getMedia(); - if( "audio".equals(media) ) { - if( !addDefaultMedia( MediaType.AUDIO, name ) ) - throw new IOException( "Could not create audio device" ); - } else if( "video".equals(media) ) { - if( !addDefaultMedia( MediaType.VIDEO, name ) ) - throw new IOException( "Could not create video device" ); - } else { - throw new IOException( "Unknown media type: " + media ); - } - List payloadTypes = description.getPayloadTypes(); - for( PayloadTypePacketExtension payloadType : payloadTypes ) { - MediaFormat mf = getSupportedFormat( name, payloadType ); - if( mf == null ) - continue; //no match - ret.add( createContentPacketExtention( senders, name, devices.get(name), mf, payloadType.getID() ) ); - toclean = null; - streamNameToMediaFormats.put( name, mf ); - streamNameToPayloadTypeId.put( name, (byte) payloadType.getID() ); - break; //stop on first match - } - } - if( media == null ) - throw new IOException(); - } - if( ret.size() == 0 ) + for (ContentPacketExtension cpe : cpes) { + try { + toclean = name = cpe.getName(); + if (name == null) + throw new IOException(); + + String media = null; + + List descriptions = cpe.getChildExtensionsOfType(RtpDescriptionPacketExtension.class); + + for (RtpDescriptionPacketExtension description : descriptions) { + media = description.getMedia(); + boolean mediaAdded = false; + if (audio && "audio".equals(media)) { + mediaAdded = addDefaultMedia(MediaType.AUDIO, name); + } else if (video && "video".equals(media)) { + mediaAdded = addDefaultMedia(MediaType.VIDEO, name); + } + if (mediaAdded) { + List payloadTypes = description.getPayloadTypes(); + for (PayloadTypePacketExtension payloadType : payloadTypes) { + MediaFormat mf = getSupportedFormat(name, payloadType); + if (mf == null) + continue; //no match + final ContentPacketExtension cpeResponse = + createContentPacketExtention(senders, name, devices.get(name), mf, + payloadType.getID()); + addEncryption(name, description, + (RtpDescriptionPacketExtension) cpeResponse.getChildExtensions().get(0)); + ret.add(cpeResponse); + toclean = null; + streamNameToMediaFormats.put(name, mf); + streamNameToPayloadTypeId.put(name, (byte) payloadType.getID()); + break; //stop on first match + } + } + } + if (media == null) + throw new IOException(); + } finally { + if (toclean != null) + devices.remove(toclean); + } + } + if( ret.size() == 0 ) return null; return ret; - } finally { - if( toclean != null ) - devices.remove(toclean); - } } + private MediaFormat getSupportedFormat( String name, PayloadTypePacketExtension payloadType ) { MediaDevice dev = devices.get(name); MediaType mediaType = dev.getMediaType(); @@ -310,11 +308,46 @@ private MediaFormat getSupportedFormat( String name, PayloadTypePacketExtension // && mf.getEncoding().equals(payloadType.getName()) ) { //FIXME: we should probably check advanced attributes and format parameters, but my guess is // that in most cases we can adapt. - if (mf.matches(mediaType, payloadType.getName(), payloadType.getClockrate(), payloadType.getChannels(), null)) {//formatParameters is not used by default + final String clockrate = payloadType.getClockrate(); + final String[] split = clockrate.split("/"); + if(split.length > 0){ + if (mf.matches(mediaType, payloadType.getName(), Double.valueOf(split[0]), payloadType.getChannels(), null)) {//formatParameters is not used by default return mf; - } + }} } return null; } + public void closeStreams() { + for (JingleStream jingleStream : jingleStreams.values()) { + jingleStream.shutdown(); + } + } + + private RtpDescriptionPacketExtension addEncryption(String streamName, RtpDescriptionPacketExtension offer, + RtpDescriptionPacketExtension response) { + final EncryptionPacketExtension encryptionOffer = offer.getEncryption(); + if (encryptionOffer != null) { + final SDesControl sDesControl = + (SDesControl) LibJitsi.getMediaService().createSrtpControl(SrtpControlType.SDES); +// sDesControl.getInitiatorCryptoAttributes(); + List cryptoPacketExtensions + = encryptionOffer.getCryptoList(); + List peerAttributes + = new ArrayList(cryptoPacketExtensions.size()); + + for (CryptoPacketExtension cpe : cryptoPacketExtensions) { + peerAttributes.add(cpe.toSrtpCryptoAttribute()); + } + final SrtpCryptoAttribute srtpCryptoAttribute = sDesControl.responderSelectAttribute(peerAttributes); + if (srtpCryptoAttribute != null) { + final EncryptionPacketExtension encryptionResponse = new EncryptionPacketExtension(); + encryptionResponse.addChildExtension(new CryptoPacketExtension(srtpCryptoAttribute)); + response.setEncryption(encryptionResponse); + response.setAttribute("profile", "RTP/SAVPF"); + streamNameToSDesControl.put(streamName, sDesControl); + } + } + return response; + } } \ No newline at end of file diff --git a/src/com/xonami/javaBells/StunTurnAddress.java b/src/com/xonami/javaBells/StunTurnAddress.java index cf38dab..f31c42a 100644 --- a/src/com/xonami/javaBells/StunTurnAddress.java +++ b/src/com/xonami/javaBells/StunTurnAddress.java @@ -27,13 +27,14 @@ public class StunTurnAddress { * fetch happens in the background, starting when a StunTurnAddress object is created. * * @param connection - * @return + * @ */ public static StunTurnAddress getAddress( XMPPConnection connection ) { String hosts[] = new String[] { - connection.getServiceName(), - connection.getHost(), - "jitsi.org", //fallback on jitsi.org +// connection.getServiceName(), + "54.171.56.211" +// connection.getHost(), +// "jitsi.org", //fallback on jitsi.org }; String hostsAsOneString = ""; for( int i=0; i iceAgentKillList = new LinkedList(); private JingleStreamManager jingleStreamManager; private JingleStream jingleStream; private final CallMode callMode; @@ -73,6 +77,9 @@ protected void closeSession(Reason reason) { jingleStream.shutdown(); if( iceAgent != null ) iceAgent.freeAgent(); + for(IceAgent ia:iceAgentKillList){ + ia.freeAgent(); + } } @Override @@ -89,15 +96,15 @@ public void handleSessionInitiate(JingleIQ jiq) { break; case ANSWER: System.out.println("Accepting call!"); - + // set the peerJid peerJid = jiq.getFrom(); // okay, it matched, so accept the call and start negotiating StunTurnAddress sta = StunTurnAddress.getAddress( connection ); - + jingleStreamManager = new JingleStreamManager(CreatorEnum.initiator); - List acceptedContent = jingleStreamManager.parseIncomingAndBuildMedia( jiq, ContentPacketExtension.SendersEnum.both ); + List acceptedContent = jingleStreamManager.parseIncomingAndBuildMedia( jiq, SendersEnum.responder ); if( acceptedContent == null ) { System.out.println("Rejecting call!"); @@ -115,7 +122,7 @@ public void handleSessionInitiate(JingleIQ jiq) { JingleIQ iq = JinglePacketFactory.createSessionAccept(myJid, peerJid, sessionId, acceptedContent); connection.sendPacket(iq); state = SessionState.NEGOTIATING_TRANSPORT; - + iceAgent.addRemoteCandidates( jiq ); iceAgent.startConnectivityEstablishment(); active = true; @@ -136,13 +143,15 @@ public void handleSessionInitiate(JingleIQ jiq) { connection.sendPacket(iq); closeSession(Reason.FAILED_APPLICATION); } catch( Exception e ) { - System.out.println("An error occured. Rejecting call!"); + System.out.println("An error occured. Rejecting call!"); + System.out.println(e.getMessage()); + System.out.println(e.getStackTrace()); JingleIQ iq = JinglePacketFactory.createCancel(myJid, peerJid, sessionId); connection.sendPacket(iq); closeSession(Reason.FAILED_APPLICATION); } } - + @Override public void handleSessionInfo(JingleIQ jiq) { if( !check(jiq) ) @@ -153,26 +162,26 @@ public void handleSessionInfo(JingleIQ jiq) { } unsupportedInfo( jiq ); } - + @Override public void handleSessionAccept(JingleIQ jiq) { switch( callMode ) { case CALL: // System.out.println( "====:: Got session accept from " + jiq + " :: " + peerJid ); - + if( peerJid == null ) peerJid = jiq.getFrom(); - + //acknowledge if( !checkAndAck(jiq) ) return; - + // System.out.println( "====:: Processing accept" ); state = SessionState.NEGOTIATING_TRANSPORT; - + try { - if( null == jingleStreamManager.parseIncomingAndBuildMedia( jiq, SendersEnum.both ) ) + if( null == jingleStreamManager.parseIncomingAndBuildMedia( jiq, SendersEnum.initiator ) ) throw new IOException( "No incoming streams detected." ); // System.out.println( "====:: Processing accept a" ); iceAgent.addRemoteCandidates( jiq ); @@ -200,24 +209,24 @@ public void handleSessionAccept(JingleIQ jiq) { break; } } - + @Override public void handleTransportInfo(JingleIQ jiq) { switch( callMode ) { case CALL: // System.out.println( "====:: Got transport-info from " + jiq + " :: " + peerJid ); - + if( peerJid == null ) peerJid = jiq.getFrom(); - + //acknowledge if( !checkAndAck(jiq) ) return; - + // System.out.println( "====:: Processing transport-info..." ); state = SessionState.NEGOTIATING_TRANSPORT; - + try { // if( null == jingleStreamManager.parseIncomingAndBuildMedia( jiq, SendersEnum.both ) ) // throw new IOException( "No incoming streams detected." ); @@ -271,7 +280,8 @@ public void propertyChange(PropertyChangeEvent evt) { for( String s : iceAgent.getStreamNames() ) { System.out.println( "For Stream : " + s ); jingleStream = jingleStreamManager.startStream(s, iceAgent); - jingleStream.quickShow(jingleStreamManager.getDefaultAudioDevice()); + jingleStream.startAudio(jingleStreamManager.getDefaultAudioDevice()); +// jingleStream.quickShow(jingleStreamManager.getDefaultAudioDevice()); } } catch (IOException ioe) { System.out.println( "IOException." ); @@ -287,7 +297,62 @@ public void propertyChange(PropertyChangeEvent evt) { } } + public void handleContentAdd(JingleIQ jiq) { + checkAndAck(jiq); + try { + switch (callMode) { + case ANSWER: + + System.out.println("Content Add!"); + + // set the peerJid + peerJid = jiq.getFrom(); + + // okay, it matched, so accept the call and start negotiating +// StunTurnAddress sta = StunTurnAddress.getAddress(connection); + +// jingleStreamManager = new JingleStreamManager(CreatorEnum.initiator); + List acceptedContent = jingleStreamManager.parseIncomingAndBuildMedia(jiq, SendersEnum.responder); + + if (acceptedContent == null) { + System.out.println("Rejecting content add!"); + // it didn't match. Reject the call. + closeSession(Reason.INCOMPATIBLE_PARAMETERS); + return; + } + if(iceAgent.getState() == IceProcessingState.COMPLETED || iceAgent.getState() == IceProcessingState.TERMINATED){ + System.out.println("Media Stream Names: " + jingleStreamManager.getMediaNames()); + iceAgent.createStreams(jingleStreamManager.getMediaNames()); + + iceAgent.addAgentStateChangeListener(this); + iceAgent.addLocalCandidateToContents(acceptedContent); + + JingleIQ iq = JinglePacketFactory.createContentAccept(myJid, peerJid, sessionId, acceptedContent); + connection.sendPacket(iq); + state = SessionState.NEGOTIATING_TRANSPORT; + + iceAgent.addRemoteCandidates(jiq); + iceAgent.startConnectivityEstablishment(); + active = true; + } else { + JingleIQ iq = JinglePacketFactory.createContentReject(myJid, peerJid, sessionId, acceptedContent); + connection.sendPacket(iq); + } + break; + } + } catch (Exception e) { + System.out.println("An error occured. Rejecting call!"); + JingleIQ iq = JinglePacketFactory.createCancel(myJid, peerJid, sessionId); + connection.sendPacket(iq); + closeSession(Reason.FAILED_APPLICATION); + } + } + public boolean isActive() { return active; } + + public void closeSession(){ + closeSession(Reason.GONE); + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CandidatePacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CandidatePacketExtension.java index 1ecf942..f0aca56 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CandidatePacketExtension.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CandidatePacketExtension.java @@ -14,11 +14,6 @@ public class CandidatePacketExtension extends AbstractPacketExtension implements Comparable { - /** - * The namespace. - */ - public static final String NAMESPACE - = "urn:xmpp:jingle:transports:ice-udp:1"; /** * The name of the "candidate" element. */ @@ -99,7 +94,7 @@ public class CandidatePacketExtension extends AbstractPacketExtension */ public CandidatePacketExtension() { - super(NAMESPACE, ELEMENT_NAME); + super(null, ELEMENT_NAME); } /** @@ -111,7 +106,7 @@ public CandidatePacketExtension() */ protected CandidatePacketExtension(String elementName) { - super(NAMESPACE, elementName); + super(null, elementName); } /** @@ -139,7 +134,7 @@ public int getComponent() * * @param foundation the candidate foundation as defined in ICE-CORE. */ - public void setFoundation(int foundation) + public void setFoundation(String foundation) { super.setAttribute(FOUNDATION_ATTR_NAME, foundation); } @@ -149,9 +144,9 @@ public void setFoundation(int foundation) * * @return the candidate foundation as defined in ICE-CORE. */ - public int getFoundation() + public String getFoundation() { - return super.getAttributeAsInt(FOUNDATION_ATTR_NAME); + return super.getAttributeAsString(FOUNDATION_ATTR_NAME); } /** diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java index 6fb6cc2..ddd099e 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java @@ -44,6 +44,14 @@ public JingleIQProvider() ( PayloadTypePacketExtension.class)); + // provider + providerManager.addExtensionProvider( + RtcpMuxExtension.ELEMENT_NAME, + RtpDescriptionPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + ( + RtcpMuxExtension.class)); + // provider providerManager.addExtensionProvider( ParameterPacketExtension.ELEMENT_NAME, @@ -60,10 +68,39 @@ public JingleIQProvider() // provider providerManager.addExtensionProvider( - EncryptionPacketExtension.ELEMENT_NAME, - RtpDescriptionPacketExtension.NAMESPACE, - new DefaultPacketExtensionProvider - (EncryptionPacketExtension.class)); + EncryptionPacketExtension.ELEMENT_NAME, + RtpDescriptionPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + (EncryptionPacketExtension.class)); + + + // provider + providerManager.addExtensionProvider( + CryptoPacketExtension.ELEMENT_NAME, + RtpDescriptionPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + (CryptoPacketExtension.class)); + + // provider + providerManager.addExtensionProvider( + StreamsPacketExtension.ELEMENT_NAME, + RtpDescriptionPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + (StreamsPacketExtension.class)); + + // provider + providerManager.addExtensionProvider( + StreamPacketExtension.ELEMENT_NAME, + RtpDescriptionPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + (StreamPacketExtension.class)); + + // provider + providerManager.addExtensionProvider( + SsrcPacketExtension.ELEMENT_NAME, + RtpDescriptionPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + (SsrcPacketExtension.class)); // provider providerManager.addExtensionProvider( @@ -72,13 +109,6 @@ public JingleIQProvider() new DefaultPacketExtensionProvider (ZrtpHashPacketExtension.class)); - // provider - providerManager.addExtensionProvider( - CryptoPacketExtension.ELEMENT_NAME, - RtpDescriptionPacketExtension.NAMESPACE, - new DefaultPacketExtensionProvider - (CryptoPacketExtension.class)); - //ice-udp transport providerManager.addExtensionProvider( IceUdpTransportPacketExtension.ELEMENT_NAME, diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/PayloadTypePacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/PayloadTypePacketExtension.java index c538b0a..3f2a897 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/PayloadTypePacketExtension.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/PayloadTypePacketExtension.java @@ -18,10 +18,6 @@ */ public class PayloadTypePacketExtension extends AbstractPacketExtension { - /** - * The namespace. - */ - public static final String NAMESPACE = "urn:xmpp:jingle:apps:rtp:1"; /** * The name of the "payload-type" element. */ @@ -62,7 +58,7 @@ public class PayloadTypePacketExtension extends AbstractPacketExtension */ public PayloadTypePacketExtension() { - super(NAMESPACE, ELEMENT_NAME); + super(null, ELEMENT_NAME); } /** @@ -95,7 +91,7 @@ public int getChannels() * * @param clockrate the sampling frequency in Hertz used by this encoding. */ - public void setClockrate(int clockrate) + public void setClockrate(String clockrate) { super.setAttribute(CLOCKRATE_ATTR_NAME, clockrate); } @@ -105,9 +101,9 @@ public void setClockrate(int clockrate) * * @return the sampling frequency in Hertz used by this encoding. */ - public int getClockrate() + public String getClockrate() { - return getAttributeAsInt(CLOCKRATE_ATTR_NAME); + return getAttributeAsString(CLOCKRATE_ATTR_NAME); } /** diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/RtcpMuxExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/RtcpMuxExtension.java new file mode 100644 index 0000000..248ccae --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/RtcpMuxExtension.java @@ -0,0 +1,25 @@ +package net.java.sip.communicator.impl.protocol.jabber.extensions.jingle; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.AbstractPacketExtension; + +import java.util.List; + +/** + * Copyright (c) Tuenti Technologies. All rights reserved. + * + * @author Manuel Peinado Gallego + */ +public class RtcpMuxExtension extends AbstractPacketExtension { + /** + * The name of the "payload-type" element. + */ + public static final String ELEMENT_NAME = "rtcp-mux"; + + /** + * Creates a new {@link RtcpMuxExtension} instance. + */ + public RtcpMuxExtension() + { + super(null, ELEMENT_NAME); + } +} \ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/RtpDescriptionPacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/RtpDescriptionPacketExtension.java index 20cb3e1..148aa75 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/RtpDescriptionPacketExtension.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/RtpDescriptionPacketExtension.java @@ -18,7 +18,7 @@ * @author Emil Ivov */ public class RtpDescriptionPacketExtension - extends AbstractPacketExtension + extends AbstractPacketExtension { /** * The name space for RTP description elements. @@ -44,7 +44,7 @@ public class RtpDescriptionPacketExtension * The list of payload types that this description element contains. */ private final List payloadTypes - = new ArrayList(); + = new ArrayList(); /** * An optional encryption element that contains encryption parameters for @@ -63,7 +63,7 @@ public class RtpDescriptionPacketExtension * negotiating RTP extension headers as per RFC 5282. */ private List extmapList - = new ArrayList(); + = new ArrayList(); /** * The combined list of all child elements that this extension contains. @@ -287,4 +287,4 @@ public List getExtmapList() { return extmapList; } -} +} \ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/SsrcPacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/SsrcPacketExtension.java new file mode 100644 index 0000000..7207a4e --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/SsrcPacketExtension.java @@ -0,0 +1,16 @@ +package net.java.sip.communicator.impl.protocol.jabber.extensions.jingle; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.AbstractPacketExtension; + +/** + * Copyright (c) Tuenti Technologies. All rights reserved. + * + * @author Manuel Peinado Gallego + */ +public class SsrcPacketExtension extends AbstractPacketExtension { + public static final String ELEMENT_NAME = "ssrc"; + + public SsrcPacketExtension() { + super(null, ELEMENT_NAME); + } +} \ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/StreamPacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/StreamPacketExtension.java new file mode 100644 index 0000000..434d6af --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/StreamPacketExtension.java @@ -0,0 +1,26 @@ +package net.java.sip.communicator.impl.protocol.jabber.extensions.jingle; + +import ch.imvs.sdes4j.srtp.SrtpCryptoAttribute; +import net.java.sip.communicator.impl.protocol.jabber.extensions.AbstractPacketExtension; + +/** + * Copyright (c) Tuenti Technologies. All rights reserved. + * + * @author Manuel Peinado Gallego + */ +public class StreamPacketExtension extends AbstractPacketExtension { + public static final String ELEMENT_NAME = "stream"; + + public StreamPacketExtension() { + super(null, ELEMENT_NAME); + } + + public void setSsrc(SsrcPacketExtension ssrc) { + getChildExtensions().clear(); + addChildExtension(ssrc); + } + + public SsrcPacketExtension getSsrc() { + return getChildExtensionsOfType(SsrcPacketExtension.class).get(0); + } +} \ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/StreamsPacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/StreamsPacketExtension.java new file mode 100644 index 0000000..3094bfb --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/StreamsPacketExtension.java @@ -0,0 +1,41 @@ +package net.java.sip.communicator.impl.protocol.jabber.extensions.jingle; + +import java.util.*; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.*; + + +/** + * Copyright (c) Tuenti Technologies. All rights reserved. + * + * @author Manuel Peinado Gallego + */ +public class StreamsPacketExtension extends AbstractPacketExtension { + public static final String ELEMENT_NAME = "streams"; + + private List streamList = new ArrayList(); + + /** + * Creates a new instance of this EncryptionPacketExtension. + */ + public StreamsPacketExtension() { + super(null, ELEMENT_NAME); + } + + public void addStream(StreamPacketExtension stream) { + if(!streamList.contains(stream)) { + streamList.add(stream); + } + } + + /** + * Returns a reference to the list of crypto elements that + * we have registered with this encryption element so far. + * + * @return a reference to the list of crypto elements that + * we have registered with this encryption element so far. + */ + public List getStreamList() { + return streamList; + } +}