1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2025-12-06 11:01:16 +01:00

Jingle Extension added to Smack Repository

git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@6517 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
Thiago Camargo 2007-01-04 17:25:30 +00:00 committed by thiago
parent f1972c2571
commit 4b6de6647b
104 changed files with 16411 additions and 0 deletions

View file

@ -0,0 +1,421 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2002-2006 Jive Software. All rights reserved.
* ====================================================================
* The Jive Software License (based on Apache Software License, Version 1.1)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by
* Jive Software (http://www.jivesoftware.com)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Smack" and "Jive Software" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please
* contact webmaster@jivesoftware.com.
*
* 5. Products derived from this software may not be called "Smack",
* nor may "Smack" appear in their name, without prior written
* permission of Jive Software.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*/
package org.jivesoftware.smackx.jingle;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.jingle.listeners.JingleMediaListener;
import org.jivesoftware.smackx.jingle.listeners.JingleTransportListener;
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.media.MediaNegotiator;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import org.jivesoftware.smackx.jingle.nat.TransportNegotiator;
import org.jivesoftware.smackx.jingle.nat.TransportResolver;
import org.jivesoftware.smackx.packet.Jingle;
import org.jivesoftware.smackx.packet.JingleContentDescription;
import org.jivesoftware.smackx.packet.JingleContentDescription.JinglePayloadType;
import org.jivesoftware.smackx.packet.JingleError;
import java.util.List;
/**
* An incoming Jingle Session implementation.
* This class has especific bahavior to accept and establish a received Jingle Session Request.
*
* This class is not directly used by users. Instead, users should refer to the
* JingleManager class, that will create the appropiate instance...
*
* @author Alvaro Saurin
*/
public class IncomingJingleSession extends JingleSession {
// states
private final Accepting accepting;
private final Pending pending;
private final Active active;
private JingleSessionRequest initialSessionRequest;
/**
* Constructor for a Jingle incoming session
*
* @param conn the XMPP connection
* @param responder the responder
* @param resolver The transport resolver
*/
protected IncomingJingleSession(XMPPConnection conn, String responder,
List payloadTypes, TransportResolver resolver) {
super(conn, responder, conn.getUser());
// Create the states...
accepting = new Accepting(this);
pending = new Pending(this);
active = new Active(this);
setMediaNeg(new MediaNegotiator(this, payloadTypes));
if (resolver.getType().equals(TransportResolver.Type.rawupd)) {
setTransportNeg(new TransportNegotiator.RawUdp(this, resolver));
}
if (resolver.getType().equals(TransportResolver.Type.ice)) {
setTransportNeg(new TransportNegotiator.Ice(this, resolver));
}
}
/**
* Constructor for a Jingle Incoming session with a defined Media Manager
*
* @param conn the XMPP connection
* @param responder the responder
* @param resolver The transport resolver
* @param jingleMediaManager The Media Manager for this Session
*/
protected IncomingJingleSession(XMPPConnection conn, String responder,
List payloadTypes, TransportResolver resolver, JingleMediaManager jingleMediaManager) {
this(conn, responder, payloadTypes, resolver);
this.jingleMediaManager = jingleMediaManager;
}
/**
* Start the session for a initial Jingle request packet.
*
* @param initialJingleSessionRequest the initial Jingle Session Request
* @throws XMPPException
*/
public void start(JingleSessionRequest initialJingleSessionRequest) throws XMPPException {
if (invalidState()) {
Jingle packet = initialJingleSessionRequest.getJingle();
System.out.println("invalidState");
if (packet != null) {
// Initialize the session information
setSid(packet.getSid());
// Establish the default state
setState(accepting);
updatePacketListener();
respond(packet);
} else {
throw new IllegalStateException(
"Session request with null Jingle packet.");
}
} else {
throw new IllegalStateException("Starting session without null state.");
}
}
/**
* Start the session using initial Jingle Session Request used to create this session..
*
* @throws XMPPException
*/
public void start() throws XMPPException {
start(this.getInitialSessionRequest());
}
/**
* Get the initial Jingle packet request
*
* @return
*/
JingleSessionRequest getInitialSessionRequest() {
return initialSessionRequest;
}
/**
* Get the initial Jingle packet request
*
* @param initialRequest the initial Jingle packet
*/
void setInitialSessionRequest(JingleSessionRequest initialRequest) {
this.initialSessionRequest = initialRequest;
}
// States
/**
* First stage when we have received a session request, and we accept the
* request. We start in this stage, as the instance is created when the user
* accepts the connection...
*/
public class Accepting extends JingleNegotiator.State {
public Accepting(JingleNegotiator neg) {
super(neg);
}
/**
* Initiate the incoming session. We have already sent the ACK partially
* accepting the session...
*
* @throws XMPPException
*/
public Jingle eventInitiate(Jingle inJingle) throws XMPPException {
// Set the new session state
setState(pending);
return super.eventInitiate(inJingle);
}
/**
* An error has occurred.
*
* @throws XMPPException
*/
public void eventError(IQ iq) throws XMPPException {
triggerSessionClosedOnError(new JingleException(iq.getError().getMessage()));
super.eventError(iq);
}
}
/**
* "Pending" state: we are waiting for the transport and content
* negotiators.
*/
private class Pending extends JingleNegotiator.State {
JingleMediaListener jingleMediaListener;
JingleTransportListener jingleTransportListener;
public Pending(JingleNegotiator neg) {
super(neg);
// Create the listeners that will send a "session-accept" when the
// sub-negotiators are done.
jingleMediaListener = new JingleMediaListener() {
public void mediaClosed(PayloadType cand) {
}
public void mediaEstablished(PayloadType pt) {
checkFullyEstablished();
}
};
jingleTransportListener = new JingleTransportListener() {
public void transportEstablished(TransportCandidate local,
TransportCandidate remote) {
checkFullyEstablished();
}
public void transportClosed(TransportCandidate cand) {
}
public void transportClosedOnError(XMPPException e) {
}
};
}
/**
* Enter in the pending state: wait for the sub-negotiators.
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventEnter()
*/
public void eventEnter() {
// Add the listeners to the sub-negotiators...
System.out.println("Pending eventEnter");
addMediaListener(jingleMediaListener);
addTransportListener(jingleTransportListener);
super.eventEnter();
}
/**
* Exit of the state
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventExit()
*/
public void eventExit() {
removeMediaListener(jingleMediaListener);
removeTransportListener(jingleTransportListener);
super.eventExit();
}
/**
* Check if the session has been fully accepted by all the
* sub-negotiators and, in that case, send an "accept" message...
*/
private void checkFullyEstablished() {
if (isFullyEstablished()) {
PayloadType.Audio bestCommonAudioPt = getMediaNeg()
.getBestCommonAudioPt();
TransportCandidate bestRemoteCandidate = getTransportNeg()
.getBestRemoteCandidate();
TransportCandidate acceptedLocalCandidate = getTransportNeg()
.getAcceptedLocalCandidate();
if (bestCommonAudioPt != null && bestRemoteCandidate != null
&& acceptedLocalCandidate != null) {
// Ok, send a packet saying that we accept this session
Jingle jout = new Jingle(Jingle.Action.SESSIONACCEPT);
// ... with the audio payload type and the transport
// candidate
jout.addDescription(new JingleContentDescription.Audio(
new JinglePayloadType(bestCommonAudioPt)));
jout.addTransport(getTransportNeg().getJingleTransport(
bestRemoteCandidate));
addExpectedId(jout.getPacketID());
sendFormattedJingle(jout);
}
}
}
/**
* The other endpoint has accepted the session.
*/
public Jingle eventAccept(Jingle jin) throws XMPPException {
PayloadType acceptedPayloadType = null;
TransportCandidate acceptedLocalCandidate = null;
// We process the "accepted" if we have finished the
// sub-negotiators. Maybe this is not needed (ie, the other endpoint
// can take the first valid transport candidate), but otherwise we
// must cancel the negotiators...
//
if (isFullyEstablished()) {
acceptedPayloadType = getAcceptedAudioPayloadType(jin);
acceptedLocalCandidate = getAcceptedLocalCandidate(jin);
if (acceptedPayloadType != null && acceptedLocalCandidate != null) {
if (acceptedPayloadType.equals(getMediaNeg().getBestCommonAudioPt())
&& acceptedLocalCandidate.equals(getTransportNeg()
.getAcceptedLocalCandidate())) {
setState(active);
}
} else {
throw new JingleException(JingleError.MALFORMED_STANZA);
}
}
return super.eventAccept(jin);
}
/**
* We have received a confirmation.
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventAck(org.jivesoftware.smack.packet.IQ)
*/
public Jingle eventAck(IQ iq) throws XMPPException {
setState(active);
return super.eventAck(iq);
}
/**
* An error has occurred.
*
* @throws XMPPException
*/
public void eventError(IQ iq) throws XMPPException {
if (iq == null) return;
triggerSessionClosedOnError(new XMPPException(iq.getError()));
super.eventError(iq);
}
}
/**
* "Active" state: we have an agreement about the session.
*/
private class Active extends JingleNegotiator.State {
public Active(JingleNegotiator neg) {
super(neg);
}
/**
* We have a established session: notify the listeners
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventEnter()
*/
public void eventEnter() {
PayloadType.Audio bestCommonAudioPt = getMediaNeg().getBestCommonAudioPt();
TransportCandidate bestRemoteCandidate = getTransportNeg()
.getBestRemoteCandidate();
TransportCandidate acceptedLocalCandidate = getTransportNeg()
.getAcceptedLocalCandidate();
// Trigger the session established flag
System.out.println("eventEntered");
triggerSessionEstablished(bestCommonAudioPt, bestRemoteCandidate,
acceptedLocalCandidate);
super.eventEnter();
}
/**
* Terminate the connection.
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventTerminate(org.jivesoftware.smackx.packet.Jingle)
*/
public Jingle eventTerminate(Jingle jin) throws XMPPException {
triggerSessionClosed("Closed Remotely");
return super.eventTerminate(jin);
}
/**
* An error has occurred.
*
* @throws XMPPException
*/
public void eventError(IQ iq) throws XMPPException {
triggerSessionClosedOnError(new XMPPException(iq.getError().getMessage()));
super.eventError(iq);
}
}
}

View file

@ -0,0 +1,703 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2005 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.ServiceDiscoveryManager;
import org.jivesoftware.smackx.jingle.listeners.CreatedJingleSessionListener;
import org.jivesoftware.smackx.jingle.listeners.JingleListener;
import org.jivesoftware.smackx.jingle.listeners.JingleSessionListener;
import org.jivesoftware.smackx.jingle.listeners.JingleSessionRequestListener;
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.nat.BasicResolver;
import org.jivesoftware.smackx.jingle.nat.JingleTransportManager;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import org.jivesoftware.smackx.jingle.nat.TransportResolver;
import org.jivesoftware.smackx.packet.DiscoverInfo;
import org.jivesoftware.smackx.packet.Jingle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Jingle is a session establishment protocol defined in (XEP-0166).
* It defines a framework for negotiating and managing out-of-band ( data that is send and receive through other connection than XMPP connection) data sessions over XMPP.
* With this protocol you can setup VOIP Calls, Video Streaming, File transfers and whatever out-of-band session based transmission.
*
* To create a Jingle Session you need a Transport method and a Payload type.
*
* A transport method is how it will trasmit and receive network packets. Transport MUST have one or more candidates.
* A transport candidate is an IP Address with a defined port, that other party must send data to.
*
* A supported payload type, is the data encoding format that the jmf will be transmitted.
* For instance an Audio Payload "GSM".
*
* A Jingle session negociates a payload type and a pair of transport candidates.
* Which means that when a Jingle Session is establhished you will have two defined transport candidates with addresses
* and a defined Payload type.
* In other words, you will have two IP address with their respective ports, and a Codec type defined.
*
* The JingleManager is a facade built upon Jabber Jingle (XEP-166) to allow the
* use of Jingle. This implementation allows the user to simply
* use this class for setting the Jingle parameters, create and receive Jingle Sessions.
*
* In order to use the Jingle, the user must provide a
* TransportManager that will handle the resolution of potential IP addresses taht can be used to transport the streaming (jmf).
* This TransportManager can be initialized with several default resolvers,
* including a fixed solver that can be used when the address and port are know
* in advance.
* This API have ready to use Transport Managers, for instance: BasicTransportManager, STUNTransportManager, BridgedTransportManager.
*
* You should also especify a JingleMediaManager if you want that JingleManager assume Media control
* Using a JingleMediaManager implementation is the easier way to implement a Jingle Application.
*
* Otherwise before creating an outgoing connection, the user must create jingle session
* listeners that will be called when different events happen. The most
* important event is <i>sessionEstablished()</i>, that will be called when all
* the negotiations are finished, providing the payload type for the
* transmission as well as the remote and local addresses and ports for the
* communication. See JingleSessionListener for a complete list of events that can be
* observed.
*
* This is an example of how to use the JingleManager:
* <i>This example implements a Jingle VOIP Call between two users.</i>
*
* <pre>
*
* To wait for an Incoming Jingle Session:
*
* try {
*
* // Connect to a XMPP Server
* XMPPConnection x1 = new XMPPConnection("xmpp.com");
* x1.connect();
* x1.login("juliet", "juliet");
*
* // Create a JingleManager using a BasicResolver
* final JingleManager jm1 = new JingleManager(
* x1, new BasicTransportManager());
*
* // Create a JingleMediaManager. In this case using Jingle Audio Media API
* JingleMediaManager jingleMediaManager = new AudioMediaManager();
*
* // Set the JingleMediaManager
* jm1.setMediaManager(jingleMediaManager);
*
* // Listen for incoming calls
* jm1.addJingleSessionRequestListener(new JingleSessionRequestListener() {
* public void sessionRequested(JingleSessionRequest request) {
*
* try {
* // Accept the call
* IncomingJingleSession session = request.accept();
*
*
* // Start the call
* session.start();
* } catch (XMPPException e) {
* e.printStackTrace();
* }
*
* }
* });
*
* Thread.sleep(15000);
*
* } catch (Exception e) {
* e.printStackTrace();
* }
*
* To create an Outgoing Jingle Session:
*
* try {
*
* // Connect to a XMPP Server
* XMPPConnection x0 = new XMPPConnection("xmpp.com");
* x0.connect();
* x0.login("romeo", "romeo");
*
* // Create a JingleManager using a BasicResolver
* final JingleManager jm0 = new JingleManager(
* x0, new BasicTransportManager());
*
* // Create a JingleMediaManager. In this case using Jingle Audio Media API
* JingleMediaManager jingleMediaManager = new AudioMediaManager(); // Using Jingle Media API
*
* // Set the JingleMediaManager
* jm0.setMediaManager(jingleMediaManager);
*
* // Create a new Jingle Call with a full JID
* OutgoingJingleSession js0 = jm0.createOutgoingJingleSession("juliet@xmpp.com/Smack");
*
* // Start the call
* js0.start();
*
* Thread.sleep(10000);
* js0.terminate();
*
* Thread.sleep(3000);
*
* } catch (Exception e) {
* e.printStackTrace();
* }
* </pre>
*
* @author Thiago Camargo
* @author Alvaro Saurin
* @see JingleListener
* @see TransportResolver
* @see org.jivesoftware.smackx.jingle.nat.JingleTransportManager
* @see OutgoingJingleSession
* @see IncomingJingleSession
* @see JingleMediaManager
* @see org.jivesoftware.smackx.jingle.nat.BasicTransportManager , STUNTransportManager, BridgedTransportManager, TransportResolver, BridgedResolver, ICEResolver, STUNResolver and BasicResolver.
*/
public class JingleManager implements JingleSessionListener {
// non-static
final List<JingleSession> jingleSessions = new ArrayList<JingleSession>();
// Listeners for manager events (ie, session requests...)
private List<JingleSessionRequestListener> jingleSessionRequestListeners;
// Listeners for created JingleSessions
private List<CreatedJingleSessionListener> creationListeners = new ArrayList<CreatedJingleSessionListener>();
// The XMPP connection
private XMPPConnection connection;
// The Media Manager
private JingleMediaManager jingleMediaManager;
// The Jingle transport manager
private final JingleTransportManager jingleTransportManager;
static {
ProviderManager providerManager = ProviderManager.getInstance();
providerManager.addIQProvider("jingle", "http://jabber.org/protocol/jingle",
new org.jivesoftware.smackx.provider.JingleProvider());
providerManager.addExtensionProvider("description", "http://jabber.org/protocol/jingle/description/audio",
new org.jivesoftware.smackx.provider.JingleContentDescriptionProvider.Audio());
providerManager.addExtensionProvider("description", "http://jabber.org/protocol/jingle/description/audio",
new org.jivesoftware.smackx.provider.JingleContentDescriptionProvider.Audio());
providerManager.addExtensionProvider("transport", "http://jabber.org/protocol/jingle/transport/ice",
new org.jivesoftware.smackx.provider.JingleTransportProvider.Ice());
providerManager.addExtensionProvider("transport", "http://jabber.org/protocol/jingle/transport/raw-udp",
new org.jivesoftware.smackx.provider.JingleTransportProvider.RawUdp());
providerManager.addExtensionProvider("busy", "http://jabber.org/protocol/jingle/info/audio",
new org.jivesoftware.smackx.provider.JingleContentInfoProvider.Audio.Busy());
providerManager.addExtensionProvider("hold", "http://jabber.org/protocol/jingle/info/audio",
new org.jivesoftware.smackx.provider.JingleContentInfoProvider.Audio.Hold());
providerManager.addExtensionProvider("mute", "http://jabber.org/protocol/jingle/info/audio",
new org.jivesoftware.smackx.provider.JingleContentInfoProvider.Audio.Mute());
providerManager.addExtensionProvider("queued", "http://jabber.org/protocol/jingle/info/audio",
new org.jivesoftware.smackx.provider.JingleContentInfoProvider.Audio.Queued());
providerManager.addExtensionProvider("ringing", "http://jabber.org/protocol/jingle/info/audio",
new org.jivesoftware.smackx.provider.JingleContentInfoProvider.Audio.Ringing());
// Enable the Jingle support on every established connection
// The ServiceDiscoveryManager class should have been already
// initialized
XMPPConnection.addConnectionCreationListener(new ConnectionCreationListener() {
public void connectionCreated(XMPPConnection connection) {
JingleManager.setServiceEnabled(connection, true);
}
});
}
/**
* Default constructor with a defined XMPPConnection, Transport Resolver and a Media Manager
* If a fully implemented JingleMediaSession is entered, JingleManager manage Jingle signalling and jmf
*
* @param connection XMPP Connection to be used
* @param jingleTransportManager transport resolver to be used
* @param jingleMediaManager an implemeted JingleMediaManager to be used.
*/
public JingleManager(XMPPConnection connection, JingleTransportManager jingleTransportManager, JingleMediaManager jingleMediaManager) {
this.connection = connection;
this.jingleTransportManager = jingleTransportManager;
this.jingleMediaManager = jingleMediaManager;
connection.getRoster().addRosterListener(new RosterListener() {
public void entriesAdded(Collection addresses) {
}
public void entriesUpdated(Collection addresses) {
}
public void entriesDeleted(Collection addresses) {
}
public void presenceChanged(String XMPPAddress) {
JingleSession aux = null;
for (JingleSession jingleSession : jingleSessions) {
if (jingleSession.getInitiator().equals(XMPPAddress) || jingleSession.getResponder().equals(XMPPAddress)) {
aux = jingleSession;
}
}
if (aux != null)
try {
aux.terminate();
} catch (XMPPException e) {
e.printStackTrace();
}
}
});
}
/**
* Default constructor with a defined XMPPConnection and a Transport Resolver
*
* @param connection XMPP Connection to be used
* @param jingleTransportManager transport resolver to be used
*/
public JingleManager(XMPPConnection connection, JingleTransportManager jingleTransportManager) {
this(connection, jingleTransportManager, null);
}
/**
* Default constructor with a defined XMPPConnection.
* A default JingleTransportmanager based on BasicResolver will be used in this JingleManager transport.
*
* @param connection XMPP Connection to be used
*/
public JingleManager(XMPPConnection connection) {
this(connection, new JingleTransportManager() {
protected TransportResolver createResolver() {
return new BasicResolver();
}
});
}
/**
* Default constructor with a defined XMPPConnection and a defined Resolver.
* A default JingleTransportmanager based on BasicResolver will be used in this JingleManager transport.
*
* @param connection XMPP Connection to be used
*/
public JingleManager(XMPPConnection connection, final TransportResolver resolver) {
this(connection, new JingleTransportManager() {
protected TransportResolver createResolver() {
return resolver;
}
});
}
/**
* Enables or disables the Jingle support on a given connection.
*
*
* Before starting any Jingle jmf session, check that the user can handle
* it. Enable the Jingle support to indicate that this client handles Jingle
* messages.
*
* @param connection the connection where the service will be enabled or
* disabled
* @param enabled indicates if the service will be enabled or disabled
*/
public synchronized static void setServiceEnabled(XMPPConnection connection,
boolean enabled) {
if (isServiceEnabled(connection) == enabled) {
return;
}
if (enabled) {
ServiceDiscoveryManager.getInstanceFor(connection).addFeature(
Jingle.NAMESPACE);
} else {
ServiceDiscoveryManager.getInstanceFor(connection).removeFeature(
Jingle.NAMESPACE);
}
}
/**
* Returns true if the Jingle support is enabled for the given connection.
*
* @param connection the connection to look for Jingle support
* @return a boolean indicating if the Jingle support is enabled for the
* given connection
*/
public static boolean isServiceEnabled(XMPPConnection connection) {
return ServiceDiscoveryManager.getInstanceFor(connection).includesFeature(
Jingle.NAMESPACE);
}
/**
* Returns true if the specified user handles Jingle messages.
*
* @param connection the connection to use to perform the service discovery
* @param userID the user to check. A fully qualified xmpp ID, e.g.
* jdoe@example.com
* @return a boolean indicating whether the specified user handles Jingle
* messages
*/
public static boolean isServiceEnabled(XMPPConnection connection, String userID) {
try {
DiscoverInfo result = ServiceDiscoveryManager.getInstanceFor(connection)
.discoverInfo(userID);
return result.containsFeature(Jingle.NAMESPACE);
}
catch (XMPPException e) {
e.printStackTrace();
return false;
}
}
/**
* Get the JingleTransportManager of this JingleManager
*
* @return
*/
public JingleTransportManager getJingleTransportManager() {
return jingleTransportManager;
}
/**
* Get the Media Manager of this Jingle Manager
*
* @return
*/
public JingleMediaManager getMediaManager() {
return jingleMediaManager;
}
/**
* Set the Media Manager of this Jingle Manager
*
* @param jingleMediaManager JingleMediaManager to be used for open, close, start and stop jmf streamings
*/
public void setMediaManager(JingleMediaManager jingleMediaManager) {
this.jingleMediaManager = jingleMediaManager;
}
/**
* Add a Jingle session request listenerJingle to listen to incoming session
* requests.
*
* @param jingleSessionRequestListener an implemented JingleSessionRequestListener
* @see #removeJingleSessionRequestListener(JingleSessionRequestListener)
* @see JingleListener
*/
public synchronized void addJingleSessionRequestListener(
final JingleSessionRequestListener jingleSessionRequestListener) {
if (jingleSessionRequestListener != null) {
if (jingleSessionRequestListeners == null) {
initJingleSessionRequestListeners();
}
synchronized (jingleSessionRequestListeners) {
jingleSessionRequestListeners.add(jingleSessionRequestListener);
}
}
}
/**
* Removes a Jingle session listenerJingle.
*
* @param jingleSessionRequestListener The jingle session jingleSessionRequestListener to be removed
* @see #addJingleSessionRequestListener(JingleSessionRequestListener)
* @see JingleListener
*/
public void removeJingleSessionRequestListener(JingleSessionRequestListener jingleSessionRequestListener) {
if (jingleSessionRequestListeners == null) {
return;
}
synchronized (jingleSessionRequestListeners) {
jingleSessionRequestListeners.remove(jingleSessionRequestListener);
}
}
/**
* Adds a CreatedJingleSessionListener.
* This listener will be called when a session is created by the JingleManager instance.
*
* @param createdJingleSessionListener
*/
public void addCreationListener(CreatedJingleSessionListener createdJingleSessionListener) {
this.creationListeners.add(createdJingleSessionListener);
}
/**
* Removes a CreatedJingleSessionListener.
* This listener will be called when a session is created by the JingleManager instance.
*
* @param createdJingleSessionListener
*/
public void removeCreationListener(CreatedJingleSessionListener createdJingleSessionListener) {
this.creationListeners.remove(createdJingleSessionListener);
}
/**
* Trigger CreatedJingleSessionListeners that a session was created.
*
* @param jingleSession
*/
public void triggerSessionCreated(JingleSession jingleSession) {
jingleSessions.add(jingleSession);
jingleSession.addListener(this);
for (CreatedJingleSessionListener createdJingleSessionListener : creationListeners) {
try {
createdJingleSessionListener.sessionCreated(jingleSession);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void sessionEstablished(PayloadType pt, TransportCandidate rc, TransportCandidate lc, JingleSession jingleSession) {
}
public void sessionDeclined(String reason, JingleSession jingleSession) {
jingleSession.removeListener(this);
jingleSessions.remove(jingleSession);
jingleSession.close();
System.err.println("Declined");
}
public void sessionRedirected(String redirection, JingleSession jingleSession) {
jingleSession.removeListener(this);
jingleSessions.remove(jingleSession);
}
public void sessionClosed(String reason, JingleSession jingleSession) {
jingleSession.removeListener(this);
jingleSessions.remove(jingleSession);
}
public void sessionClosedOnError(XMPPException e, JingleSession jingleSession) {
jingleSession.removeListener(this);
jingleSessions.remove(jingleSession);
}
/**
* Register the listenerJingles, waiting for a Jingle packet that tries to
* establish a new session.
*/
private void initJingleSessionRequestListeners() {
PacketFilter initRequestFilter = new PacketFilter() {
// Return true if we accept this packet
public boolean accept(Packet pin) {
if (pin instanceof IQ) {
IQ iq = (IQ) pin;
if (iq.getType().equals(IQ.Type.SET)) {
if (iq instanceof Jingle) {
Jingle jin = (Jingle) pin;
if (jin.getAction().equals(Jingle.Action.SESSIONINITIATE)) {
return true;
}
}
}
}
return false;
}
};
jingleSessionRequestListeners = new ArrayList<JingleSessionRequestListener>();
// Start a packet listener for session initiation requests
connection.addPacketListener(new PacketListener() {
public void processPacket(Packet packet) {
triggerSessionRequested((Jingle) packet);
}
}, initRequestFilter);
}
/**
* Disconnect all Jingle Sessions
*/
public void disconnectAllSessions() {
List<JingleSession> sessions = jingleSessions.subList(0, jingleSessions.size());
for (JingleSession jingleSession : sessions)
try {
jingleSession.terminate();
} catch (XMPPException e) {
e.printStackTrace();
}
sessions.clear();
}
/**
* Activates the listenerJingles on a Jingle session request.
*
* @param initJin the packet that must be passed to the jingleSessionRequestListener.
*/
void triggerSessionRequested(Jingle initJin) {
JingleSessionRequestListener[] jingleSessionRequestListeners = null;
// Make a synchronized copy of the listenerJingles
synchronized (this.jingleSessionRequestListeners) {
jingleSessionRequestListeners = new JingleSessionRequestListener[this.jingleSessionRequestListeners.size()];
this.jingleSessionRequestListeners.toArray(jingleSessionRequestListeners);
}
// ... and let them know of the event
JingleSessionRequest request = new JingleSessionRequest(this, initJin);
for (int i = 0; i < jingleSessionRequestListeners.length; i++) {
jingleSessionRequestListeners[i].sessionRequested(request);
}
}
// Session creation
/**
* Creates an Jingle session to start a communication with another user.
*
* @param responder the fully qualified jabber ID with resource of the other
* user.
* @param payloadTypes list of supported payload types
* @return The session on which the negotiation can be run.
*/
public OutgoingJingleSession createOutgoingJingleSession(String responder,
List<PayloadType> payloadTypes) throws XMPPException {
if (responder == null || StringUtils.parseName(responder).length() <= 0
|| StringUtils.parseServer(responder).length() <= 0
|| StringUtils.parseResource(responder).length() <= 0) {
throw new IllegalArgumentException(
"The provided user id was not fully qualified");
}
OutgoingJingleSession session;
TransportResolver resolver = jingleTransportManager.getResolver();
if (jingleMediaManager != null)
session = new OutgoingJingleSession(connection, responder, payloadTypes, resolver, jingleMediaManager);
else
session = new OutgoingJingleSession(connection, responder, payloadTypes, jingleTransportManager.getResolver());
triggerSessionCreated(session);
return session;
}
/**
* Creates an Jingle session to start a communication with another user.
*
* @param responder the fully qualified jabber ID with resource of the other
* user.
* @return the session on which the negotiation can be run.
*/
public OutgoingJingleSession createOutgoingJingleSession(String responder) throws XMPPException {
if (this.getMediaManager() == null) return null;
return createOutgoingJingleSession(responder, this.getMediaManager().getPayloads());
}
/**
* When the session request is acceptable, this method should be invoked. It
* will create an JingleSession which allows the negotiation to procede.
*
* @param request the remote request that is being accepted.
* @param payloadTypes the list of supported Payload types that can be accepted
* @return the session which manages the rest of the negotiation.
*/
IncomingJingleSession createIncomingJingleSession(
JingleSessionRequest request, List<PayloadType> payloadTypes) throws XMPPException {
if (request == null) {
throw new NullPointerException("Received request cannot be null");
}
IncomingJingleSession session;
TransportResolver resolver = jingleTransportManager.getResolver();
if (jingleMediaManager != null)
session = new IncomingJingleSession(connection, request
.getFrom(), payloadTypes, resolver, jingleMediaManager);
else
session = new IncomingJingleSession(connection, request
.getFrom(), payloadTypes, resolver);
triggerSessionCreated(session);
return session;
}
/**
* When the session request is acceptable, this method should be invoked. It
* will create an JingleSession which allows the negotiation to procede.
* This method use JingleMediaManager to select the supported Payload types.
*
* @param request the remote request that is being accepted.
* @return the session which manages the rest of the negotiation.
*/
IncomingJingleSession createIncomingJingleSession(JingleSessionRequest request) throws XMPPException {
if (request == null) {
throw new NullPointerException("JingleMediaManager is not defined");
}
if (jingleMediaManager == null) return null;
return createIncomingJingleSession(request, jingleMediaManager.getPayloads());
}
/**
* Get a session with the informed JID. If no session is found, return null.
*
* @param jid
* @return
*/
public JingleSession getSession(String jid) {
for (JingleSession jingleSession : jingleSessions) {
if (jingleSession instanceof OutgoingJingleSession) {
if (jingleSession.getResponder().equals(jid)) {
return jingleSession;
}
} else if (jingleSession instanceof IncomingJingleSession) {
if (jingleSession.getInitiator().equals(jid)) {
return jingleSession;
}
}
}
return null;
}
/**
* Reject the session. If we don't want to accept the new session, send an
* appropriate error packet.
*
* @param request the request to be rejected.
*/
protected void rejectIncomingJingleSession(JingleSessionRequest request) {
Jingle initiation = request.getJingle();
IQ rejection = JingleSession.createError(initiation.getPacketID(), initiation
.getFrom(), initiation.getTo(), 403, "Declined");
connection.sendPacket(rejection);
}
}

View file

@ -0,0 +1,354 @@
package org.jivesoftware.smackx.jingle;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.jingle.listeners.JingleListener;
import org.jivesoftware.smackx.packet.Jingle;
import org.jivesoftware.smackx.packet.JingleError;
import java.util.ArrayList;
/**
* Basic Jingle negotiator.
* <p/>
* </p>
* <p/>
* JingleNegotiator implements some basic behavior for every Jingle negotiation.
* It implements a "state" pattern: each stage should process Jingle packets and
* act depending on the current state in the negotiation...
* <p/>
* </p>
*
* @author Alvaro Saurin
*/
public abstract class JingleNegotiator {
private State state; // Current negotiation state
private XMPPConnection connection; // The connection associated
private final ArrayList listeners = new ArrayList();
private String expectedAckId;
/**
* Default constructor.
*/
public JingleNegotiator() {
this(null);
}
/**
* Default constructor with a XMPPConnection
*
* @param connection the connection associated
*/
public JingleNegotiator(XMPPConnection connection) {
this.connection = connection;
state = null;
}
/**
* Get the XMPP connection associated with this negotiation.
*
* @return the connection
*/
public XMPPConnection getConnection() {
return connection;
}
/**
* Set the XMPP connection associated.
*
* @param connection the connection to set
*/
public void setConnection(XMPPConnection connection) {
this.connection = connection;
}
/**
* Inform if current state is null
*
* @return true if current state is null
*/
public boolean invalidState() {
return state == null;
}
/**
* Return the current state
*
* @return the state
*/
public State getState() {
return state;
}
/**
* Return the current state class
*
* @return the state
*/
public Class getStateClass() {
if (state != null) {
return state.getClass();
} else {
return Object.class;
}
}
/**
* Set the new state.
*
* @param newState the state to set
* @throws XMPPException
*/
protected void setState(State newState) {
boolean transition = newState != state;
if (transition && state != null) {
state.eventExit();
}
state = newState;
if (transition && state != null) {
state.eventEnter();
}
}
// Acks management
/**
* Add expected ID
* @param id
*/
public void addExpectedId(String id) {
expectedAckId = id;
}
/**
* Check if the passed ID is the expected ID
* @param id
* @return
*/
public boolean isExpectedId(String id) {
if (id != null) {
return id.equals(expectedAckId);
} else {
return false;
}
}
/**
* Remove and expected ID
* @param id
*/
public void removeExpectedId(String id) {
addExpectedId((String) null);
}
// Listeners
/**
* Add a Jingle session listener to listen to incoming session requests.
*
* @param li The listener
* @see org.jivesoftware.smackx.jingle.listeners.JingleListener
*/
public void addListener(JingleListener li) {
synchronized (listeners) {
listeners.add(li);
}
}
/**
* Removes a Jingle session listener.
*
* @param li The jingle session listener to be removed
* @see org.jivesoftware.smackx.jingle.listeners.JingleListener
*/
public void removeListener(JingleListener li) {
synchronized (listeners) {
listeners.remove(li);
}
}
/**
* Get a copy of the listeners
*
* @return a copy of the listeners
*/
protected ArrayList getListenersList() {
ArrayList result;
synchronized (listeners) {
result = new ArrayList(listeners);
}
return result;
}
/**
* Dispatch an incomming packet. This method is responsible for recognizing
* the packet type and, depending on the current state, deliverying the
* packet to the right event handler and wait for a response.
*
* @param iq the packet received
* @param id the ID of the response that will be sent
* @return the new packet to send (either a Jingle or an IQ error).
* @throws XMPPException
*/
public abstract IQ dispatchIncomingPacket(IQ iq, String id)
throws XMPPException;
/**
* Close the negotiation.
*/
public void close() {
setState(null);
}
/**
* A Jingle exception.
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public static class JingleException extends XMPPException {
private final JingleError error;
/**
* Default constructor.
*/
public JingleException() {
super();
error = null;
}
/**
* Constructor with an error message.
*
* @param msg The message.
*/
public JingleException(String msg) {
super(msg);
error = null;
}
/**
* Constructor with an error response.
*
* @param error The error message.
*/
public JingleException(JingleError error) {
super();
this.error = error;
}
/**
* Return the error message.
*
* @return the error
*/
public JingleError getError() {
return error;
}
}
/**
* Negotiation state and events.
* <p/>
* </p>
* <p/>
* Describes the negotiation stage.
*/
public static class State {
private JingleNegotiator neg; // The negotiator
/**
* Default constructor, with a reference to the negotiator.
*
* @param neg The negotiator instance.
*/
public State(JingleNegotiator neg) {
this.neg = neg;
}
/**
* Get the negotiator
*
* @return the negotiator.
*/
public JingleNegotiator getNegotiator() {
return neg;
}
/**
* Set the negotiator.
*
* @param neg the neg to set
*/
public void setNegotiator(JingleNegotiator neg) {
this.neg = neg;
}
// State transition events
public Jingle eventAck(IQ iq) throws XMPPException {
// We have received an Ack
return null;
}
public void eventError(IQ iq) throws XMPPException {
throw new JingleException(iq.getError().getMessage());
}
public Jingle eventInvite() throws XMPPException {
throw new IllegalStateException(
"Negotiation can not be started in this state.");
}
public Jingle eventInitiate(Jingle jin) throws XMPPException {
return null;
}
public Jingle eventAccept(Jingle jin) throws XMPPException {
return null;
}
public Jingle eventRedirect(Jingle jin) throws XMPPException {
return null;
}
public Jingle eventModify(Jingle jin) throws XMPPException {
return null;
}
public Jingle eventDecline(Jingle jin) throws XMPPException {
return null;
}
public Jingle eventInfo(Jingle jin) throws XMPPException {
return null;
}
public Jingle eventTerminate(Jingle jin) throws XMPPException {
if (neg != null) {
neg.close();
}
return null;
}
public void eventEnter() {
}
public void eventExit() {
if (neg != null) {
neg.removeExpectedId(null);
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,106 @@
package org.jivesoftware.smackx.jingle;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.packet.Jingle;
import java.util.List;
/**
* A Jingle session request.
*
* This class is a facade of a received Jingle request. The user can have direct
* access to the Jingle packet (<i>JingleSessionRequest.getJingle() </i>) of
* the request or can use the convencience methods provided by this class.
*
* @author Alvaro Saurin
*/
public class JingleSessionRequest {
private final Jingle jingle; // The Jingle packet
private final JingleManager manager; // The manager associated to this
// request
/**
* A recieve request is constructed from the Jingle Initiation request
* received from the initator.
*
* @param manager The manager handling this request
* @param jingle The jingle IQ recieved from the initiator.
*/
public JingleSessionRequest(JingleManager manager, Jingle jingle) {
this.manager = manager;
this.jingle = jingle;
}
/**
* Returns the fully-qualified jabber ID of the user that requested this
* session.
*
* @return Returns the fully-qualified jabber ID of the user that requested
* this session.
*/
public String getFrom() {
return jingle.getFrom();
}
/**
* Returns the session ID that uniquely identifies this session.
*
* @return Returns the session ID that uniquely identifies this session
*/
public String getSessionID() {
return jingle.getSid();
}
/**
* Returns the Jingle packet that was sent by the requestor which contains
* the parameters of the session.
*/
public Jingle getJingle() {
return jingle;
}
/**
* Accepts this request and creates the incoming Jingle session.
*
* @param pts list of supported Payload Types
* @return Returns the <b><i>IncomingJingleSession</b></i> on which the
* negotiation can be carried out.
*/
public synchronized IncomingJingleSession accept(List<PayloadType> pts) throws XMPPException {
IncomingJingleSession session = null;
synchronized (manager) {
session = manager.createIncomingJingleSession(this,
pts);
session.setInitialSessionRequest(this);
}
return session;
}
/**
* Accepts this request and creates the incoming Jingle session.
*
* @return Returns the <b><i>IncomingJingleSession</b></i> on which the
* negotiation can be carried out.
*/
public synchronized IncomingJingleSession accept() throws XMPPException {
IncomingJingleSession session = null;
synchronized (manager) {
session = manager.createIncomingJingleSession(this);
session.setInitialSessionRequest(this);
}
return session;
}
/**
* Rejects the session request.
*/
public synchronized void reject() {
synchronized (manager) {
manager.rejectIncomingJingleSession(this);
}
}
}

View file

@ -0,0 +1,440 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2002-2006 Jive Software. All rights reserved.
* ====================================================================
* The Jive Software License (based on Apache Software License, Version 1.1)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by
* Jive Software (http://www.jivesoftware.com)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Smack" and "Jive Software" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please
* contact webmaster@jivesoftware.com.
*
* 5. Products derived from this software may not be called "Smack",
* nor may "Smack" appear in their name, without prior written
* permission of Jive Software.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*/
package org.jivesoftware.smackx.jingle;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.jingle.listeners.JingleMediaListener;
import org.jivesoftware.smackx.jingle.listeners.JingleTransportListener;
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.media.MediaNegotiator;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import org.jivesoftware.smackx.jingle.nat.TransportNegotiator;
import org.jivesoftware.smackx.jingle.nat.TransportResolver;
import org.jivesoftware.smackx.packet.Jingle;
import org.jivesoftware.smackx.packet.JingleContentDescription;
import org.jivesoftware.smackx.packet.JingleContentDescription.JinglePayloadType;
import org.jivesoftware.smackx.packet.JingleError;
import java.util.List;
/**
* An outgoing Jingle session implementation.
* This class has especific bahavior to Request and establish a new Jingle Session.
*
* This class is not directly used by users. Instead, users should refer to the
* JingleManager class, that will create the appropiate instance...
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public class OutgoingJingleSession extends JingleSession {
// states
private final Inviting inviting;
private final Pending pending;
private final Active active;
/**
* Constructor for a Jingle outgoing session.
*
* @param conn the XMPP connection
* @param responder the other endpoint
* @param payloadTypes A list of payload types, in order of preference.
* @param resolver The transport resolver.
*/
protected OutgoingJingleSession(XMPPConnection conn, String responder,
List payloadTypes, TransportResolver resolver) {
super(conn, conn.getUser(), responder);
setSid(generateSessionId());
// Initialize the states.
inviting = new Inviting(this);
pending = new Pending(this);
active = new Active(this);
// Create description and transport negotiatiors...
setMediaNeg(new MediaNegotiator(this, payloadTypes));
if (resolver.getType().equals(TransportResolver.Type.rawupd)) {
setTransportNeg(new TransportNegotiator.RawUdp(this, resolver));
}
if (resolver.getType().equals(TransportResolver.Type.ice)) {
setTransportNeg(new TransportNegotiator.Ice(this, resolver));
}
}
/**
* Constructor for a Jingle outgoing session with a defined Media Manager
*
* @param conn the XMPP connection
* @param responder the other endpoint
* @param payloadTypes A list of payload types, in order of preference.
* @param resolver The transport resolver.
* @param jingleMediaManager The Media Manager for this Session
*/
protected OutgoingJingleSession(XMPPConnection conn, String responder,
List payloadTypes, TransportResolver resolver, JingleMediaManager jingleMediaManager) {
this(conn, responder, payloadTypes, resolver);
this.jingleMediaManager = jingleMediaManager;
}
/**
* Initiate the negotiation with an invitation. This method must be invoked
* for starting all negotiations. It is the initial starting point and,
* afterwards, any other packet processing is done with the packet listener
* callback...
*
* @throws IllegalStateException
*/
public void start(JingleSessionRequest req) throws IllegalStateException {
if (invalidState()) {
setState(inviting);
// Use the standard behavior, using a null Jingle packet
try {
updatePacketListener();
respond((Jingle) null);
} catch (XMPPException e) {
e.printStackTrace();
close();
}
} else {
throw new IllegalStateException("Starting session without null state.");
}
}
/**
* Initiate the negotiation with an invitation. This method must be invoked
* for starting all negotiations. It is the initial starting point and,
* afterwards, any other packet processing is done with the packet listener
* callback...
*
* @throws IllegalStateException
*/
public void start() throws IllegalStateException {
start(null);
}
// States
/**
* Current state when we want to invite the other endpoint.
*/
public class Inviting extends JingleNegotiator.State {
public Inviting(JingleNegotiator neg) {
super(neg);
}
/**
* Create an invitation packet.
*/
public Jingle eventInvite() {
// Create an invitation packet, saving the Packet ID, for any ACK
return new Jingle(Jingle.Action.SESSIONINITIATE);
}
/**
* The receiver has partially accepted our invitation. We go to the
* pending state while the content and transport negotiators work...
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventAck(org.jivesoftware.smack.packet.IQ)
*/
public Jingle eventAck(IQ iq) {
setState(pending);
return null;
}
/**
* The other endpoint has declined the invitation with an error.
*
* @throws XMPPException
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventError(org.jivesoftware.smack.packet.IQ)
*/
public void eventError(IQ iq) throws XMPPException {
triggerSessionDeclined(null);
super.eventError(iq);
}
/**
* The other endpoint wants to redirect this connection.
*/
public Jingle eventRedirect(Jingle jin) {
String redirArg = null;
// TODO: parse the redirection parameters...
triggerSessionRedirect(redirArg);
return null;
}
}
/**
* "Pending" state: we are waiting for the transport and content
* negotiators.
* <p/>
* Note: the transition from/to this state is done with listeners...
*/
public class Pending extends JingleNegotiator.State {
JingleMediaListener jingleMediaListener;
JingleTransportListener jingleTransportListener;
public Pending(JingleNegotiator neg) {
super(neg);
// Create the listeners that will send a "session-accept" when
// the sub-negotiators are done.
jingleMediaListener = new JingleMediaListener() {
public void mediaClosed(PayloadType cand) {
}
public void mediaEstablished(PayloadType pt) {
checkFullyEstablished();
}
};
jingleTransportListener = new JingleTransportListener() {
public void transportEstablished(TransportCandidate local,
TransportCandidate remote) {
checkFullyEstablished();
}
public void transportClosed(TransportCandidate cand) {
}
public void transportClosedOnError(XMPPException e) {
}
};
}
/**
* Enter in the pending state: install the listeners.
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventEnter()
*/
public void eventEnter() {
// Add the listeners to the sub-negotiators...
addMediaListener(jingleMediaListener);
addTransportListener(jingleTransportListener);
}
/**
* Exit of the state: remove the listeners.
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventExit()
*/
public void eventExit() {
removeMediaListener(jingleMediaListener);
removeTransportListener(jingleTransportListener);
}
/**
* Check if the session has been fully accepted by all the
* sub-negotiators and, in that case, send an "accept" message...
*/
private void checkFullyEstablished() {
if (isFullyEstablished()) {
PayloadType.Audio bestCommonAudioPt = getMediaNeg()
.getBestCommonAudioPt();
TransportCandidate bestRemoteCandidate = getTransportNeg()
.getBestRemoteCandidate();
// Ok, send a packet saying that we accept this session
// with the audio payload type and the transport
// candidate
Jingle jout = new Jingle(Jingle.Action.SESSIONACCEPT);
jout.addDescription(new JingleContentDescription.Audio(
new JinglePayloadType(bestCommonAudioPt)));
jout.addTransport(getTransportNeg().getJingleTransport(
bestRemoteCandidate));
// Send the "accept" and wait for the ACK
addExpectedId(jout.getPacketID());
sendFormattedJingle(jout);
}
}
/**
* The other endpoint has finally accepted our invitation.
*
* @throws XMPPException
*/
public Jingle eventAccept(Jingle jin) throws XMPPException {
PayloadType acceptedPayloadType = null;
TransportCandidate acceptedLocalCandidate = null;
// We process the "accepted" if we have finished the
// sub-negotiators. Maybe this is not needed (ie, the other endpoint
// can take the first valid transport candidate), but otherwise we
// must cancel the negotiators...
//
if (isFullyEstablished()) {
acceptedPayloadType = getAcceptedAudioPayloadType(jin);
acceptedLocalCandidate = getAcceptedLocalCandidate(jin);
if (acceptedPayloadType != null && acceptedLocalCandidate != null) {
if (acceptedPayloadType.equals(getMediaNeg().getBestCommonAudioPt())
&& acceptedLocalCandidate.equals(getTransportNeg()
.getAcceptedLocalCandidate())) {
setState(active);
}
} else {
throw new JingleException(JingleError.NEGOTIATION_ERROR);
}
}
return null;
}
/**
* We have received the Ack of our "accept"
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventAck(org.jivesoftware.smack.packet.IQ)
*/
public Jingle eventAck(IQ iq) {
setState(active);
return null;
}
/**
* The other endpoint wants to redirect this connection.
*/
public Jingle eventRedirect(Jingle jin) {
String redirArg = null;
// TODO: parse the redirection parameters...
triggerSessionRedirect(redirArg);
return null;
}
/**
* The other endpoint has rejected our invitation.
*
* @throws XMPPException
*/
public Jingle eventTerminate(Jingle jin) throws XMPPException {
triggerSessionDeclined(null);
return super.eventTerminate(jin);
}
/**
* An error has occurred.
*
* @throws XMPPException
*/
public void eventError(IQ iq) throws XMPPException {
triggerSessionClosedOnError(new XMPPException(iq.getError().getMessage()));
super.eventError(iq);
}
}
/**
* State when we have an established session.
*/
public class Active extends JingleNegotiator.State {
public Active(JingleNegotiator neg) {
super(neg);
}
/**
* We have a established session: notify the listeners
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventEnter()
*/
public void eventEnter() {
PayloadType.Audio bestCommonAudioPt = getMediaNeg().getBestCommonAudioPt();
TransportCandidate bestRemoteCandidate = getTransportNeg()
.getBestRemoteCandidate();
TransportCandidate acceptedLocalCandidate = getTransportNeg()
.getAcceptedLocalCandidate();
// Trigger the session established flag
System.out.println("eventEntered");
triggerSessionEstablished(bestCommonAudioPt, bestRemoteCandidate,
acceptedLocalCandidate);
super.eventEnter();
}
/**
* Terminate the connection.
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventTerminate(org.jivesoftware.smackx.packet.Jingle)
*/
public Jingle eventTerminate(Jingle jin) throws XMPPException {
triggerSessionClosed("Closed Remotely");
return super.eventTerminate(jin);
}
/**
* An error has occurred.
*
* @throws XMPPException
*/
public void eventError(IQ iq) throws XMPPException {
triggerSessionClosedOnError(new XMPPException(iq.getError().getMessage()));
super.eventError(iq);
}
}
}

View file

@ -0,0 +1,32 @@
package org.jivesoftware.smackx.jingle.listeners;
import org.jivesoftware.smackx.jingle.JingleSession;
/**
* $RCSfile$
* $Revision: $
* $Date: 17/11/2006
*
* Copyright 2003-2006 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Inteface used to dispatch a event when a Jingle session is created.
*/
public interface CreatedJingleSessionListener {
public void sessionCreated(JingleSession jingleSession);
}

View file

@ -0,0 +1,40 @@
/**
* $RCSfile$
* $Revision: $
* $Date: $11-07-2006
*
* Copyright 2003-2006 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.listeners;
/**
* Jingle listeners interface.
*
* This is the list of events that can be observed from a JingleSession and some
* sub negotiators. This listeners can be added to different elements of the
* Jingle model.
*
* For example, a JingleManager can notify any SessionRequestListenerListener
* listener when a new session request is received. In this case, the
* <i>sessionRequested()</i> of the listener will be executed, and the listener
* will be able to <i>accept()</i> or <i>decline()</i> the invitation.
*
* @author Alvaro Saurin
*/
public interface JingleListener {
}

View file

@ -0,0 +1,50 @@
/**
* $RCSfile$
* $Revision: $
* $Date: $11-07-2006
*
* Copyright 2003-2006 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.listeners;
/**
* Interface for listening to jmf info events.
*/
public interface JingleMediaInfoListener extends JingleListener {
/**
* The other end is busy.
*/
public void mediaInfoBusy();
/**
* We are on hold.
*/
public void mediaInfoHold();
/**
* The jmf is muted.
*/
public void mediaInfoMute();
/**
* We are queued.
*/
public void mediaInfoQueued();
/**
* We are ringing.
*/
public void mediaInfoRinging();
}

View file

@ -0,0 +1,42 @@
/**
* $RCSfile$
* $Revision: $
* $Date: $11-07-2006
*
* Copyright 2003-2006 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.listeners;
import org.jivesoftware.smackx.jingle.media.PayloadType;
/**
* Interface for listening to jmf events.
*/
public interface JingleMediaListener extends JingleListener {
/**
* Notification that the jmf has been negotiated and established.
*
* @param pt The payload type agreed.
*/
public void mediaEstablished(PayloadType pt);
/**
* Notification that a payload type must be cancelled
*
* @param cand The payload type that must be closed
*/
public void mediaClosed(PayloadType cand);
}

View file

@ -0,0 +1,75 @@
/**
* $RCSfile$
* $Revision: $
* $Date: $11-07-2006
*
* Copyright 2003-2006 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.listeners;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
/**
* Interface for listening for session events.
*/
public interface JingleSessionListener extends JingleListener {
/**
* Notification that the session has been established. Arguments specify
* the payload type and transport to use.
*
* @param pt the Payload tyep to use
* @param remoteCandidate the remote candidate to use for connecting to the remote
* service.
* @param localCandidate the local candidate where we must listen for connections
* @param jingleSession Session that called the method
*/
public void sessionEstablished(PayloadType pt, TransportCandidate remoteCandidate,
TransportCandidate localCandidate, JingleSession jingleSession);
/**
* Notification that the session was declined.
*
* @param reason the reason (if any).
* @param jingleSession Session that called the method
*/
public void sessionDeclined(String reason, JingleSession jingleSession);
/**
* Notification that the session was redirected.
*
* @param redirection
* @param jingleSession session that called the method
*/
public void sessionRedirected(String redirection, JingleSession jingleSession);
/**
* Notification that the session was closed normally.
*
* @param reason the reason (if any).
* @param jingleSession Session that called the method
*/
public void sessionClosed(String reason, JingleSession jingleSession);
/**
* Notification that the session was closed due to an exception.
*
* @param e the exception.
* @param jingleSession session that called the method
*/
public void sessionClosedOnError(XMPPException e, JingleSession jingleSession);
}

View file

@ -0,0 +1,36 @@
/**
* $RCSfile$
* $Revision: $
* $Date: $11-07-2006
*
* Copyright 2003-2006 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.listeners;
import org.jivesoftware.smackx.jingle.JingleSessionRequest;
/**
* Interface to listener Jingle session requests.
*
* @author Alvaro Saurin
*/
public interface JingleSessionRequestListener extends JingleListener {
/**
* A request to start a session has been recieved from another user.
*
* @param request The request from the other user.
*/
public void sessionRequested(JingleSessionRequest request);
}

View file

@ -0,0 +1,56 @@
/**
* $RCSfile$
* $Revision: $
* $Date: $11-07-2006
*
* Copyright 2003-2006 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.listeners;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
/**
* Interface for listening to transport events.
*/
public interface JingleTransportListener extends JingleListener {
/**
* Notification that the transport has been established.
*
* @param local The transport candidate that has been used for listening
* in the local machine
* @param remote The transport candidate that has been used for
* transmitting to the remote machine
*/
public void transportEstablished(TransportCandidate local,
TransportCandidate remote);
/**
* Notification that a transport must be cancelled.
*
* @param cand The transport candidate that must be cancelled. A value
* of "null" means all the transports for this session.
*/
public void transportClosed(TransportCandidate cand);
/**
* Notification that the transport was closed due to an exception.
*
* @param e the exception.
*/
public void transportClosedOnError(XMPPException e);
}

View file

@ -0,0 +1,77 @@
/**
* $RCSfile$
* $Revision: $
* $Date: $11-07-2006
*
* Copyright 2003-2006 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.media;
/**
* Content info. Content info messages are complementary messages that can be
* transmitted for informing of events like "busy", "ringtone", etc.
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public abstract class ContentInfo {
/**
* Audio content info messages.
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public static class Audio extends ContentInfo {
public static final ContentInfo.Audio BUSY = new ContentInfo.Audio("busy");
public static final ContentInfo.Audio HOLD = new ContentInfo.Audio("hold");
public static final ContentInfo.Audio MUTE = new ContentInfo.Audio("mute");
public static final ContentInfo.Audio QUEUED = new ContentInfo.Audio("queued");
public static final ContentInfo.Audio RINGING = new ContentInfo.Audio("ringing");
private String value;
public Audio(String value) {
this.value = value;
}
public String toString() {
return value;
}
/**
* Returns the MediaInfo constant associated with the String value.
*/
public static ContentInfo fromString(String value) {
value = value.toLowerCase();
if (value.equals("busy")) {
return BUSY;
} else if (value.equals("hold")) {
return HOLD;
} else if (value.equals("mute")) {
return MUTE;
} else if (value.equals("queued")) {
return QUEUED;
} else if (value.equals("ringing")) {
return RINGING;
} else {
return null;
}
}
}
}

View file

@ -0,0 +1,85 @@
/**
* $RCSfile$
* $Revision: $
* $Date: $11-07-2006
*
* Copyright 2003-2006 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.media;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import java.util.ArrayList;
import java.util.List;
/**
* This class provides necessary Jingle Session jmf methods and behavior.
*
* The goal of this class is to provide a flexible way to make JingleManager control jmf streaming APIs without implement them.
* For instance you can implement a file transfer using java sockets or a VOIP Media Manager using JMF.
* You can implement many JingleMediaManager according to you necessity.
*
* @author Thiago Camargo
*/
public abstract class JingleMediaManager {
private List<PayloadType> payloads = new ArrayList<PayloadType>();
/**
* Return all supported Payloads for this Manager
*
* @return The Payload List
*/
public List<PayloadType> getPayloads() {
return payloads;
}
/**
* Adds a supported Payload type to Manager
*
* @param payloadType
*/
public void addPayloadType(PayloadType payloadType) {
payloads.add(payloadType);
}
/**
* Removes a supported Payload type from Manager
*
* @param payloadType
*/
public void removePayloadType(PayloadType payloadType) {
payloads.remove(payloadType);
}
/**
* Get the preferred Payload Type
*/
public PayloadType getPreferredPayloadType() {
//TODO a better way to choose the preferred Payload
return payloads.size() > 0 ? payloads.get(0) : null;
}
/**
* Create a Media Session Implementation
*
* @param payloadType
* @param remote
* @param local
* @return
*/
public abstract JingleMediaSession createMediaSession(PayloadType payloadType, final TransportCandidate remote, final TransportCandidate local);
}

View file

@ -0,0 +1,119 @@
/**
* $RCSfile$
* $Revision: $
* $Date: $11-07-2006
*
* Copyright 2003-2006 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.media;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
/**
* Public Abstract Class provides a clear interface between Media Session and Jingle API.
*
* When a Jingle Session is fully stablished, we will have a Payload Type and two transport candidates defined for it.
* Smack Jingle API don´t implement Media Transmit and Receive methods.
* But provides an interface to let the user implements it using another API. For instance: JMF.
*
* <i>The Class that implements this one, must have the support to transmit and receive the jmf.</i>
* <i>This interface let the user choose his own jmf API.</i>
*
* @author Thiago Camargo
*/
public abstract class JingleMediaSession {
// Payload Type of the Session
private PayloadType payloadType;
// Local Transport details
private TransportCandidate local;
// Remote Transport details
private TransportCandidate remote;
/**
* Creates a new JingleMediaSession Instance to handle Media methods.
*
* @param payloadType Payload Type of the transmittion
* @param remote Remote accepted Transport Candidate
* @param local Local accepted Transport Candidate
*/
public JingleMediaSession(PayloadType payloadType, TransportCandidate remote,
TransportCandidate local) {
this.local = local;
this.remote = remote;
this.payloadType = payloadType;
initialize();
}
/**
* Returns the PayloadType of the Media Session
*
* @return
*/
public PayloadType getPayloadType() {
return payloadType;
}
/**
* Returns the Media Session local Candidate
*
* @return
*/
public TransportCandidate getLocal() {
return local;
}
/**
* Returns the Media Session remote Candidate
*
* @return
*/
public TransportCandidate getRemote() {
return remote;
}
/**
* Initialize the RTP Channel preparing to transmit and receive.
*/
public abstract void initialize();
/**
* Starts a RTP / UDP / TCP Transmission to the remote Candidate
*/
public abstract void startTrasmit();
/**
* Starts a RTP / UDP / TCP Receiver from the remote Candidate to local Candidate
*/
public abstract void startReceive();
/**
* Set transmit activity. If the active is true, the instance should trasmit.
* If it is set to false, the instance should pause transmit.
* @param active
*/
public abstract void setTrasmit(boolean active);
/**
* Stops a RTP / UDP / TCP Transmission to the remote Candidate
*/
public abstract void stopTrasmit();
/**
* Stops a RTP / UDP / TCP Receiver from the remote Candidate to local Candidate
*/
public abstract void stopReceive();
}

View file

@ -0,0 +1,555 @@
package org.jivesoftware.smackx.jingle.media;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.jingle.JingleNegotiator;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.listeners.JingleListener;
import org.jivesoftware.smackx.jingle.listeners.JingleMediaListener;
import org.jivesoftware.smackx.packet.Jingle;
import org.jivesoftware.smackx.packet.JingleContentDescription;
import org.jivesoftware.smackx.packet.JingleContentDescription.JinglePayloadType;
import org.jivesoftware.smackx.packet.JingleError;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Manager for jmf descriptor negotiation.
*
*
* This class is responsible for managing the descriptor negotiation process,
* handling all the xmpp packets interchange and the stage control.
*
* @author Alvaro Saurin
*/
public class MediaNegotiator extends JingleNegotiator {
private final JingleSession session; // The session this negotiation
// Local and remote payload types...
private final List<PayloadType> localAudioPts = new ArrayList<PayloadType>();
private final List<PayloadType> remoteAudioPts = new ArrayList<PayloadType>();
private PayloadType.Audio bestCommonAudioPt;
// states
private final Inviting inviting;
private final Accepting accepting;
private final Pending pending;
private final Active active;
/**
* Default constructor. The constructor establishes some basic parameters,
* but it does not start the negotiation. For starting the negotiation, call
* startNegotiation.
*
* @param js The jingle session.
*/
public MediaNegotiator(JingleSession js, List<PayloadType> pts) {
super(js.getConnection());
session = js;
bestCommonAudioPt = null;
if (pts != null) {
if (pts.size() > 0) {
localAudioPts.addAll(pts);
}
}
// Create the states...
inviting = new Inviting(this);
accepting = new Accepting(this);
pending = new Pending(this);
active = new Active(this);
}
/**
* Dispatch an incomming packet. The medthod is responsible for recognizing
* the packet type and, depending on the current state, deliverying the
* packet to the right event handler and wait for a response.
*
* @param iq the packet received
* @return the new Jingle packet to send.
* @throws XMPPException
*/
public IQ dispatchIncomingPacket(IQ iq, String id) throws XMPPException {
IQ jout = null;
if (invalidState()) {
if (iq == null) {
// With a null packet, we are just inviting the other end...
setState(inviting);
jout = getState().eventInvite();
} else {
if (iq instanceof Jingle) {
// If there is no specific jmf action associated, then we
// are being invited to a new session...
setState(accepting);
jout = getState().eventInitiate((Jingle) iq);
} else {
throw new IllegalStateException(
"Invitation IQ received is not a Jingle packet in Media negotiator.");
}
}
} else {
if (iq == null) {
return null;
} else {
if (iq.getType().equals(IQ.Type.ERROR)) {
// Process errors
getState().eventError(iq);
} else if (iq.getType().equals(IQ.Type.RESULT)) {
// Process ACKs
if (isExpectedId(iq.getPacketID())) {
jout = getState().eventAck(iq);
removeExpectedId(iq.getPacketID());
}
} else if (iq instanceof Jingle) {
// Get the action from the Jingle packet
Jingle jin = (Jingle) iq;
Jingle.Action action = jin.getAction();
if (action != null) {
if (action.equals(Jingle.Action.CONTENTACCEPT)) {
jout = getState().eventAccept(jin);
} else if (action.equals(Jingle.Action.CONTENTDECLINE)) {
jout = getState().eventDecline(jin);
} else if (action.equals(Jingle.Action.DESCRIPTIONINFO)) {
jout = getState().eventInfo(jin);
} else if (action.equals(Jingle.Action.CONTENTMODIFY)) {
jout = getState().eventModify(jin);
}
// Any unknown action will be ignored: it is not a msg
// to us...
}
}
}
}
// Save the Id for any ACK
if (id != null) {
addExpectedId(id);
} else {
if (jout != null) {
addExpectedId(jout.getPacketID());
}
}
return jout;
}
/**
* Return true if the content is negotiated.
*
* @return true if the content is negotiated.
*/
public boolean isEstablished() {
return getBestCommonAudioPt() != null;
}
/**
* Return true if the content is fully negotiated.
*
* @return true if the content is fully negotiated.
*/
public boolean isFullyEstablished() {
return isEstablished() && getState() == active;
}
// Payload types
private PayloadType.Audio calculateBestCommonAudioPt(List remoteAudioPts) {
final ArrayList commonAudioPtsHere = new ArrayList();
final ArrayList commonAudioPtsThere = new ArrayList();
PayloadType.Audio result = null;
if (!remoteAudioPts.isEmpty()) {
commonAudioPtsHere.addAll(localAudioPts);
commonAudioPtsHere.retainAll(remoteAudioPts);
commonAudioPtsThere.addAll(remoteAudioPts);
commonAudioPtsThere.retainAll(localAudioPts);
if (!commonAudioPtsHere.isEmpty() && !commonAudioPtsThere.isEmpty()) {
PayloadType.Audio bestPtHere = (PayloadType.Audio) commonAudioPtsHere
.get(0);
PayloadType.Audio bestPtThere = (PayloadType.Audio) commonAudioPtsThere
.get(0);
// If both match, use it
if (bestPtHere.equals(bestPtThere)) {
result = bestPtHere;
} else {
// Otherwise, use the one of the initiator...
// FIXME: this is an invented behavior!!!
String initiator = session.getInitiator();
String me = session.getConnection().getUser();
if (initiator.equals(me)) {
result = bestPtHere;
} else {
result = bestPtThere;
}
}
}
}
return result;
}
private List obtainPayloads(Jingle jin) {
List result = new ArrayList();
Iterator iDescr = jin.getDescriptions();
// Add the list of payloads: iterate over the descriptions...
while (iDescr.hasNext()) {
JingleContentDescription.Audio descr = (JingleContentDescription.Audio) iDescr
.next();
if (descr != null) {
// ...and, then, over the payloads.
// Note: we use the last "description" in the packet...
result.clear();
result.addAll(descr.getAudioPayloadTypesList());
}
}
return result;
}
/**
* Adds a payload type to the list of remote payloads.
*
* @param pt the remote payload type
*/
public void addRemoteAudioPayloadType(PayloadType.Audio pt) {
if (pt != null) {
synchronized (remoteAudioPts) {
remoteAudioPts.add(pt);
}
}
}
/**
* Create an offer for the list of audio payload types.
*
* @return a new Jingle packet with the list of audio Payload Types
*/
private Jingle getAudioPayloadTypesOffer() {
JingleContentDescription.Audio audioDescr = new JingleContentDescription.Audio();
// Add the list of payloads for audio and create a
// JingleContentDescription
// where we announce our payloads...
audioDescr.addAudioPayloadTypes(localAudioPts);
return new Jingle(audioDescr);
}
// Predefined messages and Errors
/**
* Create an IQ "accept" message.
*/
private Jingle createAcceptMessage() {
Jingle jout = null;
// If we hava a common best codec, send an accept right now...
jout = new Jingle(Jingle.Action.CONTENTACCEPT);
jout.addDescription(new JingleContentDescription.Audio(
new JinglePayloadType.Audio(bestCommonAudioPt)));
return jout;
}
// Payloads
/**
* Get the best common codec between both parts.
*
* @return The best common PayloadType codec.
*/
public PayloadType.Audio getBestCommonAudioPt() {
return bestCommonAudioPt;
}
// Events
/**
* Trigger a session established event.
*
* @param bestPt payload type that has been agreed.
*/
protected void triggerMediaEstablished(PayloadType bestPt) {
ArrayList listeners = getListenersList();
Iterator iter = listeners.iterator();
while (iter.hasNext()) {
JingleListener li = (JingleListener) iter.next();
if (li instanceof JingleMediaListener) {
JingleMediaListener mli = (JingleMediaListener) li;
mli.mediaEstablished(bestPt);
}
}
}
/**
* Trigger a jmf closed event.
*
* @param currPt current payload type that is cancelled.
*/
protected void triggerMediaClosed(PayloadType currPt) {
ArrayList listeners = getListenersList();
Iterator iter = listeners.iterator();
while (iter.hasNext()) {
JingleListener li = (JingleListener) iter.next();
if (li instanceof JingleMediaListener) {
JingleMediaListener mli = (JingleMediaListener) li;
mli.mediaClosed(currPt);
}
}
}
/**
* Terminate the jmf negotiator
*/
public void close() {
super.close();
}
// States
/**
* First stage when we send a session request.
*/
public class Inviting extends JingleNegotiator.State {
public Inviting(MediaNegotiator neg) {
super(neg);
}
/**
* Create an initial Jingle packet, with the list of payload types that
* we support. The list is in order of preference.
*/
public Jingle eventInvite() {
return getAudioPayloadTypesOffer();
}
/**
* We have received the ACK for our invitation.
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventAck(org.jivesoftware.smack.packet.IQ)
*/
public Jingle eventAck(IQ iq) {
setState(pending);
return null;
}
}
/**
* We are accepting connections.
*/
public class Accepting extends JingleNegotiator.State {
public Accepting(MediaNegotiator neg) {
super(neg);
}
/**
* We have received an invitation! Respond with a list of our payload
* types...
*/
public Jingle eventInitiate(Jingle jin) {
synchronized (remoteAudioPts) {
remoteAudioPts.addAll(obtainPayloads(jin));
}
return getAudioPayloadTypesOffer();
}
/**
* Process the ACK of our list of codecs (our offer).
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventAck(org.jivesoftware.smack.packet.IQ)
*/
public Jingle eventAck(IQ iq) throws XMPPException {
Jingle response = null;
if (!remoteAudioPts.isEmpty()) {
// Calculate the best common codec
bestCommonAudioPt = calculateBestCommonAudioPt(remoteAudioPts);
// and send an accept if we havee an agreement...
if (bestCommonAudioPt != null) {
response = createAcceptMessage();
} else {
throw new JingleException(JingleError.NO_COMMON_PAYLOAD);
}
setState(pending);
}
return response;
}
}
/**
* Pending class: we are waiting for the other enpoint, that must say if it
* accepts or not...
*/
public class Pending extends JingleNegotiator.State {
public Pending(MediaNegotiator neg) {
super(neg);
}
/**
* A content info has been received. This is done for publishing the
* list of payload types...
*
* @param jin The input packet
* @return a Jingle packet
* @throws JingleException
*/
public Jingle eventInfo(Jingle jin) throws JingleException {
PayloadType.Audio oldBestCommonAudioPt = bestCommonAudioPt;
List offeredPayloads = new ArrayList();
Jingle response = null;
boolean ptChange = false;
offeredPayloads = obtainPayloads(jin);
if (!offeredPayloads.isEmpty()) {
synchronized (remoteAudioPts) {
remoteAudioPts.clear();
remoteAudioPts.addAll(offeredPayloads);
}
// Calculate the best common codec
bestCommonAudioPt = calculateBestCommonAudioPt(remoteAudioPts);
if (bestCommonAudioPt != null) {
// and send an accept if we have an agreement...
ptChange = !bestCommonAudioPt.equals(oldBestCommonAudioPt);
if (oldBestCommonAudioPt == null || ptChange) {
response = createAcceptMessage();
}
} else {
throw new JingleException(JingleError.NO_COMMON_PAYLOAD);
}
}
// Parse the Jingle and get the payload accepted
return response;
}
/**
* A jmf description has been accepted. In this case, we must save the
* accepted payload type and notify any listener...
*
* @param jin The input packet
* @return a Jingle packet
* @throws JingleException
*/
public Jingle eventAccept(Jingle jin) throws JingleException {
PayloadType.Audio agreedCommonAudioPt;
List offeredPayloads = new ArrayList();
Jingle response = null;
if (bestCommonAudioPt == null) {
// Update the best common audio PT
bestCommonAudioPt = calculateBestCommonAudioPt(remoteAudioPts);
response = createAcceptMessage();
}
offeredPayloads = obtainPayloads(jin);
if (!offeredPayloads.isEmpty()) {
if (offeredPayloads.size() == 1) {
agreedCommonAudioPt = (PayloadType.Audio) offeredPayloads.get(0);
if (bestCommonAudioPt != null) {
// If the accepted PT matches the best payload
// everything is fine
if (!agreedCommonAudioPt.equals(bestCommonAudioPt)) {
throw new JingleException(JingleError.NEGOTIATION_ERROR);
}
}
} else if (offeredPayloads.size() > 1) {
throw new JingleException(JingleError.MALFORMED_STANZA);
}
}
return response;
}
/**
* The other part has declined the our codec...
*
* @throws JingleException
*/
public Jingle eventDecline(Jingle inJingle) throws JingleException {
triggerMediaClosed(getBestCommonAudioPt());
throw new JingleException();
}
/*
* (non-Javadoc)
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventError(org.jivesoftware.smack.packet.IQ)
*/
public void eventError(IQ iq) throws XMPPException {
triggerMediaClosed(getBestCommonAudioPt());
super.eventError(iq);
}
/**
* ACK received.
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventAck(org.jivesoftware.smack.packet.IQ)
*/
public Jingle eventAck(IQ iq) {
if (isEstablished()) {
setState(active);
}
return null;
}
}
/**
* "Active" state: we have an agreement about the codec...
*/
public class Active extends JingleNegotiator.State {
public Active(MediaNegotiator neg) {
super(neg);
}
/**
* We have an agreement.
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventEnter()
*/
public void eventEnter() {
triggerMediaEstablished(getBestCommonAudioPt());
super.eventEnter();
}
/**
* We are breaking the contract...
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventExit()
*/
public void eventExit() {
triggerMediaClosed(getBestCommonAudioPt());
super.eventExit();
}
}
}

View file

@ -0,0 +1,289 @@
package org.jivesoftware.smackx.jingle.media;
/**
* Represents a payload type.
*
* @author Alvaro Saurin
*/
public class PayloadType {
public static int MAX_FIXED_PT = 95;
public static int INVALID_PT = 65535;
private int id;
private String name;
private int channels;
/**
* Constructor with Id, name and number of channels
*
* @param id The identifier
* @param name A name
* @param channels The number of channels
*/
public PayloadType(int id, final String name, int channels) {
super();
this.id = id;
this.name = name;
this.channels = channels;
}
/**
* Default constructor.
*/
public PayloadType() {
this(INVALID_PT, null, 1);
}
/**
* Constructor with Id and name
*
* @param id The identification
* @param name A name
*/
public PayloadType(int id, String name) {
this(id, name, 1);
}
/**
* Copy constructor
*
* @param pt The other payload type.
*/
public PayloadType(PayloadType pt) {
this(pt.getId(), pt.getName(), pt.getChannels());
}
/**
* Get the ID.
*
* @return the ID
*/
public int getId() {
return id;
}
/**
* Set the ID.
*
* @param id ID
*/
public void setId(int id) {
this.id = id;
}
/**
* Get the printable name.
*
* @return printable name for the payload type
*/
public String getName() {
return name;
}
/**
* Set the printable name.
*
* @param name the printable name
*/
public void setName(String name) {
this.name = name;
}
/**
* Get the number of channels used by this payload type.
*
* @return the number of channels
*/
public int getChannels() {
return channels;
}
/**
* Set the numer of channels for a payload type.
*
* @param channels The number of channels
*/
public void setChannels(int channels) {
this.channels = channels;
}
/**
* Return true if the Payload type is not valid
*
* @return true if the payload type is invalid
*/
public boolean isNull() {
if (getId() == INVALID_PT) {
return true;
} else if (getName() == null) {
return true;
}
return false;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + getChannels();
result = PRIME * result + getId();
result = PRIME * result + (getName() == null ? 0 : getName().hashCode());
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final PayloadType other = (PayloadType) obj;
if (getChannels() != other.getChannels()) {
return false;
}
if (getId() != other.getId()) {
return false;
}
// Compare names only for dynamic payload types
if (getId() > MAX_FIXED_PT) {
if (getName() == null) {
if (other.getName() != null) {
return false;
}
} else if (!getName().equals(other.getName())) {
return false;
}
}
return true;
}
/**
* Audio payload type.
*/
public static class Audio extends PayloadType {
private int clockRate;
/**
* Constructor with all the attributes of an Audio payload type
*
* @param id The identifier
* @param name The name assigned to this payload type
* @param channels The number of channels
* @param rate The clock rate
*/
public Audio(int id, String name, int channels, int rate) {
super(id, name, channels);
clockRate = rate;
}
/**
* Empty constructor.
*/
public Audio() {
super();
clockRate = 0;
}
/**
* Constructor with Id and name
*
* @param id the Id for the payload type
* @param name the name of the payload type
*/
public Audio(int id, String name) {
super(id, name);
clockRate = 0;
}
/**
* Copy constructor
*
* @param pt the other payload type
*/
public Audio(PayloadType pt) {
super(pt);
clockRate = 0;
}
/**
* Copy constructor
*
* @param pt the other payload type
*/
public Audio(PayloadType.Audio pt) {
super(pt);
clockRate = pt.getClockRate();
}
/**
* Get the sampling clockRate for a payload type
*
* @return The sampling clockRate
*/
public int getClockRate() {
return clockRate;
}
/**
* Set tha sampling clockRate for a playload type.
*
* @param rate The sampling clockRate
*/
public void setClockRate(int rate) {
clockRate = rate;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
final int PRIME = 31;
int result = super.hashCode();
result = PRIME * result + getClockRate();
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Audio other = (Audio) obj;
if (getClockRate() != other.getClockRate()) {
return false;
}
return true;
}
}
}

View file

@ -0,0 +1,102 @@
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.XMPPException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
/**
* Basic Resolver takes all IP addresses of the interfaces and uses the
* first non-loopback address.
* A very simple and easy to use resolver.
*/
public class BasicResolver extends TransportResolver {
/**
* Constructor.
*/
public BasicResolver() {
super();
}
/**
* Resolve the IP address.
* <p/>
* The BasicResolver takes the IP addresses of the interfaces and uses the
* first non-loopback, non-linklocal and non-sitelocal address.
*/
public synchronized void resolve() throws XMPPException {
setResolveInit();
clearCandidates();
Enumeration ifaces = null;
try {
ifaces = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
e.printStackTrace();
}
while (ifaces.hasMoreElements()) {
NetworkInterface iface = (NetworkInterface) ifaces.nextElement();
Enumeration iaddresses = iface.getInetAddresses();
while (iaddresses.hasMoreElements()) {
InetAddress iaddress = (InetAddress) iaddresses.nextElement();
if (!iaddress.isLoopbackAddress() && !iaddress.isLinkLocalAddress() && !iaddress.isSiteLocalAddress()) {
TransportCandidate tr = new TransportCandidate.Fixed(iaddress.getHostAddress() != null ? iaddress.getHostAddress() : iaddress.getHostName(), getFreePort());
tr.setLocalIp(iaddress.getHostAddress() != null ? iaddress.getHostAddress() : iaddress.getHostName());
addCandidate(tr);
setResolveEnd();
return;
}
}
}
try {
ifaces = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
e.printStackTrace();
}
while (ifaces.hasMoreElements()) {
NetworkInterface iface = (NetworkInterface) ifaces.nextElement();
Enumeration iaddresses = iface.getInetAddresses();
while (iaddresses.hasMoreElements()) {
InetAddress iaddress = (InetAddress) iaddresses.nextElement();
if (!iaddress.isLoopbackAddress() && !iaddress.isLinkLocalAddress()) {
TransportCandidate tr = new TransportCandidate.Fixed(iaddress.getHostAddress() != null ? iaddress.getHostAddress() : iaddress.getHostName(), getFreePort());
tr.setLocalIp(iaddress.getHostAddress() != null ? iaddress.getHostAddress() : iaddress.getHostName());
addCandidate(tr);
setResolveEnd();
return;
}
}
}
try {
TransportCandidate tr = new TransportCandidate.Fixed(InetAddress.getLocalHost().getHostAddress() != null ? InetAddress.getLocalHost().getHostAddress() : InetAddress.getLocalHost().getHostName(), getFreePort());
tr.setLocalIp(InetAddress.getLocalHost().getHostAddress() != null ? InetAddress.getLocalHost().getHostAddress() : InetAddress.getLocalHost().getHostName());
addCandidate(tr);
} catch (Exception e) {
e.printStackTrace();
}
setResolveEnd();
}
public void initialize() throws XMPPException {
setInitialized();
}
public void cancel() throws XMPPException {
// Nothing to do here
}
}

View file

@ -0,0 +1,32 @@
package org.jivesoftware.smackx.jingle.nat;
/**
* $RCSfile$
* $Revision: $
* $Date: 15/11/2006
*
* Copyright 2003-2006 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* A Basic Jingle Transport Manager implementation.
*
*/
public class BasicTransportManager extends JingleTransportManager{
protected TransportResolver createResolver() {
return new BasicResolver();
}
}

View file

@ -0,0 +1,94 @@
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import java.util.Random;
/**
* Bridged Resolver use a RTPBridge Service to add a relayed candidate.
* A very reliable solution for NAT Traversal.
*
* The resolver verify is the XMPP Server that the client is connected offer this service.
* If the server supports, a candidate is requested from the service.
* The resolver adds this candidate
*/
public class BridgedResolver extends TransportResolver{
XMPPConnection connection;
Random random = new Random();
long sid;
/**
* Constructor.
* A Bridged Resolver need a XMPPConnection to connect to a RTP Bridge.
*/
public BridgedResolver(XMPPConnection connection) {
super();
this.connection = connection;
}
/**
* Resolve Bridged Candidate.
* <p/>
* The BridgedResolver takes the IP addresse and ports of a jmf proxy service.
*/
public synchronized void resolve() throws XMPPException {
setResolveInit();
clearCandidates();
sid = Math.abs(random.nextLong());
RTPBridge rtpBridge = RTPBridge.getRTPBridge(connection, String.valueOf(sid));
BasicResolver basicResolver = new BasicResolver();
basicResolver.initializeAndWait();
basicResolver.resolve();
TransportCandidate localCandidate = new TransportCandidate.Fixed(
rtpBridge.getIp(), rtpBridge.getPortA());
localCandidate.setLocalIp(basicResolver.getCandidate(0).getLocalIp());
TransportCandidate remoteCandidate = new TransportCandidate.Fixed(
rtpBridge.getIp(), rtpBridge.getPortB());
remoteCandidate.setLocalIp(basicResolver.getCandidate(0).getLocalIp());
localCandidate.setSymmetric(remoteCandidate);
remoteCandidate.setSymmetric(localCandidate);
localCandidate.setPassword(rtpBridge.getPass());
remoteCandidate.setPassword(rtpBridge.getPass());
localCandidate.setSessionId(rtpBridge.getSid());
remoteCandidate.setSessionId(rtpBridge.getSid());
localCandidate.setConnection(this.connection);
remoteCandidate.setConnection(this.connection);
addCandidate(localCandidate);
setResolveEnd();
}
public void initialize() throws XMPPException {
clearCandidates();
if (!RTPBridge.serviceAvailable(connection)) {
setInitialized();
throw new XMPPException("No RTP Bridge service available");
}
setInitialized();
}
public void cancel() throws XMPPException {
// Nothing to do here
}
}

View file

@ -0,0 +1,75 @@
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.listeners.CreatedJingleSessionListener;
import org.jivesoftware.smackx.jingle.listeners.JingleSessionListener;
import org.jivesoftware.smackx.jingle.media.PayloadType;
/**
* $RCSfile$
* $Revision: $
* $Date: 15/11/2006
*
* Copyright 2003-2006 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* A Jingle Transport Manager implementation to be used for NAT Networks.
* This kind of transport needs that the connected XMPP Server provide a Bridge Service. (http://www.jivesoftware.com/protocol/rtpbridge)
* To relay the jmf outside the NAT.
*
* @author Thiago Camargo
*/
public class BridgedTransportManager extends JingleTransportManager implements JingleSessionListener, CreatedJingleSessionListener {
XMPPConnection xmppConnection;
public BridgedTransportManager(XMPPConnection xmppConnection) {
super();
this.xmppConnection = xmppConnection;
}
// Return the correspondent resolver
protected TransportResolver createResolver() {
BridgedResolver bridgedResolver = new BridgedResolver(this.xmppConnection);
return bridgedResolver;
}
// Implement a Session Listener to relay candidates after establishment
public void sessionEstablished(PayloadType pt, TransportCandidate rc, TransportCandidate lc, JingleSession jingleSession) {
RTPBridge rtpBridge = RTPBridge.relaySession(lc.getConnection(), lc.getSessionId(), lc.getPassword(), rc, lc);
}
public void sessionDeclined(String reason, JingleSession jingleSession) {
}
public void sessionRedirected(String redirection, JingleSession jingleSession) {
}
public void sessionClosed(String reason, JingleSession jingleSession) {
}
public void sessionClosedOnError(XMPPException e, JingleSession jingleSession) {
}
// Session Created
public void sessionCreated(JingleSession jingleSession) {
jingleSession.addListener(this);
}
}

View file

@ -0,0 +1,67 @@
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.XMPPException;
/**
* The FixedResolver is a resolver where
* the external address and port are previously known when the object is
* initialized.
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public class FixedResolver extends TransportResolver {
TransportCandidate fixedCandidate;
/**
* Constructor.
*/
public FixedResolver(String ip, int port) {
super();
setFixedCandidate(ip, port);
}
/**
* Create a basic resolver, where we provide the IP and port.
*
* @param ip an IP address
* @param port a port
*/
public void setFixedCandidate(String ip, int port) {
System.out.println("FIXED");
fixedCandidate = new TransportCandidate.Fixed(ip, port);
}
/**
* Resolve the IP address.
*/
public synchronized void resolve() throws XMPPException {
if (!isResolving()) {
setResolveInit();
clearCandidates();
if (fixedCandidate.getLocalIp() == null)
fixedCandidate.setLocalIp(fixedCandidate.getIp());
if (fixedCandidate != null) {
addCandidate(fixedCandidate);
}
setResolveEnd();
}
}
/**
* Initialize the resolver.
*
* @throws XMPPException
*/
public void initialize() throws XMPPException {
setInitialized();
}
public void cancel() throws XMPPException {
// Nothing to do here
}
}

View file

@ -0,0 +1,85 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2005 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import de.javawi.jstun.test.demo.ice.Candidate;
import de.javawi.jstun.test.demo.ice.ICENegociator;
import de.javawi.jstun.util.UtilityException;
import org.jivesoftware.smack.XMPPException;
import java.net.UnknownHostException;
import java.util.List;
/**
* ICE Resolver for Jingle transport method that results in sending data between two entities using the Interactive Connectivity Establishment (ICE) methodology. (XEP-0176)
* The goal of this resolver is to make possible to establish and manage out-of-band connections between two XMPP entities, even if they are behind Network Address Translators (NATs) or firewalls.
* To use this resolver you must have a STUN Server and be in a non STUN blocked network.
*
* @author Thiago Camargo
*/
public class ICEResolver extends TransportResolver {
public ICEResolver() {
super();
this.setType(Type.ice);
}
public void initialize() throws XMPPException {
if (!isResolving() && !isResolved()) {
System.out.println("Initialized");
ICENegociator cc = new ICENegociator((short) 1);
// gather candidates
cc.gatherCandidateAddresses();
// priorize candidates
cc.prioritizeCandidates();
// get SortedCandidates
//List<Candidate> sortedCandidates = cc.getSortedCandidates();
for (Candidate candidate : cc.getSortedCandidates())
try {
TransportCandidate transportCandidate = new TransportCandidate.Ice(candidate.getAddress().getInetAddress().getHostAddress(), 1, candidate.getNetwork(), "1", candidate.getPort(), "1", candidate.getPriority());
transportCandidate.setLocalIp(candidate.getBase().getAddress().getInetAddress().getHostAddress());
this.addCandidate(transportCandidate);
System.out.println("C: " + candidate.getAddress().getInetAddress() + "|" + candidate.getBase().getAddress().getInetAddress() + " p:" + candidate.getPriority());
} catch (UtilityException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
this.setInitialized();
}
public void cancel() throws XMPPException {
}
/**
* Resolve the IP and obtain a valid transport method.
*/
public synchronized void resolve() throws XMPPException {
this.setResolveInit();
System.out.println("Resolve");
this.setResolveEnd();
}
}

View file

@ -0,0 +1,46 @@
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.XMPPException;
/**
* $RCSfile$
* $Revision: $
* $Date: 02/01/2007
*
* Copyright 2003-2006 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class ICETransportManager extends JingleTransportManager {
ICEResolver iceResolver = null;
public ICETransportManager() {
iceResolver = new ICEResolver();
try {
iceResolver.initializeAndWait();
} catch (XMPPException e) {
e.printStackTrace();
}
}
protected TransportResolver createResolver() {
try {
iceResolver.resolve();
} catch (XMPPException e) {
e.printStackTrace();
}
return iceResolver;
}
}

View file

@ -0,0 +1,54 @@
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.XMPPException;
/**
* Transport manager for Jingle.
*
* This class makes easier the use of transport resolvers by presenting a simple
* interface for algorithm selection. The transport manager also keeps the match
* between the resolution method and the &lt;transport&gt; element present in
* Jingle packets.
*
* As Jingle have many transport methods (official and unofficial methods),
* this abstract class helps us to extends the transport support of the API.
*
* This class must be used with a JingleManager instance in the following way:
*
* JingleManager jingleManager = new JingleManager(xmppConnection, new BasicTransportManager());
*
* @author Thiago Camargo
*/
public abstract class JingleTransportManager {
// This class implements the context of a Strategy pattern...
/**
* Deafult contructor.
*/
public JingleTransportManager() {
}
/**
* Get a new Transport Resolver to be used in a Jingle Session
*
* @return
*/
public TransportResolver getResolver() throws XMPPException {
TransportResolver resolver = createResolver();
if (resolver == null) {
resolver = new BasicResolver();
}
resolver.initializeAndWait();
return resolver;
}
/**
* Create a Transport Resolver instance according to the implementation.
*
* @return
*/
protected abstract TransportResolver createResolver();
}

View file

@ -0,0 +1,462 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2005 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.PacketCollector;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smackx.ServiceDiscoveryManager;
import org.jivesoftware.smackx.packet.DiscoverItems;
import org.xmlpull.v1.XmlPullParser;
import java.util.Iterator;
/**
* RTPBridge IQ Packet used to request and retrieve a RTPBridge Candidates that can be used for a Jingle Media Transmission between two parties that are behind NAT.
* This Jingle Bridge has all the needed information to establish a full UDP Channel (Send and Receive) between two parties.
* <i>This transport method should be used only if other transport methods are not allowed. Or if you want a more reliable transport.</i>
* <p/>
* High Level Usage Example:
* <p/>
* RTPBridge rtpBridge = RTPBridge.getRTPBridge(xmppConnection, sessionID);
*
* @author Thiago Camargo
*/
public class RTPBridge extends IQ {
private String sid;
private String pass;
private String ip;
private String name;
private int portA = -1;
private int portB = -1;
private String hostA;
private String hostB;
private BridgeAction bridgeAction = BridgeAction.create;
private enum BridgeAction {
create, change
}
/**
* Element name of the packet extension.
*/
public static final String NAME = "rtpbridge";
/**
* Element name of the packet extension.
*/
public static final String ELEMENT_NAME = "rtpbridge";
/**
* Namespace of the packet extension.
*/
public static final String NAMESPACE = "http://www.jivesoftware.com/protocol/rtpbridge";
static {
ProviderManager.getInstance().addIQProvider(NAME, NAMESPACE, new Provider());
}
/**
* Creates a RTPBridge Instance with defined Session ID
*
* @param sid
*/
public RTPBridge(String sid) {
this.sid = sid;
}
/**
* Creates a RTPBridge Instance with defined Session ID
*
* @param sid
* @param bridgeAction
*/
public RTPBridge(String sid, BridgeAction bridgeAction) {
this.sid = sid;
this.bridgeAction = bridgeAction;
}
/**
* Creates a RTPBridge Packet without Session ID
*/
public RTPBridge() {
}
/**
* Get the attributes string
*/
public String getAttributes() {
StringBuilder str = new StringBuilder();
if (getSid() != null)
str.append(" sid='").append(getSid()).append("'");
if (getPass() != null)
str.append(" pass='").append(getPass()).append("'");
if (getPortA() != -1)
str.append(" porta='").append(getPortA()).append("'");
if (getPortB() != -1)
str.append(" portb='").append(getPortB()).append("'");
if (getHostA() != null)
str.append(" hosta='").append(getHostA()).append("'");
if (getHostB() != null)
str.append(" hostb='").append(getHostB()).append("'");
return str.toString();
}
/**
* Get the Session ID of the Packet (usually same as Jingle Session ID)
*
* @return
*/
public String getSid() {
return sid;
}
/**
* Set the Session ID of the Packet (usually same as Jingle Session ID)
*
* @param sid
*/
public void setSid(String sid) {
this.sid = sid;
}
/**
* Get the Host A IP Address
*
* @return
*/
public String getHostA() {
return hostA;
}
/**
* Set the Host A IP Address
*
* @param hostA
*/
public void setHostA(String hostA) {
this.hostA = hostA;
}
/**
* Get the Host B IP Address
*
* @return
*/
public String getHostB() {
return hostB;
}
/**
* Set the Host B IP Address
*
* @param hostB
*/
public void setHostB(String hostB) {
this.hostB = hostB;
}
/**
* Get Side A receive port
*
* @return
*/
public int getPortA() {
return portA;
}
/**
* Set Side A receive port
*
* @param portA
*/
public void setPortA(int portA) {
this.portA = portA;
}
/**
* Get Side B receive port
*
* @return
*/
public int getPortB() {
return portB;
}
/**
* Set Side B receive port
*
* @param portB
*/
public void setPortB(int portB) {
this.portB = portB;
}
/**
* Get the RTP Bridge IP
*
* @return
*/
public String getIp() {
return ip;
}
/**
* Set the RTP Bridge IP
*
* @param ip
*/
public void setIp(String ip) {
this.ip = ip;
}
/**
* Get the RTP Agent Pass
*
* @return
*/
public String getPass() {
return pass;
}
/**
* Set the RTP Agent Pass
*
* @param pass
*/
public void setPass(String pass) {
this.pass = pass;
}
/**
* Get the name of the Candidate
*
* @return
*/
public String getName() {
return name;
}
/**
* Set the name of the Candidate
*
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* Get the Child Element XML of the Packet
*
* @return
*/
public String getChildElementXML() {
StringBuilder str = new StringBuilder();
str.append("<" + ELEMENT_NAME + " xmlns='" + NAMESPACE + "' sid='").append(sid).append("'>");
if (bridgeAction.equals(BridgeAction.create))
str.append("<candidate/>");
else
str.append("<relay ").append(getAttributes()).append(" />");
str.append("</" + ELEMENT_NAME + ">");
return str.toString();
}
/**
* IQProvider for RTP Bridge packets.
* Parse receive RTPBridge packet to a RTPBridge instance
*
* @author Thiago Rocha
*/
public static class Provider implements IQProvider {
public Provider() {
super();
}
public IQ parseIQ(XmlPullParser parser) throws Exception {
boolean done = false;
int eventType;
String elementName;
String namespace;
if (!parser.getNamespace().equals(RTPBridge.NAMESPACE))
throw new Exception("Not a RTP Bridge packet");
RTPBridge iq = new RTPBridge();
for (int i = 0; i < parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("sid"))
iq.setSid(parser.getAttributeValue(i));
}
// Start processing sub-elements
while (!done) {
eventType = parser.next();
elementName = parser.getName();
namespace = parser.getNamespace();
if (eventType == XmlPullParser.START_TAG) {
if (elementName.equals("candidate")) {
for (int i = 0; i < parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("ip"))
iq.setIp(parser.getAttributeValue(i));
else if (parser.getAttributeName(i).equals("pass"))
iq.setPass(parser.getAttributeValue(i));
else if (parser.getAttributeName(i).equals("name"))
iq.setName(parser.getAttributeValue(i));
else if (parser.getAttributeName(i).equals("porta"))
iq.setPortA(Integer.parseInt(parser.getAttributeValue(i)));
else if (parser.getAttributeName(i).equals("portb"))
iq.setPortB(Integer.parseInt(parser.getAttributeValue(i)));
}
}
} else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals(RTPBridge.ELEMENT_NAME)) {
done = true;
}
}
}
return iq;
}
}
/**
* Get a new RTPBridge Candidate from the server.
* If a error occurs or the server don´t support RTPBridge Service, null is returned.
*
* @param xmppConnection
* @param sessionID
* @return
*/
public static RTPBridge getRTPBridge(XMPPConnection xmppConnection, String sessionID) {
if (!xmppConnection.isConnected()) {
return null;
}
RTPBridge rtpPacket = new RTPBridge(sessionID);
rtpPacket.setTo(RTPBridge.NAME + "." + xmppConnection.getServiceName());
PacketCollector collector = xmppConnection
.createPacketCollector(new PacketIDFilter(rtpPacket.getPacketID()));
xmppConnection.sendPacket(rtpPacket);
RTPBridge response = (RTPBridge) collector
.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Cancel the collector.
collector.cancel();
return response;
}
/**
* Check if the server support RTPBridge Service.
*
* @param xmppConnection
* @return
*/
public static boolean serviceAvailable(XMPPConnection xmppConnection) {
if (!xmppConnection.isConnected()) {
return false;
}
System.out.println("service listing");
ServiceDiscoveryManager disco = ServiceDiscoveryManager
.getInstanceFor(xmppConnection);
try {
DiscoverItems items = disco.discoverItems(xmppConnection.getServiceName());
Iterator iter = items.getItems();
while (iter.hasNext()) {
DiscoverItems.Item item = (DiscoverItems.Item) iter.next();
if (item.getEntityID().startsWith("rtpbridge.")) {
return true;
}
}
}
catch (XMPPException e) {
e.printStackTrace();
}
return false;
}
/**
* Check if the server support RTPBridge Service.
*
* @param xmppConnection
* @return
*/
public static RTPBridge relaySession(XMPPConnection xmppConnection, String sessionID, String pass, TransportCandidate proxyCandidate, TransportCandidate localCandidate) {
if (!xmppConnection.isConnected()) {
return null;
}
RTPBridge rtpPacket = new RTPBridge(sessionID, RTPBridge.BridgeAction.change);
rtpPacket.setTo(RTPBridge.NAME + "." + xmppConnection.getServiceName());
rtpPacket.setType(Type.SET);
rtpPacket.setPass(pass);
rtpPacket.setPortA(localCandidate.getPort());
rtpPacket.setPortB(proxyCandidate.getPort());
rtpPacket.setHostA(localCandidate.getIp());
rtpPacket.setHostB(proxyCandidate.getIp());
// System.out.println("Relayed to: " + candidate.getIp() + ":" + candidate.getPort());
PacketCollector collector = xmppConnection
.createPacketCollector(new PacketIDFilter(rtpPacket.getPacketID()));
xmppConnection.sendPacket(rtpPacket);
RTPBridge response = (RTPBridge) collector
.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Cancel the collector.
collector.cancel();
return response;
}
}

View file

@ -0,0 +1,509 @@
package org.jivesoftware.smackx.jingle.nat;
import de.javawi.jstun.test.BindingLifetimeTest;
import de.javawi.jstun.test.DiscoveryInfo;
import de.javawi.jstun.test.DiscoveryTest;
import org.jivesoftware.smack.XMPPException;
import org.xmlpull.mxp1.MXParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
/**
* Transport resolver using the JSTUN library, to discover public IP and use it as a candidate.
*
* The goal of this resolver is to take possible to establish and manage out-of-band connections between two XMPP entities, even if they are behind Network Address Translators (NATs) or firewalls.
*
* @author Alvaro Saurin
*/
public class STUNResolver extends TransportResolver {
// The filename where the STUN servers are stored.
public final static String STUNSERVERS_FILENAME = "META-INF/stun-config.xml";
// Fallback values when we don't have any STUN server to use...
private final static String FALLBACKHOSTNAME = "stun.xten.net";
private final static int FALLBACKHOSTPORT = 3478;
// Current STUN server we are using
protected STUNService currentServer;
protected Thread resolverThread;
protected int defaultPort;
protected String resolvedPublicIP;
protected String resolvedLocalIP;
/**
* Constructor with default STUN server.
*/
public STUNResolver() {
super();
this.defaultPort = 0;
this.currentServer = new STUNService();
}
/**
* Constructor with a default port.
*
* @param defaultPort Port to use by default.
*/
public STUNResolver(int defaultPort) {
this();
this.defaultPort = defaultPort;
}
/**
* Return true if the service is working.
*
* @see TransportResolver#isResolving()
*/
public boolean isResolving() {
return super.isResolving() && resolverThread != null;
}
/**
* Set the STUN server name and port
*
* @param ip the STUN server name
* @param port the STUN server port
*/
public void setSTUNService(String ip, int port) {
currentServer = new STUNService(ip, port);
}
/**
* Get the name of the current STUN server.
*
* @return the name of the STUN server
*/
public String getCurrentServerName() {
if (!currentServer.isNull()) {
return currentServer.getHostname();
} else {
return null;
}
}
/**
* Get the port of the current STUN server.
*
* @return the port of the STUN server
*/
public int getCurrentServerPort() {
if (!currentServer.isNull()) {
return currentServer.getPort();
} else {
return 0;
}
}
/**
* Load the STUN configuration from a stream.
*
* @param stunConfigStream An InputStream with the configuration file.
* @return A list of loaded servers
*/
public ArrayList loadSTUNServers(java.io.InputStream stunConfigStream) {
ArrayList serversList = new ArrayList();
String serverName;
int serverPort;
try {
XmlPullParser parser = new MXParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(stunConfigStream, "UTF-8");
int eventType = parser.getEventType();
do {
if (eventType == XmlPullParser.START_TAG) {
// Parse a STUN server definition
if (parser.getName().equals("stunServer")) {
serverName = null;
serverPort = -1;
// Parse the hostname
parser.next();
parser.next();
serverName = parser.nextText();
// Parse the port
parser.next();
parser.next();
try {
serverPort = Integer.parseInt(parser.nextText());
}
catch (Exception e) {
}
// If we have a valid hostname and port, add
// it to the list.
if (serverName != null && serverPort != -1) {
STUNService service = new STUNService(serverName, serverPort);
serversList.add(service);
}
}
}
eventType = parser.next();
}
while (eventType != XmlPullParser.END_DOCUMENT);
}
catch (XmlPullParserException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
currentServer = bestSTUNServer(serversList);
return serversList;
}
/**
* Load a list of services: STUN servers and ports. Some public STUN servers
* are:
* <p/>
* <pre>
* iphone-stun.freenet.de:3478
* larry.gloo.net:3478
* stun.xten.net:3478
* stun.fwdnet.net
* stun.fwd.org (no DNS SRV record)
* stun01.sipphone.com (no DNS SRV record)
* stun.softjoys.com (no DNS SRV record)
* stun.voipbuster.com (no DNS SRV record)
* stun.voxgratia.org (no DNS SRV record)
* stun.noc.ams-ix.net
* </pre>
* <p/>
* This list should be contained in a file in the "META-INF" directory
*
* @return a list of services
*/
public ArrayList loadSTUNServers() {
ArrayList serversList = new ArrayList();
// Load the STUN configuration
try {
// Get an array of class loaders to try loading the config from.
ClassLoader[] classLoaders = new ClassLoader[2];
classLoaders[0] = new STUNResolver() {
}.getClass().getClassLoader();
classLoaders[1] = Thread.currentThread().getContextClassLoader();
for (int i = 0; i < classLoaders.length; i++) {
Enumeration stunConfigEnum = classLoaders[i]
.getResources(STUNSERVERS_FILENAME);
while (stunConfigEnum.hasMoreElements() && serversList.isEmpty()) {
URL url = (URL) stunConfigEnum.nextElement();
java.io.InputStream stunConfigStream = null;
stunConfigStream = url.openStream();
serversList.addAll(loadSTUNServers(stunConfigStream));
stunConfigStream.close();
}
}
}
catch (Exception e) {
e.printStackTrace();
}
// If the list of candidates is empty, add at least one default server
if (serversList.isEmpty()) {
currentServer = new STUNService(FALLBACKHOSTNAME, FALLBACKHOSTPORT);
serversList.add(currentServer);
}
return serversList;
}
/**
* Get the best usable STUN server from a list.
*
* @return the best STUN server that can be used.
*/
private STUNService bestSTUNServer(ArrayList listServers) {
if (listServers.isEmpty()) {
return null;
} else {
// TODO: this should use some more advanced criteria...
return (STUNService) listServers.get(0);
}
}
/**
* Resolve the IP and obtain a valid transport method.
*/
public synchronized void resolve() throws XMPPException {
setResolveInit();
clearCandidates();
TransportCandidate candidate = new TransportCandidate.Fixed(
resolvedPublicIP, getFreePort());
candidate.setLocalIp(resolvedLocalIP);
System.out.println("RESOLVING : " + resolvedPublicIP + ":" + candidate.getPort());
addCandidate(candidate);
setResolveEnd();
}
/**
* Initialize the resolver.
*
* @throws XMPPException
*/
public void initialize() throws XMPPException {
System.out.println("Initialized");
if (!isResolving()&&!isResolved()) {
// Get the best STUN server available
if (currentServer.isNull()) {
loadSTUNServers();
}
// We should have a valid STUN server by now...
if (!currentServer.isNull()) {
clearCandidates();
resolverThread = new Thread(new Runnable() {
public void run() {
// Iterate through the list of interfaces, and ask
// to the STUN server for our address.
try {
Enumeration ifaces = NetworkInterface.getNetworkInterfaces();
String candAddress;
int candPort;
while (ifaces.hasMoreElements()) {
NetworkInterface iface = (NetworkInterface) ifaces
.nextElement();
Enumeration iaddresses = iface.getInetAddresses();
while (iaddresses.hasMoreElements()) {
InetAddress iaddress = (InetAddress) iaddresses
.nextElement();
if (!iaddress.isLoopbackAddress()
&& !iaddress.isLinkLocalAddress()) {
// Reset the candidate
candAddress = null;
candPort = -1;
DiscoveryTest test = new DiscoveryTest(iaddress,
currentServer.getHostname(),
currentServer.getPort());
try {
// Run the tests and get the
// discovery
// information, where all the
// info is stored...
DiscoveryInfo di = test.test();
candAddress = di.getPublicIP() != null ?
di.getPublicIP().getHostAddress() : null;
// Get a valid port
if (defaultPort == 0) {
candPort = getFreePort();
} else {
candPort = defaultPort;
}
// If we have a valid candidate,
// add it to the list.
if (candAddress != null && candPort >= 0) {
TransportCandidate candidate = new TransportCandidate.Fixed(
candAddress, candPort);
candidate.setLocalIp(iaddress.getHostAddress() != null ? iaddress.getHostAddress() : iaddress.getHostName());
addCandidate(candidate);
resolvedPublicIP = candidate.getIp();
resolvedLocalIP = candidate.getLocalIp();
return;
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
catch (SocketException e) {
e.printStackTrace();
}
finally {
setInitialized();
}
}
}, "Waiting for all the transport candidates checks...");
resolverThread.setName("STUN resolver");
resolverThread.start();
} else {
throw new IllegalStateException("No valid STUN server found.");
}
}
}
/**
* Cancel any operation.
*
* @see TransportResolver#cancel()
*/
public synchronized void cancel() throws XMPPException {
if (isResolving()) {
resolverThread.interrupt();
setResolveEnd();
}
}
/**
* Clear the list of candidates and start the resolution again.
*
* @see TransportResolver#clear()
*/
public synchronized void clear() throws XMPPException {
this.defaultPort = 0;
super.clear();
}
/**
* STUN service definition.
*/
protected class STUNService {
private String hostname; // The hostname of the service
private int port; // The port number
/**
* Basic constructor, with the hostname and port
*
* @param hostname The hostname
* @param port The port
*/
public STUNService(String hostname, int port) {
super();
this.hostname = hostname;
this.port = port;
}
/**
* Default constructor, without name and port.
*/
public STUNService() {
this(null, -1);
}
/**
* Get the host name of the STUN service.
*
* @return The host name
*/
public String getHostname() {
return hostname;
}
/**
* Set the hostname of the STUN service.
*
* @param hostname The host name of the service.
*/
public void setHostname(String hostname) {
this.hostname = hostname;
}
/**
* Get the port of the STUN service
*
* @return The port number where the STUN server is waiting.
*/
public int getPort() {
return port;
}
/**
* Set the port number for the STUN service.
*
* @param port The port number.
*/
public void setPort(int port) {
this.port = port;
}
/**
* Basic format test: the service is not null.
*
* @return true if the hostname and port are null
*/
public boolean isNull() {
if (hostname == null) {
return true;
} else if (hostname.length() == 0) {
return true;
} else if (port < 0) {
return true;
} else {
return false;
}
}
/**
* Check a binding with the STUN currentServer.
* <p/>
* Note: this function blocks for some time, waiting for a response.
*
* @return true if the currentServer is usable.
*/
public boolean checkBinding() {
boolean result = false;
try {
BindingLifetimeTest binding = new BindingLifetimeTest(hostname, port);
binding.test();
while (true) {
Thread.sleep(5000);
if (binding.getLifetime() != -1) {
if (binding.isCompleted()) {
return true;
}
} else {
break;
}
}
}
catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
}

View file

@ -0,0 +1,51 @@
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.XMPPException;
/**
* $RCSfile$
* $Revision: $
* $Date: 15/11/2006
*
* Copyright 2003-2006 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* A Jingle Transport Manager implementation to be used on NAT networks with STUN Service NOT Blocked.
*
* @author Thiago Camargo
*/
public class STUNTransportManager extends JingleTransportManager {
STUNResolver stunResolver = null;
public STUNTransportManager() {
stunResolver = new STUNResolver() {
};
try {
stunResolver.initializeAndWait();
} catch (XMPPException e) {
e.printStackTrace();
}
}
protected TransportResolver createResolver() {
try {
stunResolver.resolve();
} catch (XMPPException e) {
e.printStackTrace();
}
return stunResolver;
}
}

View file

@ -0,0 +1,855 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2002-2006 Jive Software. All rights reserved.
* ====================================================================
* The Jive Software License (based on Apache Software License, Version 1.1)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by
* Jive Software (http://www.jivesoftware.com)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Smack" and "Jive Software" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please
* contact webmaster@jivesoftware.com.
*
* 5. Products derived from this software may not be called "Smack",
* nor may "Smack" appear in their name, without prior written
* permission of Jive Software.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*/
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.XMPPConnection;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
/**
* Transport candidate.
*
* A candidate represents the possible transport for data interchange between
* the two endpoints.
*
* @author Thiago Camargo
* @author Alvaro Saurin
*/
public abstract class TransportCandidate {
private String name;
private String ip; // IP address
private int port; // Port to use, or 0 for any port
private String localIp;
private int generation;
protected String password;
private String sessionId;
private XMPPConnection connection;
private TransportCandidate symmetric;
// Listeners for events
private final List<TransportResolverListener.Checker> listeners = new ArrayList();
public String getIp() {
return ip;
}
/**
* Set the IP address.
*
* @param ip the IP address
*/
public void setIp(String ip) {
this.ip = ip;
}
/**
* Get local IP to bind to this candidate
*
* @return
*/
public String getLocalIp() {
return localIp == null ? ip : localIp;
}
/**
* Set local IP to bind to this candidate
*
* @param localIp
*/
public void setLocalIp(String localIp) {
this.localIp = localIp;
}
/**
* Get the symetric candidate for this candidate if it exists.
*
* @return
*/
public TransportCandidate getSymmetric() {
return symmetric;
}
/**
* Set the symetric candidate for this candidate.
*
* @param symetric
*/
public void setSymmetric(TransportCandidate symetric) {
this.symmetric = symetric;
}
/**
* Get the password used by ICE or relayed candidate
*
* @return a password
*/
public String getPassword() {
return password;
}
/**
* Set the password used by ICE or relayed candidate
*
* @param password a password
*/
public void setPassword(String password) {
this.password = password;
}
/**
* Get the XMPPConnection use to send or receive this candidate
*
* @return
*/
public XMPPConnection getConnection() {
return connection;
}
/**
* Set the XMPPConnection use to send or receive this candidate
*
* @param connection
*/
public void setConnection(XMPPConnection connection) {
this.connection = connection;
}
/**
* Get the jingle´s sessionId that is using this candidate
*
* @return
*/
public String getSessionId() {
return sessionId;
}
/**
* Set the jingle´s sessionId that is using this candidate
*
* @param sessionId
*/
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
/**
* Empty constructor
*/
public TransportCandidate() {
this(null, 0, 0);
}
/**
* Constructor with IP address and port
*
* @param ip The IP address.
* @param port The port number.
*/
public TransportCandidate(String ip, int port) {
this(ip, port, 0);
}
/**
* Constructor with IP address and port
*
* @param ip The IP address.
* @param port The port number.
* @param generation The generation
*/
public TransportCandidate(String ip, int port, int generation) {
this.ip = ip;
this.port = port;
this.generation = generation;
}
/**
* Return true if the candidate is not valid.
*
* @return true if the candidate is null.
*/
public boolean isNull() {
if (ip == null) {
return true;
} else if (ip.length() == 0) {
return true;
} else if (port < 0) {
return true;
} else {
return false;
}
}
/**
* Get the port, or 0 for any port.
*
* @return the port or 0
*/
public int getPort() {
return port;
}
/**
* Set the port, using 0 for any port
*
* @param port the port
*/
public void setPort(int port) {
this.port = port;
}
/**
* Get the generation for a transportElement definition
*
* @return the generation
*/
public int getGeneration() {
return generation;
}
/**
* Set the generation for a transportElement definition.
*
* @param generation the generation number
*/
public void setGeneration(int generation) {
this.generation = generation;
}
/**
* Get the name used for identifying this transportElement method (optional)
*
* @return a name used for identifying this transportElement (ie,
* "myrtpvoice1")
*/
public String getName() {
return name;
}
/**
* Set a name for identifying this transportElement.
*
* @param name the name used for the transportElement
*/
public void setName(String name) {
this.name = name;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final TransportCandidate other = (TransportCandidate) obj;
if (generation != other.generation) {
return false;
}
if (getIp() == null) {
if (other.getIp() != null) {
return false;
}
} else if (!getIp().equals(other.getIp())) {
return false;
}
if (getName() == null) {
if (other.getName() != null) {
return false;
}
} else if (!getName().equals(other.getName())) {
return false;
}
if (getPort() != other.getPort()) {
return false;
}
return true;
}
/**
* Check if a transport candidate is usable. The transport resolver should
* check if the transport candidate the other endpoint has provided is
* usable.
* <p/>
* This method provides a basic check where it sends a "ping" to the remote
* address provided in the candidate. If the "ping" succedess, the candidate
* is accepted. Subclasses should provide better methods if they can...
*/
public void check() {
//TODO candidate is being checked trigger
//candidatesChecking.add(cand);
Thread checkThread = new Thread(new Runnable() {
public void run() {
boolean isUsable;
InetAddress candAddress;
try {
candAddress = InetAddress.getByName(getIp());
isUsable = true;//candAddress.isReachable(CHECK_TIMEOUT);
} catch (Exception e) {
isUsable = false;
}
triggerCandidateChecked(isUsable);
//TODO candidate is being checked trigger
//candidatesChecking.remove(cand);
}
}, "Transport candidate check");
checkThread.setName("Transport candidate test");
checkThread.start();
}
/**
* Trigger a new candidate checked event.
*
* @param result The result.
*/
private void triggerCandidateChecked(boolean result) {
for (TransportResolverListener.Checker trl : getListenersList()) {
trl.candidateChecked(this, result);
}
}
/**
* Get the list of listeners
*
* @return the list of listeners
*/
public List<TransportResolverListener.Checker> getListenersList() {
synchronized (listeners) {
return new ArrayList(listeners);
}
}
/**
* Add a transport resolver listener.
*
* @param li The transport resolver listener to be added.
*/
public void addListener(TransportResolverListener.Checker li) {
synchronized (listeners) {
listeners.add(li);
}
}
/**
* Fixed transport candidate
*/
public static class Fixed extends TransportCandidate {
public Fixed() {
super();
}
/**
* Constructor with IP address and port
*
* @param ip The IP address.
* @param port The port number.
*/
public Fixed(String ip, int port) {
super(ip, port);
}
/**
* Constructor with IP address and port
*
* @param ip The IP address.
* @param port The port number.
* @param generation The generation
*/
public Fixed(String ip, int port, int generation) {
super(ip, port, generation);
}
}
/**
* Ice candidate.
*/
public static class Ice extends TransportCandidate implements Comparable {
private String id; // An identification
private String username;
private int preference;
private Protocol proto;
private Channel channel;
private int network;
public Ice() {
super();
}
/**
* Constructor with the basic elements of a transport definition.
*
* @param ip the IP address to use as a local address
* @param generation used to keep track of the candidates
* @param network used for diagnostics (used when the machine has
* several NICs)
* @param password user name, as it is used in ICE
* @param port the port at the candidate IP address
* @param username user name, as it is used in ICE
* @param preference preference for this transportElement, as it is used
* in ICE
*/
public Ice(String ip, int generation, int network,
String password, int port, String username,
int preference) {
super(ip, port, generation);
proto = Protocol.UDP;
channel = Channel.MYRTPVOICE;
this.network = network;
this.password = password;
this.username = username;
this.preference = preference;
}
/**
* Get the ID
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Set the ID
*
* @param id the id to set
*/
public void setId(String id) {
this.id = id;
}
/**
* Get the protocol used for the transmission
*
* @return the protocol used for transmission
*/
public Protocol getProto() {
return proto;
}
/**
* Set the protocol for the transmission
*
* @param proto the protocol to use
*/
public void setProto(Protocol proto) {
this.proto = proto;
}
/**
* Get the network interface used for this connection
*
* @return the interface number
*/
public int getNetwork() {
return network;
}
/**
* Set the interface for this connection
*
* @param network the interface number
*/
public void setNetwork(int network) {
this.network = network;
}
/**
* Get the username for this transportElement in ICE
*
* @return a username string
*/
public String getUsername() {
return username;
}
/**
* Get the channel
*
* @return the channel associated
*/
public Channel getChannel() {
return channel;
}
/**
* Set the channel for this transportElement
*
* @param channel the new channel
*/
public void setChannel(Channel channel) {
this.channel = channel;
}
/**
* Set the username for this transportElement in ICE
*
* @param username the username used in ICE
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Get the preference number for this transportElement
*
* @return the preference for this transportElement
*/
public int getPreference() {
return preference;
}
/**
* Set the preference order for this transportElement
*
* @param preference a number identifying the preference (as defined in
* ICE)
*/
public void setPreference(int preference) {
this.preference = preference;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Ice other = (Ice) obj;
if (getChannel() == null) {
if (other.getChannel() != null) {
return false;
}
} else if (!getChannel().equals(other.getChannel())) {
return false;
}
if (getId() == null) {
if (other.getId() != null) {
return false;
}
} else if (!getId().equals(other.getId())) {
return false;
}
if (getNetwork() != other.getNetwork()) {
return false;
}
if (getPassword() == null) {
if (other.getPassword() != null) {
return false;
}
} else if (!getPassword().equals(other.password)) {
return false;
}
if (getPreference() != other.getPreference()) {
return false;
}
if (getProto() == null) {
if (other.getProto() != null) {
return false;
}
} else if (!getProto().equals(other.getProto())) {
return false;
}
if (getUsername() == null) {
if (other.getUsername() != null) {
return false;
}
} else if (!getUsername().equals(other.getUsername())) {
return false;
}
return true;
}
public boolean isNull() {
if (super.isNull()) {
return true;
} else if (getProto().isNull()) {
return true;
} else if (getChannel().isNull()) {
return true;
}
return false;
}
/**
* Compare the to other Transport candidate.
*
* @param arg another Transport candidate
* @return a negative integer, zero, or a positive integer as this
* object is less than, equal to, or greater than the specified
* object
*/
public int compareTo(Object arg) {
if (arg instanceof TransportCandidate.Ice) {
TransportCandidate.Ice tc = (TransportCandidate.Ice) arg;
if (getPreference() < tc.getPreference()) {
return -1;
} else if (getPreference() > tc.getPreference()) {
return 1;
}
}
return 0;
}
}
/**
* Type-safe enum for the transportElement protocol
*/
public static class Protocol {
public static final Protocol UDP = new Protocol("udp");
public static final Protocol TCP = new Protocol("tcp");
public static final Protocol TCPACT = new Protocol("tcp-act");
public static final Protocol TCPPASS = new Protocol("tcp-pass");
public static final Protocol SSLTCP = new Protocol("ssltcp");
private String value;
public Protocol(String value) {
this.value = value;
}
public String toString() {
return value;
}
/**
* Returns the Protocol constant associated with the String value.
*/
public static Protocol fromString(String value) {
if (value == null) {
return UDP;
}
value = value.toLowerCase();
if (value.equals("udp")) {
return UDP;
} else if (value.equals("tcp")) {
return TCP;
} else if (value.equals("tcp-act")) {
return TCPACT;
} else if (value.equals("tcp-pass")) {
return TCPPASS;
} else if (value.equals("ssltcp")) {
return SSLTCP;
} else {
return UDP;
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Protocol other = (Protocol) obj;
if (value == null) {
if (other.value != null) {
return false;
}
} else if (!value.equals(other.value)) {
return false;
}
return true;
}
/**
* Return true if the protocol is not valid.
*
* @return true if the protocol is null
*/
public boolean isNull() {
if (value == null) {
return true;
} else if (value.length() == 0) {
return true;
} else {
return false;
}
}
}
/**
* Type-safe enum for the transportElement channel
*/
public static class Channel {
public static final Channel MYRTPVOICE = new Channel("myrtpvoice");
public static final Channel MYRTCPVOICE = new Channel("myrtcpvoice");
private String value;
public Channel(String value) {
this.value = value;
}
public String toString() {
return value;
}
/**
* Returns the MediaChannel constant associated with the String value.
*/
public static Channel fromString(String value) {
if (value == null) {
return MYRTPVOICE;
}
value = value.toLowerCase();
if (value.equals("myrtpvoice")) {
return MYRTPVOICE;
} else if (value.equals("tcp")) {
return MYRTCPVOICE;
} else {
return MYRTPVOICE;
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Channel other = (Channel) obj;
if (value == null) {
if (other.value != null) {
return false;
}
} else if (!value.equals(other.value)) {
return false;
}
return true;
}
/**
* Return true if the channel is not valid.
*
* @return true if the channel is null
*/
public boolean isNull() {
if (value == null) {
return true;
} else if (value.length() == 0) {
return true;
} else {
return false;
}
}
}
}

View file

@ -0,0 +1,842 @@
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.jingle.JingleNegotiator;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.listeners.JingleListener;
import org.jivesoftware.smackx.jingle.listeners.JingleTransportListener;
import org.jivesoftware.smackx.packet.Jingle;
import org.jivesoftware.smackx.packet.JingleTransport.JingleTransportCandidate;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* Transport negotiator.
*
*
* This class is responsible for managing the transport negotiation process,
* handling all the packet interchange and the stage control.
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public abstract class TransportNegotiator extends JingleNegotiator {
// The time we give to the candidates check before we accept or decline the
// transport (in milliseconds)
private final static int CANDIDATES_ACCEPT_PERIOD = 4000;
// The session this nenotiator belongs to
private final JingleSession session;
// The transport manager
private final TransportResolver resolver;
// Transport candidates we have offered
private final List offeredCandidates = new ArrayList();
// List of remote transport candidates
private final List remoteCandidates = new ArrayList();
// Valid remote candidates
private final List validRemoteCandidates = new ArrayList();
// The best local candidate we have offered (and accepted by the other part)
private TransportCandidate acceptedLocalCandidate;
// The thread that will report the result to the other end
private Thread resultThread;
// Listener for the resolver
private TransportResolverListener.Resolver resolverListener;
// states
private final Inviting inviting;
private final Accepting accepting;
private final Pending pending;
private final Active active;
/**
* Default constructor.
*
* @param js The Jingle session
* @param transResolver The JingleTransportManager to use
*/
public TransportNegotiator(JingleSession js,
TransportResolver transResolver) {
super(js.getConnection());
session = js;
resolver = transResolver;
resultThread = null;
// Create the states...
inviting = new Inviting(this);
accepting = new Accepting(this);
pending = new Pending(this);
active = new Active(this);
}
/**
* Get a new instance of the right TransportNegotiator class with this
* candidate.
*
* @return A TransportNegotiator instance
*/
public abstract org.jivesoftware.smackx.packet.JingleTransport getJingleTransport(TransportCandidate cand);
/**
* Return true if the transport candidate is acceptable for the current
* negotiator.
*
* @return true if the transport candidate is acceptable
*/
public abstract boolean acceptableTransportCandidate(TransportCandidate tc);
/**
* Obtain the best local candidate we want to offer.
*
* @return the best local candidate
*/
public final TransportCandidate getBestLocalCandidate() {
return resolver.getPreferredCandidate();
}
/**
* Set the best local transport candidate we have offered and accepted by
* the other endpoint.
*
* @param bestLocalCandidate the acceptedLocalCandidate to set
*/
private void setAcceptedLocalCandidate(TransportCandidate bestLocalCandidate)
throws XMPPException {
for (int i = 0; i < resolver.getCandidateCount(); i++) {
//TODO FIX The EQUAL Sentence
if (resolver.getCandidate(i).getIp().equals(bestLocalCandidate.getIp())) {
acceptedLocalCandidate = resolver.getCandidate(i);
return;
}
}
//System.out.println("BEST: " + bestLocalCandidate.getIp());
throw new XMPPException("Local transport candidate has not be offered.");
}
/**
* Get the best accepted local candidate we have offered.
*
* @return a transport candidate we have offered.
*/
public TransportCandidate getAcceptedLocalCandidate() {
return acceptedLocalCandidate;
}
/**
* Obtain the best common transport candidate obtained in the negotiation.
*
* @return the bestRemoteCandidate
*/
public abstract TransportCandidate getBestRemoteCandidate();
/**
* Get the list of remote candidates.
*
* @return the remoteCandidates
*/
private List getRemoteCandidates() {
return remoteCandidates;
}
/**
* Add a remote candidate to the list. The candidate will be checked in
* order to verify if it is usable.
*
* @param rc a remote candidate to add and check.
*/
private void addRemoteCandidate(TransportCandidate rc) {
// Add the candidate to the list
if (rc != null) {
if (acceptableTransportCandidate(rc)) {
synchronized (remoteCandidates) {
remoteCandidates.add(rc);
}
// Check if the new candidate can be used.
checkRemoteCandidate(rc);
}
}
}
/**
* Add a offered candidate to the list.
*
* @param rc a remote candidate we have offered.
*/
private void addOfferedCandidate(TransportCandidate rc) {
// Add the candidate to the list
if (rc != null) {
synchronized (offeredCandidates) {
offeredCandidates.add(rc);
}
}
}
/**
* Check asynchronously the new transport candidate.
*
* @param offeredCandidate a transport candidates to check
*/
private void checkRemoteCandidate(final TransportCandidate offeredCandidate) {
System.out.println("CHECK");
offeredCandidate.addListener(new TransportResolverListener.Checker() {
public void candidateChecked(TransportCandidate cand,
final boolean validCandidate) {
if (validCandidate) {
addValidRemoteCandidate(offeredCandidate);
}
}
public void candidateChecking(TransportCandidate cand) {
}
});
offeredCandidate.check();
}
/**
* Return true if the transport is established.
*
* @return true if the transport is established.
*/
private boolean isEstablished() {
return getBestRemoteCandidate() != null && getAcceptedLocalCandidate() != null;
}
/**
* Return true if the transport is fully established.
*
* @return true if the transport is fully established.
*/
public final boolean isFullyEstablished() {
return isEstablished() && getState() == active;
}
/**
* Launch a thread that checks, after some time, if any of the candidates
* offered by the other endpoint is usable. The thread does not check the
* candidates: it just checks if we have got a valid one and sends an Accept
* in that case.
*/
private void delayedCheckBestCandidate(final JingleSession js, final Jingle jin) {
//
// If this is the first insertion in the list, start the thread that
// will send the result of our checks...
//
if (resultThread == null && !getRemoteCandidates().isEmpty()) {
resultThread = new Thread(new Runnable() {
public void run() {
// Sleep for some time, waiting for the candidates checks
try {
Thread.sleep(CANDIDATES_ACCEPT_PERIOD
+ TransportResolver.CHECK_TIMEOUT);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Once we are in pending state, look for any valid remote
// candidate, and send an "accept" if we have one...
TransportCandidate bestRemote = getBestRemoteCandidate();
State state = getState();
if (bestRemote != null && (state == pending || state == active)) {
// Accepting the remote candidate
Jingle jout = new Jingle(Jingle.Action.TRANSPORTACCEPT);
jout.addTransport(getJingleTransport(bestRemote));
// Send the packet
js.sendFormattedJingle(jin, jout);
if (isEstablished()) {
setState(active);
}
}
}
}, "Waiting for all the transport candidates checks...");
resultThread.setName("Transport Resolver Result");
resultThread.start();
}
}
/**
* Add a valid remote candidate to the list. The remote candidate has been
* checked, and the remote
*
* @param remoteCandidate a remote candidate to add
*/
private void addValidRemoteCandidate(TransportCandidate remoteCandidate) {
// Add the candidate to the list
if (remoteCandidate != null) {
synchronized (validRemoteCandidates) {
System.out.println("ADDED Valid Cand: " + remoteCandidate.getIp());
validRemoteCandidates.add(remoteCandidate);
}
}
}
/**
* Get the list of valid (ie, checked) remote candidates.
*
* @return The list of valid (ie, already checked) remote candidates.
*/
final ArrayList getValidRemoteCandidatesList() {
synchronized (validRemoteCandidates) {
return new ArrayList(validRemoteCandidates);
}
}
/**
* Get an iterator for the list of valid (ie, checked) remote candidates.
*
* @return The iterator for the list of valid (ie, already checked) remote
* candidates.
*/
public final Iterator getValidRemoteCandidates() {
return Collections.unmodifiableList(getRemoteCandidates()).iterator();
}
/**
* Add an offered remote candidate. The transport candidate can be unusable:
* we must check if we can use it.
*
* @param rc the remote candidate to add.
*/
private void addRemoteCandidates(List rc) {
if (rc != null) {
System.out.println("SIZE OF LISTA: " + rc.size());
if (rc.size() > 0) {
for (Object aRc : rc) {
addRemoteCandidate((TransportCandidate) aRc);
}
}
}
}
/**
* Parse the list of transport candidates from a Jingle packet.
*
* @param jin The input jingle packet
*/
private static ArrayList obtainCandidatesList(Jingle jin) {
ArrayList result = new ArrayList();
if (jin != null) {
// Get the list of candidates from the packet
Iterator iTrans = jin.getTransports();
while (iTrans.hasNext()) {
org.jivesoftware.smackx.packet.JingleTransport trans = (org.jivesoftware.smackx.packet.JingleTransport) iTrans.next();
System.out.println("LISTA SIZE: " + trans.getCandidatesCount());
Iterator iCand = trans.getCandidates();
while (iCand.hasNext()) {
JingleTransportCandidate cand = (JingleTransportCandidate) iCand
.next();
TransportCandidate transCand = cand.getMediaTransport();
result.add(transCand);
}
}
}
return result;
}
private boolean isOfferStarted() {
return resolver.isResolving() || resolver.isResolved();
}
/**
* Send an offer for a transport candidate
*
* @param cand
*/
private synchronized void sendTransportCandidateOffer(TransportCandidate cand) {
if (!cand.isNull()) {
// Offer our new candidate...
addOfferedCandidate(cand);
session.sendFormattedJingle(new Jingle(getJingleTransport(cand)));
}
}
/**
* Create a Jingle packet where we announce our transport candidates.
*
* @throws XMPPException
*/
private void sendTransportCandidatesOffer() throws XMPPException {
List notOffered = resolver.getCandidatesList();
notOffered.removeAll(offeredCandidates);
// Send any unset candidate
for (Object aNotOffered : notOffered) {
sendTransportCandidateOffer((TransportCandidate) aNotOffered);
}
// .. and start a listener that will send any future candidate
if (resolverListener == null) {
// Add a listener that sends the offer when the resolver finishes...
resolverListener = new TransportResolverListener.Resolver() {
public void candidateAdded(TransportCandidate cand) {
sendTransportCandidateOffer(cand);
}
public void end() {
}
public void init() {
}
};
resolver.addListener(resolverListener);
}
if (!(resolver.isResolving() || resolver.isResolved())) {
// Resolve our IP and port
System.out.println("RESOLVER CALLED");
resolver.resolve();
}
}
/**
* Dispatch an incoming packet. The method is responsible for recognizing
* the packet type and, depending on the current state, deliverying the
* packet to the right event handler and wait for a response.
*
* @param iq the packet received
* @return the new Jingle packet to send.
* @throws XMPPException
*/
public final IQ dispatchIncomingPacket(IQ iq, String id) throws XMPPException {
IQ jout = null;
if (invalidState()) {
if (iq == null) {
// With a null packet, we are just inviting the other end...
setState(inviting);
jout = getState().eventInvite();
} else {
if (iq instanceof Jingle) {
// If there is no specific jmf action associated, then we
// are being invited to a new session...
setState(accepting);
jout = getState().eventInitiate((Jingle) iq);
} else {
throw new IllegalStateException(
"Invitation IQ received is not a Jingle packet in Transport negotiator.");
}
}
} else {
if (iq == null) {
return null;
} else {
if (iq.getType().equals(IQ.Type.ERROR)) {
// Process errors
getState().eventError(iq);
} else if (iq.getType().equals(IQ.Type.RESULT)) {
// Process ACKs
if (isExpectedId(iq.getPacketID())) {
jout = getState().eventAck(iq);
removeExpectedId(iq.getPacketID());
}
} else if (iq instanceof Jingle) {
// Get the action from the Jingle packet
Jingle jin = (Jingle) iq;
Jingle.Action action = jin.getAction();
if (action != null) {
if (action.equals(Jingle.Action.TRANSPORTACCEPT)) {
jout = getState().eventAccept(jin);
} else if (action.equals(Jingle.Action.TRANSPORTDECLINE)) {
jout = getState().eventDecline(jin);
} else if (action.equals(Jingle.Action.TRANSPORTINFO)) {
jout = getState().eventInfo(jin);
} else if (action.equals(Jingle.Action.TRANSPORTMODIFY)) {
jout = getState().eventModify(jin);
}
}
}
}
}
// Save the Id for any ACK
if (id != null) {
addExpectedId(id);
} else {
if (jout != null) {
addExpectedId(jout.getPacketID());
}
}
return jout;
}
/**
* Trigger a Transport session established event.
*
* @param local TransportCandidate that has been agreed.
* @param remote TransportCandidate that has been agreed.
*/
private void triggerTransportEstablished(TransportCandidate local,
TransportCandidate remote) {
ArrayList listeners = getListenersList();
for (Object listener : listeners) {
JingleListener li = (JingleListener) listener;
if (li instanceof JingleTransportListener) {
JingleTransportListener mli = (JingleTransportListener) li;
System.out.println("triggerTransportEstablished " + local.getLocalIp());
mli.transportEstablished(local, remote);
}
}
}
/**
* Trigger a Transport closed event.
*
* @param cand current TransportCandidate that is cancelled.
*/
private void triggerTransportClosed(TransportCandidate cand) {
ArrayList listeners = getListenersList();
for (Object listener : listeners) {
JingleListener li = (JingleListener) listener;
if (li instanceof JingleTransportListener) {
JingleTransportListener mli = (JingleTransportListener) li;
mli.transportClosed(cand);
}
}
}
// States
/**
* First stage when we send a session request.
*/
public final class Inviting extends JingleNegotiator.State {
public Inviting(TransportNegotiator neg) {
super(neg);
}
/**
* Create an initial Jingle packet with an empty transport.
*/
public Jingle eventInvite() {
return new Jingle(getJingleTransport(null));
}
/**
* We have received some candidates. This can happen _before_ the ACK
* has been recieved...
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventInfo(org.jivesoftware.smackx.packet.Jingle)
*/
public Jingle eventInfo(Jingle jin) throws XMPPException {
// Parse the Jingle and get any proposed transport candidates
addRemoteCandidates(obtainCandidatesList(jin));
// Wait for some time and check if we have a valid candidate to
// use...
delayedCheckBestCandidate(session, jin);
return null;// super.eventInfo(jin);
}
/**
* The other endpoint has partially accepted our invitation: start
* offering a list of candidates.
*
* @return an IQ packet
* @throws XMPPException
*/
public Jingle eventAck(IQ iq) throws XMPPException {
sendTransportCandidatesOffer();
setState(pending);
return super.eventAck(iq);
}
}
/**
* We are accepting connections. This is the starting state when we accept a
* connection...
*/
public final class Accepting extends JingleNegotiator.State {
public Accepting(TransportNegotiator neg) {
super(neg);
}
/**
* We have received an invitation. The packet will be ACKed by lower
* levels...
*/
public Jingle eventInitiate(Jingle jin) throws XMPPException {
// Parse the Jingle and get any proposed transport candidates
//addRemoteCandidates(obtainCandidatesList(jin));
// Start offering candidates
sendTransportCandidatesOffer();
// All these candidates will be checked asyncronously. Wait for some
// time and check if we have a valid candidate to use...
delayedCheckBestCandidate(session, jin);
// Set the next state
setState(pending);
return super.eventInitiate(jin);
}
}
/**
* We are still receiving candidates
*/
public final class Pending extends JingleNegotiator.State {
public Pending(TransportNegotiator neg) {
super(neg);
}
/**
* One of our transport candidates has been accepted.
*
* @param jin The input packet
* @return a Jingle packet
* @throws XMPPException an exception
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventAccept(org.jivesoftware.smackx.packet.Jingle)
*/
public Jingle eventAccept(Jingle jin) throws XMPPException {
Jingle response = null;
// Parse the Jingle and get the accepted candidate
ArrayList accepted = obtainCandidatesList(jin);
if (!accepted.isEmpty()) {
for (TransportCandidate cand : (List<TransportCandidate>) accepted) {
System.out.println("Cand: " + cand.getIp());
}
TransportCandidate cand = (TransportCandidate) accepted.get(0);
setAcceptedLocalCandidate(cand);
if (isEstablished()) {
System.out.println("SET ACTIVE");
setState(active);
}
}
return response;
}
/**
* We have received another remote transport candidates.
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventInfo(org.jivesoftware.smackx.packet.Jingle)
*/
public Jingle eventInfo(Jingle jin) throws XMPPException {
sendTransportCandidatesOffer();
// Parse the Jingle and get any proposed transport candidates
addRemoteCandidates(obtainCandidatesList(jin));
// Wait for some time and check if we have a valid candidate to
// use...
delayedCheckBestCandidate(session, jin);
return null;//super.eventInfo(jin);
}
/**
* None of our transport candidates has been accepted...
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventDecline(org.jivesoftware.smackx.packet.Jingle)
*/
public Jingle eventDecline(Jingle inJingle) throws JingleException {
throw new JingleException("No common payload found.");
}
}
/**
* "Active" state: we have an agreement about the codec...
*/
public final class Active extends JingleNegotiator.State {
public Active(TransportNegotiator neg) {
super(neg);
}
/**
* We have an agreement.
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventEnter()
*/
public void eventEnter() {
System.out.println("Transport stabilished");
triggerTransportEstablished(getAcceptedLocalCandidate(),
getBestRemoteCandidate());
super.eventEnter();
}
/**
* We have finished the transport.
*
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventEnter()
*/
public void eventExit() {
triggerTransportClosed(null);
super.eventExit();
}
}
// Subclasses
/**
* Raw-UDP transport negotiator
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public static final class RawUdp extends TransportNegotiator {
/**
* Default constructor, with a JingleSession and transport manager.
*
* @param js The Jingle session this negotiation belongs to.
* @param res The transport resolver to use.
*/
public RawUdp(JingleSession js, final TransportResolver res) {
super(js, res);
}
/**
* Get a TransportNegotiator instance.
*/
public org.jivesoftware.smackx.packet.JingleTransport getJingleTransport(TransportCandidate bestRemote) {
org.jivesoftware.smackx.packet.JingleTransport.RawUdp jt = new org.jivesoftware.smackx.packet.JingleTransport.RawUdp();
jt.addCandidate(new org.jivesoftware.smackx.packet.JingleTransport.RawUdp.Candidate(bestRemote));
return jt;
}
/**
* Obtain the best common transport candidate obtained in the
* negotiation.
*
* @return the bestRemoteCandidate
*/
public TransportCandidate getBestRemoteCandidate() {
// Hopefully, we only have one validRemoteCandidate
ArrayList cands = getValidRemoteCandidatesList();
if (!cands.isEmpty()) {
return (TransportCandidate) cands.get(0);
} else {
System.out.println("No Remote Candidate");
return null;
}
}
/**
* Return true for fixed candidates.
*/
public boolean acceptableTransportCandidate(TransportCandidate tc) {
return tc instanceof TransportCandidate.Fixed;
}
}
/**
* Ice transport negotiator.
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public static final class Ice extends TransportNegotiator {
/**
* Default constructor, with a JingleSession and transport manager.
*
* @param js The Jingle session this negotiation belongs to.
* @param res The transport manager to use.
*/
public Ice(JingleSession js, final TransportResolver res) {
super(js, res);
}
/**
* Get a TransportNegotiator instance.
*
* @param candidate
*/
public org.jivesoftware.smackx.packet.JingleTransport getJingleTransport(TransportCandidate candidate) {
org.jivesoftware.smackx.packet.JingleTransport.Ice jt = new org.jivesoftware.smackx.packet.JingleTransport.Ice();
jt.addCandidate(new org.jivesoftware.smackx.packet.JingleTransport.Ice.Candidate(candidate));
return jt;
}
/**
* Obtain the best remote candidate obtained in the negotiation so far.
*
* @return the bestRemoteCandidate
*/
public TransportCandidate getBestRemoteCandidate() {
TransportCandidate.Ice result = null;
ArrayList<TransportCandidate.Ice> cands = getValidRemoteCandidatesList();
if (!cands.isEmpty()) {
int lowest = 65560;
TransportCandidate.Ice chose = null;
for (TransportCandidate.Ice transportCandidate : cands) {
System.out.println("Pref: " + transportCandidate.getPreference() + " :" + transportCandidate.getIp());
if (transportCandidate.getPreference() < lowest) {
chose = transportCandidate;
lowest = transportCandidate.getPreference();
}
}
result = chose;
}
return result;
}
/**
* Return true for ICE candidates.
*/
public boolean acceptableTransportCandidate(TransportCandidate tc) {
try {
InetAddress.getByName(tc.getIp()).isReachable(3000);
DatagramSocket socket = new DatagramSocket(0);
socket.connect(InetAddress.getByName(tc.getIp()), tc.getPort());
return true;
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}
}

View file

@ -0,0 +1,433 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2002-2006 Jive Software. All rights reserved.
* ====================================================================
* The Jive Software License (based on Apache Software License, Version 1.1)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by
* Jive Software (http://www.jivesoftware.com)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Smack" and "Jive Software" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please
* contact webmaster@jivesoftware.com.
*
* 5. Products derived from this software may not be called "Smack",
* nor may "Smack" appear in their name, without prior written
* permission of Jive Software.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*/
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.XMPPException;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* A TransportResolver is used for obtaining a list of valid transport
* candidates. A transport candidate is composed by an IP address and a port number.
* It is called candidate, because it can be elected or not.
*
* @author Thiago Camargo
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public abstract class TransportResolver {
public enum Type {
rawupd, ice
}
;
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
public Type type = Type.rawupd;
// the time, in milliseconds, before a check aborts
public static final int CHECK_TIMEOUT = 3000;
// Listeners for events
private final ArrayList listeners = new ArrayList();
// TRue if the resolver is working
private boolean resolving;
// This will be true when all the transport candidates have been gathered...
private boolean resolved;
// This indicates that a transport resolver is initialized
private boolean initialized = false;
// We store a list of candidates internally, just in case there are several
// possibilities. When the user asks for a transport, we return the best
// one.
protected final List candidates = new ArrayList();
// Remote candidates that are being checked
private final static ArrayList candidatesChecking = new ArrayList();
/**
* Default constructor.
*/
protected TransportResolver() {
super();
resolving = false;
resolved = false;
}
/**
* Initialize the Resolver
*/
public abstract void initialize() throws XMPPException;
/**
* Start a the resolution.
*/
public abstract void resolve() throws XMPPException;
/**
* Clear the list of candidates and start a new resolution process.
*
* @throws XMPPException
*/
public void clear() throws XMPPException {
cancel();
candidates.clear();
//resolve();
}
/**
* Cancel any asynchronous resolution operation.
*/
public abstract void cancel() throws XMPPException;
/**
* Return true if the resolver is working.
*
* @return true if the resolver is working.
*/
public boolean isResolving() {
return resolving;
}
/**
* Return true if the resolver has finished the search for transport
* candidates.
*
* @return true if the search has finished
*/
public boolean isResolved() {
return resolved;
}
/**
* Set the Transport Resolver as initialized.
*/
public synchronized void setInitialized() {
initialized = true;
}
/**
* Chack if the Transport Resolver is initialized
*
* @return
*/
public synchronized boolean isInitialized() {
return initialized;
}
/**
* Indicate the beggining of the resolution process. This method must be
* used by subclasses at the begining of their resolve() method.
*/
protected synchronized void setResolveInit() {
resolved = false;
resolving = true;
triggerResolveInit();
}
/**
* Indicate the end of the resolution process. This method must be used by
* subclasses at the begining of their resolve() method.
*/
protected synchronized void setResolveEnd() {
resolved = true;
resolving = false;
triggerResolveEnd();
}
// Listeners management
/**
* Add a transport resolver listener.
*
* @param li The transport resolver listener to be added.
*/
public void addListener(TransportResolverListener li) {
synchronized (listeners) {
listeners.add(li);
}
}
/**
* Removes a transport resolver listener.
*
* @param li The transport resolver listener to be removed
*/
public void removeListener(TransportResolverListener li) {
synchronized (listeners) {
listeners.remove(li);
}
}
/**
* Get the list of listeners
*
* @return the list of listeners
*/
public ArrayList getListenersList() {
synchronized (listeners) {
return new ArrayList(listeners);
}
}
/**
* Trigger a new candidate added event.
*
* @param cand The candidate added to the list of candidates.
*/
protected void triggerCandidateAdded(TransportCandidate cand) {
Iterator iter = getListenersList().iterator();
while (iter.hasNext()) {
TransportResolverListener trl = (TransportResolverListener) iter.next();
if (trl instanceof TransportResolverListener.Resolver) {
TransportResolverListener.Resolver li = (TransportResolverListener.Resolver) trl;
System.out.println("triggerCandidateAdded : " + cand.getLocalIp());
li.candidateAdded(cand);
}
}
}
/**
* Trigger a event notifying the initialization of the resolution process.
*/
private void triggerResolveInit() {
Iterator iter = getListenersList().iterator();
while (iter.hasNext()) {
TransportResolverListener trl = (TransportResolverListener) iter.next();
if (trl instanceof TransportResolverListener.Resolver) {
TransportResolverListener.Resolver li = (TransportResolverListener.Resolver) trl;
li.init();
}
}
}
/**
* Trigger a event notifying the obtention of all the candidates.
*/
private void triggerResolveEnd() {
Iterator iter = getListenersList().iterator();
while (iter.hasNext()) {
TransportResolverListener trl = (TransportResolverListener) iter.next();
if (trl instanceof TransportResolverListener.Resolver) {
TransportResolverListener.Resolver li = (TransportResolverListener.Resolver) trl;
li.end();
}
}
}
// Candidates management
/**
* Clear the list of candidate
*/
protected void clearCandidates() {
synchronized (candidates) {
candidates.clear();
}
}
/**
* Add a new transport candidate
*
* @param cand The candidate to add
*/
protected void addCandidate(TransportCandidate cand) {
synchronized (candidates) {
if (!candidates.contains(cand))
candidates.add(cand);
}
// Notify the listeners
triggerCandidateAdded(cand);
}
/**
* Get an iterator for the list of candidates
*
* @return an iterator
*/
public Iterator getCandidates() {
synchronized (candidates) {
System.out.println("CNUM: " + candidates.size());
return Collections.unmodifiableList(new ArrayList(candidates)).iterator();
}
}
/**
* Get the candididate with the highest preference.
*
* @return The best candidate, according to the preference order.
*/
public TransportCandidate getPreferredCandidate() {
TransportCandidate result = null;
ArrayList cands = (ArrayList) getCandidatesList();
if (cands.size() > 0) {
Collections.sort(cands);
// Return the last candidate
result = (TransportCandidate) cands.get(cands.size() - 1);
System.out.println("Result: " + result.getIp());
}
return result;
}
/**
* Get the numer of transport candidates.
*
* @return The length of the transport candidates list.
*/
public int getCandidateCount() {
synchronized (candidates) {
return candidates.size();
}
}
/**
* Get the list of candidates
*
* @return the list of transport candidates
*/
public List getCandidatesList() {
ArrayList result = null;
synchronized (candidates) {
result = new ArrayList(candidates);
}
return result;
}
/**
* Get the n-th candidate
*
* @return a transport candidate
*/
public TransportCandidate getCandidate(int i) {
TransportCandidate cand;
synchronized (candidates) {
cand = (TransportCandidate) candidates.get(i);
}
return cand;
}
/**
* Initialize Transport Resolver and wait until it is complete unitialized.
*/
public void initializeAndWait() throws XMPPException {
this.initialize();
try {
System.out.print("Initializing...");
while (!this.isInitialized()) {
System.out.print(".");
Thread.sleep(1000);
}
System.out.print("Resolved\n");
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* Obtain a free port we can use.
*
* @return A free port number.
*/
protected int getFreePort() {
ServerSocket ss;
int freePort = 0;
for (int i = 0; i < 10; i++) {
freePort = (int) (10000 + Math.round(Math.random() * 10000));
freePort = freePort % 2 == 0 ? freePort : freePort + 1;
try {
ss = new ServerSocket(freePort);
freePort = ss.getLocalPort();
ss.close();
return freePort;
}
catch (IOException e) {
e.printStackTrace();
}
}
try {
ss = new ServerSocket(0);
freePort = ss.getLocalPort();
ss.close();
}
catch (IOException e) {
e.printStackTrace();
}
return freePort;
}
}

View file

@ -0,0 +1,48 @@
package org.jivesoftware.smackx.jingle.nat;
/**
* Transport resolver Interface
*/
public abstract interface TransportResolverListener {
/**
* Resolver listener.
*/
public interface Resolver extends TransportResolverListener {
/**
* The resolution process has been started.
*/
public void init();
/**
* A transport candidate has been added
*
* @param cand The transport candidate.
*/
public void candidateAdded(TransportCandidate cand);
/**
* All the transport candidates have been obtained.
*/
public void end();
}
/**
* Resolver checker.
*/
public interface Checker extends TransportResolverListener {
/**
* A transport candidate has been checked.
*
* @param cand The transport candidate that has been checked.
* @param result True if the candidate is usable.
*/
public void candidateChecked(TransportCandidate cand, boolean result);
/**
* A transport candidate is being checked.
*
* @param cand The transport candidate that is being checked.
*/
public void candidateChecking(TransportCandidate cand);
}
}

View file

@ -0,0 +1 @@
<body>Smack extensions API.</body>

View file

@ -0,0 +1,504 @@
/**
* $RCSfile$
* $Revision: 2407 $
* $Date: 2004-11-02 23:37:00 +0000 (Tue, 02 Nov 2004) $
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.packet;
import org.jivesoftware.smack.packet.IQ;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* An Jingle sub-packet, which is used by XMPP clients to exchange info like
* descriptions and transports. <p/> The following link summarizes the
* requirements of Jingle IM: <a
* href="http://www.jabber.org/jeps/jep-0166.html">Valid tags</a>.
* <p/>
* <p/> Warning: this is an non-standard protocol documented by <a
* href="http://www.jabber.org/jeps/jep-0166.html">JEP-166</a>. Because this is
* a non-standard protocol, it is subject to change.
*
* @author Alvaro Saurin
*/
public class Jingle extends IQ {
// static
public static final String NAMESPACE = "http://jabber.org/protocol/jingle";
public static final String NODENAME = "jingle";
// non-static
private String sid; // The session id
private Action action; // The action associated to the Jingle
private String initiator; // The initiator as a "user@host/resource"
private String responder; // The responder
// Sub-elements of a Jingle object.
private final List descriptions = new ArrayList();
private final List transports = new ArrayList();
private JingleContentInfo contentInfo;
/**
* A constructor where the main components can be initialized.
*/
public Jingle(final List descs, final List trans, final JingleContentInfo mi,
final String sid) {
super();
if (descs != null) {
descriptions.addAll(descs);
}
if (trans != null) {
transports.addAll(trans);
}
setContentInfo(mi);
setSid(sid);
// Set null all other fields in the packet
initiator = null;
responder = null;
action = null;
}
/**
* Constructor with a description.
*
* @param descr a description
*/
public Jingle(final JingleContentDescription descr) {
super();
addDescription(descr);
// Set null all other fields in the packet
initiator = null;
responder = null;
// Some default values for the most common situation...
action = Jingle.Action.DESCRIPTIONINFO;
this.setType(IQ.Type.SET);
}
/**
* Constructor with a transport.
*
* @param trans a transport
*/
public Jingle(final JingleTransport trans) {
super();
addTransport(trans);
// Set null all other fields in the packet
initiator = null;
responder = null;
// Some default values for the most common situation...
action = Jingle.Action.TRANSPORTINFO;
this.setType(IQ.Type.SET);
}
/**
* Constructor with a content info.
*
* @param info The content info
*/
public Jingle(final JingleContentInfo info) {
super();
setContentInfo(info);
// Set null all other fields in the packet
initiator = null;
responder = null;
// Some default values for the most common situation...
action = Jingle.Action.DESCRIPTIONINFO;
this.setType(IQ.Type.SET);
}
/**
* A constructor where the action can be specified.
*
* @param action The action.
*/
public Jingle(final Jingle.Action action) {
this(null, null, null, null);
this.action = action;
// In general, a Jingle with an action is used in a SET packet...
this.setType(IQ.Type.SET);
}
/**
* A constructor where the session ID can be specified.
*
* @param sid The session ID related to the negotiation.
* @see #setSid(String)
*/
public Jingle(final String sid) {
this(null, null, null, sid);
}
/**
* The default constructor
*/
public Jingle() {
super();
}
/**
* Set the session ID related to this session. The session ID is a unique
* identifier generated by the initiator. This should match the XML Nmtoken
* production so that XML character escaping is not needed for characters
* such as &.
*
* @param sid the session ID
*/
public final void setSid(final String sid) {
this.sid = sid;
}
/**
* Returns the session ID related to the session. The session ID is a unique
* identifier generated by the initiator. This should match the XML Nmtoken
* production so that XML character escaping is not needed for characters
* such as &.
*
* @return Returns the session ID related to the session.
* @see #setSid(String)
*/
public String getSid() {
return sid;
}
/**
* Returns the XML element name of the extension sub-packet root element.
* Always returns "jingle"
*
* @return the XML element name of the packet extension.
*/
public static String getElementName() {
return NODENAME;
}
/**
* Returns the XML namespace of the extension sub-packet root element.
* According the specification the namespace is always
* "http://jabber.org/protocol/jingle"
*
* @return the XML namespace of the packet extension.
*/
public static String getNamespace() {
return NAMESPACE;
}
/**
* @return the audioInfo
*/
public JingleContentInfo getContentInfo() {
return contentInfo;
}
/**
* @param contentInfo the audioInfo to set
*/
public void setContentInfo(final JingleContentInfo contentInfo) {
this.contentInfo = contentInfo;
}
/**
* Get an iterator for the content descriptions
*
* @return the descriptions
*/
public Iterator getDescriptions() {
synchronized (descriptions) {
return Collections.unmodifiableList(new ArrayList(descriptions)).iterator();
}
}
/**
* Get an iterator for the content descriptions
*
* @return the descriptions
*/
public ArrayList getDescriptionsList() {
synchronized (descriptions) {
return new ArrayList(descriptions);
}
}
/**
* Add a new content description.
*
* @param desc the descriptions to add
*/
public void addDescription(final JingleContentDescription desc) {
if (desc != null) {
synchronized (descriptions) {
descriptions.add(desc);
}
}
}
/**
* Add a list of JingleContentDescription elements
*
* @param descsList the list of transports to add
*/
public void addDescriptions(final List descsList) {
if (descsList != null) {
synchronized (descriptions) {
descriptions.addAll(descsList);
}
}
}
/**
* Get an iterator for the transport.
*
* @return the transports
*/
public Iterator getTransports() {
synchronized (transports) {
return Collections.unmodifiableList(new ArrayList(transports)).iterator();
}
}
/**
* Get the list of transports.
*
* @return the transports list.
*/
public ArrayList getTransportsList() {
synchronized (transports) {
return new ArrayList(transports);
}
}
/**
* Add a new TransportNegotiator element
*
* @param trans the transports to add
*/
public void addTransport(final JingleTransport trans) {
if (trans != null) {
synchronized (transports) {
transports.add(trans);
}
}
}
/**
* Add a list of TransportNegotiator elements
*
* @param transList the list of transports to add
*/
public void addTransports(final List transList) {
if (transList != null) {
synchronized (transports) {
transports.addAll(transList);
}
}
}
/**
* Get the action specified in the packet
*
* @return the action
*/
public Action getAction() {
return action;
}
/**
* Set the action in the packet
*
* @param action the action to set
*/
public void setAction(final Action action) {
this.action = action;
}
/**
* Get the initiator. The initiator will be the full JID of the entity that
* has initiated the flow (which may be different to the "from" address in
* the IQ)
*
* @return the initiator
*/
public String getInitiator() {
return initiator;
}
/**
* Set the initiator. The initiator must be the full JID of the entity that
* has initiated the flow (which may be different to the "from" address in
* the IQ)
*
* @param initiator the initiator to set
*/
public void setInitiator(final String initiator) {
this.initiator = initiator;
}
/**
* Get the responder. The responder is the full JID of the entity that has
* replied to the initiation (which may be different to the "to" addresss in
* the IQ).
*
* @return the responder
*/
public String getResponder() {
return responder;
}
/**
* Set the responder. The responder must be the full JID of the entity that
* has replied to the initiation (which may be different to the "to"
* addresss in the IQ).
*
* @param resp the responder to set
*/
public void setResponder(final String resp) {
responder = resp;
}
/**
* Get a hash key for the session this packet belongs to.
*
* @param sid The session id
* @param initiator The initiator
* @return A hash key
*/
public static int getSessionHash(final String sid, final String initiator) {
final int PRIME = 31;
int result = 1;
result = PRIME * result + (initiator == null ? 0 : initiator.hashCode());
result = PRIME * result + (sid == null ? 0 : sid.hashCode());
return result;
}
/**
* Return the XML representation of the packet.
*
* @return the XML string
*/
public String getChildElementXML() {
StringBuilder buf = new StringBuilder();
buf.append("<").append(getElementName());
buf.append(" xmlns=\"").append(getNamespace()).append("\"");
if (getInitiator() != null) {
buf.append(" initiator=\"").append(getInitiator()).append("\"");
}
if (getResponder() != null) {
buf.append(" responder=\"").append(getResponder()).append("\"");
}
if (getAction() != null) {
buf.append(" action=\"").append(getAction()).append("\"");
}
if (getSid() != null) {
buf.append(" sid=\"").append(getSid()).append("\"");
}
buf.append(">");
//TODO Update to accept more than one content per session (XEP-0166)
buf.append("<content name='Audio-Content'>");
// Look for possible payload types, and dump them.
synchronized (descriptions) {
for (int i = 0; i < descriptions.size(); i++) {
JingleContentDescription desc = (JingleContentDescription) descriptions
.get(i);
buf.append(desc.toXML());
}
}
// If the packet has transports, dump them.
synchronized (transports) {
for (int i = 0; i < transports.size(); i++) {
JingleTransport trans = (JingleTransport) transports.get(i);
buf.append(trans.toXML());
}
}
buf.append("</content>");
// and the same for audio jmf info
if (contentInfo != null) {
buf.append(contentInfo.toXML());
}
buf.append("</").append(getElementName()).append(">");
return buf.toString();
}
/**
* The "action" in the jingle packet, as an enum.
*/
public static enum Action {
CONTENTACCEPT, CONTENTADD, CONTENTDECLINE, CONTENTMODIFY,
CONTENTREMOVE, DESCRIPTIONADD, DESCRIPTIONDECLINE,
DESCRIPTIONINFO, DESCRIPTIONMODIFY, SESSIONACCEPT,
SESSIONINFO, SESSIONINITIATE, SESSIONREDIRECT,
SESSIONTERMINATE, TRANSPORTACCEPT, TRANSPORTDECLINE,
TRANSPORTINFO, TRANSPORTMODIFY;
private static String names[] = {"content-accept", "content-add", "content-decline", "content-modify",
"content-remove", "description-accept", "description-decline", "description-info",
"description-modify", "session-accept", "session-info", "session-initiate",
"session-redirect", "session-terminate", "transport-accept", "transport-decline",
"transport-info", "transport-modify"};
/**
* Returns the String value for an Action.
*/
public String toString() {
return names[this.ordinal()];
}
/**
* Returns the Action for a String value.
*/
public static Action getAction(String str) {
for (int i = 0; i < names.length; i++) {
if (names[i].equals(str)) return Action.values()[i];
}
return null;
}
}
}

View file

@ -0,0 +1,278 @@
package org.jivesoftware.smackx.packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* Jingle content description.
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public abstract class JingleContentDescription implements PacketExtension {
// static
public static final String NODENAME = "description";
// non-static
private final List payloads = new ArrayList();
/**
* Creates a content description..
*/
public JingleContentDescription() {
super();
}
/**
* Returns the XML element name of the element.
*
* @return the XML element name of the element.
*/
public String getElementName() {
return NODENAME;
}
/**
* Return the namespace.
*
* @return The namespace
*/
public abstract String getNamespace();
/**
* Adds a audio payload type to the packet.
*
* @param pt the audio payload type to add.
*/
public void addJinglePayloadType(final JinglePayloadType pt) {
synchronized (payloads) {
payloads.add(pt);
}
}
/**
* Adds a list of payloads to the packet.
*
* @param pts the payloads to add.
*/
public void addAudioPayloadTypes(final List pts) {
synchronized (payloads) {
Iterator ptIter = pts.iterator();
while (ptIter.hasNext()) {
PayloadType.Audio pt = (PayloadType.Audio) ptIter.next();
addJinglePayloadType(new JinglePayloadType.Audio(pt));
}
}
}
/**
* Returns an Iterator for the audio payloads in the packet.
*
* @return an Iterator for the audio payloads in the packet.
*/
public Iterator getJinglePayloadTypes() {
return Collections.unmodifiableList(getJinglePayloadTypesList()).iterator();
}
/**
* Returns a list for the audio payloads in the packet.
*
* @return a list for the audio payloads in the packet.
*/
public ArrayList getJinglePayloadTypesList() {
synchronized (payloads) {
return new ArrayList(payloads);
}
}
/**
* Return the list of Payload types contained in the description.
*
* @return a list of PayloadType.Audio
*/
public ArrayList getAudioPayloadTypesList() {
ArrayList result = new ArrayList();
Iterator jinglePtsIter = getJinglePayloadTypes();
while (jinglePtsIter.hasNext()) {
JinglePayloadType jpt = (JinglePayloadType) jinglePtsIter.next();
if (jpt instanceof JinglePayloadType.Audio) {
JinglePayloadType.Audio jpta = (JinglePayloadType.Audio) jpt;
result.add(jpta.getPayloadType());
}
}
return result;
}
/**
* Returns a count of the audio payloads in the Jingle packet.
*
* @return the number of audio payloads in the Jingle packet.
*/
public int getJinglePayloadTypesCount() {
synchronized (payloads) {
return payloads.size();
}
}
/**
* Convert a Jingle description to XML.
*
* @return a string with the XML representation
*/
public String toXML() {
StringBuilder buf = new StringBuilder();
synchronized (payloads) {
if (payloads.size() > 0) {
buf.append("<").append(getElementName());
buf.append(" xmlns=\"").append(getNamespace()).append("\" >");
Iterator pt = payloads.listIterator();
while (pt.hasNext()) {
JinglePayloadType pte = (JinglePayloadType) pt.next();
buf.append(pte.toXML());
}
buf.append("</").append(getElementName()).append(">");
}
}
return buf.toString();
}
/**
* Jingle audio description
*/
public static class Audio extends JingleContentDescription {
public static final String NAMESPACE = "http://jabber.org/protocol/jingle/description/audio";
public Audio() {
super();
}
/**
* Utility constructor, with a JinglePayloadType
*/
public Audio(final JinglePayloadType pt) {
super();
addJinglePayloadType(pt);
}
public String getNamespace() {
return NAMESPACE;
}
}
/**
* A payload type, contained in a descriptor.
*
* @author Alvaro Saurin
*/
public static class JinglePayloadType {
public static final String NODENAME = "payload-type";
private PayloadType payload;
/**
* Create a payload type.
*
* @param payload the payload
*/
public JinglePayloadType(final PayloadType payload) {
super();
this.payload = payload;
}
/**
* Create an empty payload type.
*/
public JinglePayloadType() {
this(null);
}
/**
* Returns the XML element name of the element.
*
* @return the XML element name of the element.
*/
public static String getElementName() {
return NODENAME;
}
/**
* Get the payload represented.
*
* @return the payload
*/
public PayloadType getPayloadType() {
return payload;
}
/**
* Set the payload represented.
*
* @param payload the payload to set
*/
public void setPayload(final PayloadType payload) {
this.payload = payload;
}
protected String getChildAttributes() {
return null;
}
public String toXML() {
StringBuilder buf = new StringBuilder();
if (payload != null) {
buf.append("<").append(getElementName()).append(" ");
// We covert here the payload type to XML
if (payload.getId() != PayloadType.INVALID_PT) {
buf.append(" id=\"").append(payload.getId()).append("\"");
}
if (payload.getName() != null) {
buf.append(" name=\"").append(payload.getName()).append("\"");
}
if (payload.getChannels() != 0) {
buf.append(" channels=\"").append(payload.getChannels()).append("\"");
}
if (getChildAttributes() != null) {
buf.append(getChildAttributes());
}
buf.append("/>");
}
return buf.toString();
}
/**
* Audio payload type element
*/
public static class Audio extends JinglePayloadType {
public Audio(final PayloadType.Audio audio) {
super(audio);
}
protected String getChildAttributes() {
StringBuilder buf = new StringBuilder();
PayloadType pt = getPayloadType();
if (pt instanceof PayloadType.Audio) {
PayloadType.Audio pta = (PayloadType.Audio) pt;
buf.append(" clockrate=\"").append(pta.getClockRate()).append("\" ");
}
return buf.toString();
}
}
}
}

View file

@ -0,0 +1,138 @@
package org.jivesoftware.smackx.packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smackx.jingle.media.ContentInfo;
/**
* Jingle content info
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public class JingleContentInfo implements PacketExtension {
protected ContentInfo mediaInfoElement;
private String namespace;
/**
* Empty constructor, with no jmf info.
*/
public JingleContentInfo() {
this(null);
}
/**
* Constructor with a jmf info
*
* @param mediaInfoElement MediaInfo element
*/
public JingleContentInfo(final ContentInfo mediaInfoElement) {
super();
this.mediaInfoElement = mediaInfoElement;
}
/**
* Get the jmf info element.
*
* @return the mediaInfoElement
*/
public ContentInfo getMediaInfo() {
return mediaInfoElement;
}
/**
* Get the element name
*/
public String getElementName() {
// Media info is supposed to be just a single-word command...
return getMediaInfo().toString();
}
/**
* Set the name space.
*
* @param ns the namespace
*/
protected void setNamespace(final String ns) {
namespace = ns;
}
/**
* Get the publilc namespace
*/
public String getNamespace() {
return namespace;
}
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<").append(getElementName()).append(" xmlns=\"");
buf.append(getNamespace()).append("\" ");
buf.append("/>");
return buf.toString();
}
/**
* Transport part of a Jingle packet.
*/
public static class Audio extends JingleContentInfo {
public static final String NAMESPACE = "http://jabber.org/protocol/jingle/info/audio";
public Audio(final ContentInfo mi) {
super(mi);
setNamespace(NAMESPACE);
}
public String getNamespace() {
return NAMESPACE;
}
// Subclasses: specialize the Audio jmf info...
/**
* Busy jmf info.
*/
public static class Busy extends Audio {
public Busy() {
super(ContentInfo.Audio.BUSY);
}
}
/**
* Hold jmf info.
*/
public static class Hold extends Audio {
public Hold() {
super(ContentInfo.Audio.HOLD);
}
}
/**
* Mute jmf info.
*/
public static class Mute extends Audio {
public Mute() {
super(ContentInfo.Audio.MUTE);
}
}
/**
* Queued jmf info.
*/
public static class Queued extends Audio {
public Queued() {
super(ContentInfo.Audio.QUEUED);
}
}
/**
* Ringing jmf info.
*/
public static class Ringing extends Audio {
public Ringing() {
super(ContentInfo.Audio.RINGING);
}
}
}
}

View file

@ -0,0 +1,102 @@
package org.jivesoftware.smackx.packet;
import org.jivesoftware.smack.packet.PacketExtension;
public class JingleError implements PacketExtension {
public static String NAMESPACE = "http://jabber.org/protocol/jingle#error";
public static final JingleError OUT_OF_ORDER = new JingleError("out-of-order");
public static final JingleError UNKNOWN_SESSION = new JingleError("unknown-session");
public static final JingleError UNSUPPORTED_CONTENT = new JingleError(
"unsupported-content");
public static final JingleError UNSUPPORTED_TRANSPORTS = new JingleError(
"unsupported-transports");
// Non standard error messages
public static final JingleError NO_COMMON_PAYLOAD = new JingleError(
"no-common-payload");
public static final JingleError NEGOTIATION_ERROR = new JingleError(
"negotiation-error");
public static final JingleError MALFORMED_STANZA = new JingleError("malformed-stanza");
private String message;
/**
* Creates a new error with the specified code and message.
*
* @param message a message describing the error.
*/
public JingleError(final String message) {
this.message = message;
}
/**
* Returns the message describing the error, or null if there is no message.
*
* @return the message describing the error, or null if there is no message.
*/
public String getMessage() {
return message;
}
/**
* Returns the error as XML.
*
* @return the error as XML.
*/
public String toXML() {
StringBuilder buf = new StringBuilder();
if (message != null) {
buf.append("<error type=\"cancel\">");
buf.append("<").append(message).append(" xmlns=\"").append(NAMESPACE).append(
"\"/>");
buf.append("</error>");
}
return buf.toString();
}
/**
* Returns a Action instance associated with the String value.
*/
public static JingleError fromString(String value) {
if (value != null) {
value = value.toLowerCase();
if (value.equals("out-of-order")) {
return OUT_OF_ORDER;
} else if (value.equals("unknown-session")) {
return UNKNOWN_SESSION;
} else if (value.equals("unsupported-content")) {
return UNSUPPORTED_CONTENT;
} else if (value.equals("unsupported-transports")) {
return UNSUPPORTED_TRANSPORTS;
} else if (value.equals("no-common-payload")) {
return NO_COMMON_PAYLOAD;
} else if (value.equals("negotiation-error")) {
return NEGOTIATION_ERROR;
} else if (value.equals("malformed-stanza")) {
return MALFORMED_STANZA;
}
}
return null;
}
public String toString() {
return getMessage();
}
public String getElementName() {
return message;
}
public String getNamespace() {
return NAMESPACE;
}
}

View file

@ -0,0 +1,409 @@
package org.jivesoftware.smackx.packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* A jingle transport extension
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public class JingleTransport implements PacketExtension {
// static
public static final String NODENAME = "transport";
// non-static
protected String namespace;
protected final List candidates = new ArrayList();
/**
* Default constructor.
*/
public JingleTransport() {
super();
}
/**
* Utility constructor, with a transport candidate element.
*
* @param candidate A transport candidate element to add.
*/
public JingleTransport(final JingleTransportCandidate candidate) {
super();
addCandidate(candidate);
}
/**
* Copy constructor.
*
* @param tr the other jingle transport.
*/
public JingleTransport(final JingleTransport tr) {
if (tr != null) {
namespace = tr.namespace;
if (tr.candidates.size() > 0) {
candidates.addAll(tr.candidates);
}
}
}
/**
* Adds a transport candidate.
*
* @param candidate the candidate
*/
public void addCandidate(final JingleTransportCandidate candidate) {
if (candidate != null) {
synchronized (candidates) {
candidates.add(candidate);
}
}
}
/**
* Get an iterator for the candidates
*
* @return an iterator
*/
public Iterator getCandidates() {
return Collections.unmodifiableList(getCandidatesList()).iterator();
}
/**
* Get the list of candidates.
*
* @return The candidates list.
*/
public ArrayList getCandidatesList() {
ArrayList res = null;
synchronized (candidates) {
res = new ArrayList(candidates);
}
return res;
}
/**
* Get the number of transport candidates.
*
* @return The number of transport candidates contained.
*/
public int getCandidatesCount() {
return getCandidatesList().size();
}
/**
* Returns the XML element name of the element.
*
* @return the XML element name of the element.
*/
public String getElementName() {
return NODENAME;
}
/**
* Set the namespace.
*
* @param ns The namespace
*/
protected void setNamespace(final String ns) {
namespace = ns;
}
/**
* Get the namespace.
*
* @return The namespace
*/
public String getNamespace() {
return namespace;
}
/**
* Return the XML representation for this element.
*/
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<").append(getElementName()).append(" xmlns=\"");
buf.append(getNamespace()).append("\" ");
synchronized (candidates) {
if (getCandidatesCount() > 0) {
buf.append(">");
Iterator iter = getCandidates();
while (iter.hasNext()) {
JingleTransportCandidate candidate = (JingleTransportCandidate) iter
.next();
buf.append(candidate.toXML());
}
buf.append("</").append(getElementName()).append(">");
} else {
buf.append("/>");
}
}
return buf.toString();
}
/**
* Candidate element in the transport. This class acts as a view of the
* "TransportCandidate" in the Jingle space.
*
* @author Alvaro Saurin
* @see TransportCandidate
*/
public static abstract class JingleTransportCandidate {
public static final String NODENAME = "candidate";
// The transport candidate contained in the element.
protected TransportCandidate transportCandidate;
/**
* Creates a new TransportNegotiator child.
*/
public JingleTransportCandidate() {
super();
}
/**
* Creates a new TransportNegotiator child.
*
* @param candidate the jmf transport candidate
*/
public JingleTransportCandidate(final TransportCandidate candidate) {
super();
setMediaTransport(candidate);
}
/**
* Returns the XML element name of the element.
*
* @return the XML element name of the element.
*/
public static String getElementName() {
return NODENAME;
}
/**
* Get the current transportElement candidate.
*
* @return the transportElement candidate
*/
public TransportCandidate getMediaTransport() {
return transportCandidate;
}
/**
* Set the transportElement candidate.
*
* @param cand the transportElement candidate
*/
public void setMediaTransport(final TransportCandidate cand) {
if (cand != null) {
transportCandidate = cand;
}
}
/**
* Get the list of attributes.
*
* @return a string with the list of attributes.
*/
protected String getChildElements() {
return null;
}
/**
* Obtain a valid XML representation of a trancport candidate
*
* @return A string containing the XML dump of the transport candidate.
*/
public String toXML() {
StringBuilder buf = new StringBuilder();
String childElements = getChildElements();
if (transportCandidate != null && childElements != null) {
buf.append("<").append(getElementName()).append(" ");
buf.append(childElements);
buf.append("/>");
}
return buf.toString();
}
}
// Subclasses
/**
* RTP-ICE profile
*/
public static class Ice extends JingleTransport {
public static final String NAMESPACE = "http://jabber.org/protocol/jingle/transport/ice";
public Ice() {
super();
setNamespace(NAMESPACE);
}
/**
* Add a transport candidate
*
* @see org.jivesoftware.smackx.packet.JingleTransport#addCandidate(org.jivesoftware.smackx.packet.JingleTransport.JingleTransportCandidate)
*/
public void addCandidate(final JingleTransportCandidate candidate) {
super.addCandidate(candidate);
}
/**
* Get the list of candidates. As a "raw-udp" transport can only contain
* one candidate, we use the first in the list...
*
* @see org.jivesoftware.smackx.packet.JingleTransport#getCandidates()
*/
public ArrayList getCandidatesList() {
ArrayList copy = new ArrayList();
ArrayList superCandidatesList = super.getCandidatesList();
for (int i = 0; i < superCandidatesList.size(); i++) {
copy.add(superCandidatesList.get(i));
}
return copy;
}
public static class Candidate extends JingleTransportCandidate {
/**
* Default constructor
*/
public Candidate() {
super();
}
/**
* Constructor with a transport candidate.
*/
public Candidate(final TransportCandidate tc) {
super(tc);
}
/**
* Get the elements of this candidate.
*/
protected String getChildElements() {
StringBuilder buf = new StringBuilder();
if (transportCandidate != null) {// && transportCandidate instanceof TransportCandidate.Ice) {
TransportCandidate.Ice tci = (TransportCandidate.Ice) transportCandidate;
// We convert the transportElement candidate to XML here...
buf.append(" generation=\"").append(tci.getGeneration()).append("\"");
buf.append(" ip=\"").append(tci.getIp()).append("\"");
buf.append(" port=\"").append(tci.getPort()).append("\"");
buf.append(" network=\"").append(tci.getNetwork()).append("\"");
buf.append(" username=\"").append(tci.getUsername()).append("\"");
buf.append(" password=\"").append(tci.getPassword()).append("\"");
buf.append(" preference=\"").append(tci.getPreference()).append("\"");
// Optional elements
if (transportCandidate.getName() != null) {
buf.append(" name=\"").append(tci.getName()).append("\"");
}
}
return buf.toString();
}
}
}
/**
* Raw UDP profile.
*/
public static class RawUdp extends JingleTransport {
public static final String NAMESPACE = "http://jabber.org/protocol/jingle/transport/raw-udp";
public RawUdp() {
super();
setNamespace(NAMESPACE);
}
/**
* Add a transport candidate
*
* @see org.jivesoftware.smackx.packet.JingleTransport#addCandidate(org.jivesoftware.smackx.packet.JingleTransport.JingleTransportCandidate)
*/
public void addCandidate(final JingleTransportCandidate candidate) {
candidates.clear();
super.addCandidate(candidate);
}
/**
* Get the list of candidates. As a "raw-udp" transport can only contain
* one candidate, we use the first in the list...
*
* @see org.jivesoftware.smackx.packet.JingleTransport#getCandidates()
*/
public ArrayList getCandidatesList() {
ArrayList copy = new ArrayList();
ArrayList superCandidatesList = super.getCandidatesList();
if (superCandidatesList.size() > 0) {
copy.add(superCandidatesList.get(0));
}
return copy;
}
/**
* Raw-udp transport candidate.
*/
public static class Candidate extends JingleTransportCandidate {
/**
* Default constructor
*/
public Candidate() {
super();
}
/**
* Constructor with a transport candidate.
*/
public Candidate(final TransportCandidate tc) {
super(tc);
}
/**
* Get the elements of this candidate.
*/
protected String getChildElements() {
StringBuilder buf = new StringBuilder();
if (transportCandidate != null && transportCandidate instanceof TransportCandidate.Fixed) {
TransportCandidate.Fixed tcf = (TransportCandidate.Fixed) transportCandidate;
buf.append(" generation=\"").append(tcf.getGeneration()).append("\"");
buf.append(" ip=\"").append(tcf.getIp()).append("\"");
buf.append(" port=\"").append(tcf.getPort()).append("\"");
// Optional parameters
String name = tcf.getName();
if (name != null) {
buf.append(" name=\"").append(name).append("\"");
}
}
return buf.toString();
}
}
}
}

View file

@ -0,0 +1 @@
<body>XML packets that are part of the XMPP extension protocols.</body>

View file

@ -0,0 +1,125 @@
package org.jivesoftware.smackx.provider;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.packet.JingleContentDescription;
import org.jivesoftware.smackx.packet.JingleContentDescription.JinglePayloadType;
import org.xmlpull.v1.XmlPullParser;
/**
* Parser for a Jingle description
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public abstract class JingleContentDescriptionProvider implements PacketExtensionProvider {
/**
* Default constructor
*/
public JingleContentDescriptionProvider() {
super();
}
/**
* Parse a iq/jingle/description/payload-type element.
*
* @param parser the input to parse
* @return a payload type element
* @throws Exception
*/
protected JinglePayloadType parsePayload(final XmlPullParser parser)
throws Exception {
int ptId = 0;
String ptName;
int ptChannels = 0;
try {
ptId = Integer.parseInt(parser.getAttributeValue("", "id"));
} catch (Exception e) {
}
ptName = parser.getAttributeValue("", "name");
try {
ptChannels = Integer.parseInt(parser.getAttributeValue("", "channels"));
} catch (Exception e) {
}
return new JinglePayloadType(new PayloadType(ptId, ptName, ptChannels));
}
/**
* Parse a iq/jingle/description element.
*
* @param parser the input to parse
* @return a description element
* @throws Exception
*/
public PacketExtension parseExtension(final XmlPullParser parser) throws Exception {
boolean done = false;
JingleContentDescription desc = getInstance();
while (!done) {
int eventType = parser.next();
String name = parser.getName();
if (eventType == XmlPullParser.START_TAG) {
if (name.equals(JingleContentDescription.JinglePayloadType.NODENAME)) {
desc.addJinglePayloadType(parsePayload(parser));
} else {
throw new Exception("Unknow element \"" + name + "\" in content.");
}
} else if (eventType == XmlPullParser.END_TAG) {
if (name.equals(JingleContentDescription.NODENAME)) {
done = true;
}
}
}
return desc;
}
/**
* Return a new instance of this class. Subclasses must overwrite this
* method.
*/
protected abstract JingleContentDescription getInstance();
/**
* Jingle audio
*/
public static class Audio extends JingleContentDescriptionProvider {
/**
* Default constructor
*/
public Audio() {
super();
}
/**
* Parse an audio payload type.
*/
public JinglePayloadType parsePayload(final XmlPullParser parser)
throws Exception {
JinglePayloadType pte = super.parsePayload(parser);
PayloadType.Audio pt = new PayloadType.Audio(pte.getPayloadType());
int ptClockRate = 0;
try {
ptClockRate = Integer.parseInt(parser.getAttributeValue("", "clockrate"));
} catch (Exception e) {
}
pt.setClockRate(ptClockRate);
return new JinglePayloadType.Audio(pt);
}
/**
* Get a new instance of this object.
*/
protected JingleContentDescription getInstance() {
return new JingleContentDescription.Audio();
}
}
}

View file

@ -0,0 +1,106 @@
package org.jivesoftware.smackx.provider;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smackx.jingle.media.ContentInfo;
import org.jivesoftware.smackx.packet.JingleContentInfo;
import org.xmlpull.v1.XmlPullParser;
/**
* Jingle Audio jmf-info provider
*
* @author Alvaro Saurin
*/
public class JingleContentInfoProvider implements PacketExtensionProvider {
/**
* Creates a new provider. ProviderManager requires that every
* PacketExtensionProvider has a public, no-argument constructor
*/
public JingleContentInfoProvider() {
super();
}
public PacketExtension parseExtension(final XmlPullParser parser) throws Exception {
// This method must be overwritten by subclasses...
return null;
}
/**
* JingleContentDescription.Audio info provider
*/
public static class Audio extends JingleContentInfoProvider {
private PacketExtension audioInfo;
/**
* Empty constructor.
*/
public Audio() {
this(null);
}
/**
* Constructor with an audio info.
*
* @param audioInfo the jmf info
*/
public Audio(final PacketExtension audioInfo) {
super();
this.audioInfo = audioInfo;
}
/**
* Parse a JingleContentDescription.Audio extension.
*/
public PacketExtension parseExtension(final XmlPullParser parser)
throws Exception {
PacketExtension result = null;
if (audioInfo != null) {
result = audioInfo;
} else {
String elementName = parser.getName();
// Try to get an Audio content info
ContentInfo mi = ContentInfo.Audio.fromString(elementName);
if (mi != null) {
result = new JingleContentInfo.Audio(mi);
}
}
return result;
}
// Sub-elements
public static class Busy extends Audio {
public Busy() {
super(new JingleContentInfo.Audio.Busy());
}
}
public static class Hold extends Audio {
public Hold() {
super(new JingleContentInfo.Audio.Hold());
}
}
public static class Mute extends Audio {
public Mute() {
super(new JingleContentInfo.Audio.Mute());
}
}
public static class Queued extends Audio {
public Queued() {
super(new JingleContentInfo.Audio.Queued());
}
}
public static class Ringing extends Audio {
public Ringing() {
super(new JingleContentInfo.Audio.Ringing());
}
}
}
}

View file

@ -0,0 +1,128 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2005 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.provider;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smackx.packet.Jingle;
import org.jivesoftware.smackx.packet.JingleContentDescription;
import org.jivesoftware.smackx.packet.JingleContentInfo;
import org.jivesoftware.smackx.packet.JingleTransport;
import org.xmlpull.v1.XmlPullParser;
/**
* The JingleProvider parses Jingle packets.
*
* @author Alvaro Saurin
*/
public class JingleProvider implements IQProvider {
/**
* Creates a new provider. ProviderManager requires that every
* PacketExtensionProvider has a public, no-argument constructor
*/
public JingleProvider() {
super();
}
/**
* Parse a iq/jingle element.
*/
public IQ parseIQ(final XmlPullParser parser) throws Exception {
Jingle jingle = new Jingle();
String sid = "";
Jingle.Action action;
String initiator = "";
String responder = "";
boolean done = false;
// Sub-elements providers
JingleContentDescriptionProvider jdpAudio = new JingleContentDescriptionProvider.Audio();
JingleTransportProvider jtpRawUdp = new JingleTransportProvider.RawUdp();
JingleTransportProvider jtpIce = new JingleTransportProvider.Ice();
JingleContentInfoProvider jmipAudio = new JingleContentInfoProvider.Audio();
int eventType;
String elementName;
String namespace;
// Get some attributes for the <jingle> element
sid = parser.getAttributeValue("", "sid");
action = Jingle.Action.getAction(parser.getAttributeValue("", "action"));
initiator = parser.getAttributeValue("", "initiator");
responder = parser.getAttributeValue("", "responder");
jingle.setSid(sid);
jingle.setAction(action);
jingle.setInitiator(initiator);
jingle.setResponder(responder);
// Start processing sub-elements
while (!done) {
eventType = parser.next();
elementName = parser.getName();
namespace = parser.getNamespace();
if (eventType == XmlPullParser.START_TAG) {
// Parse some well know subelements, depending on the namespaces
// and element names...
if (elementName.equals(JingleContentDescription.NODENAME)
&& namespace.equals(JingleContentDescription.Audio.NAMESPACE)) {
jingle.addDescription((JingleContentDescription) jdpAudio
.parseExtension(parser));
} else if (elementName.equals(JingleTransport.NODENAME)) {
// Parse the possible transport namespaces
if (namespace.equals(JingleTransport.RawUdp.NAMESPACE)) {
jingle.addTransport((JingleTransport) jtpRawUdp
.parseExtension(parser));
} else if (namespace.equals(JingleTransport.Ice.NAMESPACE)) {
jingle.addTransport((JingleTransport) jtpIce
.parseExtension(parser));
} else {
throw new XMPPException("Unknown transport namespace \""
+ namespace + "\" in Jingle packet.");
}
} else if (namespace.equals(JingleContentInfo.Audio.NAMESPACE)) {
jingle.setContentInfo((JingleContentInfo) jmipAudio
.parseExtension(parser));
} else if (elementName.equals("content")) {
//TODO Separate Contents (XEP-0166)
} else {
throw new XMPPException("Unknown combination of namespace \""
+ namespace + "\" and element name \"" + elementName
+ "\" in Jingle packet.");
}
} else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals(Jingle.getElementName())) {
done = true;
}
}
}
return jingle;
}
}

View file

@ -0,0 +1,231 @@
package org.jivesoftware.smackx.provider;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import org.jivesoftware.smackx.packet.JingleTransport;
import org.jivesoftware.smackx.packet.JingleTransport.JingleTransportCandidate;
import org.xmlpull.v1.XmlPullParser;
/**
* Provider for a Jingle transport element
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public abstract class JingleTransportProvider implements PacketExtensionProvider {
/**
* Creates a new provider. ProviderManager requires that every
* PacketExtensionProvider has a public, no-argument constructor
*/
public JingleTransportProvider() {
super();
}
/**
* Obtain the corresponding TransportNegotiator instance.
*
* @return a new TransportNegotiator instance
*/
protected JingleTransport getInstance() {
return new JingleTransport();
}
/**
* Parse a iq/jingle/transport element.
*
* @param parser the structure to parse
* @return a transport element.
* @throws Exception
*/
public PacketExtension parseExtension(final XmlPullParser parser) throws Exception {
boolean done = false;
JingleTransport trans = getInstance();
while (!done) {
int eventType = parser.next();
String name = parser.getName();
if (eventType == XmlPullParser.START_TAG) {
if (name.equals(JingleTransportCandidate.NODENAME)) {
JingleTransportCandidate jtc = parseCandidate(parser);
if (jtc != null) trans.addCandidate(jtc);
} else {
throw new Exception("Unknown tag \"" + name + "\" in transport element.");
}
} else if (eventType == XmlPullParser.END_TAG) {
if (name.equals(JingleTransport.NODENAME)) {
done = true;
}
}
}
return trans;
}
protected abstract JingleTransportCandidate parseCandidate(final XmlPullParser parser)
throws Exception;
/**
* RTP-ICE profile
*/
public static class Ice extends JingleTransportProvider {
/**
* Defauls constructor.
*/
public Ice() {
super();
}
/**
* Obtain the corresponding TransportNegotiator.Ice instance.
*
* @return a new TransportNegotiator.Ice instance
*/
protected JingleTransport getInstance() {
return new JingleTransport.Ice();
}
/**
* Parse a iq/jingle/transport/candidate element.
*
* @param parser the structure to parse
* @return a candidate element
* @throws Exception
*/
protected JingleTransportCandidate parseCandidate(XmlPullParser parser) throws Exception {
TransportCandidate.Ice mt = new TransportCandidate.Ice();
String channel = parser.getAttributeValue("", "channel");
String generation = parser.getAttributeValue("", "generation");
String ip = parser.getAttributeValue("", "ip");
String name = parser.getAttributeValue("", "name");
String network = parser.getAttributeValue("", "network");
String username = parser.getAttributeValue("", "username");
String password = parser.getAttributeValue("", "password");
String port = parser.getAttributeValue("", "port");
String preference = parser.getAttributeValue("", "preference");
String proto = parser.getAttributeValue("", "proto");
if (channel != null) {
mt.setChannel(new TransportCandidate.Channel(channel));
}
if (generation != null) {
try {
mt.setGeneration(Integer.parseInt(generation));
} catch (Exception e) {
}
}
if (ip != null) {
mt.setIp(ip);
} else {
return null;
}
if (name != null) {
mt.setName(name);
}
if (network != null) {
try {
mt.setNetwork(Integer.parseInt(network));
} catch (Exception e) {
}
}
if (username != null) {
mt.setUsername(username);
}
if (password != null) {
mt.setPassword(password);
}
if (port != null) {
try {
mt.setPort(Integer.parseInt(port));
} catch (Exception e) {
}
}
if (preference != null) {
try {
mt.setPreference(Integer.parseInt(preference));
} catch (Exception e) {
}
}
if (proto != null) {
mt.setProto(new TransportCandidate.Protocol(proto));
}
return new JingleTransport.Ice.Candidate(mt);
}
}
/**
* Raw UDP profile
*/
public static class RawUdp extends JingleTransportProvider {
/**
* Defauls constructor.
*/
public RawUdp() {
super();
}
/**
* Obtain the corresponding TransportNegotiator.RawUdp instance.
*
* @return a new TransportNegotiator.RawUdp instance
*/
protected JingleTransport getInstance() {
return new JingleTransport.RawUdp();
}
/**
* Parse a iq/jingle/transport/candidate element.
*
* @param parser the structure to parse
* @return a candidate element
* @throws Exception
*/
protected JingleTransportCandidate parseCandidate(XmlPullParser parser) throws Exception {
TransportCandidate.Fixed mt = new TransportCandidate.Fixed();
String generation = parser.getAttributeValue("", "generation");
String ip = parser.getAttributeValue("", "ip");
String name = parser.getAttributeValue("", "name");
String port = parser.getAttributeValue("", "port");
//System.out.println();
if (generation != null) {
try {
mt.setGeneration(Integer.parseInt(generation));
} catch (Exception e) {
}
}
if (ip != null) {
mt.setIp(ip);
}
if (name != null) {
mt.setName(name);
}
if (port != null) {
try {
mt.setPort(Integer.parseInt(port));
} catch (Exception e) {
}
}
return new JingleTransport.RawUdp.Candidate(mt);
}
}
}

View file

@ -0,0 +1 @@
<body>Provides pluggable parsing logic for Smack extensions.</body>